关于Spring切面在@PostConstruct上失效的问题---源码分析

444 阅读2分钟

起因

在我们项目中有部分操作需要在初始化方法/定时任务中插入一段通用逻辑, 当时我采用了切面对相应的被注解的方法进行拓展(@Schedule/@PostConstruct/@RabbitListener).

但在测试时发现切面只对@Schedule和@RabbitListener生效, @PostConstruct并没有织入对应的拓展逻辑.

分析

部分生效说明切面是正常的, 那么问题就出现在@PostConstruct里.

@PostConstruct是属于JSR250的一个拓展规范, 其注释中写到了 "This method MUST be invoked before the class is put into service"方法必须在类投入使用前调用. 这里的在Spring中可以认为是Bean实例.

目前到这里就比较清晰了, 切面对@PostConstruct注解的方法失效的原因很有可能是在初始化方法执行时切面还未投入使用 (因为切面也属于Bean实例)

源码分析

@PostConstruct实现源码分析

Spring对于JSR250规范定义的注解处理: org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.

在这个后处理器中定义了对 @PostConstruct/@PreDestroy 注解的实现和定义(@Resource的实现也是在此处):

声明使用的初始化注解和销毁注解

/**
 * Create a new CommonAnnotationBeanPostProcessor,
 * with the init and destroy annotation types set to
 * {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy},
 * respectively.
 */
public CommonAnnotationBeanPostProcessor() {
    setOrder(Ordered.LOWEST_PRECEDENCE - 3);
    setInitAnnotationType(PostConstruct.class);
    setDestroyAnnotationType(PreDestroy.class);
    ignoreResourceType("javax.xml.ws.WebServiceContext");

    // java.naming module present on JDK 9+?
    if (jndiPresent) {
       this.jndiFactory = new SimpleJndiBeanFactory();
    }
}

@PostConstruct/@PreDestroy的具体实现在 CommonAnnotationBeanPostProcessor 父类: InitDestroyAnnotationBeanPostProcessor中.

InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization

// BeanPostProcessor
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                // 解析Bean中带有 初始化/销毁 注解的元数据
		LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
		try {
                        // 尝试调用初始化方法(详见下一代码块)
			metadata.invokeInitMethods(bean, beanName);
		}
		catch (InvocationTargetException ex) {
			throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
		}
		return bean;
	}

InitDestroyAnnotationBeanPostProcessor内部类: LifecycleMetadata

	/**
	 * Class representing information about annotated init and destroy methods.
	 */
	private class LifecycleMetadata {

		private final Class<?> targetClass;
                
                // 解析后的初始化方法
		private final Collection<LifecycleElement> initMethods;

                // 解析后的销毁方法
		private final Collection<LifecycleElement> destroyMethods;
                
                // 经过 检查/注册 后的初始化方法
		@Nullable
		private volatile Set<LifecycleElement> checkedInitMethods;

                // 经过 检查/注册 后的销毁
		@Nullable
		private volatile Set<LifecycleElement> checkedDestroyMethods;


		public void invokeInitMethods(Object target, String beanName) throws Throwable {
			Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods;
			Collection<LifecycleElement> initMethodsToIterate =
					(checkedInitMethods != null ? checkedInitMethods : this.initMethods);
			if (!initMethodsToIterate.isEmpty()) {
				for (LifecycleElement element : initMethodsToIterate) {
					if (logger.isTraceEnabled()) {
						logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod());
					}
					element.invoke(target);
				}
			}
		}
}

Spring AOP分析

Spring源码分析之AOP从解析到调用

Spring AOP的代理对象是在BeanPostProcessor#postProcessBeforeInitialization进行解析的, 而代理对象的创建在BeanPostProcessor#postProcessAfterInitialization

那么到这里就能得出为什么切面对于@PostConstruct注解的方向无法生效的原因了.