代理模式
概述
代理模式(Proxy Pattern)是指为其他对象提供代理,来控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,属于结构型设计模式。如图
代理的目的:
- 保护目标对象
- 增强目标对象功能
下面来看一个简单的代理实现
目标接口
public interface Subject { void request(); }
目标接口实现
public class RealSubject implements Subject { @Override public void request() { System.out.println("Real Subject Called"); } }
代理类
public class Proxy { private Subject subject; public Proxy(Subject subject) { this.subject = subject; } void request() { before(); this.subject.request(); after(); } private void before() { System.out.println("before request ...."); } private void after() { System.out.println("after request ..."); } }
客户端调用
public class Client { public static void main(String[] args) { Proxy proxy = new Proxy(new RealSubject()); proxy.request(); } }
测试结果
看下类图
Subject是顶层目标接口,RealSubject是真实对象(被代理对象),Proxy是代理对象,代理对象持有被代理对象的引用,客户端调用代理对象方法,会同时调用被代理对象的方法,只是在调用被代理对象方法的前后增加了逻辑。在代码中,我们想到代理就想到是对被代理对象的增强,其实就是在原本的逻辑前后增加一些逻辑,而调用者无感知。代理模式属于结构型模式,有静态代理和动态代理。
静态代理
静态代理实现需实现相同接口或者父类,下面我们来举一个明星与经纪人的例子,在现实生活中经纪人就相当于明星的代理人,帮助明星接活挣钱,想要找到明星就得联系其经纪人。看下静态代理的实现,以下都是胡编的
歌星接口
public interface ISingStar { void sing(); }
实现类
public class MaleSinger implements ISingStar { private String name; public MaleSinger(String name) { this.name = name; } @Override public void sing() { System.out.println("大家好我是"+ name + ",跟我一起唱"); } }
静态代理类(经纪人)
public class BrokerStaticProxy implements ISingStar{ private ISingStar singStar; public BrokerStaticProxy(ISingStar singStar) { this.singStar = singStar; } public void sing() { before(); this.singStar.sing(); after(); } private void after() { System.out.println("收钱"); } private void before() { System.out.println("恰谈,签合同"); } }
测试类
public class StaticProxyTest { public static void main(String[] args) { ISingStar singStar = new MaleSinger("周杰伦"); BrokerStaticProxy staticProxy = new BrokerStaticProxy(singStar); staticProxy.sing(); } }
结果
可以看到客户端调用代理类,在没有对目标类做修改使用代理类对目标类功能进行了增强。但是当我们的业务场景越来越复杂,会发现静态代理只能代理实现同一接口的类,所以下面我们来介绍动态代理
动态代理
JDK动态代理
动态代理和静态代理对吧基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。我们还以经纪人为例,假设现在经纪人不仅需要代理歌星还要代理影星,下面我们看代码
影星接口类
public interface IMovieStar { void perform(); }
影星接口实现
public class MaleMovieStar implements IMovieStar { String name; public MaleMovieStar(String name) { this.name = name; } @Override public void perform() { System.out.println("大家好,我是"+ name + ",演戏"); } }
动态代理类
public class BrokerDynamicProxy implements InvocationHandler { private Object target; public Object getInstance(Object target) { this.target = target; Class<?> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object obj = method.invoke(target,args); after(); return obj; } private void after() { System.out.println("收钱"); } private void before() { System.out.println("恰谈,签合同"); } }
测试类
public class DynamicProxyTest { public static void main(String[] args) { try { IMovieStar movieStar = (IMovieStar) new BrokerDynamicProxy().getInstance(new MaleMovieStar("刘德华")); movieStar.perform(); System.out.println("================================="); ISingStar singStar = (ISingStar)new BrokerDynamicProxy().getInstance(new MaleSinger("周杰伦")); singStar.sing(); }catch (Exception e) { e.printStackTrace(); } } }
测试结果
对于JDK动态代理实现原理,我们不仅要知其然,还得知其所以然。JDK Proxy功能如此强大,是怎么实现的呢。我们都知道JDK Proxy是采用字节码重组生成新的对象代替原来对象以达到动态代理的目的,JDK Proxy生成对象的步骤
- 拿到被代理对象的应用,获取到它的所有接口,反射获取
- JDK Proxy生成一个新的类,同时新的类要实现被代理对象所有的实现接口
- 编译新生成的java代码为.class
- 重写加载到JVM中运行
以上这个过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是$开头的 class文件一般都是自动生成的。那么我们有没有办法看到代替后的对象的真容呢?做一个这样测试,我们从内存中的对象字节码通过文件流输出到一个新的 class 文件,然后,利用反编译工具查看 class 的源代码。来看测试代码:
public class DynamicProxyTest { public static void main(String[] args) { try { IMovieStar movieStar = (IMovieStar) new BrokerDynamicProxy().getInstance(new MaleMovieStar("刘德华")); movieStar.perform(); byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{IMovieStar.class}); FileOutputStream fos = new FileOutputStream("E://$Proxy0.class"); fos.write(bytes); fos.close(); }catch (Exception e) { e.printStackTrace(); } } }
反编译出来
import com.stu.pattern.proxy.dynmicproxy.IMovieStar; import java.lang.reflect.*; public final class $Proxy0 extends Proxy implements IMovieStar { public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } public final boolean equals(Object obj) { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void perform() { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } private static Method m1; private static Method m2; private static Method m0; private static Method m3; static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m3 = Class.forName("com.stu.pattern.proxy.dynmicproxy.IMovieStar").getMethod("perform", new Class[0]); } catch(NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch(ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }
我们可以看到新生成的类继承了Proxy类并且实现了目标接口(这就是我们JDK动态代理被代理对象必须实习接口的原因),在新生成的类中重写了接口的所有方法,而且在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用 。可以看到重写了perform方法, super.h.invoke调用父类的invoke也就是BrokerDynamicProxy的invoke,传入了目标对象的方法,实现了动态代理。
Cglib动态代理
静态代理和动态代理都需要实现接口,而Cglib只需要被代理对象是一个类就行,我们仍然以经纪人为例使用Cglib实现动态代理
歌星类
public class SingStar { void sing() { System.out.println("唱歌ing。。。"); } }
影星类
public class MovieStar { void perform() { System.out.println("表演ing。。。"); } }
代理类
public class BrokerCglibProxy implements MethodInterceptor { public Object getInstance(Class<?> clazz) { //代理工具类 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object obj = methodProxy.invokeSuper(o,objects); after(); return null; } private void after() { System.out.println("收钱"); } private void before() { System.out.println("恰谈,签合同"); } }
测试类
public class CglibProxyTest { public static void main(String[] args) { MovieStar movieStar = (MovieStar)new BrokerCglibProxy().getInstance(MovieStar.class); movieStar.perform(); SingStar singStar = (SingStar)new BrokerCglibProxy().getInstance(SingStar.class); singStar.sing(); } }
结果
看的出来我们使用Cglib实现了动态代理,可以发现Cglib可以代理任意的类,那么它的原理我们可以看下这位兄弟的博客讲的很详细了(https://www.cnblogs.com/cruze/p/3865180.html)
静态代理和动态代理区别
- 静态代理被代理类如果新增方法,代理类需要同步新增
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
代理模式优缺点
使用代理模式优点:
- 代理可以将目标对象与代理对象隔离,一点程度上降低了系统的耦合,扩展性好
- 保护目标对象
- 增强目标对象功能
当然也有缺点:
- 造成系统设计的类增多
- 在调用端与目标对象之间加了个代理对象,造成请求速度变慢
- 增加了系统的复杂度