Spring注解驱动开发(二)

注:此笔记为尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)学习笔记,并同时参考[https://blog.csdn.net/xjhqre/article/details/123264069]博主文章,其中包含个人的笔记和理解,仅做学习笔记之用。

8、Bean生命周期

Spring注解驱动开发(二)_第1张图片

简述:

1、实例化一个Bean--也就是我们常说的new;

2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;

3、如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值

4、如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以)

5、如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);

6、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;

7、如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法

8、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、

注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。

9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法

10、最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法

以下为详细介绍:

bean从创建 ----> 初始化 -----> 销毁的过程

容器管理bean的生命周期

我们可以自定义初始化和销毁方法

构造(对象创建)

​ 单实例:在容器启动的时候创建对象
​ 多实例:在每次获取的时候创建对象
初始化

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

销毁

  • 单实例:在容器关闭的时候销毁
  • 多实例:容器不会管理这个bean,所以不会调用销毁方法

初始化和销毁方法:

先查看源码,再调用API

1.通过@Bean注解指定init-method和destroy-method
2.实现InitializingBean和DisposableBean接口,重写里面的destroy和afterPropertiesSet方法
3.使用@PostConstruct和@PreDestroy注解
4.BeanPostProcessor:接口,bean后置处理器,在bean初始化前后进行一些处理

初始化和销毁方法:

先查看源码,再调用API

1.通过@Bean注解指定init-method和destroy-method
2.实现InitializingBean和DisposableBean接口,重写里面的destroy和afterPropertiesSet方法
3.使用@PostConstruct和@PreDestroy注解
4.BeanPostProcessor:接口,bean后置处理器,在bean初始化前后进行一些处理

8.1、自定义初始化和销毁方法

最原始在bean.xml中自定义的bean初始化和销毁方法

Spring注解驱动开发(二)_第2张图片

通过@Bean注解指定init-method和destroy-method

8.1.1、创建Car类

Spring注解驱动开发(二)_第3张图片

@Component
public class Car {
	public Car(){
		System.out.println("car constructor...");
	}
	
	public void init(){
		System.out.println("car ... init...");
	}
	
	public void detory(){
		System.out.println("car ... detory...");
	}
}
8.1.2、创建配置文件
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.xjhqre")
public class MainConfig5 {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Car car() {
        return new Car();
    }
}
8.1.3、测试

Spring注解驱动开发(二)_第4张图片

@Test
public void test6() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig5.class);
    applicationContext.close();
}

8.2、InitializingBean和DisposableBean

通过让bean实现InitializingBean接口来定义初始化逻辑

通过让bean实现DisposableBean接口来定义销毁逻辑

InitializingBean中有一个方法afterPropertiesSet,该方法在bean创建并赋值后调用

8.2.1、创建Cat类
public class Cat implements InitializingBean, DisposableBean {

    public Cat() {
        System.out.println("创建猫对象");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("销毁猫对象");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化猫对象");
    }
}
8.2.2、创建配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.xjhqre.pojo")
public class MainConfig6 {

    @Bean
    public Cat cat() {
        return new Cat();
    }
}
8.2.3、测试
@Test
public void test7() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig6.class);
    applicationContext.close();
}

8.3、@PostConstruct和@PreDestroy

@PostConstruct:在bean创建完成并且属性赋值完成后进行初始化

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}

@PreDestroy:在容器销毁bean之前执行

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}
8.3.1、创建Dog类

Spring注解驱动开发(二)_第5张图片

public class Dog {

    public Dog() {
        System.out.println("创建狗对象");
    }

    @PostConstruct
    public void init() {
        System.out.println("初始化狗对象");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("销毁狗对象");
    }
}
8.3.2、配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.xjhqre.pojo")
public class MainConfig7 {

    @Bean
    public Dog dog() {
        return new Dog();
    }
}
8.3.3、测试
@Test
public void test8() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig7.class);
    applicationContext.close();
}

8.4、BeanPostProcessor

BeanPostProcessor接口中有两个方法:

  • postProcessBeforeInitialization:在初始化之前执行
  • postProcessAfterInitialization:在初始化之后执行
8.4.1、编写MyBeanPostProcessor
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
     * @param bean 刚创建好的实例
     * @param beanName 实例的id
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("id为" + beanName + "的bean对象:" + bean + "执行postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("id为" + beanName + "的bean对象:" + bean + "postProcessAfterInitialization");
        return bean;
    }
}
8.4.2、配置类
@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.xjhqre.pojo")
public class MainConfig7 {

    @Bean
    public Dog dog() {
        return new Dog();
    }
}
8.4.3、测试
@Test
public void test8() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig7.class);
    applicationContext.close();
}
8.4.4、BeanPostProcessor执行过程

执行populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
开始initializeBean初始化bean
先执行applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);,遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
然后执行invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
最后执行applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

Spring注解驱动开发(二)_第6张图片

Spring注解驱动开发(二)_第7张图片

Spring注解驱动开发(二)_第8张图片

在这里插入图片描述

9、@Value注解

使用**@Value赋值**,赋值方法:

  1. 直接在@Value中写基本数值
  2. 使用SpEL表达式:#{}
  3. 使用${}:取出配置文件中的值(在运行环境变量里的值)

9.1、修改Person类

public class Person {

    @Value("kdz")
    private String name;

    @Value("#{20-2}")
    private Integer age;

    @Value("${person.email}")
    private String email;

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

9.2、配置类

@PropertySource(value = {"classpath:/person.properties"})
@ComponentScan(value = "com.kdz.pojo")
public class MainConfig9 {

    @Bean
    public Person person() {
        return new Person();
    }
}

9.3、测试类

@Test
public void test9() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig9.class);
    Person person = applicationContext.getBean(Person.class);
    System.out.println(person);

    // 也可以使用环境变量取出
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    String property = environment.getProperty("person.email");
    System.out.println(property);
}

在这里插入图片描述

10、自动装配

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

10.1、@AutoWired

给属性自动注入值

默认优先按照类型去容器中找对应的组件,找到就赋值

如果该类型的组件有多个,再将属性名作为组件的id去容器中查找

@AutoWired可以在构造器、参数、方法、属性上标注

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

   boolean required() default true;

}
10.1.1、required属性

@AutoWired可以给required属性赋值,类型为boolean,默认为true。表示该组件是否必须

当required=false时,表示这个组件不是必须的在调用getBean方法时如果找不到对应的组件时不会直接抛出异常,而是返回一个null

Spring注解驱动开发(二)_第9张图片

Spring注解驱动开发(二)_第10张图片

Spring注解驱动开发(二)_第11张图片

Spring注解驱动开发(二)_第12张图片

10.1.2、标注在方法上

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

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

@Autowired
public void setCar(Car car) {
    this.car = car;
}
10.1.3、标注在构造器上

默认加在IOC容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作

我们可以在有参构造函数上标注@Autowired让IOC容器创建组件时调用该类的有参构造方法

如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略

Spring注解驱动开发(二)_第13张图片

Spring注解驱动开发(二)_第14张图片

@Autowired
public Boss(Car car) {
    this.car = car;
}
10.1.3、标注在参数上
public Boss(@Autowired Car car) {
    this.car = car;
}

@Bean标注的方法创建对象的时候,方法参数的值从容器中获取

// 参数car会从IOC容器中获取,可以省略Car前的@Autowired
@Bean
public Color color(Car car) {
    return new Color();
}

10.2、@Qualifier

使用@Qualifier指定需要装配的组件的id,而不是用属性名

@Qualifier需要和@AutoWired一起使用

10.3、@Primary

在这里插入图片描述

该注解可以让Spring进行自动装配的时候,默认使用首选的bean

可以继续使用@Qualifier指定需要装配的bean的名字

无论是否有依赖注入,@Primary标注的bean都会被容器创建

/**
 * 指示当多个候选者有资格自动装配单值依赖项时,应优先考虑 bean。如果候选中恰好存在一个“主”bean,则它将是自动装配的值。
 * 这个注解在语义上等同于 Spring XML 中元素的primary属性。
 * 可用于任何直接或间接使用@Component注释的类或使用Bean注释的方法。
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}

10.4、@Resource

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

可以给属性name赋值,自定义组件名称

Spring注解驱动开发(二)_第15张图片

Spring注解驱动开发(二)_第16张图片

@Resource(name="bookDao2")
private BookDao bookDao;

但该注解没有支持@Primary、@Autowired(require=false)的功能

10.5、@Inject

需要导入javax.inject依赖

和@Autowired功能一样,但没有required=false的功能

Spring注解驱动开发(二)_第17张图片

@Inject
private BookDao bookDao;

三种注入方式的小结

Spring注解驱动开发(二)_第18张图片

Spring注解驱动开发(二)_第19张图片

构造器需要对象直接从容器中获取

默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作

Spring注解驱动开发(二)_第20张图片

Spring注解驱动开发(二)_第21张图片

Spring注解驱动开发(二)_第22张图片

11、使用Spring容器底层组件

自定义组件想要使用Spring容器底层的一些组件(ApplicationContext、BeanFactory),需要自定义组件实现xxxAware接口

在创建对象的时候,会调用接口规定的方法,注入相关的组件

Aware的子接口

Spring注解驱动开发(二)_第23张图片

11.1、编写Red类

@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {

    private ApplicationContext applicationContext;

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

    // 获取当前bean对象的名称
    @Override
    public void setBeanName(String name) {
        System.out.println("当前bean的名字:" + name);
    }

    // 解析String语句中的占位符 $ 或 #
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String s = resolver.resolveStringValue("你好${os.name}, 我是#{20*18}");
        System.out.println("解析的字符串:" + s);
    }
}

11.2、配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.xjhqre.pojo")
public class MainConfig10 {
}

11.3、测试

@Test
public void test10() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig9.class);
}

Spring注解驱动开发(二)_第24张图片

11.4 Aware小结

每一个xxxAware都有一个对应的xxxAwareProcessor(后置处理器),用来处理相关逻辑

以ApplicationContextAwareProcessor为例

Spring注解驱动开发(二)_第25张图片

Spring注解驱动开发(二)_第26张图片

遍历所有的processor

Spring注解驱动开发(二)_第27张图片

Spring注解驱动开发(二)_第28张图片

Spring注解驱动开发(二)_第29张图片

利用后置处理器processor判断Aware的类型,注入对应的ApplicationContext

12、@Profile注解

@Profile注解是Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能

例子:在不同环境下使用不同的数据源,在开发环境下使用A数据源,在测试环境下使用B数据源,在生产环境下使用C数据源

需要导入c3p0依赖C3p0:JDBC DataSources/Resource Pools和数据库驱动依赖MySQL Connector

Spring注解驱动开发(二)_第30张图片

12.1、编写db.properties

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=123456

12.2、编写配置类

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

加了环境标识的bean,只有在这个环境被激活的时候才能注册到容器中,默认环境为default,即@Profile("default")

@Profile注解写在类上时,只有当指定的环境被激活时,整个类才会被注册

@Configuration // 告诉Spring这是一个配置类
@PropertySource("classpath:/db.properties")
public class MainConfig11 implements EmbeddedValueResolverAware {

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

    private StringValueResolver valueResolver;

    // 测试数据库
    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${jdbc.password}")String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
        String driverClass = valueResolver.resolveStringValue("${jdbc.driverClassName}");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    // 开发数据库
    @Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${jdbc.password}")String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/atguigu?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
        String driverClass = valueResolver.resolveStringValue("${jdbc.driverClassName}");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    // 生产数据库
    @Profile("prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("${jdbc.password}")String pwd) throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
        String driverClass = valueResolver.resolveStringValue("${jdbc.driverClassName}");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
    }
}

12.3、环境激活方式

激活运行环境方式:

1.命令行方式,在虚拟机选项中输入:-Dspring.profiles.active=test

Spring注解驱动开发(二)_第31张图片

2.使用代码方式:

1)使用无参构造器创建application,AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
2)设置需要激活的环境:applicationContext.getEnvironment().setActiveProfiles("test", "dev");
3)注册主配置类:applicationContext.register(MainConfig11.class);
4)刷新容器:applicationContext.refresh();

12.4、测试

测试中使用代码方式激活环境

@Test
public void test11() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.getEnvironment().setActiveProfiles("test", "dev");
    applicationContext.register(MainConfig11.class);
    applicationContext.refresh();
    String[] dataSources = applicationContext.getBeanNamesForType(DataSource.class);
    for (String dataSource : dataSources) {
        System.out.println(dataSource);
    }
}

只能使用无参构造的方式创建容器的原因

Spring注解驱动开发(二)_第32张图片

Spring注解驱动开发(二)_第33张图片

Spring注解驱动开发(二)的学习笔记到此完结,笔者归纳、创作不易,大佬们给个3连再起飞吧

你可能感兴趣的:(Spring,spring,java,后端)