文档章节

AOP各种的实现

城固如春
 城固如春
发布于 2016/09/29 20:09
字数 5212
阅读 21
收藏 3
点赞 0
评论 0

 1 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. */    
  18. public static class LogInvocationHandler implements InvocationHandler {    
  19.   
  20.     private Object target; //目标对象    
  21.   
  22.     LogInvocationHandler(Object target) {    
  23.         this.target = target;    
  24.     }    
  25.   
  26.     @Override    
  27.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    
  28.         //执行原有逻辑    
  29.         Object rev = method.invoke(target, args);    
  30.         //执行织入的日志,你可以控制哪些方法执行切入逻辑    
  31.         if (method.getName().equals("doSomeThing2")) {    
  32.             System.out.println("记录日志");    
  33.         }    
  34.         return rev;    
  35.     }    
  36. }    
  37.   
  38. 接口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代码

public class Business implements IBusiness, IBusiness2 {    

  1.   
  2.     @Override    
  3.     public boolean doSomeThing() {    
  4.         System.out.println("执行业务逻辑");    
  5.         return true;    
  6.     }    
  7.   
  8.     @Override    
  9.     public void doSomeThing2() {    
  10.         System.out.println("执行业务逻辑2");    
  11.     }    
  12.   
  13. }   
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. for (int i = 0; i < this.interfaces.length; i++) {    
  2.   localObject1 = this.interfaces[i].getMethods();    
  3.   for (int k = 0; k < localObject1.length; k++) {    
  4.      addProxyMethod(localObject1[k], this.interfaces[i]);    
  5.   }    
  6. }    
  7.   
  8. //添加一个带有InvocationHandler的构造方法    
  9. MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);    
  10.   
  11. //循环生成方法体代码(省略)    
  12. //方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)    
  13. this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")    
  14.   
  15. //将生成的字节码,写入硬盘    
  16. localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");    
  17. 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;") //将生成的字节码,写入硬盘 localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class"); localFileOutputStream.write(this.val$classFile);

 

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

 

Java代码 

  1. public class ProxyBusiness implements IBusiness, IBusiness2 {    
  2.   
  3. private LogInvocationHandler h;    
  4.   
  5. @Override    
  6. public void doSomeThing2() {    
  7.     try {    
  8.         Method m = (h.target).getClass().getMethod("doSomeThing", null);    
  9.         h.invoke(this, m, null);    
  10.     } catch (Throwable e) {    
  11.         // 异常处理(略)    
  12.     }    
  13. }    
  14.   
  15. @Override    
  16. public boolean doSomeThing() {    
  17.     try {    
  18.        Method m = (h.target).getClass().getMethod("doSomeThing2", null);    
  19.        return (Boolean) h.invoke(this, m, null);    
  20.     } catch (Throwable e) {    
  21.         // 异常处理(略)    
  22.     }    
  23.     return false;    
  24. }    
  25.   
  26. public ProxyBusiness(LogInvocationHandler h) {    
  27.     this.h = h;    
  28. }    
  29.   
  30. //测试用    
  31. public static void main(String[] args) {    
  32.     //构建AOP的Advice    
  33.     LogInvocationHandler handler = new LogInvocationHandler(new Business());    
  34.     new ProxyBusiness(handler).doSomeThing();    
  35.     new ProxyBusiness(handler).doSomeThing2();    
  36. }    
  37. }   
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.   
  5.     public static void byteCodeGe() {    
  6.         //创建一个织入器    
  7.         Enhancer enhancer = new Enhancer();    
  8.         //设置父类    
  9.         enhancer.setSuperclass(Business.class);    
  10.         //设置需要织入的逻辑    
  11.         enhancer.setCallback(new LogIntercept());    
  12.         //使用织入器创建子类    
  13.         IBusiness2 newBusiness = (IBusiness2) enhancer.create();    
  14.         newBusiness.doSomeThing2();    
  15.     }    
  16.   
  17.     /**   
  18.      * 记录日志   
  19.      */    
  20.     public static class LogIntercept implements MethodInterceptor {    
  21.   
  22.         @Override    
  23.         public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {    
  24.             //执行原有逻辑,注意这里是invokeSuper    
  25.             Object rev = proxy.invokeSuper(target, args);    
  26.             //执行织入的日志    
  27.             if (method.getName().equals("doSomeThing2")) {    
  28.                 System.out.println("记录日志");    
  29.             }    
  30.             return rev;    
  31.         }    
  32.     }   
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代码

public static class MyTranslator implements Translator {    

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

执行业务逻辑2    

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

public static void main(String[] args) {    

  1.    new Business().doSomeThing();    
  2.    new Business().doSomeThing2();    
  3. }    
  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代码

public IMsgFilterService getThis()    

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

public boolean evaluateMsg () {    

  1.    // 执行此方法将不会织入切入逻辑    
  2. return evaluateMsg(String message);    
  3. }    
  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实践

本文转载自:http://blog.csdn.net/fk_baker/article/details/6889022

共有 人打赏支持
城固如春
粉丝 8
博文 195
码字总数 22304
作品 0
杭州
程序员
AOP动态代理的实现机制

1 AOP各种的实现 AOP就是面向切面编程,我们可以从几个层面来实现AOP。 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比...

markGao ⋅ 2014/01/02 ⋅ 0

3幅图让你了解Spring AOP

AOP(Aspect Oriented Programming),是面向切面编程的技术。AOP基于IoC基础,是对OOP的有益补充。 AOP之所以能得到广泛认可,主要是因为它将应用系统拆分分了2个部分:核心业务逻辑(Core bu...

白志华 ⋅ 2015/10/18 ⋅ 0

Spring 源码分析(三) —— AOP(一)AOP原理

AOP概论 AOP(Aspect-Oriented Programming,面向切面的编程),谈起AOP,则一定会追溯到OOP(Object Oriented Programming,面向对象编程),因为AOP可以说是对OOP的补充和完善,而这一切的理...

水门-kay ⋅ 2016/02/29 ⋅ 2

Spring核心系列之AOP(二)

Spring核心系列之AOP(二) Hello,大家好,在Spring核心系列之AOP(一)中给大家讲了Spring AOP的最强用的注解方式实现AOP(没看的小伙伴最好先看一看,本文后面的例子大多都使用注解开发),这一...

⋅ 01/11 ⋅ 0

Spring基础知识——是什么、为什么用以及体系结构

一、Spring是什么 Spring是分层的Java SE/EE应用一站式的轻量级开源框架,以IoC(Inverse of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层S...

littleant2 ⋅ 2016/01/06 ⋅ 0

Spring的AOP逐层深入——AOP的基本原理(六)

什么是AOP AOP(Aspect Oriented Programming),意思是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP基于IoC基础,是对OOP(Object Oriented Progr...

architect刘源源 ⋅ 01/13 ⋅ 0

使用Spring的注解方式实现AOP

spring对AOP的实现提供了很好的支持。下面我们就使用Spring的注解来完成AOP做一个例子。 首先,为了使用Spring的AOP注解功能,必须导入如下几个包。aspectjrt.jar,aspectjweaver.jar,cglib-n...

mifans ⋅ 2016/12/23 ⋅ 0

IOC/AOP随笔目录

在当前软件开发OO设计中,面对软件需求的各种潜在变化,我们可能会采用领域驱动开发,把我们的各个业务逻辑分层次隔离解除耦合,这就出现了N层架构(这面值得是逻辑上的分层,当然我们的逻辑...

zting科技 ⋅ 2017/01/10 ⋅ 0

bbossgroups-3.0 发布,新增子项目bboss mvc

bbossgroups-3.0发布 release version : bbossgroups-3.0 release date: 2011/02/26 主要的功能特性: 1.新增的一套mvc框架即bboss-mvc子项目,这是bbossgroups-3.0相比bbossgroups-2.0-RC1......

小编辑 ⋅ 2011/02/28 ⋅ 2

Spring进行面向切面编程的一个简单例子

一、eclipse新建java项目取名SpringTest 二、导入sping包到构建路径 还需要aspectjweaver.jar 三、创建java类(当然先要创建各种包) IHelloService.java HelloServiceImpl.java SimpleHello...

wangxuwei ⋅ 2016/01/04 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

CENTOS7防火墙命令记录

安装Firewall命令: yum install firewalld firewalld-config Firewall开启常见端口命令: firewall-cmd --zone=public --add-port=80/tcp --permanent firewall-cmd --zone=public --add-po......

cavion ⋅ 今天 ⋅ 0

【C++】【STL】利用chromo来测量程序运行时间与日志时间打印精确到微秒

直接上代码吧,没啥好说的。头疼。 #include <iostream>#include <string>#include <ctime>#include <sstream>#include <iomanip>#include <thread>#include <chrono>using ......

muqiusangyang ⋅ 今天 ⋅ 0

Mac环境下svn的使用

在Windows环境中,我们一般使用TortoiseSVN来搭建svn环境。在Mac环境下,由于Mac自带了svn的服务器端和客户端功能,所以我们可以在不装任何第三方软件的前提下使用svn功能,不过还需做一下简...

故久呵呵 ⋅ 今天 ⋅ 0

破解公司回应苹果“USB限制模式”:已攻破

本周四,苹果发表声明称 iOS 中加入了一项名为“USB 限制模式”的功能,可以防止 iPhone 在连接其他设备的时候被破解,并且强调这一功能并不是针对 FBI 等执法部门,为的是保护用户数据安全。...

六库科技 ⋅ 今天 ⋅ 0

MyBtais整合Spring Boot整合,TypeHandler对枚举类(enum)处理

概要 问题描述 我想用枚举类来表示用户当前状态,枚举类由 code 和 msg 组成,但我只想把 code 保存到数据库,查询处理,能知道用户当前状态,这应该怎么做呢?在 Spring 整合MyBatis 的时候...

Wenyi_Feng ⋅ 今天 ⋅ 0

synchronized与Lock的区别

# <center>王梦龙的读书笔记第一篇</center> ## <center>-synchronized与Lock的区别</centre> ###一、从使用场景来说 + synchronized 是能够注释代码块、类、方法但是它的加锁是和解锁使用一......

我不想加班 ⋅ 今天 ⋅ 0

VConsole的使用

手机端控制台打印输出,方便bug的排查。 首先需要引入vconsole.min.js 文件,然后在文件中创造实例。就能直接使用了。 var vConsole = new VConsole(); vConsole的文件地址...

大美琴 ⋅ 今天 ⋅ 0

Java NIO之字符集

1 字符集和编解码的概念 首先,解释一下什么是字符集。顾名思义,就是字符的集合。它的初衷是把现实世界的符号映射为计算机可以理解的字节。比如我创造一个字符集,叫做sex字符集,就包含两个...

士别三日 ⋅ 今天 ⋅ 0

Spring Bean基础

1、Bean之间引用 <!--如果Bean配置在同一个XML文件中,使用local引用--><ref bean="someBean"/><!--如果Bean配置在不同的XML文件中,使用ref引用--><ref local="someBean"/> 其实两种......

霍淇滨 ⋅ 今天 ⋅ 0

05、基于Consul+Upsync+Nginx实现动态负载均衡

1、Consul环境搭建 下载consul_0.7.5_linux_amd64.zip到/usr/local/src目录 cd /usr/local/srcwget https://releases.hashicorp.com/consul/0.7.5/consul_0.7.5_linux_amd64.zip 解压consu......

北岩 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部