Spring注解驱动开发02

生命周期

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

通常意义上讲的bean的生命周期,指的是bean从创建到初始化,经过一系列的流程,最终销毁的过程。只不过,在Spring中,bean的生命周期是由Spring容器来管理的。在Spring中,我们可以自己来指定bean的初始化和销毁的方法。我们指定了bean的初始化和销毁方法之后,当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。

1.1、配置文件xml中定义初始化和销毁方法

首先,创建一个名称为Car的类,这个类的实现比较简单,如下所示

package com.jian.bean;

public class Car {
    public Car(){
        System.out.println("Car...Constructor...");
    }
    public void init(){
        System.out.println("Car初始化...");
    }
    public void destroy(){
        System.out.println("Car销毁...");
    }
}

然后在resources–>beans.xml中配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="car" class="com.jian.bean.Car" init-method="init" destroy-method="destroy"/>
    
</beans>

1.2、@Bean方式定义初始化和销毁

新增一个配置类config—>MainConfigLifeCycle

package com.jian.config;

import com.jian.bean.Car;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

添加测试方法test–>LifeCycleTest

package com.jian.test;

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

public class LifeCycleTest {
    @Test
    public void test(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
        System.out.println("容器创建完成...");
    }
}

在这里插入图片描述

从输出结果中可以看出,在Spring容器中,先是调用了Car类的构造方法来创建Car对象,接下来便是调用了Car对象的init()方法来进行初始化。

有些小伙伴们可能会问了,运行上面的测试方法并没有打印出bean的销毁方法中的信息啊,那什么时候执行bean的销毁方法呢?这个问题问的很好,bean的销毁方法是在容器关闭的时候被调用的。

所以,我们在LifeCycleTest类中的test()方法中,添加关闭容器的代码,如下所示

    @Test
    public void test() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
        System.out.println("容器创建完成...");
        //2.关闭容器
        applicationContext.close();
    }

在这里插入图片描述

上面主要是针对单实例的初始化和销毁,接下来我们讨论多实例的初始化和销毁

首先,我们在MainConfigOfLifeCycle配置类中的car()方法上通过@Scope注解将Car对象设置成多实例bean,如下所示。

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

然后,我们修改一下LifeCycleTest类中的test()方法,将关闭容器的那行代码给注释掉,如下所示

    @Test
    public void test() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
        System.out.println("容器创建完成...");
        //2.关闭容器
        //applicationContext.close();
    }

在这里插入图片描述

可以看到,当我们将Car对象设置成多实例bean,并且没有获取bean实例对象时,Spring容器并没有执行bean的构造方法、初始化方法和销毁方法。

然后我们修改test方法,去获取car的Bean

    @Test
    public void test() {
        //1.创建IOC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
        System.out.println("容器创建完成...");
        Object car = applicationContext.getBean("car");
        //2.关闭容器
        //applicationContext.close();
    }

运行测试

在这里插入图片描述

当我们在获取多实例bean对象的时候,会创建对象并进行初始化,但是销毁方法是在什么时候被调用呢?是在容器关闭的时候吗?我们可以将LifeCycleTest类中的test()方法里面的那行关闭容器的代码放开来进行验证,就像下面这样

在这里插入图片描述

可以看到,多实例的bean在容器关闭的时候是不进行销毁的,也就是说你每次获取时,IOC容器帮你创建出对象交还给你,至于要什么时候销毁这是你自己的事,Spring容器压根就不会再管理这些多实例的bean了

2、InitializingBean和DisposableBean接口

除了@Bean注解中使用init-method属性和destroy-method属性来指定初始化方法和销毁方法外,我们还可以让一个类实现InitializingBean和DisposableBean接口来指定初始化方法和销毁方法

InitializingBean和DisposableBean接口的使用

首先,创建一个Cat的类来实现InitializingBean和DisposableBean这俩接口,代码如下所示,注意该Cat类上标注了一个@Component注解

package com.jian.bean;

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

@Component
public class Cat implements InitializingBean, DisposableBean {
    public Cat(){
        System.out.println("Cat Constructor...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Cat destroy...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Cat afterPropertiesSet...");
    }
}

然后,在MainConfigOfLifeCycle配置类中通过包扫描的方式将以上类注入到Spring容器中。

@Configuration
@ComponentScan("com.jian.bean")
public class MainConfigLifeCycle {
    @Scope("prototype")
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Car car() {
        return new Car();
    }
}

测试输出

在这里插入图片描述

从输出的结果信息中可以看出,单实例bean情况下,IOC容器创建完成后,会自动调用bean的初始化方法;而在容器销毁前,会自动调用bean的销毁方法。

DisposableBean接口注意事项

多实例bean的生命周期不归Spring容器来管理,这里的DisposableBean接口中的方法是由Spring容器来调用的,所以如果一个多实例bean实现了DisposableBean接口是没有啥意义的,因为相应的方法根本不会被调用,当然了,在XML配置文件中指定了destroy方法,也是没有任何意义的。所以,在多实例bean情况下,Spring是不会自动调用bean的销毁方法的

3、@PostContruct和@PreDestroy注解

@PostContructor注解

@PostContructor是Java自己的注解,是JSR-250规范里面定义的一个注解。我们来看下@PostConstruct注解的源码,如下所示。

在这里插入图片描述

@PostConstruct注解被用来修饰一个非静态的void()方法。被@PostConstruct注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。被@PostConstruct注解修饰的方法通常在构造函数之后,init()方法之前执行

Constructor(构造方法)→@Autowired(依赖注入)→@PostConstruct(注释的方法)

@PreDestroy注解

@PreDestroy注解同样是Java提供的,它也是JSR-250规范里面定义的一个注解。看下它的源码,如下所示。

在这里插入图片描述

被@PreDestroy注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy注解修饰的方法会在destroy()方法之前,Servlet被彻底卸载之前执行。执行顺序如下所示:

调用destroy()方法→@PreDestroy→destroy()方法→bean销毁

使用案例

首先,我们创建一个Dog类,如下所示,注意在该类上标注了一个@Component注解。

package com.jian.bean;

import org.springframework.stereotype.Component;

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

@Component
public class Dog {
    public Dog() {
        System.out.println("Dog Constructor...");
    }

    // 在对象创建完成并且属性赋值完成之后调用
    @PostConstruct
    public void init() {
        System.out.println("Dog Init...");
    }

    //在容器销毁(移除)对象之前调用
    @PreDestroy
    public void destroy() {
        System.out.println("Dog destroy...");
    }
}

然后,在MainConfigOfLifeCycle配置类中通过包扫描的方式将以上类注入到Spring容器中

最后测试

在这里插入图片描述

从输出的结果信息中可以看出,被@PostConstruct注解修饰的方法是在bean创建完成并且属性赋值完成之后才执行的,而被@PreDestroy注解修饰的方法是在容器销毁bean之前执行的,通常是进行一些清理工作。

4、BeanPostProcessor后置处理器

首先,我们来看下BeanPostProcessor的源码,看下它到底是个什么鬼,如下所示

在这里插入图片描述

从源码可以看出,BeanPostProcessor是一个接口,其中有两个方法,即postProcessBeforeInitialization和postProcessAfterInitialization这两个方法,这两个方法分别是在Spring容器中的bean初始化前后执行,所以Spring容器中的每一个bean对象初始化前后,都会执行BeanPostProcessor接口的实现类中的这两个方法
也就是说,postProcessBeforeInitialization方法会在bean实例化和属性设置之后,自定义初始化方法之前被调用,而postProcessAfterInitialization方法会在自定义初始化方法之后被调用。当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行。对于自定义的BeanPostProcessor实现类,还可以让其实现Ordered接口自定义排序
实例

我们创建一个MyBeanPostProcessor类,实现BeanPostProcessor接口,如下所示

package com.jian.bean;

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

/**
 *后置处理器,在初始化前后进行处理工作
 */
//将后置处理器加到容器中
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessorBeforeInitialization..."+beanName+"-->"+bean);
        return bean;
    }

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

接下来,我们应该是要编写测试用例来进行测试了。不过,在这之前,咱们得做几处改动,第一处改动是将MainConfigOfLifeCycle配置类中的car()方法上的@Scope("prototype")注解给注释掉,因为咱们之前做测试的时候,是将Car对象设置成多实例bean了

最后执行测试方法test()

在这里插入图片描述

可以看到,postProcessBeforeInitialization方法会在bean实例化和属性设置之后,自定义初始化方法之前被调用,而postProcessAfterInitialization方法会在自定义初始化方法之后被调用

5、BeanPostProcessor处理流程

Bean的初始化与销毁

一、通过@Bean指定init-method和destroy-method

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

二、通过让Bean实现InitializingBean和DisposableBean接口

@Component
public class Cat implements InitializingBean, DisposableBean {
    public Cat() {
        System.out.println("Cat Constructor...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Cat destroy...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Cat afterPropertiesSet...");
    }
}

三、通过使用JSR-250规范里面定义的@PostConstruct和@PreDestroy注解

  • @PostConstruct:在bean创建完成并且属性赋值完成之后,来执行初始化方法
  • @PreDestroy:在容器销毁bean之前通知我们进行清理工作、
@Component
public class Dog {
    public Dog() {
        System.out.println("Dog Constructor...");
    }

    // 在对象创建完成并且属性赋值完成之后调用
    @PostConstruct
    public void init() {
        System.out.println("Dog Init...");
    }

    //在容器销毁(移除)对象之前调用
    @PreDestroy
    public void destroy() {
        System.out.println("Dog destroy...");
    }
}

四、通过让Bean实现BeanPostProcessor接口

/**
 *后置处理器,在初始化前后进行处理工作
 */
//将后置处理器加到容器中
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessorBeforeInitialization..."+beanName+"-->"+bean);
        return bean;
    }

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

通过以上这四种方式,我们就可以对bean的整个生命周期进行控制:

  • bean的实例化:调用bean的构造方法,我们可以在bean的无参构造方法中执行相应的逻辑。
  • bean的初始化:在初始化时,可以通过BeanPostProcessor的postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法进行拦截,执行自定义的逻辑;通过@PostConstruct注解、InitializingBean和init-method来指定bean初始化前后执行的方法,在该方法中咱们可以执行自定义的逻辑。
  • bean的销毁:可以通过@PreDestroy注解、DisposableBean和destroy-method来指定bean在销毁前执行的方法,在该方法中咱们可以执行自定义的逻辑。

BeanPostProcessor源码解析

postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法这两处打上断点来进行调试,如下所示。

在这里插入图片描述

随后,我们以Debug的方式来运行LifeCycleTest类中的test()方法,运行后的效果如下所示:

在这里插入图片描述

温馨提示:方法调用栈是先进后出的,也就是说,最先调用的方法会最后退出,每调用一个方法,JVM会将当前调用的方法放入栈的栈顶,方法退出时,会将方法从栈顶的位置弹出

第一步,我们在IDEA的方法调用栈中,找到LifeCycleTest类的test()方法并单击,此时Eclipse的主界面会定位到LifeCycleTest类的test()方法中,如下所示。

在这里插入图片描述

在LifeCycleTest类的test()方法中,首先通过new实例对象的方式创建了一个IOC容器

第二步,通过IDEA的方法调用栈继续分析,单击LifeCycleTest类的test()方法上面的那个方法,这时会进入AnnotationConfigApplicationContext类的构造方法中

在这里插入图片描述

可以看到,在AnnotationConfigApplicationContext类的构造方法中会调用refresh()方法。

第三步,我们继续跟进方法调用栈,如下所示,可以看到,方法的执行定位到AbstractApplicationContext类的refresh()方法中的如下那行代码处

在这里插入图片描述

这行代码的作用就是初始化所有的(非懒加载的)单实例bean对象。

第四步,我们继续跟进方法调用栈,如下所示,可以看到,方法的执行定位到AbstractApplicationContext类的finishBeanFactoryInitialization()方法中的如下那行代码处。

在这里插入图片描述

这行代码的作用同样是初始化所有的(非懒加载的)单实例bean。

第五步,我们继续跟进方法调用栈,如下所示,可以看到,方法的执行定位到DefaultListableBeanFactory类的preInstantiateSingletons()方法的最后一个else分支调用的getBean()方法上。

在这里插入图片描述

第六步,继续跟进方法调用栈,如下所示

在这里插入图片描述

此时方法定位到AbstractBeanFactory类的getBean()方法中了,在getBean()方法中,又调用了doGetBean()方法

第七步,继续跟进方法调用栈,如下所示,此时,方法的执行定位到AbstractBeanFactory类的doGetBean()方法中的如下那行代码处。

在这里插入图片描述

可以看到,在Spring内部是通过getSingleton()方法来获取单实例bean的。

第八步,继续跟进方法调用栈,如下所示,此时,方法定位到DefaultSingletonBeanRegistry类的getSingleton()方法中的如下那行代码处。

在这里插入图片描述

可以看到,在getSingleton()方法里面又调用了getObject()方法来获取单实例bean。

第九步,继续跟进方法调用栈,如下所示,此时,方法定位到AbstractBeanFactory类的doGetBean()方法中的如下那行代码处。

在这里插入图片描述

也就是说,当第一次获取单实例bean时,由于单实例bean还未创建,那么Spring会调用createBean()方法来创建单实例bean。

第十步,继续跟进方法调用栈,如下所示,可以看到,方法的执行定位到AbstractAutowireCapableBeanFactory类的createBean()方法中的如下那行代码处

在这里插入图片描述

可以看到,Spring中创建单实例bean调用的是doCreateBean()方法。

第十一步,继续跟进方法调用栈,如下所示,此时,方法的执行已经定位到AbstractAutowireCapableBeanFactory类的doCreateBean()方法中的如下那行代码处了

在这里插入图片描述

在initializeBean()方法里面会调用一系列的后置处理器。

第十二步,继续跟进方法调用栈,如下所示,此时,方法的执行定位到AbstractAutowireCapableBeanFactory类的initializeBean()方法中的如下那行代码处

在这里插入图片描述

小伙伴们需要重点留意一下这个applyBeanPostProcessorsBeforeInitialization()方法。

回过头来我们再来看看AbstractAutowireCapableBeanFactory类的doCreateBean()方法中的如下这行代码。

在这里插入图片描述

没错,在以上initializeBean()方法中调用了后置处理器的逻辑,这我上面已经说到了。小伙伴们需要特别注意一下,在AbstractAutowireCapableBeanFactory类的doCreateBean()方法中,调用initializeBean()方法之前,还调用了一个populateBean()方法,我也在上图中标注出来了。

我们点进去这个populateBean()方法中,看下这个方法到底执行了哪些逻辑,如下所示。
在这里插入图片描述

populateBean()方法同样是AbstractAutowireCapableBeanFactory类中的方法,它里面的代码比较多,但是逻辑非常简单,populateBean()方法做的工作就是为bean的属性赋值。也就是说,在Spring中会先调用populateBean()方法为bean的属性赋好值,然后再调用initializeBean()方法。

接下来,我们好好分析下initializeBean()方法,为了方便,我将Spring中AbstractAutowireCapableBeanFactory类的initializeBean()方法的代码特意提取出来了,如下所示。
在这里插入图片描述

在initializeBean()方法中,调用了invokeInitMethods()方法,代码行如下所示。

this.invokeInitMethods(beanName, wrappedBean, mbd);

invokeInitMethods()方法的作用就是执行初始化方法,这些初始化方法包括我们之前讲的:在XML配置文件的标签中使用init-method属性指定的初始化方法;在@Bean注解中使用initMehod属性指定的方法;使用@PostConstruct注解标注的方法;实现InitializingBean接口的方法等。
在调用invokeInitMethods()方法之前,Spring调用了applyBeanPostProcessorsBeforeInitialization()这个方法,代码行如下所示。

wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);

在调用invokeInitMethods()方法之后,Spring又调用了applyBeanPostProcessorsAfterInitialization()这个方法,如下所示。

wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

这里,我们先来看看applyBeanPostProcessorsBeforeInitialization()方法中具体执行了哪些逻辑,该方法位于AbstractAutowireCapableBeanFactory类中,源码如下所示

在这里插入图片描述

可以看到,在applyBeanPostProcessorsBeforeInitialization()方法中,会遍历所有BeanPostProcessor对象,然后依次执行所有BeanPostProcessor对象的postProcessBeforeInitialization()方法,一旦BeanPostProcessor对象的postProcessBeforeInitialization()方法返回null以后,则后面的BeanPostProcessor对象便不再执行了,而是直接退出for循环。这些都是我们看源码看到的

经过上面的一系列的跟踪源码分析,我们可以将关键代码的调用过程使用如下伪代码表述出来。

populateBean(beanName, mbd, instanceWrapper); // 给bean进行属性赋值
initializeBean(beanName, exposedObject, mbd)
{
	applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	invokeInitMethods(beanName, wrappedBean, mbd); // 执行自定义初始化
	applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

也就是说,在Spring中,调用initializeBean()方法之前,调用了populateBean()方法为bean的属性赋值,为bean的属性赋好值之后,再调用initializeBean()方法进行初始化。

在initializeBean()中,调用自定义的初始化方法(即invokeInitMethods())之前,调用了applyBeanPostProcessorsBeforeInitialization()方法,而在调用自定义的初始化方法之后,又调用了applyBeanPostProcessorsAfterInitialization()方法。至此,整个bean的初始化过程就这样结束了。

6、BeanPostProcessor在Spring底层的使用

我们先来看下BeanPostProcessor接口的源码,如下所示。

在这里插入图片描述

可以看到,在BeanPostProcessor接口中,提供了两个方法:postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法。postProcessBeforeInitialization()方法会在bean初始化之前调用,postProcessAfterInitialization()方法会在bean初始化之后调用。接下来,我们就来分析下BeanPostProcessor接口在Spring中的实现。

接下来,列举几个BeanPostProcessor接口在Spring中的实现类,来让大家更加清晰的理解BeanPostProcessor接口在Spring底层的应用。

ApplicationContextAwareProcessor类

org.springframework.context.support.ApplicationContextAwareProcessor是BeanPostProcessor接口的一个实现类,这个类的作用是可以向组件中注入IOC容器,大致的源码如下所示

在这里插入图片描述

要想使用ApplicationContextAwareProcessor类向组件中注入IOC容器,我们就不得不提Spring中的另一个接口了,即ApplicationContextAware。如果需要向组件中注入IOC容器,那么可以让组件实现ApplicationContextAware接口。

例如,我们创建一个Dog类,使其实现ApplicationContextAware接口,此时,我们需要实现ApplicationContextAware接口中的setApplicationContext()方法,在setApplicationContext()方法中有一个ApplicationContext类型的参数,这个就是IOC容器对象,我们可以在Dog类中定义一个ApplicationContext类型的成员变量,然后在setApplicationContext()方法中为这个成员变量赋值,此时就可以在Dog类中的其他方法中使用ApplicationContext对象了,如下所示。

@Component
public class Dog implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    public Dog() {
        System.out.println("Dog Constructor...");
    }

    // 在对象创建完成并且属性赋值完成之后调用
    @PostConstruct
    public void init() {
        System.out.println("Dog Init...");
    }

    //在容器销毁(移除)对象之前调用
    @PreDestroy
    public void destroy() {
        System.out.println("Dog destroy...");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

这就是BeanPostProcessor在Spring底层的一种使用场景。至于上面的案例代码为何会在setApplicationContext()方法中获取到ApplicationContext对象,这就是ApplicationContextAwareProcessor类的功劳了!

接下来,我们就深入分析下ApplicationContextAwareProcessor类。

我们先来看下ApplicationContextAwareProcessor类中对于postProcessBeforeInitialization()方法的实现,如下所示
在这里插入图片描述

在Bean初始化前,先对当前的Bean进行类型判断,如果当前bean的类型不是EnvironmentAware,不是EmbeddedValueResolverAware,不是ResourceLoaderAware,不是ApplicationEventPublisherAware,不是MessageSourceAware,也不是ApplicationContextAware,那么直接返回bean。如果是上面类型中的一种类型,那么最终会调用invokeAwareInterfaces()方法,并将bean传递给该方法。
接下来继续看invokeAwareInterfaces()方法

在这里插入图片描述

可以看到invokeAwareInterfaces()方法的源码比较简单,就是判断当前bean属于哪种接口类型,然后将bean强转为哪种接口类型的对象,接着调用接口中的方法,将相应的参数传递到接口的方法中。这里,我们在创建Dog类时,实现的是ApplicationContextAware接口,所以,在invokeAwareInterfaces()方法中,会执行相应的逻辑代码

我们可以看到,此时会将this.applicationContext传递到ApplicationContextAware接口的setApplicationContext()方法中。所以,我们在Dog类的setApplicationContext()方法中就可以直接接收ApplicationContext对象了。

BeanValidationPostProcessor类

org.springframework.validation.beanvalidation.BeanValidationPostProcessor类主要是用来为bean进行校验操作的,当我们创建bean,并为bean赋值后,我们可以通过BeanValidationPostProcessor类为bean进行校验操作。BeanValidationPostProcessor类的源码如下所示
在这里插入图片描述

可以看到,在postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法中的主要逻辑都是调用doValidate()方法对bean进行校验,只不过在这两个方法中都会对afterInitialization这个boolean类型的成员变量进行判断,若afterInitialization的值为false,则在postProcessBeforeInitialization()方法中调用doValidate()方法对bean进行校验;若afterInitialization的值为true,则在postProcessAfterInitialization()方法中调用doValidate()方法对bean进行校验。
在这里插入图片描述

InitDestroyAnnotationBeanPostProcessor类

org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor类主要用来处理@PostConstruct注解和@PreDestroy注解。

例如,我们之前创建的Dog类中就使用了@PostConstruct注解和@PreDestroy注解,如下所示

    // 在对象创建完成并且属性赋值完成之后调用
    @PostConstruct
    public void init() {
        System.out.println("Dog Init...");
    }

    //在容器销毁(移除)对象之前调用
    @PreDestroy
    public void destroy() {
        System.out.println("Dog destroy...");
    }

那么,在Dog类中使用了@PostConstruct注解和@PreDestroy注解来标注方法,Spring怎么就知道什么时候执行@PostConstruct注解标注的方法,什么时候执行@PreDestroy注解标注的方法呢?这就要归功于InitDestroyAnnotationBeanPostProcessor类了。

接下来,我们也通过Debug的方式来跟进下代码的执行流程。首先,在Dog类的init()方法上打上一个断点,调试后,发现在进入使用@PostConstruct注解标注的方法之前,Spring调用了InitDestroyAnnotationBeanPostProcessor类的postProcessBeforeInitialization()方法,如下所示。

在这里插入图片描述

在InitDestroyAnnotationBeanPostProcessor类的postProcessBeforeInitialization()方法中,首先会找到bean中有关生命周期的注解,比如@PostConstruct注解等,找到这些注解之后,则将这些信息赋值给LifecycleMetadata类型的变量metadata,之后调用metadata的invokeInitMethods()方法,通过反射来调用标注了@PostConstruct注解的方法。这就是为什么标注了@PostConstruct注解的方法会被Spring执行的原因

AutowiredAnnotationBeanPostProcessor类

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor类主要是用于处理标注了@Autowired注解的变量或方法。

属性赋值

1、@Value注解

Spring中的@Value注解可以为bean中的属性赋值。我们先来看看@Value注解的源码,如下所示。

在这里插入图片描述

从@Value注解的源码中我们可以看出,@Value注解可以标注在字段、方法、参数以及注解上,而且在程序运行期间生效。

@Value注解的使用

不通过配置文件注入属性的情况

  • 注入普通字符串

    @Value("Jack")
    private String username;
    
  • 注入操作系统属性

    @value("#{systemProperties['os.name']}")
    private String systemPropertiesName;
    
  • 注入SpEl表达式结果

    @Value("#{3*3}")
    private double multipeNum;
    
  • 注入其他bean中属性的值

    @Value("#{person.name}")
    private String username;
    
  • 注入文件资源

    @Value("classpath:/config.properties")
    private Resource resourceFile;
    
  • 注入URL资源

    @Value("http:www.baidu.com")
    private Resource url;
    

通过配置文件注入属性的情况

首先,我们可以在项目的src/main/resources目录下新建一个属性文件,例如person.properties,其内容如下

person.nickName=Jack

然后,我们新建一个MainConfigOfPropertyValues配置类,并在该类上使用@PropertySource注解读取外部配置文件中的key/value并保存到运行的环境变量中。

package com.jian.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.meimeixia.bean.Person;

@PropertySource(value={"classpath:/person.properties"})
@Configuration
public class MainConfigOfPropertyValues {

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

加载完外部的配置文件以后,接着我们就可以使用${key}取出配置文件中key所对应的值,并将其注入到bean的属性中了。

package com.meimeixia.bean;

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

public class Person {
	
	@Value("Jack")
	private String name;
	@Value("#{20-2}")
	private Integer age;
	
	@Value("${person.nickName}")
	private String nickName; // 昵称
    ......
}

@Value中#{}和${}的区别

我们在这里提供一个测试属性文件,例如advance_value_inject.properties,大致的内容如下所示。

server.name=server1,server2,server3
author.name=Jack

然后,新建一个AdvanceValueInject类,并在该类上使用@PropertySource注解读取外部属性文件中的key/value并保存到运行的环境变量中,即加载外部的advance_value_inject.properties属性文件。

package com.meimeixia.bean;

import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource(value={"classpath:/advance_value_inject.properties"})
public class AdvanceValueInject {

	// ···
	
}

以上准备工作做好之后,下面我们就来看看${}的用法

${}的用法

{}里面的内容必须符合SpEL表达式,通过@Value(“${spelDefault.value}”)我们可以获取属性文件中对应的值,但是如果属性文件中没有这个属性,那么就会报错。不过,我们可以通过赋予默认值来解决这个问题,如下所示。

@Value("${author.name:July}")
private String name;

上述代码的含义是表示向bean的属性中注入属性文件中的author.name属性所对应的值,如果属性文件中没有author.name这个属性,那么便向bean的属性中注入默认值July

#{}的用法

#{}里面的内容同样也是必须符合SpEL表达式。例如

// SpEL:调用字符串Hello World的concat方法
@Value("#{'Hello World'.concat('!')}")
private String helloWorld;

// SpEL:调用字符串的getBytes方法,然后再调用其length属性
@Value("#{'Hello World'.bytes.length}")
private String helloWorldBytes;

#{}和${}混合使用

${···}#{···}可以混合使用,例如,

// SpEL:传入一个字符串,根据","切分后插入列表中, #{}和${}配合使用时,注意不能反过来${}在外面,而#{}在里面
@Value("#{'${server.name}'.split(',')}")
private List<String> severs;

上面片段的代码的执行顺序:通过${server.name}从属性文件中获取值并进行替换,然后就变成了执行SpEL表达式{'server1,server2,server3'.split(',')}

在上文中#{}在外面,${}在里面可以执行成功,那么反过来是否可以呢?也就是说能否让${}在外面,#{}在里面,就像下面这样呢

// SpEL:注意不能反过来,${}在外面,而#{}在里面,因为这样会执行失败
@Value("${#{'HelloWorld'.concat('_')}}")
private List<String> severs2;

答案是不能。因为Spring执行${}的时机要早于#{},当Spring执行外层的${}时,内部的#{}为空,所以会执行失败!

小结

  • #{···}:用于执行SpEl表达式,并将内容赋值给属性
  • ${···}:主要用于加载外部属性文件中的值
  • ${···}#{···}可以混合使用,但是必须#{}在外面,${}在里面

2、@PropertySource注解

@PropertySource注解是Spring 3.1开始引入的配置类注解。通过@PropertySource注解可以将properties配置文件中的key/value存储到Spring的Environment中,Environment接口提供了方法去读取配置文件中的值,参数是properties配置文件中定义的key值。当然了,也可以使用@Value注解用${}占位符为bean的属性注入值
我们来看一下@PropertySource注解的源代码,如下所示。

在这里插入图片描述

从@PropertySource的源码中可以看出,我们可以通过@PropertySource注解指定多个properties文件,使用的形式如下所示。

@PropertySource(value={"classpath:/person.properties", "classpath:/car.properties"})

在@PropertySource注解的上面还标注了如下的注解信息

@Repeatable(PropertySources.class)

所以我们也可以使用@PropertySources注解来指定properties配置文件

@PropertySources注解

首先,我们也来看下@PropertySources注解的源码,如下所示。

在这里插入图片描述

@PropertySources注解的源码比较简单,只有一个PropertySource[]数组类型的value属性,那我们如何使用@PropertySources注解指定配置文件呢?其实也很简单,使用如下所示的方式就可以了。

@PropertySources(value={
    @PropertySource(value={"classpath:/person.properties"}),
    @PropertySource(value={"classpath:/car.properties"}),
})

@PropertySource注解使用案例

首先,我们在工程的src/main/resources目录下创建一个配置文件,例如person.properties,该文件的内容如下所示。

person.nickName=BabyJack

然后,我们在Person类中新增一个nickName字段,如下所示。

package com.jian.bean;

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

public class Person {
    @Value("Jack")
    private String name;
    private Integer age;
    private String 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;
    }

    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 + '\'' +
                '}';
    }
}

目前,我们并没有为Person类的nickName字段赋值,所以,此时Person类的nickName字段的值为空。我们可以运行PropertyValueTest类中的test01()方法来看下输出结果,如下所示。

在这里插入图片描述

可以看到,Person类的nickName字段的值确实输出了null。

使用XML配置文件方式获取值

如果我们需要在XML配置文件中获取person.properties文件中的值,那么我们首先需要在Spring的XML配置文件中引入context名称空间,并且使用context命名空间导入person.properties文件,之后在bean的属性字段中使用如下方式将person.properties文件中的值注入到Person类的nickName字段上。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:person.properties"/>
    <bean id="person" class="com.jian.bean.Person">
        <property name="name" value="Json"/>
        <property name="age" value="22"/>
        <property name="nickName" value="${person.nickName}"/>
    </bean>
    
</beans>

这样就可以将person.properties文件中的值注入到Person类的nickName字段上了。

然后,我们在PropertyValueTest类中创建一个test02()测试方法,如下所示。

@Test
public void test02(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    Object person = applicationContext.getBean("person");
    System.out.println(person);
}

在这里插入图片描述

使用注解方式获取值

如果我们使用注解的方式,那么该如何做呢?首先,我们需要在MainConfigOfPropertyValues配置类上添加一个@PropertySource注解,如下所示。

package com.jian.config;

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

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

这里使用的@PropertySource(value={"classpath:/person.properties"})注解就相当于XML配置文件中使用的<context:property-placeholder location="classpath:person.properties" />

然后,我们就可以在Person类的nickName字段上使用@Value注解来获取person.properties文件中的值了,如下所示。

public class Person {
    @Value("Jack")
    private String name;
    @Value("#{3*7}")
    private Integer age;
    @Value("${person.nickName}")
    private String nickName;
    //...
}

配置完成后,我们再次运行PropertyValueTest类中的test()方法来看下输出结果,如下所示。

在这里插入图片描述

可以看到,此时Person类的nickName字段已经注入BabyJack这个值了

使用Environment获取值

使用@PropertySource注解读取外部配置文件中的key/value之后,是将其保存到运行的环境变量中了,所以我们也可以通过运行环境来获取外部配置文件中的值。

这里,我们可以稍微修改一下PropertyValueTest类中的test()方法,即在其中添加一段使用Environment获取person.properties文件中的值的代码,如下所示。

@Test
public void test(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValue.class);
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    System.out.println(environment.getProperty("person.nickName"));
    Object person = applicationContext.getBean("person");
    System.out.println(person);
    applicationContext.close();
}

运行test方法测试

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值