您的位置:首页 > 运维架构

AOP思想、静态、动态代理与Cglib

2018-01-15 23:58 363 查看
AOP编程(面向切面编程):这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

面向切面编程(AOP是Aspect OrientedProgram的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。

但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。

也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。

AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
 AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
JDK静态代理(图[b]来自转载)[/b]


 



 


如果使用静态代理,对于每一个要代理的类为了添加相同的操作,需要分别去实现SubjectProxy,就会造成代理类过多

静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能。

 

JDK动态代理(生成的是类的兄弟)(利用反射机制、接口)[b](图来自转载)[/b]



 






 




使用动态代理,对于每一个要代理的类,都只需要TestProxy,ProxyHandler则固定,不需要改动

其实方法直接调用就可以完成功能,为什么还要加个代理呢?

原因是采用代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部。

 

2、代理实现的一般模式
  其实代理的一般模式就是静态代理的实现模式:首先创建一个接口(JDK代理都是面向接口的),然后创建具体实现类来实现这个接口,在创建一个代理类同样实现这个接口,不同之处在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。

 

 

代理的另外一种实现方式(生成的是类的孩子)

cglib(Code Generation Library)(利用ASM、继承)

1.  public class Base {  //被代理类
2.      public void add() {  
3. 
        System.out.println("add ------------");  
4.      }  
5. 


 

1.  public class CglibProxy implements MethodInterceptor {  
2.      //此为代理类,用于在pointcut处添加advise 
3.      public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable { //拦截器 
4.          // 添加切面逻辑(advise),此处是在目标类代码执行之前,即为MethodBeforeAdviceInterceptor。  
5. 
        System.out.println("before-------------");  
6.          // 执行目标类add方法  
7. 
        proxy.invokeSuper(object, args);  
8.          // 添加切面逻辑(advise),此处是在目标类代码执行之后,即为MethodAfterAdviceInterceptor。  
9. 
        System.out.println("after--------------");  
10.         return null;  
11.
    }  
12.
}  

 

1.  public class Factory {  //工厂类,生成加强过的目标类
2.       * 获得增强之后的目标类,即添加了切入逻辑advice之后的目标类 
3. 
    public static Base getInstance(CglibProxy proxy) {  
4.          Enhancer enhancer = new Enhancer();  
5. 
        enhancer.setSuperclass(Base.class);  
6.   //回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法  
7. 
        enhancer.setCallback(proxy);  
8.          // 此刻,base不是单纯的目标类,而是增强过的目标类  
9. 
        Base base = (Base) enhancer.create();  
10.         return base;  
11.
    }  
12. } 

 

1.  public class Test {  //测试类
2. 
    public static void main(String[] args) {  
3.          CglibProxy proxy = new CglibProxy();//代理类  
4. 
        // base为生成的增强过的目标类  
5.          Base base = Factory.getInstance(proxy);  
6. 
        base.add();  
7.      }  
8. 
}  

 

归纳:

jdk动态代理:接口,目标类实现接口,拦截器(实现了InvocationHandler接口)+反射

1.     目标接口,有个目标方法

2.     目标接口实现类,有方法实体

3.     我的拦截器类实现InnovationHandler接口

接收的参数为:目标实现类对象,通过构造函数给private obj赋值

实现其invoke方法:内部添加具体逻辑并调用目标方法

4.     在测试类

创建一个目标接口实现类对象

创建一个我的拦截器对象,并传入目标方法实现类对象

使用Proxy. newProxyInstance生成目标接口实例

调用接口方法

 

cglib的实现:无需接口,拦截器(实现MethodInterceptor接口)

1.     需要导入cglib包,asm包

2.     目标类,有目标方法

3.     实现MethodInterceptor的拦截器类

      接收的参数为:目标类对象,通过构造函数给private obj赋值

实现其invoke方法:内部添加具体逻辑并调用目标方法

添加createProxy方法,方法里面设置当前代理对象的父类为目标类

(此处说明代理生成的对象为目标对象的子类)

4.     在测试类

      创建一个目标类对象

      创建一个我的拦截器类,传入目标类对象

      调用我的拦截器类的createProxy方法生成目标类对象的子类

      通过子类调用父类的目标方法

 

优缺点:从执行效率上看,Cglib动态代理效率较高

Mybatis用jdk动态代理,spring两种都用,有接口时用动态代理,没有接口用cglib

总结:jdk需要接口,生成的代理和目标类都需要实现接口,生成的是兄弟

Cglib不需要接口,不需要更改目标类,生成的代理是目标类的子类,是儿子

 文章部分内容转载自其他博客
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java
相关文章推荐