在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
【跟MM在网上聊天,一开头总是“hi, 你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自己的回答,接收到其他的话时再通知我回答,怎么样,酷吧。
代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。
客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。】
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
(1)优点:
(1)职责清晰。
2)高扩展性。
3)智能化。
(2)缺点:
(1)由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
(2)实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
(3)使用场景:
(1)远程代理。
(2)虚拟代理。
(3)Copy-on-Write 代理。
(4)保护(Protect or Access)代理。
(5)Cache代理。
(6)防火墙(Firewall)代理。
(7)同步化(Synchronization)代理。
(8)智能引用(Smart Reference)代理。
(4)注意事项:
(1)和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
(2)和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
(5)应用实例:
1、Windows 里面的快捷方式。
2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。
3、买火车票不一定在火车站买,也可以去代售点。
4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
5、spring aop。
13.1 用继承
/** * 问题:我想记录坦克的移动时间 * 最简单的办法:修改代码,记录时间 * 问题2:如果无法改变方法源码呢? * 用继承? */ public class Tank implements Movable { /** * 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new Tank2().move(); } } class Tank2 extends Tank { @Override public void move() { long start = System.currentTimeMillis(); super.move(); long end = System.currentTimeMillis(); System.out.println(end - start); } } interface Movable { void move(); }
13.2 用代理
/** * 问题:我想记录坦克的移动时间 * 最简单的办法:修改代码,记录时间 * 问题2:如果无法改变方法源码呢? * 用继承? * v05:使用代理 */ public class Tank implements Movable { /** * 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new TankTimeProxy(new Tank()).move(); } } class TankTimeProxy implements Movable { Tank tank; public TankTimeProxy(Tank tank) { this.tank = tank; } @Override public void move() { long start = System.currentTimeMillis(); tank.move(); long end = System.currentTimeMillis(); System.out.println(end - start); } } interface Movable { void move(); }
13.3 代理有各种类型
/** * 问题:我想记录坦克的移动时间 * 最简单的办法:修改代码,记录时间 * 问题2:如果无法改变方法源码呢? * 用继承? * v05:使用代理 * v06:代理有各种类型 * 问题:如何实现代理的各种组合?继承?Decorator? */ public class Tank implements Movable { /** * 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new TankTimeProxy().move(); } } class TankTimeProxy implements Movable { Tank tank; @Override public void move() { long start = System.currentTimeMillis(); tank.move(); long end = System.currentTimeMillis(); System.out.println(end - start); } } class TankLogProxy implements Movable { Tank tank; @Override public void move() { System.out.println("start moving..."); tank.move(); System.out.println("stopped!"); } } interface Movable { void move(); }
13.4 对象改成Movable类型
/** * 问题:我想记录坦克的移动时间 * 最简单的办法:修改代码,记录时间 * 问题2:如果无法改变方法源码呢? * 用继承? * v05:使用代理 * v06:代理有各种类型 * 问题:如何实现代理的各种组合?继承?Decorator? * v07:代理的对象改成Movable类型-越来越像decorator了 * */ public class Tank implements Movable { /** * 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Tank t = new Tank();//start moving... TankTimeProxy ttp = new TankTimeProxy(t);//Tank moving claclacla... TankLogProxy tlp = new TankLogProxy(ttp);//7650 tlp.move();//stopped! // System.out.println("------"); // new TankLogProxy( // new TankTimeProxy( // new Tank() // ) // ).move(); // //start moving... // //Tank moving claclacla... // //1570 // //stopped! // System.out.println("------"); // new TankTimeProxy( // new TankLogProxy( // new Tank() // ) // ).move(); // //start moving... // //Tank moving claclacla... // //stopped! // //6392 } } class TankTimeProxy implements Movable { Movable m; public TankTimeProxy(Movable m) { this.m = m; } @Override public void move() { long start = System.currentTimeMillis(); m.move(); long end = System.currentTimeMillis(); System.out.println(end - start); } } class TankLogProxy implements Movable { Movable m; public TankLogProxy(Movable m) { this.m = m; } @Override public void move() { System.out.println("start moving..."); m.move(); long end = System.currentTimeMillis(); System.out.println("stopped!"); } } interface Movable { void move(); }
13.5 jdk的动态代理
cglib实现动态代理
/** * CGLIB实现动态代理不需要接口 */ public class Main { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Tank.class); enhancer.setCallback(new TimeMethodInterceptor()); Tank tank = (Tank)enhancer.create(); tank.move(); } } class TimeMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println(o.getClass().getSuperclass().getName()); System.out.println("before"); Object result = null; result = methodProxy.invokeSuper(o, objects); System.out.println("after"); return result; } } class Tank { public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
JDK的动态代理
/** * 问题:我想记录坦克的移动时间 * 最简单的办法:修改代码,记录时间 * 问题2:如果无法改变方法源码呢? * 用继承? * v05:使用代理 * v06:代理有各种类型 * 问题:如何实现代理的各种组合?继承?Decorator? * v07:代理的对象改成Movable类型-越来越像decorator了 * v08:如果有stop方法需要代理... * 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 Object * (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢? * 分离代理行为与被代理对象 * 使用jdk的动态代理 */ public class Tank implements Movable { /** * 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Tank tank = new Tank(); //reflection 通过二进制字节码分析类的属性和方法 Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{Movable.class}, //tank.class.getInterfaces() new LogHander(tank) ); m.move(); } } class LogHander implements InvocationHandler { Tank tank; public LogHander(Tank tank) { this.tank = tank; } //getClass.getMethods[] @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("method " + method.getName() + " start.."); Object o = method.invoke(tank, args); System.out.println("method " + method.getName() + " end!"); return o; } } interface Movable { void move(); }
13.6 横切代码与业务逻辑代码分离 AOP
/** * 问题:我想记录坦克的移动时间 * 最简单的办法:修改代码,记录时间 * 问题2:如果无法改变方法源码呢? * 用继承? * v05:使用代理 * v06:代理有各种类型 * 问题:如何实现代理的各种组合?继承?Decorator? * v07:代理的对象改成Movable类型-越来越像decorator了 * v08:如果有stop方法需要代理... * 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 * (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢? * 分离代理行为与被代理对象 * 使用jdk的动态代理 * * v09: 横切代码与业务逻辑代码分离 AOP */ public class Tank implements Movable { /** * 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Tank tank = new Tank(); Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{Movable.class}, //tank.class.getInterfaces() new TimeProxy(tank) ); m.move(); } } class TimeProxy implements InvocationHandler { Movable m; public TimeProxy(Movable m) { this.m = m; } public void before() { System.out.println("method start.."); } public void after() { System.out.println("method stop.."); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object o = method.invoke(m, args); after(); return o; } } interface Movable { void move(); }
13.7 反射观察生成的代理对象
/** * 问题:我想记录坦克的移动时间 * 最简单的办法:修改代码,记录时间 * 问题2:如果无法改变方法源码呢? * 用继承? * v05:使用代理 * v06:代理有各种类型 * 问题:如何实现代理的各种组合?继承?Decorator? * v07:代理的对象改成Movable类型-越来越像decorator了 * v08:如果有stop方法需要代理... * 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型 * (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢? * 分离代理行为与被代理对象 * 使用jdk的动态代理 * * v09: 横切代码与业务逻辑代码分离 AOP * v10: 通过反射观察生成的代理对象 * jdk反射生成代理必须面向接口,这是由Proxy的内部实现决定的 */ public class Tank implements Movable { /** * 模拟坦克移动了一段儿时间 */ @Override public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Tank tank = new Tank(); System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true"); Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{Movable.class}, //tank.class.getInterfaces() new TimeProxy(tank) ); m.move(); } } class TimeProxy implements InvocationHandler { Movable m; public TimeProxy(Movable m) { this.m = m; } public void before() { System.out.println("method start.."); } public void after() { System.out.println("method stop.."); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //Arrays.stream(proxy.getClass().getMethods()).map(Method::getName).forEach(System.out::println); before(); Object o = method.invoke(m, args); after(); return o; } } interface Movable { void move(); }
13.8 SpringAOP
IOC+AOP
bean工厂+灵活装配+动态行为拼装,成就spring在Java框架中的一哥地位。
public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("app_auto.xml"); Tank t = (Tank)context.getBean("tank"); t.move(); } } public class Tank { /** * 模拟坦克移动了一段儿时间 */ public void move() { System.out.println("Tank moving claclacla..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } } @Aspect public class TimeProxy { /* //需要在xml文件中进行配置 public void before() { System.out.println("method start.." + System.currentTimeMillis()); } public void after() { System.out.println("method stop.." + System.currentTimeMillis()); } */ @Before("execution (void com.mashibing.dp.spring.v2.Tank.move())") public void before() { System.out.println("method start.." + System.currentTimeMillis()); } @After("execution (void com.mashibing.dp.spring.v2.Tank.move())") public void after() { System.out.println("method stop.." + System.currentTimeMillis()); } } app_auto.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <!-- <bean id="d" class="com.mashibing.Driver"></bean> <bean id="tank" class="com.mashibing.Tank"> <property name="driver" ref="d"></property> </bean> --> <!--注解表示法--> <aop:aspectj-autoproxy/> <bean id="tank" class="com.mashibing.dp.spring.v2.Tank"/> <bean id="timeProxy" class="com.mashibing.dp.spring.v2.TimeProxy"/> <!--配置文件表示法--> <!-- <aop:config>--> <!-- <aop:aspect id="time" ref="timeProxy">--> <!-- <aop:pointcut id="onmove" expression="execution(void com.mashibing.dp.spring.v1.Tank.move())"/>--> <!-- <aop:before method="before" pointcut-ref="onmove"/>--> <!-- <aop:after method="after" pointcut-ref="onmove"/>--> <!-- </aop:aspect>--> <!-- </aop:config>--> </beans> pom.xml <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>