前言
非常重要的一个设计模式, 也很常见, 很多框架都有它的影子. 定义就不多说了. 两点:
1, 为其它对象提供一个代理服务, 间接控制对这个对象的访问, 联想 Spring 事务机制, 在合适的方法上加个 transaction 注解, 就分分钟实现了事务.
2, 除了 1, 代理对象还能充当中介的角色.
为什么要有代理模式?
如果希望不给原有对象附加太多的责任(和本对象无关的冗余代码), 但是还想能为其实现新功能, 那么代理模式就是做这个的. 还是联系 Spring 事务机制, 很好的应用场景.
实际生活里, 可以联系租房, 大家租房一般都会找中介, 这个中介就是代理对象的角色, 它解耦 (分离) 了房东的一部分责任, 因为房东太忙了, 或者房东不屑于做这些事情, 故交给代理对象去做.
一句话: 解耦合, 提高扩展性
静态代理模式
顾名思义, 代理类被显示的指定了, 即代理在代码里被写死了. 实现最简单, 也很少用, 但是能帮助快速理解思想
- public interface StaticProxy {
- void dosth();
- }
- ////////////// 接下来是很熟悉的做法, 实现这个借口
- public class RealRole implements StaticProxy {
- @Override
- public void dosth() {
- System.out.println("do sth");
- }
- }
- /////////// 然后重要的角色 -- 代理类, 联系 Spring 事务机制
- public class ProxyRole implements StaticProxy {
- private StaticProxy staticProxy;
- public ProxyRole() {
- this.staticProxy = new RealRole();
- }
- @Override
- public void dosth() {
- // 真正业务逻辑之前的处理, 比如加上事务控制
- before();
- this.staticProxy.dosth(); // 真正的业务逻辑处理, 比如数据库的 crud
- after(); // 善后处理, 比如, 事务提交
- }
- private void after() {
- System.out.println("after dosth");
- }
- private void before() {
- System.out.println("before dosth");
- }
- }
- //////// 执行
- public class ProxyMain {
- public static void main(String[] args) {
- StaticProxy staticProxy = new ProxyRole();
- staticProxy.dosth();
- }
- }
打印 ==============
- before dosth
- do sth
- after dosth
如上就是最简单的静态代理模式的实现, 很直观, 就是使用委托的思想, 把责任转移到被代理的对象上, 代理类实现非业务相关的功能
缺陷
静态代理非常简单, 但是它的缺陷也是显然的, 因为静态代理的代理关系在 IDE 编译时就确定了, 如果接口改变了, 不仅实现类要改变, 代理类也要改变, 代理类和接口之间的耦合非常严重.
动态代理模式
和静态代理相反, 代理类不是写死的, 而是动态的创建. 又分为两种实现方案:
基于 JDK 实现
也很简单, 就是利用 Java 的 API 来实现代理类, 即我们不用自己写代理类了, 也就是上面例子里的类 --ProxyRole. 到这里, 其实也能猜出来, 本质就是利用 Java 的反射机制在程序运行期动态的创建接口的实现类, 并生产代理对象而已, 如此一来, 就能避免实现的接口 --StaticProxy 改变了, 导致代理类也跟着变的场景发生. 下面看实现代码:
首先写好需要实现的接口, 和具体实现类
- public interface DynamicProxy {
- void dosth();
- }
- ////////////
- public class DynamicRealRole implements DynamicProxy {
- @Override
- public void dosth() {
- System.out.println("do sth");
- }
- }
然后, 要实现 JDK 的一个接口 --InvocationHandler,Java 的动态代理机制中, 有两个重要的类, 一个是 InvocationHandler 接口, 一个是 Proxy 类.
注意, DynamicProxyRole 不是代理类, 代理类我们不需要自己写, 它是 JDK 动态生成给我们的(反射机制)
- public class DynamicProxyRole implements InvocationHandler {
- private Object object; // 被代理的对象
- public DynamicProxyRole(Object object) { // 构造注入
- this.object = object;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- before();
- Object ret = method.invoke(object, args);
- after();
- return ret;
- }
- private void after() {
- System.out.println("after dosth");
- }
- private void before() {
- System.out.println("before dosth");
- }
- }
InvocationHandler 接口只有一个方法 -- invoke, 参数也很好理解, 分别是:
proxy: 代理的真实对象, 也就是实现类的对象
method: 要调用真实对象的某个方法的 Method 对象, 也就是会调用 dosth 的方法的对象
args: 调用真实对象某个方法时接受的参数, 没有就是空数组
最后写运行类
- public class DynamicMain {
- private static DynamicProxy realRole = new DynamicRealRole(); // 被代理的类, 也就是实现类
- private static DynamicProxyRole dynamicProxyRole = new DynamicProxyRole(realRole);
- public static void main(String[] args) {
- // 通过 JDK 动态代理获取被代理对象 (实现类的对象) 的代理对象, 该代理类实现了指定的需要去代理的接口, 也就是第 2 个参数
- DynamicProxy dynamicProxyObj = (DynamicProxy) Proxy.newProxyInstance(
- realRole.getClass().getClassLoader(), // 被代理的类的加载器
- realRole.getClass().getInterfaces(), // 被代理的类需要实现的接口, 可以有多个
- dynamicProxyRole // 必须是实现了 InvocationHandler 接口的类, invoke 方法里写业务逻辑和代理方法
- );
- dynamicProxyObj.dosth();
- }
- }
Proxy 类的作用是动态创建一个代理对象, 也就是代理对象不需要我们自己写. Proxy 提供了许多的方法, 用的最多的是 newProxyInstance, 注释里也写了:
其中第一个参数是被代理的类的加载器, 传入的目的是告诉 JDK 由哪个类加载器对生成的代理进行加载. 其实就是真实的类 (实现类) 的对象的加载器.
第二个参数是代理类需要实现的接口, 可以多个. 其实就是接口 DynamicProxy, 很好理解, 在静态代理模式中, 我们就需要手动实现这个接口, 来实现代理类.
第三个参数就是实现了 InvocationHandler 接口的类即可, 原因是此类里有 invoke 方法, 而通过 Proxy 的 newProxyInstance 方法生成的代理类去调用接口方法 (dosth) 时, 对方法 (dosth) 的调用会自动委托给 InvocationHandler 接口的 invoke 方法, 这样也就实现了代理模式.
综上, 代理对象就实现了在程序运行时产生. 进一步要知道, 所有的 JDK 动态代理都会继承 java.lang.reflect.Proxy, 同时还会实现我们指定的接口(Proxy 的 newProxyInstance 第二个参数里的接口).
看到这里, 也确定, JDK 动态代理核心就是反射思想的应用, 没什么新鲜的东西.
缺陷
JDK 动态代理这种方式只能代理接口, 这是其缺陷
基于 CGLib 实现
Java 动态代理是基于接口实现的, 如果对象没有实现接口, 那么可以用 CGLIB 类库实现, 它的原理是基于继承实现代理类. 代码也不难
首先, 写一个类, 其没有实现接口, 此时前面的 JDK 动态代理就无法使用了.
- public class NoInterfaceReal {
- public void dosth() {
- System.out.println("do sth");
- }
- }
其次, 需要实现 CGLIB 类库提供的接口 --MethodInterceptor
在这之前, 先下载 CGLib 包
- <!-- https://mvnrepository.com/artifact/cglib/cglib -->
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>3.2.10</version>
- </dependency>
然后实现其提供的接口 --MethodInterceptor, 关键方法是 intercept
- public class CGLibProxy implements MethodInterceptor {
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- before();
- Object ret = proxy.invokeSuper(obj, args);
- after();
- return ret;
- }
- private void after() {
- System.out.println("after dosth");
- }
- private void before() {
- System.out.println("before dosth");
- }
- }
实现了 MethodInterceptor 接口后, 后续生成的代理对象对 dosth 方法的调用会被转发到 intercept 方法, 自然也就实现了代理模式.
最后, 通过 CGLIB 动态代理生成代理对象, 就完成了代理模式, 非常简单
- public class CGLibMain {
- public static void main(String[] args) {
- CGLibProxy cgLibProxy = new CGLibProxy();
- NoInterfaceReal proxy = (NoInterfaceReal) Enhancer.create(NoInterfaceReal.class, cgLibProxy);
- proxy.dosth();
- }
- }
通过 CGLib 的 Enhancer 类 create 了一个代理对象, 参数传入需要被代理的类(可以不是接口), 和实现了 MethodInterceptor 接口的类的对象即可. CGLib 就会为我们自动生成继承了被代理的类的代理对象, 通过代理对象调用 dosth 方法, 其调用会被委托给第二个参数里的 intercept 方法.
综上得知:
1,CGLib 底层是利用 asm 字节码框架实现的, 该框架可以在 Java 程序运行时对字节码进行修改和动态生成, 故它可以代理普通类, 具体细节是通过继承和重写需要被代理的类 (NoInterfaceReal) 来实现.
2,CGLib 可以实现对方法的代理, 即可以实现拦截 (只代理) 某个方法.
3, 通过 CGLib 的 Enhancer 类来 create 代理对象. 而对这个对象所有非 final 方法的调用都会委托给 MethodInterceptor 接口的 intercept, 我们可以在该方法内部写拦截代码, 最后在通过调用 MethodProxy 对象的 invokeSuper() 方法, 把调用转发给真实对象
缺陷
无法对 final 类, 或者 final 方法进行代理
代理模式的性能对比
直接搬运结论: CGLib 底层基于 asm 框架实现, 比 Java 反射性能好, 但是比 JDK 动态代理稍微慢一些
代理模式的缺点
主要是性能问题, 什么增加系统复杂度等都不是事儿. 同等条件, 用代理, 肯定比不用代理要慢一些.
代理模式和装饰器模式对比
实现方式上很像, 但是目标不一样, 后者是为了给类 (对象) 增加新的功能, 不改变 API, 前者除了这些作用, 目标主要是为了使用中间人 (代理角色) 给本类 (对象) 减少负担.
参见: 对复合 (协作) 算法 / 策略的封装方法 -- 装饰模式总结
代理模式的应用
非常常见了, AOP 非常典型, 还有各种框架的拦截器机制, 数据库切换等工具...
来源: https://www.cnblogs.com/kubixuesheng/p/5183782.html