初探Java中的@PostConstruct注解

问题

之前在做后端项目遇到一个奇怪的问题,我装配到Spring容器中的一个Bean在另外一个类中无法被注入,该Bean的类型如下:

 

java

复制代码

@Component @Data public class FeishuCrawlerConfig{...}

我使用@Component注解将其装配到Spring容器中,然后在另外一个类中将其自动注入,格式如下:

 

java

复制代码

@Component public class FeishuTenantTokenEntity { @Autowired private FeishuCrawlerConfig feishuConfig; ... }

正常来讲feishuConfig这个变量会被自动注入完成初始化,但是后面我在使用这个变量时却抛出了NPE。后来经过跟同事的讨论才发现我是在FeishuTenantTokenEntity的构造方法中调用到了feishuConfig的方法,导致了NPE的产生。构造方法如下:

 

java

复制代码

public FeishuTenantTokenEntity() IOException { // refreshTokenValue()方法中使用到feishuConfig的方法 this.tokenValue = refreshTokenValue(); this.accessTime = System.currentTimeMillis(); }

产生这个问题的原因是Spring中的Bean在初始化时,会先执行其构造方法,再注入@Autowired注解标注的其他Bean。我们的代码中在构造方法中就使用到了@Autowired注入的feishuConfig对象,这时它还没有初始化完成,自然会抛出NPE。这个问题的解决方法就是使用@PostConstruct注解标注的方法替代FeishuTenantTokenEntity的构造方法,该方法如下:

 

java

复制代码

@PostConstruct public void init() throws IOException { this.tokenValue = refreshTokenValue(); this.accessTime = System.currentTimeMillis(); }

Spring在初始化Bean时,会在注入@Autowired注解标注的Bean后执行@PostConstruct注解标注的方法。我们在init()方法中使用feishuConfig的方法显然是没问题的,并且它还能替代构造方法的作用。Spring中Bean初始化的执行顺序是构造方法>依赖注入( @Autowired )> @PostConstruct标注的方法

@PostConstruct注解

讲到这里,不得不细说一下@PostConstruct注解。

 

java

复制代码

/** * The PostConstruct annotation is used on a method that needs to be executed * after dependency injection is done to perform any initialization. This * method MUST be invoked before the class is put into service. This * annotation MUST be supported on all classes that support dependenc * injection. The method annotated with PostConstruct MUST be invoked even * if the class does not request any resources to be injected. Only one * method can be annotated with this annotation. The method on which the * PostConstruct annotation is applied MUST fulfill all of the following * criteria: * <p> * <ul> * <li>The method MUST NOT have any parameters except in the case of * interceptors in which case it takes an InvocationContext object as * defined by the Interceptors specification.</li> * <li>The method defined on an interceptor class MUST HAVE one of the * following signatures: * <p> * void &#060;METHOD&#062;(InvocationContext) * <p> * Object &#060;METHOD&#062;(InvocationContext) throws Exception * <p> * <i>Note: A PostConstruct interceptor method must not throw application * exceptions, but it may be declared to throw checked exceptions including * the java.lang.Exception if the same interceptor method interposes on * business or timeout methods in addition to lifecycle events. If a * PostConstruct interceptor method returns a value, it is ignored by * the container.</i> * </li> * <li>The method defined on a non-interceptor class MUST HAVE the * following signature: * <p> * void &#060;METHOD&#062;() * </li> * <li>The method on which PostConstruct is applied MAY be public, protected, * package private or private.</li> * <li>The method MUST NOT be static except for the application client.</li> * <li>The method MAY be final.</li> * <li>If the method throws an unchecked exception the class MUST NOT be put into * service except in the case of EJBs where the EJB can handle exceptions and * even recover from them.</li></ul> * @since Common Annotations 1.0 * @see javax.annotation.PreDestroy * @see javax.annotation.Resource */ @Documented @Retention (RUNTIME) @Target(METHOD) public @interface PostConstruct { }

这是Java官方对@PostConstruct注解的注释文档,可以看到@PostConstruct注解是用于初始化方法上,该方法在依赖注入完成后执行。Java对被@PostConstruct标注的方法做出了如下限制:

  • 除拦截器方法外,该方法不能有任何参数
  • @PostConstruct标注的拦截器方法不能抛出application异常
  • 该方法可以被public,protected,package private和private修饰
  • 该方法不能为静态的,但是可以被final修饰 简而言之,如果你的类中的构造方法需要使用到依赖注入的变量,你可以用@PostConstruct标注的方法来替代构造方法完成初始化。

Spring如何实现 @PostConstruct注解

我们知道在Spring中,Bean的初始化一般分为实例化,属性赋值,初始化和销毁这四个过程。在实例化阶段的前后,InstantiationAwareBeanPostProcessor接口的postProcessBeforeInstantiation和postProcessAfterInstantiation方法会被调用。在初始化阶段的前后,BeanPostProcessor接口的postProcessBeforeInitialization和postProcessAfterInitialization方法会被调用。开发者也可以继承这些接口拓展功能。如下图所示:

在这四个过程中间,Bean的依赖注入发生在属性赋值这个阶段。Spring会在postProcessBeforeInstantiation方法中也就是依赖注入完成之后调用@PostConstruct注解标注的方法,完成Bean的部分初始化工作。 Spring的具体做法就是在创建Bean时,会将它里面被@PostConstruct注解标注的方法保存到Bean的元数据中,在后面调用postProcessBeforeInstantiation方法时,会利用反射调用Bean的元数据中被 @PostConstruct注解标注的方法,从而完成部分初始化工作。感兴趣的同学可以看看源码。

注意

Java官方已在Java 9中弃用了@PostConstruct注解,并在Java 11中删除了@PostConstruct注解。 实现InitializingBean接口并重写其中的afterPropertiesSet方法也可以实现@PostConstruct注解相同的功能。

 

java

复制代码

/** * Interface to be implemented by beans that need to react once all their properties * have been set by a {@link BeanFactory}: e.g. to perform custom initialization, * or merely to check that all mandatory properties have been set. * * <p>An alternative to implementing {@code InitializingBean} is specifying a custom * init method, for example in an XML bean definition. For a list of all bean * lifecycle methods, see the {@link BeanFactory BeanFactory javadocs}. * * @author Rod Johnson * @author Juergen Hoeller * @see DisposableBean * @see org.springframework.beans.factory.config.BeanDefinition#getPropertyValues() * @see org.springframework.beans.factory.support.AbstractBeanDefinition#getInitMethodName() */ public interface InitializingBean { /** * Invoked by the containing {@code BeanFactory} after it has set all bean properties * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc. * <p>This method allows the bean instance to perform validation of its overall * configuration and final initialization when all bean properties have been set. * @throws Exception in the event of misconfiguration (such as failure to set an * essential property) or if initialization fails for any other reason */ void afterPropertiesSet() throws Exception; }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值