AspectJ @Around增强静态初始化时修改clinit上下文引发异常问题咨询
问题:AspectJ环绕静态初始化器引发IllegalAccessError
这是针对AspectJ环绕静态初始化器问题的跟进提问,此前的方案能完成切面编织,但运行时出现大量异常。
切面代码
package com.acme.aspects.exceptions; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Aspect public class StaticInitializerExceptions { private static final Logger LOGGER = LoggerFactory.getLogger(StaticInitializerExceptions.class); @Around("staticinitialization(!is(InterfaceType)) && !within(MyAspect) && !within(com.acme.aspects.exceptions.StaticInitializerExceptions)") public Object logStaticInitializerException(ProceedingJoinPoint joinPoint) throws Throwable { try { return joinPoint.proceed(); } catch (Throwable t) { LOGGER.error("STATIC INITIALIZER EXCEPTION CAUGHT", t); throw t; } } }
编写该切面的目的是排查初始化器中未知的NoClassDefFoundError问题,但切面编织后,程序中几乎所有静态初始化器都抛出异常——原因是目标类的<clinit>代码被移到了切面生成的辅助方法中,导致修改static final字段时违反JVM规则。
示例受影响类
public class AcmeContextListener implements ServletContextListener { private static final Logger LOGGER = LoggerFactory.getLogger(AcmeContextListener.class); // ... 其他代码 }
错误信息
<ri > <an > - STATIC INITIALIZER EXCEPTION CAUGHT at com.acme.listener.AcmeContextListener.clinit$_aroundBody0(AcmeContextListener.java:20) ~[_servlets.jar:25.6.0.0] at com.acme.listener.AcmeContextListener.clinit$_aroundBody1$advice(AcmeContextListener.java:17) ~[_servlets.jar:25.6.0.0] java.lang.IllegalAccessError: Update to static final field com.acme.listener.AcmeContextListener.LOGGER attempted from a different method (clinit$_aroundBody0) than the initializer method <clinit> at com.acme.listener.AcmeContextListener.<clinit>(AcmeContextListener.java:1) ~[_servlets.jar:25.6.0.0]
解决方案
1. 改用@AfterThrowing替代@Around
JVM禁止在<clinit>方法之外修改static final字段,而@Around切面会将原<clinit>的代码移到辅助方法中执行,触发该限制。改用@AfterThrowing可以直接捕获静态初始化器的异常,同时不改变原<clinit>的执行上下文:
@Aspect public class StaticInitializerExceptions { private static final Logger LOGGER = LoggerFactory.getLogger(StaticInitializerExceptions.class); @AfterThrowing( pointcut = "staticinitialization(!is(InterfaceType)) && !within(com.acme.aspects.exceptions.StaticInitializerExceptions)", throwing = "t" ) public void logStaticInitializerException(JoinPoint joinPoint, Throwable t) { LOGGER.error( "STATIC INITIALIZER EXCEPTION CAUGHT in {}", joinPoint.getSignature().getDeclaringTypeName(), t ); } }
如果需要在初始化前执行前置逻辑,可结合@Before通知,但注意不要在前置逻辑中干扰初始化流程。
2. 使用原生AspectJ语法(编译时编织)
原生AspectJ的afterThrowing()通知处理静态初始化器时,不会修改原<clinit>的执行结构,能彻底避免此类问题:
public aspect StaticInitializerExceptions { private static final Logger LOGGER = LoggerFactory.getLogger(StaticInitializerExceptions.class); after() throwing(Throwable t) : staticinitialization(!InterfaceType) && !within(StaticInitializerExceptions) { LOGGER.error( "STATIC INITIALIZER EXCEPTION CAUGHT in {}", thisJoinPoint.getSignature().getDeclaringTypeName(), t ); } }
3. 不推荐:通过切点排除含static final字段的类
若坚持使用@Around,可尝试通过切点表达式排除包含static final字段初始化的类,但这种方式维护成本极高,无法覆盖所有场景,不建议采用。
内容的提问来源于Stack Exchange,提问作者MeBigFatGuy
相关产品推荐
相关产品推荐

