《B站-Spring源码解析》学习笔记(四)——自动装配

视频地址:https://www.bilibili.com/video/BV1oW41167AV
对应代码Git库地址:https://github.com/whh306318848/spring-annotation.git

  1. 自动装配:Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值
  2. 使用@Autowired注解自动注入
    2.1. 默认优先按照类型去容器中找对应的组件,使用applicationContext.getBean(BookDao.class);
    2.2. 如果找到相同类型的组件,再将属性的名称作为组件的id去容器中查找applicationContext.getBean(“bookDao”)
    2.3. 在被装配的变量上(即使用@Autowired注解的地方),使用@Qualifier注解指定要装配的组件的id,而不是使用属性名
    2.4. 默认情况下,一定要将属性赋值好,如果没有找到就会报错,但也可以使用@Autowired(required = false)设置自动装配是非必须的,能装配则装配,如果找不到就不装配
    2.5. 在组件上(即组件注册的地方,如@Bean、@Service等注解上),使用@Primary注解,让Spring进行自动装配的时候,默认使用首选的Bean
// 注入的Bean,名字默认是首字母小写
@Repository
public class BookDao {

    private String lable = "1";

    public String getLable() {
        return lable;
    }

    public void setLable(String lable) {
        this.lable = lable;
    }

    @Override
    public String toString() {
        return "BookDao{" +
                "lable='" + lable + '\'' +
                '}';
    }
}
@Service
public class BookService {

    @Qualifier("bookDao")
    @Autowired(required = false)
    private BookDao bookDao;

    public void print() {
        System.out.println(this.bookDao);
    }

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}
/**
 * @author: wuhaohua
 * @date: Created in 2021/1/5 17:22
 * @description: 自动装配配置类
 * 自动装配:
 * Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值;
 * 1)、使用@Autowired注解自动注入,其原理是:
 *      a.默认优先按照类型去容器中找对应的组件,使用applicationContext.getBean(BookDao.class);
 *      b.如果找到相同类型的组件,再将属性的名称作为组件的id去容器中查找applicationContext.getBean("bookDao")
 *      c.在被装配的变量上(即使用@Autowired注解的地方),使用@Qualifier注解指定要装配的组件的id,而不是使用属性名
 *      d.默认情况下,一定要将属性赋值好,如果没有找到就会报错,但也可以使用@Autowired(required = false)设置自动装配是非必须的,能装配则装配,如果找不到就不装配
 *      e.在组件上(即组件注册的地方,如@Bean、@Service等注解上),使用@Primary注解,让Spring进行自动装配的时候,默认使用首选的Bean,也可以继续使用@Qualifier注解指定需要装配的bean的名字
 */
@Configuration
@ComponentScan({"com.atguigu.service", "com.atguigu.dao", "com.atguigu.controller"})
public class MainConfigOfAutowired {

    @Primary
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLable("2");
        return bookDao;
    }
}
  1. Spring还支持使用@Resource(JSR250)和@Inject(JSR330),这两个注解是Java规范的注解,而@Autowired是Spring的注解
    3.1. @Resource可以和@Autowired一样实现自动装配功能,但其默认是按照属性对应的组件名称进行装配的
    3.2. @Resource若需要装配指定的Bean,可以使用其name属性进行设置
    3.3. @Resource没有能支持@Primary功能,没有支持@Autowired(required = false)功能
@Service
public class BookService {

//    @Qualifier("bookDao")
//    @Autowired(required = false)
    @Resource(name = "bookDao2")
    private BookDao bookDao;

    public void print() {
        System.out.println(this.bookDao);
    }

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}

3.4. 如果要使用@Inject注解,需要先导入Javax Inject依赖

<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

3.5. @Inject支持@Primary功能,但不支持@Autowired(required = false)功能

@Service
public class BookService {

//    @Qualifier("bookDao")
//    @Autowired(required = false)
//    @Resource(name = "bookDao2")
    @Inject
    private BookDao bookDao;

    public void print() {
        System.out.println(this.bookDao);
    }

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}
  1. AutowiredAnnotationBeanPostProcessor:负责解析完成自动装配,其是一个后置处理器,打开其源码可以看到其除了支持Autowired注解外,还支持Value、Inject、Lookup等注解
  2. @Autowired可以标注在构造器、参数、方法、属性等位置实现自动注解
    5.1. 标注在方法上
@Component
public class Boss {

    private Car car;

    public Car getCar() {
        return car;
    }

    // 将@Autowired注解标注在方法上,Spring容器在创建当前对象时,就会调用该方法完成赋值
    // 方法使用的参数,自定义类型的值从IOC容器中获取
    @Autowired
    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

5.2. 标注构造器上,如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以从容器中获取

/**
 * @author: wuhaohua
 * @date: Created in 2021/1/11 16:54
 * @description: TODO
 * 默认加在IOC容器中的组件,容器启动时会自动调用无参构造器创建对象,然后再进行初始化赋值等操作
 */
@Component
public class Boss {

    private Car car;

    // 构造器要用的组件,都是从容器中获取的
    @Autowired
    public Boss(Car car) {
        this.car = car;
        System.out.println("Boss...有参构造器...");
    }

    public Car getCar() {
        return car;
    }


    // 将@Autowired注解标注在方法上,Spring容器在创建当前对象时,就会调用该方法完成赋值
    // 方法使用的参数,自定义类型的值从IOC容器中获取
//    @Autowired
    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

5.3. 标注在参数上

@Component
public class Boss {

    private Car car;

    // 构造器要用的组件,都是从容器中获取的
    public Boss(@Autowired Car car) {
        this.car = car;
        System.out.println("Boss...有参构造器...");
    }

    public Car getCar() {
        return car;
    }


    // 将@Autowired注解标注在方法上,Spring容器在创建当前对象时,就会调用该方法完成赋值
    // 方法使用的参数,自定义类型的值从IOC容器中获取
//    @Autowired
    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

5.4. 标注在@Bean标注的方法上:创建对象的时候,方法参数的值是从容器中获取的,默认不写@Autowired注解,效果是一样的,都能自动装配

/***
 * @Bean标的方法,创建对象的时候,方法参数的值是从容器中获取的
 **/
@Bean
public Color color(Car car) {
    Color color = new Color();
    color.setCar(car);
    return color;
}
  1. 自定义组件想要使用Spring容器底层的一些组件(如ApplicationContext,BeanFactory等)
    6.1. 自定义组件需要实现xxxAware接口,在创建对象的时候,Spring会调用接口规定的方法注入相关组件
@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);
    }
}

6.2. 把Spring底层一些组件注入到自定义的Bean中:xxxAware,功能使用xxxProcessor后置处理器实现,如ApplicationContextAware其是使用ApplicationContextAwareProcessor实现注入的

  1. @Profile注解是Spring为我们提供的,可以根据当前环境(开发、测试、生产等),动态的激活和切换一系列组件(配置、数据源、中间件等)的功能
    7.1. 以不同环境需要连接不同数据源为例
    在pom文件中引入c3p0和mysql-connector包
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

创建数据库配置文件dbconfig.properties

db.user=root
db.password=root
db.driverClass=com.mysql.jdbc.Driver

创建具有多环境配置的配置类

@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

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

    private StringValueResolver valueResolver;

    private String diverClass;

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

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

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

    @Profile("prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("{db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/sw-zs");
        dataSource.setDriverClass(diverClass);
        return dataSource;
    }

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

创建用于验证效果的测试类

public class IOCTest_Profile {

    @Test
    public void test01() {
        // 1、创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
        System.out.println("容器创建完成... ");

        // 关闭容器
        applicationContext.close();
        System.out.println("容器创建完成... ");
    }
}

7.2. @Profile注解,用于指定当前组件在哪个环境的情况下才能被注册到容器中,如果当前组件没有指定,则在任何环境下都能注册到容器中
7.3. 切换环境的方式包括:
a. 使用命令行参数,在JVM参数位置加载-Dspring.profiles.active=test
b. 使用代码的方式激活某种环境

public class IOCTest_Profile {

    @Test
    public void test01() {
        // 1、创建一个applicationContext
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 2、设置需要激活的环境
        applicationContext.getEnvironment().setActiveProfiles("dev");
        // 3、注册主配置类
        applicationContext.register(MainConfigOfProfile.class);
        // 4、启动刷新容器
        applicationContext.refresh();

        System.out.println("容器创建完成... ");

        String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
        for (String name: namesForType) {
            System.out.println(name);
        }

        // 关闭容器
        applicationContext.close();
        System.out.println("容器创建完成... ");
    }
}

7.3. 当@Profile注解标注在配置类上时,只有是指定的环境时,整个配置类里面的所有配置才会生效
7.4. 没有标注环境标识的bean,在任何环境下都是会被加载的

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据