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(); } }
结果:
实现动态代理步骤:
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 代理,因此性能略差一些。