关于后端:静态代理还是动态代理来聊聊Java中的代理设计模式

38次阅读

共计 5448 个字符,预计需要花费 14 分钟才能阅读完成。

代理模式(Proxy Design Pattern)是一种结构型设计模式,为一个对象提供一个代理对象,而后应用代理对象管制对原对象的援用。即通过代理对象拜访指标对象。被代理的对象能够是近程对象、创立开销大的对象或须要安全控制的对象。

一、代理模式介绍

代理模式次要有两个局部:

  • 形象主题:申明一个公共接口,给代理类和实在对象进行实现。让实在对象和代理对象一一对应
  • 实在主题:定义所要代理的实在对象,其中包含理论的业务逻辑和操作
  • 代理主题:首先代理对象有实在对象的所有接口,保障能齐全代替实在对象。此外还会有其余的办法和操作,来扩大相干的性能

其次要构造的 UML 图如下所示:

  1. Subject:形象主题类,通过接口或抽象类申明主题和代理对象实现的业务办法
  2. RealSubject:实在主题类,实现 Subject 中的具体业务,是代理对象所代表的实在对象
  3. ProxySubject:代理类,其外部含有对实在主题的援用,它能够拜访、管制或扩大 RealSubject 的性能
  4. Client:客户端,通过应用代理类来拜访实在的主题类

依照下面的类图,能够实现如下代码:

// 主题类接口
public interface Subject {void Request();
}

// 实在的主题类
public class RealSubject implements Subject{

    @Override
    public void Request() {System.out.println("我是实在的主题类");
    }
}

// 代理类
public class Proxy implements Subject{private RealSubject realSubject = new RealSubject(); // 保障对实在对象的援用
    
    public void PreRequest(){....// 调用 Request()前的操作
    }
    
    @Override
    public void Request() {PreRequest();
        // 对实在对象的调用
        realSubject.Request();
        postRequest();}
    
    public void PostRequest(){....// 调用 Request()后的操作
    }
    
  
}

// 客户端
public class Client {public static void main(String[] args) {Proxy proxy = new Proxy();
        proxy.Request();}
}

代理模式有比拟宽泛的应用,比方Spring AOPRPC、缓存等。在 Java 中,依据代理的创立期间,能够将代理模式分为动态代理和动静代理,上面就来别离论述。

二、代理模式实现

动静代理和动态代理的辨别就是语言类型是在运行时查看还是在编译期查看。大白话就是动态代理中的代理类是程序员本人写的,动静代理中的代理类程序员不必写,在代码运行过程中它能主动生成。

2.1 动态代理

动态代理是指在编译期,也就是在 JVM 运行之前就曾经获取到了代理类的字节码信息。即 Java 源码生成 .class 文件期间:

因为在 JVM 运行前代理类和实在主题类曾经是确定的,因而也被称为动态代理。

在理论应用中,通常须要定义一个公共接口及其办法,被代理对象(指标对象)与代理对象一起实现雷同的接口或继承雷同的父类。也就是咱们本人须要结构一个代理类。在理论的日常开发中,简直看不到应用动态代理的场景。

2.2 动静代理

动静代理,也就是在 JVM 运行期间动静构建对象和动静调用代理办法。

罕用的实现形式是反射。反射机制 是指程序在运行期间能够拜访、检测和批改其自身状态或行为的一种能力,应用反射咱们能够调用任意一个类对象,以及其中蕴含的属性及办法。比方 JDK Proxy。

此外动静代理也能够通过 ASM(Java 字节码操作框架)来实现。比方 CGLib。

2.2.1 JDK Proxy

这种形式是 JDK 本身提供的一种形式,它的实现不须要援用第三方类,只须要实现 InvocationHandler 接口,重写 invoke() 办法即可。代码实现如下所示:

public class ProxyExample {

    static interface Car {void running();
    }
    static class Bus implements Car {
        @Override
        public void running() {System.out.println("bus is running");
        }
    }
    static class Taxi implements Car {
        @Override
        public void running() {System.out.println("taxi is runnig");
        }
    }
    // 外围局部 JDK Proxy 代理类
    static class JDKProxy implements InvocationHandler {
        private Object target;

        public Object getInstance(Object target) {
            this.target = target;
            // 取得代理对象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
         }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = method.invoke(target, args);
            return result;
        }
    }

    public static void main(String[] args) {JDKProxy jdkProxy = new JDKProxy();
        Car instance = (Car) jdkProxy.getInstance(new Taxi());
        instance.running();}
}

动静代理的外围是实现 Invocation 接口,咱们再看看 Invocation 接口的源码:

public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

实际上是通过 invoke() 办法来触发代理的执行办法。最终使得实现 Invocation 接口的类具备动静代理的能力。

动静代理的益处在于不须要和动态代理一样提前写好公共的代理接口,只须要实现 Invocation 接口就可领有动静代理能力。上面咱们再来看看 CGLib 是如何实现的

2.2.2 CGLib

CGLib 动静代理采取的是创立指标类的子类的形式,通过子类化,咱们能够达到近似应用被调用者自身的成果。其实现代码如下所示:

public class CGLibExample {

    static class car {public void running() {System.out.println("car is running");
        }
    }

    static class CGLibProxy implements MethodInterceptor {
        private Object target;

        public Object getInstance(Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            // 设置父类为实例类
            enhancer.setSuperclass(this.target.getClass());
            // 回调办法
            enhancer.setCallback(this);
            // 创立代理对象
            return enhancer.create();}
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    }

    public static void main(String[] args) {CGLibProxy cgLibProxy = new CGLibProxy();
        car instance = (car) cgLibProxy.getInstance(new car());
        instance.running();}
}

从代码能够看出 CGLib 也是通过实现代理器的接口,而后再调用某个办法实现动静代理,不同的是 CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动静代理:

Enhancer enhancer = new Enhancer();
// 设置父类为实例类
enhancer.setSuperclass(this.target.getClass());
// 回调办法
enhancer.setCallback(this);
// 创立代理对象
return enhancer.create();

2.2.3 JDK Proxy 和 CGLib 的区别

  1. 起源:JDK Proxy 是 JDK 自带的性能,CGLib 是第三方提供的工具
  2. 实现:JDK Proxy 通过拦截器加反射的形式实现;CGLib 基于 ASM 实现,性能比拟高
  3. 接口:JDK Proxy 只能代理继承接口的类,CGLib 不须要通过接口来实现,它是通过实现子类的形式来实现调用

三、代理模式的利用场景

代理模式其实在日常的开发中并不常见,然而在一些框架中应用的很多,比方 springAop, myBatis mapper, 近程代理等等

3.1 MapperProxyFactory

在 MyBatis 中,也存在着代理模式的应用,比方 MapperProxyFactory。其中的newInstance() 办法就是生成一个具体的代理来实现性能,代码如下:

public class MapperProxyFactory<T> {
  private final Class<T> mapperInterface;
    
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    
  public MapperProxyFactory(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}

  public Class<T> getMapperInterface() {return mapperInterface;}

  public Map<Method, MapperMethodInvoker> getMethodCache() {return methodCache;}

  // 创立代理类
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

3.2 Spring AOP

代理模式最常应用的一个利用场景就是在业务零碎中开发一些非功能性需要,比方监控、统计、鉴权、限流、事务、日志等。将这些附加性能与业务性能解耦,放在代理类中对立解决,让程序员只须要关注业务方面的开发。而 Spring AOP 的切面实现原理就是基于动静代理

Spring AOP 的底层通过下面提到的 JDK Proxy 和 CGLib 动静代理机制,为指标对象执行横向织入。当 Bean 实现了接口时,Spring 就会应用 JDK Proxy,在没有实现接口时就会应用 CGLib。也能够在配置中强制应用 CGLib:

<aop:aspectj-autoproxy proxy-target-class="true"/>

3.3 近程代理

java 中的 RMI(Remote Method Invocation),比方

RPC 框架的实现能够看作成是一种代理模式,通过近程代理、将网络同步、数据编解码等细节暗藏起来,让客户端在应用 RPC 服务时,不用思考这些细节。

四、代理模式实战

4.1 利用动态代理模式实现模仿拜访网页性能

这里能够应用动态代理模式来实现模仿拜访网页的性能

4.2 利用

参考资料

http://c.biancheng.net/view/1359.html

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/de…

https://time.geekbang.org/column/article/7489

https://time.geekbang.org/column/article/201823

《Java 重学设计模式》

《大话设计模式》

正文完
 0