(12)Spring自动装载的注解

主要讲解的注解如下:

  • @required注解(只能修饰setter方法,表示配置阶段就拥有明确的值
  • @Autowired注解 (可以在构造函数上,setter方,成员变量上面,注解将多个Bean注入到Array,Set,Map类型)
  • @Primary注解 (配合java配置注解的方式,可以用到方法上
  • @Qualifier注解(成员变量,构造函数的参数中,或者是方法上面)
  • @Resource注解(修饰在方法和成员变量上,这个是注解是java本身标准的)
  • @PostConstruct和PreDestroy注解 (bean的声明周期回调,bean实例化之后,销毁之前执行,注解是java标准的)

在使用注解进行自动装配之前,需要实现实现打开注解的配置,需要在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/>

</beans>

比如接下来讲解的注解@Required,本质上是由BeanPostProcessor后置处理器来进行处理注解的,
基本原理:
继承InstantiationAwareBeanPostProcessorAdapter,在postProcessPropertyValues()方法中进行校验,可以具体看下RequiredAnnotationBeanPostProcessor的postProcessPropertyValues处理部分的源码

//注入属性
    @Override
    public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

        //如果容器缓存中没有指定Bean名称
        if (!this.validatedBeanNames.contains(beanName)) {
            //如果指定Bean定义中没有设置skipRequiredCheck属性
            if (!shouldSkip(this.beanFactory, beanName)) {
                List<String> invalidProperties = new ArrayList<>();
                //遍历所有属性
                for (PropertyDescriptor pd : pds) {
                    //如果属性添加了@Required注解,且属性集合中不包含指定名称的属性
                    if (isRequiredProperty(pd) && !pvs.contains(pd.getName())) {
                        //当前属性为无效的属性
                        invalidProperties.add(pd.getName());
                    }
                }
                //如果无效属性集合不为空
                if (!invalidProperties.isEmpty()) {
                    throw new BeanInitializationException(buildExceptionMessage(invalidProperties, beanName));
                }
            }
            //将Bean名称缓存到容器中
            this.validatedBeanNames.add(beanName);
        }
        //返回经过验证的属性值
        return pvs;
    }

由于实现PriorityOrdered接口,@required ,执行优先级比@Autowired、@Resource都要低,getOrder()方法获取的值越小,越被先执行。
常用的注解有对应的后置处理器,有如下的关系

BeanPostProcessor具体实现类 处理注释 优先级(数字越低优先级越高)
AutowiredAnnotationBeanPostProcessor @Autowoired Integer.MAX -2
CommonAnnotationBeanPostProcessor @Resource Integer.MAX -3
RequiredAnnotationBeanPostProcessor @required Integer.MAX -1

1.@Required注解

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

/**
 * @Project: spring
 * @description:  只能在setter方法使用  当指明Required为是,明确这个属性不能为空,
 * @author: sunkang
 * @create: 2018-09-16 17:32
 * @ModificationHistory who      when       What
 **/
public class RequiredAnotation {
    private AnotationName anotationName;
    
    public AnotationName getAnotationName() {
        return anotationName;
    }

    @Required
    public void setAnotationName(AnotationName anotationName) {
        this.anotationName = anotationName;
    }
}

这个还需要在xml配置是根据类型来装配,不然无法进行自动装配,当然也可以配合 @Autowired注解来实现

   <bean id = "requiredAnotation" class="com.spring.annotationbase.RequiredAnotation" autowire="byType">
    </bean>

当Bean的属性配置了这个注解时,该Bean的属性必须在配置阶段就拥有明确的值,通过精确地Bean定义,或者通过自动装载。如果Bean的属性没有配置,容器会抛出异常。这一机制可以避免之后出现的空指针异常问题

2.@Autowired注解

  • 构造函数上面使用@Autowired注解:
  • 注入到传统的setter方法上面
  • 用到任何名字任何参数的方法上面
  • 将@Autowired用到成员变量上面
  • 将多个Bean注入到Array类型中
  • 将多个Bean注入到Set类型中
  • 将多个Bean注入到Map类型中,Map的值可以是任何一种容器中的Bean的类型,key当然就是对应Bean的名字。

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

每个类仅有一个注解构造函数可以被标记为必须的,但是非必须的够早函数可以注解多个在存在多个非必须注解(required=false)的情况下,Spring会选择一个最贪婪的构造函数(满足最多的依赖的)
@Autowired注解要优于@Required注解

具体的配置如下:

 * @Project: spring
 * @description:  @autowired的演示
 * @author: sunkang
 * @create: 2018-09-16 17:51
 * @ModificationHistory who      when       What
 **/
public class Autowiredannotation {
    private String name;

    /*注入到成员变量*/
    @Autowired
    private AnotationName anotationName;

    /*符合所有的bean,注入到list集合上面*/
    @Autowired
    private List<AnotationName> anotationNameList;
    
    /*符合所有的bean注入到数组里面*/
    @Autowired
    private AnotationName[] anotationNames;

    /*注入到Map集合,String为bean名称,value为具体的bean*/
    @Autowired(required = false)
    private Map<String,AnotationName> anotationNameMap;

    /*注入到setter方法里面*/
    @Autowired
    public void setAnotationName(AnotationName anotationName) {
        this.anotationName = anotationName;
        System.out.println("setter方法注入");
    }
    @Autowired
    public Autowiredannotation(AnotationName anotationName) {
        this.anotationName=anotationName;
        System.out.println("构造方法注入");
    }

   /*注入到任何名字任何参数的方法上面*/
    public Autowiredannotation(@Autowired AnotationName anotationName,String name) {
        this.anotationName=anotationName;
        this.name = name;
    }
}

3.通过@Primary来自动装载

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

  • 完全基于java的配置的方式
    下面演示java的配置方式进行注入
/**
 * @Project: spring
 * @description:     标记注解的名称
 * @author: sunkang
 * @create: 2018-09-16 17:38
 * @ModificationHistory who      when       What
 **/
public class AnotationName {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public AnotationName(String name) {
        this.name = name;
    }
    public AnotationName() {
    }
}

模拟一个bean,为自动装配模式

**
 * @Project: spring
 * @description:  一个bean,含有AnotationName的引用
 * @Qualifier("second")的优先级比primary的优先级高
 *
 * @author: sunkang
 * @create: 2018-09-16 18:57
 * @ModificationHistory who      when       What
 **/
@Component("primaryAnotation")
public class PrimaryAnotation {

    @Autowired
//    @Qualifier("second")
    private AnotationName  anotationName;

    public AnotationName getAnotationName() {
        return anotationName;
    }


    public void setAnotationName(AnotationName anotationName) {
        this.anotationName = anotationName;
    }
}

java注解的配置方式,配置有两个候选人等待被注入,一个是修饰为@primary,一个的修饰符为second

/**
 * @Project: spring
 * @description:  java的配置方式,这里可以跟xml的配置方式进行类比
 * @author: sunkang
 * @create: 2018-09-16 18:38
 * @ModificationHistory who      when       What
 **/
@Configuration
//@ImportResource("classpath:anotationbased/spring-primary.xml")
public class PrimaryConfig {

    private AnotationName anotationName;

    @Primary
    @Bean(name="primaryBean")
    public AnotationName getAnotationName() {
        return new AnotationName("primaryName");
    }

    @Bean(name="secondBean")
    @Qualifier("second")
    public AnotationName getSencondAnotationName(){
        return new AnotationName("sencondName");
    }
}

进行测试,容器用了注解的方式来驱动的

/**
 * @Project: spring
 * @description:  基于注解方式的容器启动
 * @author: sunkang
 * @create: 2018-09-16 18:53
 * @ModificationHistory who      when       What
 **/
public class PrimayAnnotationTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PrimaryConfig.class);


        context.register(PrimaryAnotation.class);

        PrimaryAnotation primaryBean   = context.getBean("primaryAnotation",PrimaryAnotation.class);

        System.out.println(primaryBean.getAnotationName().getName());
    }
}

测试结果如下: 可以发现完全通过注解的方式也可以实现依赖注入,当PrimaryAnotation 修饰的配置@Qualifier("second")的注释被打开时,测试结果为sencondName,也就是修饰符为sencond的对象别注入了

primaryName

可以对比一下xml的配置方式:这两种的配置方式是一样

  • 基于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 id="primaryName" class="com.spring.annotationbase.Primary.AnotationName" primary="true">
        <property name="name" value="primaryName"/>
    </bean>

    <bean id="sencondName" class="com.spring.annotationbase.Primary.AnotationName">
        <property name="name" value="sencondName"/>
        <qualifier value="second"/>
    </bean>

    <bean id="primaryAnotation" class="com.spring.annotationbase.Primary.PrimaryAnotation" >
    </bean>
</beans>

基于xml配置的测试方法,测试结果与注解一样

/**
 * @Project: spring
 * @description:  xml 的测试方法
 * @author: sunkang
 * @create: 2018-09-17 22:36
 * @ModificationHistory who      when       What
 **/
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("anotationbased/spring-primary.xml");

        PrimaryAnotation primaryBean   = context.getBean("primaryAnotation",PrimaryAnotation.class);

        System.out.println(primaryBean.getAnotationName().getName());
    }
}

4.通过限定符的自动装载

@Primary在相同类型的几个实例之间选择选择依赖的时候,是一种很高效的方式。但是如果想要更多的控制自动装载的过程,@Qualifier注解就更为有用了

修饰成员变量

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;
    }

    // ...

}

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

@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 class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

5.通过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>

实际上在CustomAutowireConfigurer的 postProcessBeanFactory中,注入了一个默认QualifierAnnotationAutowireCandidateResolver解析器

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

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

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

6.@Resource注解

Spring支持JSR-250标准中的@Resource注解来注入实例变量或者setter方法
@Resource需要一个名字的属性,而默认的情况下,Spring会将Bean的名字注入。

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;
    }

}

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

7.@PostConstruct和PreDestroy注解

CommonAnnotationBeanPostProcessor不仅仅识别@Resource注解,也识别JSR-250标准中的生命周期注解。也针对回调函数的描述了注解外其他的方法实现回调。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...
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容