《Spring揭秘》——IOC梳理1(基础,基于注解的配置)
依赖注入的三种方式:
1、构造方法注入:对象构造完成后,即进入就绪状态,可以马上使用。缺点是有时候构造方法的参数列表较长,构造方法无法被继承,无法设置默认值。
2、setter方法注入:相对宽松,可在对象构造完成后再注入。setter方法可以被继承,允许设置默认值。缺点是无法在构造完成后就进入就绪状态。
3、接口注入:类需要实现某个接口,接口定义了注入方法,方法的参数即为所依赖对象的类型。(不推荐,较为死板和烦琐。)
IoC Service Provider的职责:
业务对象的构建管理:业务对象无需关心所依赖的对象如何构建如何取得,交由IoC Service Provider进行构建;
业务对象的依赖绑定:IoC Service Provider的最终使命所在,通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象进行绑定。
Spring提供了两种容器类型:
BeanFactory:基础类型IOC容器,提供完整的IOC服务支持。默认采用延迟初始化策略,容器启动速度快,所要的资源有限。
ApplicationContext:ApplicationContext在BeanFactory的基础上构建,是相对高级的容器实现,拥有BeanFactory的所有支持,且提供了其他高级特性。其管理的对象会在该容器启动后,默认全部初始化并绑定完成。所以,该容器会需要更多系统资源。
BeanFactory的XML之旅:
<beans>:XML配置文件中最顶层的元素,它下面可以包含0个或1个<description>和多个<bean>以及<import>或者<alias>。属性设置包括:default-lazy-init 、default-autowire 、default-init-method 、default-destroy-method 、default-dependency-check 。
<description>:指定一些描述性信息。
<import>:如果A.xml中的<bean>定义可能依赖B.xml中的某些<bean>定义,那就在A.xml中使用<import>将B.xml引入到A.xml。
<alias>:当bean名称过长时,可以通过alias来指定别名。<alias name="aabbcc" alias="abc"/>
<bean>:id属性(内部bean可省)、class属性、(name属性)
<bean id="helloSpring" name="//test t" class="com.test.HelloSpring" ></bean>
可以通过ApplicationContext的getBean("helloSpring")获取,还可以用"//test","t"代替,name可通过逗号、空格分割开。
构造方法注入:<constructor-arg>,若存在多个构造方法,可能要使用type和index属性。要注意index从0开始。
<bean id="car1" class="com.test.Car">
<constructor-arg value="Audi" index="0"></constructor-arg>
<constructor-arg value="ShangHai" index="1"></constructor-arg>
<constructor-arg value="300000.0" type="double"></constructor-arg>
</bean>
setter方法注入:要确保提供了无参构造方法。此外,<constructor-arg>和<property>可同时使用。
<bean id="person" class="com.test.Person"> <property name="name" value="Tom"/> <property name="age" value="24"/> <property name="car" ref="car2"/> <!-- <property name="car"> <ref bean="car2"/> 可以引用同一文件或父容器 <ref local="car2"/> 只能引用同一配置文件的Bean <ref parent="car2"/> 引用父容器的Bean </property> --> </bean>
<list>:对应注入对象类型是java.util.List及其子类 或者 数组类型的依赖对象。有序。
<list>
<ref bean="car1"></ref>
<ref bean="car2"></ref>
<bean class="com.test.Car"></bean>
</list>
<set>:对应注入对象类型是java.util.Set及其子类的依赖对象。无序。
<map>:对应注入对象类型是java.util.Map及其子类的依赖对象。
<map> <entry key="AA" value-ref="car1"></entry> <entry key="BB" value-ref="car2"></entry> </map>
<props>:相当于简化后的<map>,key只为String类型。对应配置类型为java.util.Properties的对象依赖。
<bean id="dataSource" class="com.test.DataSource">
<property name="properties">
<props>
<prop key="user">root</prop>
<prop key="password">1234</prop>
</props>
</property>
</bean>
若想配置的集合可以被复用,则可使用<util:list>、<util:set>、<util:map>。
<util:set id ="strs"> <value>str1</value> <value>str2</value> <value>str3</value> </util:set>
c命名空间(构造器)和p命名空间(属性赋值):相对而言更加简洁。不能直接使用集合类。
<bean id="employee" class="com.test.Employee" p:name="Aaa" p:carList-ref="cars1"></bean> <bean id="carLast" class="com.test.Car" c:brand="BRAND" c:corp="CORP" c:price="1" p:maxSpeed="111"></bean>
depend-on:使用depend-on来要求容器在初始化自身实例之前首先实例化其他的对象,要保证该对象一定先实例化。
autowire:5种自动绑定方式:no(默认),byName,byType,constructor,autodetect。推荐手动绑定,此外beans中也包含default-autowire,可进行统一配置。
dependency-check:对其所依赖的对象进行最终检查。不常使用。
lazy-init:延迟加载。注意的是若a对象设置了延迟加载,而其是另一个对象b的构造函数所需要的对象,b没有设置延迟加载,则a依然会被初始化。在beans属性中同样有default-lazy-init属性可进行统一配置。
bean的scope:singleton(默认,生命周期可直接参照IoC容器),prototype(当对象实例给请求方后,请求方负责对象后记生命周期的管理工作),request,session,global session。此外,自定义session参照P55。
关于继承:
<bean id="newsProviderTemplate" abstract="true"> <property name="newPersistener"> <ref bean="djNewsPersister"/> </property> </bean> <bean id="superNewsProvider" parent="newsProviderTemplate" class="..FXNewsProvider"> <property name="newsListener">
<ref bean="djNewsListener"> </property> </bean> <bean id="subNewsProvider" parent="newsProviderTemplate" class="..SpecificFXNewsProvider"> <property name="newsListener"> <ref bean="specificNewsListener"/> </property> </bean>
newsProviderTemplate就相当于一个配置模板,这个bean不可以实例化,同时可以不指定class属性。当多个bean定义拥有多个相同属性配置的时候,会带来很大便利。此外,parent所指向的对象也可以不是abstract的,而是具体的。额外强调的一点:若不想容器在初始化的时候实例化某些对象,就可以将abstract属性设置为true,以避免容器实例化。
工厂方法与 FactoryBean
1.静态工厂方法:
public class StaticBarInterfaceFactory { public static BarInterface getInstance() { return new BarInterfaceImpl(); } } <bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>
容器调用该静态方法工厂类的指定工厂方法(getInstance),并返回方法调用后的结果,即BarInterfaceImpl的实例。
有的工厂类的工厂方法可能需要参数来返回相应实例, 可以通过<constructor-arg>来指定工厂方法需要的参数 :
public class StaticBarInterfaceFactory{ public static BarInterface getInstance(Foobar foobar){ return new BarInterfaceImpl(foobar); } } <bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"> <constructor-arg> <ref bean="foobar"/> </constructor-arg> </bean> <bean id="foobar" class="...FooBar"/>
针对静态工厂方法实现类的bean定义,使用<constructor-arg>传入的是工厂方法的参数,而不是静态工厂方法实现类的构造方法的参数。
2.非静态工厂方法:
public class NonStaticBarInterfaceFactory{ public BarInterface getInstance(){ return new BarInterfaceImpl(); } }
<bean id="barFactory" class="...NonStaticBarInterfaceFactory"/>
<bean id="bar" factory-bean="barFactory" factory-method="getInstance"/>
使用factory-bean属性来指定工厂方法所在的工厂类实例,而不是通过class属性来指定工厂方法所在类的类型。指定工厂方法名则相同,都是通过factory-method属性进行的。
如果非静态工厂方法调用时也需要提供参数的话,处理方式是与静态的工厂方法相似的,都可以通过<constructor-arg>来指定方法调用参数。
3.FactoryBean
FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,FactoryBean本身就是生产对象的工厂。
使用FactoryBean的情况:当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候, 就可以实现org.springframework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。
public class CarFactoryBean implements FactoryBean<Car>{ private String brand; public void setBrand(String brand) { this.brand = brand; } @Override public Car getObject() throws Exception { // TODO Auto-generated method stub return new Car(brand, 500000); } @Override public Class<?> getObjectType() { // TODO Auto-generated method stub return Car.class; } @Override public boolean isSingleton() { // TODO Auto-generated method stub return true; } }
<!-- 通过FactoryBean来配置实例 实际返回的实例通过getObject() -->
<bean id="car" class="com.test.CarFactoryBean">
<property name="brand" value="BMW"></property>
</bean>
Spring容器内部许多地方了使用FactoryBean。比较常见的FactoryBean实现:JndiObjectFactoryBean;LocalSessionFactoryBean;SqlMapClientFactoryBean;ProxyFactoryBean;TransactionProxyFactoryBean。
方法注入与方法替换
<bean id="newsBean" class="..domain.FXNewsBean" scope="prototype"></bean> <bean id="mockPersister" class="..impl.MockNewsPersister"> <property name="newsBean"> <ref bean="newsBean"/> </property> </bean>
上文代码中,newsBean为prototype,mockPersister为singleton,存在setNewsBean方法。当调用persistNews方法时会打印newsBean的信息(调用getNewsBean())。当我们按照该代码配置并多次调用persistNews时会发现:每次调用的newsBean是同一个,虽然newsBean的scope是prototype。
原因:但当容器将一个FXNewsBean的实例注入MockNewsPersister之后, MockNewsPersister就会一直持有这个FXNewsBean实例的引用。虽然每次输出都调用了getNewsBean()方法并返回了 FXNewsBean 的实例,但实际上每次返回的都是MockNewsPersister持有的容器第一次注入的实例。这就是问题之所在,在第一个实例注入后, MockNewsPersister再也没有重新向容器申请新的实例。
方法注入:
容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象。(依赖cglib)
<bean id="newsBean" class="..domain.FXNewsBean" scope="prototype"></bean> <bean id="mockPersister" class="..impl.MockNewsPersister"> <lookup-method name="getNewsBean" bean="newsBean"/> </bean>
通过<lookup-method>的name属性指定需要注入的方法名, bean属性指定需要注入的对象。使用lookup方法注入是有一定范围的,一般是在通过一个Singleton Bean获取一个Prototype Bean时使用。
补充方法:使用BeanFactoryAware接口
Spring框架提供了一个BeanFactoryAware接口,容器在实例化实现了该接口的bean定义的过程中,会自动将容器本身注入该bean。这样,该bean就持有了它所处的BeanFactory的引用。(其实将应用与Spring框架类绑定在一起,实为下策。)
public interface BeanFactoryAware { void setBeanFactory(BeanFactory beanFactory) throws BeansException; }
public class MockNewsPersister implements IFXNewsPersister,BeanFactoryAware { private BeanFactory beanFactory; public void setBeanFactory(BeanFactory bf) throws BeansException { this.beanFactory = bf; } public void persistNews(FXNewsBean bean) { persistNews(); } public void persistNews(){ System.out.println("persist bean:"+getNewsBean()); } public FXNewsBean getNewsBean() { return beanFactory.getBean("newsBean");
}
}
<bean id="newsBean" class="..domain.FXNewsBean" scope="prototype"></bean>
<bean id="mockPersister" class="..impl.MockNewsPersister"></bean>
补充方法2:使用ObjectFactoryCreatingFactoryBean (P62)
方法替换:
方法替换主要体现在方法的实现层面上,它可以灵活替换或者以新的方法实现覆盖掉原来某个方法的实现逻辑。基本上可以认为,方法替换可以帮助我们实现简单的方法拦截功能。 (但其实使用不多,一般多使用AOP。)
<bean id="djNewsProvider" class="..FXNewsProvider"> ... <replaced-method name="getAndPersistNews" replacer="providerReplacer"></replaced-method> </bean> <bean id="providerReplacer" class="..FXNewsProviderMethodReplacer"></bean>
注意providerReplacer 要implements MethodReplacer。(Spring3.x P108)
基于注解的配置
关于@Autowired:是按照类型匹配进行依赖注入的 ,类似于byType。如果没有一个和标注类型匹配的Bean,则会抛出异常。如果希望即使找不到匹配的Bean也不要抛出异常,可以使用required属性:@Autowired(required=false)。
//属性 public class FXNewsProvider{ @Autowired private IFXNewsListener newsListener; @Autowired private IFXNewsPersister newPersistener; } //构造方法定义:将根据构造方法参数类型,来决定将什么样的依赖对象注入给当前对象。 @Autowired public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister){ this.newsListener = newsListner; this.newPersistener = newsPersister; } //方法定义:不仅可以标注于传统的setter方法之上,而且还可以标注于任意名称的方法定义之上,只要该方法定义了需要被注入的参数
@Autowired public void setUp(IFXNewsListener newsListener,IFXNewsPersister newPersistener){ this.newsListener = newsListener; this.newPersistener = newPersistener; }
//对集合类进行标注:Spring会将容器中所有类型为Plugin的Bean注入到该变量中。
@Autowired(required=false)
private List<Plugin> plugins;
当@Autowired同时找到两个或者多个同一类型的对象实例,则需要配合@Qualifier,@Qualifier实际上是byName自动绑定的注解版。
@Autowired @Qualifier("reutersNewsListner") private IFXNewsListener newsListener;
//除了在属性域中使用,还可以在方法中进行标注 public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener){...}
关于<context:component-scan>
<context:component-scan>默认扫描的注解类型是@Component。为@Component标注的对象指定名称:@Component("userDao")。
在@Component语义基础上细化后的@Repository、 @Service和@Controller也同样可以在<context:component-scan>扫描到。使用案例如下:
spring.xml
<context:component-scan base-package="com.echo">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
springmvc.xml
<context:component-scan base-package="com.echo" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
include-filter和exclude-filter可以使用的type类型有annotation、 assignable、 regex和aspectj四种。(具体参照Spring3.x P121)
此外,Bean默认作用范围是singleton,Spring提供了@Scope来进行显示的作用范围配置:
@Scope("prototype") @Component public class Car{ ... }
对标准注解的支持:
Spring还支持JSR250的@Resource和@PostConstruct以及@PreDestroy。
@Resource与@Autowired不同,它遵循的是byName自动绑定形式的行为准则:@Resource(name="djNewsListener")。
@PostConstruct :类似于init-method 。可以在一个Bean中定义多个@PostConstruct。
@PreDestroy :类似于destroy-method 。可以在一个Bean中定义多个@PreDestroy。