Spring注解驱动开发03

自动装配

1、@Autowired、@Qualifier、@Primary

@Autowired注解

@Autowired注解可以对类成员变量、方法和构造函数进行标注,完成自动装配的工作。@Autowired注解可以放在类、接口以及方法上。

在使用@Autowired注解之前,我们对一个bean配置属性时,是用如下XML配置文件的形式进行配置的。

<property name="属性名" ref=" 属性值"/>

下面我们来看一下@Autowired注解的源码,如下所示。

在这里插入图片描述

这儿对@Autowired注解说明一下:

  1. @Autowired注解默认是优先按照类型去容器中找对应的组件,相当于是调用了如下这个方法:

    applicationContext.getBean(类名.class);
    

    若找到则就赋值

  2. 如果找到多个相同类型的组件,那么是将属性名称作为组件的id,到IOC容器中进行查找,这时就相当于是调用了如下这个方法:

    applicationContext.getBean("组件的id");
    

@Qualifier注解

@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那么就需要配合@Qualifier注解来使用了。

下面我们来看一下@Qualifier注解的源码,如下所示

在这里插入图片描述

@Primary注解

在Spring中使用注解时,常常会使用到@Autowired这个注解,它默认是根据类型Type来自动注入的。但有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类。

下面我们来看一下@Primary注解的源码,如下所示。
在这里插入图片描述

自动装配

在进行项目实战之前,我们先来说说什么是Spring组件的自动装配。Spring组件的自动装配就是Spring利用依赖注入,也就是我们通常所说的DI,完成对IOC容器中各个组件的依赖关系赋值

测试@Autowired注解

这里,我们以之前项目中创建的BookDao、BookService和BookController为例进行说明。BookDao、BookService和BookController的初始代码分别如下所示。

  • BookDao

    package com.jian.dao;
    
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class BookDao {
       
    }
    
  • BookService

    package com.jian.service;
    
    import com.jian.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
        @Qualifier("bookDao")
        private BookDao bookDao;
    
        @Override
        public String toString() {
            return "BookService{" +
                    "bookDao=" + bookDao +
                    '}';
        }
    }
    
  • BookController

    package com.jian.controller;
    
    import com.jian.service.BookService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class BookController {
        @Autowired
        private BookService bookService;
    }
    

新增一个配置类MainConfigOfAutowired

package com.jian.config;

import com.jian.dao.BookDao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@ComponentScan({"com.jian.dao","com.jian.service"})
public class MainConfigOfAutowired {
    
}

添加测试类AutowiredTest进行测试

@Test
public void test(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
    BookService bookService = applicationContext.getBean(BookService.class);
    System.out.println(bookService.toString());
}

在这里插入图片描述

接下来测试BookService类中使用@Autowired注解注入的BookDao(最后输出了该BookDao的信息),和我们直接在Spring IOC容器中获取的BookDao是不是同一个对象

@Test
public void test(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
    BookService bookService = applicationContext.getBean(BookService.class);
    System.out.println(bookService.toString());
    BookDao bookDao = applicationContext.getBean(BookDao.class);
    System.out.println(bookDao);
}

在这里插入图片描述

可以看到,我们在BookService类中使用@Autowired注解注入的BookDao对象和直接从IOC容器中获取的BookDao对象是同一个对象

如果在Spring容器中存在对多个BookDao对象,那么这时又该如何处理呢?

首先,为了更加直观的看到我们使用@Autowired注解装配的是哪个BookDao对象,我们得对BookDao类进行改造,为其加上一个lable字段,并为其赋一个默认值,如下所示。

package com.jian.dao;

import org.springframework.stereotype.Repository;

@Repository
public class BookDao {
    private String label="1";

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    @Override
    public String toString() {
        return "BookDao{" +
                "label='" + label + '\'' +
                '}';
    }
}

然后,我们就在MainConfigOfAutowired配置类中注入一个BookDao对象,并且显示指定该对象在IOC容器中的bean的名称为bookDao2,并还为该对象的lable字段赋值为2,如下所示。

package com.jian.config;

import com.jian.dao.BookDao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@ComponentScan({"com.jian.dao","com.jian.service"})
public class MainConfigOfAutowired {
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

目前,在我们的IOC容器中就会注入两个BookDao对象。那此时,@Autowired注解到底装配的是哪个BookDao对象呢?

接着,我们来运行一下IOCTest_Autowired类中的test01()方法,发现输出的结果信息如下所示。

在这里插入图片描述

可以看到,结果信息输出了lable=1,这说明,@Autowired注解默认是优先按照类型去容器中找对应的组件,找到就赋值;如果找到多个相同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行查找。

那我们如何让@Autowired注解装配bookDao2呢? 这个问题问的好,其实很简单,我们只须将BookService类中的bookDao属性的名称全部修改为bookDao2即可,如下所示。

@Service
public class BookService {
    @Autowired
    //@Qualifier("bookDao2")
    private BookDao bookDao2;

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

此时,我们再运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。

在这里插入图片描述

测试@Qualifer注解

从测试@Autowired注解的结果来看,@Autowired注解默认是优先按照类型去容器中找对应的组件,找到就赋值;如果找到多个相同类型的组件,那么再将属性的名称作为组件的id,到IOC容器中进行查找。

如果IOC容器中存在多个相同类型的组件时,那么我们可不可以显示指定@Autowired注解装配哪个组件呢?有些小伙伴肯定会说:废话!你都这么问了,那肯定可以啊!没错,确实是可以的!此时,@Qualifier注解就派上用场了!

在之前的测试案例中,IDEA控制台中输出了BookDao [lable=2],这说明@Autowired注解装配了bookDao2,那我们如何显示的让@Autowired注解装配bookDao2呢?

比较简单,我们只需要在BookService类里面的bookDao2字段上添加@Qualifier注解,显示指定@Autowired注解装配bookDao即可,如下所示。

@Service
public class BookService {
    @Autowired
    @Qualifier("bookDao2")
    private BookDao bookDao;

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

此时,我们再次运行AutowiredTest类中的test()方法,输出的结果信息如下所示。

在这里插入图片描述

若容器中没有bookDao的组件时,再去获取,则会报错

{@org.springframework.beans.factory.annotation.Qualifier(value=bookDao), @org.springframework.beans.factory.annotation.Autowired(required=true)}

解决方案就是在BookService类的@Autowired注解里面添加一个属性required=false,如下所示。

加上required=false这个玩意的意思就是说找到就装配,找不到就拉到,就别装配了。

package com.jian.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.meimeixia.dao.BookDao;

@Service
public class BookService {

	@Qualifier("bookDao")
	@Autowired(required=false)
	private BookDao bookDao2;
	
	public void print() {
		System.out.println(bookDao2);
	}

	@Override
	public String toString() {
		return "BookService [bookDao2=" + bookDao2 + "]";
	}
	
}

测试@primary注解

在Spring中,对同一个接口而言,可能会有几种不同的实现类,而默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类。

如果IOC容器中相同类型的组件有多个,那么我们不可避免地就要来回用@Qualifier注解来指定要装配哪个组件,这还是比较麻烦的,Spring正是帮我们考虑到了这样一种情况,就提供了这样一个比较强大的注解,即@Primary。我们可以利用这个注解让Spring进行自动装配的时候,默认使用首选的bean。

说了这么多,下面我们就用一个小例子来测试一下@Primary注解。

首先,我们在MainConfigOfAutowired配置类的bookDao()方法上添加上@Primary注解,如下所示。

@Configuration
@ComponentScan({"com.jian.dao","com.jian.service"})
public class MainConfigOfAutowired {
    @Bean("bookDao2")
    @Primary
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

注意:此时,我们需要注释掉BookService类中bookDao字段上的@Qualifier注解,这是因为@Qualifier注解为显示指定装配哪个组件,如果使用了@Qualifier注解,无论是否使用了@Primary注解,都会装配@Qualifier注解标注的对象。

@Service
public class BookService {
    @Autowired
    //@Qualifier("bookDao2")
    private BookDao bookDao;

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

设置完成后,我们再次运行IOCTest_Autowired类中的test01()方法,输出的结果信息如下所示。

在这里插入图片描述

可以看到,此时lable的值为2,这说明装配了MainConfigOfAutowired配置类中注入的bookDao2。

那我们非要装配bookDao,可不可以呢?当然可以了,我们只须使用@Qualifier("bookDao")来显示指定装配bookDao即可

2、@Resource和@Inject注解

@Resource注解

@Resource注解是Java规范里面的,也可以说它是JSR250规范里面定义的一个注解。该注解默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,那么默认取字段名将其作为组件的名称在IOC容器中进行查找,如果注解写在setter方法上,那么默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的一点是,如果name属性一旦指定,那么就只会按照名称进行装配
我们先看一下@Resource注解的源码,如下所示。

在这里插入图片描述

@Inject注解

@Inject注解也是Java规范里面的,也可以说它是JSR330规范里面定义的一个注解。该注解默认是根据参数名去寻找bean注入,支持Spring的@Primary注解优先注入,@Inject注解还可以增加@Named注解指定要注入的bean。

我们先看一下@Inject注解的源码,如下所示。

温馨提示,要想使用@Inject注解,需要在项目的pom.xml文件中添加如下依赖,即导入javax.inject这个包。

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

测试@Resource注解

首先,我们将项目中的BookService类标注在bookDao字段上的@Autowired注解和@Qualifier注解注释掉,然后添加上@Resource注解,如下所示

@Service
public class BookService {
    //@Autowired
    //@Qualifier("bookDao2")
    @Resource
    private BookDao bookDao;

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

然后,我们运行一下AutowiredTest类中的test()方法,输出的结果信息如下所示。

在这里插入图片描述

可以看到,使用@Resource注解也能够自动装配组件,只不过此时自动装配的是lable为1的bookDao,而不是我们在MainConfigOfAutowired配置类中配置的优先装配的lable为2的bookDao。MainConfigOfAutowired配置类中配置的lable为2的bookDao如下所示

@Configuration
@ComponentScan({"com.jian.dao","com.jian.service"})
public class MainConfigOfAutowired {
    @Bean("bookDao2")
    @Primary
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

这也进一步说明,@Resource注解和@Autowired注解的功能是一样的,都能实现自动装配,只不过@Resource注解默认是按照组件名称(即属性的名称)进行装配的。虽然@Resource注解具备自动装配这一功能,但是它是不支持@Primary注解优先注入的功能的,而且也不能像@Autowired注解一样能添加required=false属性。

我们在使用@Resource注解时,可以通过@Resource注解的name属性显示指定要装配的组件的名称。例如,我们要想装配lable为2的bookDao,只需要为@Resource注解添加 name="bookDao2"属性即可,如下所示。

@Service
public class BookService {
    //@Autowired
    //@Qualifier("bookDao2")
    @Resource(name = "bookDao2")
    private BookDao bookDao;

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

接着,我们再次运行AutowiredTest类中的test()方法,输出的结果信息如下所示。

在这里插入图片描述

可以看到,此时输出了lable为2的bookDao,说明@Resource注解可以通过name属性显示指定要装配的bean。

测试@Inject注解

在BookService类中,将@Resource注解注释掉,添加@Inject注解,如下所示

@Service
public class BookService {
    //@Autowired
    //@Qualifier("bookDao2")
    //@Resource(name = "bookDao2")
    @Inject
    private BookDao bookDao;

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

修改完毕后,我们运行AutowiredTest类中的test()方法,输出的结果信息如下所示。

在这里插入图片描述

可以看到,使用@Inject注解默认输出的是lable为2的bookDao。这是因为@Inject注解和@Autowired注解一样,默认优先装配使用了@Primary注解标注的组件。

其实,这也进一步说明了,@Inject注解和@Autowired注解的功能是一样的,都能实现自动装配,而且它俩都支持@Primary注解优先注入的功能。只不过,@Inject注解不能像@Autowired注解一样能添加required=false属性,因为它里面没啥属性

@Resource和@Inject这俩注解与@Autowired注解的区别

  • @Autowired是Spring中的专有注解,而@Resource是Java中JSR250规范里面定义的一个注解,@Inject是Java中JSR330规范里面定义的一个注解
  • @Autowired支持参数required=false,而@Resource和@Inject都不支持
  • @Autowired和@Inject支持@Primary注解优先注入,而@Resource不支持
  • @Autowired通过@Qualifier指定注入特定bean,@Resource可以通过参数name指定注入bean,而@Inject需要通过@Named注解指定注入bean

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

再谈@Autowired注解

我们再来看下@Autowired注解的源码

在这里插入图片描述

我们通过@Autowired注解的源码可以看出,在@Autowired注解上标注有如下的注解信息

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})

可以看出@Autowired注解不仅可以标注在字段上,而且还可以标注在构造方法、实例方法以及参数上

案例

首先,我们在项目中新建一个Boss类,在Boss类中有一个Car类的引用,并且我们使用@Component注解将Dog类加载到IOC容器中,如下所示

package com.jian.bean;

import org.springframework.stereotype.Component;

@Component
public class Boss {
    
    private Car car;

    public Car getCar() {
        return car;
    }

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

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

注意,Car类上也要标注@Component注解,即它也要被加载到IOC容器中。

新建好以上Boss类之后,我们还需要在MainConfigOfAutowired配置类的@ComponentScan注解中进行配置,使其能够扫描com.jian.bean包下的类,如下所示

@Configuration
@ComponentScan({"com.jian.dao","com.jian.service","com.jian.bean"})
public class MainConfigOfAutowired {
    @Bean("bookDao2")
    @Primary
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLabel("2");
        return bookDao;
    }
}

此时,我们可以直接在Boss类中的car字段上添加@Autowired注解,使其自动装配

@Autowired标注在set方法上

我们可以将@Autowired注解标注在setter方法上,如下所示

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

当@Autowired注解标注在方法上时,Spring容器在创建当前对象的时候,就会调用相应的方法为对象赋值。如果标注的方法存在参数时,那么方法使用的参数和自定义类型的值,需要从IOC容器中获取。

然后,我们在AutowiredTest类的test()方法中获取和打印Boss信息的代码,如下所示

@Test
public void test(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
    Boss boss = applicationContext.getBean(Boss.class);
    System.out.println(boss.toString());
    applicationContext.close();
}

在这里插入图片描述

说明已经获取到了car的信息,也就是说可以将@Autowired注解标注在方法上

为了验证最终的输出结果是否是从IOC容器中获取的,我们可以在Autowired类的test()方法中直接获取Car对象的信息,如下所示

@Test
public void test(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
    Boss boss = applicationContext.getBean(Boss.class);
    System.out.println(boss.toString());
    Car car = applicationContext.getBean(Car.class);
    System.out.println(car);
    applicationContext.close();
}

在这里插入图片描述

这已然说明在Boss类中通过@Autowired注解获取到的Car对象和直接从IOC容器中获取到Car对象是同一个对象

@Autowired标注在构造方法上

在上面的案例中,我们在Boss类上使用了@Component注解,此时,Spring会默认将该类加载进IOC容器中,IOC容器启动的时候默认会调用bean的无参构造器创建对象,然后再进行初始化、赋值等操作。

接下来,我们为Boss类添加一个有参构造方法,然后去除setCar()方法上的@Autowired注解,将@Autowired注解标注在有参构造方法上,并在构造方法中打印一条信息,如下所示。

@Component
public class Boss {

    private Car car;

    @Autowired
    public Boss(Car car) {
        this.car = car;
        System.out.println("Boss Constructor ... ");
    }

    public Car getCar() {
        return car;
    }

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

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

接着,我们运行AutowiredTest类中的test()方法进行测试,可以看到输出结果信息中存在如下一行信息

在这里插入图片描述

说明IOC容器在启动的时候调用了Boss类的有参构造方法。并且还可以从输出的如下两行信息中看出,通过Boss类的toString()方法打印出的Car对象和直接从IOC容器中获取的Car对象是同一个对象

这里,需要大家注意的是,使用@Autowired注解标注在构造方法上时,构造方法中的参数对象也是从IOC容器中获取的。

其实,还有一点我得说明一下,使用@Autowired注解标注在构造方法上时,如果组件中只有一个有参构造方法,那么这个有参构造方法上的@Autowired注解可以省略,并且参数位置的组件还是可以自动从IOC容器中获取

@Autowired标注在参数上

我们也可以将@Autowired注解标注在参数上,例如,在Boss类中我们将构造方法上的@Autowired注解标注在构造方法的参数上,如下所示

public Boss(@Autowired Car car) {
    this.car = car;
    System.out.println("Boss Constructor ... ");
}

当然了,也可以将@Autowired注解标注在setter方法的参数上,如下所示

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

最终的效果与标注在字段、实例方法和构造方法上的效果都是一样的

在这里插入图片描述

于是,我们可以得出结论:无论@Autowired注解是标注在字段上、实例方法上、构造方法上还是参数上,参数位置的组件都是从IOC容器中获取。

如果Spring的bean中只有一个有参构造方法,并且这个有参构造方法只有一个参数,这个参数还是IOC容器中的对象,当@Autowired注解标注在这个构造方法的参数上时,那么我们可以将其省略掉,如下所示

public Boss(Car car) {
    this.car = car;
    System.out.println("Boss Constructor ... ");
}

运行测试后效果一样

标注在方法位置

@Autowired注解可以标注在某个方法的位置上。这里,为了更好的演示效果,我们新建一个Color类,在Color类中有一个Car类型的成员变量,如下所示

package com.jian.bean;

import org.springframework.stereotype.Component;

@Component
public class Color {
    private Car car;

    public Car getCar() {
        return car;
    }

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

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

然后,我们在MainConfigOfAutowired配置类中实例化Color类,如下所示。

@Bean
public Color color() {
    return new Color();
}

接着,我们在AutowiredTest类中再创建一个test02()测试方法,如下所示。

在这里插入图片描述

说明此时的Color对象中的Car对象为空。此时,我们可以将Car对象作为一个参数传递到MainConfigOfAutowired配置类的color()方法中,并且将该Car对象设置到Color对象中,如下所示

@Bean
public Color color(Car car) {
    Color color = new Color();
    color.setCar(car);
    return color;
}

当然了,我们也可以使用@Autowired注解来标注color()方法中的car参数,就像下面这样

@Bean
public Color color(@Autowired Car car) {
    Color color = new Color();
    color.setCar(car);
    return color;
}

最后运行测试

在这里插入图片描述

说明Car对象被成功创建并设置到Color对象中了

至此,我们可以得出结论:如果方法只有一个IOC容器中的对象作为参数,当@Autowired注解标注在这个方法的参数上时,我们可以将@Autowired注解省略掉。也就说@Bean注解标注的方法在创建对象的时候,方法参数的值是从IOC容器中获取的,此外,标注在这个方法的参数上的@Autowired注解可以省略。

其实,我们用到最多的还是把@Autowired注解标注在方法位置,即使用@Bean注解+方法参数这种形式,此时,该方法参数的值从IOC容器中获取,并且还可以默认不写@Autowired注解,因为效果都是一样的,都能实现自动装配!

4、Aware注入Spring底层组件及原理

自定义的组件要想使用Spring容器底层的一些组件,比如ApplicationContext(IOC容器)、底层的BeanFactory等等,那么只需要让自定义组件实现XxxAware接口即可。此时,Spring在创建对象的时候,会调用XxxAware接口中定义的方法注入相关的组件
们来看下Aware接口的源码

在这里插入图片描述

可以看到,Aware接口是Spring 3.1版本中引入的接口,在Aware接口中,并未定义任何方法。

接下来,我们看看都有哪些接口继承了Aware接口,如下所示

在这里插入图片描述

xxxAware接口案例

接下来,我们就挑选几个常用的XxxAware接口来简单的说明一下。

ApplicationContextAware接口使用的比较多,我们先来说说这个接口,通过ApplicationContextAware接口我们可以获取到IOC容器。

public class Red implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("IOC容器名为:" + applicationContext);
        this.applicationContext = applicationContext;
    }

}

首先,我们创建一个Red类,它得实现ApplicationContextAware接口,并在实现的setApplicationContext()方法中将ApplicationContext输出,如下所示

其实,我们也可以让Red类同时实现几个XxxAware接口,例如,使Red类再实现一个BeanNameAware接口,我们可以通过BeanNameAware接口获取到当前bean在Spring容器中的名称,如下所示

public class Red implements ApplicationContextAware, BeanNameAware {
    private ApplicationContext applicationContext;

    /**
     *IOC容器创建时,为这个组件起的名字
     */
    @Override
    public void setBeanName(String s) {
        System.out.println("当前Bean的名字:"+s);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("IOC容器名为:" + applicationContext);
        this.applicationContext = applicationContext;
    }
}

当然了,我们可以再让Red类实现一个EmbeddedValueResolverAware接口,我们通过EmbeddedValueResolverAware接口能够获取到String值解析器,如下所示。

public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;

    /**
     * IOC容器创建时,为这个组件起的名字
     */
    @Override
    public void setBeanName(String s) {
        System.out.println("当前Bean的名字:" + s);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("IOC容器名为:" + applicationContext);
        this.applicationContext = applicationContext;
    }

    /**
     * 参数resolver:IOC容器启动时会自动地将这个String值的解析器传递过来给我们
     */
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String resolveStringValue = resolver.resolveStringValue("你好,${os.name},我来自:#{36*10}");
        System.out.println("解析的字符串是:"+resolveStringValue);
    }
}

IOC容器启动时会自动地将String值的解析器(即StringValueResolver)传递过来给我们用,咱们可以用它来解析一些字符串,解析哪些字符串呢?比如包含#{}这样的字符串。我们可以看一下StringValueResolver类的源码,如下所示

在这里插入图片描述

接着,我们需要在Red类上标注@Component注解将该类添加到IOC容器中

最后,运行AutowiredTest类中的test02()方法,输出的结果信息如下所示

在这里插入图片描述

在咱们自定义的组件中获取到的IOC容器和测试方法中获取到的IOC容器是不是同一个东东呢?带着这样一个疑问,你不妨试试运行一下以下test02()方法

@Test
public void test02(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);
    Color color = applicationContext.getBean(Color.class);
    System.out.println(color);
    System.out.println(applicationContext);
}

在这里插入图片描述

这已然说明了在咱们自定义的组件中获取到的IOC容器和测试方法中获取到的IOC容器是同一个东东

XxxAware原理

XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类。 例如,我们这里以ApplicationContextAware接口为例,ApplicationContextAware接口的底层原理就是由ApplicationContextAwareProcessor类实现的。从ApplicationContextAwareProcessor类的源码可以看出,其实现了BeanPostProcessor接口,本质上是一个后置处理器

在这里插入图片描述

接下来,我们就以分析ApplicationContextAware接口的原理为例,看看Spring是怎么将ApplicationContext对象注入到Red类中的。

首先,我们在Red类的setApplicationContext()方法上打一个断点,然后,我们以debug的方式来运行AutowiredTest类中的test02()方法

接着,我们在IDEA的方法调用栈中找到postProcessBeforeInitialization()方法并鼠标单击它,如下所示,此时,自动定位到了postProcessBeforeInitialization()方法中

在这里插入图片描述

其实,postProcessBeforeInitialization()方法所在的类就是ApplicationContextAwareProcessor。postProcessBeforeInitialization()方法的逻辑还算比较简单。

紧接着,我们来看下在postProcessBeforeInitialization()方法中调用的invokeAwareInterfaces()方法,如下所示。

在这里插入图片描述

5、@Profile注解

前言

在实际的企业开发环境中,往往都会将环境分为开发环境、测试环境和生产环境,并且每个环境基本上都是互相隔离的,也就是说,开发环境、测试环境和生产环境它们之间是互不相通的。在以前的开发过程中,如果开发人员完成相应的功能模块并通过单元测试后,那么他会通过手动修改配置文件的形式,将项目的配置修改成测试环境,发布到测试环境中进行测试。测试通过后,再将配置修改为生产环境,发布到生产环境中。这样手动修改配置的方式,不仅增加了开发和运维的工作量,而且总是手工修改各项配置文件会很容易出问题。那么,有没有什么方式可以解决这些问题呢?答案是:有!通过@Profile注解就可以完全做到这点。
@Profile注解概述

在容器中如果存在同一类型的多个组件,那么可以使用@Profile注解标识要获取的是哪一个bean。也可以说@Profile注解是Spring为我们提供的可以根据当前环境,动态地激活和切换一系列组件的功能。这个功能在不同的环境使用不同的变量的情景下特别有用,例如,开发环境、测试环境、生产环境使用不同的数据源,在不改变代码的情况下,可以使用这个注解来动态地切换要连接的数据库。

接下来,我们来看下@Profile注解的源码,如下所示。
在这里插入图片描述

从其源码中我们可以得出如下三点结论:

  1. @Profile注解不仅可以标注在方法上,也可以标注在配置类上。
  2. 如果@Profile注解标注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所有配置才会生效。
  3. 如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中,当然了,前提是在整个配置类生效的情况下。

实战案例

接下来,我们就一起来看一个案例,即使用@Profile注解实现开发、测试和生产环境的配置和切换。这里,我们以开发过程中要用到的数据源为例(数据源也是一种组件哟😊)。我们希望在开发环境中,数据源是连向A数据库的;在测试环境中,数据源是连向B数据库的,而且在这一过程中,测试人员压根就不需要改动任何代码;最终项目上线之后,数据源连向C数据库,而且最重要的一点是在整个过程中,我们不希望改动大量的代码,而实现数据源的切换

环境搭建

首先,我们需要在pom.xml文件中添加c3p0数据源和MySQL驱动的依赖,如下所示。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>

添加完以上依赖之后,我们还得在项目中新建一个配置类,例如MainConfigOfProfile,并在该配置类中模拟开发、测试、生产环境的数据源,如下所示。

package com.jian.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

@Configuration
public class MainConfigOfProfile {
    @Bean("testDataSource")
    public DataSource dataSourceTest() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }
    @Bean("devDataSource")
    public DataSource dataSourceDev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis_plus_copy?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }
    @Bean("prodDataSource")
    public DataSource dataSourceProd() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssmbuild?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }
}

该配置类这样写,是一点儿问题都没有的,但你有没有想过这一点,在真实项目开发中,那些数据库连接的相关信息,例如用户名、密码以及MySQL数据库驱动类的全名,这些都是要抽取在一个配置文件中的。你想一想,是不是这么一回事啊!

因此,我们需要在项目的src/main/resources目录下新建一个配置文件,例如dbconfig.properties,在其中写上数据库连接的相关信息,如下所示。

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

那么如何在MainConfigOfProfile配置类中获取以上配置文件中的值呢?这就需要我们大显神通了,看过我前面Spring注解驱动开发系列教程的童鞋们,相信这对你们并不难,而且正好可以复习一下前面所学习的知识点。

不过,我在这儿还是得说一点,该MainConfigOfProfile配置类实现了一个EmbeddedValueResolverAware接口,我们通过该接口能够获取到String值解析器。也就是说,IOC容器启动时会自动地将String值的解析器(即StringValueResolver)传递过来给我们用,咱们可以用它来解析一些字符串。

package com.jian.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
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.PropertySource;
import org.springframework.util.StringValueResolver;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

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

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

    private StringValueResolver valueResolver;

    private String driverClass;


    @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/mybatis_plus?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @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/mybatis_plus_copy?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @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/ssmbuild?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

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

其实,这个配置类相对来说还算是比较简单的,其中使用@Bean(“devDataSource”)注解标注的是开发环境使用的数据源;使用@Bean(“testDataSource”)注解标注的是测试环境使用的数据源;使用@Bean(“prodDataSource”)注解标注的是生产环境使用的数据源。

接着,我们创建一个单元测试类,例如ProfileTest,并在该类中新建一个test01()方法来进行测试,如下所示

package com.jian.test;

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

import javax.sql.DataSource;

public class ProfileTest {
    @Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
        String[] names = applicationContext.getBeanNamesForType(DataSource.class);
        for (String name : names) {
            System.out.println(name);
        }
        applicationContext.close();
    }
}

最后,运行以上test01()方法,输出的结果信息如下所示

在这里插入图片描述

可以看到三种不同的数据源成功注册到了IOC容器中,说明我们的环境搭建成功了

根据环境注册Bean

我们成功搭建环境之后,接下来,就是要实现根据不同的环境来向IOC容器中注册相应的bean了。也就是说,我们要实现在开发环境注册开发环境下使用的数据源;在测试环境注册测试环境下使用的数据源;在生产环境注册生产环境下使用的数据源。此时,@Profile注解就显示出其强大的特性了。

我们在MainConfigOfProfile配置类中为每个数据源添加@Profile注解标识,如下所示。

package com.jian.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.beans.PropertyVetoException;

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

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

    private StringValueResolver valueResolver;

    private String driverClass;


    @Profile("test")  // 定义了一个环境标识,只有当test环境被激活以后,我们这个bean才能被注册进来
    @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/mybatis_plus?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Profile("dev")  // 定义了一个环境标识,只有当dev环境被激活以后,我们这个bean才能被注册进来
    @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/mybatis_plus_copy?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Profile("prod")  // 定义了一个环境标识,只有当prod环境被激活以后,我们这个bean才能被注册进来
    @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/ssmbuild?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

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

可以看到,我们使用@Profile(“dev”)注解来标识在开发环境下注册devDataSource;使用@Profile(“test”)注解来标识在测试环境下注册testDataSource;使用@Profile(“prod”)注解来标识在生产环境下注册prodDataDource。

此时,我们运行IOCTest_Profile类中的test01()方法,发现IDEA控制台并未输出任何结果信息。 说明我们为不同的数据源添加@Profile注解后,默认是不会向IOC容器中注册bean的,需要我们根据环境显示指定向IOC容器中注册相应的bean。

换句话说,通过@Profile注解加了环境标识的bean,只有这个环境被激活的时候,相应的bean才会被注册到IOC容器中。

如果我们需要一个默认的环境,那么该怎么办呢?此时,我们可以通过@Profile(“default”)注解来标识一个默认的环境,例如,我们将devDataSource环境标识为默认环境,如下所示。

    @Profile("default")
    //@Profile("dev")  // 定义了一个环境标识,只有当dev环境被激活以后,我们这个bean才能被注册进来
    @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/mybatis_plus_copy?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

此时,我们运行ProfileTest类中的test01()方法,输出的结果信息如下所示

在这里插入图片描述

可以看到,我们在devDataSource数据源上使用@Profile(“default”)注解将其设置为默认的数据源,运行测试方法时Eclipse控制台会输出devDataSource。

接下来,我们将devDataSource数据源上的@Profile(“default”)注解还原成@Profile(“dev”)注解,重新标识它为一个开发环境下注册的数据源,好方便下面的测试。

那么,我们如何根据不同的环境来注册相应的bean呢?例如,我们想在程序运行的时候,将其切换到测试环境下。

第一种方式就是根据命令行参数来确定环境,我们在运行程序的时候可以添加相应的命令行参数。例如,如果我们现在的环境是测试环境,那么可以在运行程序的时候添加如下命令行参数。
在这里插入图片描述

此时,点击Run按钮运行IOCTest_Profile类中的test01()方法,输出的结果信息如下所示。

在这里插入图片描述

第二种方式就是通过写代码的方式来激活某种环境,其实主要是通过AnnotationConfigApplicationContext类的无参构造方法来实现,具体步骤如下:

  1. 在bean上加@Profile注解,其value属性值为环境标识,可以自定义
  2. 使用AnnotationConfigApplicationContext类的无参构造方法创建容器
  3. 设置容器环境,其值为第1步设置的环境标识
  4. 设置容器的配置类
  5. 刷新容器

温馨提示:2、4、5步其实是AnnotationConfigApplicationContext类中带参构造方法的步骤,以上这几个步骤相当于是把其带参构造方法拆开,在其中插入一条语句设置容器环境,这些我们可以在AnnotationConfigApplicationContext类的带参构造方法中看到,如下所示
在这里插入图片描述

好了,我们要开始正式编写代码来激活某种环境了。我们先在程序中调用AnnotationConfigApplicationContext类的无参构造方法来创建一个IOC容器,然后在容器进行初始化之前,为其设置相应的环境,接着再为容器设置主配置类,最后刷新一下容器。例如,我们将IOC容器设置为测试环境,如下所示

@Test
public void test01(){
    //1.使用无参构造器创建一个IOC容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    //2.在我们的容器还没启动创建其他的bean之前,先设置需要激活的环境(可以设置激活多个环境)
    applicationContext.getEnvironment().setActiveProfiles("test","dev");
    //3.注册主配置类
    applicationContext.register(MainConfigOfProfile.class);
    //4.启动刷新容器
    applicationContext.refresh();
    String[] names = applicationContext.getBeanNamesForType(DataSource.class);
    for (String name : names) {
        System.out.println(name);
    }
    //关闭容器
    applicationContext.close();
}

在这里插入图片描述

如果此时测试环境里面还有一些其他的组件,比如Yellow

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

那么在测试环境被激活的情况下,测试环境下的所有bean都会被注册到IOC容器中。

@Test
public void test01(){
    //1.使用无参构造器创建一个IOC容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    //2.在我们的容器还没启动创建其他的bean之前,先设置需要激活的环境(可以设置激活多个环境)
    applicationContext.getEnvironment().setActiveProfiles("test","dev");
    //3.注册主配置类
    applicationContext.register(MainConfigOfProfile.class);
    //4.启动刷新容器
    applicationContext.refresh();
    String[] names = applicationContext.getBeanNamesForType(DataSource.class);
    for (String name : names) {
        System.out.println(name);
    }
    Yellow bean = applicationContext.getBean(Yellow.class);
    System.out.println(bean);
    //关闭容器
    applicationContext.close();
}

在这里插入图片描述

@Profile注解不仅可以标注在方法上,也可以标注在配置类上。如果标注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所有配置才会生效。例如,我们在MainConfigOfProfile配置类上标注上@Profile("dev")注解,如下所示

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

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

然后,我们来运行ProfileTest类中的test02()方法,在运行该方法之前,记得要把获取Yellow组件并打印的两行代码给注释掉,要不然运行test02()方法之后,IDEA控制台就会报错

这是因为我们在test02()方法中指定了当前的环境为测试环境,而MainConfigOfProfile配置类上标注的注解为@Profile(“dev”),说明该配置类中的所有配置只有在开发环境下才会生效。所以,此时没有任何数据源注册到IOC容器中,自然IDEA控制台中就不会输出任何信息了。
如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中

首先,我们要将MainConfigOfProfile配置类上标注的@Profile("dev")注解给注释掉,好方便接下来的测试。

然后,再将Yellow组件上的@Profile("test")注解给注释掉,此时,MainConfigOfProfile配置类的完整代码如下所示

package com.jian.config;

import com.jian.bean.Yellow;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.beans.PropertyVetoException;

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

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

    private StringValueResolver valueResolver;

    private String driverClass;

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

    @Profile("test")  // 定义了一个环境标识,只有当test环境被激活以后,我们这个bean才能被注册进来
    @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/mybatis_plus?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    //    @Profile("default")
    @Profile("dev")  // 定义了一个环境标识,只有当dev环境被激活以后,我们这个bean才能被注册进来
    @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/mybatis_plus_copy?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Profile("prod")  // 定义了一个环境标识,只有当prod环境被激活以后,我们这个bean才能被注册进来
    @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/ssmbuild?useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

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

接着,修改一下ProfileTest类中的test01()方法,即放开获取Yellow组件并打印的两行代码。

@Test
public void test01(){
    //1.使用无参构造器创建一个IOC容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    //2.在我们的容器还没启动创建其他的bean之前,先设置需要激活的环境(可以设置激活多个环境)
    applicationContext.getEnvironment().setActiveProfiles("test");
    //3.注册主配置类
    applicationContext.register(MainConfigOfProfile.class);
    //4.启动刷新容器
    applicationContext.refresh();
    String[] names = applicationContext.getBeanNamesForType(DataSource.class);
    for (String name : names) {
        System.out.println(name);
    }
    Yellow bean = applicationContext.getBean(Yellow.class);
    System.out.println(bean);
    //关闭容器
    applicationContext.close();
}

可以看到,当前的环境指定为了开发环境,那么此时Yellow这个组件会被注册到IOC容器中吗

在这里插入图片描述

从以上输出结果中可以看到,Yellow组件上并没有使用@Profile注解进行标注,但是它在开发环境下被注册到IOC容器中了。

如果此时将当前的环境指定为生产环境,那么Yellow这个组件还会被注册到IOC容器中吗

@Test
public void test01(){
    //1.使用无参构造器创建一个IOC容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    //2.在我们的容器还没启动创建其他的bean之前,先设置需要激活的环境(可以设置激活多个环境)
    applicationContext.getEnvironment().setActiveProfiles("dev");
    //3.注册主配置类
    applicationContext.register(MainConfigOfProfile.class);
    //4.启动刷新容器
    applicationContext.refresh();
    String[] names = applicationContext.getBeanNamesForType(DataSource.class);
    for (String name : names) {
        System.out.println(name);
    }
    Yellow bean = applicationContext.getBean(Yellow.class);
    System.out.println(bean);
    //关闭容器
    applicationContext.close();
}

在这里插入图片描述

这进一步说明了,虽然Yellow组件上并没有使用@Profile注解进行标注,但是它也在生产环境下被注册到IOC容器中了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值