1、背景
想学习PostConstruct的注解的原因是在spring代码中犯了这样一个错误
@Component
public class A{
@Autowired
private B b;
public static <T> T testB(String test) {
b.method();
return test;
}
}
由于上边的testB方法是一个静态方法,所以可以直接调用A.testB,但是testB里边用到了b.method(),这个b是通过spring注入进去的,在真正的执行过程会报b的空指针异常,这是因为静态方法加载时b还没有注入,所以调用静态方法导致仍使用加载静态方法时的状态,从而导致空指针异常,为了解决,改成以下代码:
@Component
public class A{
@Autowired
private B b;
private static A a;
@PostConstruct
public void init(){
a = this;
}
public static <T> T testB(String test) {
a.b.method();
return test;
}
}
用了上边这种方法,我们就可以在静态方法中调用spring注入的对象了,之后就可以正常运行了,可以猜想以下这个注解的作用,首先这个init方法是将A的实例化对象赋给静态变量a,然后这个a就可以调用注入后的成员b了,那么这个注解标注的方法的执行时机肯定是在实例化后,同时要在@Autowired注入后执行,然后就对猜测进行验证。
2、PostConstruct注解原理
2.1、注解介绍
进入这个注解注解上方有这样一段注释
注释翻译后是这样子的:
从注解所处的包中可以看出这个注解还是属于java拓展包定义的注解,同时可以从他的注释中获取以下信息:
- 注解所处方法调用时机:完成依赖注入以执行任何初始化之后,在类投入服务之前调用
- 所有支持依赖注入的类都要支持此方法。这个注解是在javax.annotation包下的,也就是java拓展包定义的注解,并不是spring定义的,但至于为什么不在java包下,是因为java语言的元老们认为这个东西并不是java核心需要的工具,因此就放到扩展包里(javax中的x就是extension的意思),而spring是支持依赖注入的,因此spring必须要自己来实现@PostConstruct的功能。
- 只能有一个方法可以使用这个注解
然后注释下边还有一些条件:
翻译的有些潦草,翻译下来是这样子的:
总体意思就是:
- 方法不能有任何参数,除了拦截器,返回值要为void,如果有返回值将会被忽略
- 方法可以是任何权限修饰符修饰
- 不能是static,可以是final
综上所述,在spring项目中,在一个bean的初始化过程中,方法执行先后顺序为
Constructor > @Autowired > @PostConstruct
这个注解就解决了开头的问题,同时也是在初始化时就可以使用依赖组件
2.2、spring对于PostConstruct的实现
CommonAnnotationBeanPostProcessor这个BeanPostProcessor通过继承InitDestroyAnnotationBeanPostProcessor对@javax.annotation.PostConstruct和@javax.annotation.PreDestroy注解的支持。
所以第一步先找到spring源码中的CommonAnnotationBeanPostProcessor这个类
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
ignoreResourceType("javax.xml.ws.WebServiceContext");
}
setInitAnnotationType(PostConstruct.class);这个方法就是将当前的初始化注解设置为PostConstruct,这个是调用了父类InitDestroyAnnotationBeanPostProcessor的方法
public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
this.initAnnotationType = initAnnotationType;
}
然后看是哪个地方使用了这个变量,发现只有一个方法使用了
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {
return this.emptyLifecycleMetadata;
}
List<LifecycleElement> initMethods = new ArrayList<>();
List<LifecycleElement> destroyMethods = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<LifecycleElement> currInitMethods = new ArrayList<>();
final List<LifecycleElement> currDestroyMethods = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
currDestroyMethods.add(new LifecycleElement(method));
if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
});
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :
new LifecycleMetadata(clazz, initMethods, destroyMethods));
}
整体看来,这个方法是做了一个循环,查找当前类和父类的所有有PostConstruct注解的方法,然后放到一个list中,由此可见,spring对PostConstruct注解的实现并没有严格按照JDK中介绍的只能有一个方法可以使用这个注解,所以即便有多个也不会抛异常,关于多个PostConstruct注解的顺序性,在doWithLocalMethods时就被打乱了,它强调了Class类不能保证getDeclaredMethods()的顺序,所以说关于PostConstruct注解标注方法的顺序性应该是乱序的
然后看LifecycleElement的初始化
public LifecycleElement(Method method) {
if (method.getParameterCount() != 0) {
throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method);
}
this.method = method;
this.identifier = (Modifier.isPrivate(method.getModifiers()) ?
ClassUtils.getQualifiedMethodName(method) : method.getName());
}
从LifecycleElement可以看出如果PostConstruct注解标注的方法参数不为空,就会抛出IllegalStateException异常,也是印证了开头说的方法参数不能为空
然后看下是怎么执行的,也就是顺藤摸瓜,看哪些地方调用了buildLifecycleMetadata,就是下边这些发现
顺着这些方法发现整体就是采用反射的方式进行PostConstruct标注方法的调用