Java设计模式——代理模式

一、概述

代理模式的定义:

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

二、角色职责与UML

2.2 UML

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZkpEyNRj-1655547197322)(media/16553608911791/16553647038850.jpg)]

2.3 角色与职责

Subject(抽象主体)

  • 定义需要被代理的方法

RealSubject(真实主体)

  • 实现抽象主体的方法

Proxy

  • 包含一个真实主体对象
  • 实现真实主体被代理的方法并对其进行代理

上面所述的代理模式称为静态代理,代理的对象都是固定的,拓展性有限,当你有n个不同的被代理类时,就会有n个不同的代理类,很是麻烦。
而且,静态代理从结构上来讲就是装饰器模式,并没有什么分别,学习代理模式学习的重点也不是静态代理。
如果不清楚装饰器模式的可以去看我的另一篇博客Java设计模式————装饰器模式

而我们的业务中不可能每次都是一个两个的被代理,所以动态代理就出现了,为的就是可以在极少的代码量的情况下可以代理我们几乎所有的被代理类,并且保持良好的扩展性
下面将会介绍我们在开发中常用的两种代理模式,JDK动态代理和Cglib动态代理

三、分类

3.1 JDK动态代理

JDK动态代理是Java自己为我们提供的,本质上JDK动态代理是通过判断代理类实现的接口来获取到需要代理的方法的,所以如果我们要使用JDK动态代理,那么代理类必须实现了某个接口。

JDK动态代理依托于两个类:Proxy和InvocationHandler这两个类
InvocationHandler的作用在于定义我们代理的方法的拦截逻辑
Proxy的作用在于根据被代理对象的类加载器、实现的接口以及代理方法的拦截逻辑来创建一个被代理对象的代理对象

好了,Talk is cheap,Show you the code.下面展示一个JDK动态代理的例子

首先随便写一个接口

/**
 * @author ZhongJing </p>
 * @Description 被代理对象实现的接口,本质上来讲对于JDK动态代理来讲,被代理类实现了什么接口我们才可以代理什么接口 </p>
 */
public interface HelloWorld {

    String sayHello();

}

然后写我们的被代理类,被代理类实现我们上述的接口

/**
 * @author ZhongJing </p>
 * @Description 我们的目标被代理类
 */
public class Hello implements HelloWorld{

    @Override
    public String sayHello() {
        System.out.println("Hello");
        return "Hello";
    }
}

接下来是我们写的JDK万能代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author ZhongJing </p>
 * @Description JDK动态代理类 </p>
 */
public class JdkProxy<T> implements InvocationHandler {

    /**
     * 被代理对象
     */
    private T proxiedTarget;

    /**
     * 构造器,用来接收被代理对象
     */
    public JdkProxy(T proxiedTarget) {
        this.proxiedTarget = proxiedTarget;
    }

    /**
     * 获取被代理对象的代理对象
     */
    public static <T> T getProxy(T t) {
        /*
            ClassLoader loader:当前被代理对象的类加载器
            Class<?>[] interfaces:当前被代理对象实现的所有接口
            InvocationHandler h:被代理对象执行目标方法的时候可以定义拦截增强方法
         */
        Object proxy = Proxy.newProxyInstance(
                t.getClass().getClassLoader(),
                t.getClass().getInterfaces(),
                new JdkProxy<T>(t)
        );

        return (T) proxy;
    }

    /**
     * 定义目标方法在执行期间的拦截逻辑,会拦截被代理对象的每一个方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 目标方法执行前的逻辑
        System.out.println("方法执行前……");

        // 反射执行
        Object result = method.invoke(proxiedTarget, args);
        System.out.println("目标方法返回值:" + result);

        // 目标方法执行后的逻辑
        System.out.println("方法执行后……");

        return null;
    }
}

最后编写一个测试类来测试我们的代理是否成功
需要注意的是,我们并不能代理被代理对象本身的方法,就算是他Override接口的方法也不行,类型必须是一个接口类型

/**
 * @author ZhongJing </p>
 * @Description 测试JDK代理 </p>
 */
public class MainTest {

    public static void main(String[] args) {

        Hello hello = new Hello();

        HelloWorld proxy = JdkProxy.getProxy(hello);

        // 不能代理被代理对象自己的方法,只能转成被代理对象的接口类型,不然会报错
//        Hello proxy = JdkProxy.getProxy(hello);

        proxy.sayHello();

    }

}

测试结果:

方法执行前……
Hello
目标方法返回值:Hello
方法执行后……

3.2 Cglib动态代理

Cglib动态代理并不依赖于被代理类的接口,换而言之就是Cglib不需要我们的被代理类必须去实现某一个接口。
他的底层是以操作字节码的方式在底层创建一个类,让我们的代理类直接去继承目标被代理类,这个代理类会重写目标代理类中所有非final的方法,并且对父类的所有方法进行拦截,从而实现我们的拦截逻辑。

下面继续show you the code

在show you the code前,我们需要引入cglib的包,因为我使用的是maven工程,所以就直接引入依赖了

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

首先创建一个被代理类,就简单一些,只是为了演示Cglib动态代理,被代理类就随便写了

/**
 * @author ZhongJing </p>
 * @Description 目标被代理类 </p>
 */
public class Hello {

    public String sayHello() {
        System.out.println("Hello");
        return "Hello";
    }

}

编写Cglib动态代理类

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author ZhongJing </p>
 * @Description Cglib动态代理类 </p>
 */
public class CglibProxy {

    public static <T> T createProxy(T t) {

        // 创建增强器
        Enhancer enhancer = new Enhancer();

        // 设置父类,传入我们的被代理类,增强其会为被代理类创建一个子类
        enhancer.setSuperclass(t.getClass());

        // 设置回调方法
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 在这里编写拦截逻辑
             */
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

                System.out.println("方法执行前……");

                // 执行目标方法
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("代理方法:" + method.getName() + ",返回值:" + result);

                System.out.println("方法执行后……");

                return result;
            }
        });

        Object o = enhancer.create();
        return (T) o;
    }

}

测试Cglib动态代理

/**
 * @author ZhongJing </p>
 * @Description Cglib动态代理测试类 </p>
 */
public class MainTest {

    public static void main(String[] args) {

        Hello hello = new Hello();

        Hello proxy = CglibProxy.createProxy(hello);

        proxy.sayHello();

    }

}

测试结果:

方法执行前……
Hello
代理方法:sayHello,返回值:Hello
方法执行后……

四、总结

静态代理就不需要说了,静态代理只适合代理某个特定的类,使用场景极其有限,且与装饰器模式大同小异。
JDK动态代理和Cglib动态代理并没有明显的优劣之分,在我们熟悉的Spring中这两个也都有使用的地方,只能说看场景决定吧。
JDK动态代理的实现基于接口,否则无法使用JDK动态代理。
Cglib动态代理基于继承,相比于JDK动态代理使用范围会大一些


版权声明:本文为qq_38762212原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。