目录

spring注解驱动之自动装配

自动装配:Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值

@Autowired和@Qualifier和@Primary

@Autowired

1
2
3
4
5
6
7
8
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
  
   boolean required() default true;

}
  1. 默认优先按照类型去容器中找对应的组件: applicationContext.getBean(BookDao.class);找到就赋值
  2. 如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找:applicationContext.getBean(“bookDao”)
  3. 自动装配默认一定要将属性赋值好,没有就会报错;可以使用@Autowired(required=false);

@Qualifier

如果没有使用@Qualifier指定需要装配的组件的id,则默认使用要装配的属性名作为组件id,如果指定了组件di则不使用属性名作为组件id。配合@Autowired一起使用,可以在一个类型对应多个bean的时候使用

1
2
3
4
5
6
7
8
9
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {

   String value() default "";

}

@Primary

让Spring进行自动装配的时候,默认首选使用的bean;也可以继续使用@Qualifier指定需要装配的bean的名字

在没有使用@Qualifier明确指定的情况下,优先使用@Primary注解的bean,明确指定的情况下则使用明确指定的那个

1
2
3
4
5
6
7
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Primary {

}

注意:@Primary是用在声明Bean的时候,如下:

1
2
3
4
5
6
7
@Primary
@Bean("bookDao2")
public BookDao bookDao(){
   BookDao bookDao = new BookDao();
   bookDao.setLable("2");
   return bookDao;
}

@Resource和@Inject

Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

@Resource:

  •      可以和@Autowired一样实现自动装配功能;默认是按照属性名称进行装配的,也可以使用name属性指定要注入的属性名称
    
  •      没有能支持@Primary功能没有支持@Autowired(reqiured=false);
    

@Inject:

  •      需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;
    

区别:@Autowired是Spring定义的; @Resource、@Inject都是java规范

AutowiredAnnotationBeanPostProcessor

解析完成自动装配功能

20201029123801

@Autowired在属性以外的其他位置注入属性

构造器,参数,方法,属性;都是从容器中获取参数组件的值

标注在方法位置

@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配,eg:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.eh.bean;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class RainBow {

    @Getter
    private Red red;

    //@Autowired,标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
    //方法使用的参数,自定义类型的值从ioc容器中获取
    @Autowired
    public void setRed(Red red) {
        this.red = red;
    }

}

@Bean示例: 这种情况用的最多

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/**
 * @Bean标注的方法创建对象的时候,方法参数的值从容器中获取
 * @param car
 * @return
 */
@Bean
public Color color(Car car){
   Color color = new Color();
   color.setCar(car);
   return color;
}

标在构造器上

如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.eh.bean;

import lombok.Getter;
import org.springframework.stereotype.Component;

@Component
public class RainBow {

    @Getter
    private Red red;

    // @Autowired 可以省略不写
    public RainBow(Red red) {
        this.red = red;
    }

}

放在参数位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Component
public class RainBow {

    @Getter
    private Red red;

  // 同上面一样,方法中的@Autowired可以省略不写
    @Autowired
    public void setRed(@Autowired Red red) {
        this.red = red;
    }

}

在自定义组件中注入Spring底层组件

例如ApplicationContext,BeanFactory等

在自定义组件实现xxxAware,在创建对象的时候,会调用接口规定的方法注入相关组件;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
* 为某个bean提供spring底层容器的能力,通过使用函数回调的方式
 * A marker superinterface indicating that a bean is eligible to be notified by the
 * Spring container of a particular framework object through a callback-style method.
 * The actual method signature is determined by individual subinterfaces but should
 * typically consist of just one void-returning method that accepts a single argument.
 *
 * <p>Note that merely implementing {@link Aware} provides no default functionality.
 * Rather, processing must be done explicitly, for example in a
 * {@link org.springframework.beans.factory.config.BeanPostProcessor}.
 * Refer to {@link org.springframework.context.support.ApplicationContextAwareProcessor}
 * for an example of processing specific {@code *Aware} interface callbacks.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 */
public interface Aware {

}

继承Aware接口的接口有

  • ApplicationEventPublisherAware (org.springframework.context)

    注入事件开发器

  • NotificationPublisherAware (org.springframework.jmx.export.notification)

  • MessageSourceAware (org.springframework.context)

    国际化

  • BeanFactoryAware (org.springframework.beans.factory)

  • EnvironmentAware (org.springframework.context)

  • EmbeddedValueResolverAware (org.springframework.context)

    值解析器,可以取出占位符表示的值

  • ResourceLoaderAware (org.springframework.context)

    资源加载

  • ImportAware (org.springframework.context.annotation)

    导入相关

  • LoadTimeWeaverAware (org.springframework.context.weaving)

  • BeanNameAware (org.springframework.beans.factory)

    Bean名字

  • BeanClassLoaderAware (org.springframework.beans.factory)

    类加载器

  • ApplicationContextAware (org.springframework.context)

    注入IOC容器

具体由xxxAwareProcessor处理实现xxxAware接口类,例如ApplicationContextAware->ApplicationContextAware

演示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("传入的ioc:" + applicationContext);
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("当前bean的名字:" + name);
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} 我是 #{20*18}");
        System.out.println("解析的字符串:" + resolveStringValue);
    }
}

@Profile

Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;

注意
在开发中不建议使用@Profile来区分环境注册组件,可以使用maven的profile,因为使用@Profile还得写各种环境的组件,但是使用maven的profile只需要隔离属性配置文件即可。私以为@Profile只适合在某个环境中独有某个组件时才使用,如果在各个环境都有某个组件,建议使用maven的profile,在打包的时候区分环境,写一个组件注册逻辑,读取不同的环境变量。

@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件

  • 加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
  • 写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
  • 没有标注环境标识的bean在,任何环境下都是加载的;

演示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

    @Value("${db.user}")
    private String user;

    private StringValueResolver valueResolver;

    private String driverClass;


    @Bean
    public Yellow yellow() {
        return new Yellow();
    }

    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }


    @Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Profile("prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515");

        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }

}

切换环境有两种方式:

  1. 使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test

  2. 代码的方式激活某种环境

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    // 这里使用无参的构造方法,有参的直接就完成初始化了。
    AnnotationConfigApplicationContext applicationContext = 
                 new AnnotationConfigApplicationContext();
         //1、创建一个applicationContext
         //2、设置需要激活的环境
         applicationContext.getEnvironment().setActiveProfiles("dev");
         //3、注册主配置类
         applicationContext.register(MainConfigOfProfile.class);
         //4、启动刷新容器
         applicationContext.refresh();