代理模式
- 定义: 给目标对象提供一个代理对象, 并由代理对象控制对目标对象的引用.
- 目的
- 通过引入代理对象的方式来间接访问目标对象, 防止直接访问目标对象给系统带来的不必要的复杂性.
- 通过代理对象对原有的业务增强.
- 举例说明, 我们想买进口牛奶喝, 不使用代理模式, 就相当于我们自己坐飞机到国外购买牛奶带回来喝. 而使用代理模式就相当于找了代购帮我们买.
- 这里真实类和代理类都必须实现公共接口.
- 同时, 需要在代理类中包含真实类.
静态代理
- 只适用于简单的情况
- 抽象接口
//抽象接口, 描述了服务提供者的行为 public interface ToolsFactory { void saleMilk(Integer num); }
- 代理类
//代理对象, 包含真实的对象, 为真实对象的服务进行增强, 和真实对象继承同一个接口 public class ProxyMan implements ToolsFactory { //被包含的真实对象 private ToolsFactory factory; private WomanToolsFactory wFactory; public ProxyMan(ToolsFactory factory) { super(); this.factory = factory; } @Override public void saleMilk(Integer num) { dosomeThingBefore(); factory.saleMilk(num); dosomeThingEnd(); } private void dosomeThingBefore() { System.out.println("根据您的需求, 进行市场调研和产品分析"); } private void dosomeThingEnd() { System.out.println("为您免费发货"); } }
- 真实类
public class AFactory implements ToolsFactory { @Override public void saleMilk(Integer num) { System.out.println("购买了" + num + "盒牛奶"); } }
- 测试
public static void main(String[] args) { ToolsFactory factory = new AFactory(); ProxyMan proxy = new ProxyMan(factory); proxy.saleMilk(5); }
- 抽象接口
- 此时, 业务拓宽了, 不只代购牛奶了, 又开始代购奶粉, 马桶, 手表...., 此时就会引发出一个非常致命的缺陷.
- 即静态代理违反了开闭原则
- 开闭原则: 程序对外扩展开发, 对修改关闭, 换句话说, 当需求发生变化时, 我们可以通过添加新模块来满足新需求, 而不是通过修改原有代码满足新需求.
- 违反了开闭原则
- 扩展性差: 要在原有代码上添加代购奶粉, 手表的代码.
- 可维护性差: 现在不按盒买了, 按斤买, 又要修改代码.
动态代理
- 由于业务的扩大, 我们开了一家外贸公司, 这时, 你想买什么东西, 我们就会派出专门的代购员帮你采购.
- 动态代理中的Proxy, 就相当于代购员, 而我们的外贸公司需要有优质的代购服务, 即需要实现InvocationHandler接口中的invoke()方法.
- 代码
- 要实现的业务
//A业务 public interface ToolsFactory { void saleMilk(Integer num); } public class AFactory implements ToolsFactory { @Override public void saleMilk(Integer num) { System.out.println("购买了" + num + "盒牛奶"); } } //B业务 public interface BToolsFactory { void saleWatch(String brand); } public class BFactory implements BToolsFactory { @Override public void saleWatch(String brand) { System.out.println("为您选购" + brand + "品牌的手表"); } }
- 动态代理类
public class Company implements InvocationHandler { //被代理的对象 private Object factory; public Object getFactory() { return factory; } public void setFactory(Object factory) { this.factory = factory; } //通过Proxy获取动态代理的对象 public Object getProxyInstance() { //类加载器, 接口, 当前对象 return Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), this); } @Override //通过动态代理对象对方法进行增强. /** * proxy: 代理对象 * method: 要优化, 拦截的方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { dosomeThingBefore(); Object ret = method.invoke(factory, args); dosomeThingAfter(); return ret; } private void dosomeThingBefore() { System.out.println("根据您的需求, 进行市场调研和产品分析"); } private void dosomeThingAfter() { System.out.println("免费配送"); } }
- 测试
public static void main(String[] args) { ToolsFactory aFactory = new AFactory(); BToolsFactory bFactory = new BFactory(); Company company = new Company(); company.setFactory(aFactory); ToolsFactory one = (ToolsFactory) company.getProxyInstance(); one.saleMilk(5); System.out.println("===================================================="); company.setFactory(bFactory); BToolsFactory two = (BToolsFactory) company.getProxyInstance(); two.saleWatch("Glashutte"); }
- 要实现的业务
- 这里我们可以看到动态代理的一个好处, 即符合单一职责原则
- 单一职责原则: 一个类或接口只负责一项职责, 尽量设计出功能单一的接口.
深入分析动态代理
- 我们debug上面的测试类, 用company.getProxyInstance()创建的one, two, 它们的类名竟然是$Proxy0, $Proxy1, 但我们并没有创建这些类啊! 是不是动态生成的呢?
- 抱着这个疑问, 我们首先看一下一个类的完整生命周期
- javac命令编译成 .class文件
- 类加载器加载这些字节码文件生成Class对象
- Class对象保存在JVM内存模型的方法区(元空间)中.
- 之后通过new的方式就能生成实例对象(在堆中)了.
- 当某个实例对象不会被引用到的时候, 就会被gc卸载了.
- 实际上, 动态代理忽略了第一步, 即没有Java源文件, 直接生成了字节码文件.
- 字节码的来源有两个
- 硬盘(Java源文件编译)
- JVM在内存中直接生成字节码文件.
- 字节码的来源有两个
- 怎么在内存中生成的?
- 我们从newProxyInstance()方法为始, 读一下源码
- 下述代码在JVM中生成了字节码, 把这个字节码加载完后再生成class对象.
/* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs);
- 这行代码之后就是利用反射new出实例了.
- 我们再来看一下getProxyClass0()这个方法
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); } 首先看看接口超没超过65535, 之后进入proxyClassCache.get()方法
- 再看看 proxyClassCache.get()
public V get(K key, P parameter) { Objects.requireNonNull(parameter); expungeStaleEntries(); Object cacheKey = CacheKey.valueOf(key, refQueue); // lazily install the 2nd level valuesMap for the particular cacheKey ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } 前面这些代码是从缓冲中加载代理类的.
- 要是没拿到的话, 就要自己生成代理类了
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
看一下这个apply方法 在Proxy类的私有类ProxyClassFactory中@Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
- 首先要拿到接口的对象, 判断是不是真的接口, 有没有冲突, 加没加public修饰符.
- 之后从一个计数器中拿到一个数字, 拼接出proxyName,
/* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; 我们看一下前缀proxyClassNamePrefix, 原来我们的$Proxy0, $Proxy1就是这样来的. private static final String proxyClassNamePrefix = "$Proxy";
- 之后把类名, 接口传进ProxyGenerator.gerateProxyClalss()方法生成一个byte[]数组, 这个byte数组其实就是前面所说的在内存中生成的字节码文件.
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
- 接着通过该字节码文件生成Class对象(defineClass0是一个native本地方法..)
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
- 生成的Class文件结构是什么?
- 我们先编写工具类
public class ProxyUtils { /** * 将根据类信息动态生成的二进制字节码保存到硬盘中 * clazz: 需要生成动态代理类的类 * @param proxyName 为动态生成的代理类的名字 */ public static void generateClassFile(Class clazz, String proxyName) { //根据类信息和提供的代理类名, 生成字节码 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, new Class[]{clazz}); String paths = clazz.getResource(".").getPath(); System.out.println(paths); FileOutputStream out = null; try { //保存到硬盘中 out = new FileOutputStream(paths + proxyName + ".class"); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if(out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
- 生成$Proxy0字节码文件
public static void main(String[] args) { ToolsFactory aFactory = new AFactory(); BToolsFactory bFactory = new BFactory(); Company company = new Company(); company.setFactory(aFactory); ToolsFactory one = (ToolsFactory) company.getProxyInstance(); one.saleMilk(5); ProxyUtils.generateClassFile(aFactory.getClass(), one.getClass().getSimpleName()); }
- 用反编译工具打开$Proxy0.class, 我们发现如下代码
public final void saleMilk(Integer paramInteger) { try { this.h.invoke(this, m3, new Object[] { paramInteger }); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } }
- 这里的h是: protected InvocationHandler h;
- 也正是在这里, 调用了我们Company里的invoke方法, 完成对原有方法的增强
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { dosomeThingBefore(); Object ret = method.invoke(factory, args); dosomeThingAfter(); return ret; }
- 也正是在这里, 调用了我们Company里的invoke方法, 完成对原有方法的增强