使用spring aspect ltw和tomcat进行web开发的aspect加载问题

使用spring aspect ltw与tomcat整合开发时,需要ltw一般有两种方法。一种是通过aspectj本身的classload机制,在tomcat启动脚本中,加载相应的java agent数据信息;另一种就是通过修改上下文的classLoader,而使用spring提供的tomcatInstrmentClassLoader来进行aspect的编织工作。
然而,在笔者的开发环境中,却发生了aspect无法被再次编织的问题。经过反复的检查,确认了是由于classLoader加载类的先后顺序以及spring对instrment的注入顺序发生了混乱,而导致aspect类并没有正确的被编织。

    使用spring aspect ltw与tomcat整合开发时,需要ltw一般有两种方法。一种是通过aspectj本身的classload机制,在tomcat启动脚本中,加载相应的java agent数据信息;另一种就是通过修改上下文的classLoader,而使用spring提供的tomcatInstrmentClassLoader来进行aspect的编织工作。
    然而,在笔者的开发环境中,却发生了aspect无法被再次编织的问题。经过反复的检查,确认了是由于classLoader加载类的先后顺序以及spring对instrment的注入顺序发生了混乱,而导致aspect类并没有正确的被编织。

     无论是指定修改启动脚本(追加javaagent为spring-instrument.jar)还是ltw(修改context classLoader为spring-instrument-tomcat中的tomcatInstrumentPreClassLoader)的方法,都无法避开一个重要的问题,即这个instrument是由spring追加入classLoader的,即只要当spring启动起来之后,后续的类才会被进行aspect编织。而如果想要编织的类在spring启动之前就被加载了,那么在spring启动之后,再次寻找这个类,这个类就不会被再次加载了(因为,classLoader已经加载过了)。
    通常在启动tomcat时,报的错误就是提示我们写的aspect并没有aspectOf这个方法的异常(这使我们误以为是由于使用了tomcat而与正常的java程序有不同的地方),而实际上是由于aspect并没有被编织的原因。

     具体的问题,见下文,我在web.xml中配置了一个监听器,该监听器启动顺序在spring的contextListener之前。而这个监听器中,使用了一个类似classFinder的方法,这个classFinder会扫描项目中所有的类,导致所有的类均被加载。而当这个监听器启动之后,所有的类都被classLoader加载过了,spring的aspect支持就不再起作用了。

<!-- 日志监听器 -->
	<listener>
		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
	</listener>
	<!-- 系统启动监听器 -->
	<listener>
		<listener-class>com.m_ylf.GtipStartupInitListener</listener-class>
	</listener>
	<!-- spring配置监听器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

     以下就是在spring listener启动之前,执行的一个代码片段。

List<XXX> updatableList = ClassUtils
			.initializeClass(GtipClassFinder.findSubClass(XXX.class));

     上面的代码,是出现在InitLister中的某一个实现类中的,使用了classFinder(xwork的一个类)查找了指定接口的实现类。该实现使用了asm进行类查找,查找时会加载项目中所有的类,从而导致这些类被classLoader加载。而classLoader在加载时,所提供的instrument还并没有被追加classFileTransformer(在aspect中即被追加aspect的ClassFileTransformer,而此追加由spring的aop:loadTimeWeaver提供),最终的结果就是所有加载的类都没有被编织,而是使用原始的javac编译之后的class。这样,我们编写的aspect程序就没有任何作用。

     修改结果,将相应的initLister代码移植到spring往jvm(使用javaagent方式)或classLoader(使用context classLoader替换)追加相应的ClassFileTransformer之后。
    spring追加ClassFileTransformer是通过AspectJWeavingEnabler这个类来完成的,该类在spring初始化所有bean以及完成全部配置之间运行,是一个BeanFactoryPostProcessor类。所以我们的作法也很简单了,将我们的initListern也声明成一个bean,同时也实现BeanFactoryPostProcessotr接口,并将原来的init方法(实现listener的接口方法)修改为postProcessBeanFactory方法(实现beanFactoryPostProcessor接口)。同时,为了保证其在运行于aspectj之后,需要设置其的order(实现Ordered接口),将其顺序修改为在aspectjWeavingEnable之后即可。

     修改了配置之后,再次重新启动tomcat,一切即按照正确的加载顺序进行了。我们写的aspect和要编织的类也能够正常的被编织了。

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/use-spring-aspect-ltw-and-tomcat-to-web-classload-aspect-notok.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注