【SpringBoot_ANNOTATIONS】 总集篇

组件注册 01 @Configuration @Bean

Spring重要特性:IOC/DI (控制反转 / 依赖注入)

传统方式

使用beans.xml

  1. 创建bean类
package com.example.annotations.bean;

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

  1. idea中创建beans.xml
    new – xml configuration file – Spring config
    在这里插入图片描述
<bean id="person" class="com.example.annotations.bean.Person">
    <property name="name" value="lxl"/>
    <property name="age" value="21"/>
</bean>
  1. 测试
    @Test
    void beanTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }

注解方式 @Configuration @Bean

  1. 编写MainConfig类:配置类等同于beans.xml

    package com.example.annotations.config;
    
    import com.example.annotations.bean.Person;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    // @Configuration : 告诉Spring这是个配置类
    @Configuration
    public class MainConfig {
    
        //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
        @Bean("person")
        public Person getPerson(){
            return new Person("qd",21);
        }
    }
    
  2. 测试

        @Test
        void beanConfigTest(){
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
            Person person = applicationContext.getBean(Person.class);
    
            //获取对应类型组件在IOC容器中的名字 返回一个数组
            String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
    //        System.out.println(Arrays.toString(beanNamesForType));
    
            for (String str:beanNamesForType){
                System.out.println("toString:" + str.toString());
            }
        }
    

Notes

- @Configuration:告诉Spring这是个配置类
- @Bean:给容器注入一个bean
	- 标注在类上:类型为bean类的类型,id默认为类的小写
	- 标注在方法上:类型为返回值的类型,id默认使用方法的id

组件注册 02 @ComponentScan 自动扫描组件 & 指定扫描规则

资源准备

package com.example.annotations.controller;

import org.springframework.stereotype.Controller;

@Controller
public class BookController {
}
package com.example.annotations.service;

import org.springframework.stereotype.Service;

@Service
public class BookService {
}

传统方式

beans.xml中:

<context:component-scan base-package="com.example.annotations.controller"></context:component-scan>
    <bean id="person" class="com.example.annotations.bean.Person">
        <property name="name" value="lxl"/>
        <property name="age" value="21"/>
    </bean>

此后 只要在该包下标注了 @Controller @Service @Repository @Component 都会被加载进IOC容器
测试:

    @Test
    void packageScanTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String str:beanDefinitionNames){
            System.out.println(str);
        }

    }

在这里插入图片描述

有person是因为在beans.xml手动注入的

注解方式 @ComponentScan()

使用的时候注释掉 beans.xml中的 包扫描属性

<!-- 这行 注释掉 -->
<!--    <context:component-scan base-package="com.example.annotations.controller"></context:component-scan>-->    


    <bean id="person" class="com.example.annotations.bean.Person">
        <property name="name" value="lxl"/>
        <property name="age" value="21"/>
    </bean>

直接扫描

  1. 在MainConfig上加上注解 @Configuration(value = “com.example.annotations.service”)

    // @Configuration : 告诉Spring这是个配置类
    @Configuration
    @ComponentScan(value = "com.example.annotations")  // 包扫描注解:只要在该包下标注了 @Controller @Service @Repository @Component 都会被加载进IOC容器
    public class MainConfig {
    
        //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
        @Bean("person")
        public Person getPerson(){
            return new Person("qd",21);
        }
    }
    
  2. 测试

        @Test
        void packageScanTest(){
            //xml
    //        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
            //配置类测试
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
            String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
            for (String str:beanDefinitionNames){
                System.out.println(str);
            }
    
        }
    
  3. 结果:
    注入进容器的id默认是类名的小写(不严格是小写,实际为驼峰命名)
    不玩钱不完全截图

    为什么会有mainConfig?MainConfig.class上标注了@Configuration注解

    @Configuration 源码

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component   // 有这个注解,会被加载进容器
    public @interface Configuration {
        @AliasFor(
            annotation = Component.class
        )
        String value() default "";
    
        boolean proxyBeanMethods() default true;
    }
    

过滤规则

@includeFilters
// @Configuration : 告诉Spring这是个配置类
@Configuration
@ComponentScan(value = "com.example.annotations",
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = { Service.class,Controller.class})},
        useDefaultFilters = false
)
public class MainConfig {
    //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
    @Bean("person")
    public Person getPerson(){
        return new Person("qd",21);
    }
}

测试:
在这里插入图片描述

@excludeFilters

???

// @Configuration : 告诉Spring这是个配置类
@Configuration
@ComponentScan(value = "com.example.annotations",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class})},
        useDefaultFilters = false
)
public class MainConfig {
    //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
    @Bean("person")
    public Person getPerson(){
        return new Person("qd",21);
    }
}

测试:可看到过滤掉了controller和Service
在这里插入图片描述

组件注册 03 FilterType

FilterType.ANNOTATION 注解

常用

@ComponentScan(value = "com.example.annotations",
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = { Service.class,Controller.class})},
        useDefaultFilters = false
)

FilterType.ASSIGNABLE_TYPE 按照给定的类型

@ComponentScan(value = "com.example.annotations",
         includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes ={MyTypeFilter.class, BookController.class})},
        useDefaultFilters = false
)

在这里插入图片描述

FilterType.ASPECTJ 不常用 使用ASPECTJ表达式

FilterType.REGEX 使用正则表达式

FilterType.CUSTOM 自定义规则

注意:bean不能重名 不然会报错

@ComponentScan(value = "com.example.annotations",
         includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes ={MyTypeFilter.class})},
        useDefaultFilters = false
)

需要实现TypeFilter接口 并重写match方法

public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当秋安正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        // 获取正在扫描的类名
        String className = classMetadata.getClassName();
        System.out.println("className" + className);
        return className.contains("er")?true:false;//true表示加载进容器  false表示不加载进容器
    }
}

组件注册 04 @Scope 设置组件作用域

参数详解

常用

prototype:多实例
singleton:单实例

不常用

request:同一次请求创建一个实例
session:同一个session创建一个实例

示例

单例模式(默认情况)

配置类

@Configuration
public class MainConfig2 {
    // 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称
    // 不写scope的情况下 默认是singleton  相当于 @Scope("singleton")
    @Bean("person2") 
    public Person person(){
        return new Person("qd2",22);
    }
}

测试

    @Test
    void scopeTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        Object bean = applicationContext.getBean("person2");
        Object bean1 = applicationContext.getBean("person2");
        System.out.println("bean :"+bean.toString());
        System.out.println("bean1:"+bean1.toString());
        System.out.println(bean == bean1);
    }

在这里插入图片描述

多例模式

@Configuration
public class MainConfig2 {
    // 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称
    @Bean("person2")
    @Scope("prototype")
    public Person person(){
        return new Person("qd2",22);
    }
}

等同于

    <bean id="person" class="com.example.annotations.bean.Person" scope="prototype">
        <property name="name" value="lxl"/>
        <property name="age" value="21"/>
    </bean>

测试结果:
在这里插入图片描述

Notes

单实例

单例模式下(singleton)

  1. 在容器加载的时候加载bean
  2. IOC容器启动会调用方法创建对象放进IOC容器中
  3. 以后每次获取直接从容器(map.get())中获取

多实例

多例模式下(prototype)

  1. IOC容器启动并不会调用方法创建对象放在容器中
  2. 每次获取的时候才会调用方法创建对象

组件注册 05 @Lazy 懒加载

单实例bean:默认在容器启动的时候创建对象
多实例bean:容器启动的时候不创建对象 第一次使用(获取)Bean的时候创建对象 并初始化

测试代码

    @Test
    void scopeTest2(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        System.out.println("容器创建完毕");
        System.out.println("准备获取bean...");
        Object person = applicationContext.getBean("person");
        System.out.println("创建之后获取的 "+person.toString());
    }

单实例 测试

默认情况

@Configuration
public class MainConfig {
    //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
    @Bean("person")
    public Person getPerson(){
        System.out.println("准备创建person...");
        return new Person("qd",21);
    }
}

运行结果
在这里插入图片描述

加上懒加载 @Lazy

@Configuration
public class MainConfig {
    //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
    @Bean("person")
    @Lazy
    public Person getPerson(){
        System.out.println("准备创建person...");
        return new Person("qd",21);
    }
}

在这里插入图片描述

多实例 测试

@Configuration
public class MainConfig {
    //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
    @Bean("person")
    @Scope("prototype")
    public Person getPerson(){
        System.out.println("准备创建person...");
        return new Person("qd",21);
    }
}

在这里插入图片描述

组件注册 06 @Conditional 按照条件注册bean

SpringBoot底层大量使用的注解,表示按照一定的条件进行判断,若满足条件则给容器中注册bean
@Conditional可以标注在类上 也可以标注在方法上

    @Test
    void conditionTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("容器创建完毕");
        System.out.println("准备获取bean...");
        Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
        for (String person:beansOfType.keySet()){
            System.out.println(beansOfType.get(person));
        }
    }

MainConfig2.java

@Configuration
public class MainConfig2 {
    // 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称

//    @Scope("prototype")
    @Bean("person2")
    public Person person(){
        System.out.println("person2创建中...");
        return new Person("qd2",22);
    }

    @Bean("bill")
    public Person person1(){
        System.out.println("bill创建中...");
        return new Person("bill",62);
    }

    @Bean("linus")
    public Person person2(){
        System.out.println("linus创建中...");
        return new Person("linus",48);
    }
}

在这里插入图片描述

加上条件判断

LinuxCondition.java

package com.example.annotations.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 获取IOC使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        // 获取bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        // 获取当前环境信息
        Environment environment = context.getEnvironment();
        // 动态获取环境变量的值
        String property = environment.getProperty("os.name");

        if (property.contains("Linux")){
            System.out.println("the running environment is 'Linux'");
            return true;
        }
        return false;
    }
}

注意

  1. 实现Condition接口的时候注意导的包
  2. 需要实现的方法:matches 参数解释:
    1. ConditionContext:判断条件能使用的上下文环境
  3. AnnotationTypeMetadata:注释信息
  4. BeanDefinitionRegistry registry = context.getRegistry(); 所有bean定义都在这个类,也可以用其来注册/移除/查询/一个bean定义

WindowsCondition.class

package com.example.annotations.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 获取运行时环境
        Environment environment = context.getEnvironment();
        // 动态获取环境变量的值
        String property = environment.getProperty("os.name");
        // 匹配环境
        if (property.contains("Windows")){
            System.out.println("the running environment is 'Windows'");
            return true;
        }
        return false;
    }
}
@Configuration
public class MainConfig2 {
    // 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称
    @Bean("person2")
    public Person person(){
        System.out.println("person2创建中...");
        return new Person("qd2",22);
    }

    @Conditional(WindowsCondition.class)
    @Bean("bill")
    public Person person1(){
        System.out.println("bill创建中...");
        return new Person("bill",62);
    }

    @Conditional(LinuxCondition.class)
    @Bean("linus")
    public Person person2(){
        System.out.println("linus创建中...");
        return new Person("linus",48);
    }
}

运行结果;
在这里插入图片描述

组件注册 07 @Import 给容器快速导入一个组件

给容器注册组件:

  1. 包扫描 + 组件标注注解(@Controller @Service @Repository @Component),局限于自己写的
  2. @Bean(可导入第三方包里的组件)
  3. @Import(快速给容器中导入组件)
    1. @Import([要导入容器中的组件]):容器中就会自动注册这个组件,id默认为全类名 @Import(Color.class) @Import({Color.class,Red.class})
    2. 实现ImportSelector接口:返回需要导入的组件的全类名数组
    3. 实现ImportBeanDefinitionRegistrar接口:手动注册bean到容器中
  4. 使用Spring提供的FactoryBean工厂

配置类 MainConfig3.class

测试

    @Test
    void importTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
        System.out.println("容器创建完毕");
        System.out.println("准备获取bean...");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name:beanDefinitionNames){
            System.out.println(name);
        }
    }

ImportSelector 测试

Color.java

package com.example.annotations.bean;

public class Color {
}

Red.java

package com.example.annotations.bean;

public class Red {
}

MyImportSelector.java

package com.example.annotations.importselector;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
 * 自定义逻辑返回需要导入的组件
 */
public class MyImportSelector implements ImportSelector {

    // 返回值:导入到容器中的组件 (全类名)
    // AnnotationMetadata:当前标注@Import注解的类的所有注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 不能返回 null  可以是空数组
        return new String[]{"com.example.annotations.bean.Color","com.example.annotations.bean.Red"};
    }
}

运行结果:
在这里插入图片描述

实现ImportBeanDefinitionRegistrar接口

MyImportBeanDefinitionRegistrar.class

package com.example.annotations.importselector;

import com.example.annotations.bean.RainBow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param importingClassMetadata 当前类的注解信息
     * @param registry               BeanDefinition的注册类  负责将bean添加进容器
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 指定bean定义信息(Bean的类型 Bean的作用域 ...)
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);

        // 注册:指定bean名
        if (!registry.containsBeanDefinition("rainbow")){
            System.out.println(" start create 'rainbow' ...");

            registry.registerBeanDefinition("rainbow",rootBeanDefinition);
        }else {
            System.out.println("'rainbow' already exists ");
        }
    }
}

运行结果
在这里插入图片描述

使用FatoryBean

普通bean

将bean导入到容器后,容器会调用其无参构造器,默认创建一个对象注册在容器中

FactoryBean

  1. 是一个接口
  2. 整合第三方框架的时候用的多
  3. 默认获取的是工厂bean调用getObject创建的对象
  4. 要获取工厂bean本身,需要给id前加一个 &
需要实现方法

getObject()
getObjectType()

isSingleton() :默认是true 单实例

实例测试

ColorFactoryBean.java

package com.example.annotations.bean;

import org.springframework.beans.factory.FactoryBean;

/**
 * 创建一个Spring定义的FactoryBean
 */
public class ColorFactoryBean implements FactoryBean<Color> {


    /**
     * 返回分Color对象会被添加到容器中
     * @return Color
     * @throws Exception
     */
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

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

    /**
     * true: 单实例 在容器中保存一份
     * false:多实例 每获取一次就会创建一个新bean
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

MainConfig2.java

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

测试

    @Test
    void factoryBeanTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("容器创建完毕");
        System.out.println("准备获取bean...");
        Object bean1 = applicationContext.getBean("colorFactoryBean");//Color.class
        Object bean2 = applicationContext.getBean("colorFactoryBean");//Color.class
        System.out.println("bean1 == bean2? => " + (bean1 == bean2));
    }

测试结果
true 运行结果
现将ColorFactoryBean.java中的isSingleton改为false,再次运行
false 运行结果
查看获取的类型

    @Test
    void factoryBeanTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("容器创建完毕");
        System.out.println("准备获取bean...");
        Object bean1 = applicationContext.getBean("colorFactoryBean");//Color.class  获取的是工厂bean调用 
        Object bean2 = applicationContext.getBean("&colorFactoryBean");//ColorFactoryBean.class
        System.out.println("bean1 == bean2? => " + (bean1 == bean2));
        System.out.println("bean1 " + bean1.getClass());
        System.out.println("bean2 " + bean2.getClass());
    }

类型测试

生命周期 01 @Bean指定初始化和销毁方法

bean生命周期:bean创建 – 初始化 – 销毁
容器管理bean的生命周期
可自定义初始化和销毁方法:容器在bean进行到当前生命周期的时候来调用自定义的初始化和销毁方法

package com.example.annotations.bean;

public class Car {
    public Car() {
        System.out.println("car constructor ...");
    }

    /**
     * 做初始化方法
     * 初始化方法不可以有参数 但可以抛异常
     */
    public void init() {
        System.out.println("car init ...");
    }

    /**
     * 做销毁方法
     * 销毁方法不可以有参数 但可以抛异常
     */
    public void destroy() {
        System.out.println("car destroy ...");
    }


}

指定初始化和销毁方法(传统方式)

beans.xml

	<bean id="bean_xml_car" class="com.example.annotations.bean.Car" init-method="init" destroy-method="destroy"/>

使用注解

scope采用默认

		@Configuration
	public class MainConfigOfLifeCycle {
	    @Bean(initMethod = "init" ,destroyMethod = "destroy")
	    public Car car(){
	        return new Car();
	    }
	}
	

测试

	    @Test
	    void lifeCycleTest(){
	        //配置类测试
	        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
	        System.out.println("容器创建完毕");
	    }

测试结果

在这里插入图片描述

现在test中容器关闭

@Test
    void lifeCycleTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完毕");
        //关闭容器
        applicationContext.close();
    }

close之后 调用了销毁方法

scope改为prototype

MainConfigOfLifeCycle

@Configuration
public class MainConfigOfLifeCycle {
    @Bean(value = "car4LifeCycle",initMethod = "init" ,destroyMethod = "destroy")
    @Scope("prototype")
    public Car car(){
        return new Car();
    }
}

测试

    @Test
    void lifeCycleTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完毕");
        //关闭容器
        applicationContext.close();
    }

测试结果
在这里插入图片描述

手动获取bean

test


    @Test
    void lifeCycleTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完毕");
        //获取bean
        System.out.println("准备获取bean");
        Object car4LifeCycle = applicationContext.getBean("car4LifeCycle");
        System.out.println("获取完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果
在这里插入图片描述
可看出,prototype下 不会调用销语句

Notes

构造(对象创建)

  1. 单实例:容器启动的时候创建对象
  2. 多实例:每次获取的时候创建对象

初始化

对象创建完成并赋值完成 调用初始化方法

销毁

  1. 单实例:容器关闭的时候
  2. 多实例:容器不会管理这个bean;销毁方法不会由容器调用

单实例

  1. 容器启动的时候创建对象
  2. 会在创建容器完毕前 获取bean并完成初始化方法
  3. 容器关闭的时候会调用bean的销毁方法

多实例

  1. 容器创建的时候并不会创建或者调用初始化
  2. 每次获取的时候创建对象 调用初始化
  3. 销毁:容器不会管理这个bean,销毁方法不会由容器来调用

生命周期 02 实现InitializingBean, DisposableBean接口

Cat.java

package com.example.annotations.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

/**
 * 生命周期测试
 */
public class Cat implements InitializingBean, DisposableBean {
    /**
     * 构造
     */
    public Cat() {
        System.out.println("cat constructor ...");
    }

    /**
     * 做初始化方法
     * 初始化方法不可以有参数 但可以抛异常
     */
    public void init() {
        System.out.println("cat init ...");
    }

    /**
     * 初始化设置完属性后
     * 来源于 InitializingBean
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("cat afterPropertiesSet ...");
    }

    /**
     * 销毁的时候调用
     * 来源于 DisposableBean
     * @throws Exception
     */
    @Override
    public void destroy() throws Exception {
        System.out.println("cat destroy ...");
    }
    public void destroyMethod() {
        System.out.println("cat destroyMethod ...");
    }
}

MainConfigOfLifeCycle.class中添加以下方法

    @Bean(value = "cat4LifeCycle",initMethod = "init",destroyMethod = "destroyMethod")
    public Cat cat(){
        return new Cat();
    }

测试

    @Test
    void lifeCycleTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完毕");
        //获取bean
        System.out.println("准备获取bean");
        Object car4LifeCycle = applicationContext.getBean("cat4LifeCycle");
        System.out.println("获取完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果:
在这里插入图片描述

生命周期 03 @PostConstruct & @PreDestroy

使用JSR250
@PostConstruct:在bean创建完成并且属性赋值完成后执行此方法
@PreDestroy:在容器销毁bean之前通知进行清理工作

Dog.java

package com.example.annotations.bean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * 测试
 * @PostConstruct
 * @PreDestroy
 */
public class Dog {
    public Dog() {
        System.out.println("dog construct ...");
    }

    /**
     * 对象创建并赋值之后调用
     */
    @PostConstruct
    public void init(){
        System.out.println("dog @PostConstruct ...");
    }

    /**
     * 容器移除对象前调用
     */
    @PreDestroy
    public void destroy(){
        System.out.println("dog @PreDestroy ...");
    }

}

配置新加

    @Bean(value = "dog4LifeCycle")
    public Dog dog(){
        return new Dog();
    }

测试

    @Test
    void lifeCycleTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

结果
在这里插入图片描述
现添加之前的生命周期方法

package com.example.annotations.bean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * 测试
 * @PostConstruct
 * @PreDestroy
 */
public class Dog {
    public Dog() {
        System.out.println("dog construct ...");
    }

    /**
     * 做初始化方法
     * 初始化方法不可以有参数 但可以抛异常
     */
    public void init() {
        System.out.println("dog init ...");
    }

    /**
     * 对象创建并赋值之后调用
     */
    @PostConstruct
    public void postConstruct(){
        System.out.println("dog @PostConstruct ...");
    }

    /**
     * 容器移除对象前调用
     */
    @PreDestroy
    public void preDestroy(){
        System.out.println("dog @PreDestroy ...");
    }

    /**
     * 做销毁方法
     * 销毁方法不可以有参数 但可以抛异常
     */
    public void destroyMethod() {
        System.out.println("dog destroyMethod ...");
    }

}

配置类:

    @Bean(value = "dog4LifeCycle",initMethod = "init",destroyMethod = "destroyMethod")
    public Dog dog(){
        return new Dog();
    }

测试结果
在这里插入图片描述

扩展 JSR250
原文连接

  1. 简介
    JSR 250(Java Specification Requests),Java注解规范,定义了一系列基于Java EE和Java SE通用注解。它避免了不同框架或组件间重复(或冗余)的注解。JSR 250正式发布于2006年5月11日。随着申明式注解配置被越来越多地应用在Java框架(比如Spring),JSR 250可能在未来会持续增长,所以在使用诸如Spring之类的框架时,尽量使用JSR 250中定义的注解,避免和特定框架紧耦合。

  2. 注解列表

    注解作用
    @Generated标记该资源是自动生成的
    @Resource定义了对某个资源的引用
    @Resources容器针对多资源的注解
    @PostConstruct标记在方法上用于依赖注入的初始化动作
    @PreDestroy标记在方法上用于该对象实例从容器销毁时的前置操作
    @Priority定义了该类执行的优先级 如拦截器的优先级
    @RunAs定义了应用在Java EE容器运行时的角色
    @RolesAllowed定义了什么安全角色被允许执行方法
    @PermitAll标记在类或方法上允许所有角色执行(所有)方法
    @DenyAll标记在方法上不允许所有角色调用
    @DeclareRoles用于定义系统的安全角色
    @DataSourceDefinition定义一个数据源,并通过JNDI注册到容器
    @ManagedBean定义一个被容器管理的对象

生命周期 04 BeanPostProcessor 后置处理器

BeanPostProcessor是一个接口:bean的后置处理器
在bean初始化前后进行一些处理工作
postProcessBeforeInitialization:初始化之前调用
postProcessAfterInitialization:初始化之后调用

自定义后置处理器

package com.example.annotations.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * 测试 BeanPostProcessor(后置处理器)
 * 初始化前后进行处理工作
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 初始化之前调用
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization === " + beanName +" => " + bean );
        return bean;
    }

    /**
     * 初始化之后调用
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization === " + beanName +" => " + bean );
        return bean;
    }
}

配置类

@Import(MyBeanPostProcessor.class) :

  1. 将MyBeanPostProcessor纳入到容器中;
  2. 这样该容器中的所有bean在初始化前后都会执行该后置处理器中的逻辑,即使未定义初始化方法这些逻辑也会执行的
@Configuration
@Import(MyBeanPostProcessor.class) //将MyBeanPostProcessor纳入到容器中;这样该容器中的所有bean在初始化前后都会执行该后置处理器中的逻辑,即使未定义初始化方法这些逻辑也会执行的
public class MainConfigOfLifeCycle {
    @Bean(value = "dog4LifeCycle",initMethod = "init",destroyMethod = "destroyMethod")
    public Dog dog(){
        return new Dog();
    }
}

测试

    @Test
    void lifeCycleTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器创建完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果
在这里插入图片描述

属性赋值 01 @Value赋值

传统方式

public class Person {
    private String name;
    private Integer age;
    private String nickName;

    public Person() {
    }

    public Person(String name, Integer age, String nickName) {
        this.name = name;
        this.age = age;
        this.nickName = nickName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

beans.xml

    <context:property-placeholder location="classpath:person.properties" />
    <bean id="person4PropertyValues" class="com.example.annotations.bean.Person">
        <property name="age" value="21"/>
        <property name="name" value="qd"/>
        <property name="nickName" value="${person.nickName}"/>
    </bean>

在这里插入图片描述

配置文件
person.properties

person.nickName=Aimer

测试

    @Test
    void xmlPropertyValuesTest(){
        //传统方式
        ClassPathXmlApplicationContext xmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
        System.out.println("容器创建完毕");
        //获取bean
        System.out.println("准备获取bean");
        Object person4PropertyValues = xmlApplicationContext.getBean("person4PropertyValues");
        System.out.println(person4PropertyValues.toString());
        System.out.println("获取完毕");
        //关闭容器
        xmlApplicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果:
在这里插入图片描述

注解方式

  1. 基本数值
  2. SPEL表达式 : #{22-1} => 21
  3. ${} :取出配置文件的值 [在运行环境变量中的值]

Person.java

public class Person {
    @Value("qd")
    private String name;
    @Value("#{22-1}")
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

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

配置类

@Configuration
public class MainConfig4PropertyValues {
    @Bean("person4PropertyValues")
    public Person person(){
        return new Person();
    }
}

测试

    @Test
    void propertyValuesTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4PropertyValues.class);
        System.out.println("容器创建完毕");
        //获取bean
        System.out.println("准备获取bean");
        Object person4PropertyValues = applicationContext.getBean("person4PropertyValues");
        System.out.println(person4PropertyValues.toString());
        System.out.println("获取完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果
在这里插入图片描述

注意 无论是beans.xml 还是配置类 都要保证bean的名字不重复

属性赋值 02 @PropertySource赋值

使用@PropertySource读取外部配置文件中的 k-v 保存到运行的环境变量中
加载完外部的配置文件以后 使用${}取出配置文件中的值
配置类

package com.example.annotations.config;

import com.example.annotations.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource(value = {"classpath:/person.properties"})
public class MainConfig4PropertyValues {
    @Bean("person4PropertyValues")
    public Person person(){
        return new Person();
    }
}

Person.java

package com.example.annotations.bean;


import org.springframework.beans.factory.annotation.Value;


public class Person {
    @Value("qd")
    private String name;
    @Value("21")
    private Integer age;
    @Value("${person.nickName}")
    private String nickName;

    public Person(String name, Integer age, String nickName) {
        this.name = name;
        this.age = age;
        this.nickName = nickName;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

配置文件
person.properties

person.nickName=Aimer

测试

    @Test
    void propertyValuesTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4PropertyValues.class);
        System.out.println("容器创建完毕");
        //获取bean
        System.out.println("准备获取bean");
        Object person4PropertyValues = applicationContext.getBean("person4PropertyValues");
        System.out.println(person4PropertyValues.toString());
        System.out.println("获取完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果
在这里插入图片描述

扩展:

    @Test
    void propertyValuesTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4PropertyValues.class);
        System.out.println("容器创建完毕");

//        //获取bean
//        System.out.println("准备获取bean");
//        Object person4PropertyValues = applicationContext.getBean("person4PropertyValues");
//        System.out.println(person4PropertyValues.toString());
//        System.out.println("获取完毕");

        //运行环境测试输出
        System.out.println("运行环境测试输出");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        String property = environment.getProperty("person.nickName");
        System.out.println("person.nickName" + " => " + property);
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果
在这里插入图片描述

  • 可以使用@ProPertySources注解指定多个PropertySource

自动装配 01 @Autowired & @Qualifier & @Primary

自动装配:Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值

  1. 常用@Autowired

    • 默认优先按照类型去容器中找对应的组件
    • 若ioc中有多个同类型的bean,再将属性名称作组件id去容器中查找(会报错
  2. 也可以使用 @Qualifier("[beanName]")来指定注入bean,这种情况下可以存在相同类型的多个bean‘

  3. 如果没有bean,会报错 自动装配默认一定要将属性赋值好,否则会报错

    可以修改这种默认设置: @Autowired(required=false)
    多数据源的时候用的多

  4. @Primary:让Spring进行自动装配的时候默认使用首选的bean,也可以继续使用@Qulifier指定需要装配的bean的名字

    优先级:@Qulifier > @Primary

@Primary测试

常用@Autowired
BookDao.java


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

    public Integer getLable() {
        return lable;
    }

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

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

BookService.java

@Service
public class BookService {

    @Autowired
    private BookDao booKDao;

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

BookController.java

@Controller
public class BookController {
    @Autowired
    private BookService bookService;
}

配置类
MainConfig4Autowire.java

package com.example.annotations.config;

import com.example.annotations.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(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller"})
public class MainConfig4Autowire {


    @Bean("bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLable(2);
        return bookDao;
    }
}

测试:

    @Test
    void autowiredTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4Autowire.class);
        System.out.println("容器创建完毕");
        //获取bean
        System.out.println("准备获取bean");
        BookService bookService = applicationContext.getBean(BookService.class);
        System.out.println(bookService.toString());
        System.out.println("获取完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果:
在这里插入图片描述

配置类不加 @Primary 的时候,启动测试会报错,因为有两个BookDao的bean;配置类如果不指定注入的话,运行结果如下:

在这里插入图片描述

@Qualifier测试

配置类:

@Configuration
@ComponentScan(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller"})
public class MainConfig4Autowire {


//    @Primary
    @Bean("bookDao2")
    public BookDao bookDao(){
        BookDao bookDao = new BookDao();
        bookDao.setLable(2);
        return bookDao;
    }
}

BookService.java


@Service
public class BookService {

    @Autowired
    @Qualifier("bookDao2")
    private BookDao booKDao;

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

运行结果:
在这里插入图片描述

自动装配 02 @Resource & @Inject

Spring还支持使用@Resource 属于JSR250@Inject 属于JSR330

@Resource & @Inject 属于java规范的注解
@Autowired 属于Spring的注解

@Resource

  1. 可以和@Autowired一样实现自动装配功能,默认是按照组件名称装配的
  2. 没有能支持@Primary 或者 @Autowired(required = false) 或者@Qulifier
@Service
public class BookService {
    @Resource
    private BookDao bookDao;
    @Override
    public String toString() {
        return "BookService{" +
                "booKDao=" + bookDao +
                '}';
    }
}

运行结果
在这里插入图片描述

@Inject

  1. 需要在maven库中找到 javax.inject 包中的central依赖
  2. 和**@Autowired**功能相似,但没有required属性

没有实现代码

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

@Autowired的位置:构造器 方法 参数 属性 都是从容器中获取参数的值

  1. 方法上:Spring容器创建当前对象就会调用此方法 完成赋值
    • 方法使用的参数 自定义类型就会从ioc容器中获取
  2. 构造器上: 构造器要用的组件都是从容器中获取
    • 若bean中只有一个有参构造方法,@Autowired注解可以省掉,默认会调用这个参数位置的组件还是会从ioc容器获取
  3. 参数上:@Bean + 方法参数:默认不写@Autowired ,也能自动装配 也是从ioc容器中获取

Boss.java

@Component
public class Boss {

    private Car car;

    public Car getCar() {
        return car;
    }

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

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

Car.java

/**
 * 生命周期测试
 * 自动装配测试
 */
@Component
public class Car {
    public Car() {
        System.out.println("car constructor ...");
    }

    /**
     * 做初始化方法
     * 初始化方法不可以有参数 但可以抛异常
     */
    public void init() {
        System.out.println("car init ...");
    }

    /**
     * 做销毁方法
     * 销毁方法不可以有参数 但可以抛异常
     */
    public void destroy() {
        System.out.println("car destroy ...");
    }
}

配置类

@Configuration
@ComponentScan(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller","com.example.annotations.bean"})
public class MainConfig4Autowire {

}

测试:

    @Test
    void autowiredTest(){
        //配置类测试
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4Autowire.class);
        System.out.println("容器创建完毕");
        //获取bean
        System.out.println("准备获取bean");
        Boss boss = applicationContext.getBean(Boss.class);
        Car car = applicationContext.getBean(Car.class);
        System.out.println(boss.toString());
        System.out.println(car.toString());
        System.out.println("获取完毕");
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

在这里插入图片描述

@Bean + 方法参数

@Component
public class Boss {

    private Car car;

    public Boss(Car car) {
        System.out.println("构造方法注入");
        this.car = car;

    }

    public Car getCar() {
        return car;
    }

//    @Autowired
    public void setCar(Car car) {
        System.out.println("setter注入");
        this.car = car;
    }

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

运行测试之后可看到有相应输出

自动装配 04 Aware 注入Spring底层组件 & 原理

总接口:Aware

  1. 自定义组件想要使用Spring容器底层的一些组件(ApplicationContext BeanFactory 等等)
  2. 需要让自定义组件实现xxAware接口:在创建对象的时候会调用接口规定的方法注入相关组件 总体参照 Aware 接口 ,把Spring底层一些组件注入到自定义的bean中
  3. xxxAware功能:使用xxProcessor 如ApplicationContextAware ==》 ApplicationContextAwareProcessor,利用后置处理器判断实现了哪个接口,就将Bean转成相应的接口类型,再选择注入相应类型的applicationContext
/**
 * 生命周期 感知接口测试
 */
@Component//注入后自动调用实现的感知接口方法
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    @Override
    public void setBeanName(String name) {
        System.out.println("调用了 setBeanName");
        System.out.println("当前bean的名字 " + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        System.out.println("调用了 setApplicationContext");
    }

    /**
     * 解析占位符
     * @param resolver
     */
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        System.out.println("调用了 setEmbeddedValueResolver");
        String resolveString = resolver.resolveStringValue(" hello ${os.name} ; age #{22-1}");
        System.out.println("解析的字符串 " + resolveString);
    }
}

配置类

@Configuration
@ComponentScan(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller","com.example.annotations.bean"})
public class MainConfig4Autowire {

}

测试

    @Test
    void autowiredTest(){
        //配置类测试
        System.out.println("准备创建容器");
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4Autowire.class);
        System.out.println("容器创建完毕");
        
        //关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

测试结果:
在这里插入图片描述

自动装配 05 @Profile环境搭建

condition相似
@Profile:Spring为我们提供的可以根据当前环境动态的激活和切换一系列组件的功能 也可以根据环境切换数据源

dev
test
prov

@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定的时候任何环境下都能注册这个组件

  1. 加了环境标识的bean 只有这个环境被激活的时候才能注册到容器中。
  2. 写在配置类上,只有是指定的环境的时候,整个配置类中的所有配置才能开始生效
  3. 没有标注环境标识的bean,任何环境下都是加载的。默认是default

修改运行环境的几种方式

  1. 使用命令行动态参数:在虚拟机参数位置加载 -Dspring.profiles.active=[运行环境]-Dspring.profiles.active=test
  2. 手动代码设置
    1. 获取ApplicationContext :注意要使用无参方式获取,否则刷新之后还是没有注入的状态(具体可查看源码分析)
    2. 设置需要激活的环境
    3. 注册主配置类
    4. 启动刷新容器

代码测试

配置类

/**
 * 用于测试数据源和运行环境
 * 使用了多种方式设置参数
 */
@PropertySource("classpath:/dbConfig.properties")
@Configuration
public class MainConfig4Profile implements EmbeddedValueResolverAware {
    public MainConfig4Profile() {
    }

    @Value("${db.user}")
    private String user;
    // 用于EmbeddedValueResolverAware接口测试
    private String driverClass;

    @Profile("prov")
    @Bean("provDataSouce")
    public DataSource dataSource4Prov() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/sb_anno_prov?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
        // 加载驱动的时候会抛异常
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Profile("dev")
    @Bean("devDataSouce")
    public DataSource dataSource4Dev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("${db.user}");
        dataSource.setPassword("${db.password}");
        dataSource.setJdbcUrl("${db.user}");
        dataSource.setDriverClass("${db.driverClass}");
        return dataSource;
    }

    @Profile("test")
    @Bean("testDataSouce")
    public DataSource dataSource4Test(@Value("${db.password}") String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/sb_anno_test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.driverClass = resolver.resolveStringValue("${db.driverClass}");
        System.out.println("解析后的driverClass " + driverClass);
    }
}

dbConfig.properties

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

pom.xml

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!-- 注意版本问题,本地安装的是5.几的版本,而springboot默认导入的是最新的,需要手动指定版本号-->
            <version>5.1.46</version>
            <scope>runtime</scope>
        </dependency>

测试

    @Test
    void profileTest(){
        //配置类测试
        System.out.println("准备创建容器");
        // 1. 获取ApplicationContext 注意要使用无参构造器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        System.out.println("容器创建完毕");
        // 2. 设置需要激活的环境
        applicationContext.getEnvironment().setActiveProfiles("test","dev");
        // 3. 注册配置类
        applicationContext.register(MainConfig4Profile.class);
        // 4. 启动刷新容器
        applicationContext.refresh();

        System.out.println("容器刷新完毕");
        // 获取bean
        String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
        for (String name : beanNamesForType) {
            System.out.println("当前环境下注入的bean" + name);
        }

        // 关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

运行结果:
在这里插入图片描述
AnnotationConfigApplicationContext.java部分源码(无参构造):

	public AnnotationConfigApplicationContext() {
		StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
		this.reader = new AnnotatedBeanDefinitionReader(this);
		createAnnotatedBeanDefReader.end();
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

AnnotationConfigApplicationContext.java部分源码(有参构造):

	/**
	 * 有参方式调用的时候,最后的refresh相当于没有设置
	 */
	public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		this();
		register(componentClasses);
		refresh();
	}

AOP 01 AOP功能测试

AOP:动态代理

指在程序运行期间动态的将某段代码切入到指定位置进行运行的编程方式

步骤

  1. 导入AOP模块
  2. 定义业务逻辑类:在业务逻辑运行的时候打印日志(方法前后 异常 …)
  3. 定义日志切面类:切面里的方法需动态感知目标方法运行到哪里然后执行
  4. 给切面类的目标方法标注何时何地运行(通知注解)
  5. 将切面类和业务逻辑类(目标方法所在类)都加到容器中
  6. 必须告诉Spring哪个是切面类(给切面类加上@Aspect注解)
  7. 给配置类中的@EnableAspectJAutoProxy开启基于注解的AOP模式

    Spring中很多的@EnableXXX表示用来开启某一功能

通知注解

作用注解备注
前置通知@Before
后置通知@After无论正常结束还是异常结束都会调用
返回通知@AfterReturning正常返回后运行
异常通知@AfterThrowing
环绕通知@Around动态代理 手动推进目标方法运行joinPoint.proceed() 相当于一个最底层的通知

代码测试

业务逻辑类
MathCaculator.java

/**
 * @Classname MathCaculator
 * @Description AOP测试_业务逻辑类
 */

public class MathCaculator {
    public int div(int i,int j){
        System.out.println("MathCaculator 运行 div() ...");
        return i/j;
    }
}

切面类


/**
 * @Classname LogAspects
 * @Description 切面类
 * @Date 2021/8/5 10:34
 * @Created by LXLDEMO
 */
@Aspect
public class LogAspects {
    /**
     * 抽取公共的切入点表达式
     * 指向目标方法
     * 1. 本类引用
     * 2. 其他的切面引用
     */
    @Pointcut("execution(public int com.example.annotations.bean.MathCaculator.div(int ,int ))")
    public void pointCut(){

    }

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("运行@Before");
        System.out.println("方法名字 " + joinPoint.getSignature().getName());
        System.out.println("方法参数 " + Arrays.toString(args));
    }

    /**
     * 注解中参数的这种写法属于其他切面的调用方式
     * 全包名
     * @param joinPoint
     */
    @After("com.example.annotations.aop.LogAspects.pointCut()")
    public void LogEnd(JoinPoint joinPoint){
        System.out.println("运行@After " + joinPoint.getSignature().getName());
        System.out.println(joinPoint.getSignature().getName() + "运行结束");
    }

    /**
     * 当有多个参数的时候 JoinPoint必须作为第一个参数
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "pointCut()",returning = "result")
    public void logReturning(JoinPoint joinPoint,Object result){
        System.out.println("运行@AfterReturning");
        System.out.println(joinPoint.getSignature().getName()+"正常返回" + result);
    }

    /**
     * 当有多个参数的时候 JoinPoint必须作为第一个参数
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "pointCut()",throwing = "exception")
    public void logException(JoinPoint joinPoint,Exception exception){
	    System.out.println("运行@AfterThrowing");
        System.out.println("" + joinPoint.getSignature().getName() +" === 异常信息:"+exception);
    }
}

配置类

/**
 * @Classname MainConfig4AOP
 * @Description 配置类
 * @Date 2021/8/5 10:31
 * @Created by LXLDEMO
 */
@EnableAspectJAutoProxy
@Configuration
public class MainConfig4AOP {
    @Bean
    public MathCaculator caculator(){
        System.out.println("准备注入 MathCaculator ...");
        return new MathCaculator();
    }

    @Bean
    public LogAspects logAspects(){
        System.out.println("准备注入 LogAspects ...");
        return new LogAspects();
    }
}

测试

    @Test
    void Aoptest(){
        //配置类测试
        System.out.println("准备创建容器");
        // 注意要使用无参构造器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4AOP.class);
        System.out.println("容器创建完毕");

        // 获取bean
        MathCaculator mathCaculator = applicationContext.getBean(MathCaculator.class);
        mathCaculator.div(1,1);

        // 关闭容器
        applicationContext.close();
        System.out.println("容器已关闭");
    }

测试结果
在这里插入图片描述

将参数换成(1,0)
在这里插入图片描述

to be continued …

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值