PostConstruct注解原理说明

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PostConstruct注解原理说明相关的知识,希望对你有一定的参考价值。

参考技术A 首先我们知道这个注解的作用是:在类被初始化的时候 会执行被@PostConstruct注解的方法。
那它到底是怎么工作的,什么时候被执行,都有什么限制规则,会引起什么问题呢 ?
本着一切都要验证的原则,开始解剖,但是从哪里开始下刀呢?既然是类的初始化,自然是想到了BeanFactory这个庞大的工厂,那我们就从spring初始化bean的入口开刀吧。

bean被初始化的过程了解过spring运行原理的应该都知道了,这里简单说明一下:

首先我们看BeanFactory,随便找一个实现类AbstractAutowireCapableBeanFactory进去

我们知道bean被实例化有两种方式,一种是注入的时候就实例化,一种是第一次用的时候才实例化(懒加载)。先看autowireBean方法

到这里可以看到,在初始化bean的时候,先循环所有的BeanPostProcessor执行postProcessBeforeInitialization 方法;再调用 bean的初始化;最后执行applyBeanPostProcessorsAfterInitialization方法。而我们的@PostConstruct就是在第一步中的一个 BeanPostProcessor 里处理的。

我们进到BeanPostProcessor里面可以看到它有很多实现。

而位于spring-beans包下的org.springframework.beans.factory.annotaion.InitDestroyAnnotationBeanPostProcessor成功的引起了我们的怀疑,那就先进去看看吧。

那么 initAnnotationType 都有哪些呢

CommonAnnotationBeanPostProcessor 是 继承自InitDestroyAnnotationBeanPostProcessor的,到这里,我们可以知道。跟我们前面的猜测一样,解析过程是通过反射来获取@PostConstruct注解的方法,并放到initMethods集合里面去。然后再通过反射调用这个集合里面的所有方法,完成初始化行为的。

最后再通过PostConstruct注解类源码看看它的使用注意事项:

至此,针对@PostConstruct总结如下:
1.作用在类的方法上,在依赖注入完成之后初始化方法,这个方法是在类被放入服务之前被调用,调用顺序是 构造函数->依赖注入->PostConstruct
2.所有支持依赖注入的类都要支持这个方法。
3.只有一个方法可以加这个注解,虽然PostConstruct官方的类上有这个描述,但真实运行效果上看的话,可以给多个方法加,而且都可以被执行。
4.被PostContruct修饰的方法要满足以下几点:
-方法不能有任何参数,除非是 拦截器中的 InvocationContext对象。
-方法不能是静态的,除非是启动类。
-方法是final的, 不能被重写。
-如果方法对应的类是容器中的类,不要抛出UncheckedException

简单介绍@PostConstruct@PreDestroy@DependsOn@Order等注解的作用及嵌套使用时优先级问题

简单介绍@PostConstruct、@PreDestroy、@DependsOn、@Order等注解的作用及嵌套使用时优先级问题

1、@PostConstruct和@PreDestroy注解

说明:@PostConstruct注解好多人以为是Spring提供的,其实是Java自己的注解。

从JavaEE5规范开始,Servlet中增加了两个影响Servlet生命周期的注解,@PostConstruct@PreDestroy这两个注解被用来修饰一个非静态的void()方法,并且被修饰的方法不能抛出异常。

被@PostContruct注解的方法:

  • 会在服务器加载Servlet的时候运行,并且只会被服务器执行一次,具体运行时期是在构造函数执行之后,init()初始化方法之前。

  • 通常我们会在Spring框架中使用@postConstruct注解,该注解的方法在整个Bean初始化中的顺序:Constructor(构造方法)->@Autowired(依赖注入)->@PostConstruct(注释的方法)

  • 该注解的用法:

    @PostConstruct
    public void method()
    
  • 或者:

    public @PostConstruct	 void method()
    

被@PreDestroy修饰的方法:

  • 会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法,会在destroy()方法之后运行,在Servlet被彻底卸载之前。

执行大致流程图:

被注解的Servlet生命周期:

  • 需要注意的是,注解会多多少少地影响到服务器的启动速度。服务器在启动时候会遍历Web 应用的WEB-INF/classes下的所有class文件与WEB-INF/lib下的所有jar文件,以检查哪些类使用了注解。

  • 如果应用程序中没有 使用任何注解,可以在Web.xml中设置metadata-complete属性为true。

  • 支持@PostConstruct和@PreDestroy注解的服务器需要支持Servlet2.5规范,Tomcat5.x仅支持Servlet2.4规范。

@PostContruct注解的方法,Spring中如何发现?

其实在Bean依赖注入也会经常用到,比如所有这样一个场景:

要将对象B注入到对象A,那么首先就必须得生成对象A和对象B,才能执行注入。

  • 如果一个类A中有个成员变量b被@Autowried注解,那么@Autowired注入是发生在A的构造方法执行完之后的。
  • 如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。

实例演示:

A类:

@Component
public class A 

    @Autowired
    private B b;

    public A()
        System.out.println("执行A的构造函数,此时b还未被注入,b=" + b);
    

    @PostConstruct
    public void init()
        System.out.println("@PostConstruct注解此方法,会在对象b注入完成之后自动调用此方法,b=" + b);
    

B类:

@Component
public class B 
    public B()
        System.out.println("执行B的构造函数!");
    

直接运行Spring容器,查看结果:

执行A的构造函数,此时b还未被注入,b=null
执行B的构造函数!
@PostConstruct注解此方法,会在对象b注入完成之后自动调用此方法,b=cn.wbs.test.B@265adfad

2、@DependsOn注解

该注解的作用:

  • 具有依赖关系。
  • 假如在Test02类上加上@DependsOn(value = "test01"),那么就说明Test02在加载时,要依赖于Test01类,Spring IOC 容器会优先加载Test01,然后再加载Test02类。

参考文章:https://blog.csdn.net/weixin_43591980/article/details/121547379?spm=1001.2014.3001.5501

实例演示:假设现在有2个类Test01、Test02,需要交给Spring IOC容器托管:

  • 静态变量的属性值需要通过Spring容器赋值,值(hello和world)定义在application.properties中。
  • 注意:@Value注解不可以给静态变量注入属性值 (否则获取的注入结果为null)。
  • 所以需要再setter方法上标注注入值,setter方法也是不可以加static关键字的
@Component
public class Test01 

    public static String HELLO;

    public static String WORLD;


    @Value("$spring.test.hello")
    public void setHELLO(String HELLO) 
        Test01.HELLO = HELLO;
    

    @Value("$spring.test.world")
    public void setWORLD(String WORLD) 
        Test01.WORLD = WORLD;
    

@Component
public class Test02 

    @PostConstruct
    public  void init()
        
    
    
    public Test02()
        
    

业务需求:我需要在Test02的无参构造方法加载时,控制台打印Test01类中的HELLO静态变量值,然后在 init()方法执行时,控制台打印Test01类中的WORLD静态变量值。

最初的简单想法如下:

@Component
public class Test02 

    @PostConstruct
    public  void init()
        System.out.println("2:" + Test01.HELLO);
    

    public Test02()
        System.out.println("1:" + Test01.WORLD);
    

此时运行Spring容器,查看控制台结果:

1null
2null

为什么会出现这种情况呢?原因就是因为我们在Test02类中调用Test01类中的静态变量时,Test01类即使是和Test02类同时注入Spring容器中,微观上可以认为是同步的,所以在Test02类去调用Test01类中的静态变量时,HELLOWORLD的值还没有被@Value注解加载注入,所以会打印null值。

解决办法:就是使用@DependOn(value = “test01”)注解!

@Component
@DependsOn(value = "test01")
public class Test02 

    @PostConstruct
    public  void init()
        System.out.println("2:" + Test01.HELLO);
    

    public Test02()
        System.out.println("1:" + Test01.WORLD);
    

再次运行Spring容器,查看控制台结果:

#这样值就有了,意思是运行Test02类时,知道Test02中调用了Test01类的资源了,所以先去加载Test011:WORLD
2:HELLO

3、@Order注解

  • @Order注解的作用是定义Spring IOC容器中Bean的执行顺序的优先级。

实例演示:

@Component
@Order(1)
public class Test01 
   System.out.println("执行Test01类");


@Component
@Order(2)
public class Test02 
   System.out.println("执行Test02类");

运行spring容器,查看控制台结果:

执行Test01类
执行Test02

如上述代码所示,通过@Order注解定义优先级,2个Bean对象从IOC容器中的加载顺序为:Test01、Test02,实际上上述演示的业务需求使用@Order注解也可以实现。

以上是关于PostConstruct注解原理说明的主要内容,如果未能解决你的问题,请参考以下文章

@PostConstruct注解

@PostConstruct注解介绍

@PostConstruct和@PreConstruct注解

@PostConstruct与@PreConstruct注解

@PostConstruct方法的使用以及原理,@Component+@PostConstruct方法将一个方法完成初始化操作

Spring@PostConstruct和@PreDestroy注解详解