跟前面学的 创建型
设计模式:
用来解决对象创建问题 (封装复杂的创建过程,解耦对象的创建代码和使用代码)
不同,结构型
设计模式:
总结了一些类或对象组合在一起的经典结构 (用于解决特定应用场景的问题)
本节先来探探代理模式,知道模式是啥,应用场景,静态代理和动态代理就差不多了~
二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。
0x1、定义和应用场景
代理,这个概念很好理解,举个形象化的例子:
以前国内访问不了Android官网,开发者需要 科学上网 才能访问,利用中间的桥梁就是 代理服务器。
网络异常,图片无法展示
|
通过中间人 → 代理服务器,我们从 直接 访问Android官方变成 间接 访问。
而开发中的代理模式:在不改变原始类(被代理类)的情况下,通过引入代理类为原始类附加功能。
角色有下面三个:
网络异常,图片无法展示
|
- 抽象主题类:声明公用方法,给后面两个角色实现,一般是接口形式;
- 实现主题类:实现抽象主题类的方法,最终的操作对象;
- 代理类:实现抽象主题类,包含对实现主题类对象的操作;
看着很高深,其实不然,等下看看静态代理的例子就秒懂了,先说下代理模式的应用场景:
- 保护代理 → 控制目标对象的访问,给不同用户提供不同的访问权限;
- 智能引用代理 → 访问对象时附加额外操作,业务系统开发非功能性需求(监控、统计、日志等)
- 虚拟代理 → 延迟初始化,创建小对象代理大对象(包含大量IO资源)的创建,大对象真正需要时才创建;
- 远程代理 → 需要本地执行远程服务代码的场景,中间件领域,如远程代理框架gRpc、Dubbo等;
- 缓存代理 → 缓存客户端请求结果并对缓存生命周期进行管理的场景;
说完场景,接着说下静态代理和动态代理的区别:
- 静态代理 → 代理类的字节码在 运行前 就编译好;
- 动态代理 → 代理类的字节码在 运行时 由虚拟机中的程序自动创建;
0x2、静态代理示例
代理模式一般就是用静态代理实现的,写个简单的代购例子:
// 抽象主题 public interface ShoppingOverseas { void shopping(String thing); } // 主题实现类 public class OverseasShop implements ShoppingOverseas { @Override public void shopping(String thing) { System.out.println("海外商家卖出一件:" + thing); } } // 代理类 public class ShoppingProxy implements ShoppingOverseas { OverseasShop shop = new OverseasShop(); @Override public void shopping(String thing) { System.out.println("代购收到你的下单信息"); shop.shopping(thing); System.out.println("代购帮你在海外商家买了:" + thing); } } // 测试用例 public class ProxyTest { public static void main(String[] args) { ShoppingProxy proxy = new ShoppingProxy(); proxy.shopping("鞋子"); } }
输出结果如下:
网络异常,图片无法展示
|
上面这种是中规中矩的代理模式:原始类和代理类都实现相同接口,实际开发中可没那么好的事,代码很多时候不是我们开发维护的(如第三方类库),没法直接修改原始类,给它重新定义一个接口。
如果硬是要上代理模式,可以采用 继承原始类重写方法 的方式变通,重写原始类方法或新增方法。
另外,静态代理还存在一个问题:类Double,要添加附加功能的原始类很多的话,直接爆炸,而且要写很多"重复"代码,增加了不必要的开发成本。这种情况可以通过 动态代理(Dynamic Proxy)
来解决:
在运行时,动态地创建对应的代理类,然后在系统中用代理类替换掉原始类。
0x2、Java中的动态代理
Java中,常见的动态代理实现方式有两种:
JDK动态代理
→ 底层依赖反射机制,被代理类要实现接口方法,通过invokeHandler对所需方法进行增强;
CGLIB代理
→ 利用ASM框架,修改字节码生成子类来处理;
① JDK动态代理示例
玩法如下:
- ① 定义代理接口;
- ② 真实对象实现这个代理接口;
- ③ 定义动态代理类,实现 InvocationHandler 接口,重写invoke()方法,做些小动作;
- ④ 调用Proxy类创建的代理类;
代码示例如下:
// 代理接口 public interface Shopping { void shopping(String thing); void pay(); } // 实现代理接口的真实对象(这里定义了2个) public class FirstShoppingImpl implements Shopping { @Override public void shopping(String thing) { System.out.println("在一号商家购买:" + thing); } @Override public void pay() { System.out.println("在一号商家付款"); } } public class SecondShoppingImpl implements Shopping { @Override public void shopping(String thing) { System.out.println("在二号商家购买:" + thing); } @Override public void pay() { System.out.println("在二号商家付款"); } } // 动态代理类 public class DynamicShoppingProxy implements InvocationHandler { private Shopping object; // 委托类对象 public DynamicShoppingProxy(Shopping object) { this.object = object; } /** * @param proxy 被代理的对象 * @param method 被代理对象的方法 * @param args 方法的参数 * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("shopping")) { System.out.println("开始购物..."); // 反射调用类里的实际方法,返回方法的返回值,没有返回值的话返回null method.invoke(object, args); System.out.println("结束购物..."); } else if (method.getName().equals("pay")) { System.out.println("开始付款..."); method.invoke(object, args); System.out.println("结束付款..."); } return null; } } // 测试用例:调用Proxy类创建的代理类 public class DynamicProxyTest { public static void main(String[] args) { // 生成的动态代理文件保存到当前项目的com/sun/proxy目录下 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 创建动态代理实例 DynamicShoppingProxy proxy1 = new DynamicShoppingProxy(new FirstShoppingImpl()); DynamicShoppingProxy proxy2 = new DynamicShoppingProxy(new SecondShoppingImpl()); // newProxyInstance()动态生成代理类,参数依次为:类加载器(要仅限代理的类)、被代理类实现的接口,动态代理实例 Shopping s1 = (Shopping) (Proxy.newProxyInstance(Shopping.class.getClassLoader(), new Class[]{Shopping.class}, proxy1)); System.out.println(s1.getClass().getName()); s1.shopping("鞋子"); Shopping s2 = (Shopping) (Proxy.newProxyInstance(Shopping.class.getClassLoader(), new Class[]{Shopping.class}, proxy2)); System.out.println(s2.getClass().getName()); s2.shopping("衣服"); s2.pay(); } }