spring AOP 代理(静态与动态+使用cglib实现)
一、没有代理模式
缺点:
1、工作量特别大,如果项目中有多个类,多个方法,则要修改多次。
2、违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护。
3、违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时、延时;每一个方法引起它变化的原因就有多种。
4、违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象。而在Test类中,Test与Math都是细节。
假设需实现一个计算的类Math、完成加、减、乘、除功能,如下所示:
Math类:
public class Math { //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //减 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }
现在需求发生了变化,要求项目中所有的类在执行方法时输出执行耗时。最直接的办法是修改源代码,如下所示:
public class Math { //加 public int add(int n1,int n2){ //开始时间 long start = getTime(); delay(); int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); System.out.println("共用时:" + (getTime() - start)); return result; } //减 public int sub(int n1,int n2){ //开始时间 long start = getTime(); delay(); int result=n1*n2; System.out.println(n1+"-"+n2+"="+result); System.out.println("共用时:" + (getTime() - start)); return result; } //乘 public int mut(int n1,int n2){ //开始时间 long start = getTime(); delay(); int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); System.out.println("共用时:" + (getTime() - start)); return result; } //除 public int div(int n1,int n2){ //开始时间 long start = getTime(); delay(); int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); System.out.println("共用时:" + (getTime() - start)); return result; } public static long getTime() { return System.currentTimeMillis(); } public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }
测试运行:
Math math = new Math(); int n=100; int m=5; math.add(n,m); math.sub(n,m); math.mut(n,m); math.div(n,m);
二、静态代理
1、定义抽象主题接口。
IMath:
public interface IMath { int add(int n1,int n2); int sub(int n1,int n2); int mut(int n1,int n2); int div(int n1,int n2); }
2、实现接口:
MathImpl
public class MathImpl implements IMath{ //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //减 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }
3、代理类
MathProxy
public class MathProxy implements IMath { IMath math = new MathImpl(); @Override public int add(int n1, int n2) { //开始时间 long start = getTime(); delay(); int result = math.add(n1, n2); System.out.println("共用时:" + (getTime() - start)); return result; } @Override public int sub(int n1, int n2) { //开始时间 long start = getTime(); delay(); int result = math.sub(n1, n2); System.out.println("共用时:" + (getTime() - start)); return result; } @Override public int mut(int n1, int n2) { //开始时间 long start = getTime(); delay(); int result = math.mut(n1, n2); System.out.println("共用时:" + (getTime() - start)); return result; } @Override public int div(int n1, int n2) { //开始时间 long start = getTime(); delay(); int result = math.div(n1, n2); System.out.println("共用时:" + (getTime() - start)); return result; } public static long getTime() { return System.currentTimeMillis(); } public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }
测试运行:
IMath math = new MathProxy(); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);
通过静态代理,是否完全解决了上述的4个问题:
已解决:
1、解决了“开闭原则(OCP)”的问题,因为并没有修改Math类,而扩展出了MathProxy类。
2、解决了“依赖倒转(DIP)”的问题,通过引入接口。
3、解决了“单一职责(SRP)”的问题,Math类不再需要去计算耗时与延时操作,但从某些方面讲MathProxy还是存在该问题。
未解决:
4、如果项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。
如果要解决上面的问题,可以使用动态代理。
三、动态代理
1、接口不变:
2、实现类接口不变
3、修改代理类,实现接口InvocationHandler
DynamicProxy
package com.wbg.springAOP.springdynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Random; /** * 动态代理类 */ public class DynamicProxy implements InvocationHandler { IMath math = new MathImpl(); //目标代理对象 Object targetObject; public DynamicProxy(Object target){ this.targetObject=target; } public DynamicProxy(){ } /** * 获得被代理后的对象 * @param object 被代理的对象 * @return 代理后的对象 */ public Object getProxyObject(Object object){ this.targetObject=object; return Proxy.newProxyInstance( targetObject.getClass().getClassLoader(), //类加载器 targetObject.getClass().getInterfaces(), //获得被代理对象的所有接口 this); //InvocationHandler对象 //loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来生成代理对象进行加载 //interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了 //h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行 } /** * 获取时间 * @return */ public static long getTime() { return System.currentTimeMillis(); } /** * 延迟 */ public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 当用户调用对象中的每个方法时都通过下面的方法执行,方法必须在接口 * proxy 被代理后的对象 * method 将要被执行的方法信息(反射) * args 执行方法时需要的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start=getTime(); delay(); Object result=method.invoke(targetObject,args); System.out.println("共用时:" + (getTime() - start)); return result; } }
4、测试:
方式一:
DynamicProxy dynamicProx = new DynamicProxy(new MathImpl()); IMath math = (IMath) Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{IMath.class},dynamicProx); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);
方式二:
//实例化一个MathProxy代理对象 //通过getProxyObject方法获得被代理后的对象 IMath math=(IMath)new DynamicProxy().getProxyObject(new MathImpl()); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);
运行结果
JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法,参数说明如下:
使用内置的Proxy实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理。
如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。
四、动态代理,使用cglib实现
CGLIB(Code Generation Library)是一个开源项目,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口,通俗说cglib可以在运行时动态生成字节码。
1、引用cglib,通过maven
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.9</version> </dependency>
2、实现DynamicProxyCgilb类 接口MethodInterceptor
package com.wbg.springAOP.springdynamic; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.Random; /* * 动态代理类 * 实现了一个方法拦截器接口 */ public class DynamicProxyCgilb implements MethodInterceptor { //被代理对象 Object targetObject; //动态生成一个新的类,使用父类的无参构造方法创建一个指定了特定回调的代理实例 public Object getProxyObject(Object object){ this.targetObject = object; //增强器,动态代码生成器 Enhancer enhancer=new Enhancer(); //回调方法 enhancer.setCallback(this); //设置生成类的父类类型 enhancer.setSuperclass(targetObject.getClass()); //动态生成字节码并返回代理对象 return enhancer.create(); } // 拦截方法 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { long start=getTime(); delay(); Object result=methodProxy.invoke(targetObject,objects); System.out.println("共用时:" + (getTime() - start)); return result; } /** * 获取时间 * @return */ public static long getTime() { return System.currentTimeMillis(); } /** * 延迟 */ public static void delay(){ try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }
3、测试
MathImpl math = (MathImpl) new DynamicProxyCgilb().getProxyObject(new MathImpl()); int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2);
使用cglib可以实现动态代理,即使被代理的类没有实现接口,但被代理的类必须不是final类。demo:https://github.com/weibanggang/springaopstaticanddynamic.git