Java静态代理与动态代理

2,779 阅读7分钟

一、概述

1、什么是代理模式

      先来看看代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗地讲,代理模式就是提供一个对象相当于中介者,他可以帮我们调用目标的功能,同时我们可以在使用目标功能的同时扩展其他功能,而不需要直接改到目标类。

2、为什么要使用代理模式

      通过代理模式帮我们调用目标类,那为什么我们不直接调用目标类,而要额外弄一个类出来间接调用,这不是闲得慌吗?存在即是道理,既然存在这样一种模式,自然有它的好处,下面来看下代理模式的好处:

  • 中介隔离作用:在某些情况下,如果某个类并不想被直接调用,那么通过创建代理类,就 可以避免目标类被直接调用,这样可以保护目标类不会被曝光
  • 可以扩展功能而不用影响目标类:假设有一个核心类提供了一些核心业务,而我们希望使用这个功能时上报日志,或者某个业务要先进行某些处理才调用,那这样如果直接改核心类,就会给核心功能加上了一些无关的内容,变得这个功能与业务有很大关联,难以复用。这时我们使用代理类的话,就可以在代理类中添加自己的业务逻辑,而不需要破坏原有的核心功能。

3、代理模式的种类

      代理模式可以分为静态代理和动态代理,下面会进行详解。


二、静态代理

1、概念

      静态代理指是由程序员手动编写或者自动生成的代理类,在程序编译的时候就可以生成对应的class文件,在运行时可以直接使用。

2、静态代理的实现

      静态代理实现起来比较简单,只需要与目标类实现相同的接口,然后持有目标类对象,就可以对目标类进行扩展。下面看例子:

首先创建一个接口ISayHello.java:

public interface ISayHello {
    void sayHello();
}

目标类User.java:

public class User implements ISayHello {

	@Override
	public void sayHello() {
		System.out.println("hello");
	}

}

静态代理类StaticProxy.java

public class StaticProxy implements ISayHello {

	/** 持有目标类的对象,实现间接调用 */
	private User target; 
	
	public StaticProxy(User target) {
		this.target = target;
	}

	@Override
	public void sayHello() {
		System.out.println("before hello");
		if (target != null) {
			target.sayHello(); // 实际调用目标功能
		}
		System.out.println("after hello");
	}

}

静态代理的使用

public class Main {

	public static void main(String[] args) {
		StaticProxy proxy = new StaticProxy(new User());
		proxy.sayHello();
	}

}

输出结果

before hello
hello
after hello

3、静态代理应用场景

      对一些第三方框架进行扩展代理、自动生成的代码,例如Android中的AIDL等。


三、动态代理

1、概述

      与静态代理不同,动态代理不需要提前创建对象,只需要提供一个动态创建器,程序会在运行时候动态生成对应的代理类。

2、动态代理的好处

      动态代理的好处主要就是可以减少对接口的依赖,降低耦合度,减少冗余代码,方便对代理方法的统一管理。我们可以从以下几点体现出来:

  • 减少方法的实现:如果一个接口有1000个方法,而我们只想代理其中某几个方法,使用静态代理需要代理类实现所有的方法,如果接口增加一个方法,相应的代理类也要增加实现,代码会变得十分冗余。但如果使用动态代理,就可以简单地只对某几个方法进行处理,不需要加入大量无用的代码。
  • 可以服务多个目标类:因为静态代理在编译时期就已经生成对应的类,所以对于每一个代理类,都已经固定了要服务的目标类,这就导致了如果需要多个目标类,就要编写对应的代理类,导致类会很多。而使用动态代理,则可以一个动态处理器为不同的目标类作服务,从而减少类的编写。

3、动态代理的实现

      动态代理的实现主要使用了InvocationHandler,下面看例子:

接口和目标类均使用静态代理的例子,现在实现动态代理类DynamicProxyHandler.java:

public class DynamicProxyHandler implements InvocationHandler {
	
	private Object target; 
	
	public Object bind(Object target) {
		this.target = target;
		if (target == null) {
			return null;
		}
		// 通过newProxyInstance方法创建代理类
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), this);
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (method.getName().equals("sayHello")) {
			// 如果是需要代理的方法则进行处理
			System.out.println("before hello");
			Object invoke = method.invoke(target, args); // 实际调用目标功能
			System.out.println("after hello");
			return invoke;
		}
		return method.invoke(target, args);
	}
	
}

动态代理的使用

public class Main {

	public static void main(String[] args) {
		DynamicProxyHandler proxyHandler = new DynamicProxyHandler();
		ISayHello proxy = (ISayHello) proxyHandler.bind(new User());
		proxy.sayHello();
	}

}

输出结果

before hello
hello
after hello

4、动态代理的原理

      从上面的例子可以看到,动态代理生存代理类主要是依靠Proxy.newProxyInstance这一个静态方法,因此,下面就看看这一个方法做了什么

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);
        }

        // 通过缓存获取代理类,没有则生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            // 获取代理类的构造函数
            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;
                    }
                });
            }
            // 调用构造函数构造代理类的实类
            return cons.newInstance(new Object[]{h});
        } // ...省略一堆catch
    }

      上面这个方法抛开一些检查的逻辑,核心方法就是getProxyClass0(loader, intfs)来生成我们需要的代理类,后后通过构造方法传入InvocationHandler来创建实例,接下来看一下getProxyClass0(loader, intfs)的实现

private static Class<?> getProxyClass0(ClassLoader loader,
        Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            // 检查接口的方法数
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // 从缓存中获取代理类,如果没有则会进行生成
        // proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
        return proxyClassCache.get(loader, interfaces);
    }

      这个方法只有几行代码,首先检查一下接口的数量,然后从缓存中获取类,如果找不到的话会使用ProxyClassFactory来生成,下面看看是如何生成的

private static final class ProxyClassFactory 
    implements BiFunction<ClassLoader, Class<?>[], Class<?>> {

	// 代理类的名称前缀
	private static final String proxyClassNamePrefix = "$Proxy";
	
	// 代理类的序号,以此确定唯一的代理类
	private static final AtomicLong nextUniqueNumber = new AtomicLong();
	
	@Override
	public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
	
	    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
	    for (Class<?> intf : interfaces) {
	        Class<?> interfaceClass = null;
	        try {
	            interfaceClass = Class.forName(intf.getName(), false, loader);
	        } catch (ClassNotFoundException e) {
	        }
	        if (interfaceClass != intf) {
	            // 判断是否可以由指定的类加载
	        }
	
	        if (!interfaceClass.isInterface()) {
	            // 判断是否一个接口
	        }
	
	        if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
	            // 判断是否有重复的接口
	        }
	    }
	
	    String proxyPkg = null;
	    // 生成代理类的访问标志, 默认是public final的
	    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) {
	        // 公有的接口,则默认包名com.sun.proxy
	        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
	    }
	
	    // 生成代理类序号
	    long num = nextUniqueNumber.getAndIncrement();
	    // 代理类的名字 包名+前缀+序号,例如com.sun.proxy$Proxy1
	    String proxyName = proxyPkg + proxyClassNamePrefix + num;
	
	    // 使用generateProxyClass生成字节码
	    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
	        proxyName, interfaces, accessFlags);
	    try {
	        // 根据二进制文件生成相应的Class实例
	        return defineClass0(loader, proxyName,
	            proxyClassFile, 0, proxyClassFile.length);
	    } catch (ClassFormatError e) {
	        throw new IllegalArgumentException(e.toString());
	    }
	}
}

      可以看到,这个类通过 apply来生成我们所需的代理类,这个类主要做了几件事:

  1. 进行一些合法性判断,不合法则抛出异常
  2. 生成包名+前缀+序号的代理类,例如com.sun.proxy$Proxy1
  3. 如果接口是公有的,则默认包名是com.sun.proxy;否则和接口的包名相同
  4. 通过ProxyGenerator.generateProxyClass来生成字节码
  5. 同过defineClass0来生成class实

5、动态代理应用场景

     例如Retrofit网络加载框架、Spring中AOP的实现等。


四、总结

  • 代理模式可以在不改变目标类的情况下对功能进行扩展
  • 静态代理编译时便生成class文件,动态代理运行时通过反射生成对应的class文件
  • 静态代理需要实现接口的所有方法,一个代理类能服务的目标类有限
  • 动态代理可以只对某些方法进行处理,一个代理处理器可以服务多个目标类
  • 对于一些接口比较简单、或者自动生成的通用性代码,可以选择使用静态代理;对于一些庞大的接口,频繁地需要改动接口,你已经觉得改得很烦,可以选择使用动态代理。

五、参考资料

Java动态代理从入门到原理再到实战

JDK动态代理的底层实现之Proxy源码分析