设计模式-代理模式

本文介绍了Java中的代理模式,包括静态代理和动态代理。通过一个生活中的购票场景展示了代理模式的应用,并详细解释了静态代理的实现过程。接着探讨了动态代理,指出静态代理的冗余问题,并通过Proxy类的API演示了动态代理的创建。最后,分析了Proxy.newProxyInstance()的源码实现,以及动态代理在AOP中的应用。

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

java的代理模式是很常见的一种设计模式,记录一下我的理解。源码

代理模式主要的思想是将委托类(被代理类)和真正使用委托类逻辑的类做一个解耦。
主要有2个好处:
	1.保护目标对象:被代理类无需对外公开,外部通信仅通过代理类进行沟通
	2.增强目标对象:可以在不改动被代理类的情况下对所需功能做增强,外部调用方对此也无感知

不考虑很多根据业务场景的分类(比如远程代理、虚拟代理、保护代理等等等…),我们只考虑编码实现方案那就分为2种:

  1. 静态代理
  2. 动态代理

对于真正使用代理的场景有很多,例如Android中Resource与ResourceImpl、Retrofit中获取api接口、Collections.synchronizedList实现线程安全数组等场景。这里暂时不展开说明,如有兴趣再行记录。

举个生活中的例子:

假设你爸让你帮他买张机票此时你想在你爸不知情的情况下赚点零花钱。你上网查携程上的票价是1000元,你跟你爸说这张票1500,这样你就从中赚取了500零花钱。

这就是一个典型的代理模式。其中的角色分别是:

携程		 --> 被代理类
你		 --> 代理类
你爸		 --> 用户,享受服务的一方

先试写一下对于这件事我们如何编码,我们使用代理模式是为了让结构更灵活更清晰,所以此时我们的实现思路是这样:

  1. 将买票这个行为抽象出来,不管是哪个角色,最终想要的结果都是买票
  2. 找一个真正买票的地方实现买票服务,也就是创建一个被代理类
  3. 你去把票买了然后把票给你爸,也就是对外提供一个代理类,用户通过这个代理类获得服务。此时这个代理类就是你。

不管是动态代理还是静态代理,都得实现这几个步骤。

静态代理

/**
 * 抽象出来的买票服务接口,大家想做的事都是买票这么个事
 */
public interface TicketServer {
    float buyTicket(String dest);
}

2.我从携程去买,那就创建一个携程类,然后去实现买票服务

/**
 * 被代理类
 * 真正提供服务的地方,干实事的
 */
public class XC implements TicketServer {
    @Override
    public float buyTicket(String dest) {
        System.out.println("出票成功 机票 To " + dest + " No:" + (int) (Math.random() * 1000));
        return 1000;
    }
}

3.创建一个实体对你爸提供服务。

public class Son implements TicketServer {

    TicketServer server = new XC();

    public Son() {
    }

    @Override
    public float buyTicket(String dest) {
        float fares = server.buyTicket(dest);
        fares += 500;
        return fares;
    }
}

到这里你老爸可以通过Son也就是你买到票了,你就是携程的代理,被代理类(目标类)就是携程。

老爸这个类可以这样子指使你:

public class Father {
    public static void main(String[] args) {
        
        TicketServer son = new XC(); // java多态的体现,你爸是想要买个票,所以让你买也行,让你姐去买也可以
        System.out.println("票价为:" + son.buyTicket("北京") + "元");
    }
}

至此静态代理结束,此时你如果发现携程买的票比较贵你可以自己换成去哪儿,换成12306都是OK的,你爸并不知道。

但是有个问题,如果你爸知道你赚他钱就让你姐去买,那你姐是不是也要和Son这个代理类一样去创建一个Daughter类,然后实现和你一样的功能?这样的话如果你爸再想找别人别人还得创建这个类。明显静态代理的方式虽然能达到我们所要的保护目标和增强目标的2个目的,但是略显冗余了。

动态代理

用动态代理实现也是一样的步骤,只不过Son类不需要我们再手动创建,而是调用Proxy的API让系统通过反射替我们动态生成。

你老爸这样调用你去买票:

public class Father {
    public static void main(String[] args) {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        
        TicketServer son = (TicketServer) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{TicketServer.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                TicketServer xc = new XC();
                float fares = (float) method.invoke(xc, args);
                return fares + 500;
            }
        });
        System.out.println("票价为:" + son.buyTicket("北京") + "元");
    }
}

到这里静态代理和动态代理的写法都写完了。但是为了结构更清晰,我们把对老爸提供的东西做一个抽取

public class TicketUtils {

    /**
     * 静态代理方式
     *
     * @return
     */
    public static TicketServer staticTicket() {
        return new Son();
    }

    /**
     * 动态代理方式
     *
     * @return
     */
    public static TicketServer dynamicTicket() {
        TicketServer server = new XC();

        return (TicketServer) Proxy.newProxyInstance(server.getClass().getClassLoader(), server.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                float fares = (float) method.invoke(server, args);
                fares += 500;
                return fares;
            }
        });
    }
}

你爸最后调用你就很简单了

public class Father {

    public static void main(String[] args) {
        // 这段代码可以在ide中输出用户为我们生成的临时类
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        /**
         * 静态代理方式
         */
//        TicketServer son = TicketUtils.staticTicket();
//        System.out.println("票价为:" + son.buyTicket("北京") + "元");

        /**
         * 动态代理方式
         */
        TicketServer son = TicketUtils.dynamicTicket();
        System.out.println("票价为:" + son.buyTicket("北京") + "元");
    }
}

以上是最最最基础的讲解了。

接下来我们来看看Proxy这个类如何帮我们完成代理类的创建工作。

Proxy

// loader:类加载器,用于在动态生成类之后将类加载进来
// interfaces:所需要实现的接口,也就是我们抽象出来的公共接口,上例中的TicketServer
// h:执行handler
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

首先我们看一下系统为我们生成的类长什么样子这样才能更好的理解它生成的类是如何为我们工作的。
通过System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
这段代码会将动态生成的类保留下来,我们可以在ide中看到类:

// 它实现了TicketServer接口,和我们自己创建代理类时一样
// 它继承了Proxy
public final class $Proxy0 extends Proxy implements TicketServer {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    ...
    public final float buyTicket(String var1) throws  {
        try {
            return (Float)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    ...
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.lianwenhong.proxydemo.TicketServer").getMethod("buyTicket", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

在newProxyInstance()方法中其实会通过反射调用构造方法,然后将我们传入的h传进来,所以这时候调用到$Proxy0的构造进而调用到父类的构造:

protected Proxy(InvocationHandler h) {
  	Objects.requireNonNull(h);
  	this.h = h; // 先记住将h赋值给本地对象
}

当我们拿到代理类调用buyTicket()时其内部是return (Float)super.h.invoke(this, m3, new Object[]{var1});是调用h的invoke()方法,所以这时候也就是调用我们Proxy.newProxyInstance()时传入的InvocationHandler对象。而m3是在静态代码块中赋值的也就是buyTicket()方法。所以到这里就可以理解了为什么最终我们要在InvocationHandler->invoke()方法中做买票的逻辑了。

Proxy.newProxyInstance() -> InvocationHandler.invoke()的回调过程已然形成闭环。

接下来我们看下Proxy.newProxyInstance()是如何返回代理类实例的(也就是$Proxy0):

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{
		... 做一些前期判空和Access权限校验工作
		
	 // 返回代理类$Proxy0的Class对象(内部逻辑是缓存中有直接返回,否则通过ProxyClassFactory创建一个)
     Class<?> cl = getProxyClass0(loader, intfs);
		...
     try {
		...
		 // 通过反射找到构造方法
         final Constructor<?> cons = cl.getConstructor(constructorParams);
         final InvocationHandler ih = h;
         if (!Modifier.isPublic(cl.getModifiers())) {
             AccessController.doPrivileged(new PrivilegedAction<Void>() {
                 public Void run() {
                     // 设置构造方法可访问
                     cons.setAccessible(true);
                     return null;
                 }
             });
         }
         // 通过反射new出$Proxy0对象并返回
         return cons.newInstance(new Object[]{h});
     } catch (...){
     	...
     }
 }

所以具体创建代理Class类的方法是在getProxyClass0()方法:

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

   // proxyClassCache是一个缓存结构WeakCache,其使用方式类似map以所要代理的接口interface为key,以ProxyClassFactory为value,如果不存在该key则会通过ProxyClassFactory创建出一个代理类。
    return proxyClassCache.get(loader, interfaces);
}

首次进入代理类为空,是通过ProxyClassFactory.apply()方法生成的:

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
     // 遍历interface,找出代理类所应该继承的所有接口
     for (Class<?> intf : interfaces) {
         Class<?> interfaceClass = null;
         try {
             // 通过反射将接口Class对象加载进来,内部是使用了classloader类加载机制
             interfaceClass = Class.forName(intf.getName(), false, loader);
         } catch (ClassNotFoundException e) {
         }
         if (interfaceClass != intf) {
             throw new IllegalArgumentException(
                 intf + " is not visible from class loader");
         }
        
         if (!interfaceClass.isInterface()) {
         	 // 如果传入的不是接口类型,则会在此处抛出IllegalArgumentException异常提示传入的不是一个接口类型
             throw new IllegalArgumentException(
                 interfaceClass.getName() + " is not an interface");
         }
        
         if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
             throw new IllegalArgumentException(
                 "repeated interface: " + interfaceClass.getName());
         }
     }

     String proxyPkg = null;     // package to define proxy class in
     int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
     for (Class<?> intf : interfaces) {
         int flags = intf.getModifiers();
         if (!Modifier.isPublic(flags)) {
             accessFlags = Modifier.FINAL;
             String name = intf.getName();
             int n = name.lastIndexOf('.');
             String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
             if (proxyPkg == null) {
                 proxyPkg = pkg;
             } else if (!pkg.equals(proxyPkg)) {
                 throw new IllegalArgumentException(
                     "non-public interfaces from different packages");
             }
         }
     }

     if (proxyPkg == null) {
         // if no non-public proxy interfaces, use com.sun.proxy package
         // 合成全类名,PROXY_PACKAGE其实是com.sun.proxy
         proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
     }

     /*
      * Choose a name for the proxy class to generate.
      */
     long num = nextUniqueNumber.getAndIncrement();
      //  生成文件名,例如$Proxy0,根据不同的需求创建出多个的话根据后面的数字区分
     String proxyName = proxyPkg + proxyClassNamePrefix + num;

    // 通过ProxyGenerator工具类根据一定规则编写内部逻辑代码并生成类字节数组,其实是一套通用写法
     byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
         proxyName, interfaces, accessFlags);
     try {
         // 调用defineClass0生成真正的class文件并加载进内存(其内部就是调用类加载器生成class文件并加载)
         return defineClass0(loader, proxyName,
                             proxyClassFile, 0, proxyClassFile.length);
     } catch (ClassFormatError e) {
         throw new IllegalArgumentException(e.toString());
     }
 }

我们只要了解一下就行。

newProxyInstance()大致原理就是:

  1. 根据传入的接口信息去缓存中获取或创建代理类Class对象。
    a. 缓存中存在代理类class对象则直接返回

    b. 不存在则根据传入的interface接口通过一系列拼接类名、定义修饰符等操作然后调用ProxyGenerator.generateProxyClass()方法生成字节数组,然后将该字节数组交给ClassLoader底层的defineClass0(native方法)生成一个新的class文件并返回。

  2. 利用反射机制获取到代理类对象的构造方法并执行生成代理类对象并返回

总结: 就是根据传入的接口通过反射动态创建一个代理类对象,该对象继承于Proxy并实现代理模式中的接口,然后将创建代理类过程中传入的InvocationHandler对象保存起来,当调用目标方法时是调用保存起来的InvocationHandler对象的invoke()方法回调给使用者。

By the way:
源码中的ProxyClassFactory中是使用sun.misc.ProxyGenerator 类来生class的。生成Class文件的方法不止这一个,我们也可以用 Javassist 技术动态生成class。

拓展

动态代理也可以用于实现AOP切面编程。例如方法的埋点,耗时统计等等。只需在invoke内部实现AOP的业务逻辑即可。这样JVM动态生成代理类时就包含了所需的AOP逻辑。

参考文章:
JVM的Proxy.newProxyInstance原理分析
自己实现Java 动态代理 Proxy
Proxy.newProxyInstance源码探究
JDK动态代理底层原理&为什么需要接口?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值