代理模式——实现代理模式三种方式的思考

  代理模式,就是我们使用代理对象来代替真实对象的访问,这样在不改变真实对象的前提下,就可以控制或者增加一些操作,达到扩展真实对象的目的。代理模式的本质就是控制或者增强对象的访问。在实际的开发中,代理模式分为静态代理和动态代理,而动态代理又分为JDK动态代理和Cglib动态代理两种方式。下面对这三种实现方式进行一个总结。

静态代理

  顾名思义,静态代理就是手动的对代理对象编写代理类,在被代理的对象发生变化的时候,代理对象不能动态的改变,需要我们手动的去修改。在静态代理中,实现思路是代理对象和真实的对象实现同样的接口,实现和被代理对象同样的方法,并在实现中增加了对应的业务逻辑,然后代理对象持有一个真实的对象,在使用的过程中,我们实际上使用的是代理对象,由代理对象控制是否访问真实的对象。静态代理需要手动的实现被代理对象的每一个方法,即使需要增加的逻辑是相同的,也不能做到统一去实现,这也是动态代理和静态代理最本质上的区别。

示例代码

  1. 创建一个需要增强的对象的接口Subject
public interface Subject {
    String request();
}
  1. 实现一个真实的目标对象
public class RealSubject implements Subject {
    @Override
    public String request() {
        System.out.println("this is RealSubject:request()");
        return "RealSubject:request()";
    }
}
  1. 实现代理对象,代理对象需要持有一个被代理的对象,同时实现和被代理对象相同的接口,并实现每个方法的增强逻辑
public class StaticProxy implements Subject{
    private Subject subject;
    public StaticProxy(Subject subject){
        this.subject = subject;
    }

    @Override
    public String request() {
        beforeRequest();
        String result = this.subject.request();
        afterRequest();
        return result;
    }

    private void afterRequest() {
        System.out.println("after request!");
    }

    private void beforeRequest() {
        System.out.println("before request!");
    }

}
  1. 实现一个测试类Client,在调用的过程中,我们需要先创建一个真实的对象,然后再创建一个代理对象,代理对象持有真实的对象,实际调用的过程中,我们实际上是调用的代理对象的实例。
public class Client {

    @Test
    public void testStaticProxy() {
        // 创建代理对象,用来代理真正的目标对象
        Subject subject = new StaticProxy(new RealSubject());
        String result = subject.request();
        System.out.println("-----------------------------------");
        System.out.println("result= " + result);
    }
}
  1. 结果如下:
before request!
this is RealSubject:request()
after request!
-----------------------------------
result= RealSubject:request()

静态代理的uml类图如下

代理模式——实现代理模式三种方式的思考_第1张图片

JDK动态代理

  JDK动态代理是面向接口的代理,需要被代理的对象实现一个接口,jdk会将这个接口中的所有方法进行统一的加强,加强的逻辑就是代理类,这个过程中,代理类不再要求和被代理对象实现相同的接口,而是要实现jdk的InvocationHandler接口,在invoke方法中统一的实现加强的逻辑。jdk的动态代理实现原理就是jdk给我们生成了一个新的class对象,这个对象实现了和被代理对象相同的接口(这就是为什么要求要被代理对象要有一个接口的原因),在新创建的对象中,jdk将我们实现的增强代码和原来的代码放在了一起,生成了一个新的方法,这样就做到了对被代理对象的增强。

代码示例如下:

  1. 先创建一个被代理对象的接口
public interface JdkSubject {
    String request();
}
  1. 根据这个接口实现真正被代理对象
public class JdkRealSubject implements JdkSubject {

    private String name;

    public JdkRealSubject() {
    }

    public JdkRealSubject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String request() {
        System.out.println("this is JdkRealSubject:request()");
        return "JdkRealSubject:request():" + (null == name ? "" : name);
    }
}
  1. 实现代理对象。在实现代理对象的过程中,代理对象同样需要持有一个被代理的对象,不同于静态代理的是,我们需要用jdk的Proxy.newProxyInstance()方法给真实的对象创建一个新的代理对象(实现了InvocationHandler接口的对象),并在这个代理对象中实现增强的逻辑。在示例代码中,我们将代理对象和获得代理对象的逻辑进行了统一,实现在一个类JdkProxy中
public class JdkProxy implements InvocationHandler {
    private JdkSubject subject;

    public JdkProxy(JdkSubject subject) {
        this.subject = subject;
    }

    public Object getProxyInstance() throws Exception {
        return Proxy.newProxyInstance(this.subject.getClass().getClassLoader(), this.subject.getClass().getInterfaces(),
            this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeRequest();
        Object invoke = method.invoke(this.subject, args);
        afterRequest();
        return invoke;
    }

    private void afterRequest() {
        System.out.println("after request!");
    }

    private void beforeRequest() {
        System.out.println("before request!");
    }
}
  1. 编写一个测试类
public class JdkClient {

    @Test
    public void testJdkProxy() throws Exception {
        // 创建代理对象,用来代理真正的目标对象
        JdkSubject subject = (JdkSubject) (new JdkProxy(new JdkRealSubject())).getProxyInstance();
        String result = subject.request();
        System.out.println("-----------------------------------");
        System.out.println("result= " + result);
    }
}
  1. 测试结果如下
before request!
this is JdkRealSubject:request()
after request!
-----------------------------------
result= JdkRealSubject:request():

  在JDK的动态代理中,如果真实的对象有含参的构造函数或者getter和setter方法怎么弄呢?
  要回答这个问题,我们需要从两个方面来看待:

    1. 首先JDK的动态代理是基于接口的,所以getter和setter方法如果不在接口中,自然是不会被增强的
    1. 因为JDK动态代理需要持有一个真实的对象,所以真实的对象是在我们客户端来创建的,并不是在代理类中创建的,所以在有构造函数的时候,我们在客户端创建就好。
  1. 我们写一个带有构造函数的测试方法如下:
 @Test
    public void testJdkProxyHasConstructArgus() throws Exception {
        // 创建代理对象,用来代理真正的目标对象
        JdkSubject subject = (JdkSubject) (new JdkProxy(new JdkRealSubject("name"))).getProxyInstance();
        String result = subject.request();
        System.out.println("-----------------------------------");
        System.out.println("result= " + result);
    }
  1. 测试结果如下
before request!
this is JdkRealSubject:request()
after request!
-----------------------------------
result= JdkRealSubject:request():name

JDK动态代理的uml类图如下

代理模式——实现代理模式三种方式的思考_第2张图片

Cglib动态代理

  Cglib动态代理不强制要求被代理对象实现任何的接口,只要是一个正常的类就可以,其实现原理是cglib会生成一个代理类,这个类继承了需要被代理的类,然后把增强逻辑和被代理的对象的方法都写入到新的代理类中,新生成的代理类会重写除了私有和final的所有方法。

代码示例

  1. 我们创建一个真实的对象,这个对象不需要创实现一个接口
public class CglibSubjectClass {

    private String name;

    public CglibSubjectClass() {
    }

    public CglibSubjectClass(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String request() {
        System.out.println("this is JdkRealSubjectCglibSubjectClass:request()");
        return "CglibSubjectClass:request():" + (null == name ? "" : name);
    }
}
  1. 我们实现一个代理对象,这个对象需要实现cglib的MethodInterceptor接口,将增强的代码放到intercept()方法中,在使用中,我们需要用cglib的Enhancer类创建一个代理对象,然后使用这个代理对象。在示例中,我们将这两个功能实现在一个类CglibProxy中,
public class CglibProxy implements MethodInterceptor {

    /**
     * 没有构造函数参数的构建代理方式
     *
     * @return :
     */
    public static CglibSubjectClass getSubjectClassProxy() {
        // 创建Enhancer对象,用来生成代理类
        Enhancer enhancer = new Enhancer();
        // 设置需要继承的类
        enhancer.setSuperclass(CglibSubjectClass.class);
        // 设置代理的回调方法
        enhancer.setCallback(new CglibProxy());
        return (CglibSubjectClass) enhancer.create();
    }

    /**
     * 有构造函数参数的创建代理方式
     *
     * @param argumentTypes :
     * @param arguments :
     * @return :
     */
    public static CglibSubjectClass getSubjectClassProxy(Class[] argumentTypes,
        Object[] arguments) {
        // 创建Enhancer对象,用来生成代理类
        Enhancer enhancer = new Enhancer();
        // 设置需要继承的类
        enhancer.setSuperclass(CglibSubjectClass.class);
        // 设置代理的回调方法
        enhancer.setCallback(new CglibProxy());
        return (CglibSubjectClass) enhancer.create(argumentTypes, arguments);
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        beforeRequest();
        // 这里需要调用invokeSuper方法才能调用到真正的对象
        Object invoke = methodProxy.invokeSuper(o, objects);
        afterRequest();
        return invoke;
    }

    private void afterRequest() {
        System.out.println("after request!");
    }

    private void beforeRequest() {
        System.out.println("before request!");
    }
}
  1. 测试方法
public class CglibClient {

    /**
     * 没有构造函数的方式
     *
     * @throws Exception :
     */
    @Test
    public void testCglibProxyNoConstructArguments() throws Exception {
        // 创建代理对象,用来代理真正的目标对象
        CglibSubjectClass subject = CglibProxy.getSubjectClassProxy();
        String result = subject.request();
        System.out.println("-----------------------------------");
        System.out.println("result= " + result);
    }
 }
  1. 执行结果
before request!
this is JdkRealSubjectCglibSubjectClass:request()
after request!
-----------------------------------
result= CglibSubjectClass:request():

  同样的道理,cglib对于真实对象如果有构造方法和getter和setter方法是如何操作的呢?
  我们可以从一下两个方面进行分析

    1. cglib是使用Enhancer类来创建代理对象的,所以如果构造函数中有参数,也需要调用Enhancer类带参数的方法来创建,这个方法已经在上面的CglibProxy类中,我们单独抽出来看看:
  /**
     * 有构造函数参数的创建代理方式
     *
     * @param argumentTypes :
     * @param arguments :
     * @return :
     */
    public static CglibSubjectClass getSubjectClassProxy(Class[] argumentTypes,
        Object[] arguments) {
        // 创建Enhancer对象,用来生成代理类
        Enhancer enhancer = new Enhancer();
        // 设置需要继承的类
        enhancer.setSuperclass(CglibSubjectClass.class);
        // 设置代理的回调方法
        enhancer.setCallback(new CglibProxy());
        return (CglibSubjectClass) enhancer.create(argumentTypes, arguments);
    }

在CglibClient中编写一个测试函数

    /**
     * 有构造函数参数的调用方式
     *
     * @throws Exception :
     */
    @Test
    public void testCglibProxyHasConstructArguments() throws Exception {
        // 创建代理对象,用来代理真正的目标对象
        String name = "name";
        CglibSubjectClass subject = CglibProxy.getSubjectClassProxy(new Class[] {name.getClass()}, new Object[] {name});
        String result = subject.request();
        System.out.println("-----------------------------------");
        System.out.println("result= " + result);
    }

执行结果如下:

before request!
this is JdkRealSubjectCglibSubjectClass:request()
after request!
-----------------------------------
result= CglibSubjectClass:request():name
    1. cglib是对类的增强,对其中非final的共有方法都进行了增强,如果被代理的对象中getter和setter方法是public的非final,自然也会增强,拥有增强的逻辑(这就很奇怪,但是确实如此)
      在CglibClient中编写一个测试函数
    /**
     * 用setter和getter的方式设置值
     *
     * @throws Exception :
     */
    @Test
    public void testCglibProxyHasGetterAndSetter() throws Exception {
        // 创建代理对象,用来代理真正的目标对象
        CglibSubjectClass subject = CglibProxy.getSubjectClassProxy();
        String result = subject.request();
        System.out.println("-----------------------------------");
        System.out.println("result= " + result);
        System.out.println("---------执行setter方法---------");
        subject.setName("name");
        System.out.println("---------执行getter方法---------");
        System.out.println("name=" + subject.getName());
    }

执行结果如下:

before request!
this is JdkRealSubjectCglibSubjectClass:request()
after request!
-----------------------------------
result= CglibSubjectClass:request():
---------执行setter方法---------
before request!
after request!
---------执行getter方法---------
before request!
after request!
name=name

从结果中我们可以清楚的看到,不论是setter还是getter方法,cglib都给我们进行了增强,所以我们应该明白Spring为什么不推荐用setter方法来注入对象,而是推荐使用构造方法的方式了吧,因为spring的aop默认就是用的cglib的实现方式,所以如果采用setter方法注入方式,会写很多多余的二进制代码。

Cglib动态代理的uml类图如下

代理模式——实现代理模式三种方式的思考_第3张图片


后记
  个人总结,欢迎转载、评论、批评指正

你可能感兴趣的:(设计模式,代理模式,java)