Java-动态代理

1.什么是动态代理。
2.如何代理。
3.代理的类在哪里。
4.如何调用的方法。

动态代理就是动态在方法前后进行增强,如:
代理前:

void say();

代理后:

System.out.println("before say()");
void say();
System.out.println("after say()");

动态代理需要用到java.lang.reflect.Proxy类。类中有:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)  throws IllegalArgumentException 方法,可以帮助我们生成代理类。
loader:类加载器。
interfaces:被代理类继承的接口。
h:InvocationHandler  实现类 代理动作在其 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 方法中进行。
基本情况介绍完了下面看示例:

/**
 * 被动态代理类需要实现的接口
 */
public interface Hello {
    void sayHello();
}
/**
 *  被动态代理类
 */
public class HelloImpl implements Hello {

    @Override
    public void sayHello() {
        System.out.println("HelloImpl->sayHello");
    }

}
/**
 * 代理方法增强
 */
public class HelloInvocationHandler implements InvocationHandler {

    private Object obj;

    public HelloInvocationHandler(Object obj) {
        super();
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("--------before--------");
        method.invoke(obj, args);
        System.out.println("--------after--------");
        return null;
    }

}
/**
 * 测试
 */
public class TestProxy {

    public static void main(String[] args) {
        //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Hello hello = new HelloImpl();
        HelloInvocationHandler handler = new HelloInvocationHandler(hello);

        Hello proxy = (Hello) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), new Class[] { Hello.class },
                handler);
        proxy.sayHello();
    }

}

输出:


--------before--------
HelloImpl->sayHello
--------after--------

这样就完成了一个最基本的动态代理。那么是如何进行代理的呢,答案肯定就在newProxyInstance方法中。点进去查看源码:

   /** parameter types of a proxy class constructor */
  private static final Class<?>[] constructorParams = { InvocationHandler.class };
  
  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
        //省略非关键代码
        /*
         * Look up or generate the designated proxy class.
         * 查找或生成指定的代理类
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            //把上面获取到的代理类实例化即使用反射调用其构造器,构造器参数即constructorParams
            //从第2行代码中声明的参数即可看出参数类型就是接口InvocationHandler
            //传入的参数h即我们自己实现的HelloInvocationHandler
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            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);
        }
    }

好了我们已经弄清了怎么生成的代理类实例,那么代理类是怎么生成的呢,下面看getProxyClass0方法:

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
	
	//省略非关键代码
	
	// 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
	//上面的英文注释说的很清楚了 如果已经存在了那么直接返回,不存在则通过ProxyClassFactory生成
	return proxyClassCache.get(loader, interfaces);
}

那我们去ProxyClassFactory中看如何生成代理类的:

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
	// prefix for all proxy class name
	//这里就定义了代理类生成的前缀,所以所有的代理类都是 $Proxy0这种格式的
	private static final String proxyClassNamePrefix = "$Proxy";

	// next number to use for generation of unique proxy class names
	//上面说了代理类的前缀,其中$Proxy0中的0就是通过此计数器实现的
	private static final AtomicLong nextUniqueNumber = new AtomicLong();

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

	   //省略了一些操作接口方法的代码
	   
		long num = nextUniqueNumber.getAndIncrement();
		//这里就对文件名进行了拼接
		String proxyName = proxyPkg + proxyClassNamePrefix + num;

		/*
		 * Generate the specified proxy class.
		 * 生成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());
		}
	}
}

好了终于到了最关键的生成class字节码文件的时刻了看:ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);方法:

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

public static byte[] generateProxyClass(final String name, Class[] interfaces){
  ProxyGenerator gen = new ProxyGenerator(name, interfaces);
  //classFile即class文件字节码的流
  final byte[] classFile = gen.generateClassFile();
  //这里就是我们是否把class字节码文件保存到磁盘 通过sun.misc.ProxyGenerator.saveGeneratedFiles设置
  if (saveGeneratedFiles) {
    //此处进行写入磁盘的操作,代码太多省略了
  }
  return classFile;
}

好下面继续看generateClassFile,此方法真正的进行了class文件生成:

private byte[] generateClassFile() {
    
    //省略代码...

     //终于开始真正生成字节码流
     ByteArrayOutputStream bout = new ByteArrayOutputStream();
     DataOutputStream dout = new DataOutputStream(bout);
     try {
         //这里就是组合class字节码文件的二进制流 需要有一些class文件结构的知识
         // u4 magic; java魔数表示这是一个java文件, 意思是“咖啡 宝贝”。
         dout.writeInt(0xCAFEBABE);
         // u2 minor_version; 次版本号
         dout.writeShort(CLASSFILE_MINOR_VERSION);
          // u2 major_version; 主版本号
         dout.writeShort(CLASSFILE_MAJOR_VERSION);
         // (write constant pool) 常量
         cp.write(dout); 
         // u2 access_flags; 访问标志
         dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
         // u2 this_class;类索引
         dout.writeShort(cp.getClass(dotToSlash(className)));
         // u2 super_class;父类索引
         dout.writeShort(cp.getClass(superclassName));
         // u2 interfaces_count;接口索引
         dout.writeShort(interfaces.length);
         // u2 interfaces[interfaces_count]; 
         for (int i = 0; i < interfaces.length; i++) {
             dout.writeShort(cp.getClass(
                 dotToSlash(interfaces[i].getName())));
         }
         // u2 fields_count;字段表集合
         dout.writeShort(fields.size());
         // field_info fields[fields_count];
         for (FieldInfo f : fields) {
             f.write(dout);
         }
         // u2 methods_count;方法表集合
         dout.writeShort(methods.size());
         // method_info methods[methods_count];
         for (MethodInfo m : methods) {
             m.write(dout);
         }
         // u2 attributes_count;属性表
         dout.writeShort(0); // (no ClassFile attributes for proxy classes)

     } catch (IOException e) {
         throw new InternalError("unexpected I/O Exception");
     }
     //返回字节码流
     return bout.toByteArray();
 }

好了终于拿到代理类的字节码流了,那么下面把这这个类加载到虚拟机就可以使用了,是的通过defineClass0加载代理类:
  private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);这是一个native方法。

类也生成好了,也加载到虚拟机了,那么就可以使用此类了。

那么此类是如何工作的呢,我们通过配置参数把生成的class文件写入硬盘在项目根目录(注意不是src下,也不是target下(如果使用maven的话)):com\sun\proxy\$Proxy0.class;所以可以回答第3个问题了
反编译查看一下:

package com.sun.proxy;

import com.proxy.Hello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//生成的代理类 继承了proxy类,并且实现类Hello接口,所以现在可以回答第5个问题了
public final class $Proxy0 extends Proxy implements Hello{
  //这些方法会在下面的静态方法块中获取
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;

//paramInvocationHandler 即我们传入的那个HelloInvocationHandler 
//在Proxy.newProxyInstance()方法中的  return cons.newInstance(new Object[]{h});即是调用了此构造方法
  public $Proxy0(InvocationHandler paramInvocationHandler)throws {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)throws  {
    try{ 
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }catch (RuntimeException localRuntimeException){
      throw localRuntimeException;
    }catch (Throwable localThrowable){
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

//此方法就是代理类代理了我们的sayHello方法
  public final void sayHello() throws {
    try{
        //这里就调用了我们传入的HelloInvocationHandler的invoke方法
        //到这里就应该明白整个过程是怎么调用的了吧。
        //所以动态代理就是为我们生成了这个类,并且在调用方法时调用了我们传入的
        //HelloInvocationHandler的invoke方法所以这也就是为什么我们调用自己的方法会进入invoke方法
      this.h.invoke(this, m3, null);
      return;
    }catch (RuntimeException localRuntimeException){
      throw localRuntimeException;
    }catch (Throwable localThrowable){
      throw new UndeclaredThrowableException(localThrowable);
      }
  }

  public final String toString() throws {
    try{
      return (String)this.h.invoke(this, m2, null);
    }catch (RuntimeException localRuntimeException){
      throw localRuntimeException;
    }catch (Throwable localThrowable){
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode() throws {
    try{
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }catch (RuntimeException localRuntimeException){
      throw localRuntimeException;
    }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.proxy.Hello").getMethod("sayHello", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }catch (NoSuchMethodException localNoSuchMethodException){
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }catch (ClassNotFoundException localClassNotFoundException){
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}


以上基本上动态代理的源码都分析完了下面回答一下上面提出的问题:
1.什么是动态代理。可以动态在方法前后插入操作
2.如何代理。传入参数classLoader,Inferface,InvocationHandler
3.代理的类在哪里。直接通过代码生成class字节码文件流,并且加载到虚拟机中,如果我们配置参数:
 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
那么会生成的项目根目录下的:com.sun.proxy.$Proxy0,
4.如何调用的方法。Proxy.newProxyInstance()方法返回的对象实际上是$Proxy0这个对象的实例,所以我们调用方法后会调用我们传入的HelloInvocationHandler的invoke方法,从而实现了调用增强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值