Spring 基于注解的配置

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 的方法。

原文地址:https://www.cnblogs.com/wsy0202/p/12874994.html