概述
在学习spring aop过程中,发现有个怎么都绕不过去的坎,就是AspectJ的使用。少了这一部分,一些spring aop的源码总觉得少了点什么,看不大懂。所以接下来会写几篇关于AspectJ的入门使用。
安装示例代码
1、首先,下载最新的稳定版本(我自己下的是AspectJ 1.9.2, Released 24 Oct 2018 版本)aspectj-1.9.2.jar
http://www.eclipse.org/aspectj/downloads.php#stable_release
2、安装
aspectJ的安装很简单,运行 java -jar aspectj-1.9.2.jar 然后选下jdk路径及最终aspectj要安装(AspecJ安装路径)到哪里就好
3、安装最后,会提示你要配置环境变量了
按照环境配下PATH变量 和 CLASSPATH变量就好
export PATH=${JAVA_HOME}/bin:$PATH:/Users/hdj/software/aspectj/bin
export CLASSPATH=${CLASSPATH}:/Users/hdj/software/aspectj/lib/aspectjrt.jar
4、编译代码示例
进入 ${aspectJ 安装路径}/doc/examples
执行 ajc -argfile telecom/billing.lst
5、运行示例
java telecom.BillingSimulation
6、运行结果
示例总结
简单总结下:
1)安装配置aspectJ环境
2)使用 ajc -argfile 编译类
3)运行编译后的字节码
上述的例子是aspectJ 官方的例子 ,为了说明如何使用aspectJ的使用,我们看另一个例子
另一个例子
知道如何运行aspectJ给的官方示例后,我们继续学习官网给的另一个例子,例子路径
${aspectJ 路径}/doc/examples/tjp/Demo.java
我们先来看看原始类
public class Demo {
static Demo d;
public static void main(String[] args){
new Demo().go();
}
void go(){
d = new Demo();
//调用foo方法
d.foo(1,d);
//调用bar方法
System.out.println(d.bar(new Integer(3)));
}
void foo(int i, Object o){
//打印foo
System.out.println("Demo.foo(" + i + ", " + o + ")\n");
}
String bar (Integer j){
System.out.println("Demo.bar(" + j + ")\n");
return "Demo.bar(" + j + ")";
}
}
再来看看一个随着Demo.java一起被 ajc编译的特殊的类GetInfo.java
:
package tjp;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
aspect GetInfo {
static final void println(String s){ System.out.println(s); }
pointcut goCut(): cflow(this(Demo) && execution(void go()));
pointcut demoExecs(): within(Demo) && execution(* *(..));
Object around(): demoExecs() && !execution(* go()) && goCut() {
println("Intercepted message: " +
thisJoinPointStaticPart.getSignature().getName());
println("in class: " +
thisJoinPointStaticPart.getSignature().getDeclaringType().getName());
printParameters(thisJoinPoint);
println("Running original method: \n" );
Object result = proceed();
println(" result: " + result );
return result;
}
// 打印参数
static private void printParameters(JoinPoint jp) {
println("Arguments: " );
Object[] args = jp.getArgs();
String[] names = ((CodeSignature)jp.getSignature()).getParameterNames();
Class[] types = ((CodeSignature)jp.getSignature()).getParameterTypes();
for (int i = 0; i < args.length; i++) {
println(" " + i + ". " + names[i] +
" : " + types[i].getName() +
" = " + args[i]);
}
}
}
这里和spring aop的使用很多方面非常类似,最后执行的结果如下:
这里说明一下几点:
GetInfo.java 含有的特殊关键词
GetInfo.java含有很多特殊的关键词,这些关键词java是无法识别的,需要通过ajc编译才能织入到字节码中
切入点表达式
spring的切入点表达式我们非常熟悉,使用aspectJ时,也需要配置切入点,需要配置有哪些方法需要被切入
//表示Demo类的go方法调用过程中的方法需要被增强
pointcut goCut(): cflow(this(Demo) && execution(void go()));
//每一个Demo类定义的方法
pointcut demoExecs(): within(Demo) && execution(* *(..));
// 由于 goCut() 这个切入点还包含go()方法自己的调用,因此需要把自己给排掉
Object around(): demoExecs() && !execution(* go()) && goCut();
可能会问,既然为啥要同时配置
demoExecs() 以及 !execution(* go()) && goCut() 单独有一个不够么?
1)demoExecs()表示拦截Demo类的所有方法,如果不加后面的限制,会同时拦截main方法,以及go()方法
2)如果只有!execution(* go()) && goCut(),直接编译时候会报错(warning)执行后直接会报java.lang.StackOverflowError
3)如果只有!execution(* go()) && goCut(),可以执行,但是只会拦截go方法
thisJoinPointStaticPart 和 thisJoinPoint
thisJoinPoint 包含了关于当前切入点的一些信息(通过反射获取的)
thisJoinPointStaticPart 包含一些静态信息,参考官方文档
通过配置切入点,我们实现了不改变Demo.java源码的前提下,往Demo.java方法的前后插入了一段代码。
与Spring 切面写法的对比
对比下之前在学习Spring时候,配置的切面
//声明这是一个组件
@Component
//声明这是一个切面Bean
@Aspect
@Slf4j
public class ServiceAspect {
//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution(* com.hdj.learn.spring.aop.service..*(..))")
public void aspect() {
}
/*
* 配置前置通知,使用在方法aspect()上注册的切入点
* 同时接受JoinPoint切入点对象,可以没有该参数
*/
@Before("aspect()")
public void before(JoinPoint joinPoint) {
log.info("before " + joinPoint);
}
//配置后置通知,使用在方法aspect()上注册的切入点
@After("aspect()")
public void after(JoinPoint joinPoint) {
log.info("after " + joinPoint);
}
//配置环绕通知,使用在方法aspect()上注册的切入点
@Around("aspect()")
public void around(JoinPoint joinPoint) {
long start = System.currentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
} catch (Throwable e) {
long end = System.currentTimeMillis();
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
}
}
//配置后置返回通知,使用在方法aspect()上注册的切入点
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint) {
log.info("afterReturn " + joinPoint);
}
//配置抛出异常后通知,使用在方法aspect()上注册的切入点
@AfterThrowing(pointcut = "aspect()", throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex) {
log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
}
}
其实非常类似,有切面、有通知、有目标类等等,切入点的表达式也非常类似。
总结下
初步了解了aspectJ的使用,我们可以了解以下几点:
1)aspectJ的使用是在编译期,通过特殊的编译器可以在不改变代码的前提下织入代码(当然能不能在运行期
,我还没有确认)
2)aspectJ的使用,也是配置切入点、通知
问题
到了这里基本了解了aspectJ的使用,但是还有几个问题。
1)我们在spring中并没有看到需要aspectj之类的关键词,而是使用java代码就可以了,这是如何做到的
2)同样,我们也没有使用特殊的编译器
3)Spring源码中与aspectJ 相关的AjType究竟是啥?
这些问题,我们会在下一篇博客里解决
10月份加班比较多,耽搁了写博客,这周开始回复更新,会尽量补上上个月的。= = 虽然没啥人看哈哈哈。