结合javassist进行通用性代码扩展修改的思路

最近产品中有一种想法,即在保证源项目不动的基础之上,提供一种通用的扩展原产品逻辑的方法.对于这种情况,有很多方法可以做,比如直接上aspectj,或者是spring aop,也可以是单个javassist修改.但对于实施方扩展源码,还是从相应的几个问题入手.

1. 修改的实现手段是否有难度,比如aspectj的切面语法
2. 相应的应用技术是否对项目运行时有性能影响,比如aspectj中*号匹配模式
3. 过多的修改类是否容易进行管理和处理,比如过多的切面是否好处理

考虑到以上的思路,想做一种简单的修改方式,即将相应的场景固定下来.比如,修改参数信息的,修改结果,以及替换实现等几个固定场景.并且,这些场景都是针对单一的修改场景,并不是一种范围匹配方式的.因为根据实际经验,进行实施时,往往是修改特定的场景中的特定的数据信息. 至于常规的类似日志记录,信息拦截等,还是通过切面来统一实现的好.

以下以一种修改参数信息的场景进行举例:
相应的实际效果,如下:

//源类
public class T8 {
    public static void abc(int a, int b) {
        System.out.println("->" + a + "," + b);
    }
}

//修改类
public class T81 {
    @Param(clazz = "t2.T8", method = "abc", params = {"int", "int"}, order = 2)
    public Map<Integer, Object> xchg1(int a, int b) {
        System.out.println("xchg1->" + a + "," + b);
        return ImmutableMap.of(1, a + 2, 2, b + 3);
    }
}

以上的代码,基于以下的工作方式.

1 获取相应的原始调用参数信息
2 将相应的参数传递给相应的修改处理器
3 如果有多个处理器,即链式调用
4 处理完之后,将参数信息重新传递回原来的方法调用

整个工作方式,基于javassist.因为它提供一种代码式的插入方式,相比asm,在应用层面更加方便.相应的文档参考地址如: http://jboss-javassist.github.io/javassist/tutorial/tutorial.html
因为是,处理参数信息,即在整个方法调用前插入相应的修改代码,使用的即是相应的ctmethod.insertBefore(code) 处理.

继续阅读“结合javassist进行通用性代码扩展修改的思路”

spring LTW agent方式在tomcat 8下无效

在常规的spring ltw中,我们对tomcat使用ltw,一般是以下两种方式。

//1 在启动项中添加javaagent选项
-javaagent:e:/spring-instrument-4.1.1.RELEASE.jar

//2 使用自定义的启动器,在META-INF/context.xml中增加以下内容
<?xml version="1.0" encoding="UTF-8" ?>
<Context>
 <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader" /> 
</Context>

这两种方式在tomcat6以及tomcat7均可以正常工作。但对于tomcat8,第1种方式已经不能再工作,仅能使用第二种方式。原因就在于tomcat8的webClassLoader已经提供了InstrumentableClassLoader接口。此接口将导致spring直接将aspectj的AspectJClassBypassingClassFileTransformer 直接添加到tomcat的classLoader中。但由于不是很正确的实现方式,导致aspect在使用tomcat8提供的classLoader时,并不能有效地对自己的advice进行weaver,导致报以下的错误信息: 

java.lang.NoSuchMethodError: XXXAdvice.aspectOf()LXXXAdvice;
 atXXX.index(AbcController.java:30)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
 at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
 at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)

这个错误的产生在于aspectJ的初始化过程和classLoader之间的交互行为,以及tomcat8中不准确的缓存行为。

继续阅读“spring LTW agent方式在tomcat 8下无效”

aspectj中call和execute在spring架构中的重要区别

在使用aspectj进行定义pointcut时,经常碰到的问题就是该使用call还是execute。当然,在spring中,只支持execute,而不支持call,但使用aspectj如ltw时就可以使用,但两者究竟有什么区别,最大的区别在哪儿,适用点又在哪儿。这就要从定义出来,来了解相关信息了。

在文档《aspectj_in_action_second_editon.pdf》中,针对二者,主要如下面所述:

a call is on the caller side, whereas execution happens on the receiver side—they’re two completely different places. 

即call工作在调用端,而execute工作在被调用端。
如我们将在pointcut打印一句helloworld。那么针对helloAction调用helloService而言。针对call调用,这句打印将会在helloAction中调用,而execute调用,而是在helloService中调用。

以上只是表面上的区别,在具体使用时其实并不是太大的差别,但如果有以下一句话,那么区别就可能完全不一样了:

aspectj是通过修改字节码来完成相应的功能,通过在相应的pointcut定义指定的advice来完成功能。

具体点说,就是aspectj首先需要能够匹配到相应的pointcut,然后才能执行相应的advice方法,如果不能匹配到pointcut,那么就不能执行相应的方法。

继续阅读“aspectj中call和execute在spring架构中的重要区别”

使用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类并没有正确的被编织。

继续阅读“使用spring aspect ltw和tomcat进行web开发的aspect加载问题”

使用普通java开发编译注解版的aspectj程序

使用java5语法(annotation)版的aspectj进行ltw化的aspect开发。在进行运行配置时,必须保证相应的aspect也同样在weaver中,以进行再次编织。

     平时使用aspect开发程序时,一般都是使用普通的aspect语法,然后通过使用ajc进行编译之后,再在命令行下面运行。这样,对于使用ide工具进行java程序开发的人员来说非常麻烦。自aspectj支持java5语法之后,就不再直接使用普通的aspect语法了,而是直接使用Aspectj Annotation语法,然后通过ltw来直接运行aspectj程序。
    本文主要讲解如何使用aspectj的ltw功能进行程序开发,以及对于其中一个在javac环境下的特殊配置文件处理。

继续阅读“使用普通java开发编译注解版的aspectj程序”

spring和aspectj的结合inject原理(下篇)

     上篇讲解了在spring中aspectj,对普通@Transactional的事务控制支持,这篇主要了解一下spring的@Configurable对新建对象的spring属性支持,并顺带发布一个简单的rich domain的例子:)
    上篇地址:http://www.iflym.com/index.php/code/the-theory-about-spring-and-aspectj-pre.html

继续阅读“spring和aspectj的结合inject原理(下篇)”