spring系列-注解驱动原理及源码-自动装配

86 阅读9分钟

目录

一、spring规范的bean自动注入

1、使用@Autowired自动注入

2、使用@Qualifier指定需要装配的组件

3、使用@Autowired装配null对象

4、使用@Primary装配首选bean

5、总结

6、自动装配原理

二、java规范的bean自动注入

1、@Resource自动装配

2、@Inject自动装配

3、总结

三、方法、构造器位置的自动装配

1、@Autowired标注在方法上

2、@Autowired标注在构造器上

3、@Autowired标注在参数上

4、@Bean的自动装配

5、总结

四、自定义组件使用spring容器底层组件

1、基本使用

2、解释

 3、ApplicationContextAwareProcessor注入源码

五、使用@Profile根据环境注入组件

1、使用@Profile


一、spring规范的bean自动注入

1、使用@Autowired自动注入

(1)自动扫描包

@Configuration
@ComponentScan({"com.xiang.spring.dao", "com.xiang.spring.controller", "com.xiang.spring.service"})
public class MainConfigAutowired {
}

(2)定义bean

package com.xiang.spring.service;

import com.xiang.spring.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
// 名字默认是类名首字母小写
@Service
public class BookService {

    @Autowired(required = false)
    private BookDao bookDao;

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

    @Override
    public String toString() {
        return "BookService{" +
                "bookDao=" + bookDao +
                '}';
    }
}
package com.xiang.spring.dao;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

// 名字默认是类名首字母小写
@Repository
public class BookDao {
}

(3)测试类

@Test
public void test01() {
    // 创建ioc容器,容器创建时,默认会将单例的bean都创建出来
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigAutowired.class);
    BookService bean = applicationContext.getBean(BookService.class);
    System.out.println(bean);
    BookDao bean1 = applicationContext.getBean(BookDao.class);
    System.out.println(bean1);
}

(4)@Autowired自动装配顺序

① 优先按照class类型查找对应的组件,如果找到就赋值。

    private BookDao bookDao; - 先按照类型查找

② 如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找。

    private BookDao bookDao; - 如果多个,按照bookDao这个属性名查找

2、使用@Qualifier指定需要装配的组件

(1)装配时,添加@Qualifier注解

// 使用@Qualifier指定要装配的组件的id,而不是使用属性名
@Qualifier("bookDao")
@Autowired
private BookDao bookDao;

3、使用@Autowired装配null对象

不加@Autowired(required = false)的话,如果需要装配的bean没有找到,程序会报错。

加了@Autowired(required = false)的话,如果需要装配的bean没有找到,会装配null。

// 使用@Qualifier指定要装配的组件的id,而不是使用属性名
@Qualifier("bookDao")
@Autowired(required = false)
private BookDao bookDao;

4、使用@Primary装配首选bean

有多个相同类型的bean时,@Primary注释的bean可以被优先注入,而不是使用属性名查找注入。

(1)在bean上添加@Primary注解

@Primary
@Repository
public class BookDao {
}

5、总结

自动装配

     spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值

(1) @Autowired自动注入,一个组件想要使用另一个组件

     ① 优先按照class类型查找对应的组件,如果找到就赋值。

         private BookDao bookDao; - 先按照类型查找

     ② 如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找。

         private BookDao bookDao; - 如果多个,按照bookDao这个属性名查找

     ③ @Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名。

     ④ 自动装配默认一定要将属性赋值好,没有找到组件就会报错。

         可以使用@Autowired(required = false) ,没有该组件不会报错,是null。

     ⑤ @Primary:让spring进行自动装配的时候,默认使用首选的bean。

         直接在bean上面加@Primary,多个同类型的bean会优先使用@Primary修饰的。

         也可以使用@Qualifier指定,@Qualifier优先级更强。

6、自动装配原理

AutowiredAnnotationBeanPostProcessor 实现自动装配

二、java规范的bean自动注入

1、@Resource自动装配

@Resource:

    可以和@Autowired一样实现自动装配功能。默认是按照组件名称进行装配的。

    可以指定装配名称:@Resource(name = "bookDao")

    不支持@Qualifier、@Primary、required = false

(1)在bean上加@Resource

// 使用@Resource自动注入
@Resource(name = "bookDao")
private BookDao bookDao;

2、@Inject自动装配

@Inject:

    需要额外导入javax.inject包依赖。

    和@Autowired的功能一样,支持@Qualifier、@Primary,不支持required = false

(1)pom文件导入依赖

<!--@Inject依赖-->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

(2)使用@Inject注入

// 使用@Inject自动注入
@Inject
private BookDao bookDao;

3、总结

spring还支持使用@Resource(JSR250)和@Inject(JSR330)【java规范的注解】

     @Resource:

         可以和@Autowired一样实现自动装配功能。默认是按照组件名称进行装配的。

         可以指定装配名称:@Resource(name = "bookDao")

         不支持@Qualifier、@Primary、required = false

     @Inject:

         需要额外导入javax.inject包依赖。

         和@Autowired的功能一样,支持@Qualifier、@Primary,不支持required = false

     @Autowired是spring的,@Resource和@Inject是java的规范。

     AutowiredAnnotationBeanPostProcessor实现自动装配

三、方法、构造器位置的自动装配

1、@Autowired标注在方法上

(1)标注在set方法上

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

2、@Autowired标注在构造器上

(1)标注在构造器上

/**
* 写一个有参构造器
* @Autowired标注在构造器上,构造器用的参数也会从ioc容器中取
* 如果只有一个有参构造器,@Autowired是可以省略的,参数位置的组件也会自动注入。
*/
@Autowired // 如果只有一个有参构造器,可以省略
private Boos(Car car) {
    this.car = car;
    System.out.println("boos 的有参构造器");
}

3、@Autowired标注在参数上

(1)标注在构造器参数上

private Boos(@Autowired Car car) {
    this.car = car;
    System.out.println("boos 的有参构造器");
}

(2)标注在方法参数上

public void setCar(@Autowired Car car) {
    this.car = car;
}

4、@Bean的自动装配

(1)@Bean自动装配

/**
* @Bean标注的注解创建对象的时候,方法参数的值从容器中获取
*/
@Bean
public Color color(Car car) {
    System.out.println("color bean " + car);
    Color color = new Color();
    color.setCar(car);
    return color;
}

5、总结

@Autowired:可以标注 构造器、参数、方法、属性。

     ① 标注在方法上:

         标注在方法上,spring容器创建当前对象时,就会调用方法完成赋值。

         方法使用的参数,自定义类型的值,从ioc容器中进行获取。

         @Bean标注的注解创建对象的时候,方法参数的值从容器中获取。默认不写@Autowired

     ② 标注在构造器上:

         @Autowired标注在构造器上,构造器用的参数也会从ioc容器中取

         如果只有一个有参构造器,@Autowired是可以省略的,参数位置的组件也会自动注入。

     ③ 标注在参数上:

         在方法、构造器中的参数前标注@Autowired,也会从ioc容器中获取参数组件值。

四、自定义组件使用spring容器底层组件

1、基本使用

(1)定义bean

package com.xiang.spring.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;

@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("传入的ioc:" + applicationContext);
        this.applicationContext = applicationContext;
    }

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

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

(2)测试类查看执行结果

@Test
public void test01() {
    // 创建ioc容器,容器创建时,默认会将单例的bean都创建出来
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigAutowired.class);
}

// 查看Red组件执行结果
当前bean的名字:red
解析的字符串:你好Windows 10 我是360
传入的ioc:org.springframework.context.annotation.AnnotationConfigApplicationContext@4141d797

2、解释

自定义组件想要使用spring容器底层的一些组件(ApplicationContext、BeanFactory、等等)

         只需让自定义组件实现XxxxAware,在创建对象时,会调用接口规定的方法,注入相关的组件。

         把spring底层的一些组件注入到自定义的Bean中。

         这些XxxxAware的功能,都是使用XxxxProcessor实现的:

             ApplicationContextAware -> ApplicationContextAwareProcessor

Aware接口有以下子接口:

 3、ApplicationContextAwareProcessor注入源码

@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
   AccessControlContext acc = null;

   if (System.getSecurityManager() != null &&
         (bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
               bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
               bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
      acc = this.applicationContext.getBeanFactory().getAccessControlContext();
   }

   if (acc != null) {
      AccessController.doPrivileged(new PrivilegedAction<Object>() {
         @Override
         public Object run() {
            invokeAwareInterfaces(bean);
            return null;
         }
      }, acc);
   }
   else {
      invokeAwareInterfaces(bean);
   }

   return bean;
}


private void invokeAwareInterfaces(Object bean) {
   if (bean instanceof Aware) {
      if (bean instanceof EnvironmentAware) {
         ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
      }
      if (bean instanceof EmbeddedValueResolverAware) {
         ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
      }
      if (bean instanceof ResourceLoaderAware) {
         ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
      }
      if (bean instanceof ApplicationEventPublisherAware) {
         ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
      }
      if (bean instanceof MessageSourceAware) {
         ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
      }
      if (bean instanceof ApplicationContextAware) {
         ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
      }
   }
}

五、使用@Profile根据环境注入组件

1、使用@Profile

(1)pom引入依赖

<!--c3p0数据源-->
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.44</version>
</dependency>

(2)在resources目录下新建配置文件db.properties

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

(3)配置类

package com.xiang.spring.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.StringValueResolver;

import javax.sql.DataSource;


/**
* profile:
*      Spring为我们提供的 可以根据当前环境,动态的激活和切换一系列组件的功能。
*      指定组件在哪个环境下才能被注册到环境中。
*      (1)加了@Profile的bean,只有这个环境被激活的时候才能注册到ioc容器中。
*          默认是default环境
*      (2)@Profile 写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能生效。
*      (3)没有标注@Profile的bean在任何环境下都是会加载的。
*
*
* 例如:数据源:开发环境(dev)、测试环境(uat)、生产环境(prod)
*
*/
//@Profile("prod")
@PropertySource("classpath:/db.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

    // 三种方式获取配置文件数据
    @Value("${db.user}")
    private String user;
    private String driverClass;

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

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

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

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

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

(4)测试类查看执行结果

package com.xiang.spring.test;

import com.xiang.spring.config.MainConfigOfProfile;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;

public class IOCTest_Profile {

    /**
     * 切换环境
     *      (1)使用命令行参数
     *          run-runconfiguration  -> 参数写上:-Dspring.profiles.active=prod
     *      (2)使用代码的方式激活某个环境
     */
    @Test
    public void test01() {
        // 创建ioc容器,容器创建时,默认会将单例的bean都创建出来
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
        String[] dataSources = applicationContext.getBeanNamesForType(DataSource.class);
        for (String dataSource : dataSources) {
            System.out.println(dataSource);
        }
    }

    @Test
    public void test02() {
        // 使用代码方式 设置环境
        // 1.创建一个applicationContext
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 2.设置激活的环境,环境可设置多个
        applicationContext.getEnvironment().setActiveProfiles("prod", "uat");
        // 3.注册配置类
        applicationContext.register(MainConfigOfProfile.class);
        // 4.启动刷新容器
        applicationContext.refresh();
        String[] dataSources = applicationContext.getBeanNamesForType(DataSource.class);
        for (String dataSource : dataSources) {
            System.out.println(dataSource);
        }
    }
}