1.使用注解定义Bean
前面说过,不管是 XML 还是注解,它们都是表达 Bean 定义的载体,其实质都是为 Spring 容器提供 Bean 定义的信息,在表现形式上都是将 XML 定义的内容通过类注解进行描述。Spring 从2.0开始就引入了基于注解的配置方式,在2.5时得到了完善,在4.0时进一步增强。
我们知道,Spring 容器成功启动的三大要件分别是 Bean定义信息、Bean实现类及 Spring 本身。如果采用基于XML 的配置,则 Bean定义信息和 Bean实现类本身是分离的:而如果采用基于注解的配置文件,则 Bean定义信息通过在 Bean实现类上标注注解实现。
下面是使用注解定义一个 DAO 的 Bean:
import org.springframework.stereotype.Component; //①通过 Repository 定义一个 DAO 的 Bean @Component("userDao") public class UserDao { ... }
在①处使用 @Component 注解在 UserDao 类声明处对类进行标注,它可以被 Spring 容器识别,Spring 容器自动将 POJO 转换为容器管理的 Bean。它和以下 XML 配置是等效的:
<bean id="userDao" class="com.smart.anno.UserDao"/>
除 @Component 外,Spring还提供了3个功能基本和 @Component 等效的注解,分别用于对 DAO、Service及 Web 层的 Controller 进行注解。
@Repository:用于对 DAO 实现类进行标注。
@Service:用于对 Service 实现类进行标注。
@Controller:用于对 Controller 实现类进行标注。
之所以要在 @Component 之外提供这3个特殊的注解,是为了让标注类本身的用途清晰化,完全可以用 @Component 替代这3个特殊的注解。但是,我们推荐使用特定的注解标注特定的 Bean,毕竟这样一眼就可以看出 Bean 的真实身份。
2.扫描注解定义的Bean
Spring 提供了一个 context 命名空间,它提供了通过扫描类包以应用注解定义 Bean 的方式,如下所示。
<?xml version="1.0" encoding="UTF-8" ?> <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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.smart.anno"/> </beans>
通过 context 命名空间的 component-scan 的 base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包里的所有类,并从类的注解信息中获取 Bean 的定义信息。
如果仅希望扫描特定的类而非基包下的所有类,那么可以使用 resource-pattern 属性过滤出特定的类,如下:
<context:component-scan base-package="com.smart" resource-pattern="anno/*.class" />
这里将基类包设置为 com.smart;默认情况下 resource-pattern 属性的值为“**/*.class”,即基类包里的所有类,将其设置为"anno/*.class",则 Spring 仅会扫描基类包里 anno 子包中的类。
通过 resource-pattern 属性可以按资源名称对基类包中的类进行过滤。如果只使用 resource-pattern,就会发现很多时候它并不能满足要求,如仅需过滤基类包中实现了 XxxSemce 接口的类或标注了某个特定注解的类等。
不过这些需求可以很容易地通过 <context:component-scan> 的过滤子元素实现,如下:
<context:component-scan base-package="com.smart"> <context:include-filter type="regex" expression="com.smart.anno.*Dao"/> <context:include-filter type="regex" expression="com.smart.anno.*Service"/> <context:exclude-filter type="aspectj" expression="com.smart..*Controller+"/> </context:component-scan>
<context:include-filter> 表示要包含的目标类,而 <context:exclude-filter> 表示要排除的目标类。
一个 <context:component-scan> 下可以拥有若干个 <context:exclude-filter> 和 <context:include-filter> 元素。这两个过滤元素均支持多种类型的过滤表达式,说明如下。
类型 | 示例 | 说明 |
annotation | com.smart.XxxAnnotation |
所有标注了XxxAnnotation的类。该类型采用目标类是否标注了某个注 |
assignable | com.smart.XxxService | 所有继承或扩展XxxService的类。该类型采用目标类是否继承或扩展了某个特定类进行过滤 |
aspectj | com.smart.*Service+ | 所有类名以Service结束的类及继承或扩展它们的类 |
regex | com.smart.anno..* | 所有com.smart.anno类包下的类。该类型采用正则表达式根据目标类的类名进行过滤 |
custom | com.smart.XxxTypeFilter | 采用XxxTypeFilter代码方式实现过滤规则。该类必须实现org.springframework.core.type.TypeFilter接口 |
在所有这些过滤类型中,除 custom 类型外,aspectj 的过滤表达能力是最强的,它可以轻易实现其他类型所能表达的过滤规则。
<context:component-scan/> 拥有一个容易被忽视的 use-default-filters 属性,其默认值为 true,表示默认会对标注 @Component、@Controller、@Service 及 @Reposity 的 Bean 进行扫描。<context:component-scan/> 先根据 <exclude-filter/> 列出需要排除的黑名单,再通过 <include-filter/> 列出需要包含的白名单。由于 use-default-filters 属性默认值的作用,下面的配置片段不但会扫描 @Controller 的Bean,还会扫描 @Component、@Service 及 @Reposity 的 Bean。
<context:component—scan base—package="com.smart"> <context:include—filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component—scan>
换言之,在以上配置中,加不加 <context:include-filter/> 的效果都是一样的。如果想仅扫描 @Controller 的Bean,则必须将 use-default-filters 属性设置为 false。
<context:component—scan base—package="com.smart" use-default-filters="false"> <context:include—filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component—scan>
3.自动装配Bean
1)使用 @Autowired 进行自动注入
Spring 通过 @Autowired 注解实现 Bean 的依赖注入。来看一个 LogonService 的例子,如下所示。
//①定义一个 Service 的 Bean @Service public class LogonService implements BeanNameAware { //②分别注入logDao和userDao的Bean @Autowired private LogDao logDao; @Autowired private UserDao userDao; }
在①处使用 @Service 将 LogonService 标注为一个 Bean,在②处通过 @Autowired 注入 LogDao 及 UserDao 的 Bean。@Autowired 默认按类型(byType)匹配的方式在容器中查找匹配的 Bean,当有且仅有一个匹配的 Bean 时,Spring 将其注入 @Autowired 标注的变量中。
2)使用 @Autowired 的 required 属性
如果容器中没有一个和标注变量类型匹配的 Bean,那么 Spring 容器启动时将报 NoSuchBeanDefinitionException 异常。如果希望 Spring 即使找不到匹配的 Bean 完成注入也不要抛出异常,那么可以使用 @Autowired(required=false) 进行标注,如下所示。
... @Service public class LogonService implements BeanNameAware { @Autowired(required=false) private LogDao logDao; ... }
在默认情况下,@Autowired 的 required 属性值为 ture,即要求必须找到匹配的 Bean,否则将报异常。
3)使用 @Qualifier 指定注入 Bean 的名称
如果容器中有一个以上匹配的 Bean时,则可以通过 @Qualifier 注解限定 Bean 的名称,如下所示。
@Service public class LogonService implements BeanNameAware { @Autowired private LogDao logDao; //① @Autowired @Qualifier("userDao") private UserDao userDao; }
这时,假设容器有两个类型为 UserDao 的 Bean,一个名为 userDao,另一个名为 otherUserDao,则①处会注入名为 userDao 的 Bean。
4)对类方法进行标注
@Autowired 可以对类成员变量及方法的入参进行标注,下面在类的方法上使用 @Autowired 注解,如下所示。
@Service public class LogonService implements BeanNameAware { private LogDao logDao; private UserDao userDao; //①自动将LogDao传给方法入参 @Autowired public void setLogDao(LogDao logDao) { this.logDao = logDao; } //②自动将名为userDao的Bean传给入参 @Autowired @Qualifier("userDao") public void setUserDao(UserDao userDao) { System.out.println("auto inject"); this.userDao = userDao; } }
如果一个方法拥有多个入参,则在默认情况下,Spring 将自动选择匹配入参类型的 Bean 进行注入。Spring 允许对方法入参标注 @Qualifier 以指定注入 Bean 的名称,如下:
@Autowired public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao){ System.out.println("multi param inject"); this.userDao = userDao; this.logDao =logDao; }
在以上例子中,UserDao 的入参注入名为 userDao 的 Bean,而 LogDao 的入参注入 LogDao 类型的Bean。
一般情况下,在 Spring 容器中大部分 Bean 都是单实例的,所以一般无须通过 @Repository、@Service 等注解的 value 属性为 Bean 指定名称,也无须使用 @Qualifier 注解按名称进行注入。
虽然 Spring 支持在属性和方法上标注自动注入注解 @Autowlred,但在实际项目开发中建议采用在方法上标注@Autowired 注解,因为这样更加“面向对象”,也方便单元测试的编写。如果将注解标注在私有属性上,则在单元测试时就很难用编程的办法设置属性值。
5)对集合类进行标注
如果对类中集合类的变量或方法入参进行 @Autowired 标注,那么 Spring 会将容器中类型匹配的所有 Bean 都自动注入进来。下面来看一个具体的例子,如下所示。
@Component public class MyComponent { //①Spring 会将容器中所有类型为Plugin的Bean注入这个变量中 @Autowired(required=false) private List<Plugin> plugins; //②将Plugin类型的Bean注入Map中 @Autowired private Map<String,Plugin> pluginMaps; public List<Plugin> getPlugins() { return plugins; } }
Spring 如果发现变量是一个 List 和一个 Map 集合类,则它会将容器中匹配集合元素类型的所有 Bean 都注入进来。在②处将实现 Plugin 接口的 Bean 注入 Map 集合,是 Spring4.0 提供的新特性,其中 key 是 Bean 的名字,value 是所有实现了 Plugin 的 Bean。
这里,Plugin 是一个接口,它拥有两个实现类,分别是 OnePlugin 和 TwoPlugin,其中 OnePlugin 如下所示。
@Component @Order(value = 1)//①指定此插件的加载顺序,值越小,优先被加载 public class OnePlugin implements Plugin{ }
通过 @Component 标注为 Bean,Spring 会将 OnePlugin 和 TwoPlugin 这两个 Bean都注入 plugins 中。在默认情况下,这两个 Bean 的加载顺序是不确定的,在Spring4.0中可以通过 @Order 注解或实现 Ordered 接口来决定Bean加载的顺序,值越小,优先被加载。
6)对延迟依赖注入的支持
Spring4.0 支持延迟依赖注入,即在 Spring 容器启动的时候,对于在 Bean 上标注 @Lazy 及 @Autowired 注解的属性,不会立即注入属性值,而是延迟到调用此属性的时候才会注入属性值,如下所示。
@Lazy//①此处需要标注延迟注解 @Repository public class LogDao implements InitializingBean { } @Service public class LogonService implements BeanNameAware { @Lazy//②此处需要标注延迟注解 @Autowired(required=false) private LogDao logDao; }
对 Bean 实施延迟依赖注入,要注意 @Lazy 注解必须同时标注在属性及目标Bean上,如示例的①和②处,二者缺一,则延迟注入无效。
7)对标准注解的支持
此外,Spring 还支持 JSR-250 中定义的 @Resource 和 JSR-330 中定义的 @lnject 注解,这两个标准注解和 @Autowired 注解的功用类似,都是对类变更及方法入参提供自动注入功能。@Resource 注解要求提供一个 Bean 名称的属性,如果属性为空,则自动采用标注处的变量名或方法名作为 Bean 的名称,如下所示。
@Component public class Boss { private Car car; @Resource("car") private void setCar(Car car){ System.out.println("execute in setCar"); this.car = car; } }
这时,如果 @Resource 未指定 "car” 属性,则也可以根据属性方法得到需要注入的 Bean 名称。可见 @Autowired 默认按类型匹配注入 Bean,@Resource 则按名称匹配注入Bean。而 @lnject 和 @Autowired 同样也是按类型匹配注入Bean的,只不过它没有 required 属性。可见,不管是 @Resource 还是 @lnject 注解,其功能都没有 @Autowired 丰富,因此,除非必要,大可不必在乎这两个注解。
4.Bean作用范围及生命过程方法
通过注解配置的 Bean 和通过 <bean> 配置的 Bean 一样,默认的作用范围都是 singleton。Spring 为注解配置提供了一个 @Scope 注解,可以通过它显式指定 Bean 的作用范围,如下所示。
@Scope(BeanDefinition.SCOPE_PROTOTYPE) @Component public class Car { ... }
在使用 <bean> 进行配置时,可以通过 init-method 和 destory-method 属性指定 Bean 的初始化及容器销毁前执行的方法。Spring 从2.5开始支持 JSR-250 中定义的 @PostConstruct 和 @PreDestroy 注解,在 Spring 中它们相当于 init-method 和 destory-method 属性的功能,不过在使用注解时,可以在一个 Bean 中定义多个 @PostConstruct 和 @PreDestroy 方法,如下所示。
@Component public class Boss { private Car car; public Boss(){ System.out.println("construct..."); } @Autowired private void setCar(Car car){ System.out.println("execute in setCar"); this.car = car; } @PostConstruct private void init1(){ System.out.println("execute in init1"); } @PostConstruct private void init2(){ System.out.println("execute in init2"); } @PreDestroy private void destory1(){ System.out.println("execute in destory1"); } @PreDestroy private void destory2(){ System.out.println("execute in destory2"); } }
运行如下代码启动和关闭容器
public class SimpleTest { public static void main(String[] args) throws Throwable { //①启动容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/anno/beans.xml"); //②关闭容器 ((ClassPathXmlApplicationContext)ctx).destroy(); } }
运行结果:
construct...
execute in setCar
execute in init1
execute in init2
execute in destory1
execute in destory2
这说明 Spring 先调用 Boss 的构造函数实例化 Bean,再执行 @Autowired 进行自动注入,然后分别执行标注了@PostConstruct 的方法,在容器关闭时,则分别执行标注了 @PreDestroy 的方法。