【Spring注解驱动】(一)IOC容器

前言

课程跟的是尚硅谷雷丰阳老师的《Spring注解驱动教程》,主要用于SSM框架向SpringBoot过渡,暑假有点懒散,争取这周看完。

1 容器

Spring的底层核心功能是IOC控制翻转和DI依赖注入,Spring认为所有的组件都应该放在IOC容器中,组件之间的关系通过容器进行自动装配,也就是依赖注入。

1.1 组件注册与获取

引入Spring依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
    </dependencies>

导入context依赖后,就会自动导入spring需要的其他jar包:

1.1.1 通过xml的方式注册、获取容器组件
①创建spring配置文件 bean.xml,将bean注入容器

bean.xml

    <bean id="person" class="com.hikaru.entity.Person">
        <property name="name" value="zhangsna"/>
        <property name="id" value="1"/>
    </bean>
②ClassPathXmlApplicationContext获取配置文件的IOC容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);
1.1.2 通过注解的方式注册、获取容器组件
①@Configuration @Bean 新建配置类取代配置文件并注册组件

@Configuration用于告知Spring这是一个配置类,@Bean将方法名作为Bean的id并将返回值注册到IOC容器,Bean的id也可以通过@Bean的value值进行指定

@Configuration
public class PersonConfig {
    @Bean
    public Person person() {
        return new Person("lisi", 2);
    }
}
②AnnotationConfigApplicationContext获取配置类的IOC容器
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
        Person person = (Person) context.getBean("person");
        System.out.println(person);
1.1.3 在容器中注册并过滤组件的方式:

① 包扫描+组件标注注解(Controller、Service、Respository、Component)

② @Bean 导入第三方包的组件

③ @Import 快速给容器导入一个组件

④ 使用@Import的ImportSelector过滤组件

⑤ 使用工厂Bean FactoryBean注册组件(本质还是@Bean)

⑥ 通过ImportBeanDefinitionRegistrar接口自动定义注册组件

几种方式的差异点:

① 包扫描+组件标注注解的方式注册的Bean的名称为首字母小写的类名

② @Bean注解的方式注册的Bean的名称为指定的value值,或者默认为类名(大小写不变)

③ @Import注解的方式注册的Bean的名称为全类名,如:com.hikaru.entity.Person

⑥ 通过ImportBeanDefinitionRegistrar接口自动定义注册组件,本质是@Import,但是注册的Bean名由接口方法指定

而且很神奇的一点,如果只是大小写不同的Bean是不能同时放入IOC容器的

① @ComponentScan 自动扫描组件 指定扫描规则

第一步:开启组件扫描

原始的xml开启组件自动扫描

<context:component-scan base-package="com.hikaru"/>

@ComponentScan 在配置文件开启注解扫描

@Configuration
@ComponentScan(value = "com.hikaru")
public class PersonConfig {
    @Bean
    public Person person() {
        return new Person("lisi", 2);
    }
}

测试

    @Test
    public void testIOC() {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
        // getBeanDefinitionNames获取容器中所有组件的name
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for(String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }
personConfig
personController
personDao
person
personService

第二步:@ComponentScan 指定扫描规则

ComponentScan注解包含两种过滤:

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

其中的Filter为Filter注解,包括过滤类型及值,过滤类型可以为注解、类、或者value正则表达式,默认为注解类型

FilterType 过滤规则类型
ANNOTATION 按照注解
ASSIGNABLE_TYPE 按照给定的类型
ASPECTJ 按照ASPECTJ表达式
REGEX 按照正则表达式
CUSTOM 使用自定义规则

①使用FilterType.ANNOTATION过滤类型

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
@Configuration
@ComponentScan(value = "com.hikaru",
    excludeFilters = {
        // 默认type为Annotation
        @ComponentScan.Filter(classes = {Controller.class, Service.class})
    }
)
public class PersonConfig {
    @Bean
    public Person person() {
        return new Person("lisi", 2);
    }
}

测试结果IOC容器不会扫描到Service和Controller注解的组件了:

personConfig
personDao
person

includeFilters 只扫描指定的组件

@ComponentScan(value = "com.hikaru",
    includeFilters = {
        // 默认type为Annotation
        @ComponentScan.Filter(classes = {Controller.class, Service.class})
    },
        useDefaultFilters = false
)

ComponentScan注解还有一个useDefaultFilters会默认使用默认的过滤规则,想让includeFilters生效需要设置为false

测试

personConfig
personController
personService
person

其中person以及personConfig均是配置类中的组件,不会被过滤规则影响

②使用FilterType.ASSIGNABLE_TYPE过滤类型

@Configuration
@ComponentScan(value = "com.hikaru",
    includeFilters = {
        // 默认type为Annotation
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {PersonService.class})
    },
        useDefaultFilters = false
)

③使用FilterType.ASSIGNABLE_TYPE

@Configuration
@ComponentScan(value = "com.hikaru",
    includeFilters = {
        // 默认type为Annotation
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {PersonService.class})
    },
        useDefaultFilters = false
)

测试

personConfig
personService
person
② @Bean 导入第三方包的组件并使用@Conditional 按照条件注册Bean

@Conditional 接口的value为一个Condition的数组,因此想要使用该注解,需要自定义一个实现Condition接口的类并实现接口方法matches。

@Conditional 接口

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

@Condition接口

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

现在IOC容器中有两个Person类 bill 和 linus,需求是当操作系统为windows的时候将linus注册到IOC容器,为Linux的时候则将bill注册到IOC容器(笑

    @Bean("bill")
    public Person person1() {
        return new Person("bill", 3);
    }

    @Bean("linus")
    public Person person2() {
        return new Person("linus", 3);
    }

根据IOC容器环境判断操作系统:

    @Test
    public void testIOC() {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        System.out.println(property);
    }

自定义 WindowsCondition 和 LinuxCondition,实现接口Condition以及接口方法matches,判断当前的操作系统

其中的两个参数

conditionContext:判断条件能使用的上下文环境

annotatedTypeMetadata:注解信息

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Windows"))
            return true;
        return false;
    }
}
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Linux"))
            return true;
        return false;
    }
}

测试

personConfig
person
linus

☆除此之外Condition实现类还能做的事情

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 1 获取IOC的BeanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 2 获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 3 获取当前环境信息
        // 其中封装了环境变量、虚拟机的变量等
        Environment environment = conditionContext.getEnvironment();
        // 4 获取Bean定义的注册类,可以用来在这里向IOC注册Bean
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        return false;
    }
③ @Import 给容器快速导入一个组件
@Configuration
@ComponentScan(value = "com.hikaru",
    includeFilters = {
        // 默认type为Annotation
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {Person.class})
    },
        useDefaultFilters = false
)

@Import(Color.class)
public class PersonConfig

测试

personConfig
person
com.hikaru.entity.Color
④ @Import 使用 ImportSelector过滤注册组件

ImportSelector是一个接口

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);

    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

自定义MySelector类实现ImportSelector

public class MySelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.hikaru.entity.Color"};
    }

    @Override
    public Predicate<String> getExclusionFilter() {
        return null;
    }
}

annotationMetadata包含注解的信息

测试

personConfig
person
com.hikaru.entity.Color
⑤ 使用工厂Bean FactoryBean注册Bean

①自定义FactoryBean的实现类

public class ColorFactoryBean implements FactoryBean<Color> {

    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

实现的三个方法:

getObject:定义返回的Bean

getObjectType:定义返回的Bean的类型

isSingleton:规定返回的Bean是不是单例

②在配置文件中注册

@Configuration

public class ColorConfig {
    @Bean
    public ColorFactoryBean colorFactoryBean() {
        return new ColorFactoryBean();
   }
}

使用FactoryBean,注册到IOC容器的Bean并不是返回的FactoryBean,而是FactoryBean中getObject()返回的Bean,如果想要获取FactoryBean本身,需要在前缀加上‘&’

测试

    @Test
    public void testIOC() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ColorConfig.class);
        Object colorFactoryBean = context.getBean("colorFactoryBean");
        System.out.println(colorFactoryBean);
    }

测试结果为Color即ColorFactoryBean的getObject()返回Bean而不是ColorFactoryBean本身

    @Test
    public void testIOC() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ColorConfig.class);
        Object colorFactoryBean = context.getBean("&colorFactoryBean");
        System.out.println(colorFactoryBean);
    }

使用'&'作为前缀后,测试结果为 \com.hikaru.entity.ColorFactoryBean@6b26e945

⑥ 通过ImportBeanDefinitionRegistrar接口自动定义注册组件

通过一个自定义的类实现接口及其方法。

public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if(registry.containsBeanDefinition("com.hikaru.entity.Color")) {
            // 指定Bean的信息,如Bean类型、作用域等等
            BeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            // 指定Bean名并注册到IOC容器
            registry.registerBeanDefinition("rainBow", beanDefinition);
        }
    }
}

其中的两个参数:

AnnotationMetadata:当前类的注解信息

BeanDefinitionRegistry:BeanDefinitionRegistry注册类,把所有需要添加到IOC容器的Bean,调用BeanDefinitionRegistry.registerBeanDefinition()注册到容器中。

BeanDefinition为存储Bean信息的接口,RootBeanDefinition是它的接口实现类,Idea中使用ctrl + alt + B可以快速查看一个接口的实现类

然后在配置类的Import注解中添加自定义注册Bean

@Import(value = {MySelector.class, MyImportBeanDefinitionRegister.class})

1.2 @Scope设置组件的作用域

@Scope.value
prototype 多例
singleton 单例(默认)
request 同一个请求创建一个实例
session 同一个session回话创建一个实例
1.2.1 IOC的单例与多例

单例模式下的bean会在IOC容器启动的时候就调用方法创建对象放到IOC容器中,以后每次获取就是从容器中拿(map.get()),因此每次获取的是同一个Bean

多例模式则是IOC容器不会在启动的时候调用方法创建对象,而是在每次获取的时候调用方法创建对象放到IOC容器,每次获取的是不同的Bean

①singleton(默认情况)
    @Test
    public void testIOC() {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
        // getBeanDefinitionNames获取容器中所有组件的name
        Person person1 = (Person) context.getBean("person");
        Person person2 = (Person) context.getBean("person");
        System.out.println(person1 == person2);
    }

输出结果: true

②prototype
    @Scope(value = "prototype")
    @Bean
    public Person person() {
        return new Person("lisi", 2);
    }

输出结果:false

1.3 @Lazy Bean的懒加载

单例模式下的Bean默认在容器启动的时候创建对象,@Lazy注解可以让其不在容器创建时创建对象而在第一次被调用的时候才去创建对象。

    @Scope(value = "singleton")
    @Lazy
    @Bean
    public Person person() {
        System.out.println("Person Bean被创建了");
        return new Person("lisi", 2);
    }

测试

IOC容器启动完成
Person Bean被创建了

1.5 Bean的初始化和销毁方法

1.5.1 @Bean指定初始化和销毁方法

Bean的生命周期:Bean创建-》Bean初始化-》Bean销毁;可以在@Bean注解中指定返回Bean的初始化和销毁方法:

①在Bean中定义初始化和返回方法

public class Blue {
    public Blue() {
        System.out.println("Blue Bean constructor...");
    }
    public void init() {
        System.out.println("Blue Bean initing...");
    }
    public void destroy() {
        System.out.println("Blue Bean destroying...");
    }
}

②在@Bean中指定初始化和销毁函数

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Blue blue() {
        return new Blue();
    }

③测试

    @Test
    public void testIOC() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ColorConfig.class);
        System.out.println("IOC establish finished.");
        context.close();
    }

④测试结果1(默认的单例情况)

Blue Bean constructor...
Blue Bean initing...
IOC establish finished.
Blue Bean destroying...

⑤测试结果2 (多例情况)

IOC establish finished.
Blue Bean constructor...
Blue Bean initing...

这里在Bean被使用的时候才进行创建初始化

但是为什么容器关闭之后没有销毁Bean不得而知?

⑥测试结果3 (使用懒加载注解的单例模式)

IOC establish finished.
Blue Bean constructor...
Blue Bean initing...
Blue Bean destroying...

使用懒加载注解的单例模式在容器关闭的时候是正常销毁Bean的,后续找书看看吧,不是太明白这个地方

1.5.2 通过实现接口InitializingBean和DisposableBean实现初始化和销毁Bean

InitializingBean接口的方法afterPropertiesSet()在类属性设置完后进行调用

DisposableBean接口的方法destroy()在类销毁时进行调用

1.5.3 @PostConstruct和@Predestroy 实现初始化和销毁Bean

@PostConstruct标注的方法在Bean创建完成并且属性值赋值完成进行初始化

@Predestroy标注的方法在Bean移除之前进行通知

1.5.4 BeanPostProcessor接口,bean的后置处理器实现初始化IOC容器中每个Bean方法设置

接口方法有两个:

postProcessBeforeInitialization:在初始化方法之前

postProcessAfterInitialization:在初始化方法之后

①自定义BeanPostProcessor接口的实现类

@Component
public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean);
        return bean;
    }
}

其中两个接口方法的参数bean即为初始化的bean,方法的返回值可以直接返回bean也可以进行加工封装再返回。

②在IOC容器中注册自定义的后置处理器

@Configuration
@ComponentScan(basePackageClasses = MyBeanProcessor.class)
public class ColorConfig {
    @Bean(initMethod = "init", destroyMethod = "destroy")
    @Scope("singleton")
    @Lazy
    public Blue blue() {
        return new Blue();
    }
}

③测试结果

postProcessBeforeInitialization...colorConfig=>com.hikaru.config.ColorConfig$$EnhancerBySpringCGLIB$$5ce9eaa@2e4b8173
postProcessAfterInitialization...colorConfig=>com.hikaru.config.ColorConfig$$EnhancerBySpringCGLIB$$5ce9eaa@2e4b8173
postProcessBeforeInitialization...personConfig=>com.hikaru.config.PersonConfig$$EnhancerBySpringCGLIB$$5de6d646@70e8f8e
postProcessAfterInitialization...personConfig=>com.hikaru.config.PersonConfig$$EnhancerBySpringCGLIB$$5de6d646@70e8f8e
postProcessBeforeInitialization...person=>Person{name='lisi', id=2}
postProcessAfterInitialization...person=>Person{name='lisi', id=2}
postProcessBeforeInitialization...com.hikaru.entity.Color=>com.hikaru.entity.Color@5c30a9b0
postProcessAfterInitialization...com.hikaru.entity.Color=>com.hikaru.entity.Color@5c30a9b0
postProcessBeforeInitialization...rainBow=>com.hikaru.entity.RainBow@1ddf84b8
postProcessAfterInitialization...rainBow=>com.hikaru.entity.RainBow@1ddf84b8
IOC establish finished.
Blue Bean constructor...
postProcessBeforeInitialization...blue=>com.hikaru.entity.Blue@7334aada
Blue Bean initing...
postProcessAfterInitialization...blue=>com.hikaru.entity.Blue@7334aada
Blue Bean destroying...

Process finished with exit code 0
BeanPostProcessor的原理

1.6 Bean的属性赋值

1.6.1 @Value赋值

使用@Value对Bean进行赋值,可以选择的值:

① 基本的数值

② SpringEL表达式:#{}

③ ${} 获取配置文件中的值(实际上是读取到运行环境的值)

@Component
public class Person {
    @Value("张三")
    private String name;
    @Value("#{1}")
    private Integer id;
1.6.2 @value + @PropertySource读取配置文件的值

①首先创建配置文件person.properties

person.name=李四
person.id=1

②通过@value + @PropertySource读取配置文件的值

@Component
@PropertySource(value = "classpath:/person.properties",encoding = "utf-8")
public class Person {
    @Value("${person.name}")
    private String name;
    @Value("${person.id}")
    private Integer id;

/@PropertySource会将外部资源读的k/v取到运行环境中,加载完外部的配置文件之后使用 ${} 获取运行环境中的值,或者通过environment.getProperty("person.name");

③测试

    @Test
    public void testIOC() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfPropertyValue.class);

        Person person = (Person) context.getBean("com.hikaru.entity.Person");
        System.out.println(person);

        ConfigurableEnvironment environment = context.getEnvironment();
        String property = environment.getProperty("person.name");
        System.out.println(property);
    }

测试结果:

Person{name='李四', id=1}
李四

1.7 @Autowired自动装配

首先创建BookConfig、BookService、BookServiceImpl、BookDao用于测试:

BookService

@Service
public interface BookService {
    public void print();
}

BookServiceImpl

@Service
public class BookServiceImpl implements BookService{
    @Autowired
    BookDao bookDao;

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

BookDao

@Repository
public class BookDao {
    private Integer id = 1;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

BookConfig

@Configuration
@ComponentScan(basePackageClasses = {BookService.class, BookDao.class})
public class BookConfig {
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setId(2);
        return bookDao;
    }
}

测试,输出结果为1

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {BookConfig.class})
public class IOCTest_Autowired {

    @Autowired
    BookService bookService;
    @Test
    public void test01() {
        bookService.print();
    }
}

@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境

@ContextConfiguratio() 用于引入配置类或者xml配置文件

如此一来,不使用AnnotationConfigApplicationContext的方式也能够使用IOC容器了

修改BookServiceImpl的bookDao为bookDao2

BookServiceImpl

@Service
public class BookServiceImpl implements BookService{
    @Autowired
    BookDao bookDao2;

    @Override
    public void print() {
        System.out.println(bookDao2.getId());
    }
}

测试结果:2

出现这种情况,是由于@Autowired自动装备会优先去容器中找相同类型的组件;如果找到多个相同类型的组件,再按照注解的属性名也就是这里的bookDao2去查找对应Bean名的Bean,如果没有则报错

1.7.1 使用@Autowired属性required设置是否必须装配

默认值为true,即IOC容器中没有对应组件则会报错

1.7.2 使用@Primary设置多组件时优先装配的组件
1.7.3 @Autowired实现方法、构造器的自动装配

将@Autowired注解标注在方法和构造器上即可实现自动装配方法、构造器的参数。

☆@Autowired在只有一个有参构造器时可以省略

如将BookServiceImpl修改为:

@Service
public class BookServiceImpl implements BookService{
    BookDao bookDao;

    public BookServiceImpl(BookDao bookDao2) {
        this.bookDao = bookDao2;
    }

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

测试结果为2,与@Autowired相同

☆@Bean注解标注的方法的参数也会自动从容器中获取

1.8 使用@Qulifier在自动装配出现多个组件时指定组件名

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

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

测试,输出结果为2

这里容器中BookDao类型的组件有bookDao和bookDao2两个,经过Qualifier指定后故使用bookDao2

1.9 Spring支持的Java规范@Resource、@Inject

使用@Resource(name=""),能够实现和@Autowired相同的功能,但是是默认按照名称装配的,并且不能支持@Primary注解和reqiured属性。

@Inject需要导入java.inject的包,和Autowired功能相同,支持@Primary注解但是没有reqiured属性。

1.10 ☆在Bean中注入Spring底层组件以及原理

场景:自定义组件想要使用Spring容器底层的一些组件,如Applicationcontext,BeanFactory,xxx

方法:自定义组件通过实现xxxAware接口,在创建对象的时候,就能从接口规定的方法注入相关组件

查看接口实现类的快捷键是Ctrl+T

①ApplicationContextAware接口

实现ApplicationContextAware接口的自定义组件可以通过接口方法setApplicationContext() 获取Spring的底层组件ApplicationContext

②BeanNameAware接口

实现BeanNameAware接口的自定义组件可以通过接口方法setBeanName() 获取自定义组件在容器中的Bean的名称

③setEmbeddedValueResolver接口

实现setEmbeddedValueResolver接口的自定义组件可以通过接口方法setEmbeddedValueResolver()解析字符串中的展位符,如'${}'代表IOC容器的环境变量(如之前从properties文件获取到的变量),'#{}'代表SpringEL表达式

测试

Red.java

@Component
@PropertySource(value = "classpath:/person.properties",encoding = "utf-8")
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    @Value("${person.name}")
    private String name;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("Bean设置的name为:" + s);
    }


    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        String s = stringValueResolver.resolveStringValue("你好${person.name} 我是#{20*18}");
        System.out.println(s);
    }

    @Override
    public String toString() {
        return "Red{" +
                "applicationContext=" + applicationContext +
                ", name='" + name + '\'' +
                '}';
    }
}

这里出现了一个bug找了半天,IOC容器中没有properties文件中的k/v值,原因是ColorConfig中使用了@Bean的方式注册了Bean,这样就不会扫描到组件中的@PropertySource,改用@import或者组件扫描的方式即可

ColorCoonfig

@Configuration
@Import(Red.class)
public class ColorConfig {

}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ColorConfig.class)
public class IOCTest {
    @Autowired
    Red red;

    @Test
    public void testIOC() {
        System.out.println(red);
    }
}

测试结果

Bean设置的name为:com.hikaru.entity.Red
你好李四 我是360
Red{applicationContext=org.springframework.context.support.GenericApplicationContext@2286778, started on Mon Jul 11 09:22:16 CST 2022, name='李四'}

1.11 @Profile环境搭建

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

使用场景举例:开发环境、测试环境、生产环境的程序员想要在不改变源代码的情况下使用不同数据库或者实现数据源的切换

①创建MainConfigOfFile

@Configuration
@PropertySource("classpath:/jdbc.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
    @Value("${db.username}")
    private String user;
    @Value("${db.password}")
    private String password;
    @Value("${db.url}")
    private String url;
    private String driveClass;

    @Bean
    public DataSource dataSourceDev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);
        return dataSource;
    }

    @Bean
    public DataSource dataSourceTest() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);

        return dataSource;
    }

    @Bean
    public DataSource dataSourceProd() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);

        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        this.driveClass = stringValueResolver.resolveStringValue("${db.driverClassName}");
    }
}

ComboPooledDataSource为C3P0的DataSource实现类,常用的数据库连接池还有Deruid,实现类为DruidDataSourceFactory

@Properties 读取数据库连接文件到IOC容器作为环境变量后,这里使用了@Value和实现EmbeddedValueResolverAware接口的方式读取环境变量。需要注意的是配置文件尽量使用.的二级命名方式,防止如${username}和系统环境变量名重名导致出错的情况。

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MainConfigOfProfile.class)
public class IOCTest {
    @Autowired
    DataSource dataSourceDev;

    @Test
    public void testIOC() throws SQLException {
        Connection connection = dataSourceDev.getConnection();
        System.out.println(connection);
    }
}

输出

com.mchange.v2.c3p0.impl.NewProxyConnection@a2431d0 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@1cbb87f3]

②在数据源Bean上加上@Profile

    @Profile("Dev")
    @Bean
    public DataSource dataSourceDev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);
        return dataSource;
    }

    @Profile("Test")
    @Bean
    public DataSource dataSourceTest() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);

        return dataSource;
    }

    @Profile("Prod")
    @Bean
    public DataSource dataSourceProd() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);

        return dataSource;
    }

测试

    @Test
    public void testIOC() throws SQLException {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
        String[] beanDefinitionNames = context.getBeanDefinitionNames();

        for(String name : beanDefinitionNames) {
            System.out.println(name);
        }

    }

测试结果

mainConfigOfProfile

发现一个数据源都没有

☆方式一:使用命令行固定参数指定运行环境
-Dspring.profiles.active=Test

Idea设置如下图:

方式二:使用@Profile的方式

首先在配置类中的Bean上添加@Profile注解

    @Profile("Dev")
    @Bean
    public DataSource dataSourceDev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);
        return dataSource;
    }

    @Profile("Test")
    @Bean
    public DataSource dataSourceTest() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);

        return dataSource;
    }

    @Profile("Prod")
    @Bean
    public DataSource dataSourceProd() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driveClass);
        dataSource.setJdbcUrl(url);

        return dataSource;
    }
    @Test
    public void testIOC() throws SQLException {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext();

        // 设置运行的环境
        context.getEnvironment().setActiveProfiles("Dev");
        // 在容器中注册配置类
        context.register(MainConfigOfProfile.class);
        // 刷新启动容器
        context.refresh();

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

    }

使用AnnotationConfigApplicationContext()的无参构造器,这是因为有参构造器会自动进行注册配置类和刷新,如此一来就不能进行设置运行的环境了

有参构造器会自动进行注册配置类和刷新

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

测试结果

mainConfigOfProfile
dataSourceDev
posted @ 2022-07-11 22:31  Tod4  阅读(96)  评论(0编辑  收藏  举报