原文: http://www.iteye.com/topic/1116696

1 AOP各种的实现

AOP就是面向切面编程,我们可以从几个层面来实现AOP。 AOP实现时有三种方式:生成子类字节码、生成代理类字节码、直接修改原类的字节码

在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较。

类别

机制

原理

优点

缺点

静态AOP

静态织入

在编译期,切面直接以字节码的形式编译到目标字节码文件中。

对系统无性能影响。

灵活性不够。

动态AOP

动态代理

在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。

相对于静态AOP更加灵活。

切入的关注点需要实现接口。对系统有一点性能影响。

动态字节码生成

在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。

没有接口也可以织入。

扩展类的实例方法为final时,则无法进行织入。

自定义类加载器

在运行期,目标加载前,将切面逻辑加到目标字节码里。

可以对绝大部分类进行织入。

代码中如果使用了其他类加载器,则这些类将不会被织入。

字节码转换

在运行期,所有类加载器加载字节码前,前进行拦截。

可以对所有类进行织入。

2 AOP里的公民

  • Joinpoint:拦截点,如某个业务方法。
  • Pointcut:Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint。
  • Advice:  要切入的逻辑。
  • Before Advice 在方法前切入。
  • After Advice 在方法后切入,抛出异常时也会切入。
  • After Returning Advice 在方法返回后切入,抛出异常则不会切入。
  • After Throwing Advice 在方法抛出异常时切入。
  • Around Advice 在方法执行前后切入,可以中断或忽略原有流程的执行。 
  • 公民之间的关系

    织入器通过在切面中定义pointcut来搜索目标(被代理类)的JoinPoint(切入点),然后把要切入的逻辑(Advice)织入到目标对象里,生成代理类。

3 AOP的实现机制 
  本章节将详细介绍AOP有各种实现机制。

3.1 动态代理
  Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,InvocationHandler是切面,它包含了Advice和Pointcut。

3.1.1 使用动态代理
  那如何使用动态代理来实现AOP。下面的例子演示在方法执行前织入一段记录日志的代码,其中Business是代理类,LogInvocationHandler是记录日志的切面,IBusiness, IBusiness2是代理类的接口,Proxy.newProxyInstance是织入器。
清单一:动态代理的演示

Java代码  
  1. public static void main(String[] args) {
  2. //需要代理的接口,被代理类实现的多个接口都必须在这里定义
  3. Class[] proxyInterface = new Class[] { IBusiness.class, IBusiness2.class };
  4. //构建AOP的Advice,这里需要传入业务类的实例
  5. LogInvocationHandler handler = new LogInvocationHandler(new Business());
  6. //生成代理类的字节码加载器
  7. ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();
  8. //织入器,织入代码并生成代理类
  9. IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler);
  10. //使用代理类的实例来调用方法。
  11. proxyBusiness.doSomeThing2();
  12. ((IBusiness) proxyBusiness).doSomeThing();
  13. }
  14. /**
  15. * 打印日志的切面
  16. */
  17. public static class LogInvocationHandler implements InvocationHandler {
  18. private Object target; //目标对象
  19. LogInvocationHandler(Object target) {
  20. this.target = target;
  21. }
  22. @Override
  23. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  24. //执行原有逻辑
  25. Object rev = method.invoke(target, args);
  26. //执行织入的日志,你可以控制哪些方法执行切入逻辑
  27. if (method.getName().equals("doSomeThing2")) {
  28. System.out.println("记录日志");
  29. }
  30. return rev;
  31. }
  32. }
  33. 接口IBusiness和IBusiness2定义省略。
public static void main(String[] args) { //需要代理的接口,被代理类实现的多个接口都必须在这里定义 Class[] proxyInterface = new Class[] { IBusiness.class, IBusiness2.class }; //构建AOP的Advice,这里需要传入业务类的实例 LogInvocationHandler handler = new LogInvocationHandler(new Business()); //生成代理类的字节码加载器 ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader(); //织入器,织入代码并生成代理类 IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler); //使用代理类的实例来调用方法。 proxyBusiness.doSomeThing2(); ((IBusiness) proxyBusiness).doSomeThing();
}

//打印日志的切面
public static class LogInvocationHandler implements InvocationHandler {

private Object target; //目标对象 LogInvocationHandler(Object target) { this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //执行原有逻辑 Object rev = method.invoke(target, args); //执行织入的日志,你可以控制哪些方法执行切入逻辑 if (method.getName().equals("doSomeThing2")) { System.out.println("记录日志"); } return rev;
}

}

接口IBusiness和IBusiness2定义省略。

业务类,需要代理的类。

Java代码  
  1. public class Business implements IBusiness, IBusiness2 {
  2. @Override
  3. public boolean doSomeThing() {
  4. System.out.println("执行业务逻辑");
  5. return true;
  6. }
  7. @Override
  8. public void doSomeThing2() {
  9. System.out.println("执行业务逻辑2");
  10. }
  11. }
public class Business implements IBusiness, IBusiness2 {
@Override
public boolean doSomeThing() { System.out.println("执行业务逻辑"); return true;
} @Override
public void doSomeThing2() { System.out.println("执行业务逻辑2");
}

}

输出

Java代码  
  1. 执行业务逻辑2
  2. 记录日志
  3. 执行业务逻辑
执行业务逻辑2
记录日志
执行业务逻辑

可以看到“记录日志”的逻辑切入到Business类的doSomeThing方法前了。

3.1.2 动态代理原理
    本节将结合动态代理的源代码讲解其实现原理。动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。让我们进入newProxyInstance方法观摩下,核心代码其实就三行。
清单二:生成代理类

Java代码  
  1. //获取代理类
  2. Class cl = getProxyClass(loader, interfaces);
  3. //获取带有InvocationHandler参数的构造方法
  4. Constructor cons = cl.getConstructor(constructorParams);
  5. //把handler传入构造方法生成实例
  6. return (Object) cons.newInstance(new Object[] { h });
//获取代理类
Class cl = getProxyClass(loader, interfaces);
//获取带有InvocationHandler参数的构造方法
Constructor cons = cl.getConstructor(constructorParams);
//把handler传入构造方法生成实例
return (Object) cons.newInstance(new Object[] { h });   

其中getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。清单三:查找代理类。

Java代码  
  1. // 缓存的key使用接口名称生成的List
  2. Object key = Arrays.asList(interfaceNames);
  3. synchronized (cache) {
  4. do {
  5. Object value = cache.get(key);
  6. // 缓存里保存了代理类的引用
  7. if (value instanceof Reference) {
  8. proxyClass = (Class) ((Reference) value).get();
  9. }
  10. if (proxyClass != null) {
  11. // 代理类已经存在则返回
  12. return proxyClass;
  13. } else if (value == pendingGenerationMarker) {
  14. // 如果代理类正在产生,则等待
  15. try {
  16. cache.wait();
  17. } catch (InterruptedException e) {
  18. }
  19. continue;
  20. } else {
  21. //没有代理类,则标记代理准备生成
  22. cache.put(key, pendingGenerationMarker);
  23. break;
  24. }
  25. } while (true);
  26. }
 // 缓存的key使用接口名称生成的List
Object key = Arrays.asList(interfaceNames);
synchronized (cache) { do {
Object value = cache.get(key); // 缓存里保存了代理类的引用
if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 代理类已经存在则返回 return proxyClass;
} else if (value == pendingGenerationMarker) { // 如果代理类正在产生,则等待 try {
cache.wait(); } catch (InterruptedException e) { } continue;
} else { //没有代理类,则标记代理准备生成 cache.put(key, pendingGenerationMarker); break;
} } while (true);
}

代理类的生成主要是以下这两行代码。 清单四:生成并加载代理类

Java代码  
  1. //生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)
  2. proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
  3. //使用类加载器将字节码加载到内存中
  4. proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
//生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)
proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
//使用类加载器将字节码加载到内存中
proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);

ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。
清单五:代理类的生成过程

Java代码  
  1. //添加接口中定义的方法,此时方法体为空
  2. for (int i = 0; i < this.interfaces.length; i++) {
  3. localObject1 = this.interfaces[i].getMethods();
  4. for (int k = 0; k < localObject1.length; k++) {
  5. addProxyMethod(localObject1[k], this.interfaces[i]);
  6. }
  7. }
  8. //添加一个带有InvocationHandler的构造方法
  9. MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
  10. //循环生成方法体代码(省略)
  11. //方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)
  12. this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")
  13. //将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。
  14. localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");
  15. localFileOutputStream.write(this.val$classFile);
//添加接口中定义的方法,此时方法体为空
for (int i = 0; i < this.interfaces.length; i++) { localObject1 = this.interfaces[i].getMethods(); for (int k = 0; k < localObject1.length; k++) { addProxyMethod(localObject1[k], this.interfaces[i]); }
}

//添加一个带有InvocationHandler的构造方法
MethodInfo localMethodInfo = new MethodInfo("<init>", “(Ljava/lang/reflect/InvocationHandler;)V”, 1);

//循环生成方法体代码(省略)
//方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)
this.cp.getInterfaceMethodRef(“InvocationHandler”, “invoke”, “Object; Method; Object;”)

//将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。
localFileOutputStream = new FileOutputStream(ProxyGenerator.access 000 ( t h i s . v a l 000(this.val 000(this.valname) + “.class”);
localFileOutputStream.write(this.val$classFile);

那么通过以上分析,我们可以推出动态代理为我们生成了一个这样的代理类。把方法doSomeThing的方法体修改为调用LogInvocationHandler的invoke方法。
清单六:生成的代理类源码

Java代码  
  1. public class ProxyBusiness implements IBusiness, IBusiness2 {
  2. private LogInvocationHandler h;
  3. @Override
  4. public void doSomeThing2() {
  5. try {
  6. Method m = (h.target).getClass().getMethod("doSomeThing", null);
  7. h.invoke(this, m, null);
  8. } catch (Throwable e) {
  9. // 异常处理(略)
  10. }
  11. }
  12. @Override
  13. public boolean doSomeThing() {
  14. try {
  15. Method m = (h.target).getClass().getMethod("doSomeThing2", null);
  16. return (Boolean) h.invoke(this, m, null);
  17. } catch (Throwable e) {
  18. // 异常处理(略)
  19. }
  20. return false;
  21. }
  22. public ProxyBusiness(LogInvocationHandler h) {
  23. this.h = h;
  24. }
  25. //测试用
  26. public static void main(String[] args) {
  27. //构建AOP的Advice
  28. LogInvocationHandler handler = new LogInvocationHandler(new Business());
  29. new ProxyBusiness(handler).doSomeThing();
  30. new ProxyBusiness(handler).doSomeThing2();
  31. }
  32. }
public class ProxyBusiness implements IBusiness, IBusiness2 {

private LogInvocationHandler h;

@Override
public void doSomeThing2() {
try {
Method m = (h.target).getClass().getMethod(“doSomeThing”, null);
h.invoke(this, m, null);
} catch (Throwable e) {
// 异常处理(略)
}
}

@Override
public boolean doSomeThing() {
try {
Method m = (h.target).getClass().getMethod(“doSomeThing2”, null);
return (Boolean) h.invoke(this, m, null);
} catch (Throwable e) {
// 异常处理(略)
}
return false;
}

public ProxyBusiness(LogInvocationHandler h) {
this.h = h;
}

//测试用
public static void main(String[] args) {
//构建AOP的Advice
LogInvocationHandler handler = new LogInvocationHandler(new Business());
new ProxyBusiness(handler).doSomeThing();
new ProxyBusiness(handler).doSomeThing2();
}
}

3.1.3 小结 
    从前两节的分析我们可以看出,动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,经过测试大概每个代理类比静态代理多出10几毫秒的消耗。其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。

3.2 动态字节码生成
   使用动态字节码生成技术实现AOP原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP不需要基于接口。

本节介绍如何使用Cglib来实现动态字节码技术。Cglib是一个强大的,高性能的Code生成类库,它可以在运行期间扩展Java类和实现Java接口,它封装了Asm,所以使用Cglib前需要引入Asm的jar。 清单七:使用CGLib实现AOP

Java代码  
  1. public static void main(String[] args) {
  2. byteCodeGe();
  3. }
  4. public static void byteCodeGe() {
  5. //创建一个织入器
  6. Enhancer enhancer = new Enhancer();
  7. //设置父类
  8. enhancer.setSuperclass(Business.class);
  9. //设置需要织入的逻辑
  10. enhancer.setCallback(new LogIntercept());
  11. //使用织入器创建子类
  12. IBusiness2 newBusiness = (IBusiness2) enhancer.create();
  13. newBusiness.doSomeThing2();
  14. }
  15. /**
  16. * 记录日志
  17. */
  18. public static class LogIntercept implements MethodInterceptor {
  19. @Override
  20. public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  21. //执行原有逻辑,注意这里是invokeSuper
  22. Object rev = proxy.invokeSuper(target, args);
  23. //执行织入的日志
  24. if (method.getName().equals("doSomeThing2")) {
  25. System.out.println("记录日志");
  26. }
  27. return rev;
  28. }
  29. }
public static void main(String[] args) { byteCodeGe(); }
public static void byteCodeGe() { //创建一个织入器 Enhancer enhancer = new Enhancer(); //设置父类 enhancer.setSuperclass(Business.class); //设置需要织入的逻辑 enhancer.setCallback(new LogIntercept()); //使用织入器创建子类 IBusiness2 newBusiness = (IBusiness2) enhancer.create(); newBusiness.doSomeThing2();
}

//记录日志
public static class LogIntercept implements MethodInterceptor {

@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { //执行原有逻辑,注意这里是invokeSuper Object rev = proxy.invokeSuper(target, args); //执行织入的日志 if (method.getName().equals("doSomeThing2")) { System.out.println("记录日志"); } return rev;
}

}

3.3 自定义类加载器
   如果我们实现了一个自定义类加载器,在类加载到JVM之前直接修改某些类的方法,并将切入逻辑织入到这个方法里,然后将修改后的字节码文件交给虚拟机运行,那岂不是更直接。

Javassist是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。这比使用Cglib实现AOP更加高效,并且没太多限制,实现原理如下图:

我们使用系统类加载器启动我们自定义的类加载器,在这个类加载器里加一个类加载监听器,监听器发现目标类被加载时就织入切入逻辑,咱们再看看使用Javassist实现AOP的代码:
清单八:启动自定义的类加载器

Java代码  
  1. //获取存放CtClass的容器ClassPool
  2. ClassPool cp = ClassPool.getDefault();
  3. //创建一个类加载器
  4. Loader cl = new Loader();
  5. //增加一个转换器
  6. cl.addTranslator(cp, new MyTranslator());
  7. //启动MyTranslator的main函数
  8. cl.run("jsvassist.JavassistAopDemo$MyTranslator", args);
//获取存放CtClass的容器ClassPool
ClassPool cp = ClassPool.getDefault();
//创建一个类加载器
Loader cl = new Loader();
//增加一个转换器
cl.addTranslator(cp, new MyTranslator());
//启动MyTranslator的main函数
cl.run("jsvassist.JavassistAopDemo$MyTranslator", args);

清单九:类加载监听器

Java代码  
  1. public static class MyTranslator implements Translator {
  2. public void start(ClassPool pool) throws NotFoundException, CannotCompileException {
  3. }
  4. /* *
  5. * 类装载到JVM前进行代码织入
  6. */
  7. public void onLoad(ClassPool pool, String classname) {
  8. if (!"model$Business".equals(classname)) {
  9. return;
  10. }
  11. //通过获取类文件
  12. try {
  13. CtClass  cc = pool.get(classname);
  14. //获得指定方法名的方法
  15. CtMethod m = cc.getDeclaredMethod("doSomeThing");
  16. //在方法执行前插入代码
  17. m.insertBefore("{ System.out.println(\"记录日志\"); }");
  18. } catch (NotFoundException e) {
  19. } catch (CannotCompileException e) {
  20. }
  21. }
  22. public static void main(String[] args) {
  23. Business b = new Business();
  24. b.doSomeThing2();
  25. b.doSomeThing();
  26. }
  27. }
public static class MyTranslator implements Translator {
    public void start(ClassPool pool) throws NotFoundException, CannotCompileException { } /* * * 类装载到JVM前进行代码织入 */ public void onLoad(ClassPool pool, String classname) { if (!"model$Business".equals(classname)) { return; } //通过获取类文件 try { CtClass  cc = pool.get(classname); //获得指定方法名的方法 CtMethod m = cc.getDeclaredMethod("doSomeThing"); //在方法执行前插入代码 m.insertBefore("{ System.out.println(\"记录日志\"); }"); } catch (NotFoundException e) { } catch (CannotCompileException e) { } } public static void main(String[] args) { Business b = new Business(); b.doSomeThing2(); b.doSomeThing(); }
}

输出:

Java代码  
  1. 执行业务逻辑2
  2. 记录日志
  3. 执行业务逻辑
执行业务逻辑2
记录日志
执行业务逻辑

其中Bussiness类在本文的清单一中定义。看起来是不是特别简单,CtClass是一个class文件的抽象描述。咱们也可以使用insertAfter()在方法的末尾插入代码,使用insertAt()在指定行插入代码。

3.3.1 小结
    从本节中可知,使用自定义的类加载器实现AOP在性能上要优于动态代理和Cglib,因为它不会产生新类,但是它仍然存在一个问题,就是如果其他的类加载器来加载类的话,这些类将不会被拦截。

3.4 字节码转换
    自定义的类加载器实现AOP只能拦截自己加载的字节码,那么有没有一种方式能够监控所有类加载器加载字节码呢?有,使用Instrumentation,它是 Java 5 提供的新特性,使用 Instrumentation,开发者可以构建一个字节码转换器,在字节码加载前进行转换。本节使用Instrumentation和javassist来实现AOP。

3.4.1 构建字节码转换器
    首先需要创建字节码转换器,该转换器负责拦截Business类,并在Business类的doSomeThing方法前使用javassist加入记录日志的代码。

Java代码  
  1. public class MyClassFileTransformer implements ClassFileTransformer {
  2. /**
  3. * 字节码加载到虚拟机前会进入这个方法
  4. */
  5. @Override
  6. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
  7. ProtectionDomain protectionDomain, byte[] classfileBuffer)
  8. throws IllegalClassFormatException {
  9. System.out.println(className);
  10. //如果加载Business类才拦截
  11. if (!"model/Business".equals(className)) {
  12. return null;
  13. }
  14. //javassist的包名是用点分割的,需要转换下
  15. if (className.indexOf("/") != -1) {
  16. className = className.replaceAll("/", ".");
  17. }
  18. try {
  19. //通过包名获取类文件
  20. CtClass cc = ClassPool.getDefault().get(className);
  21. //获得指定方法名的方法
  22. CtMethod m = cc.getDeclaredMethod("doSomeThing");
  23. //在方法执行前插入代码
  24. m.insertBefore("{ System.out.println(\"记录日志\"); }");
  25. return cc.toBytecode();
  26. } catch (NotFoundException e) {
  27. } catch (CannotCompileException e) {
  28. } catch (IOException e) {
  29. //忽略异常处理
  30. }
  31. return null;
  32. }
public class MyClassFileTransformer implements ClassFileTransformer {
//字节码加载到虚拟机前会进入这个方法
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println(className); //如果加载Business类才拦截 if (!"model/Business".equals(className)) { return null; }
//javassist的包名是用点分割的,需要转换下
if (className.indexOf("/") != -1) { className = className.replaceAll("/", ".");
}
try { //通过包名获取类文件 CtClass cc = ClassPool.getDefault().get(className); //获得指定方法名的方法 CtMethod m = cc.getDeclaredMethod("doSomeThing"); //在方法执行前插入代码 m.insertBefore("{ System.out.println(\"记录日志\"); }"); return cc.toBytecode();
} catch (NotFoundException e) {
} catch (CannotCompileException e) {
} catch (IOException e) { //忽略异常处理
}
return null;

}

3.4.2 注册转换器
    使用premain函数注册字节码转换器,该方法在main函数之前执行。

Java代码  
  1. public class MyClassFileTransformer implements ClassFileTransformer {
  2. public static void premain(String options, Instrumentation ins) {
  3. //注册我自己的字节码转换器
  4. ins.addTransformer(new MyClassFileTransformer());
  5. }
  6. }
public class MyClassFileTransformer implements ClassFileTransformer { public static void premain(String options, Instrumentation ins) { //注册我自己的字节码转换器 ins.addTransformer(new MyClassFileTransformer());
}
}

3.4.3 配置和执行
    需要告诉JVM在启动main函数之前,需要先执行premain函数。首先需要将premain函数所在的类打成jar包。并修改该jar包里的META-INF\MANIFEST.MF 文件。

Java代码  
  1. Manifest-Version: 1.0
  2. Premain-Class: bci. MyClassFileTransformer
Manifest-Version: 1.0
Premain-Class: bci. MyClassFileTransformer

然后在JVM的启动参数里加上。-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar

             3.4.4 输出

    执行main函数,你会发现切入的代码无侵入性的织入进去了。

Java代码  
  1. public static void main(String[] args) {
  2. new Business().doSomeThing();
  3. new Business().doSomeThing2();
  4. }
public static void main(String[] args) { new Business().doSomeThing(); new Business().doSomeThing2();
} 

输出

Java代码  
  1. model/Business
  2. sun/misc/Cleaner
  3. java/lang/Enum
  4. model/IBusiness
  5. model/IBusiness2
  6. 记录日志
  7. 执行业务逻辑
  8. 执行业务逻辑2
  9. java/lang/Shutdown
  10. java/lang/Shutdown$Lock
model/Business
sun/misc/Cleaner
java/lang/Enum
model/IBusiness
model/IBusiness2
记录日志
执行业务逻辑
执行业务逻辑2
java/lang/Shutdown
java/lang/Shutdown$Lock

从输出中可以看到系统类加载器加载的类也经过了这里。

4 AOP实战
说了这么多理论,那AOP到底能做什么呢? AOP能做的事情非常多。

  • 性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
  • 缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
  • 软件破解,使用AOP修改软件的验证类的判断逻辑。
  • 记录日志,在方法执行前后记录系统日志。
  • 工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
  • 权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。

4.1 Spring的AOP
    Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。但Spring的AOP有一定的缺点,第一个只能对方法进行切入,不能对接口,字段,静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法将被切入)。第二个同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean。第三个性能不是最好的,从3.3章节我们得知使用自定义类加载器,性能要优于动态代理和CGlib。
可以获取代理类

Java代码  
  1. public IMsgFilterService getThis()
  2. {
  3. return (IMsgFilterService) AopContext.currentProxy();
  4. }
  5. public boolean evaluateMsg () {
  6. // 执行此方法将织入切入逻辑
  7. return getThis().evaluateMsg(String message);
  8. }
  9. @MethodInvokeTimesMonitor("KEY_FILTER_NUM")
  10. public boolean evaluateMsg(String message) {
public IMsgFilterService getThis()
{ return (IMsgFilterService) AopContext.currentProxy();
}

public boolean evaluateMsg () {
// 执行此方法将织入切入逻辑
return getThis().evaluateMsg(String message);
}

@MethodInvokeTimesMonitor(“KEY_FILTER_NUM”)
public boolean evaluateMsg(String message) {

不能获取代理类

Java代码  
  1. public boolean evaluateMsg () {
  2. // 执行此方法将不会织入切入逻辑
  3. return evaluateMsg(String message);
  4. }
  5. @MethodInvokeTimesMonitor("KEY_FILTER_NUM")
  6. public boolean evaluateMsg(String message) {
public boolean evaluateMsg () { // 执行此方法将不会织入切入逻辑
return evaluateMsg(String message);
}

@MethodInvokeTimesMonitor(“KEY_FILTER_NUM”)
public boolean evaluateMsg(String message) {

4.2 参考资料

  • Java 动态代理机制分析及扩展
  • CGlib的官方网站
  • ASM官方网站
  • JbossAOP
  • Java5特性Instrumenttation实践

Java-AOP(Hook)实现机制(JDK/cglib动态代理/ASM/Javassist/AspectJ)相关推荐

  1. 代理详解 静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对 ...

  2. cglib动态代理jar包_代理模式详解:静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...

  3. Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理

    前言 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 很多人至今都是看到 代理就懵, 静态代理.动态代理.JDK动态代理.CGL ...

  4. JDK和cglib动态代理原理详解

    AOP的基础是Java动态代理,了解和使用两种动态代理能让我们更好地理解 AOP,在讲解AOP之前,让我们先来看看Java动态代理的使用方式以及底层实现原理. 转自https://www.jiansh ...

  5. SpringAOP的CGLIB动态代理的底层原理实现

    欢迎加入java学习讨论群:725562382 CGLIB动态代理: CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以 ...

  6. Java之代理(jdk静态代理,jdk动态代理,cglib动态代理,aop,aspectj)

    Java之代理... 1 一.         概念... 1 二.         jdk的静态代理... 1 三.         jdk动态代理... 4 四.         cglib 动态 ...

  7. JAVA 进阶篇 动态代理 JDK动态代理和CGlib动态代理

    JDK动态代理和CGlib动态代理 JDK动态代理: 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. CGlib动态代理: 利用ASM(开源的Java ...

  8. Java设计模式(五)代理设计模式—静态代理—JDK动态代理—Cglib动态代理

    文章目录 什么是代理模式 代理模式应用场景 代理的分类 静态代理 什么是静态代理 深入解析静态代理 小结 动态代理 什么是动态代理 JDK动态代理 原理和实现方式 代码实现 优缺点 Cglib动态代理 ...

  9. Java两种动态代理JDK动态代理和CGLIB动态代理

    目录 代理模式 JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式.为了对外开放协议,B往往实现了一个 ...

最新文章

  1. 注册表故障恢复 必须使用记录或另一备份以恢复包含系统注册表数据的文件。恢复成功...
  2. linux fedora14 u盘运行,Fedora Linux系统下挂载U盘和Windows盘
  3. C++---布隆过滤器
  4. (转)日语时间的表示法
  5. 项目cookie优化之cookie数量限制和多页面共享污染数据问题
  6. matlab simulink 汽车制动纵向动力学建模
  7. Mysql使用dos命令安装
  8. Android APP报价参考
  9. 编程语言Netty原理浅析
  10. Marvin is plain Jane WriteUp_实验吧_Crypto
  11. Realview MDK 链接脚本文件详细解析(一)–链接符号
  12. python下载某网站收费文档(一)——配合fiddler半自动版
  13. android课程设计体重测量仪,智能体重检测仪设计与实现
  14. VUE后台管理系统权限管理
  15. 利用状态机及Astar插件实现dots下寻路
  16. Rxjava +Retrofit 你需要掌握的几个技巧,Retrofit缓存,RxJava封装,统一对有无网络处理,异常处理, 返回结果问题
  17. Springboot项目中使用Kafka
  18. 如何让自己像打电子竞技一样发了疯、拼了命、的学习?
  19. 仓库 “http://ppa.launchpad.net/chris-lea/node.js/ubuntu bionic Release”
  20. Scrapy爬取网页并保存到数据库中

热门文章

  1. 启动oracle时ORA-04031错误的一种解决方法
  2. 微控制器实验计算机组成原理,组成原理实验四 微控制器实验
  3. 核函数背后隐藏着怎样的映射
  4. 大一寒假训练:暴力枚举
  5. 【图形基础篇】02 # 指令式绘图系统:如何用Canvas绘制层次关系图?
  6. Word文档选择文本
  7. 六十星系之53紫微七杀坐巳亥
  8. Multiplexer and Demultiplexer(多路复用器和解复用器)
  9. 如何拒绝公司耍无赖?(2)
  10. 《瑶瑟怨-唐诗三百首》API接口应用程序