前言
最近在代码中两次看到同事使用@PostConstruct的场景, 有些别扭, 但是也不能说不对, 毕竟是在项目初始化的时候, 代码只会执行一次, 下面我挨个举例说明并附上正统用法.
参考代码1
@Configuration
@EnableApolloConfig
public class MqConfig {
@Value("${rabbitMq.returnVisit.result.exchange}")
public String RABBITMQ_RETURNVISIT_RESULT_EXCHANGE;
@Value("${rabbitMq.returnVisit.result.routintKey}")
public String RABBITMQ_RETURNVISIT_RESULT_ROUTING_KEY;
public static final String RETURN_VISIT_RESULT_QUEUE="return.visit.result.queue";
public static String RETURN_VISIT_RESULT_EXCHANGE;
public static String RETURN_VISIT_RESULT_ROUTING_KEY;
@PostConstruct
public void init() {
CONT_RECEIVE_EXCHANGE = this.RABBITMQ_CONT_RECEIVE_EXCHANGE;
WECHAT_CONT_RECEIVE_ROUTING_KEY = this.RABBITMQ_WECHAT_CONT_RECEIVE_ROUTING_KEY;
SF_CONT_RECEIVE_ROUTING_KEY = this.RABBITMQ_SF_CONT_RECEIVE_ROUTING_KEY;
IUDP_CONT_RECEIVE_ROUTING_KEY = this.RABBITMQ_IUDP_CONT_RECEIVE_ROUTING_KEY;
RETURN_VISIT_RESULT_EXCHANGE = this.RABBITMQ_RETURNVISIT_RESULT_EXCHANGE;
RETURN_VISIT_RESULT_ROUTING_KEY = this.RABBITMQ_RETURNVISIT_RESULT_ROUTING_KEY;
}
@Bean
public Queue dsfDataSendQueue() {
return new Queue(DSF_DATA_SEND_QUEUE, true);
}
@Bean
public TopicExchange dsfDataSendTopicExchange() {
return new TopicExchange(RABBITMQ_DSF_SEND_EXCHANGE);
}
@Bean
public Binding dsfDataSendBinding() {
Binding a = BindingBuilder.bind(dsfDataSendQueue()).to(dsfDataSendTopicExchange())
.with(RABBITMQ_DSF_SEND_ROUTING_KEY);
return a;
}
经过合理的推演, 同事1应该是由于spring boot里@Value和@bean执行顺序问题采取了这种写法, apollo获取到的value会慢于rabbitmq的初始化@bean, 通过加@PostConstruct注解提前获取了@Value再执行@Bean, 挺精明的还.
参考代码2
if(inputJson.contains("hsrCIRCByMonth")){
CallToAllMediaServiceImpl callToAllMediaService = new CallToAllMediaServiceImpl();
response = callToAllMediaService.dealHesitateSuccessRateCIRC(inputJson);
}
@Component
public class CallToAllMediaServiceImpl {
@Autowired
protected IDsCallToAllMediaDAO dsCallToAllMediaDAO;
@Autowired
protected ICallToAllMediaDAO callToAllMediaDAO;
@Autowired
protected IRvReportFormDAO reportFormDAO;
@Autowired
protected CallToAllMediaManager callToAllMediaManager;
public static CallToAllMediaServiceImpl callToAllMediaService;
// 初始化返回参数map
private static HashMap<String, Boolean> fieldMap = new HashMap<String, Boolean>() {
{
put("FIELD1", false);
put("FIELD2", false);
put("FIELD3", false);
put("FIELD4", false);
put("FIELD5", false);
put("FIELD6", false);
put("FIELD7", false);
put("FIELD8", false);
put("FIELD9", false);
}
};
// 实际返回参数map
private static HashMap<String, Boolean> fieldsMap = new HashMap<String, Boolean>();
@PostConstruct
public void init(){
callToAllMediaService = this;
callToAllMediaService.callToAllMediaDAO = this.callToAllMediaDAO;
callToAllMediaService.dsCallToAllMediaDAO = this.dsCallToAllMediaDAO;
callToAllMediaService.reportFormDAO = this.reportFormDAO;
callToAllMediaService.callToAllMediaManager = this.callToAllMediaManager;
}
public String hesitateSuccessRateCIRC(String inputJson) {
................
经过推演, 同事2年限较短在写代码时, 习惯于new 对象(), 这是个springboot的项目, 在使用类的时候new了对象, 当然这里不推荐, 有一个问题就是new出来的对象对于@Autowired注入的成员变量并没有再一次的注入, 同事2先是在类首次初始化的时候加了一个static本身, 这个本身是完整的, 之后每次new的对象用@PostConstruct把首次@Autowired注入的成员变量挨个设置给新new的对象, 当然特定的场景是可以的, 并不涉及多线程的并发问题. 话说也算是挺精明的.
正统用法
查百度资料可以看到很多内容, 构造方法 > @Autowired > @PostConstruct
@PostConstruct和@PreDestroy 这两个注解是Java 5引入, 已经完全在Java 11中删除. 使用需要引入jar包
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version></dependency>
PostConstruct注释用于在执行任何初始化时执行依赖注入后需要执行的方法。必须在类投入服务之前调用此方法。所有支持依赖注入的类都必须支持此注释。
JSR-250章。2.5 javax.notation.PostConstruct
PostConstruct注释用于在完成依赖项注入以执行任何初始化之后需要执行的方法。必须在类投入使用之前调用此方法。
所有支持依赖注入的类都必须支持此注释。即使类没有请求注入任何资源,也必须调用使用PostConstruct注释的方法。
只有一个方法可以使用此批注进行批注。
应用PostConstruct注释的方法必须满足以下所有条件:除了拦截器之外,方法绝不能有任何参数,在这种情况下它采用Interceptor规范定义的InvocationContext对象。
在拦截器类上定义的方法必须具有以下签名之一:
void <METHOD>(InvocationContext)Object <METHOD>(InvocationContext)抛出异常注意:
PostConstruct拦截器方法不能抛出应用程序异常,但可以声明它抛出检查异常,包括java.lang.Exception,
如果相同的拦截器方法除了生命周期事件之外插入业务或超时方法。
如果PostConstruct拦截器方法返回一个值,容器将忽略它。
在非拦截器类上定义的方法必须具有以下签名:void <METHOD>()应用PostConstruct的方法可以是public,protected,package private或private。
除应用程序客户端外,该方法绝不能是静态的。
该方法可能是最终的。如果该方法抛出一个未经检查的异常,那么该类绝不能投入使用,除非EJB可以处理异常甚至从它们恢复的EJB
特点:
1、只有一个非静态方法能使用此注解
2、被注解的方法不得有任何参数
3、被注解的方法返回值必须为void
4、被注解方法不得抛出已检查异常
5、此方法只会被执行一次
servlet执行流程
引入别人的一个图:
总结
@PostConstruct注释允许在实例化并执行所有注入之后执行方法的定义。
相当于后构造方法, 需要使用构造方法之后的成员变量来初始化另外一些变量.
@Service
public class BeanA {
@Autowired
private BeanB beanB;
public BeanA() {
System.out.println("这是Bean A 的构造方法");
}
@PostConstruct
private void init() {
System.out.println("需要调用BeanB的方法初始化");
beanB.testB();
}
}