Spring 3.0 依赖注入的注解实现

2015-11-11 16:00:13   最后更新: 2016-11-10 17:15:16   访问数量:729




Spring 的依赖配置方式与 Spring 框架的内核自身是松耦合设计的

然而,直到 Spring 3.0 以前,使用 XML 进行依赖配置几乎是唯一的选择

Spring 3.0 的出现改变了这一状况,它提供了一系列的针对依赖注入的注解,这使得 Spring IoC 在 XML 文件之外多了一种可行的选择

想要启用这些注解首先需要在配置文件中加入

<context:component-scan base-package=”com.mypackage”/>

 

 

@Repository 注解

@Repository 注解用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean

如我们上篇日志中的那样:

SpringMVC 应用 JDBC 访问数据库

 

Spring 在容器初始化时将自动扫描 base-package 指定的包及其子包下的所有 class 文件,所有标注了 @Repository 的类都将被注册为 Spring Bean

 

为什么 @Repository 只能标注在 DAO 类上呢?这是因为该注解的作用不只是将类识别为 Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型

Spring 本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架

 

@Component、@Service、@Constroller

Spring 2.5 在 @Repository 的基础上增加了功能类似的额外三个注解:@Component、@Service、@Constroller,它们分别用于软件系统的不同层次:

  • @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次
  • @Service 通常作用在业务层,但是目前该功能与 @Component 相同
  • @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同

 

通过在类上使用 @Repository、@Component、@Service 和 @Constroller 注解,Spring 会自动创建相应的 BeanDefinition 对象,并注册到 ApplicationContext 中。这些类就成了 Spring 受管组件。这三个注解除了作用于不同软件层次的类,其使用方式与 @Repository 是完全相同的

 

同时,可以通过 @Named 为 bean 命名:

@Service @Named("testService") public class TestService { ... }

 

 

当一个 Bean 被自动检测到时,会根据那个扫描器的 BeanNameGenerator 策略生成它的 bean 名称。默认情况下,对于包含 name 属性的 @Component、@Repository、 @Service 和 @Controller,会把 name 取值作为 Bean 的名字。如果这个注解不包含 name 值或是其他被自定义过滤器发现的组件,默认 Bean 名称会是小写开头的非限定类名。如果你不想使用默认 bean 命名策略,可以提供一个自定义的命名策略。首先实现 BeanNameGenerator 接口,确认包含了一个默认的无参数构造方法。然后在配置扫描器时提供一个全限定类名

与通过 XML 配置的 Spring Bean 一样,通过上述注解标识的 Bean,其默认作用域是"singleton",为了配合这四个注解,在标注 Bean 的同时能够指定 Bean 的作用域,Spring 2.5 引入了 @Scope 注解。使用该注解时只需提供作用域的名称就行了

@Scope("prototype") @Repository public class Demo { … }

 

 

prototype 意味着该类被实现为单例模式,在多个请求中共享

 

如果你想提供一个自定义的作用域解析策略而不使用基于注解的方法,只需实现 ScopeMetadataResolver 接口,确认包含一个默认的没有参数的构造方法。然后在配置扫描器时提供全限定类名:

<context:component-scan base-package="a.b" scope-resolver="footmark.SimpleScopeResolver" />

 

 

Spring Bean 是受 Spring IoC 容器管理,由容器进行初始化和销毁的(prototype 类型由容器初始化之后便不受容器管理),通常我们不需要关注容器对 Bean 的初始化和销毁操作,由 Spring 经过构造函数或者工厂方法创建的 Bean 就是已经初始化完成并立即可用的

然而在某些情况下,可能需要我们手工做一些额外的初始化或者销毁操作,这通常是针对一些资源的获取和释放操作

 

Spring 提供了一下三种方法使用用户指定执行生命周期回调的方法:

  1. 实现 Spring 提供的两个接口:InitializingBean 和 DisposableBean,如果希望在 Bean 初始化完成之后执行一些自定义操作,则可以让 Bean 实现 InitializingBean 接口,该接口包含一个 afterPropertiesSet() 方法,容器在为该 Bean 设置了属性之后,将自动调用该方法;如果 Bean 实现了 DisposableBean 接口,则容器在销毁该 Bean 之前,将调用该接口的 destroy() 方法。这种方式的缺点是,让 Bean 类实现 Spring 提供的接口,增加了代码与 Spring 框架的耦合度,因此不推荐使用
  2. 在 XML 文件中使用 <bean> 的 init-method 和 destroy-method 属性指定初始化之后和销毁之前的回调方法,代码无需实现任何接口。这两个属性的取值是相应 Bean 类中的初始化和销毁方法,方法名任意,但是方法不能有参数
  3. 使用 @PostConstruct 和 @PreDestroy 这两个注解,只需分别将他们标注于初始化之后执行的回调方法或者销毁之前执行的回调方法上

 

除第一种方法不推荐使用外,另两种方法可以自由选择,但是最好不要混用,以免增加维护难度

 

依赖检查用来判断给定 Bean 的相应 Setter 方法是否都在实例化的时候被调用了

Spring 进行依赖检查时,只会判断属性是否使用了 Setter 注入,如果某个属性没有使用 Setter 注入,即使是通过构造函数已经为该属性注入了值,Spring 仍然认为它没有执行注入,从而抛出异常,但Spring 只管是否通过 Setter 执行了注入,而对注入的值却没有任何要求,即使注入的 <null/>,Spring 也认为是执行了依赖注入

 

<bean> 标签提供了 dependency-check 属性用于进行依赖检查。该属性的取值包括以下几种:

  • none -- 默认不执行依赖检查。可以在 <beans> 标签上使用 default-dependency-check 属性改变默认值
  • simple -- 对原始基本类型和集合类型进行检查
  • objects -- 对复杂类型进行检查(除了 simple 所检查类型之外的其他类型)
  • all -- 对所有类型进行检查

 

Spring2.0 提供的 @Required 注解提供了更细粒度的控制

 

@Required 注解只能标注在 Setter 方法之上。因为依赖注入的本质是检查 Setter 方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非 setXxxx() 类型的方法则被忽略

当某个被标注了 @Required 的 Setter 方法没有被调用,则 Spring 在解析的时候会抛出异常,以提醒开发者对相应属性进行设置

 

自动装配是指,Spring 在装配 Bean 的时候,根据指定的自动装配规则,将某个 Bean 所需要引用类型的 Bean 注入进来

<bean> 元素提供了一个指定自动装配类型的 autowire 属性,该属性有如下选项:

  • no -- 显式指定不使用自动装配
  • byName -- 如果存在一个和当前属性名字一致的 Bean,则使用该 Bean 进行注入。如果名称匹配但是类型不匹配,则抛出异常。如果没有匹配的类型,则什么也不做
  • byType -- 如果存在一个和当前属性类型一致的 Bean ( 相同类型或者子类型 ),则使用该 Bean 进行注入。byType 能够识别工厂方法,即能够识别 factory-method 的返回类型。如果存在多个类型一致的 Bean,则抛出异常。如果没有匹配的类型,则什么也不做
  • constructor -- 与 byType 类似,只不过它是针对构造函数注入而言的。如果当前没有与构造函数的参数类型匹配的 Bean,则抛出异常。使用该种装配模式时,优先匹配参数最多的构造函数
  • autodetect -- 根据 Bean 的自省机制决定采用 byType 还是 constructor 进行自动装配。如果 Bean 提供了默认的构造函数,则采用 byType;否则采用 constructor 进行自动装配

 

当使用 byType 或者 constructor 类型的自动装配的时候,自动装配也支持引用类型的数组或者使用了泛型的集合,这样,Spring 就会检查容器中所有类型匹配的 Bean,组成集合或者数组后执行注入。对于使用了泛型的 Map 类型,如果键是 String 类型,则 Spring 也会自动执行装配,将所有类型匹配的 Bean 作为值,Bean 的名字作为键

我们可以给 <beans> 增加 default-autowire 属性,设置默认的自动封装策略。默认值为"no"。如果使用自动装配的同时,也指定了 property 或者 constructor-arg 标签,则显式指定的值将覆盖自动装配的值

目前的自动封装不支持简单类型,比如基本类型、String、Class,以及它们的数组类型

 

在按类型匹配的时候 ( 可能是 byType、constructor、autodetect),同一个类型可能存在多个 Bean,如果被注入的属性是数组、集合或者 Map,这可能没有问题,但是如果只是简单的引用类型,则会抛出异常

解决方法有如下几种:

  1. 取消该 Bean 的自动装配特性,使用显式的注入。我们可能不希望某个 Bean 被当作其他 Bean 执行自动封装时的候选对象,我们可以给该 <bean> 增加 autowire-candidate="false"。(autowire-candidate 属性和 autowire 属性相互独立,互不相干。某个 Bean 可以将 autowire-candidate 设置为 false,同时使用 autowire 特性。) 另外,我们可以设置 <beans> 的 default-autowire-candidates 属性,可以在该属性中指定可以用于自动装配候选 Bean 的匹配模式,比如 default-autowire-candidates="*serv,*dao",这表示所有名字以 serv 或者 dao 结尾的 Bean 被列为候选,其他则忽略,相当于其他 Bean 都指定为 autowire-candidate="false",此时可以显式为 <bean> 指定 autowire-candidate="true"。在 <bean> 上指定的设置要覆盖 <beans> 上指定的设置
  2. 如果在多个类型相同的 Bean 中有首选的 Bean,那么可以将该 <bean> 的 primary 属性设置为 "true" ,这样自动装配时便优先使用该 Bean 进行装配。此时不能将 autowire-candidate 设为 false
  3. 如果使用的是 Java 5 以上版本,可以使用注解进行更细粒度的控制

 

@Autowired 注解

使用 @Autowired 注解进行装配,只能是根据类型进行匹配

@Autowired 注解可以用于 Setter 方法、构造函数、字段,甚至普通方法,前提是方法必须有至少一个参数。@Autowired 可以用于数组和使用泛型的集合类型。然后 Spring 会将容器中所有类型符合的 Bean 注入进来

@Autowired 标注作用于 Map 类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key

@Autowired 标注作用于普通方法时,会产生一个副作用,就是在容器初始化该 Bean 实例的时候就会调用该方法。当然,前提是执行了自动装配,所以对于不满足装配条件的情况,该方法也不会被执行

当标注了 @Autowired 后,自动注入不能满足,则会抛出异常。我们可以给 @Autowired 标注增加一个 required=false 属性,以改变这个行为。另外,每一个类中只能有一个构造函数的 @Autowired.required() 属性为 true。否则就出问题了。如果用 @Autowired 同时标注了多个构造函数,那么,Spring 将采用贪心算法匹配构造函数 ( 构造函数最长 )

@Autowired 还有一个作用就是,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作

 

Qualifier 注解指定自动装配的 name

当容器中存在多个 Bean 的类型与需要注入的相同时,注入将不能执行,我们可以给 @Autowired 增加一个候选值,做法是在 @Autowired 后面增加一个 @Qualifier 标注,提供一个 String 类型的值作为候选的 Bean 的名字:

@Autowired(required=false) @Qualifier("ppp") public void setPerson(person p){}

 

 

Qualifier 甚至可以作用于方法的参数 ( 对于方法只有一个参数的情况,我们可以将 @Qualifer 标注放置在方法声明上面,但是推荐放置在参数前面 ):

@Autowired(required=false) public void sayHello(@Qualifier("ppp")Person p,String name){}

 

 

我们可以在配置文件中指定某个 Bean 的 qualifier 名字:

<bean id="person" class="footmark.spring.Person"> <qualifier value="ppp"/> </bean>

 

 

如果没有明确指定 Bean 的 qualifier 名字,那么默认名字就是 Bean 的名字。通常,qualifier 应该是有业务含义的,例如 "domain","persistent" 等,而不应该是类似 "person" 方式

 

我们还可以将 @Qualifier 标注在集合类型上,那么所有 qualifier 名字与指定值相同的 Bean 都将被注入进来

 

最后,配置文件中需要指定每一个自定义注解的属性值。我们可以使用 <meta> 标签来代替 <qualifier/> 标签,如果 <meta> 标签和 <qualifier/> 标签同时出现,那么优先使用 <qualifier> 标签。如果没有 <qualifier> 标签,那么会用 <meta> 提供的键值对来封装 <qualifier> 标签:

<bean class="footmark.HelloWorld"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Comedy"/> </qualifier> </bean> <bean class="footmark.HelloWorld"> <meta key="format" value="DVD"/> <meta key="genre" value="Action"/> </bean>

 

 

如果 @Autowired 注入的是 BeanFactory、ApplicationContext、ResourceLoader 等系统类型,那么则不需要 @Qualifier,此时即使提供了 @Qualifier 注解,也将会被忽略;而对于自定义类型的自动装配,如果使用了 @Qualifier 注解并且没有名字与之匹配的 Bean,则自动装配匹配失败

 

如果希望根据 name 执行自动装配,那么应该使用 @Resource 注解,而不是使用 @Autowired 与 @Qualifier 的组合

@Resource 使用 byName 的方式执行自动封装。@Resource 标注可以作用于带一个参数的 Setter 方法、字段,以及带一个参数的普通方法上。@Resource 注解有一个 name 属性,用于指定 Bean 在配置文件中对应的名字。如果没有指定 name 属性,那么默认值就是字段或者属性的名字。@Resource 和 @Qualifier 的配合虽然仍然成立,但是 @Qualifier 对于 @Resource 而言,几乎与 name 属性等效

如果 @Resource 没有指定 name 属性,那么使用 byName 匹配失败后,会退而使用 byType 继续匹配,如果再失败,则抛出异常。在没有为 @Resource 注解显式指定 name 属性的前提下,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作。此时 name 属性不需要指定 ( 或者指定为""),否则注入失败;如果使用了 @Qualifier,则该注解将被忽略。而对于用户自定义类型的注入,@Qualifier 和 name 等价,并且不被忽略

 

很多时候,我们希望将配置文件中的配置属性及对应的值注入到我们的基本类型成员中,在 Spring3.0 以上,提供了 @Value 注解,实现了相应的功能

首先,在 ApplicationContext.xml 中,加入命名空间 xmlns:p="springframework.org/schema/p",并指定配置文件:

<!-- 导入属性配置文件 --> <bean id="configuration" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath*:config.properties</value> </list> </property> </bean>

 

然后,在需要注入的成员中加入 @Value 注解,并指定配置文件中的 key:

private @Value("${propertyName}") String propertyField;

 

 

加载多个不同的配置文件

也可以通过配置多个 PropertiesFactoryBean 实现加载多个不同的配置文件

<!-- 导入属性配置文件 --> <bean id="configuration" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath*:config.properties</value> </list> </property> </bean> <bean id="dbConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath*:dbconfig.properties</value> </list> </property> </bean>

 

然后,在需要注入的成员中加入 @Value 注解,并指定配置文件中的 key:

private @Value("#{configuration[propertyName]}") String propertyField; private @Value("#{dbConfiguration[dbPropertyName]}") String dbPropertyField;

 

 

详解 Spring 3.0 基于 Annotation 的依赖注入实现 http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-iocannt/

使用 Java 配置进行 Spring bean 管理 https://www.ibm.com/developerworks/cn/webservices/ws-springjava/

Spring 3.0 Reference Documentation http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/?cm_mc_uid=28330255546614387512535&cm_mc_sid_50200000=1447220650

Servlet 3 + Spring MVC零配置:去除所有xml http://blog.csdn.net/xiao__gui/article/details/46803193

《spring 实战(第三版)》第三章

 






技术帖      controller      mvc      龙潭书斋      java      service      jdk      spring      springmvc      dao      注解      annotation      repository      component     


京ICP备15018585号