spring

第一章:Spring简介

第一节:Spring简介

第二节:七大模块

第二章:Ioc

第一节:项目创建

第二节:XML方式注入bean

第三节:注解形式注bean

第三章:Aop

第四章:SSM整合

 

第一章:Spring简介

第一节:Spring简介

  1. Spring是一个开源的控制反转(Inversion of Control,IoC)和面向切面(AOP)的容器框架,它的主要目得是简化企业开发。
  2. 使用Spring来完成bean的构建,使得其他应用层只专注于自己的业务逻辑处理。
  3. Spring框架通过依赖注入DI(Dependency Injection)或者控制反转IoC的方式来管理各个对象

l IoC : 控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转。

l DI : 在运行期,由外部容器动态地将依赖对象注入到组件中。

  1. 特点:轻量级、低侵入式
  2. 核心容器Core:负责Bean的管理和构建,底层使用BeanFactory工厂模式来实现。BeanFactory使用依赖注入的方式提供给组件依赖。
  3. Spring的上下文Context:国际化等,Spring上下文是一个配置文件,主要向框架提供上下文信息。
  4. Spring AOP:面向切面的编程
  5. Spring Dao:它主要和dao层相关联,可以用该结构来管理异常处理和不同数据库供应商抛出的错误信息。其中异常层次结构简化了错误处理,并且极大地降低了需要编写地异常代码数据(例如打开和关闭连接)。
  6. Spring ORM:实体关系映射
  7. Spring Web:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  8. Spring Web MVC

第二节:七大模块

 

第二章:Ioc

第一节:项目创建

  1. 创建Maven项目,不勾选任何模板
  2. pom.xml

<properties>

        <spring.version>4.3.9.RELEASE</spring.version>

    </properties>

    <dependencies>

        <dependency>

            <!--scope为test的话,发布后不发布-->

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>4.11</version>

 

            <scope>test</scope>

        </dependency>

        <!-- Spring -->

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-core</artifactId>

            <version>${spring.version}</version>

        </dependency>

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-context</artifactId>

            <version>${spring.version}</version>

        </dependency>

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-beans</artifactId>

            <version>${spring.version}</version>

        </dependency>

 

        <!-- Spring AOP所需 -->

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-aop</artifactId>

            <version>${spring.version}</version>

        </dependency>

        <dependency>

            <groupId>org.aspectj</groupId>

            <artifactId>aspectjweaver</artifactId>

            <version>1.8.9</version>

        </dependency>

    </dependencies>

 

  1. 编写Spring的配置文件 (XXX.xml,一般叫applictionContext.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <!--属性注入方式:要求有无参的构造方法,以及标准的getset方法-->
    <!--默认构建的bean的单例模式-->

</beans>

 

 

第二节:XML方式注入bean

  1. applicationContext中bean标签

<bean id="p1" class="bean.Person">
        <property name="name" value="张三"></property>
        <property name="age" value="10"></property>
    </bean>
    <!--    构造函数注入方式,要求必须有自定义构造方法-->
    <bean id="p2" class="bean.Person">

        <constructor-arg name="name" value="lisi"></constructor-arg>
        <constructor-arg name="age" value="20"></constructor-arg>
    </bean>
    <bean id="p3" class="bean.Person">
        <constructor-arg index="0" value="wangwu">
        </constructor-arg>
        <constructor-arg name="age" value="19"></constructor-arg>
    </bean>

 

  1. test文件中测试

public class MyTest {
    @Test
    public void test(){
        //建一SpringIOC容器
        ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        Person person = (Person) appContext.getBean("p1");
        System.out.println(person);

        Person person1 = (Person) appContext.getBean("p1");;
        System.out.println(person==person1);

        Person p2 = (Person) appContext.getBean("p2");
        Person p3 = (Person) appContext.getBean("p3");

        System.out.println(p2);
        System.out.println(p3);
    }
}

 

  1. Xml文件中,使用ref引用引用类型

<bean id="car1" class="bean.Car">
    <property name="brand" value="benz"></property>
    <property name="price" value="600000"></property>
</bean>
<bean id="d1" class="bean.Driver">
    <property name="name" value="abc"></property>
    <property name="age" value="40"></property>
    <property name="car" ref="car1"></property>
</bean>

Test中:

Driver d1 = (Driver)appContext.getBean("d1");
System.out.println(d1);

 

  1. 含有List成员的对象

public class DriverListCar extends Person {
    private List<Car> cars;

    public DriverListCar() {
    }

 

<!--    含有List-->
    <bean id="driverListCar" class="bean.DriverListCar">

        <property name="name" value="a"></property>
        <property name="age" value="50"></property>
        <property name="cars" >
            <list>
<!--                引用已有的-->
                <ref bean="car1"></ref>

<!--                造新-->
                <bean class="bean.Car">

                    <property name="brand" value="bmw"></property>
                    <property name="price" value="350000"></property>
                </bean>
            </list>
        </property>
    </bean>

Test:

DriverListCar d1 = (DriverListCar) appContext.getBean("driverListCar");
System.out.println(d1);

 

  1. 含有Map成员的对象

public class DriverMapCar extends Person {
    private Map<String,Car> cars;

    public DriverMapCar() {
    }

Xml文件:

<!--    含有Map-->
    <bean id="driverMapCar" class="bean.DriverMapCar">

    <property name="name" value="b"></property>
    <property name="age" value="50"></property>
    <property name="cars" >
        <map>
<!--            键值对形式-->
            <entry key="car1" value-ref="car1"></entry>

<!--           造一辆车 -->
            <entry key="car2">

                <bean class="bean.Car">
                    <property name="brand" value="ford"></property>
                    <property name="price" value="120000"></property>
                </bean>
            </entry>
        </map>
    </property>
</bean>

Test:

 

DriverMapCar d2 = (DriverMapCar)appContext.getBean("driverMapCar");
System.out.println(d2);

 

  1. bean的属性

1) scope对象的作用域

(1) singleton:单例模式,默认

(2) prototype:原型的,容器初始化时不创建bean,每当请求bean实例时创建一个新的bean。【Controller层的类使用该作用域】

<!--    测试scope-->
     <bean id="person1" class="bean.Person" scope="prototype">

         <property  name="name" value="zhangsan"></property>
         <property name="age" value="29"></property>
     </bean>

@Test
public void testScope(){
    ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person person1 = (Person)appContext.getBean("person1");
    Person person2 = (Person)appContext.getBean("person1");
    System.out.println(person1 == person2);
}

结果为false

2) init-method和destroy-method(Ioc容器关闭回调该属性所描述的方法)

在实体类添加相应的init和destroy方法:

public  void init(){
    System.out.println("init");
}

public void destroy(){
    System.out.println("destroy");
}

 

在xml文件中设置单例模式,并指明init-method和destroy-method所对应的方法:

<bean id="person1" class="bean.Person" init-method="init" destroy-method="destroy" >
    <property  name="name" value="zhangsan"></property>
    <property name="age" value="29"></property>
</bean>

 

Test:

ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person1 = (Person) appContext.getBean("person1");
Person person2 = (Person) appContext.getBean("person1");
appContext.close();

单例模式时的输出:

 

Scope为Prototype时:

<bean id="person1" class="bean.Person" init-method="init" destroy-method="destroy" scope="prototype">
    <property  name="name" value="zhangsan"></property>
    <property name="age" value="29"></property>
</bean>

Test不变,结果为:Person构造方法,setName和init都调用了两次,但是destroy方法没有调用。

 

 

3) autowire属性:自动装配属性,一个对象的属性为另一个对象时,可以通过自动autowire进行自动装配,而不需要再指明此对象的引用了

可以byName,byType

ByName形式:

实体类:

private String name;
private String gender;
private Integer age;
private Car car;//实体类属性名称和xml相应对象的id一致,才能装配
private Book book;

 

XML文件

<bean id="book" class="com.iss.entity.Book">
    <property name="name" value="java"></property>
    <property name="anthor" value="a"></property>
</bean>
<bean id="car" class="com.iss.entity.Car">
    <property name="price" value="90"></property>
    <property name="brand" value="benz"></property>
</bean>

<bean id="p" class="com.iss.entity.Person" autowire="byName" >
    <property name="name" value="zhangsan"></property>
    <property name="gender" value="nan"></property>
</bean>

 

Test:

@Test
public void test() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
    Person p = (Person) context.getBean("p");
    System.out.println(p);

    context.close();
}

 

byType形式:找相应类型进行装配,如果都多个相同类型的bean,则会抛异常,实体类不变。

<bean id="book1" class="com.iss.entity.Book">
    <property name="name" value="java"></property>
    <property name="anthor" value="a"></property>
</bean>
<bean id="car1" class="com.iss.entity.Car">
    <property name="price" value="90"></property>
    <property name="brand" value="benz"></property>
</bean>
<!--实体类属性名称和相应对象id不同时,用byType也能装配-->
<bean id="p" class="com.iss.entity.Person" autowire="byType" >
    <property name="name" value="zhangsan"></property>
    <property name="gender" value="nan"></property>
</bean>

 

  1. bean的后置处理器:

如果需要在Spring容器中完成bean初始化方法前后添加额外的逻辑处理,可以定义一个或者多个 BeanPostProcessor 的派生类来完成,然后将该类注册到Spring的IOC容器中。

定义一个类,继承BeanPostProcessor

public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException {
        System.out.println("before:"+bean);
return bean;


    }

    public Object postProcessAfterInitialization(Object bean, String s) throws BeansException {

System.out.println("after:"+bean);

        return bean;
    }
}

在xml文件中配置后置处理器:

<!--BeanPostProcessor后置理器配置-->
    <bean class="processor.MyBeanPostProcessor"></bean>

输出结果:

 

 

第三节:注解形式注入bean

  1. 常用注解:Spring的配置文件中要加入自动进行组件扫描的配置【告诉Spring将哪些包下有注解的类完成bean的初始化】

<!-- 包名 可以描述组件所在的共同包部分,多个包可以用逗号分隔 -->

<context:component-scan  base-package="包名 , ..."></context:component-scan>

常用的注解

(1) @Component : 普通类 Student  com.isoft.bean , com.isoft.entity

(2) @Controller :  控制层 Servlet SpringMVC 等,com.isoft.servlet , com.isoft.controller

(3) @Service 业务逻辑处理层,com.isoft.service

(4) @Respository :   持久层com.isoft.dao

以上注解都有value属性,当不适用value属性时,以上类创建的对象名或者beanId名将是,类名首字母小写作为对象名

(5) @Scope("prototype") :<bean scope="?">

(6) @Autowired:注解在成员变量上,默认按照类型匹配

通常情况下@Autowired是通过byType的方法注入的,可是在多个实现类的时候,byType的方式不再是唯一,而需要通过byName的方式来注入,而这个name默认就是根据变量名来的。

(7) @Qualified

(8) @PostConstruct:相当于<bean init-method="?">注解在方法上

(9) @PreDestroy: 相当于<bean destroy-method="?">    注解在方法上

  1. Spring配置文件中添加包扫描,在applicationContext.xml

<!--配置自动扫描的包-->
    <context:component-scan base-package="com.iss"></context:component-scan>

 

  1. @Scope("prototype"):默认是单例模式,如果需要多个对象,添加此注解,但是如果多个对象,会使含有次对象的自动注入的其他对象,得不到这个参数。

实体类:

@Component(value = "car")
@Scope(value = "prototype")
public class Car implements Serializable {
    private String brand;
    private double price;

Service:

@Service
public class CarService implements CarDao {
    @Autowired
    private Car car;

 

 

Test:ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Car car1 = (Car)context.getBean("car");
car1.setBrand("benz");
car1.setPrice(100000);
Car c2 = (Car)context.getBean("car");
c2.setBrand("bmw");
c2.setPrice(200000);
System.out.println(car1);
System.out.println(c2);
System.out.println(car1==c2);
CarService carService = (CarService) context.getBean("carService");
System.out.println("service car = "+carService.getCar());

运行结果:

 

 

  1. @Autowired:在junit中不管用,需要在一个非测试类中使用才有效

1) @Autowired@Resource的区别

@Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配

@Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

  1. 当使用@AutoWired注解时,如果找到两个或多个可匹配的对象,则可以使用

Person类:

private String name;
private String gender;
private Integer age;
@Autowired
private Car car;
@Autowired
private Book book;

@Component
public class Car {
    private String brand;
    private Integer price;

 

@Component
public class Book {
    private String name;
    private String anthor;

 

Test

@Test
public void test() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
    Person p = (Person) context.getBean(Person.class);
    System.out.println(p);
    context.close();
}

  1. @ Qualifier指明要自动装配的对象:当有一个父类,多个子类时,如果用父类对象声明,而子类对象指明了name时,使用@AutoWried注解会报错,当子类对象没有指明name时,会自动装配父类对象,如果想装配某个子类对象,可以用@Qualified指明对应的name:

父类:

@Component(value = "book")
public class Book {
    private String name;
    private String anthor;

 

子类:

@Component(value = "childrenBook")
public class ChildrenBook extends Book {
    public void print(){
        System.out.println("childrenBook");
    }
}

@Component
public class Person {
    private String name;
    private String gender;
    private Integer age;
    @Autowired
    private Car car;
    @Autowired
    @Qualifier(value = "childrenBook")
    private Book book;
}

Test

@Test
public void test() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
    Person p = (Person) context.getBean(Person.class);
    p.getBook().print();
    context.close();
}

 

结果:

 

 

  1. @PostConstruct和@PreDestroy,注意:prototype时,不用调用destroy方法

@PostConstruct
public void init() {
    System.out.println("car init");
}

@PreDestroy
public void destroy() {
    System.out.println("car destroy");
}

 

第三章:AOP

  1. 简介 AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

底层实现是使用代理模式完成。

使用 :事务处理、日志、权限认证等。

  1. AOP 是一种编程典范,它通过分离横切关注点来增加程序的模块化。简单说就是 AOP 可以在不修改现有代码的情况下对现有代码增加一些功能,那么这就是 AOP 最强大的功能。目前最受欢迎的 AOP 库有两个,一个是 AspectJ, 另外一个就是Spring AOP。

Spring框架的AOP机制可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。

 

  1. 相关术语

1) 横切关注点:对哪个类的哪个方法进行拦截,拦截后执行什么逻辑,这些关注点称为横切关注点。

2) 切面(Aspect):对横切关注点的抽象 -- 切面类定义 + 拦截谁 + 拦截后做什么

3) 连接点(Joinpoint):[被拦截到的方法]

4) 切点(pointcut):[拦截谁--方法] :需要配置切点表达式,定义拦截谁 --- 配置拦截哪个类的哪个方法

5) 通知(Advice):[拦截后做什么] :  拦截到指定方法后在什么位置执行什么逻辑处理

  1. AOP的实现-XML形式

1) 步骤

(1) 明确拦截谁(哪个类的那个方法|哪个类的哪些方法|哪些类的哪些方法),拦截方法的什么地方,拦截方法之后做什么。

(2) 定义拦截器类(切面类),普通类定义,加配置成为拦截器类,描述的是通知(方法),包括前置通知,后置通知,返回通知,异常通知,环绕通知。

(3) 配置:

切点表达式PointCut拦截谁

切面类bean生成

l 所要拦截的方法被调用了要在什么位置执行切面类的哪个方法

2) 编写需要拦截的类

@Service
public class OprService {
    public int add(int n1,int n2){
        System.out.println("Call addInt");
        return  n1+n2;
    }

    public double add(double n1,double n2){
        System.out.println("Call addDouble");
        return  n1+n2;
    }
    public boolean fun(String info) throws NullPointerException{
        System.out.println("Call fun");
        if(info == null || info.trim().length()==0){
            throw  new NullPointerException("参数为");
        }
        return true;
    }
}

 

3) 编写切面类,前置通知,后置通知,返回通知,异常通知

@Component(value = "aspect1")
public class Aspect1 {
    public Aspect1(){}

    public void before(JoinPoint point) {
        System.out.println("AOP : before");
        getHandler(point);
    }

    public void after(JoinPoint point) {
        System.out.println("AOP : after");
        getHandler(point);
    }

    //参数列表的JoinPoint,Object序不能倒,第一个参数JoinPoint
    public void afterRetuning(JoinPoint point,Object o) {

        System.out.println("AOP : afterRetuning");
        getHandler(point);
        System.out.println("返回" + o);
    }
a
    public void exp(JoinPoint point,Exception e) {
        System.out.println("AOP : exp");
        getHandler(point);
        System.out.println("常信息" + e);
    }

    private void getHandler(JoinPoint point) {
        Object[] params = point.getArgs();//截到的方法的参数列表
        String methodName = point.getSignature().getName();//取所截方法的名字
        Class clazz = point.getTarget().getClass();//截方法所
        System.out.println("截到了:" + clazz.getPackage() + "." + clazz.getSimpleName() + "." + methodName);

        System.out.println("方法的参数列表:");
        for (Object o : params) {
            System.out.println(o);
        }
    }
}

 

4) 环绕通知

/**
 * 环绕通知
 */
@Component

public class Aspect2 {
    public Object around(ProceedingJoinPoint point) {
        Object result = null;
        try {
            System.out.println("处写前置通知");
            result = point.proceed();
            System.out.println("处写后置通知");
        } catch (Throwable e) {
            System.out.println("处写异常通知");
            return null;
        }
        System.out.println("处写返回通知:"+result);
        return result;
    }
}

 

 

5) Xml文件中设置关于AOP的配置信息

<!--配置自动扫描的包-->
    <context:component-scan base-package="com.iss"></context:component-scan>


    <aop:config>
<!--        *通配符表示任意返回值类-->
        <aop:pointcut id="oprservice.add" expression="execution(* com.iss.service.OprService.add(..))"/>

        <aop:pointcut id="oprservice.fun" expression="execution(public boolean com.iss.service.OprService.fun(..))"/>
<!--        配置切面-->
        <aop:aspect ref="aspect1" order="1">

            <aop:before method="before" pointcut-ref="oprservice.add"></aop:before>
            <aop:after method="after" pointcut-ref="oprservice.add"></aop:after>
            <aop:after-returning method="afterRetuning" pointcut-ref="oprservice.fun" returning="o"></aop:after-returning>
            <aop:after-throwing method="exp" pointcut-ref="oprservice.fun" throwing="e"></aop:after-throwing>
        </aop:aspect>

<!--        环绕通知配置-->
        <aop:aspect ref="aspect2" order="2">

            <aop:around method="around" pointcut-ref="oprservice.fun"></aop:around>
        </aop:aspect>
</aop:config>

 

6) 测试:

  @Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        OprService oprService = (OprService) context.getBean("oprService");
        int add1 = oprService.add(10, 20);
        System.out.println("add result :" + add1);
//返回通知
        boolean r = oprService.fun("hello");

        System.out.println("r=" + r);
        try {
            boolean r1 = oprService.fun("");
        } catch (Exception e) {

        }

 

  1. AOP的实现-注解形式

1) 同上,删除xml文件中关于aop的配置,并添加如下代码

Xml文件中,加上自动扫描切面包

<!--动扫描切面-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

2) 在切面类里添加注解,不用改代码,只需要加入关于切面的注解

@Component(value = "aspect1")
@Aspect
@Order(value = 1)
public class Aspect1 {

    //置切点
    @Pointcut("execution(* com.iss.service.OprService.add(..))")

    public void proinCutAdd(){

    }

    @Pointcut("execution(boolean com.iss.service.OprService.fun(..))")
    public void proinCutFun(){

    }
    public Aspect1(){}

    @Before(value = "proinCutAdd()")
    public void before(JoinPoint point) {
        System.out.println("AOP : before");
        getHandler(point);
    }

    @After(value = "proinCutAdd()")
    public void after(JoinPoint point) {
        System.out.println("AOP : after");
        getHandler(point);
    }

    //参数列表的JoinPoint,Object序不能倒,第一个参数JoinPoint
    @AfterReturning(value = "proinCutFun()",returning = "o")

    public void afterRetuning(JoinPoint point,Object o) {
        System.out.println("AOP : afterRetuning");
        getHandler(point);
        System.out.println("返回" + o);
    }

    @AfterThrowing(value = "proinCutFun()",throwing = "e")
    public void exp(JoinPoint point,Exception e) {
        System.out.println("AOP : exp");
        getHandler(point);
        System.out.println("常信息" + e);
    }

    private void getHandler(JoinPoint point) {
        Object[] params = point.getArgs();//截到的方法的参数列表
        String methodName = point.getSignature().getName();//取所截方法的名字
        Class clazz = point.getTarget().getClass();//截方法所
        System.out.println("截到了:" + clazz.getPackage() + "." + clazz.getSimpleName() + "." + methodName);

        System.out.println("方法的参数列表:");
        for (Object o : params) {
            System.out.println(o);
        }
    }
}

 

3) 测试:

@Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        OprService oprService = (OprService) context.getBean("oprService");
        int add1 = oprService.add(10, 20);
        System.out.println("add result :" + add1);
//返回通知
        boolean r = oprService.fun("hello");

        System.out.println("r=" + r);
        try {
            boolean r1 = oprService.fun("");
        } catch (Exception e) {

}

 

4) 环绕切面类

/**
 * 环绕通知
 */
@Component

@Aspect
@Order(value = 2)
public class Aspect2 {
    @Pointcut("execution(boolean com.iss.service.OprService.fun(..))")
    public void proinCutFun(){

    }
    @Around(value = "proinCutFun()")
    public Object around(ProceedingJoinPoint point) {
        Object result = null;
        try {
            System.out.println("处写前置通知");
            result = point.proceed();
            System.out.println("处写后置通知");
        } catch (Throwable e) {
            System.out.println("处写异常通知");
            return null;
        }
        System.out.println("处写返回通知:"+result);
        return result;
    }
}

 

posted @ 2021-05-12 20:09  YangYuJia  阅读(7)  评论(0)    收藏  举报