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、注解介绍

进入这个注解注解上方有这样一段注释

CommonsMultipartResolver 注解配置 poststruct注解_spring boot


注释翻译后是这样子的:

CommonsMultipartResolver 注解配置 poststruct注解_spring_02


从注解所处的包中可以看出这个注解还是属于java拓展包定义的注解,同时可以从他的注释中获取以下信息:

  • 注解所处方法调用时机:完成依赖注入以执行任何初始化之后,在类投入服务之前调用
  • 所有支持依赖注入的类都要支持此方法。这个注解是在javax.annotation包下的,也就是java拓展包定义的注解,并不是spring定义的,但至于为什么不在java包下,是因为java语言的元老们认为这个东西并不是java核心需要的工具,因此就放到扩展包里(javax中的x就是extension的意思),而spring是支持依赖注入的,因此spring必须要自己来实现@PostConstruct的功能。
  • 只能有一个方法可以使用这个注解
    然后注释下边还有一些条件:

CommonsMultipartResolver 注解配置 poststruct注解_spring boot_03


翻译的有些潦草,翻译下来是这样子的:

CommonsMultipartResolver 注解配置 poststruct注解_spring_04


总体意思就是:

  • 方法不能有任何参数,除了拦截器,返回值要为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,就是下边这些发现

CommonsMultipartResolver 注解配置 poststruct注解_java_05


顺着这些方法发现整体就是采用反射的方式进行PostConstruct标注方法的调用