死磕Spring AOP (学习持续更新中。。)

https://www.cnblogs.com/xiaoxi/p/5945707.html

 

在 Spring 中 AOP 代理使用 JDK 动态代理CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。

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("删除了一个用户!");
    }
}

创建一个实现接口InvocationHandler的类,他必须实现invoke方法,实例DynamicProxy。

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.controller.test;

import java.lang.reflect.Proxy;

public class DynamicTest {
    
     public static void main(String[] args){
         
            IUserService target = new UserServiceImpl();
            
            DynamicProxy handler= new DynamicProxy(target);
            
            IUserService proxy = (IUserService)Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),//目标类的类加载器
                    target.getClass().getInterfaces(), //目标类的接口
                    handler); //横切类
            proxy.addUser();
            proxy.deleteUser();
        }

}

结果:

死磕Spring AOP (学习持续更新中。。)_第1张图片

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

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:方法调用时所需要的参数。(指代的是调用真实对象某个方法时接受的参数)

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

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

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

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


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();
    }
}

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

总结:
  (1).通过输出可以看出,最终调用的是com.spring.aop.UserServiceImpl的子类(也是代理类)com.spring.aop.UserServiceImpl$$EnhancerByCGLIB$$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配置文件中加入就能强制使用
(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 代理,因此性能略差一些。

你可能感兴趣的:(死磕Spring AOP (学习持续更新中。。))