Spring内核研究-管理bean的声明周期一(InitializingBean和init-method)

来源:互联网 发布:石家庄seo公司 编辑:程序博客网 时间:2024/05/22 12:39
Spring内核研究-管理bean的声明周期一(InitializingBean和init-method)

InitializingBean

    Spirng的InitializingBean为bean提供了定义初始化方法的方式。InitializingBean是一个接口,它仅仅包含一个方法:afterPropertiesSet()。


Bean实现这个接口,在afterPropertiesSet()中编写初始化代码:
package research.spring.beanfactory.ch4;import org.springframework.beans.factory.InitializingBean;public class LifeCycleBean implements InitializingBean{ public void afterPropertiesSet() throws Exception { System.out.println("LifeCycleBean initializing..."); }}
 
在xml配置文件中并不需要对bean进行特殊的配置:
xml version="1.0" encoding="UTF-8"?>DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="lifeBean" class="research.spring.beanfactory.ch4.LifeCycleBean"> bean>beans>

 
编写测试程序进行测试:
package research.spring.beanfactory.ch4; import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource; public class LifeCycleTest { public static void main(String[] args) { XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch4/context.xml")); factory.getBean("lifeBean"); } }

    运行上面的程序我们会看到:“LifeCycleBean initializing...”,这说明bean的afterPropertiesSet已经被Spring调用了。
 
    Spring在设置完一个bean所有的合作者后,会检查bean是否实现了InitializingBean接口,如果实现就调用bean的afterPropertiesSet方法。

 SHAPE  /* MERGEFORMAT

装配bean的合作者

查看bean是否实现InitializingBean接口

调用afterPropertiesSet方法


init-method

    Spring虽然可以通过InitializingBean完成一个bean初始化后对这个bean的回调,但是这种方式要求bean实现 InitializingBean接口。一但bean实现了InitializingBean接口,那么这个bean的代码就和Spring耦合到一起了。通常情况下我不鼓励bean直接实现InitializingBean,可以使用Spring提供的init-method的功能来执行一个bean 子定义的初始化方法。
写一个java class,这个类不实现任何Spring的接口。定义一个没有参数的方法init()。
package research.spring.beanfactory.ch4;public class LifeCycleBean{ public void init(){ System.out.println("LifeCycleBean.init..."); }}
 
在Spring中配置这个bean:
xml version="1.0" encoding="UTF-8"?>DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="lifeBean" class="research.spring.beanfactory.ch4.LifeCycleBean"
 init-method="init"> bean>beans>

当Spring实例化lifeBean时,你会在控制台上看到” LifeCycleBean.init...”。
 
 
Spring要求init-method是一个无参数的方法,如果init-method指定的方法中有参数,那么Spring将会抛出java.lang.NoSuchMethodException
 
init-method指定的方法可以是public、protected以及private的,并且方法也可以是final的。
 
init-method指定的方法可以是声明为抛出异常的,就像这样:

       final protected void init() throws Exception{

           System.out.println("init method...");

           if(true) throw new Exception("init exception");

    }
如果在init-method方法中抛出了异常,那么Spring将中止这个Bean的后续处理,并且抛出一个org.springframework.beans.factory.BeanCreationException异常。
 
InitializingBean和init-method可以一起使用,Spring会先处理InitializingBean再处理init-method。
org.springframework.beans.factory.support. AbstractAutowireCapableBeanFactory完成一个Bean初始化方法的调用工作。 AbstractAutowireCapableBeanFactory是XmlBeanFactory的超类,再 AbstractAutowireCapableBeanFactory的invokeInitMethods方法中实现调用一个Bean初始化方法:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.java:
//……//在一个bean的合作者设备完成后,执行一个bean的初始化方法。protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mergedBeanDefinition)
 throws Throwable {
//判断bean是否实现了InitializingBean接口 if (bean instanceof InitializingBean) { if (logger.isDebugEnabled()) { logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } //调用afterPropertiesSet方法 ((InitializingBean) bean).afterPropertiesSet(); }//判断bean是否定义了init-methodif(mergedBeanDefinition!=null&&mergedBeanDefinition.getInitMethodName() != null) { //调用invokeCustomInitMethod方法来执行init-method定义的方法invokeCustomInitMethod(beanName, bean, mergedBeanDefinition.getInitMethodName());}}//执行一个bean定义的init-method方法protected void invokeCustomInitMethod(String beanName, Object bean, String initMethodName) throws Throwable { if (logger.isDebugEnabled()) { logger.debug("Invoking custom init method '" + initMethodName + "' on bean with name '" + beanName + "'"); } //使用方法名,反射Method对象 Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null); if (initMethod == null) { throw new NoSuchMethodException(
"
Couldn't find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'"); } //判断方法是否是public if (!Modifier.isPublic(initMethod.getModifiers())) { //设置accessible为true,可以访问private方法。 initMethod.setAccessible(true); } try { //反射执行这个方法 initMethod.invoke(bean, (Object[]) null); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } }//………..

    通过分析上面的源代码我们可以看到,init-method是通过反射执行的,而afterPropertiesSet是直接执行的。所以 afterPropertiesSet的执行效率比init-method要高,不过init-method消除了bean对Spring依赖。在实际使用时我推荐使用init-method。
    需要注意的是Spring总是先处理bean定义的InitializingBean,然后才处理init-method。如果在Spirng处理InitializingBean时出错,那么Spring将直接抛出异常,不会再继续处理init-method。
    如果一个bean被定义为非单例的,那么afterPropertiesSet和init-method在bean的每一个实例被创建时都会执行。单例 bean的afterPropertiesSet和init-method只在bean第一次被实例时调用一次。大多数情况下 afterPropertiesSet和init-method都应用在单例的bean上。
发布于 2006年7月5日 18:01   由 陈杰   有 0 篇评论
Spring内核研究-管理bean之间的关系三(自动装配)

    Spring BeanFactory提供了类似pico container中自动装配组件依赖的对象的功能。自动装配能应用在每个组件上,可以为一些组件定义自动装配,而另一些组件则不使用。

    使用”autowire”属性可以设置自动装配,autowire有五种模式:
    no
    默认属性,不进行自动装配。
    byName
    通过bean的属性名称自动装配合作者。

SHAPE /* MERGEFORMAT

 

 

按照bean定义的名称自动装配


    Spring用bean 中set方法名和BeanFactory中定义的合作者的名称做匹配,一但2者匹配,Sping就会把合作者进行注入。
可以使用id属性也可以使用name属性定义合作者的名称,这2个属性在Spring进行自动装配时没有区别。
当有多个名称相同的合作者在Spring中定义时,Srping在自动装配时选择最后一个定义的合作者注入。

SHAPE /* MERGEFORMAT

 

这个Bean将被注入到dao


    在多个合作者名称相同进行自动装配时,合作者的id属性并不会比name属性优先处理。无论怎样定义Spring总会把最后一个定义的合作者注入。
byType
    通过bean set方法中参数的类型和BeanFactory中定义合作者的类型做匹配,Spring会找到匹配的合作者进行注入。

SHAPE /* MERGEFORMAT

 

 

按照bean定义的类型自动装配


    在byType自动装配模式中,Spring不关心合作者的名称,只关心合作者的类型是否满足条件。
类似上面介绍的byName的方式,在byType方式中,当具有相同名称并且有相同类型的多个合作者被找到时,Spring会注入最后一个定义的合作者。

SHAPE /* MERGEFORMAT

 

这个Bean将被注入到dao


在byType装配时,如果有2个不同名称但是类型相同的合作者被找到,那么Spring会抛出一个依赖异常。

SHAPE /* MERGEFORMAT

 


抛出依赖异常,通知用户在byType方式中同样类型的Bean只能定义一个。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dao' defined in class path resource [research/spring/beanfactory/ch3/context.xml]: Unsatisfied dependency expressed through bean property 'database': There are 2 beans of type [class research.spring.beanfactory.ch3.Database] for autowire by type. There should have been 1 to be able to autowire property 'database' of bean 'dao'...

constructor

constructor其实时按byType的方式进行构造函数的注入。

SHAPE /* MERGEFORMAT

 

 

按照bean定义的类型自动装配

       

    constructor装配方式不关心构造参数的顺序,无论构造函数参数的顺序如何Spring都会按类型匹配到正确的合作者进行注入。

    在byType方式中,当没有找到类型相同的合作者时Spring什么都不会去做。但是在constructor方式中,当没有找到和Bean构造函数中参数类型相匹配的合作者时,Spring会抛出异常。
    Spring在进行constructor方式的自动装配时,强制要求所有的构造函数中所有的合作者都必须存在。
autodetect
    在autodetect的方式中,Spring检查一个Bean内部是否有默认的构造函数。如果有默认的参数Spring就使用byType的方式进行自动装配。如果没有默认的构造函数Spring则使用constructor的方式进行自动装配。
如果一个Bean同时定义了默认构造函数和带参数的构造函数,Spring仍会使用byType的方式进行装配。
 
 
    不管使用上述哪种装配方式,都可以在Bean中显示的定义合作者。显示定义的依赖关系优先级比自动装配高。
自动装配的功能可以和自动依赖检查一起使用。Spring会首先进行自动装配,然后在进行依赖检查。
自动装配提供了简化配置的可能性,但是我并不建议在项目中大量的使用自动装配,特别时byType方式。因为自动装配,尤其时byType方式,破坏了Bean和合作者之间显示的依赖关系,所有的依赖关系都时不明显的。在使用自动装配后我们的依赖关系需要到源代码中才能看到,这使得维护或文档化Bean的依赖关系变得很困难。
适当的使用自动装配比如byName方式的装配,是有一些好处的。比如我们在一些特定的范围里可以借助byName自动装配的功能来实现“以惯例来代替配置”的框架。
发布于 2006年7月4日 17:17   由 陈杰   有 0 篇评论
Spring内核研究-管理bean之间的关系二(自动依赖检查)
    自动依赖检查可以保证所有java bean中的属性(set方法)都在Spring中正确的配置。如果在一个java bean中定义了一个name属性,并且也setName方法。那么在开启自动依赖检查功能后,就必须在Spring中定义这个属性,否则Spring将抛出异常。
请看下面的例子:
Dao.java
包含一个setName方法。
package research.spring.beanfactory.ch3;public class Dao { private String name; public void setName(String name) { this.name = name; } }

context.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="dao" class="research.spring.beanfactory.ch3.Dao"> </bean> <bean id="database" class="research.spring.beanfactory.ch3.Database"> </bean></beans>

    我们在context.xml没有定义Dao的name属性。上面的配置,Spring可以正常的实例化Dao对象。
下面我们修改context.xml:
我们通过dependency-check=all,在Dao上增加了自动依赖检查的功能。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="dao" class="research.spring.beanfactory.ch3.Dao" dependency-check="all" > </bean> <bean id="database" class="research.spring.beanfactory.ch3.Database"> </bean></beans>

 
当配置依赖检查时,Spring实例化Dao时会抛出一个异常:


 
Spring定义了4种依赖检查的策略:
 
  • none(默认)
    不进行依赖检查。
  • simple
    只对简单属性和集合中的简单属性进行检查。不对依赖的对象检查。
  • objects
    只对为对象类型的属性进行检查。
  • all
    对所有类型进行检查。
 
    如果把上面例子里的context.xml改成这样:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="dao" class="research.spring.beanfactory.ch3.Dao" dependency-check="objects" > </bean> <bean id="database" class="research.spring.beanfactory.ch3.Database"> </bean></beans>

 Spring将不会抛出异常,因为objects只对依赖的对象进行检查。
 
dependency-check在Spring中又以下的限制:
 
  1.  不能对构造函数中的参数进行检查。
  2.  即使属性中有默认值,只要包含了set方法,那么dependency-check仍然需要检查Spring中是否配置了这个属性。
 
package research.spring.beanfactory.ch3; public class Dao { private Database database; private String name="chenjie";//dependency-check仍然会检查这个属性是否配置注入 public void setName(String name) { this.name = name; } public void setDatabase(Database database) { this.database = database; } }
即使Dao设置里name得默认值,但是只要有setName方法,dependency-check仍然会判断是否在配置文件中设置了setName对应的注入。
发布于 2006年7月4日 17:04   由 陈杰   有 0 篇评论
Spring内核研究-管理bean之间的关系一(depends-on)
    depend-on用来表示一个Bean的实例化依靠另一个Bean先实例化。如果在一个bean A上定义了depend-on B那么就表示:A 实例化前先实例化 B。
    这种情况下,A可能根本不需要持有一个B对象。
    比如说,你的DAO Bean实例化之前你必须要先实例化Database Bean,DAO Bean并不需要持有一个Database Bean的实例。因为DAO的使用是依赖Database启动的,如果Database Bean不启动,那么DAO即使实例化也是不可用的。这种情况DAO对Database的依赖是不直接的。
    除了在DAO上使用构造函数注入Database Bean以外,Spring没有任何依赖注入的关系能够满足上面的情况。但是DAO也许根本不需要Database的实例被注入,因为DAO是通过JDBC访问数据库的,它不需要调用Database 上的任何方法和属性。
    在这种情况下你可以使用depends-on来定义在DAO被实例化之前先去实例化Database。你可这样定义:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="dao" class="research.spring.beanfactory.ch3.Dao" depends-on="database"> </bean> <bean id="database" class="research.spring.beanfactory.ch3.Database"> </bean> </beans>
 
通过定义depends-on=”database”可以控制Sping实例化dao的顺序。在任何时候Spring总会保证实例化DAO之前先实例Database。
    通常depends-on常常应用在上面的场景中。如果DAO depend-on Database的同时需要得到Database的实例,那么使用构造函数注入是一个比较好的解决办法。因为构造函数注入的方式是要先实例化目标对象依赖的对象然后在实例化目标对象。关于构造函数的输入请参考另一篇文章《Spring内核研究-set方法注入和构造函数注入

    DAO depend-on Database时,也可以在DAO上定义setDatabase方法来接收一个Database的实例。这样Sping会保证DAO创建前先创建Database实例,然后在把实例化DAO后调用DAO的setDatabase方法把刚才创建的Database的实例注入给DAO。前提条件时Database必须定义成单例的。否则Spring在DAO depend-on Database时会创建一个Database的实例,在DAO.setDatabase时又会创建Database另外的一个实例。这种情况可能不是你想要的,而且很可能会造成比较隐蔽的错误。

    使用set方法注入depend-on的对象:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="dao" class="research.spring.beanfactory.ch3.Dao" depends-on="database "> <property name="database"> <ref bean="database"></ref> </property> </bean> <bean id="database" class="research.spring.beanfactory.ch3.Database"> </bean> </beans>

     一般在depends-on一个对象并且又需要这个对象实例的情况下,我都建议你使用构造函数的注入方式替换depend-on。只有不能构造函数中添加依赖对象参数的情况下才使用上面例子里的方式。
可以同时使用depends-on和构造函数注入,如A depends-on B 并且 new A(B b)。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="dao" class="research.spring.beanfactory.ch3.Dao" depends-on="database"> <constructor-arg> <ref bean="database"></ref> </constructor-arg> </bean> <bean id="database" class="research.spring.beanfactory.ch3.Database"> </bean> </beans>

    然而这种做法是不合适的,因为在构在函数中注入依赖对象的方式可以包含depends-on的情况。也就时说new A(B b)包含了A depends-on B的所有情况。既然已经定义了new A(B b)就没有必要在定义A depends-on B。所以,new A(B b)可以替代A depends-on BA创建前必须创建B,而且A不需要使用B实例的情况下只能使用A depends-on B
 

    Spring允许Bean和Bean依赖的Bean(合作者)上同时定义depends-on。比如A depends-on B && B depends-on C && C depends-on D。下面这样定义是合法的。Sping实例化他们的顺序是D->C->B->A。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="a" class="research.spring.beanfactory.ch3.A" depends-on="b" /><bean name="b" class="research.spring.beanfactory.ch3.B" depends-on="c" /><bean name="c" class="research.spring.beanfactory.ch3.C" depends-on="D" /><bean name="d" class="research.spring.beanfactory.ch3.D" /> </beans>
    但是Spring不允许A depends-on B && B depends-on A的情况。看下面的例子,由于D又依赖回A,这种在依赖关系中形成了一个闭环,Spring将无法处理这种依赖关系。所以下面的这种定义是不合法的。
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="a" class="research.spring.beanfactory.ch3.A" depends-on="b" /><bean name="b" class="research.spring.beanfactory.ch3.B" depends-on="c" /><bean name="c" class="research.spring.beanfactory.ch3.C" depends-on="D" /><bean name="d" class="research.spring.beanfactory.ch3.D" depends-on="A"/> </beans>
 
    一个Bean可以同时depends-on多个对象如,A depends-on D,C,B。可以使用“,”或“;”定义多个depends-on的对象。
 
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="a" class="research.spring.beanfactory.ch3.A" depends-on="d,c,b" /><bean name="b" class="research.spring.beanfactory.ch3.B" /><bean name="c" class="research.spring.beanfactory.ch3.C" /><bean name="d" class="research.spring.beanfactory.ch3.D" /> </beans>

    上面的例子中A的实例化需要先实例化D,C,B。Spring会按照depend-on中定义的顺序来处理Bean。在这个例子里Spring实例化对象的顺利是D->C->B->A。虽然实例化对象的顺序和前面“A depends-on B && B depends-on C && C depends-on D”的情况一下,但是这里的意义是完全不同的。不能用“A depends-on D,C,B”代替“A depends-on B && B depends-on C && C depends-on D”。

     depends-on是一个非常又用的功能,借助depends-on我们可以管理那些依赖关系不明显或者没有直接依赖关系的对象。
发布于 2006年7月4日 16:49   由 陈杰   有 0 篇评论
Spring内核研究-通过工厂注入
    Spring专门设计了对工厂模式支持,你可以使用静态工厂方法来创建一个Bean,也可以使用实例工厂的方法来创建Bean。下面分别介绍这2种方法。
静态工厂注入
    定义一个Bean使用自己类上的静态工厂方法来创建自己。
我们继续使用上一篇文章《Spring内核研究-Lookup方法注入》的例子来说明如何使用静态工厂创建Bean。
context.xml
    factory-menthod定义了userDao Bean使用UserDao类的getInstance方法来创建自己的实例。userManager仍然通过lookup方法获得userDao。Lookup方法并不关心一个Bean的实例时怎样创建的,所以可以混合使用lookup方法和factory-menthod方法。
xml version="1.0" encoding="UTF-8"?>DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="userManager" class="research.spring.beanfactory.ch2.UserManager"> <lookup-method name="getUserDao" bean="userDao" /> bean> <bean name="userDao" class="research.spring.beanfactory.ch2.UserDao"
 factory-method="getInstance" / >beans>

UserDao.java
    增加一个getInstance方法来创建自己的实例。
package research.spring.beanfactory.ch2;public class UserDao { public static UserDao getInstance() { return new UserDao("static factory method"); } private String name = ""; public UserDao(String name) { this.name = name; } public void create() { System.out.println("create user from - " + name); }}

Test.java
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource;
public class Test { public static void main(String[] args) { XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml")); UserManager manager=(UserManager) factory.getBean("userManager"); manager.createUser(); } }

运行Test.java,你会看到:

create user from - static factory method

这说明userDao使用它自己得静态工厂创建得。
 
静态工厂方法存在一些限制:
  1. 静态工厂方法上不能有参数,也不能在Spring种定义静态工厂方法的参数。
  2. 静态工厂方法只能是public的,不能是private或protected的。
  3. 静态工厂方法不能和构造函数注入一起使用。下面的定义时不能正常工作的:

 

 

package research.spring.beanfactory.ch2; import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource; public class Test { public static void main(String[] args) { XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml")); UserManager manager=(UserManager) factory.getBean("userManager"); manager.createUser(); } }
实例工厂注入
    定义一个Bean使用这个Bean的工厂对象上的工厂方法来创建自己。
我们定义一个UserDao的Factory来创建UserDao。
UserDaoFactory.java
package research.spring.beanfactory.ch2; public class UserDaoFactory{ public UserDao getUserDao(){ return new UserDao("UserDaoFactory"); }}
修改context.xml:
xml version="1.0" encoding="UTF-8"?>DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="userManager" class="research.spring.beanfactory.ch2.UserManager"> <lookup-method name="getUserDao" bean="userDao" /> bean> <bean name="userDao" class="research.spring.beanfactory.ch2.UserDao"
 factory-bean
="userDaoFactory" factory-method="getUserDao" > bean> <bean name="userDaoFactory" class="research.spring.beanfactory.ch2.UserDaoFactory"> bean>beans>

再次运行Test.java你会看到:
create user from – UserDaoFactory
 
    通过上面的配置Spring已经使用userDaoFactory实例的工厂方法来创建userDao了。
  • factory-bean定义了工厂Bean
  • factory-method定义了工厂方法
 

 

    实例工厂和静态工厂一样都存在相同的限制:
  1. 静态工厂方法上不能有参数,也不能在Spring种定义静态工厂方法的参数。
  2. 静态工厂方法只能是public的,不能是private或protected的。
  3. 静态工厂方法不能和构造函数注入一起使用。

 

   
    和静态工厂不同的是:
  •  实例工厂方法不能是静态的,而静态工厂方法必须是静态的。

 

    通过上面的例子我们看到Spring对工厂模式对了完整的支持。但是这里还是需要说明,如果使用IoC模式设计的系统一般情况下不需要为任何Bean做工厂类。在我的观点里,工厂模式仅仅是遗留系统,使用依赖注入模式可以取代工厂模式。Spring对工厂的支持仅仅是为了可以很好的集成遗留系统。
发布于 2006年7月4日 16:20   由 陈杰   有 0 篇评论
Spring内核研究-Lookup方法注入

 

Lookup方法注入

   “Lookup方法”可以使Spring替换一个bean原有的,获取其它对象具体的方法,并自动返回在容器中的查找结果。
我们来看这个例子:
UserDao.java
    在UserDao的构造函数中接受一个name参数,创建UserDao的对象会把自己的名字传递给userDao,这样userDao的create方法中就会把userDao的创建者打印出来。
package research.spring.beanfactory.ch2; public class UserDao { private String name=""; public UserDao(String name){ this.name=name; } public void create(){ System.out.println("create user from - "+name); } }

UserManager.java
    在这段代码中UserManager依靠getUserDao方法来获取UserDao对象。由于在getUserDao方法里显示的声明了如何去实例一个UserDao,所以上面的代码不符合IoC模式的风格。虽然使用GetUserDao封装了UserDao的创建过程,但是UserManager和UserDao的关系仍然非常紧密。
package research.spring.beanfactory.ch2; public class UserManager { public UserDao getUserDao() { return new UserDao("UserManager.getUserDao()"); } public void createUser() { UserDao dao = getUserDao(); //通过getUserDao获得userDao dao.create(); }}

LookupMethodTest.java
    通过BeanFactory获得UserManager,并调用createUser方法。
package research.spring.beanfactory.ch2;import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource; public class LookupMethodTest { public static void main(String[] args) { XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource("research/spring/beanfactory/ch2/context.xml")); UserManager manager=(UserManager) factory.getBean("userManager"); manager.createUser(); //create a User }}
context.xml
xml version="1.0" encoding="UTF-8"?>DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="userManager" class="research.spring.beanfactory.ch2.UserManager"> bean> <bean name="userDao class="research.spring.beanfactory.ch2.UserDao" > bean>beans>

    运行LookupMethodTest你会看到屏幕输入” create user from - UserManager.getUserDao()”。

    由于是遗留系统,所以我们不能修改UserManager。现在我希望让这个UserManager依赖的Dao对象由spring管理,而不修改原有的代码。
    在这个场景中我们就可以利用Spring提供的“Lookup方法”来替换原有的getUserDao方法,实现自动获取userDao的功能。修改context.xml:
xml version="1.0" encoding="UTF-8"?>DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="userManager" class="research.spring.beanfactory.ch2.UserManager"> <lookup-method name="getUserDao" bean="userDao" /> bean> <bean name="userDao" class="research.spring.beanfactory.ch2.UserDao" > <constructor-arg> <value>lookup methodvalue> constructor-arg> bean> <bean name="userDaoFactory" class="research.spring.beanfactory.ch2.UserDaoFactory"> bean>beans>

    再次运行LookupMethodTest你会看到不同的输出结果“create user from - lookup method”。字符串“lookup method”是通过构造函数注入给userDao的。原来的userManager.java并没有作任何修改,仍然是通过UserDao dao = getUserDao();来获得userDao的。这说明Spring已经替换了原有的getUserDao方法的实现,当执行getUserDao时Spring会在容器中寻找指定的Bean,并返回这个Bean。

Lookup方法的工作机制
    通过这种机制我们可以在不修改原系统代码的情况下,可以轻易的把UserDao换成别的类型相容的对象而不会影响原系统。Spring是使用CGLIB在字节码级别动态实现出userManager的子类,并重写getUserDao方法的方式来实现这个神奇的功能的。
修改LookupMethodTest.java:
package research.spring.beanfactory.ch2;import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource;public class LookupMethodTest { public static void main(String[] args) { XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"
research/spring/beanfactory/ch2/context.xml")); UserManager manager=(UserManager) factory.getBean("userManager"); System.out.println(manager.toString()); //打印userManager的信息 manager.createUser(); //create a User }}

    我们在获取UserManager的实例后打印出这个实例的信息,再次运行LookupMethodTest你会看到:


注意manager.toString()打印出了:
research.spring.beanfactory.ch2.UserManager$$EnhancerByCGLIB$$886cdee9@1238bd2
    这个是CGLIG动态生成的类,而不是原来的UserManager的实例。所以请记住在任何时候只要定义了一个Bean的Lookup方法,那么这个Bean的实例将是一个CGLIB动态生成的实例而不是原来类的实例。
 
Spring允许在一个Bean中定义多个Lookup方法。
   
<bean name="userManager" class="research.spring.beanfactory.ch2.UserManager"> <lookup-method name="getUserDao" bean="userDao" /> <lookup-method name="getOtherDao" bean="otherDao" />bean>

上面的做法是合法的,并且可以正常工作。
 
    虽然Spring会用CGLIB动态生成一个带有Lookup方法bean的子类,但是这并不影响Spring完成其它的功能。Sping还是允许Lookup方法和setXXX、构造函数注入以及后面我们将介绍的自动依赖检查和自动装配的功能同时使用。
 
    Spring还允许Lookup方法中定义的方法带有参数,但是Sping不会处理这些参数。
修改UserManager:
package research.spring.beanfactory.ch2; public class UserManager { private UserDao dao; public void setDao(UserDao dao) { this.dao = dao; } public UserDao getUserDao(String daoName) { return new UserDao("UserManager.getUserDao()"); } public void createUser() { UserDao dao = getUserDao(“userDao”); //通过getUserDao获得userDao dao.create(); }}

虽然方法上由参数,但是上面的代码可以正常工作。Spring不会处理这些参数。
 
Spring对Lookup方法也存在一些限制:
  • 方法不能是private的,但可以是protected的。
  • 方法不能是静态的。
在抽象类和接口上应用Lookup方法
    有一个比较有趣的用法,就是在抽象类上定义Lookup方法。你一定记得经典的工厂模式吧。定义一个抽象工厂,然后为每一类具体产品实现一个具体产品的工厂。
一个抽象工厂:
package research.spring.beanfactory.ch2;public abstract class Factory { public abstract UserDao getProduct();}

具体一类产品的工厂:
package research.spring.beanfactory.ch2; public class UserDaoFactory extends Factory{ public UserDao getProduct(){ return new UserDao("UserDaoFactory"); }}

用户可以通过:

 

new UserDaoFactory().getProduce();

 

来获取具体的UserDao产品。

    但是如果有很多产品就需要做出实现出很多工厂如,DocumentDaoFactory、GroupDaoFactory等等,这样系统中会出现大量的工厂。工厂的泛滥并不能说明系统的设计是合理的。
    既然Spring可以在抽象类上使用Lookup方法,那么我们就可以不同实现真的去实现那么多的子类了。我们可以在抽象类上直接定义Lookup方法和目标对象。用户直接通过抽象类来获得需要的产品对象。看下面这个例子:
Factory.ava
package research.spring.beanfactory.ch2; public abstract class Factory { public abstract Object getProduct();}

context.xml
    如果指定userDaoFactory的类为一个抽象类,并且再这个bean里定义了Lookup方法,那么Spring会自动生成这个抽象类的子类实现。
xml version="1.0" encoding="UTF-8"?>DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean name="userManager" class="research.spring.beanfactory.ch2.UserManager"> <lookup-method name="getUserDao" bean="userDao" /> bean> <bean name="userDao" class="research.spring.beanfactory.ch2.UserDao" > <constructor-arg> <value>lookup methodvalue> constructor-arg> bean> <bean name="userDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false"> <lookup-method name="getProduct" bean="userDao" /> bean>beans>

Test.java
package research.spring.beanfactory.ch2; import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource; public class LookupMethodTest { public static void main(String[] args) { XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml")); //获得抽象工厂 Factory abstractFactory=(Factory) factory.getBean("userDaoFactory"); UserDao userDao=(UserDao) abstractFactory.getProduct(); System.out.println(userDao.toString()); userDao.create(); } }

运行Test你会看到:
research.spring.beanfactory.ch2.UserManager$$EnhancerByCGLIB$$cc2f8f1c@12c7568
create user from - lookup method

    对,这个结果和上面的例子是完全一样的。UserDao并没有改变,我们通过抽象的Factory获得了具体的UserDao的实例。这样即使系统中很多的具体产品我们也不需要实现每类产品的工厂类了。只需要在系统中配置多个抽象工厂,并且配置每个工厂的singlton为false,在用户使用时使用不同抽象工厂的实例就可以了。
 
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false"> <lookup-method name="getProduct" bean="userDao" /> bean> <bean name="documentDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false"> <lookup-method name="getProduct" bean="documentDao" /> bean>

Spring不关心抽象类中的定义的lookup方法是否时抽象的,Spring都会重写这个方法。
 
    既然Sping可以动态实现抽象类的子类那么,它能不能动态创建出实现一个接口的类呢。答案时肯定的。上面的例子可以直接把Factory变成一个接口,仍然可以正常工作。
    这里需要注意的是,只要在一个Bean上明确的定义了Lookup方法,Spring才会使用CGLIB来做原对象的字节码代理。如果一个没有定义Lookup方法的抽象类或接口是不能直接被Spring实例的。
 
    本文介绍了Lookup方法的使用和工作原理,希望读者能够对Lookup方法有了比较深入的了解。虽然我的例子可以简化工厂模式,但是我并不鼓励大家在实际系统中这样做。因为我始终认为“工厂模式”只要在遗留系统中才会碰到。使用IoC模式基本上可以替代所有的对象创建模式。本章的例子只是为了说明Lookup方法如何使用,和Lookup方法的一些特殊情况。Lookup方法一般只在处理遗留代码时使用。
发布于 2006年7月4日 12:36   由 陈杰   有 4 篇评论
Spring内核研究-set方法注入和构造函数注入
       Spring种提供了2种常用的注入方式,set方法注入和构造函数注入。由于这2种注入方式很相似,都可以满足我们的需求,所以在大多数情况下我们忽视了这2种注入方式的区别。下面让我们看看这2种注入方式的特点。
 
        我们先看看Spring在使用set方法注入时,是怎样实例化一个Bean和Bean的合作者的:

        在A中有一个setB方法用来接收B对象的实例。那么Spring实例化A对象的过程如下:

        在不考虑Bean的初始化方法和一些Spring回调的情况下,Spring首先去调用A对象的构造函数实例化A,然后查找A依赖的对象本例子中是B(合作者)。一但找到合作者,Spring就会调用合作者(B)的构造函数实例化B。如果B还有依赖的对象Spring会把B上依赖的所有对象都按照相同的机制实例化然后调用A对象的setB(B b)b对象注入给A
        因为Spring调用一个对象的set方法注入前,这个对象必须先被实例化。所以在"使用set方法注入"的情况下Spring会首先调用对象的构造函数。
        我们在来看通过构造函数注入的过程:

        如果发现配置了对象的构造注入,那么Spring会在调用构造函数前把构造函数需要的依赖对象都实例化好,然后再把这些实例化后的对象作为参数去调用构造函数。
        在使用构造函数和set方法依赖注入时,Spring处理对象和对象依赖的对象的顺序时不一样的。一般把一个Bean设计为构造函数接收依赖对象时,其实是表达了这样一种关系:他们(依赖对象)不存在时我也不存在,即“没有他们就没有我”。
        通过构造函数的注入方式其实表达了2个对象间的一种强的聚合关系:组合关系。就比如一辆车如果没有轮子、引擎等部件那么车也就不存在了。而且车是由若干重要部件组成的,在这些部件没有的情况下车也不可能存在。这里车和他的重要部件就时组合的关系。如果你的应用中有这样类似的场景那么你应该使用“构造函数注入”的方式管理他们的关系。“构造函数注入”可以保证合作者先创建,在后在创建自己。
        通过set方法注入的方式表达了2个对象间较弱的依赖关系:聚合关系。就像一辆车,如果没有车内音像车也时可以工作的。当你不要求合作者于自己被创建时,“set方法注入”注入比较合适。
        虽然在理论上“构造函数注入”和“set方法注入”代表2种不同的依赖强度,但是在spring中,spring并不会把无效的合作者传递给一个bean。如果合作者无效或不存在spring会抛出异常,这样spring保证一个对象的合作者都是可用的。所以在spring中,“构造函数注入”和“set方法注入”唯一的区别在于2种方式创建合作者的顺序不同。
        使用构造函数依赖注入时,Spring保证所有一个对象所有依赖的对象先实例化后,才实例化这个对象。(没有他们就没有我原则)
        使用set方法依赖注入时,Spring首先实例化对象,然后才实例化所有依赖的对象。
 
 
原创粉丝点击