Skip to content

代理模式及动态代理 #2

@Jimmy2Angel

Description

@Jimmy2Angel

代理模式

定义:给某个对象提供一个代理对象,用户通过代理对象控制对原对象的访问。

上图中: - RealSubject 是原对象(本文把原对象称为"委托对象"),Proxy 是代理对象。 - Subject 是委托对象和代理对象都共同实现的接口。 - Request() 是委托对象和代理对象共同拥有的方法。

JAVA实现如下:

public class ProxyDemo {
    public static void main(String args[]){
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

interface Subject{
    void request();
}

class RealSubject implements Subject{
    public void request(){
        System.out.println("request");
    }
}

class Proxy implements Subject{
    private Subject subject;
    public Proxy(Subject subject){
        this.subject = subject;
    }
    public void request(){
        System.out.println("PreProcess");
        subject.request();
        System.out.println("PostProcess");
    }
}

代理分为两类:

  • 静态代理:代理类实现就写好了,在Java编译后是一个实际存在的class文件。
  • 动态代理:代理类是在程序运行时动态生成的类字节码,并加载到JVM中。

上文中所说的是静态代理。其中动态代理又可以分为JDK动态代理和CGLIB代理。

动态代理

JDK动态代理

JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandle来处理的。它只能对实现了接口的类进行代理。主要代码实现如下:

public class JDKProxy implements InvocationHandler {    
    private Object targetObject;//需要代理的目标对象    
    public Object newProxy(Object targetObject) {//将目标对象传入进行代理    
    	this.targetObject = targetObject;     
    	return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);//返回代理对象 
    }    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    
        checkPopedom();//一般我们进行逻辑处理的函数比如这个地方是模拟检查权限    
        Object ret = null;      // 设置方法的返回值    
        ret  = method.invoke(targetObject, args);       //调用invoke方法,ret存储该方法的返回值    
        return ret;    
    }    
    private void checkPopedom() {//模拟检查权限的例子    
        System.out.println(".:检查权限  checkPopedom()!");    
    }    
}    

Proxy类的newProxyInstance()方法大致如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) throws IllegalArgumentException{
    if (h == null) {
        throw new NullPointerException();	//必须要有一个InvocationHandle实例
    }

    Class<?> cl = getProxyClass0(loader, interfaces); // 获得代理类(重要!进去看看)

    /*
     * 调用构造器获得一个实例
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
            // create proxy instance with doPrivilege as the proxy class may
            // implement non-public interfaces that requires a special permission
            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    return newInstance(cons, ih);
                }
            });
        } else {
            return newInstance(cons, ih);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
}

getProxyClass0()方法很长,挑些重点大致如下:

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {

    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    Class<?> proxyClass = null;
    String[] interfaceNames = new String[interfaces.length];
    Set<Class<?>> interfaceSet = new HashSet<>();
    for (int i = 0; i < interfaces.length; i++) {	//根据接口名称反射生成对应的Class并放到Set中
        String interfaceName = interfaces[i].getName();
        Class<?> interfaceClass = null;
        try {
            interfaceClass = Class.forName(interfaceName, false, loader);
        } catch (ClassNotFoundException e) {
        }
        if (interfaceClass != interfaces[i]) {
            throw new IllegalArgumentException(
                interfaces[i] + " is not visible from class loader");
        }
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(
                interfaceClass.getName() + " is not an interface");
        }
        if (interfaceSet.contains(interfaceClass)) {
            throw new IllegalArgumentException(
                "repeated interface: " + interfaceClass.getName());
        }
        interfaceSet.add(interfaceClass);
        interfaceNames[i] = interfaceName;
    }

    List<String> key = Arrays.asList(interfaceNames);	//用接口名称的List作为cache的key
    /*
     * Find or create the proxy class cache for the class loader.
     */
    Map<List<String>, Object> cache;
    synchronized (loaderToCache) {	//loadToCache是一个Map<ClassLoader, Map<List<String>, Object>>
        cache = loaderToCache.get(loader);
        if (cache == null) {
            cache = new HashMap<>();
            loaderToCache.put(loader, cache);
        }
    }

    synchronized (cache) {
		//对缓存的一些处理,暂时不看
    }

    try {
        String proxyPkg = null;     // package to define proxy class
        for (int i = 0; i < interfaces.length; i++) {	
            int flags = interfaces[i].getModifiers();
            if (!Modifier.isPublic(flags)) {		//对于非公共接口,代理类包名和接口包名相同
                String name = interfaces[i].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;
            synchronized (nextUniqueNumberLock) {
                num = nextUniqueNumber++;
            }
            String proxyName = proxyPkg + proxyClassNamePrefix + num;	//生成代理类的完全限定名
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);	//产生代理类字节码(进去看看)
            try {
				//根据字节码返回相应的class实例
                proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
        // add to set of all generated proxy classes, for isProxyClass
        proxyClasses.put(proxyClass, null);

    } finally {
        synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference<Class<?>>(proxyClass));
            } else {
                cache.remove(key);
            }
            cache.notifyAll();
        }
    }
    return proxyClass;
}

ProxyGenerator是sun.misc包中的类,generateProxyClass()方法如下:

public static byte[] generateProxyClass(final String var0, Class[] var1) {
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);
    final byte[] var3 = var2.generateClassFile();
	// 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观 
    if(saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
                try {
                    FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
                    var1.write(var3);
                    var1.close();
                    return null;
                } catch (IOException var2) {
                    throw new InternalError("I/O exception saving generated file: " + var2);
                }
            }
        });
    }

    return var3;
}

saveGeneratedFiles如下:

    private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

GetBooleanAction实际上是调用Boolean.getBoolean(propName)来获得的,而Boolean.getBoolean(propName)调用了System.getProperty(name),所以我们可以设置sun.misc.ProxyGenerator.saveGeneratedFiles这个系统属性为true来把生成的class保存到本地文件来查看。
这里要注意,当把这个属性设置为true时,生成的class文件及其所在的路径都需要提前创建,否则会抛出FileNotFoundException异常。即我们要在运行当前main方法的路径下创建com/sun/proxy目录,并创建一个$Proxy0.class文件,才能够正常运行并保存class文件内容。
反编译$Proxy0.class文件,如下所示:

package com.sun.proxy;  
  
import com.mikan.proxy.HelloWorld;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.lang.reflect.UndeclaredThrowableException;  

public final class $Proxy0 extends Proxy implements HelloWorld {  
  private static Method m1;  
  private static Method m3;  
  private static Method m0;  
  private static Method m2;  
  
  public $Proxy0(InvocationHandler paramInvocationHandler) {  
    super(paramInvocationHandler);  
  }  
  
  public final boolean equals(Object paramObject) {  
    try {  
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final void sayHello(String paramString) {  
    try {  
      this.h.invoke(this, m3, new Object[] { paramString });  
      return;  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final int hashCode() {  
    try {  
      return ((Integer)this.h.invoke(this, m0, null)).intValue();  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  public final String toString() {  
    try {  
      return (String)this.h.invoke(this, m2, null);  
    }  
    catch (Error|RuntimeException localError) {  
      throw localError;  
    }  
    catch (Throwable localThrowable) {  
      throw new UndeclaredThrowableException(localThrowable);  
    }  
  }  
  
  static {  
    try {  
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });  
      m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });  
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
      return;  
    }  
    catch (NoSuchMethodException localNoSuchMethodException) {  
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
    }  
    catch (ClassNotFoundException localClassNotFoundException) {  
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
    }  
  }  
}  

可以看到动态生成的代理类有如下特点:

  1. 继承Proxy,实现代理的接口,由于java是单继承,所以JDK动态代理不支持对实现类的代理,只支持对接口的代理
  2. 提供一个InvocationHandle作为构造器参数
  3. 用静态代码块来初始化接口中方法的Method对象,以及Object中equals、hashCode、toString的Method对象
  4. 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
  5. 代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。

CGLIB代理

CGLIB代理是利用asm开源包,通过修改指定的类加载后的字节码生成一个子类实现的。CGLIB是针对类实现代理(无需该类实现接口),主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法不要声明成final的。主要代码实现如下:

public class CGLibProxy implements MethodInterceptor {    
    private Object targetObject;// CGLib需要代理的目标对象    
    public Object createProxyObject(Object obj) {    
        this.targetObject = obj;    
        Enhancer enhancer = new Enhancer();    
        enhancer.setSuperclass(obj.getClass());    
        enhancer.setCallback(this);    
        Object proxyObj = enhancer.create();    
        return proxyObj;// 返回代理对象    
    }    
    public Object intercept(Object proxy, Method method, Object[] args,    
            MethodProxy methodProxy) throws Throwable {    
        Object obj = null;    
        if ("addUser".equals(method.getName())) {// 过滤方法    
            checkPopedom();// 检查权限    
        }    
        obj = method.invoke(targetObject, args);    
        return obj;    
    }    
    private void checkPopedom() {    
        System.out.println(".:检查权限  checkPopedom()!");    
    }    
}    

动态代理的应用

AOP的实现方式就是通过对目标对象的代理在连接点前加入通知,完成统一的切面操作。Spring提供JDKProxy和CGLIB两种方式来生成代理对象,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。
默认方式是如果目标类实现了接口,则使用JDK动态代理,如果目标类没有实现接口,则采用CGLIB代理。 如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。

代理模式应用

  • 远程代理:也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。
  • 虚拟代理:是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如说你打开一个很大的HTML网页时,里面可能有很多的文字和图片,但是你还是能很快的打开它,此时你看到的是所有的文字,但图片确实一张一张加载后才能看到。那些未打开的图片框,就是通过虚拟代理来代替真实的图片,此时代理存储了真实图片的路径和尺寸。
  • 安全代理:用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候。
  • 智能指引:是指当调用真实对象的时候,代理处理另外一些事。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions