代理设计模式(JDK与CGLIB)

本文详细介绍了Java中的代理模式,包括静态代理和动态代理的概念、实现方法及其优缺点。重点讲解了JDK动态代理和CGLIB动态代理的具体实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 代理模式的定义

    • 给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问
    • 客户不直接操控原对象,而是通过代理对象间接地操控原对象
  • 代理模式UML图:

图片名称

其中:

  • RealSubject 委托对象,Proxy 是代理对象
  • Subject 是委托对象和代理对象都共同实现的接口
  • Request() 是委托对象和代理对象共同拥有的方法

我们为什么要用代理模式呢?换句话说,代理模式的好处是什么?

  • 好处一:我们可以隐藏委托类的实现
  • 好处二:可以使客户端与委托类实现解耦。即在不修改委托类的情况下实现额外的处理。

代理的实现分为

  • 静态代理代理类是在编译时就实现好的。也就是说 Java 编译完成后代理类是一个实际的 class 文件。
  • 动态代理代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时通过Java的反射机制,动态生成类的字节码,并加载到JVM中。

静态代理

看懂了上面的UML图,其实静态代理不难实现。直接先给出代码:

  • 公共接口
public interface Subject{
    void request();
}
  • 委托类
public class RealSubject implements Subject{
    public void request(){
        System.out.println("realSubject request.");
    }
}
  • 代理类:
public class Proxy implements Subject {
    private Subject subject;
    public Proxy(Subject subject) {
        this.subject = subject;
    }
    public void request() {
        System.out.println("BeforeProcess")
        subject.request();
        System.out.println("AfterProcess");
    }
}
  • 客户端:
public class Client{
    public static void main(String args[]){
        RealSubject realSubject = new RealSubject();
        Proxy p = new Proxy(realSubject);
        p.request();
    }
}
  • 输出:
BeforeProcess
realSubject request
AfterProcess

可以看出,静态代理通过聚合来实现,只要让代理类持有一个委托类的引用即可。

静态代理的特点:

  • 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。当然了,这是所以代理模式的共有优点。
  • 缺点:
    • 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
    • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理

(1)JDK动态代理:

Java实现JDK动态代理的大致步骤如下:

  • 首先定义一个委托类公共接口

  • 然后自定义一个调用处理器类(即实现 InvocationHandler 接口的类),这个类的目的是指定运行时将生成的代理类需要完成的具体任务(包括”before process”和”after process”),代理类调用任何方法都会经过这个调用处理器类。

  • 最后生成代理对象(当然也会生成代理类),需要为他指定(1)委托对象、(2)实现的一系列接口、(3)调用处理器类的实例。因此可以看出一个代理对象对应一个委托对象,对应一个调用处理器实例。

先用一个简单的例子实现Java实现JDK动态代理的整个过程:

  • 公共接口:
public interface Subject{
    void request();
}
  • 委托类:
public class RealSubject implements Subject{
    public void request(){
        System.out.println("realSubject request");
    }
}
  • 调用处理器类:
public class ProxyHandler implements InvocationHandler{
    private Subject subject;
    public ProxyHandler(Subject subject){
        this.subject = subject;
    }
    @Override
    public Object invoke(Object subject, Method method, Object[] args)
            throws Throwable {
        System.out.println("before process");
        Object result = method.invoke(subject, args);
        System.out.println("after process");
        return result;
    }
}
  • 客户端:
public class Client{
    public static void main(String[] args) {

       //1.创建委托对象
       RealSubject realSubject = new RealSubject();    

       //2.创建调用处理器对象
       ProxyHandler handler = new ProxyHandler(realSubject);   

       //3.动态生成代理对象
       Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
            RealSubject.class.getInterfaces(), handler);    

       //4.通过代理对象调用方法
       proxySubject.request();    
   }
}
  • 输出:
before process
realSubject request
after process

Jdk 的 java.lang.reflect 包下的 Proxy 类,正是构造代理类的入口。他内部的newProxyInstance 就是创建代理对象的方法,源码如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //如果h为空将抛出异常
        Objects.requireNonNull(h);

        //拷贝被代理类实现的一些接口,用于后面权限方面的一些检查
        final Class<?>[] intfs = interfaces.clone();

        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //这里对某些安全权限进行检查,确保我们有权限对预期的被代理类进行代理
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        //根据类加载器和接口创建代理类!
        Class<?> cl = getProxyClass0(loader, intfs);

        /* 使用指定的调用处理程序获取代理类的构造函数对象 */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //获得代理类的带参数的构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // 假如代理类的构造函数是非共有的,就使用反射来set accessible
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //根据代理类的构造函数来生成代理类的对象并返回
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

上述代码主要做了一下三件事:

  • 1,根据类加载器和接口创建代理类;
  • 2,获得代理类的带参数的构造函数;
  • 3,根据代理类的构造函数来生成代理类的对象(调用处理器实例为参数传入),并返回。

InvocationHandler 接口中有方法:

invoke(Object proxy, Method method, Object[] args)

这个函数是在代理对象调用任何一个方法时都会调用的,方法不同会导致第二个参数method不同,第一个参数是代理对象(表示哪个代理对象调用了method方法),第二个参数是 Method 对象(表示哪个方法被调用了),第三个参数是指定调用方法的参数。

总结一下使用JDK动态生成的代理类的特点

  • 继承 Proxy 类,并实现了在Proxy.newProxyInstance()中提供的接口数组。
  • 代理类是public final的。
  • 命名方式为$ProxyN,其中N会慢慢增加,一开始是 $Proxy1,接下来是$Proxy2。。。
  • 有一个参数为 InvocationHandler 的构造函数。这个从 Proxy.newProxyInstance() 函数内部的clazz.getConstructor(new Class[] { InvocationHandler.class }) 可以看出。
  • 动态代理类相对于静态代理类,当代理类的实现是有很多共性的(重复代码),动态代理的好处在于避免了这些重复代码,只需要关注操作。
  • Java 实现动态代理的缺点:因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),只能针对接口创建代理类,不能针对类创建代理类。

(2)CGLIB动态代理:

上文提到,JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK的动态代理了。cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

下面我们来实现简单的CGLIB动态代理。

首先,我们需要使用maven引入cglib的依赖:

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

下面开始实现cglib动态代理:

  • 委托类:
public class RealSubject {
    public void request() {
        System.out.println("request.");
    }
}

注意,该委托类没有实现接口,所以不能、或者说无法使用JDK动态代理。

  • 实现MethodInterceptor接口的方法拦截器:
public class MyMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object objcet, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before...");
        Object obj = methodProxy.invokeSuper(object, objects);
        System.out.println("after...");
        return obj;
    }
}
  • 客户端:
public class Client {
    public static void main(String[] args) {

        // 利用Enhancer类生成代理类
        Enhancer enhancer = new Enhancer();

        // 继承被代理类
        enhancer.setSuperClass(RealSubject.class);

        // 设置回调
        enhancer.setCallBack(new MyMethodInterceptor());

        // 生成代理对象
        RealSubject real = (RealSubject)enhancer.create();

        // 在调用代理类方法时,会被我们实现的拦截器拦截
        real.request();
    }
}

整个CGLIB动态代理的具体实现步骤大概如下:

  • 1、通过Enhancer类生成代理类Class的二进制字节码,并通过Class.forName加载二进制字节码,生成Class对象;
  • 2、通过反射机制获取实例构造,并初始化代理类对象。
  • 3,代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。
  • 4,在代理方法中判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行增强代理。

CGLIB动态代理的特点

  • 优点:可以在运行时对类或者是接口进行增强操作,委托类无需实现接口,这正好弥补了JDK动态代理的缺点。
  • 缺点:不能对final类以及final方法进行代理。

最后,相信大家对什么时候用JDK动态代理,什么时候用CGLIB动态代理,以及怎么用这两种代理模式,都已经了然了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值