Spring系列之 (八):AOP实现原理




1.代理模式

代理的概念:简单的理解就是通过为某一个对象创建一个代理对象,我们不直接引用原本的对象,而是由创建的代理对象来控制对原对象的引用。

代理模式是常用的java设计模式,他的特征是代理类与委托类(或目标类)有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 
按照代理的创建时期,代理类可以分为两种。 
静态代理由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 
动态代理在程序运行时,运用反射机制动态创建而成,无需手动编写代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。

代理原理:代理对象内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

2.AOP的实现原理

AOP的实现关键在于AOP框架自动创建的AOP代理。AOP代理主要分为两大类:
.静态代理:使用AOP框架提供的命令进行编译,从而在编译阶段就可以生成AOP代理类,因此也称为编译时增强;静态代理一Aspectj为代表。
.动态代理:在运行时借助于JDK动态代理,CGLIB等在内存中临时生成AOP动态代理类,因此也被称为运行时增强,Spring AOP用的就是动态代理。

AOP分为静态AOP和动态AOP。

静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。

动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为:JDK提供的动态代理技术  CGLIB(动态字节码增强技术)尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。

(1)JDK动态代理

a. JDK动态代理是面向接口的,必须提供一个委托类和代理类都要实现的接口,只有接口中的方法才能够被代理。
b. JDK动态代理的实现主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口。

InvocationHandler接口:

来看看java的API帮助文档是怎么样描述InvocationHandler接口的:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and 
dispatched to the invoke method of its invocation handler.

说明:每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。同时在invoke的方法里 我们可以对被代理对象的方法调用做增强处理(如添加事务、日志、权限验证等操作)。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

public interface InvocationHandler { 
     public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
}

参数说明: 
Object proxy:指被代理的对象。 
Method method:要调用的方法。(指代的是我们所要调用代理对象的某个方法的Method对象) 
Object[] args:方法调用时所需要的参数。(指代的是调用真实对象某个方法时接受的参数)

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。 

Proxy类: 
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 

参数说明: 
ClassLoader loader:类加载器 
Class<?>[] interfaces:得到全部的接口 
InvocationHandler h:得到InvocationHandler接口的子类实例

JDK动态代理示例:

定义一个业务接口IUserService,如下:

复制代码
package com.spring.aop;

public interface IUserService {
    //添加用户
    public void addUser();
    //删除用户
    public void deleteUser();
}
复制代码

一个简单的实现类UserServiceImpl,如下:

复制代码
package com.spring.aop;

public class UserServiceImpl implements IUserService{
    
    public void addUser(){
        System.out.println("新增了一个用户!");
    }
    
    public void deleteUser(){
        System.out.println("删除了一个用户!");
    }
}
复制代码

现在我们要实现的是,在addUser和deleteUser之前和之后分别动态植入处理。
JDK动态代理主要用到java.lang.reflect包中的两个类:Proxy和InvocationHandler。
InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑和业务逻辑编织在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
如下,我们创建一个InvocationHandler实例DynamicProxy:(当执行动态代理对象里的目标方法时,实际上会替换成调用DynamicProxy的invoke方法)

复制代码
package com.spring.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler{
    
    //被代理对象(就是要给这个目标类创建代理对象)
    private Object target;
    
    //传递代理目标的实例,因为代理处理器需要,也可以用set等方法。
    public DynamicProxy(Object target){
        this.target=target;
    }
    
    /**
     * 覆盖java.lang.reflect.InvocationHandler的方法invoke()进行织入(增强)的操作。
     * 这个方法是给代理对象调用的,留心的是内部的method调用的对象是目标对象,可别写错。
     * 参数说明:
     * proxy是生成的代理对象,method是代理的方法,args是方法接收的参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //目标方法之前执行
        System.out.println("do sth Before...");
        //通过反射机制来调用目标类方法
        Object result = method.invoke(target, args);
        //目标方法之后执行
        System.out.println("do sth After...\n");
        return result;
    }
}
复制代码

 下面是测试:

复制代码
package com.spring.aop;

//用java.lang.reflect.Proxy.newProxyInstance()方法创建动态实例来调用代理实例的方法
import java.lang.reflect.Proxy;

public class DynamicTest {
    
    public static void main(String[] args){
        //希望被代理的目标业务类
        IUserService target = new UserServiceImpl();
        //将目标类和横切类编织在一起
        DynamicProxy handler= new DynamicProxy(target);
        //创建代理实例,它可以看作是要代理的目标业务类的加多了横切代码(方法)的一个子类
        //创建代理实例(使用Proxy类和自定义的调用处理逻辑(handler)来生成一个代理对象)
        IUserService proxy = (IUserService)Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//目标类的类加载器
                target.getClass().getInterfaces(), //目标类的接口
                handler); //横切类
        proxy.addUser();
        proxy.deleteUser();
    }
}
复制代码

说明:上面的代码完成业务类代码和横切代码的编制工作,并生成了代理实例,newProxyInstance方法的第一个参数为类加载器,第二个参数为目标类所实现的一组接口,第三个参数是整合了业务逻辑和横切逻辑的编织器对象。

每一个动态代理实例的调用都要通过InvocationHandler接口的handler(调用处理器)来调用,动态代理不做任何执行操作,只是在创建动态代理时,把要实现的接口和handler关联,动态代理要帮助被代理执行的任务,要转交给handler来执行。其实就是调用invoke方法。(可以看到执行代理实例的addUser()和deleteUser()方法时执行的是DynamicProxy的invoke()方法。)

运行结果:

基本流程:用Proxy类创建目标类的动态代理,创建时需要指定一个自己实现InvocationHandler接口的回调类的对象,这个回调类中有一个invoke()用于拦截对目标类各个方法的调用。创建好代理后就可以直接在代理上调用目标对象的各个方法。

实现动态代理步骤:
A. 创建一个实现接口InvocationHandler的类,他必须实现invoke方法。
B.创建被代理的类以及接口。
C.通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler handler)创建一个代理。
D.通过代理调用方法。

使用JDK动态代理有一个很大的限制,就是它要求目标类必须实现了对应方法的接口,它只能为接口创建代理实例。我们在上文测试类中的Proxy的newProxyInstance方法中可以看到,该方法第二个参数便是目标类的接口。如果该类没有实现接口,这就要靠cglib动态代理了。

(2)CGLIB动态代理

CGLib采用非常底层的字节码技术,可以为一个类创建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势植入横切逻辑。

字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然用CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖

a.使用CGLIB动态代理不要求必须有接口,生成的代理对象是目标对象的子类对象,所以需要代理的方法不能是private或者final或者static的。
b.使用CGLIB动态代理需要有对cglib的jar包依赖(导入asm.jar和cglib-nodep-2.1_3.jar)

CGLibProxy与JDKProxy的代理机制基本类似,只是其动态代理的代理对象并非某个接口的实现,而是针对目标类扩展的子类。换句话说JDKProxy返回动态代理类,是目标类所实现接口的另一个实现版本,它实现了对目标类的代理(如同UserDAOProxy与UserDAOImp的关系),而CGLibProxy返回的动态代理类,则是目标代理类的一个子类(代理类扩展了UserDaoImpl类)

cglib 代理特点:
CGLIB 是针对类来实现代理,它的原理是对指定的目标类生成一个子类,并覆盖其中方法。因为采用的是继承,所以不能对 finall 类进行继承

我们使用CGLIB实现上面的例子:

代理的最终操作类:

复制代码
package com.spring.aop;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor{
    
    //增强器,动态代码生成器
    Enhancer enhancer = new Enhancer();
    
    /**
     * 创建代理对象
     * @param clazz
     * @return 返回代理对象
     */
    public Object getProxy(Class clazz){
        //设置父类,也就是被代理的类(目标类)
        enhancer.setSuperclass(clazz);
        //设置回调(在调用父类方法时,回调this.intercept())
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例(动态扩展了UserServiceImpl类)
        return enhancer.create();
    }
    
    /**
     * 拦截方法:在代理实例上拦截并处理目标方法的调用,返回结果
     * obj:目标对象代理的实例;
     * method:目标对象调用父类方法的method实例;
     * args:调用父类方法传递参数;
     * proxy:代理的方法去调用目标方法
     */
    public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy) 
        throws Throwable{
        
        System.out.println("--------测试intercept方法的四个参数的含义-----------");
        System.out.println("obj:"+obj.getClass());
        System.out.println("method:"+method.getName());
        System.out.println("proxy:"+proxy.getSuperName());
        if(args!=null&&args.length>0){
            for(Object value : args){
                System.out.println("args:"+value);
            }
        }

        //目标方法之前执行
        System.out.println("do sth Before...");
        //目标方法调用
        //通过代理类实例调用父类的方法,即是目标业务类方法的调用
        Object result = proxy.invokeSuper(obj, args);
        //目标方法之后执行
        System.out.println("do sth After...\n");
        return result;
    }
}
复制代码

测试类:

复制代码
package com.spring.aop;

public class CglibProxyTest {
    
    public static void main(String[] args){
        CglibProxy proxy=new CglibProxy();
        //通过java.lang.reflect.Proxy的getProxy()动态生成目标业务类的子类,即是代理类,再由此得到代理实例
        //通过动态生成子类的方式创建代理类
        IUserService target=(IUserService)proxy.getProxy(UserServiceImpl.class);
        target.addUser();
        target.deleteUser();
    }
}
复制代码

基本流程:需要自己写代理类,它实现MethodInterceptor接口,有一个intercept()回调方法用于拦截对目标方法的调用,里面使用methodProxy来调用目标方法。创建代理对象要用Enhance类,用它设置好代理的目标类、由intercept()回调的代理类实例、最后用create()创建并返回代理实例。

输出:

我们看到达到了同样的效果。它的原理是生成一个父类enhancer.setSuperclass(clazz)的子类enhancer.create(),然后对父类的方法进行拦截enhancer.setCallback(this). 对父类的方法进行覆盖,所以父类方法不能是final的。

总结: 
  (1).通过输出可以看出,最终调用的是com.spring.aop.UserServiceImpl的子类(也是代理类)com.spring.aop.UserServiceImpl

EnhancerByCGLIBEnhancerByCGLIB
43831205的方法。
  (2). private,final和static修饰的方法不能被代理。

注意:
  (1).CGLIB是通过实现目标类的子类来实现代理,不需要定义接口。
  (2).生成代理对象使用最多的是通过Enhancer和继承了Callback接口的MethodInterceptor接口来生成代理对象,设置callback对象的作用是当调用代理对象方法的时候会交给callback对象的来处理。
  (3).创建子类对象是通过使用Enhancer类的对象,通过设置enhancer.setSuperClass(Class class)和enhancer.setCallback(Callback callback)来创建代理对象。

解释MethodInterceptor接口的intercept方法:

Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;

参数说明:Object var1代表的是子类代理对象,Method var2代表的是要调用的方法反射对象,第三个参数是传递给调用方法的参数,前三个参数和JDK的InvocationHandler接口的invoke方法中参数含义是一样的,第四个参数MethodProxy对象是cglib生成的用来代替method对象的,使用此对象会比jdk的method对象的效率要高

如果使用method对象来调用目标对象的方法: method.invoke(var1, var3),则会陷入无限递归循环中, 因为此时的目标对象是目标类的子代理类对象。

MethodProxy类提供了两个invoke方法:

public Object invokeSuper(Object obj, Object[] args) throws Throwable;
public Object invoke(Object obj, Object[] args) throws Throwable;

注意此时应该使用invokeSuper()方法,顾名思义调用的是父类的方法,若使用invoke方法,则需要提供一个目标类对象,但我们只有目标类子类代理对象,所以会陷入无限递归循环中。

CGLIB所创建的动态代理对象的性能比JDK所创建的动态代理对象的性能高很多,但创建动态代理对象时比JDK创建动态代理对象要花费更长的时间。

JDK代理和CGLIB代理的总结(生成代理对象的前提是有AOP切入) 
(1)、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。 如果就是单纯的用IOC生成一个对象,也没有AOP的切入不会生成代理的,只会NEW一个实例,给Spring的Bean工厂。
(2)、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 
如何强制使用CGLIB实现AOP 
* 添加CGLIB库 
* 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>就能强制使用 
(3)、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换(没有实现接口的就用CGLIB代理,使用了接口的类就用JDK动态代理

JDK动态代理和CGLIB字节码生成的区别:
(1)、JDK动态代理只能对实现了接口的类生成代理,而不能针对类。CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final。
(2)、JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,它有几个要求
* 实现InvocationHandler;
* 使用Proxy.newProxyInstance产生代理对象;
* 被代理的对象必须要实现接口;
CGLib 必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承。
(3)、jdk的核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。cglib的核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。

五、小结

     AOP 广泛应用于处理一些具有横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,它使得开发者能用更优雅的方式处理具有横切性质的服务。不管是哪种 AOP 实现,不论是 AspectJ、还是 Spring AOP,它们都需要动态地生成一个 AOP 代理类,区别只是生成 AOP 代理类的时机不同:AspectJ 采用编译时生成 AOP 代理类,因此具有更好的性能,但需要使用特定的编译器进行处理;而 Spring AOP 则采用运行时生成 AOP 代理类,因此无需使用特定编译器进行处理。由于 Spring AOP 需要在每次运行时生成 AOP 代理,因此性能略差一些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值