Spring复杂的IOC容器之短小的注解篇
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring复杂的IOC容器之短小的注解篇相关的知识,希望对你有一定的参考价值。
Spring复杂的IOC容器之短小的注解篇
本系列文章:
Spring复杂的BeanFactory继承体系该如何理解? ----上
Spring复杂的BeanFactory继承体系该如何理解? ----中
Spring复杂的BeanFactory继承体系该如何理解?—中下
Spring复杂的BeanFactory继承体系该如何理解?—下
本篇文章作为对上述系列关于IOC容器部分的一个小补充
注解版的自动绑定(@Autowired)
1. 从自动绑定(autowire)到@Autowired
在使用依赖注入绑定FXNews相关实现类时,为了减少配置量,我们可以采用Spring的IoC容器提供的自动绑定功能,如下所示:
<beans default-autowire="byType">
<bean id="newsProvider" class="..FXNewsProvider" autowire="byType"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
可以通过default-autowire来指定默认的自动绑定方式,也可以通过每个bean定义上的autowire来指定每个bean定义各自的自动绑定方式,它们都是触发容器对相应对象给予依赖注入的标志。而将自动绑定的标志用注解来表示时,也就得到了基于注解的依赖注入,或者更确切地称为基于注解的自动绑定。
@Autowired是基于注解的依赖注入的核心注解,它的存在可以让容器知道需要为当前类注入哪些依赖。比如可以使用@Autowired对FXNewsProvider类进行标注,以表明要为FXNewsProvider注入的依赖。
使用@Autowired标注后的FXNewsProvider
public class FXNewsProvider 16
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
@Autowired
public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister)
this.newsListener = newsListner;
this.newPersistener = newsPersister;
...
与原有的byType类型的自动绑定方式类似,@Autowired也是按照类型匹配进行依赖注入的,只不过,它要比byType更加灵活,也更加强大。
@Autowired可以标注于类定义的多个位置,包括如下几个。
域(Filed)或者说属性(Property)。不管它们声明的访问限制符是private、protected还是public,只要标注了@Autowired,它们所需要的依赖注入需求就都能够被满足,如下所示:
public class FXNewsProvider
@Autowired
private IFXNewsListener newsListener;
@Autowired
private IFXNewsPersister newPersistener;
...
构造方法定义(Constructor)
。标注于类的构造方法之上的@Autowired,相当于抢夺了原有自动绑定功能中“constructor”方式的权利,它将根据构造方法参数类型,来决定将什么样的依赖对象注入给当前对象。从最初的代码示例中,我们可以看到标注于构造方法之上的@Autowired的用法。
方法定义(Method)
。@Autowired不仅可以标注于传统的setter方法之上,而且还可以标注于任意名称的方法定义之上,只要该方法定义了需要被注入的参数。下面代码给出了一个标注于这种任意名称方法之上的@Autowired使用示例代码。
标注于方法之上的@Autowired代码示例
public class FXNewsProvider
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
@Autowired
public void setUp(IFXNewsListener newsListener,IFXNewsPersister newPersistener)
this.newsListener = newsListener;
this.newPersistener = newPersistener;
...
现在,虽然可以随意地在类定义的各种合适的地方标注@Autowired,希望这些被@Autowired标注的依赖能够被注入,但是,仅将@Autowired标注于类定义中并不能让Spring的IoC容器聪明到自己去查看这些注解,然后注入符合条件的依赖对象。容器需要某种方式来了解,哪些对象标注了@Autowired,哪些对象可以作为可供选择的依赖对象来注入给需要的对象。
在考虑使用什么方式实现这一功能之前,我们先比较一下原有的自动绑定功能与使用@Autowired之后产生了哪些差别。
使用自动绑定的时候,我们将所有对象相关的bean定义追加到了容器的配置文件中,然后使用default-autowire或者autowire告知容器,依照这两种属性指定的绑定方式,将容器中各个对象绑定到一起。
在使用@Autowired之后,default-autowire或者autowire的职责就转给了@Autowired,所以,现在,容器的配置文件中就只剩下了一个个孤伶伶的bean定义,如下所示:
<beans>
<bean id="newsProvider" class="..FXNewsProvider"/> 2
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
为了给容器中定义的每个bean定义对应的实例注入依赖,可以遍历它们,然后通过反射,检查每个bean定义对应的类上各种可能位置上的@Autowired。
如果存在的话,就可以从当前容器管理的对象中获取符合条件的对象,设置给@Autowired所标注的属性域、构造方法或者方法定义。
容器遍历@Autowired并进行依赖注入的原型代码示例
Object[] beans = ...;
for(Object bean:beans)
if(autowiredExistsOnField(bean))
Field f = getQulifiedField(bean));
setAccessiableIfNecessary(f);
f.set(getBeanByTypeFromContainer());
if(autowiredExistsOnConstructor(bean))
...
if(autowiredExistsOnMethod(bean))
...
看到以上的原型代码所要完成的功能以及我们的设想,你一定想到了,我们可以提供一个Spring的IoC容器使用的BeanPostProcessor自定义实现,让这个BeanPostProcessor在实例化bean定义的过程中,来检查当前对象是否有@Autowired标注的依赖需要注入。
org.springframework.beans. factory.annotation.AutowiredAnnotationBeanPostProcessor
就是Spring提供的用于这一目的的BeanPostProcessor实现。所以,很幸运,我们不用自己去实现它了。
将FXNews相关类定义使用@Autowired标注之后,只要在IoC容器的配置文件中追加AutowiredAnnotationBeanPostProcessor
就可以让整个应用开始运作了,如下所示:
<beans>
<bean class="org.springframework.beans.factory.annotation. ➥
AutowiredAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/> 15
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
当然,这需要我们使用ApplicationContext类型的容器,否则还得做点儿多余的准备工作
注意
看着依赖注入相关的信息,一半分散在 看着依赖注入相关的信息,一半分散在Java源代码中(@Autowired标注的信息),一半 标注的信息),一半依然留在XML配置文件里,你心里一定觉得很不爽。实际上,我也是,这不是折腾人吗?
不过,别急,让我们先解决眼前的另一个问题,稍后再回过头来看看怎么进一步统一这两片国土。
2. @Qualifier的陪伴
@Autowired是按照类型进行匹配,如果当前@Autowired标注的依赖在容器中只能找到一个实例与之对应的话,那还好。可是,要是能够同时找到两个或者多个同一类型的对象实例,又该怎么办呢?
我们自己当然知道应该把具体哪个实例注入给当前对象,可是,IoC容器并不知道,所以,得通过某种方式告诉它。这时,就可以使用@Qualifier对依赖注入的条件做进一步限定,使得容器不再迷茫。
@Qualifier实际上是byName自动绑定的注解版,既然IoC容器无法自己从多个同一类型的实例中选取我们真正想要的那个,那么我们不妨就使用@Qualifier直接点名要哪个好了。假设FXNewsProvider使用的IFXNewsListener有两个实现,一个是DowJonesNewsListener,一个是ReutersNewsListener,二者相关配置如下
<beans>
<bean class="org.springframework.beans.factory.annotation. ➥
AutowiredAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="reutersNewsListner" class="..ReutersNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
如果我们想让FXNewsProvider使用ReutersNewsListener,那么就可以在FXNewsProvider的类定义中使用@Qualifier指定这一选择结果,如下:
public class FXNewsProvider
@Autowired
@Qualifier("reutersNewsListner")
private IFXNewsListener newsListener;
@Autowired
private IFXNewsPersister newPersistener;
...
以上我们使用的是标注于属性域的@Autowired进行依赖注入。如果使用@Autowired来标注构造方法或者方法定义的话,同样可以使用@Qualifier标注方法参数来达到限定注入实例的目的。
标注于方法参数之上的@Qualifier
public class FXNewsProvider
...
@Autowired
public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener)
this.newsListener = newsListener;
this.newPersistener = newPersistener;
public class FXNewsProvider
...
@Autowired
public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener ➥
newsListener,IFXNewsPersister newPersistener)
this.newsListener = newsListener;
this.newPersistener = newPersistener;
...
除此之外,@Qualifier还可以用于标注注解类型,这主要用于自定义@Qualifier的场合。有关自定义@Qualifier的内容我们这里就不做赘述了。
@Autowired之外的选择——使用JSR250 标注依赖注入关系
Spring 2.5提供的基于注解的依赖注入,除了可以使用Spring提供的@Autowired和@Qualifier来标注相应类定义之外,还可以使用JSR250的@Resource和@PostConstruct以及@PreDestroy对相应类进行标注,这同样可以达到依赖注入的目的。
@Resource与@Autowired不同,它遵循的是byName自动绑定形式的行为准则,也就是说,IoC容器将根据@Resource所指定的名称,到容器中查找beanName与之对应的实例,然后将查找到的对象实例注入给@Resource所标注的对象。同样的FXNewsProvider,如若使用@Resource进行标注以获取依赖注入的话,类似如下的样子:
public class FXNewsProvider
@Resource(name="djNewsListener") 7
private IFXNewsListener newsListener;
@Resource(name="djNewsPersister")
private IFXNewsPersister newPersistener;
...
JSR250规定,如果@Resource标注于属性域或者方法之上的话,相应的容器将负责把指定的资源注入给当前对象,所以,除了像我们这样直接在属性域上标注@Resource,还可以在构造方法或者普通方法定义上标注@Resource,这与@Autowired能够存在的地方大致相同。
确切地说,@PostConstruct和@PreDestroy不是服务于依赖注入的,它们主要用于标注对象生命周期管理相关方法,这与Spring的InitializingBean和DisposableBean接口,以及配置项中的init-method和destroy-method起到类似的作用。
使用@PostConstruct和@PreDestroy标注对象的生命周期管理方法
public class LifecycleEnabledClass
@PostConstruct
public void setUp()
...
@PreDestroy
public void destroy()
...
如果想某个方法在对象实例化之后被调用,以做某些准备工作,或者想在对象销毁之前调用某个方法清理某些资源,那么就可以像我们这样,使用@PostConstruct和@PreDestroy来标注这些方法。
当然,是使用@PostConstruct和@PreDestroy,还是使用Spring的InitializingBean和DisposableBean接口,或者init-method和destroymethod配置项,可以根据个人的喜好自己决定。
天上永远不会掉馅饼,我们只是使用@Resource或者@PostConstruct和@PreDestroy标注了相应对象,并不能给该对象带来想要的东西。所以,就像@Autowired需要AutowiredAnnotationBeanPostProcessor为它与IoC容器牵线搭桥一样,JSR250的这些注解也同样需要一个BeanPostProcessor帮助它们实现自身的价值。这个BeanPostProcessor就是org.springframework.context.
annotation.CommonAnnotationBeanPostProcessor,只有将CommonAnnotationBeanPostProcessor添加到容器,JSR250的相关注解才能发挥作用,通常如下添加相关配置即可:
<beans>
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<bean id="djNewsListener" class="..DowJonesNewsListener"/>
<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
</beans>
既然不管是@Autowired还是@Resource都需要添加相应的BeanPostProcessor到容器,那么我们就可以在基于XSD的配置文件中使用一个<context:annotation-config >配置搞定以上所有的BeanPostProcessor配置。
使用<context:annotation-config />激活注解的相关功能
<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:p="http://www.springframework.org/schema/p" ➥
xsi:schemaLocation=" ➥
http://www.springframework.org/schema/beans ➥
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ➥
http://www.springframework.org/schema/context ➥
http://www.springframework.org/schema/context/spring-context-2.5.xsd"> ➥
<context:annotation-config/>
<bean id="newsProvider" class="..FXNewsProvider"/>
<!--其他bean定义-->
...
</beans>
< context:annotation-config> 不但帮我们把 AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor注册到容器,同时还会把PersistenceAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor一并进行注册,可谓一举四得啊!
注意
Spring提供的@Autowired加上@Qualifier和JSR250提供的@Resource等注解属于两个派系。
如果要实现依赖注入的话,使用一个派别的注解就可以了。
当然,既然<context:annotation-config >对两个派系都提供了BeanPostProcessor的支持,混合使用也是没有问题的,只要别造成使用上的混乱就行
将革命进行得更彻底一些(classpath-scanning 功能介绍)
好了,该来解决让我们不爽的那个问题了。到目前为止,我们还是需要将相应对象的bean定义,一个个地添加到IoC容器的配置文件中。
与之前唯一的区别就是,不用在配置文件中明确指定依赖关系了(改用注解来表达了嘛)。既然使用注解来表达对象之间的依赖注入关系,那为什么不搞的彻底一点儿,将那些几乎“光秃秃”的bean定义从配置文件中彻底消灭呢?
OK,我们想到了,Spring开发团队也想到了,classpath-scanning的功能正是因此而诞生的!
使用相应的注解对组成应用程序的相关类进行标注之后,classpath-scanning功能可以从某一顶层包(base package)开始扫描。
当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。
这之后所发生的事情就不用我说了,既然相关的类已经添加到了容器,那么后面BeanPostProcessor为@Autowired或者@Resource所提供的注入肯定是有东西拿咯!
classpath-scanning功能的触发是由<context:component-scan >决定的。按照如下代码,在XSD形式(也只能是XSD形式)的配置文件中添加该项配置之后,classpath-scanning功能立即开启:
<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" ➥ 6
xsi:schemaLocation="http://www.springframework.org/schema/beans ➥
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ➥
7 http://www.springframework.org/schema/context ➥
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21"/>
</beans>
现在<context:component-scan >将遍历扫描org.spring21路径下的所有类型定义,寻找标注了相应注解的类,并添加到IoC容器。
提示
如果要扫描的类定义存在于不同的源码包下面,也可以为base-package指定多个以逗号分隔的扫描路径。需要的话,不要犹豫!
<context:component-scan >默认扫描的注解类型是@Component。
不过,在@Component语义基础上细化后的@Repository、@Service和@Controller也同样可以获得< context:component-scan>
的青睐。
@Component的语义更广、更宽泛,而@Repository、@Service和@Controller的语义则更具体。所以,同样对于服务层的类定义来说,使用@Service标注它,要比使用@Component更为确切。
对于其他两种注解也是同样道理,我们暂且使用语义更广的@Component来标注FXNews相关类,以便摆脱每次都要向IoC容器配置添加bean定义的苦恼。
使用@Component标注后的FXNews相关类定义
@Component
public class FXNewsProvider
@Autowired
private IFXNewsListener newsListener;
@Autowired
private IFXNewsPersister newPersistener;
...
@Component("djNewsListener")
public class DowJonesNewsListener implements IFXNewsListener
...
@Component
public class DowJonesNewsPersister implements IFXNewsPersister
...
< context:component-scan>在扫描相关类定义并将它们添加到容器的时候,会使用一种默认的命名规则,来生成那些添加到容器的bean定义的名称(beanName)。
比如DowJonesNewsPersister通过默认命名规则将获得dowJonesNewsPersister作为bean定义名称。如果想改变这一默认行为,就可
以像以上DowJonesNewsListener所对应的@Component那样,指定一个自定义的名称。
另一种方法就是使用自定义实现的BeanNameGenerator,通过<context:component-scan >的name-generator属性指定我们自己的BeanNameGenerator实现类来替换掉默认的BeanNameGenerator,也可以改变默认的bean定义
名称生成规则。
现在,除了<context:component-scan >是唯一需要添加到IoC容器的配置内容,所有的工作都可以围绕着使用注解的Java源代码来完成了。如果现在加载配置文件,启动FXNewProvider来处理外汇新闻的话,我们可以得到预期的运行效果,运行的代码如下所示:
ApplicationContext ctx = new ClassPathXmlApplicationContext("../conf.xml");
FXNewProvider provider = (FXNewProvider)ctx.getBean("FXNewsProvider");
provider.getAndPersistNews();
注意,我们通过FXNewsProvider作为bean定义名称来获取FXNewsProvider的实例,对于开头都是大写的类名来
说,bean定义名称实际上就相当于类名,这与默认的命名生成规则有些小小的差异。
你或许会觉得有些诧异,因为我们并没有使用< context:annotation-config >甚至直接将相应的BeanPostProcessor添加到容器中,而FXNewsProvider怎么会获得相应的依赖注入呢?
这个得怪< context:component-scan>“多管闲事”,它同时将AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor一并注册到了容器中,所以,依赖注入的需求得以满足。
如果你不喜欢,非要自己通过 < context:annotation-config> 或者直接添加相关 BeanPostProcessor的方式来满足@Autowired或者@Resource的需求,可以将<context:component-scan >的annotation-config属性值从默认的true改为false。不过,我想没有太好的理由非要这么做吧?
< context:component-scan>的扫描行为可以进一步定制,默认情况下它只关心@Component、@Repository、@Service和@Controller四位大员,但我们可以丰富这一范围,或者对默认的扫描结果进行过滤以排除某些类,< context:component-scan>的嵌套配置项可以帮我们达到这一目的。
<context:component-scan >部分嵌套配置项的使用示例
<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" ➥
xsi:schemaLocation="http://www.springframework.org/schema/beans ➥
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ➥
http://www.springframework.org/schema/context ➥
http://www.springframework.org/ schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="org.spring21">
<context:include-filter type="annotation" ➥
expression="cn.spring21.annotation.FXService"/>
<context:exclude-filter type="aspectj" expression=".."/>
</context:component-scan>
</beans>
include-filter和exclude-filter可以使用的type类型有annotation、assignable、regex和aspectj四种。它们的更多信息可以参考最新的Spring 参考文档。上例中,我们增加了@FXService作为新的被扫描注解对象,并使用aspectj表达式排除某些扫描结果。
以上是关于Spring复杂的IOC容器之短小的注解篇的主要内容,如果未能解决你的问题,请参考以下文章
Spring IOC官方文档学习笔记之基于Java的容器配置
JAVAWEB开发之Spring详解之——Spring的入门以及IOC容器装配Bean(xml和注解的方式)Spring整合web开发整合Junit4测试