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

  • 相关阅读:
    v-model
    CSS background 属性
    渐变背景
    mint ui的field用法和修改样式的方法
    js 数组包含
    password 密码查询
    web 单一平台登录逻辑
    内存共享锁业务逻辑(原创)
    无限分类树操作
    根据日期获取,x岁x月x天
  • 原文地址:https://www.cnblogs.com/jwen1994/p/11073315.html
Copyright © 2020-2023  润新知