zoukankan      html  css  js  c++  java
  • Spring核心技术(八)——Spring自动装载的注解

    本文针对自动装载的一些注解进行描述。

    基于注解的容器配置

    @Required注解

    @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子:

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        @Required
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // ...
    
    }

    当Bean的属性配置了这个注解时,该Bean的属性必须在配置阶段就拥有明确的值,通过精确地Bean定义,或者通过自动装载。如果Bean的属性没有配置,容器会抛出异常。这一机制可以避免之后出现的空指针异常问题。当然,仍然推荐在Bean的类中使用断言,比如,在初始化方法中。这么做可以确保请求的引用或者值在容器外使用时是正确的。

    @Autowired

    JSR 330 的 @Inject注解在下面的例子中可以用来取代Spring的@Autowired注解。

    开发者可以在构造函数上面使用@Autowired注解:

    public class MovieRecommender {
    
        private final CustomerPreferenceDao customerPreferenceDao;
    
        @Autowired
        public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
            this.customerPreferenceDao = customerPreferenceDao;
        }
    
        // ...
    
    }

    在Spring 4.3的版本中,@Autowired在构造函数上面的注解就已经不需要了,如果Bean定义了构造函数,会自动扫描直接DI的。如果配置了多个构造函数,则需要配置@Autowired来告诉容器使用哪一个来进行依赖注入。

    当然,@Autowired可以注入到传统的setter方法上面:

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        @Autowired
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // ...
    
    }

    开发者也可以将这个注解运用到任何名字任何参数的方法上面:

    public class MovieRecommender {
    
        private MovieCatalog movieCatalog;
    
        private CustomerPreferenceDao customerPreferenceDao;
    
        @Autowired
        public void prepare(MovieCatalog movieCatalog,
                CustomerPreferenceDao customerPreferenceDao) {
            this.movieCatalog = movieCatalog;
            this.customerPreferenceDao = customerPreferenceDao;
        }
    
        // ...
    
    }

    开发者也可以将@Autowired用到域上面,甚至和构造函数混用:

    public class MovieRecommender {
    
        private final CustomerPreferenceDao customerPreferenceDao;
    
        @Autowired
        private MovieCatalog movieCatalog;
    
        @Autowired
        public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
            this.customerPreferenceDao = customerPreferenceDao;
        }
    
        // ...
    
    }

    当然,也可以根据类型,将所有的ApplicationContext中的类型的Bean进行依赖注入,可以通过注解将多个Bean注入到Array类型中。

    public class MovieRecommender {
    
        @Autowired
        private MovieCatalog[] movieCatalogs;
    
        // ...
    
    }

    也可以注入到集合类型之中:

    public class MovieRecommender {
    
        private Set<MovieCatalog> movieCatalogs;
    
        @Autowired
        public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
            this.movieCatalogs = movieCatalogs;
        }
    
        // ...
    
    }

    开发者所定义的Bean可以通过实现org.springframework.core.Ordered接口或者通过使用@Order或者标准的@Priority注解来确定注入到array或者list中的顺序。

    Map类型也可以注入进去,只要key的类型为String就可以注入进去。Map的值可以是任何一种容器中的Bean的类型,key当然就是对应Bean的名字。

    public class MovieRecommender {
    
        private Map<String, MovieCatalog> movieCatalogs;
    
        @Autowired
        public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
            this.movieCatalogs = movieCatalogs;
        }
    
        // ...
    
    }

    默认的话,自动装载会在没有候选Bean可用时失败,默认的行为就是将带有注解的方法,构造函数,以及实例变量作为必须的依赖。当然,这个默认的行为可以被改为如下代码:

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        @Autowired(required=false)
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
        // ...
    
    }

    每个类仅有一个注解构造函数可以被标记为必须的,但是非必须的够早函数可以注解多个。在存在多个非必须注解(required=false)的情况下,Spring会选择一个最贪婪的构造函数(满足最多的依赖的)。
    @Autowired注解要优于@Required注解。required属性来表明其属性是否为必须的,而当依赖不能被注入的话,就会被忽略掉。而@Required朱姐,是比@Autowired要强的,它会要求依赖必须注入。如果没有对应的依赖进行注入,就会抛出异常。

    开发者可以使用@Autowired来自动装载一些依赖:BeanFactory, ApplicationContext, Environment, ResourceLoader,ApplicationEventPublisher以及MessageSource等。这些接口以及其扩展接口,比如ConfigurableApplicationContext或者ResourcePatternResolver都可以解析,不用特殊配置。

    public class MovieRecommender {
    
        @Autowired
        private ApplicationContext context;
    
        public MovieRecommender() {
        }
    
        // ...
    
    }

    @Autowired,@Inject,@Resource以及@Value等注解都是通过Spring的BeanPostProcessor的实现来实现自动装载的,也就意味着开发者不能够通过自己的BeanPostProcessor或者BeanFactoryPostProcessor来应用这些注解。开发者自己的类型必须通过明确的XML或者Spring@Bean方法来定义。

    通过@Primary来自动装载

    由于通过类型来装载可能导致多个候选者,通常很有必要来控制选择依赖的过程,一种方式就是通过Spring的@Primary注解。@Primary注解表明当一个Bean需要进行依赖注入的时候,如果有多个候选者可能注入到单值的依赖之中,那么该Bean拥有优先权。如果只有一个primary的Bean的话,那么这个Bean将成为自动装载的依赖。

    参考如下代码:

    @Configuration
    public class MovieConfiguration {
    
        @Bean
        @Primary
        public MovieCatalog firstMovieCatalog() { ... }
    
        @Bean
        public MovieCatalog secondMovieCatalog() { ... }
    
        // ...
    
    }

    上面的代码中,firstMovieCatalog作为主要的MovieCatalog
    当使用了上面的配置后,下面的Bean在自动装载的时候,会装载firstMovieCatalog

    public class MovieRecommender {
    
        @Autowired
        private MovieCatalog movieCatalog;
    
        // ...
    
    }

    如果使用XML来进行定义的话,配置如下:

    <?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.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
        <bean class="example.SimpleMovieCatalog" primary="true">
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean class="example.SimpleMovieCatalog">
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean id="movieRecommender" class="example.MovieRecommender"/>
    
    </beans>

    通过限定符的自动装载

    @Primary在相同类型的几个实例之间选择选择依赖的时候,是一种很高效的方式。但是如果想要更多的控制自动装载的过程,@Qualifier注解就更为有用了。开发者可以通过将趣分期的值和参数来关联起来,以缩小类型匹配的范围,这样就能够自动装配到特值得Bean了。代码如下:

    public class MovieRecommender {
    
        @Autowired
        @Qualifier("main")
        private MovieCatalog movieCatalog;
    
        // ...
    
    }

    @Qualifier注解可以指定到构造函数的参数,或者是方法上面:

    public class MovieRecommender {
    
        private MovieCatalog movieCatalog;
    
        private CustomerPreferenceDao customerPreferenceDao;
    
        @Autowired
        public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
                CustomerPreferenceDao customerPreferenceDao) {
            this.movieCatalog = movieCatalog;
            this.customerPreferenceDao = customerPreferenceDao;
        }
    
        // ...
    
    }

    而依赖的Bean的在XMl的定义,配置如下:

    <?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.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
        <bean class="example.SimpleMovieCatalog">
            <qualifier value="main"/>
    
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean class="example.SimpleMovieCatalog">
            <qualifier value="action"/>
    
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean id="movieRecommender" class="example.MovieRecommender"/>
    
    </beans>

    在匹配中,Bean的名字就相当于默认的限定符的值。这样,开发者可以定义一个Bean的id为main而不使用限定符元素,也会有一样的匹配结果的。然而,尽管开发者可以通过这个便利来通过名字引用指定的Bean,@Autowired在根本上仍然是通过类型来进行依赖注入的,只不过有着可选的限定符。这也意味着限定符的值,甚至Bean的名字,总是能缩小值匹配的集合,但是并不能明确的如Bean的id一样指向一个唯一的Bean。好的限定符的值比如main或者EMEA或者persistent,所表示的是特定的组件,跟Bean的id是相互独立的。Bean的id,在前面匿名Bean中是会自动生成的。

    限定符也能够应用到集合类型的,如前面的例子中,Set<MovieCatalog>。在这种情况下,所有根据声明的趣分期的匹配的Bean都会被注入到集合之中。这也说明趣分期并不是唯一的,限定符更类似一种过滤标准。比如:开发者可以定义多个MovieCatalogBean而且配置其相同的限定符的值,比如说action,那么所有含有@Qualifier("action")的Bean都会注入到Set<MovieCatalog>之中。

    如果开发者希望通过名字来进行依赖注入,甚至仅仅使用@Qualifier的值进行注入,那么可以不用@Autowired而是使用JSR-250@Resource注解,这个注解是通过名字来进行依赖注入的,而与类型无关。
    对于本身定义为集合类型或者数组类型的Bean来说,@Resource是个很好的解决方案,可以通过名字来引用特定的集合或者数组元素。当然,在Spring4.3的版本中,集合类型,map以及数组类型也能通过Spring的@Autowired的类型匹配算法匹配到了。
    @Autowired可以用在实例变量,构造函数,或者多参数的方法上,允许通过限定符注解来缩小匹配范围。相比之下,@Resource仅仅在实例变量和Setter方法的情况下支持依赖注入。因此,如果需要在多参数的方法或者构造函数上约束注入的依赖,需要使用限定符而不要用@Resource

    开发者也可以定义自己的限定符注解,代码如下:

    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface Genre {
    
        String value();
    }

    之后就可以将自定义的限定符应用到自动装载的参数上面了,如下:

    public class MovieRecommender {
    
        @Autowired
        @Genre("Action")
        private MovieCatalog actionCatalog;
        private MovieCatalog comedyCatalog;
    
        @Autowired
        public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
            this.comedyCatalog = comedyCatalog;
        }
    
        // ...
    
    }

    下一步,需要给候选的Bean提供限定符信息。开发者可以通过在<bean/>的子标签<qualifier/>标签中来指定type的值来匹配自定义的限定符注解。匹配类型注解是完整的类名的,需要和自定义的类名一致。或者,如果没有命名冲突的话,可以仅仅使用类名。参考XML代码如下:

    <?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.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
        <bean class="example.SimpleMovieCatalog">
            <qualifier type="Genre" value="Action"/>
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean class="example.SimpleMovieCatalog">
            <qualifier type="example.Genre" value="Comedy"/>
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean id="movieRecommender" class="example.MovieRecommender"/>
    
    </beans>
    

    在很多情况下,仅仅使用注解就足够了。而且这一点在注解服务更为泛化的情况下更为有用。比如说,开发者需要提供一个线下的目录的话,可以参考如下的定义:

    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface Offline {
    
    }

    之后的使用不要指定值了,自动装载配置如下:

    public class MovieRecommender {
    
        @Autowired
        @Offline
        private MovieCatalog offlineCatalog;
    
        // ...
    
    }

    XML中的配置也较为简单,如下:

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Offline"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    当然,开发者也可以在一个value属性不足的情况下定义更多的限定符的参数来限定Bean的自动装载。如果在自定义的限定符注解中定义了多个限定的参数,那么Bean必须符合全部的参数才能被视为自动装载的候选者。参考如下的例子:

    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface MovieQualifier {
    
        String genre();
    
        Format format();
    
    }

    其中Format是一个枚举类型。

    public enum Format {
        VHS, DVD, BLURAY
    }

    在前面自定义的限定符中,则必须满足两个属性的匹配才能够进行依赖注入:

    public class MovieRecommender {
    
        @Autowired
        @MovieQualifier(format=Format.VHS, genre="Action")
        private MovieCatalog actionVhsCatalog;
    
        @Autowired
        @MovieQualifier(format=Format.VHS, genre="Comedy")
        private MovieCatalog comedyVhsCatalog;
    
        @Autowired
        @MovieQualifier(format=Format.DVD, genre="Action")
        private MovieCatalog actionDvdCatalog;
    
        @Autowired
        @MovieQualifier(format=Format.BLURAY, genre="Comedy")
        private MovieCatalog comedyBluRayCatalog;
    
        // ...
    
    }

    XML中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.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
        <bean class="example.SimpleMovieCatalog">
            <qualifier type="MovieQualifier">
                <attribute key="format" value="VHS"/>
                <attribute key="genre" value="Action"/>
            </qualifier>
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean class="example.SimpleMovieCatalog">
            <qualifier type="MovieQualifier">
                <attribute key="format" value="VHS"/>
                <attribute key="genre" value="Comedy"/>
            </qualifier>
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean class="example.SimpleMovieCatalog">
            <meta key="format" value="DVD"/>
            <meta key="genre" value="Action"/>
            <!-- inject any dependencies required by this bean -->
        </bean>
    
        <bean class="example.SimpleMovieCatalog">
            <meta key="format" value="BLURAY"/>
            <meta key="genre" value="Comedy"/>
            <!-- inject any dependencies required by this bean -->
        </bean>
    
    </beans>

    可以看出,限定符的定义是类似key-value的形式存在于<qualifier/>的子标签中的。除此之外,如果限定符有效,则优先选择匹配的Bean作为依赖进行注入,如果没有,自动装载的机制会在不存在限定符的时候自动退回到<meat/>,如上述代码的最后两个Bean。

    使用泛型作为自动装载的限定符

    关于@Qualifier注解,也可以使用Java泛型来作为限定符。比如说,有如下配置:

    @Configuration
    public class MyConfiguration {
    
        @Bean
        public StringStore stringStore() {
            return new StringStore();
        }
    
        @Bean
        public IntegerStore integerStore() {
            return new IntegerStore();
        }
    
    }

    假设上面提到的Bean实现了一个泛型接口比如说:Store<String>Store<Integer>,开发者可以使用@Autowire来自动装载,而泛型的类型则作为限定符,参考如下代码:

    @Autowired
    private Store<String> s1; // <String> qualifier, injects the stringStore bean
    
    @Autowired
    private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

    泛型的限定同样可以用于List,Map或者Array上面,如下:

    // Inject all Store beans as long as they have an <Integer> generic
    // Store<String> beans will not appear in this list
    @Autowired
    private List<Store<Integer>> s;

    CustomAutowireConfigurer

    CustomAutowireConfigurer是一个BeanFactoryPostProcessor,这个后置处理器可以注册开发者自己的限定符注解,让开发者的注解不依赖于Spring限定符注解。

    <bean id="customAutowireConfigurer"
            class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
        <property name="customQualifierTypes">
            <set>
                <value>example.CustomQualifier</value>
            </set>
        </property>
    </bean>

    AutowireCandidateResolver通过以下的几种方式来决定自动装载的候选Bean:

    • Bean定义中的autowire-candidate的值
    • 任何<beans/>标签中定义的default-autowire-candidates的值
    • @Qualifier注解和任何在CustomAutowireConfigurer中定义的自定义的限定符注解

    当多个Bean限定为自动装载的候选时, 前文中提到的primary属性是优先考虑的。

    @Resource注解

    Spring支持JSR-250标准中的@Resource注解来注入实例变量或者setter方法。这也是Java EE 5和6之中的常见模式,举例来说,在JSF 1.2中管理的Bean或者是JAX-WS 2.0 endpoint等。Spring也支持这种模式来注入Spring管理的对象。

    @Resource需要一个名字的属性,而默认的情况下,Spring会将Bean的名字注入。换言之,@Resource的自动装载是基于名字语义的,就如同如下的例子一样:

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        @Resource(name="myMovieFinder")
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
    }

    如果没有明确指定名字的话,默认的名字就是实例变量的变量名,或者Setter方法中解析出来的名字。以实例变量为例,就取变量的名字作为默认的名字,如果是Setter的方法的话,会取Bean属性的名字。所以,如下的例子会注入Bean的名字的movieFinder的Bean实例。

    public class SimpleMovieLister {
    
        private MovieFinder movieFinder;
    
        @Resource
        public void setMovieFinder(MovieFinder movieFinder) {
            this.movieFinder = movieFinder;
        }
    
    }

    注解解析的名字是通过ApplicationContext来解析的。如果开发者配置了明确的Spring SimpleJndiBeanFactory的话,这些名字可以通过JNDI来解析。然而,Spring团队仍然建议开发者依赖其默认的行为并简单实用Spring的JNDI查找能力。

    在使用@Resource而没有明确指定名字的情况,它比较类似@Autowired注解,@Resource会优先查找类型匹配而不是名字匹配,也能够解析一些常见的依赖:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher以及MessageSource等接口。

    在如下的例子中,customerPreferenceDao实例变量会优先查找名字为customerPreferenceDao的Bean来进行依赖注入,然后在进行类型匹配CustomerPreferenceDaocontext实例变量作为常见的实例会自动通过ApplicationContext注入进去。

    public class MovieRecommender {
    
        @Resource
        private CustomerPreferenceDao customerPreferenceDao;
    
        @Resource
        private ApplicationContext context;
    
        public MovieRecommender() {
        }
    
        // ...
    
    }

    @PostConstructPreDestroy注解

    CommonAnnotationBeanPostProcessor不仅仅识别@Resource注解,也识别JSR-250标准中的生命周期注解。在 Spring核心技术IoC容器(六)中针对回调函数的描述了注解外其他的方法实现回调。CommonAnnotationBeanPostProcessor是注册在Spring的ApplicationContext之中的,它提供了一些方法来关联Spring的声明周期来进行回调,在下面的例子中,缓存会在构造后和销毁前进行回调。

    public class CachingMovieLister {
    
        @PostConstruct
        public void populateMovieCache() {
            // populates the movie cache upon initialization...
        }
    
        @PreDestroy
        public void clearMovieCache() {
            // clears the movie cache upon destruction...
        }
    
    }

    关于Spring的生命周期的机制在前文 《Spring核心技术IoC容器(六)》之中有更详细的描述。

  • 相关阅读:
    9-10-堆 Windows消息队列(25 分)
    9-7 二叉搜索树的结构(30 分)
    9-4 笛卡尔树(25 分)
    9-3 搜索树判断(25 分)
    7-9 堆中的路径(25 分)
    个人总结
    软工网络15个人作业4——alpha阶段个人总结
    软件工程网络15个人作业3——案例分析(201521123029 郑佳明)
    软件工程15 结对编程作业
    软件工程网络15个人阅读作业2(201521123029 郑佳明)
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461554.html
Copyright © 2011-2022 走看看