设计模式-13 代理模式(结构型模式)
一 代理设计模式
本文讲解内容的源码下载链接 : http://onl5wa4sd.bkt.clouddn.com/SpringAopPro.rar
1 代理模式的定义
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
是是个是真正的你要访问的对象(目标类),一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。
主要解决:直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
关键代码: 实现与被代理类组合。
使用场景:
租房子的时候去找中介
类图 :
2 为什么需要代理设计模式
假设需实现一个计算的类Math、完成加、减、乘、除功能,如下所示:
Math.java
package com.xinping; public class Math { //加 public int add(int param1, int param2){ int result = param1 + param2; System.out.println(param1 + "+" + param2 +"=" + result); return result; } //减 public int sub(int param1, int param2){ int result = param1 - param2; System.out.println(param1 + "-" + param2 +"=" + result); return result; } //乘 public int mul(int param1, int param2){ int result = param1 * param2; System.out.println(param1 + "*" + param2 +"=" + result); return result; } //除 public int div(int param1, int param2){ int result = param1 / param2; System.out.println(param1 + "/" + param2 +"=" + result); return result; } }
现在需求发生了变化,要求项目中所有的类在执行方法时输出执行耗时。最直接的办法是修改源代码,如下所示:
package com.xinping;
import java.util.Random;
public class Math {
public Math(){
}
//加
public int add(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 + param2;
System.out.println(param1 + "+" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
//减
public int sub(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 - param2;
System.out.println(param1 + "-" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
//乘
public int mul(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 * param2;
System.out.println(param1 + "*" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
//除
public int div(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 / param2;
System.out.println(param1 + "/" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
public void lazy(){
Random random = new Random();
int n = random.nextInt(500);
try {
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试运行:
package com.xinping.test; import junit.framework.TestCase; import com.xinping.Math; public class TestRunMath extends TestCase { protected void setUp() throws Exception { super.setUp(); } protected void tearDown() throws Exception { super.tearDown(); } public void testRunMath(){ Math math = new Math(); math.add(5, 3); math.sub(10, 3); math.mul(3, 6); math.div(4, 2); } }
运行结果:
5+3=8 共耗时: 251 毫秒 10-3=7 共耗时: 144 毫秒 3*6=18 共耗时: 153 毫秒 4/2=2 共耗时: 271 毫秒
注意消耗时间会随着 每次的运行而不同,这是模拟的加载时间,是个随机值。
缺点:
1、工作量特别大,如果项目中有多个类,多个方法,则要修改多次。
2、违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护。
3、违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时、延时;每一个方法引起它变化的原因就有多种。
4、违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象。而在Test类中,Test与Math都是细节。
使用静态代理可以解决部分问题。
二、静态代理
1、定义抽象主题接口。
IMath.java
package com.xinping.staticproxy; /*** * 接口 * 抽象主题 * * */ public interface IMath { //加 public int add(int param1, int param2); //减 public int sub(int param1, int param2); //乘 public int mul(int param1, int param2); //除 public int div(int param1, int param2); }
2、主题类,算术类,实现抽象接口。
Math.java
package com.xinping.staticproxy; public class Math implements IMath{ //加 public int add(int param1, int param2){ int result = param1 + param2; System.out.println(param1 + "+" + param2 +"=" + result); return result; } //减 public int sub(int param1, int param2){ int result = param1 - param2; System.out.println(param1 + "-" + param2 +"=" + result); return result; } //乘 public int mul(int param1, int param2){ int result = param1 * param2; System.out.println(param1 + "*" + param2 +"=" + result); return result; } //除 public int div(int param1, int param2){ int result = param1 / param2; System.out.println(param1 + "/" + param2 +"=" + result); return result; } }
3、代理类
package com.xinping.staticproxy; import java.util.Random; import com.xinping.staticproxy.IMath; import com.xinping.staticproxy.Math; /*** * 静态代理类 * * */ public class StaticMathProxy implements IMath { //被代理的对象 IMath math = new Math(); @Override public int add(int param1, int param2) { //开始时间 long start = System.currentTimeMillis(); this.lazy(); int result = math.add(param1, param2); System.out.println(param1 + "+" + param2 +"=" + result); //结束时间 long end = System.currentTimeMillis(); long consumeTime = end - start; System.out.println("共耗时: "+ consumeTime + " 毫秒"); return result; } @Override public int sub(int param1, int param2) { //开始时间 long start = System.currentTimeMillis(); this.lazy(); int result = math.sub(param1, param2); System.out.println(param1 + "+" + param2 +"=" + result); //结束时间 long end = System.currentTimeMillis(); long consumeTime = end - start; System.out.println("共耗时: "+ consumeTime + " 毫秒"); return result; } @Override public int mul(int param1, int param2) { //开始时间 long start = System.currentTimeMillis(); this.lazy(); int result = math.mul(param1, param2); System.out.println(param1 + "+" + param2 +"=" + result); //结束时间 long end = System.currentTimeMillis(); long consumeTime = end - start; System.out.println("共耗时: "+ consumeTime + " 毫秒"); return result; } @Override public int div(int param1, int param2) { //开始时间 long start = System.currentTimeMillis(); this.lazy(); int result = math.div(param1, param2); System.out.println(param1 + "+" + param2 +"=" + result); //结束时间 long end = System.currentTimeMillis(); long consumeTime = end - start; System.out.println("共耗时: "+ consumeTime + " 毫秒"); return result; } public void lazy(){ Random random = new Random(); int n = random.nextInt(500); try { Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }
4. 测试运行
package com.xinping.test01; import com.xinping.staticproxy.IMath; import com.xinping.staticproxy.StaticMathProxy; import junit.framework.TestCase; public class TestRunStaticMathProxy extends TestCase { protected void setUp() throws Exception { super.setUp(); } protected void tearDown() throws Exception { super.tearDown(); } public void testRunMath(){ IMath math = new StaticMathProxy(); math.add(5, 3); math.sub(10, 3); math.mul(3, 6); math.div(4, 2); } }
测试结果:
5+3=8 共耗时: 296 毫秒 10-3=7 共耗时: 491 毫秒 3*6=18 共耗时: 68 毫秒 4/2=2 共耗时: 195 毫秒
5、小结
通过静态代理,是否完全解决了上述的4个问题:
已解决:
5.1、解决了“开闭原则(OCP)”的问题,因为并没有修改Math类,而扩展出了MathProxy类。
5.2、解决了“依赖倒转(DIP)”的问题,通过引入接口。
5.3、解决了“单一职责(SRP)”的问题,Math类不再需要去计算耗时与延时操作,但从某些方面讲MathProxy还是存在该问题。
未解决:
5.4、如果项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。
如果要解决上面的问题,可以使用动态代理。
三、动态代理,使用JDK内置的Proxy实现
只需要一个代理类,而不是针对每个类编写代理类。
package com.xinping.test02; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Random; /** * 动态代理类 * * **/ public class DynamicMathProxy implements InvocationHandler { //被代理的对象,也叫目标类。 private Object targetObject; /** * 获得被代理后的对象 * * @p object 被代理的对象 * @return 代理后的对象 * * */ public Object getProxyObject(Object object){ this.targetObject = object; return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), //类加载器 targetObject.getClass().getInterfaces(),//获得被代理对象的所有接口 this);//InvaocationHandler对象 } /** * 当用户调用对象中的每个方法时都通过下面的方法执行,方法必须在接口 proxy 被代理后的对象 method 将要被执行的方法信息(反射) args 执行方法时需要的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //被织入的内容,开始时间 long start=System.currentTimeMillis(); lazy(); //使用反射在目标对象上调用方法并传入参数 Object result=method.invoke(targetObject, args); //被织入的内容,结束时间 Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //模拟延时 public void lazy(){ Random random = new Random(); int n = random.nextInt(500); try { Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } }
getProxyObject使用的三个参数:
loader: 一个ClassLoader对象,定义了由那个ClassLoader对象来生成代理对象进行加载
interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行
测试运行:
package com.bank.aop; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.bank.aop.dyna.DynamicMathProxy; import com.bank.aop.staticproxy.IMath; import com.bank.aop.staticproxy.Math; public class TestRunDynamicMathProxy { @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } @Test public void test() { Math mathTarget = new Math(); DynamicMathProxy dynaProxy = new DynamicMathProxy(); IMath math = (IMath) dynaProxy.getProxyObject(mathTarget); math.add(5, 3); math.sub(10, 3); math.mul(3, 6); math.div(4, 2); } }
四 CGLIB
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
1 新建业务类Math,但是没有接口
Math.java
package com.bank.aop.cglib; public class Math { //加 public int add(int param1, int param2){ int result = param1 + param2; System.out.println(param1 + "+" + param2 +"=" + result); return result; } //减 public int sub(int param1, int param2){ int result = param1 - param2; System.out.println(param1 + "-" + param2 +"=" + result); return result; } //乘 public int mul(int param1, int param2){ int result = param1 * param2; System.out.println(param1 + "*" + param2 +"=" + result); return result; } //除 public int div(int param1, int param2){ int result = param1 / param2; System.out.println(param1 + "/" + param2 +"=" + result); return result; } }
创建Cglib代理类
package com.bank.aop.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * 使用cglib动态代理 * * */ public class MathCglib implements MethodInterceptor { private Object target; /** * 创建代理对象 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override // 回调方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("事务开始"); Object object = proxy.invokeSuper(obj, args); System.out.println("事务结束"); return object; } }
测试方法:
package com.bank.aop; import org.junit.Before; import org.junit.Test; import com.bank.aop.cglib.MathCglib; import com.bank.aop.cglib.Math; public class TestRunCglibProxy { @Before public void setUp() throws Exception { } @Test public void test() { Math math = new Math(); MathCglib mathCglib = new MathCglib(); Math mathProxy = (Math) mathCglib.getInstance(math); mathProxy.add(5, 3); mathProxy.sub(10, 3); mathProxy.mul(3, 6); mathProxy.div(4, 2); } }
小结:
JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法,参数说明如下:
使用内置的Proxy实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理。
如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。
如果您觉的本篇文章有用,可以赞助作者一些小额的比特币,用来买咖啡,谢谢。 收款地址:3NTPbsJKRKhe1RE1g2rZdr2dFTDgkBUgUa
注:转载需注明出处及作者名,严禁恶意转载,尊重原作者的劳动成果。