Spring 注解驱动开发【04-自动装配】

Spring 注解驱动开发【自动装配】

1.定义

​ Spring利用依赖注入(DI),完成对ioc容器中各组件的依赖关系进行赋值

2.bean的属性自动装配

1.@Autowired【Spring定义的】

分别创建BookMapper、BookService和BookController

@Reposity
public class BookMapper{
    
    
    ...
}
@Service
//	注册进容器的组件默认bean名字为类名小写
public class BookService {
    
    
    @Autowired
    private BookMapper bookMapper;
}
@Controller
public class BookController {
    
    
    @Autowired
    private BookService bookService;
}

  • 创建配置类并添加组件扫描注解
@ComponentScan({
    
    "indi.zhihuali.service",
                "indi.zhihuali.mapper",
                "indi.zhihuali.controller"})
public class ConfigOfAutowired {
    
    
}
  • @Autowired

1.1.默认优先按照类型去容器中寻找对应组件(以BookService为例):

applicationContext.getBean(BookMapper.class);

​ 如果找到就给bookService中的bookMapper赋值

1.1.2.若找到多个相同类型的组件 再根据组件名作为组件的id去容器中查找

注意:默认情况下 添加了@Component的组件 id即为类名首字母小写

applicationContext.getBean("bookMapper");

​ 为了区分ioc容器中的组件 给mapper设置一个label从而分辨不同的mapper对象

@Data
@AllArgsConstructor
@NoArgsConstructor
//  默认以类名小写首字母作为ioc容器中bean的名字
public class BookMapper {
    
    
//    给BookMapper添加一个标签以分辨在ioc容器中是哪个mapper
//	  将BookMapper设置默认的label为 1
    private String label = "1";
}

​ 在配置类中再次注册一个组件 命名为bookMapper2并设置label为2

//	Config类
@Bean(value = "bookMapper2")
    public BookMapper bookMapper(){
    
    
            BookMapper mapper = new BookMapper();
            mapper.setLabel("2");
            return mapper;
        }

​ 在Service中添加toString方法以便查看Service中的Mapper对象label

@Service
public class BookService {
    
    
    @Autowired
    @Qualifier("bookMapper")
    private BookMapper bookMapper;
    

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

编写测试类

public class TestAutowired {
    
    
//	获取ioc容器
    private ApplicationContext context = new AnnotationConfigApplicationContext(ConfigOfAutowired.class);
    @Test
    public void test(){
    
    
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
        BookService service = context.getBean(BookService.class);
        System.out.println(service);

        //可以看到容器中的mapper和service中装配的mapper打印出来的地址相同
//        BookMapper mapper = context.getBean("bookMapper",BookMapper.class);
//        System.out.println(mapper);
    }

}

​ 输出:

BookService{bookMapper=BookMapper(label=1)}

​ 到此可以总结出:

@Autowired默认先对类型进行匹配 若同时匹配到多个类型 则按照名称进行匹配
即先byType再byName

1.1.3.同样可以通过@Qualifier(“bookMapper”) 指定需要装配的组件的id 而不是用默认的属性名**

  • 在配置类中注册mapper2

    @Bean(value = "bookMapper2")
    public BookMapper bookMapper(){
          
          
        BookMapper mapper = new BookMapper();
        mapper.setLabel("2");
        return mapper;
        }
    
  • 通过@Qualifier对bean指定

@Service
public class BookService {
    
    
    @Autowired
    @Qualifier("bookMapper2")
    private BookMapper bookMapper;


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

测试运行结果为:

​ BookService{bookMapper=BookMapper(label=2)}

1.1.4.当容器中没有匹配bean时

​ 自动装配 默认一定要将属性赋值好 没有就会报错

​ 可以通过

@Autowired(required = false)

​ 当没有指定bean时 为空也可以

BookService{
    
    bookMapper=null}

1.1.5.@Primary

在配置类中注册bean时 可以在其上添加该注解从而使其变成首选bean

当spring自动装配时,默认使用首选的bean

添加该注解后 若指定的是首选bean时 就不需要重复写@Qualifier了 如果是其他bean则仍可以通过@Qualifier进行指定

	@Primary
    @Bean(value = "bookMapper2")
    public BookMapper bookMapper(){
    
    
        BookMapper mapper = new BookMapper();
        mapper.setLabel("2");
        return mapper;
        }

2.@Resource(JSR250)和@Inject(JSR330)(不常用)【Java规范】

  • @Resource

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

@Service
public class BookService {
    
    
    @Resource
    private BookMapper bookMapper;
}

​ 没有能支持@Primary和require = false的功能

  • @Inject

    需要导入javax.inject的包 和Autowired的功能一样 但没有require = false的功能

@Autowired、@Resource、@Inject都是通过AutowiredAnnotationBeanPostProcessor 解析完成自动配置功能的

3.bean的方法、构造器位置的自动装配

3.1.在set方法上加@Autowired

创建实体类Boss、Car并注册入容器中

@Component
public class Boss {
    
    
//  @Autowored
    private Car car;

    public Car getCar() {
    
    
        return car;
    }

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

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

@Component
public class Car {
    
    
    public Car() {
    
    
    }
}
  • 测试通过方法@Autowired装配的car是否来自ioc容器中
 @Test
    public void test02(){
    
    
        Boss boss = context.getBean(Boss.class);
        System.out.println(boss);
        Car car = context.getBean(Car.class);
        System.out.println(car);
    }

​ 测试结果:

Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90

结果显示 Boss的Car属性确实来自于ioc容器中

注:@Autowired注解也可以标在形参处

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

3.2.在有参构造器上加@Autowired

**默认情况下:加载ioc容器中的组件,容器启动是会调用无参构造器创建对象,之后再进行初始化赋值等操作 **

  • 给Boss添加有参构造器
@Autowired
    public Boss(Car car) {
    
    
        this.car = car;
    }
  • 测试通过方法@Autowired装配的car是否来自ioc容器中
 @Test
    public void test02(){
    
    
        Boss boss = context.getBean(Boss.class);
        System.out.println(boss);
        Car car = context.getBean(Car.class);
        System.out.println(car);
    }

​ 测试结果:

Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90

​ 因此可得:构造器要用的组件 也是来自ioc容器中

​ 与set方法中一样,注解也可以标在形参前

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

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

3.3.在参数前加@Autowired

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

3.4.配置类中@Bean处添加@Autowired(默认不写)

参数从容器中获取 默认不写@Autowired 效果一样 都能自动装配

  • 创建实体类Manager

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Manager {
          
          
        private Car car;
    }
    
  • 在配置类中@Bean注册bean

     @Bean
        public Manager manager(@Autowired(默认不写) Car car){
          
          
            Manager manager = new Manager();
            manager.setCar(car);
            return manager;
        }
    
  • 测试通过Bean上添加@Autowired装配的car是否来自ioc容器中

     @Test
        public void test02(){
          
          
            Car car = context.getBean(Car.class);
            System.out.println(car);
            Manager manager = context.getBean(Manager.class);
            System.out.println(manager);
        }
    

    ​ 测试结果:

    Manager{car=indi.zhihuali.pojo.Car@120ba90}
    indi.zhihuali.pojo.Car@120ba90
    

4.Aware注入Spring底层组件

当组件需要用到Spring定义的底层组件如:ApplicationContext、BeanFactory…

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

Aware接口的定义:

 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.
 标记超接口,指示bean有资格被
 Spring容器通过回调方式调用特定框架对象。

将Spring底层的一些组件注入到自定义的bean中

实例:

  • 自定义类并实现xxxAware接口
@Component
public class Red implements ApplicationContextAware, EmbeddedValueResolverAware, BeanNameAware {
    
    
    private ApplicationContext applicationContext;

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

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

//    String值解析器 可以解析占位符
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
    
    
        String s = resolver.resolveStringValue("你好!${os.name},我是#{18*20}!");
        System.out.println(s);
    }
}
  • 测试
@Test
    public void test(){
    
    
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
        BookService service = context.getBean(BookService.class);
        System.out.println(service);
    }

输出结果为:

当前bean的名字red
你好!Windows 10,我是360!
传入的Iocorg.springframework.context.annotation.AnnotationConfigApplicationContext@117365b, started on Wed Sep 23 17:19:55 CST 2020
postProcessBeforeInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
postProcessAfterInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
...

xxxAware往往都会有对应的Processor后置处理器通过debug实现的xxxAware接口中重写的方法 可以看到后置处理器也在工作 也会调用BeforeInitialization()…

5.@Profile

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

在实际工作环境中 需要切换不同的环境来适应不同的业务、进行不同的操作

例如:在测试环境与开发环境中,连接数据库中不同的表

​ 在不同的环境下连接不同的Tomcat端口号等等

1.在@Bean上添加@Profile

  • 配置不同生产环境中的数据源信息
@Configuration
public class ConfigOfProfile {
    
    
//    测试环境(假设) :
    public DataSource dataSourceTest() throws PropertyVetoException {
    
    
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("1234");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass("com.jdbc.mysql.Driver");
        return dataSource;
    }
//		开发环境
    public DataSource dataSourceDev() throws PropertyVetoException {
    
    
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("1234");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
        dataSource.setDriverClass("com.jdbc.mysql.Driver");
        return dataSource;
    }
    
//		生产环境
    public DataSource dataSourceProd() throws PropertyVetoException {
    
    
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("1234");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
        dataSource.setDriverClass("com.jdbc.mysql.Driver");
        return dataSource;
    }
}

  • 通常情况下配置信息不会暴露在代码中而会写在配置文件中

    db.properties

db.user=root
db.password=1234
db.driverClass=com.mysql.jdbc.Driver
  • 获取.properties文件中数据的三种方法:

配置类首先要添加注解配置源文件

@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile{
    
    
  1. 直接通过@Value获取配置文件中的数据
	@Value("${db.user}")
    private String user;
  1. 通过向@Bean下的方法用@ Value添加形参并获取配置文件中的数据
@Bean("test")
    public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
    
    
  1. 实现EmbeddedValueResolverAware接口并重写setEmbeddedValueResolver方法对占位符进行解析获取
public class xxx implements EmbeddedValueResolverAware{
    
    
     private StringValueResolver valueResolver;
     private String driverClass;
    
 	 public void setEmbeddedValueResolver(StringValueResolver resolver) {
    
    
        this.valueResolver = resolver;
        driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}

修改后的配置类为:

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

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

    private StringValueResolver valueResolver;

    private String driverClass;

    @Bean("test")
//    test环境(假设) :
    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(driverClass);
        return dataSource;
    }

    @Bean("dev")
    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/dev");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("prod")
    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/prod");

        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

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

  • 指定组件在哪个环境的情况下才能被注册到容器中

    只有当@Profile的环境被激活时才能将这个bean注册到容器中 默认环境为default

    
    
  • 通过不同方式来激活不同的环境

    1. VM options处输入-Dspring.profiles.active=test
    2. 在Test类中注册ioc容器时通过setActiveProfiles激活环境
    public class TestProfile {
          
          
        @Test
        public void test(){
          
          
    
    //        1.通过无参构造器创建一个ioc容器
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //        2.设置需要激活的环境
            context.getEnvironment().setActiveProfiles("test","dev");
    //        3.设置主配置类
            context.register(ConfigOfProfile.class);
    //        4.启动刷新容器
            context.refresh();
    
            String[] beanDefinitionNames = context.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
          
          
                System.out.println(beanDefinitionName);
            }
        }
    }
    
    

    注意:

    ​ 原来直接将配置类作为参数传入构造器初始化ioc容器

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(xxxConfiguration.class);
    

    ​ 可以看一下ioc容器的有参构造器

    public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
          
          
    		this();
    		register(componentClasses);
    		refresh();
    	}
    

    ​ 对比上面的代码:

//        1.通过无参构造器创建一个ioc容器
		AnnotationConfigApplicationContext context = new 		AnnotationConfigApplicationContext();
//        2.设置需要激活的环境
        context.getEnvironment().setActiveProfiles("test","dev");
//        3.设置主配置类
        context.register(ConfigOfProfile.class);
//        4.启动刷新容器
        context.refresh();

可以发现

​ 原来有参构造器直接将配置类进行注入而没有配置任何profile 这里只是插入一个设置活跃profile的过程

  • 测试结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
test
dev

可见 只有test和dev被注册入ioc 而prod并没有 因此可以看出只有当当前环境的profile与bean的@Profile内部参数的一致时,该bean才会被注册入ioc容器

2.在类上添加@Profile

@Profile("test")
@Configuration
public class MyConfig{
    
    
    ....
}

只有当ioc容器中设置的环境与@Profile(“test”)一致时才会注册入整个配置类

  • 测试
public class TestProfile {
    
    
    @Test
    public void test(){
    
    
//        1.通过无参构造器创建一个ioc容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//        2.设置需要激活的环境
        context.getEnvironment().setActiveProfiles("dev");
//        3.设置主配置类
        context.register(ConfigOfProfile.class);
//        4.启动刷新容器
        context.refresh();

        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
    }
}

输出结果为:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

自定义的配置bean都没有被注册进去

此时无论类中是否有@Profile(“dev”) 由于整个配置类都不会被注册进容器 因此也不会有bean被注册

3.没有标注@Profile的bean

在配置类可以被加载的情况下 非2中情况时 没有标注环境的bean一定会被注册进容器中

@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {
    
    
    @Value("${db.user}")
    private String user;
    private StringValueResolver valueResolver;
    private String driverClass;

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

    @Bean("test")
    @Profile("test")
//    Test环境
    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(driverClass);
        return dataSource;
    }

    @Bean("dev")
    @Profile("dev")
//    开发环境
    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/dev");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("prod")
    @Profile("prod")
//    生产环境
    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/prod");

        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    
// 	给容器中添加一个不指定环境的bean
    
    
    
    @Bean
    public Person person(){
    
    
        return new Person();
    }
}

public class TestProfile {
    
    
    @Test
    public void test(){
    
    
//        1.通过无参构造器创建一个ioc容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//        2.设置需要激活的环境
        context.getEnvironment().setActiveProfiles("dev");
//        3.设置主配置类
        context.register(ConfigOfProfile.class);
//        4.启动刷新容器
        context.refresh();

        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
    }
}

测试结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
dev
person

Spring 注解驱动开发【自动装配】

1.定义

​ Spring利用依赖注入(DI),完成对ioc容器中各组件的依赖关系进行赋值

2.bean的属性自动装配

1.@Autowired【Spring定义的】

分别创建BookMapper、BookService和BookController

@Reposity
public class BookMapper{
    
    
    ...
}
@Service
//	注册进容器的组件默认bean名字为类名小写
public class BookService {
    
    
    @Autowired
    private BookMapper bookMapper;
}
@Controller
public class BookController {
    
    
    @Autowired
    private BookService bookService;
}

  • 创建配置类并添加组件扫描注解
@ComponentScan({
    
    "indi.zhihuali.service",
                "indi.zhihuali.mapper",
                "indi.zhihuali.controller"})
public class ConfigOfAutowired {
    
    
}
  • @Autowired

1.1.默认优先按照类型去容器中寻找对应组件(以BookService为例):

applicationContext.getBean(BookMapper.class);

​ 如果找到就给bookService中的bookMapper赋值

1.1.2.若找到多个相同类型的组件 再根据组件名作为组件的id去容器中查找

注意:默认情况下 添加了@Component的组件 id即为类名首字母小写

applicationContext.getBean("bookMapper");

​ 为了区分ioc容器中的组件 给mapper设置一个label从而分辨不同的mapper对象

@Data
@AllArgsConstructor
@NoArgsConstructor
//  默认以类名小写首字母作为ioc容器中bean的名字
public class BookMapper {
    
    
//    给BookMapper添加一个标签以分辨在ioc容器中是哪个mapper
//	  将BookMapper设置默认的label为 1
    private String label = "1";
}

​ 在配置类中再次注册一个组件 命名为bookMapper2并设置label为2

//	Config类
@Bean(value = "bookMapper2")
    public BookMapper bookMapper(){
    
    
            BookMapper mapper = new BookMapper();
            mapper.setLabel("2");
            return mapper;
        }

​ 在Service中添加toString方法以便查看Service中的Mapper对象label

@Service
public class BookService {
    
    
    @Autowired
    @Qualifier("bookMapper")
    private BookMapper bookMapper;
    

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

编写测试类

public class TestAutowired {
    
    
//	获取ioc容器
    private ApplicationContext context = new AnnotationConfigApplicationContext(ConfigOfAutowired.class);
    @Test
    public void test(){
    
    
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
        BookService service = context.getBean(BookService.class);
        System.out.println(service);

        //可以看到容器中的mapper和service中装配的mapper打印出来的地址相同
//        BookMapper mapper = context.getBean("bookMapper",BookMapper.class);
//        System.out.println(mapper);
    }

}

​ 输出:

BookService{bookMapper=BookMapper(label=1)}

​ 到此可以总结出:

@Autowired默认先对类型进行匹配 若同时匹配到多个类型 则按照名称进行匹配
即先byType再byName

1.1.3.同样可以通过@Qualifier(“bookMapper”) 指定需要装配的组件的id 而不是用默认的属性名**

  • 在配置类中注册mapper2

    @Bean(value = "bookMapper2")
    public BookMapper bookMapper(){
          
          
        BookMapper mapper = new BookMapper();
        mapper.setLabel("2");
        return mapper;
        }
    
  • 通过@Qualifier对bean指定

@Service
public class BookService {
    
    
    @Autowired
    @Qualifier("bookMapper2")
    private BookMapper bookMapper;


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

测试运行结果为:

​ BookService{bookMapper=BookMapper(label=2)}

1.1.4.当容器中没有匹配bean时

​ 自动装配 默认一定要将属性赋值好 没有就会报错

​ 可以通过

@Autowired(required = false)

​ 当没有指定bean时 为空也可以

BookService{
    
    bookMapper=null}

1.1.5.@Primary

在配置类中注册bean时 可以在其上添加该注解从而使其变成首选bean

当spring自动装配时,默认使用首选的bean

添加该注解后 若指定的是首选bean时 就不需要重复写@Qualifier了 如果是其他bean则仍可以通过@Qualifier进行指定

	@Primary
    @Bean(value = "bookMapper2")
    public BookMapper bookMapper(){
    
    
        BookMapper mapper = new BookMapper();
        mapper.setLabel("2");
        return mapper;
        }

2.@Resource(JSR250)和@Inject(JSR330)(不常用)【Java规范】

  • @Resource

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

@Service
public class BookService {
    
    
    @Resource
    private BookMapper bookMapper;
}

​ 没有能支持@Primary和require = false的功能

  • @Inject

    需要导入javax.inject的包 和Autowired的功能一样 但没有require = false的功能

@Autowired、@Resource、@Inject都是通过AutowiredAnnotationBeanPostProcessor 解析完成自动配置功能的

3.bean的方法、构造器位置的自动装配

3.1.在set方法上加@Autowired

创建实体类Boss、Car并注册入容器中

@Component
public class Boss {
    
    
//  @Autowored
    private Car car;

    public Car getCar() {
    
    
        return car;
    }

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

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

@Component
public class Car {
    
    
    public Car() {
    
    
    }
}
  • 测试通过方法@Autowired装配的car是否来自ioc容器中
 @Test
    public void test02(){
    
    
        Boss boss = context.getBean(Boss.class);
        System.out.println(boss);
        Car car = context.getBean(Car.class);
        System.out.println(car);
    }

​ 测试结果:

Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90

结果显示 Boss的Car属性确实来自于ioc容器中

注:@Autowired注解也可以标在形参处

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

3.2.在有参构造器上加@Autowired

**默认情况下:加载ioc容器中的组件,容器启动是会调用无参构造器创建对象,之后再进行初始化赋值等操作 **

  • 给Boss添加有参构造器
@Autowired
    public Boss(Car car) {
    
    
        this.car = car;
    }
  • 测试通过方法@Autowired装配的car是否来自ioc容器中
 @Test
    public void test02(){
    
    
        Boss boss = context.getBean(Boss.class);
        System.out.println(boss);
        Car car = context.getBean(Car.class);
        System.out.println(car);
    }

​ 测试结果:

Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90

​ 因此可得:构造器要用的组件 也是来自ioc容器中

​ 与set方法中一样,注解也可以标在形参前

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

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

3.3.在参数前加@Autowired

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

3.4.配置类中@Bean处添加@Autowired(默认不写)

参数从容器中获取 默认不写@Autowired 效果一样 都能自动装配

  • 创建实体类Manager

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Manager {
          
          
        private Car car;
    }
    
  • 在配置类中@Bean注册bean

     @Bean
        public Manager manager(@Autowired(默认不写) Car car){
          
          
            Manager manager = new Manager();
            manager.setCar(car);
            return manager;
        }
    
  • 测试通过Bean上添加@Autowired装配的car是否来自ioc容器中

     @Test
        public void test02(){
          
          
            Car car = context.getBean(Car.class);
            System.out.println(car);
            Manager manager = context.getBean(Manager.class);
            System.out.println(manager);
        }
    

    ​ 测试结果:

    Manager{car=indi.zhihuali.pojo.Car@120ba90}
    indi.zhihuali.pojo.Car@120ba90
    

4.Aware注入Spring底层组件

当组件需要用到Spring定义的底层组件如:ApplicationContext、BeanFactory…

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

Aware接口的定义:

 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.
 标记超接口,指示bean有资格被
 Spring容器通过回调方式调用特定框架对象。

将Spring底层的一些组件注入到自定义的bean中

实例:

  • 自定义类并实现xxxAware接口
@Component
public class Red implements ApplicationContextAware, EmbeddedValueResolverAware, BeanNameAware {
    
    
    private ApplicationContext applicationContext;

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

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

//    String值解析器 可以解析占位符
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
    
    
        String s = resolver.resolveStringValue("你好!${os.name},我是#{18*20}!");
        System.out.println(s);
    }
}
  • 测试
@Test
    public void test(){
    
    
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
        BookService service = context.getBean(BookService.class);
        System.out.println(service);
    }

输出结果为:

当前bean的名字red
你好!Windows 10,我是360!
传入的Iocorg.springframework.context.annotation.AnnotationConfigApplicationContext@117365b, started on Wed Sep 23 17:19:55 CST 2020
postProcessBeforeInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
postProcessAfterInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
...

xxxAware往往都会有对应的Processor后置处理器通过debug实现的xxxAware接口中重写的方法 可以看到后置处理器也在工作 也会调用BeforeInitialization()…

5.@Profile

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

在实际工作环境中 需要切换不同的环境来适应不同的业务、进行不同的操作

例如:在测试环境与开发环境中,连接数据库中不同的表

​ 在不同的环境下连接不同的Tomcat端口号等等

1.在@Bean上添加@Profile

  • 配置不同生产环境中的数据源信息
@Configuration
public class ConfigOfProfile {
    
    
//    测试环境(假设) :
    public DataSource dataSourceTest() throws PropertyVetoException {
    
    
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("1234");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass("com.jdbc.mysql.Driver");
        return dataSource;
    }
//		开发环境
    public DataSource dataSourceDev() throws PropertyVetoException {
    
    
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("1234");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
        dataSource.setDriverClass("com.jdbc.mysql.Driver");
        return dataSource;
    }
    
//		生产环境
    public DataSource dataSourceProd() throws PropertyVetoException {
    
    
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("1234");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
        dataSource.setDriverClass("com.jdbc.mysql.Driver");
        return dataSource;
    }
}

  • 通常情况下配置信息不会暴露在代码中而会写在配置文件中

    db.properties

db.user=root
db.password=1234
db.driverClass=com.mysql.jdbc.Driver
  • 获取.properties文件中数据的三种方法:

配置类首先要添加注解配置源文件

@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile{
    
    
  1. 直接通过@Value获取配置文件中的数据
	@Value("${db.user}")
    private String user;
  1. 通过向@Bean下的方法用@ Value添加形参并获取配置文件中的数据
@Bean("test")
    public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
    
    
  1. 实现EmbeddedValueResolverAware接口并重写setEmbeddedValueResolver方法对占位符进行解析获取
public class xxx implements EmbeddedValueResolverAware{
    
    
     private StringValueResolver valueResolver;
     private String driverClass;
    
 	 public void setEmbeddedValueResolver(StringValueResolver resolver) {
    
    
        this.valueResolver = resolver;
        driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}

修改后的配置类为:

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

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

    private StringValueResolver valueResolver;

    private String driverClass;

    @Bean("test")
//    test环境(假设) :
    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(driverClass);
        return dataSource;
    }

    @Bean("dev")
    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/dev");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("prod")
    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/prod");

        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

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

  • 指定组件在哪个环境的情况下才能被注册到容器中

    只有当@Profile的环境被激活时才能将这个bean注册到容器中 默认环境为default

    
    
  • 通过不同方式来激活不同的环境

    1. VM options处输入-Dspring.profiles.active=test
    2. 在Test类中注册ioc容器时通过setActiveProfiles激活环境
    public class TestProfile {
          
          
        @Test
        public void test(){
          
          
    
    //        1.通过无参构造器创建一个ioc容器
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //        2.设置需要激活的环境
            context.getEnvironment().setActiveProfiles("test","dev");
    //        3.设置主配置类
            context.register(ConfigOfProfile.class);
    //        4.启动刷新容器
            context.refresh();
    
            String[] beanDefinitionNames = context.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
          
          
                System.out.println(beanDefinitionName);
            }
        }
    }
    
    

    注意:

    ​ 原来直接将配置类作为参数传入构造器初始化ioc容器

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(xxxConfiguration.class);
    

    ​ 可以看一下ioc容器的有参构造器

    public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
          
          
    		this();
    		register(componentClasses);
    		refresh();
    	}
    

    ​ 对比上面的代码:

//        1.通过无参构造器创建一个ioc容器
		AnnotationConfigApplicationContext context = new 		AnnotationConfigApplicationContext();
//        2.设置需要激活的环境
        context.getEnvironment().setActiveProfiles("test","dev");
//        3.设置主配置类
        context.register(ConfigOfProfile.class);
//        4.启动刷新容器
        context.refresh();

可以发现

​ 原来有参构造器直接将配置类进行注入而没有配置任何profile 这里只是插入一个设置活跃profile的过程

  • 测试结果为:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
test
dev

可见 只有test和dev被注册入ioc 而prod并没有 因此可以看出只有当当前环境的profile与bean的@Profile内部参数的一致时,该bean才会被注册入ioc容器

2.在类上添加@Profile

@Profile("test")
@Configuration
public class MyConfig{
    
    
    ....
}

只有当ioc容器中设置的环境与@Profile(“test”)一致时才会注册入整个配置类

  • 测试
public class TestProfile {
    
    
    @Test
    public void test(){
    
    
//        1.通过无参构造器创建一个ioc容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//        2.设置需要激活的环境
        context.getEnvironment().setActiveProfiles("dev");
//        3.设置主配置类
        context.register(ConfigOfProfile.class);
//        4.启动刷新容器
        context.refresh();

        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
    }
}

输出结果为:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

自定义的配置bean都没有被注册进去

此时无论类中是否有@Profile(“dev”) 由于整个配置类都不会被注册进容器 因此也不会有bean被注册

3.没有标注@Profile的bean

在配置类可以被加载的情况下 非2中情况时 没有标注环境的bean一定会被注册进容器中

@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {
    
    
    @Value("${db.user}")
    private String user;
    private StringValueResolver valueResolver;
    private String driverClass;

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

    @Bean("test")
    @Profile("test")
//    Test环境
    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(driverClass);
        return dataSource;
    }

    @Bean("dev")
    @Profile("dev")
//    开发环境
    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/dev");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Bean("prod")
    @Profile("prod")
//    生产环境
    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/prod");

        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    
// 	给容器中添加一个不指定环境的bean
    
    
    
    @Bean
    public Person person(){
    
    
        return new Person();
    }
}

public class TestProfile {
    
    
    @Test
    public void test(){
    
    
//        1.通过无参构造器创建一个ioc容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//        2.设置需要激活的环境
        context.getEnvironment().setActiveProfiles("dev");
//        3.设置主配置类
        context.register(ConfigOfProfile.class);
//        4.启动刷新容器
        context.refresh();

        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            System.out.println(beanDefinitionName);
        }
    }
}

测试结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
dev
person

猜你喜欢

转载自blog.csdn.net/weixin_45729934/article/details/108761941