简介
Java EE5 引入了@PostConstruct和@PreDestroy这两个作用于类的生命周期的注解,实现类执行构造函数之后和销毁之前的自定义操作。此文主要说明@PostConstruct。
API使用说明
以下为@PostConstruct的API使用说明:
PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用 PostConstruct 注释的方法也必须被调用。只有一个方法可以用此注释进行注释。应用 PostConstruct 注释的方法必须遵守以下所有标准:该方法不得有任何参数,除非是在 EJB 拦截器 (interceptor) 的情况下,根据 EJB 规范的定义,在这种情况下它将带有一个 InvocationContext 对象 ;该方法的返回类型必须为 void;该方法不得抛出已检查异常;应用 PostConstruct 的方法可以是 public、protected、package private 或 private;除了应用程序客户端之外,该方法不能是 static;该方法可以是 final;如果该方法抛出未检查异常,那么不得将类放入服务中,除非是能够处理异常并可从中恢复的 EJB。
总结为一下几点:
- 只有一个方法可以使用此注释进行注解;
- 被注解方法不得有任何参数;
- 被注解方法返回值为void;
- 被注解方法不得抛出已检查异常;
- 被注解方法需是非静态方法;
- 此方法只会被执行一次;
实例
基于Spring boot编写的可执行方法见github:https://github.com/HappySecondBrother/example UserService方法(提供缓存数据):
package com.secbro.service;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author 二师兄
* @date 2016/8/10
*/
@Service
public class UserService {
public List<String> getUser(){
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
return list;
}
}
BusinessService方法,通过@PostConstruct调用UserService:
package com.secbro.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* @author 二师兄
* @date 2016/8/10
*/
@Service
public class BusinessService {
@Autowired
private UserService userService;
private List<String> list = null;
/**
* 构造方法执行之后,调用此方法
*/
@PostConstruct
public void init(){
System.out.println("@PostConstruct方法被调用");
// 实例化类之前缓存获得用户信息
List<String> list = userService.getUser();
this.list = list;
if(list != null && !list.isEmpty()){
for(String user : list){
System.out.println("用户:" + user);
}
}
}
public BusinessService(){
System.out.println("构造方法被调用");
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
执行结果:
构造方法被调用
@PostConstruct方法被调用
用户:张三
用户:李四
项目应用
在项目中@PostConstruct的应用场景之一是在初始化Servlet时加载一些缓存数据等。
Servlet执行流程图
这2个注解加入Servlet实现类中,则Servlet实现类的执行流程图为:
在Servlet实现类的实例化过程中,@PostConstruct注释的方法,会在构造方法之后,init方法之前进行调用。
程序实践
web.xml
1 <!-- @PostConstruct和@PreDestroy注解 --> 2 <servlet> 3 <servlet-name>AnnotationServlet</servlet-name> 4 <servlet-class>com.servlet.AnnotationServlet</servlet-class> 5 </servlet> 6 <servlet-mapping> 7 <servlet-name>AnnotationServlet</servlet-name> 8 <url-pattern>/servlet/AnnotationServlet</url-pattern> 9 </servlet-mapping>
AnnotationServlet
1 package com.servlet; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 import java.sql.Time; 6 import java.text.SimpleDateFormat; 7 import java.util.Date; 8 9 import javax.annotation.PostConstruct; 10 import javax.annotation.PreDestroy; 11 import javax.servlet.ServletException; 12 import javax.servlet.http.HttpServlet; 13 import javax.servlet.http.HttpServletRequest; 14 import javax.servlet.http.HttpServletResponse; 15 16 public class AnnotationServlet extends HttpServlet { 17 SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");//设置日期格式,精确到毫秒 18 19 public AnnotationServlet(){ 20 System.out.println("时间:"+df.format(new Date())+"执行构造函数..."); 21 } 22 23 public void destroy() { 24 this.log("时间:"+df.format(new Date())+"执行destroy()方法..."); 25 //super.destroy(); // Just puts "destroy" string in log 26 // Put your code here 27 } 28 29 @PostConstruct 30 public void someMethod(){ 31 //this.log("执行@PostConstruct修饰的someMethod()方法...");//注意:这样会出错 32 System.out.println("时间:"+df.format(new Date())+"执行@PostConstruct修饰的someMethod()方法..."); 33 } 34 35 @PreDestroy 36 public void otherMethod(){ 37 System.out.println("时间:"+df.format(new Date())+"执行@PreDestroy修饰的otherMethod()方法..."); 38 } 39 40 public void doGet(HttpServletRequest request, HttpServletResponse response) 41 throws ServletException, IOException { 42 this.log("时间:"+df.format(new Date())+"执行doGet()方法..."); 43 } 44 45 public void init() throws ServletException { 46 // Put your code here 47 this.log("时间:"+df.format(new Date())+"执行init()方法..."); 48 } 49 50 protected void service(HttpServletRequest request, HttpServletResponse response) 51 throws ServletException, IOException{ 52 this.log("时间:"+df.format(new Date())+"执行service()方法..."); 53 super.service(request, response); 54 } 55 56 }
运行结果:
应用场景之二是与spring结合使用,通过@PostConstruct 和 @PreDestroy 方法 实现bean初始化之后和bean销毁之前进行的操作。
下面演示通过 @PostConstruct 和 @PreDestory
1:定义相关的实现类:
- package com.myapp.core.annotation.init;
- import javax.annotation.PostConstruct;
- import javax.annotation.PreDestroy;
- public class PersonService {
- private String message;
- public String getMessage() {
- return message;
- }
- public void setMessage(String message) {
- this.message = message;
- }
- @PostConstruct
- public void init(){
- System.out.println("I'm init method using @PostConstrut...."+message);
- }
- @PreDestroy
- public void dostory(){
- System.out.println("I'm destory method using @PreDestroy....."+message);
- }
- }
- <?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
- http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.1.xsd">
- <!-- <context:component-scan base-package="com.myapp.core.jsr330"/> -->
- <context:annotation-config />
- <bean id="personService" class="com.myapp.core.annotation.init.PersonService">
- <property name="message" value="123"></property>
- </bean>
- </beans>
测试类:
- package com.myapp.core.annotation.init;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class MainTest {
- public static void main(String[] args) {
- ApplicationContext context = new ClassPathXmlApplicationContext("resource/annotation.xml");
- PersonService personService = (PersonService)context.getBean("personService");
- personService.dostory();
- }
- }
I'm init method using @PostConstrut....123
I'm destory method using @PreDestroy.....123
spring中Constructor、@Autowired、@PostConstruct的顺序
其实从依赖注入的字面意思就可以知道,要将对象p注入到对象a,那么首先就必须得生成对象p与对象a,才能执行注入。所以,如果一个类A中有个成员变量p被@Autowired注解,那么@Autowired注入是发生在A的构造方法执行完之后的。
如果想在生成对象时候完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
Constructor >> @Autowired >> @PostConstruct
举个例子:
- public Class AAA {
- @Autowired
- private BBB b;
- public AAA() {
- System.out.println("此时b还未被注入: b = " + b);
- }
- @PostConstruct
- private void init() {
- System.out.println("@PostConstruct将在依赖注入完成后被自动调用: b = " + b);
- }
- }
为什么我应该使用@PostConstruct来代替常规的构造函数执行初始化操作?
In a managed bean, @PostConstruct is called after the regular Java object constructor.
Why would I use @PostConstruct to initialize by bean, instead of the regular constructor itself?
- because when the constructor is called, the bean is not yet initialized - i.e. no dependencies are injected. In the @PostConstruct method the bean is fully initialized and you can use the dependencies.
- because this is the contract that guarantees that this method will be invoked only once in the bean lifecycle. It may happen (though unlikely) that a bean is instantiated multiple times by the container in its internal working, but it guarantees that @PostConstruct will be invoked only once.
in a constructor, the injection of the dependencies has not yet occurred.
In case someone still have doubts about what this means, this is a real world example just happened to me:
public class Foo {
@Inject
Logger LOG;
@PostConstruct
public void fooInit(){
LOG.info("This will be printed; LOG has already been injected");
}
public Foo() {
LOG.info("This will NOT be printed, LOG is still null");
// NullPointerException will be thrown here
}
}
Hope that helps.
换言之,还是那句话,在执行构造函数的时候,依赖还没有注入。如果你执行的初始化操作需要使用依赖,你就应该使用@PostConstruct。参考:注解@PostConstruct与@PreDestroy详解及实例
Java开发之@PostConstruct和@PreConstruct注解
通过javax @PostConstruct 和 @PreDestroy 方法 实现bean初始化之后和bean销毁之前进行的操作
spring中Constructor、@Autowired、@PostConstruct的顺序
Why would I use @PostConstruct to initialize by bean, instead of the regular constructor itself?
The other answers, especially @Bozho's one, already explained the main problem (among the others):
in a constructor, the injection of the dependencies has not yet occurred.
In case someone still have doubts about what this means, this is a real world example just happened to me:
public class Foo {
@Inject
Logger LOG;
@PostConstruct
public void fooInit(){
LOG.info("This will be printed; LOG has already been injected");
}
public Foo() {
LOG.info("This will NOT be printed, LOG is still null");
// NullPointerException will be thrown here
}
}
Hope that helps.