(12)Spring自动装载的注解

主要讲解的注解如下:

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

在使用注解进行自动装配之前,需要实现实现打开注解的配置,需要在xml的配置如下:




    


比如接下来讲解的注解@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 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的属性配置了这个注解时,该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 anotationNameList;
    
    /*符合所有的bean注入到数组里面*/
    @Autowired
    private AnotationName[] anotationNames;

    /*注入到Map集合,String为bean名称,value为具体的bean*/
    @Autowired(required = false)
    private Map 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配置的测试方法,测试结果与注解一样

/**
 * @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;
    }

    // ...

}
 
        
        
    

5.通过CustomAutowireConfigurer

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


    
        
            example.CustomQualifier
        
    

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

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

  • Bean定义中的autowire-candidate的值
  • 任何标签中定义的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...
    }

}

你可能感兴趣的:((12)Spring自动装载的注解)