Spring学习笔记(7)——Bean的基本配置

        先从IOC说起,这个概念其实是从我们平常new一个对象的对立面来说的,我们平常使用对象的时候,一般都是直接使用关键字类new一个对象,那这样有什么坏处呢?其实很显然的,使用new那么就表示当前模块已经不知不觉的和new的对象耦合了,而我们通常都是更高层次的抽象模块调用底层的实现模块,这样也就产生了模块依赖于具体的实现,这样与我们JAVA中提倡的面向接口面向抽象编程是相冲突的,而且这样做也带来系统的模块架构问题。很简单的例子,我们在进行数据库操作的时候,总是业务层调用DAO层,当然我们的DAO一般都是会采用接口开发,这在一定程度上满足了松耦合,使业务逻辑层不依赖于具体的数据库DAO层。但是我们在使用的时候还是会new一个特定数据库的DAO层,这无形中也与特定的数据库绑定了,虽然我们可以使用抽象工厂模式来获取DAO实现类,但除非我们一次性把所有数据库的DAO写出来,否则在进行数据库迁移的时候我们还是得修改DAO工厂类。 

        那我们使用IOC能达到什么呢?IOC,就是DAO接口的实现不再是业务逻辑层调用工厂类去获取,而是通过容器(比如spring)来自动的为我们的业务层设置DAO的实现类。这样整个过程就反过来,以前是我们业务层主动去获取DAO,而现在是DAO主动被设置到业务逻辑层中来了,这也就是反转控制的由来。通过IOC,我们就可以在不修改任何代码的情况下,无缝的实现数据库的换库迁移,当然前提还是必须得写一个实现特定数据库的DAO。我们把DAO普遍到更多的情况下,那么IOC就为我们带来更大的方便性,比如一个接口的多个实现,我们只需要配置一下就ok了,而不需要再一个个的写工厂来来获取了。这就是IOC为我们带来的模块的松耦合和应用的便利性。 

    那为什么说IOC很简单呢?说白了其实就是由我们平常的new转成了使用反射来获取类的实例,相信任何人只要会用java的反射机制,那么自己写一个IOC框架也不是不可能的。比如: 

…… 
public Object getInstance(String className) throws Exception 
{ 
    Object obj = Class.forName(className).newInstance(); 
    Method[] methods = obj.getClass().getMethods(); 
    for (Method method : methods) { 
        if (method.getName().intern() == "setString") { 
            method.invoke(obj, "hello world!"); 
        } 
    } 
} 
…… 

    上面的一个方法我们就很简单的使用了反射为指定的类的setString方法来设置一个hello world!字符串。其实可以看到IOC真的很简单,当然了IOC简单并不表示spring的IOC就简单,spring的IOC的功能强大就在于有一系列非常强大的配置文件维护类,它们可以维护spring配置文件中的各个类的关系,这才是spring的IOC真正强大的地方。在spring的Bean定义文件中,不仅可以为定义Bean设置属性,还支持Bean之间的继承、Bean的抽象和不同的获取方式等等功能。 

在spring的Bean配置中总的来说其实就一个标签<bean></bean>,这个bean标签就攘括了几乎所有的配置,然后bean的继承、抽象等都是基于此标签之上的,掌握了bean的配置,详细可以使自己有一个比较大的提升,尤其是对于新手来说(我也是,呵呵  )。最基础的bean配置如下: 

<bean id="bean_test" class="cn.qtone.test.HelloWorld"></bean> 

    这里我们就简单的使用HelloWorld类来实例化,使用默认的构造方法,即相当于我们使用: 

HelloWorld tmp = new HelloWorld(); 

    但有一点不同的是在spring配置中的在整个应用期间只有一个实例,即是单例的,当然这个单例是指对一个IOC容器(spring)来说的,而不是我们通常说说的单态模式。当然,spring也可以这样配置不是单态的实例,比如我们修改如下: 

<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype"></bean> 

    注意其中的不同颜色部分,这样配置后就表明每次从spring容器中获取HelloWorld的实例的时候就会new一个新对象,即我们所说的原型,spring中scope默认的是单态(singleton),当然针对web应用程序,还可以配置为request、session等范围。至于什么时候使用什么权限范围就要看应用程序的使用了,比如在多线程程序中,单态是否会对程序有所影响就需要考虑了。 

    如果HelloWorld类没有空的构造方法,只有如下的两个构造方法,那我们该如何配置呢? 
…… 
public HelloWorld(String str) 
{ 
    …… 
} 

public HelloWorld(Date date, int i) 
{ 
    …… 
} 
…… 

    由于没有默认构造方法,所以我们就需要在bean的配置中写上构造参数才可以,如下: 
<!-- 使用一个参数的构造 -->
<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype"> 
    <constructor-arg><value>hello</value></constructor-arg>  
</bean> 

    上面说的使用一个参数的,即带字符串参数的构造方法,如果想使用带日期和整型的构造方法,那么就要做如下的配置了: 

<bean id="bean_date" class="java.util.Date" /> 
<!-- 使用二个参数的构造 -->
<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype"> 
    <constructor-arg ref="bean_date"></constructor-arg>  
    <constructor-arg><value>345</value></constructor-arg>  
</bean> 

    注意到上面的配置中我们使用了ref关键字,这个是表示引用配置文件中的ID为bean_date的对象,另外对于类型,spring会做恰当的转换,比如将345转换成数字等。当然,这样对简单的构造来说不会有什么问题,如果情况比较复杂的话,那么一般建议使用序号来标定,如下: 

<!-- 使用二个参数的构造 -->
<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype"> 
    <constructor-arg index="0" ref="bean_date"></constructor-arg>  
    <constructor-arg index="1"><value>345</value></constructor-arg>  
</bean> 

    这样,使用index属性表示该参数所在位置了后,无论是spring构造起来,还是我们查看都会很方便。当然,spring也提供了自动查找,也就是依赖查找的功能,但是这个我觉得大家还是少用,因为这样会使整个配置文件看起来非常的不直观,而且不清晰,说不定过了一段时间再去看的时候就不知道是什么意思了,在正式应用项目中,还是将各个bean的关系进行组织和编写清楚为好。 

    上面所说的都是构造来实例化一个bean,但有时候我们都会使用工厂模式来获取bean。对于工厂模式,我们一般也使用静态工厂模式和实例工厂模式,这两个在spring中配置也是不太一样的。对于静态工厂模式来实例化bean的,我们的配置如下: 
<bean id="bean_string" class="cn.qtone.test.TestFactory" factory-method="getBean"/> 

    当然,我们也可以为静态工厂模式的bean来使用构造参数,这个就不说了。我们上面的bean配置对应的实际代码就应该是: 
…… 
public static Object getBean() 
{ 
    return "hello world"; 
} 
…… 

    那么spring在实例化ID为bean_string的bean时,就会使用TestFactory的getBean()方法来获取,而且TestFactory是没有被实例化的,即是使用静态方法来获取的。对于实例工厂模式的话,我们的配置和上面就稍微有点不一样了,那我们就应该配置两个bean, 一个是实例的工厂bean,还一个就是我们要获取的bean的配置了,如下: 

<bean id="bean_factory" class="cn.qtone.test.TestBeanFactory"/> 
<bean id="bean_helloWorld" factory-bean="bean_factory" factory-method="getHello"/> 

    上面的配置中,spring容器将首先实例化一个TestBeanFactory,然后再根据该类的方法getHello来获取一个bean的实例,我们这里以HelloWorld对象为例,对应的代码就应该如下: 

…… 
public HelloWorld getHello() { 
    return new HelloWorld(); 
} 
…… 

    注意到,我们这里的getHello方法并不是静态方法,而是实例方法,所以必须先实例化TestBeanFactory后才能够调用。 

    上面说的都是如何去实例化一个bean,没有说到bean的属性注入。虽然我们也可以通过构造的时候进行一次注入,但这样做不仅失去了灵活性,而且一长串的构造参数看着也眼疼哈,呵呵。当然,有一种情况下,我们是应该使用构造注入的,就是希望注入的对象不能够被外界修改时,我们这时候就必须使用构造注入了。对于bean的属性注入,以HelloWorld为例,我们可以简单的配置如下: 

<bean id="bean_test" class="cn.qtone.test.HelloWorld"> 
    <property name="hello" value="你好!" /> 
    <property name="world" value="世界" /> 
    <property name="date" ref="bean_date" /> 
</bean> 

    上面的配置中使用了三个属性注入,即spring中的setter注入方式。注意第三个属性,使用了ref,表明这个date属性的设置参数是关联到ID为bean_date的bean上去的。注意在使用setter注入的时候,属性的名称不是方法的全名称,而是满足javaBean规范的命名方式,即如果属性名称为xxx,那么其对应的方法名称就应该是:setXxx(),注意的是除了xxx中第一个字符不区分大小写之外,其他的是要严格区分的。那么对照上面的配置,我们的HelloWorld的方法就应该如下: 

…… 
public void setHello(String hello) 
{ 
    …… 
} 

public void setWorld(String world) 
{ 
    …… 
} 

public void setDate(Date date) 
{ 
    …… 
} 
…… 

    使用setter注入的好处就是可以很清晰的知道每一个注入的是什么参数和意义,通过名称就可以很容易的看出来,这也是spring提倡使用setter注入的原因之一。

Spring的配置文件的基本元素介绍:

<beans>

Spring配置文件的根元素,包含一个或多个bean元素。

default-autowire属性:默认的bean自动装配模式。可选5种模式。

no:不使用自动装配。Bean的引用必须通过ref元素定义。

byName:通过属性名字进行自动装配。

byType:如果BeanFactory中正好有一个同属性类型一样的bean,就自动装配这个属性。如果有多于一个这样的bean,就抛出一个致命异常,它指出你可能不能对那个bean使用byType的自动装配。如果没有匹配的bean,则什么都不会发生,属性不会被设置。如果这是你不想要的情况(什么都不发生),通过设置dependency-check="objects"属性值来指定在这种情况下应该抛出错误。

constructor:这个同byType类似,不过是应用于构造函数的参数。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则一个致命的错误会产生。

autodetect:通过对bean 检查类的内部来选择constructorbyType。如果找到一个缺省的构造函数,那么就会应用byType

default-dependency-check属性:默认的依赖检查模式。可选四种。

none :不进行依赖检查。没有指定值的bean属性仅仅是没有设值。

Simple:对基本类型和集合(除了合作者外,比如其他的bean,所有东西)进行依赖检查。

Object:对合作者进行依赖检查。

all :对合作者,基本类型和集合都进行依赖检查。

default-lazy-init属性:默认延迟加载。True False 

<bean>

用于告诉Spring容器一个类以及它是如何配置的。

class属性Java Bean 类名。

id属性Java BeanBeanFactory中的唯一标识,代码中通过BeanFactory获取JavaBean实例时需以此作为索引名称。

name属性:同上,如果给bean增加别名,可以通过name属性指定一个或多个id

singleton属性:指定此Java Bean是否采用单例(Singleton)模式,如果设为“true”,则在BeanFactory作用范围内,只维护此Java Bean的一个实例,代码通过BeanFactory获得此Java Bean实例的引用。反之,如果设为“false”,则通过BeanFactory获取此Java Bean实例时,BeanFactory每次都将创建一个新的实例返回。

abstract属性:设定ApplicationContext是否对bean进行预先的初始化。

parent属性:定义一个模板。

autowire属性bean自动装配模式。可选5种模式。

no:不使用自动装配。Bean的引用必须通过ref元素定义。

byName:通过属性名字进行自动装配。

byType:如果BeanFactory中正好有一个同属性类型一样的bean,就自动装配这个属性。如果有多于一个这样的bean,就抛出一个致命异常,它指出你可能不能对那个bean使用byType的自动装配。如果没有匹配的bean,则什么都不会发生,属性不会被设置。如果这是你不想要的情况(什么都不发生),通过设置dependency-check="objects"属性值来指定在这种情况下应该抛出错误。

constructor:这个同byType类似,不过是应用于构造函数的参数。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则一个致命的错误会产生。

autodetect:通过对bean 检查类的内部来选择constructorbyType。如果找到一个缺省的构造函数,那么就会应用byType

dependency-check属性:依赖检查模式。可选四种。

none :不进行依赖检查。没有指定值的bean属性仅仅是没有设值。

Simple:对基本类型和集合(除了合作者外,比如其他的bean,所有东西)进行依赖检查。

Object:对合作者进行依赖检查。

all :对合作者,基本类型和集合都进行依赖检查。

lazy-init属性:延迟加载。True False 

init-method属性:初始化方法,此方法将在BeanFactory创建JavaBean实例之后,在向应用层返回引用之前执行。一般用于一些资源的初始化工作。

destroy-method属性:销毁方法。此方法将在BeanFactory销毁的时候执行,一般用于资源释放。

factory-bean属性:通过实例工厂方法创建beanclass属性必须为空,factory-bean属性必

须指定一个bean的名字,这个bean一定要在当前的bean工厂或者父bean工厂中,并包含工厂方法。而工厂方法本身通过factory-method属性设置。

factory-method属性:设定工厂类的工厂方法。

depends-on属性Bean依赖关系。一般情况下无需设定。Spring会根据情况组织各个依赖关系的构建工作。只有某些特殊情况下,如JavaBean中的某些静态变量需要进行初始化(这是一种BadSmell,应该在设计上应该避免)。通过depends-on指定其依赖关系可保证在此Bean加载之前,首先对depends-on所指定的资源进行加载。

----------------------------------------------------------------------------------------------

 

Spring Bean 配置说明

<property>

用于设置一个属性。

name属性:属性的名称。

value属性:指定bean的属性值。BeanFactory将自动根据Java Bean对应的属性类型加以匹配。如果需要将属性值设定为null,必须使用<null/>节点。

ref属性:指定了属性对BeanFactory中其他Bean的引用关系。

 

<value>

指定bean的属性值。

 

<ref>

指定了属性对BeanFactory中其他Bean的引用关系。

bean属性:指定了属性对BeanFactory中其他Bean的引用关系。

local属性:指定了属性对BeanFactory中其他Bean的引用关系。(仅在本地(同一个)xml文件里寻找bean

parent属性:指定了属性对BeanFactory中其他Bean模板的引用关系。

 

<list>

指定bean的属性类型为List的属性值。

 

<map>

指定bean的属性类型为List的属性值。

 

<set>

指定bean的属性类型为List的属性值。

 

<props>

指定bean的属性类型为Properties的属性值。

 

<prop>

key属性:指定Properties的键

 

<idref>

用来设置属性值为容器中其他beanid name

 

<null>

指定一个空值。

 

<constructor-arg>

使用构造方法注入,指定构造方法的参数。

index属性:设置参数的序号。

ref属性:同ref

type属性:参数类型。

value属性:参数的值。

 

<lookup-method>

lookup方法注入

bean属性:要注入的bean

name属性:要注入的方法名称

 

<replaced-method>

用来把已存在的方法实现替换为其他的实现。

name属性:要替换的方法名

replacer属性:替换者类,实现org.springframework.beans.factory.support.MethodReplacer接口)

 

<arg-type>

方法返回的类型

 

 

        一、Bean的定义

        <beans…/>元素是Spring配置文件的根元素,<bean…/>元素师<beans../>元素的子元素,<beans…/>元素可以包含多个<bean…/>子元素,每个<bean…/>元素可以定义一个Bean实例,每一个Bean对应Spring容器里的一个Java实例定义Bean时通常需要指定两个属性。

         Id:确定该Bean的唯一标识符,容器对Bean管理、访问、以及该Bean的依赖关系,都通过该属性完成。Beanid属性在Spring容器中是唯一的。    

        Class:指定该Bean的具体实现类。注意这里不能使接口。通常情况下,Spring会直接使用new关键字创建该Bean的实例,因此,这里必须提供Bean实现类的类名。

        下面是定义一个Bean的简单配置

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns="http://www.springframework.org/schema/beans"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    <!-- 定义第一个Bean实例:bean1 -->  
    <bean id="bean1" class="com.Bean1" />  
      
    <!-- 定义第二个Bean实例:bean2 -->  
    <bean id="bean2" class="com.Bean2" />  
      
</bean> 

  

        Spring容器集中管理Bean的实例化,Bean实例可以通过BeanFactorygetBean(Stringbeanid)方法得到。BeanFactory是一个工厂,程序只需要获取BeanFactory引用,即可获得Spring容器管理全部实例的引用。程序不需要与具体实例的实现过程耦合。大部分Java EE应用里,应用在启动时,会自动创建Spring容器,组件之间直接以依赖注入的方式耦合,甚至无须主动访问Spring容器本身。

        当我们在配置文件中通过<bean id=”xxxx” class=”xx.XxClass”/>方法配置一个Bean时,这样就需要该Bean实现类中必须有一个无参构造器。故Spring底层相当于调用了如下代码:

 

Xxx = new xx.XxClass()  

 

 
        如果在配置文件中通过构造注入来创建Bean

 

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns="http://www.springframework.org/schema/beans"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    
    <bean id="bean1" class="com.Bean1">  
        <constructor-arg value="chenssy"/>  
        <constructor-arg value="35-354"/>  
    </bean>  
      
</beans> 

         

Spring相当于调用如下代码:

 

Bean bean = new com.Test("chenssy","35-354");

 

 

        除了可以为<bean…/>元素指定一个id属性外,还可以为<bean…/>元素指定name属性,用于为Bean实例指定别名。如果需要为Bean实例指定多个别名,可以在name属性中使用逗号、冒号或者空格来分隔多个别名,后面通过任一别名即可访问该Bean实例。但是在一些特殊的情况下,程序无法在定义Bean时就指定所有的别名,而是在其他地方为一个已经存在的Bean实例指定别名,则可以使用<alias…/>元素来完成,该元素有如下两个属性:

        name:该属性指定一个Bean实例的标识名,表示将会为该Bean指定别名。

        alias:指定一个别名.

        如:

 

<alias name=”bean1” alias=”name1”/>  
<alias name=”bean2” alias=”name2”/>  

 

 

        在默认情况下,当Spring创建ApplicationContext容器时,Spring会自动预初始化容器中所有的singleton实例,如果我们想让Spring容器预初始化某个singleton Bean,则可以为该<bean…/>元素增加lazy-init属性,该属性用于指定该Bean实例的预初始化,如果设置为true,则Spring不会预初始化该Bean实例。

 

<bean id=”person” class=”com.Person” lazy-init=”true”/> 

 

         一、       容器中Bean的作用域    当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。

        Spring支持5种作用域:

           Singleton:单例模式。在整个SpringIoC容器中,使用singleton定义的Bean将只有一个实例。

           Prototype:原型模式。每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。

           request:对于每次HTTP请求,使用request定义的Bean都将产生一个新的实例,即每次HTTP请求都会产生不同的Bean实例。当然只有在WEB应用中使用Spring时,该作用域才真正有效。

        session:对于每次HTTPSession,使用session定义的Bean都将产生一个新的实例时,即每次HTTP Session都将产生不同的Bean实例。同HTTP一样,只有在WEB应用才会有效。

        global session:每个全局的HTTPSession对应一个Bean实例。仅在portlet Context的时候才有效。

       

        比较常用的singletonprototype。如果一个Bean实例被设置为singleton,那么每次请求该Bean时都会获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为。如果一个Bean实例被设置为prototype,那么每次请求该diBeanSpring都会创建一个新的Bean实例返回给程序,在这种情况下,Spring容器仅仅使用new关键字创建Bean实例,一旦创建成功,容器将不会再跟踪实例,也不会维护Bean实例的状态。

        如果我们不指定Bean的作用域,则Spring会默认使用singleton作用域。

        Java在创建Java实例时,需要进行内存申请。销毁实例时,需要完成垃圾回收。这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价会比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean的作用域设置为prototype

        设置Bean的作用域是通过scope属性来指定。可以接受Singletonprototyperequestsessionglobal session 5个值。

 

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns="http://www.springframework.org/schema/beans"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    <!-- 配置一个singleton Bean实例:默认 -->  
    <bean id="bean1" class="com.Bean1" />  
    <!-- 配置一个prototype Bean实例 -->  
    <bean id="bean2" class="com.Bean2" scope="prototype"/>  
      
</beans> 

  

        上面的配置,对于bean1没有指定scope属性,则默认使用singleton,而bean2则指定一个prototype

        测试代码:

 

public class SpringTest {  
  
    public static void main(String[] args) {  
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");  
        //判断两次请求singleton作用域的Bean实例是否相等  
        System.out.println(ctx.getBean("bean1")==ctx.getBean("bean1"));  
        //判断两次请求prototype作用域的Bean实例是否相等  
        System.out.println(ctx.getBean("bean2")==ctx.getBean("bean2"));  
    }  
  
}

 

   程序运行结果如下:

     true

     false

        从上面的运行结果可以看出:对于singleton作用域的Bean,每次请求该idBean时都将返回同一个Bean实例,但是prototype返回的都是一个新的Bean实例,每次请求返回的Bean实例都将不同。

        对于request作用域而言,先看如下Bean实例定义:

 

<bean id=”login” class=”com.app.LoginAction” scope=”request”/>

 

 

        对于每次HTTP请求,Spring容器都会根据login Bean定义创建一个全新的LoginAction Bean实例,且该loginAction Bean实例仅在当前HTTP Request内有效。

        对于session作用域相同。只不过有效范围不同而已。

        requestsession作用域只在web应用中才会有效,并且必须在Web应用中增加额外配置才会生效。为了能够让requestsession两个作用域生效,必须将HTTP请求对象绑定到位该请求提供的服务线程上,这使得具有requestsession作用的Bean实例能够在后面的调用链中被访问到。

        因此我们可以采用两种配置方式:采用Listener配置或者采用Filter配置,在web.xml中。

        Listener配置:

 

<listener>  
    <listener-class>  
        org.springframework.web.context.request.RequestContextListener  
    </listener-class>  
</listener> 

  

        Filter配置

<filter>  
    <filter-name>requestContextFilter</filter-name>  
    <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>  
</filter>  
<filter-mapping>  
    <filter-name>requestContextFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping> 

   

        一旦在web.xml中增加上面两种配置中的一种,程序就可以在Spring配置文件中使用request或者session作用域了。如下:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns="http://www.springframework.org/schema/beans"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    <!-- 指定使用request作用域 -->  
    <bean id="p" class="com.app.Person" scope="request"/>  
      
</beans> 

  

        上面的配置文件配置了一个实现类PersonBean,指定它的作用域为request。这样Spring容器会为每次的HttP请求生成一个Person的实例,当该请求响应结束时,该实例也会被注销。

 

http://blog.csdn.net/u011225629/article/details/45459725

猜你喜欢

转载自itommy.iteye.com/blog/2291880