静态代理与动态代理详解

本文详细介绍了代理模式,包括静态代理和动态代理。静态代理通过手动创建代理类实现,适用于简单场景,但当有多个代理对象时,代码会变得冗余。动态代理则能在运行时动态创建,提供了更大的灵活性。Java动态代理基于反射实现,局限于接口,而CGLib通过字节码技术实现,能代理无接口类。

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

一、代理概念

Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题。比如,spring aop思想,就是通过代理对相应方法增强,实现代码简化,统一管理等;

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

按照代理的创建时期,代理类可以分为两种:

  • 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
  • 动态代理:在程序运行时运用反射机制动态创建而成。(java自身的reflect proxy, 非cglib)

二、静态代理实现方式

一个典型的代理模式通常有三个角色(共同接口、真实对象、代理对象),这里称之为代理三要素

1、共同接口

public interface Action {
    public void doSomething();
}

2、真实对象

public class RealObject implements Action{
    public void doSomething() {
        System.out.println("realObject  do something");
    }
}

3、代理对象

// Action 的静态工厂
public class ActionFactory{
	public static Action createActionObj(String key){
		Action ac = null;
		switch(key){
			case "real":
				ac = new RealObject();
				break;
			case "..."	
				...;
		}
	}
}

public class Proxy implements Action {
    private Action realObject;
    public Proxy(String key) {
        this.realObject = ActionFactoy.createActionObj(key);
    }
    public void doSomething() {
        System.out.println("proxy do something");
        realObject.doSomething();
    }
}

4、调用运行

  Action action = new Proxy("real");
  action .doSomething();
  
  // =======result========
  // proxy do something
  // realObject  do something

从上面代码分析看,静态代理的优缺点:

优点:

  • 扩展原功能,不侵入原代码。
  • 代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,应用工厂将它隐藏。

缺点: 若有多个对象代理,要么编写多个proxy,要么就在一个proxy实现多个代理对象的方法而使自身膨胀 冗余;

三、动态代理实现方式

先上代码:

//做蛋糕的机器
public interface cakeMachine {
    void makeCake();
}

//专门做水果蛋糕的机器,并且加上一层杏仁
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("making a Fruit Cake...");
        System.out.println("adding apricot...");
    }
}

虽然上面这种方式实现了我们的业务需求。但是仔细想一想,在现实生活中如果我们遇到这样的一个需求,我们不可能因为一个顾客的特殊需求就去修改一台蛋糕机的硬件程序,这样成本太高!而且从代码实现角度上来说,这种方式从代码上不是很优雅,修改了原来的代码。根据代码圈中「对修改封闭、对扩展开放」的思想,我们在尝试满足新的业务需求的时候应该尽量少修改原来的代码,而是在原来的代码上进行拓展。

那我们究竟应该怎么做更加合适一些呢?我们肯定是直接用水果蛋糕机做一个蛋糕,然后再人工撒上一层杏仁啦。我们需要做的,其实就是设计一个杏仁代理类(ApricotCakeProxy),这个代理类就完成撒杏仁这个动作,之后让蛋糕店直接调用即可代理类去实现即可。

//杏仁蛋糕代理
public class ApricotCakeProxy implements CakeMachine{
    private CakeMachine cakeMachine;
    public ApricotCakeProxy(CakeMachine cakeMachine) {
        this.cakeMachine = cakeMachine;
    }
    public void makeCake() {
        cakeMachine.makeCake();
        System.out.println("adding apricot...");
    }
}

//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
          //可以给各种各样的蛋糕加上杏仁
          FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotCakeProxy apricotProxy = new ApricotCakeProxy(fruitCakeMachine);
        apricotProxy.makeCake();
        apricotProxy = new ApricotCakeProxy(new ChocolateCakeMachine());
        apricotProxy.makeCake();
    }
}

我们可以看到我们也成功地做出了客人想要的杏仁红豆面包、杏仁葡萄干面包。

对于客人来说,他肯定希望我们所有的产品都有一层杏仁,这样客人最喜欢了。为了满足客人的需求,那如果我们的产品有 100 种(饼干、酸奶等),我们是不是得写 100 个代理类呢(这就是典型静态代理的缺点)?有没有一种方式可以让我们只写一次实现(撒杏仁的实现),但是任何类型的产品(蛋糕、面包、饼干、酸奶等)都可以使用呢?其实在 Java 中早已经有了针对这种情况而设计的一个接口,专门用来解决类似的问题,它就是动态代理 —— InvocationHandler。

动态代理与静态代理的区别是静态代理只能针对特定一种产品(蛋糕、面包、饼干、酸奶)做某种代理动作(撒杏仁),而动态代理则可以对所有类型产品(蛋糕、面包、饼干、酸奶等)做某种代理动作(撒杏仁)。

接下来我们针对这个业务场景做一个代码的抽象实现。首先我们分析一下可以知道这种场景的共同点是希望在所有产品上都做「撒一层杏仁」的动作,所以我们就做一个杏仁动态代理(ApricotHandler)。

//杏仁动态代理
public class ApricotHandler implements InvocationHandler{

    private Object object;

    public ApricotHandler(Object object) {
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);    //调用真正的蛋糕机做蛋糕
        System.out.println("adding apricot...");
        return result;
    }
}

撒杏仁的代理写完之后,我们直接让蛋糕店开工:

public class CakeShop {
    public static void main(String[] args) {
        //动态代理给蛋糕加上杏仁
        FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotHandler apricotHandler = new ApricotHandler(fruitCakeMachine);
        CakeMachine cakeMachine = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
                fruitCakeMachine.getClass().getInterfaces(),
                apricotHandler);
        cakeMachine.makeCake();
    }
}

通过上面代码,那我们以后不管做什么味的蛋糕,只需要编写一个invocationHandler的实现类即可。这个其实可以是使用匿名内部类,即使用时编写,极大方便业务扩展了。

动态代理其实指的是一种设计模式概念,指的是通过代理来做一些通用的事情,常见的应用有权限系统、日志系统等,都用到了动态代理。

四、动态代理的2种实现方式

1、Java 动态代理实现方式
2、CGLib(Code Generation Library)

Java 动态代理只能针对实现了接口的类进行拓展,所以细心的朋友会发现我们的代码里有一个叫 MachineCake 的接口。而 CGLib 则没有这个限制,因为 CGLib 是使用继承原有类的方式来实现代理的。

CGLib 是如何实现动态代理呢?还是前面的例子:我们要做杏仁水果蛋糕、巧克力水果蛋糕、五仁巧克力蛋糕,这时候用代码描述是这样的。

首先我们需要写一个杏仁拦截器类,这个拦截器可以给做好的蛋糕加上杏仁。

public class ApricotInterceptor implements MethodInterceptor {
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        methodProxy.invokeSuper(o, objects);
        System.out.println("adding apricot...");
        return o;
    }
}

接着直接让蛋糕店使用 CGLib 提供的工具类做杏仁水果蛋糕:

public class CakeShop {
    public static void main(String[] args) { 
        //CGLib动态代理(可以同时给蛋糕、面包等加杏仁)
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FruitCakeMachine.class);
        enhancer.setCallback(new ApricotInterceptor());
        FruitCakeMachine fruitCakeMachine = (FruitCakeMachine) enhancer.create();
        fruitCakeMachine.makeCake();
    }
}

上面的 enhancer.setSuperClass() 设置需要增强的类,而 enhancer.setCallback() 则设置需要回调的拦截器,即实现了 MethodInterceptor 接口的类。最后最后使用 enhancer.create() 生成了对应的增强类.

了解动态代理的优势及2种实现方式,下面就深入了解2种动态代理的原理。

五、Java动态代理的原理

从上面的例子我们可以知道,Java 动态代理的入口是从 Proxy.newInstance() 方法中开始的,那么我们就从这个方法开始边剖析源码边理解其原理。

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs); // 1、通过getProxyClass0()获得代理类的class对象

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams); // 2、 反射方式,获得代理类构造方法
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true); // 3、代理类Class构造器作用域为私有,通过 setAccessible 支持访问
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h}); // 4、通过构造器对象newInstance()创建代理对象的实例
        } catch (IllegalAccessException|InstantiationException e) {
       		 ...
        }
    }

从 Proxy.newInstance() 的源码我们可以看到首先调用了 getProxyClass0 方法,该方法返回了一个 Class 实例对象,该实例对象其实就是 ApricotHandler 的 Class 对象。接着获取其构造方法对象,最后生成该 Class 对象的实例。其实这里最主要的是 getProxyClass0() 方法,这里面动态生成了 ApricotHandler 的 Class 对象。下面我们就深入到 getProxyClass0() 方法中去了解这里面做了什么操作。

    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces); 
    }

getProxyClass0() 方法首先是做了一些参数校验,之后从 proxyClassCache 参数中取出 Class 对象。其实 proxyClassCache 是一个 Map 对象,缓存了所有动态创建的 Class 对象。从源码中的注释可以知道,如果从 Map 中取出的对象为空,那么其会调用 ProxyClassFactory 生成对应的 Class 对象。

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
      ...
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            ....
            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

在 ProxyClassFactory 类的源码中,最终是调用了 ProxyGenerator.genrateProxyClass() 方法生成了对应的 class 字节码文件。
到这里,我们已经把动态代理的 Java 源代码都解析完了,现在思路就很清晰了。

六、CGLib动态代理的原理

因为 JVM 并不允许在运行时修改原有类,所以所有的动态性都是通过新建类来实现的,上面说到的 Java 动态代理也不例外。所以对于 CGLib 动态代理的原理,其实也是通过动态生成代理类,最后由代理类来完成操作实现的。

对于 CGLib 动态代理的实现,我并没有深入到源码中,而是通过查阅资料了解了其大概的实现原理。

首先,我们在使用的时候通过 enhancer.setSuperclass(FruitCakeMachine.class) 传入了需要增加的类,CGLib 便会生成一个继承了改类的代理类。
接着,我们通过 enhancer.setCallback(new ApricotInterceptor()) 传入了代理类对象,CGLib 通过组装两个类的结构实现一个静态代理,从而达到具体的目的。
而在 CGLib 生成新类的过程中,其使用的是一个名为 ASM 的东西,它对 Java 的 class 文件进行操作、生成新的 class 文件。如果你对 CGLib 的原理感兴趣,不妨看看这篇文章:从兄弟到父子:动态代理在民间是怎么玩的?

参考:
https://www.imooc.com/article/details/id/21339

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值