4 代理-补充

在说动态代理时,我们先说说静态代理(java设计模式:代理模式),这就是静态代理。

静态代理:我们对某一个对象进行访问,突然需要判断访问者是否有权限或者访问次数统计、限制、添加日志、缓存等,那么我们可以直接修改代码,但是这样做不太合适,因为 1 这些特殊操作并不是对象的核心功能,2 如果后期取消权限验证,再改代码就比较恶心了。因此我们可以采用代理模式,生成代理对象,在代理对象中进行权限校验。

静态代理模式代码如下:

package dynamic.proxy;

/**简单的代理模式**/
public class SimpleProxyCompsite {
	public static void main(String[] args) {
		Pepole pep = new PepoleProxy(new User());
		pep.printName();
	}
}
//需要进行权限校验方法抽象的 集合
interface Pepole{
	public void printName();
}
//被代理对象
class User implements Pepole{
	//需要 校验的方法
	public void printName(){
		System.out.println("this is xxx");
	}
	//不需要校验的方法
	public String getName(){
		return "1231231";
	}
}

//代理对象,需要实现方法集接口
class PepoleProxy implements Pepole{
	//代理对象内部持有被代理对象
	private Pepole pep;	
	public PepoleProxy(Pepole pep){
		this.pep = pep;
	}	
	//需要特殊处理的方法
	public void printName(){
		System.out.println("权限校验");
		pep.printName();
	}
}

通过接口把代理对象的相关方法进行抽取,然后在代理类中对方法进行预处理,处理完再调用被代理对象的相关方法。

通过上述静态代理,发现如果需要对某个对象的某些方法进行代理,就需要写相关的代理对象,抽取代理方法的集合。这样有几个缺陷:1 每一个被代理对象,都需要写一个代理对象;2 每一个需要特殊处理的方法,代理对象也必须有对应的。

动态代理对象

为了解决静态代理的各种缺陷,能否考虑使用一种动态的代理,解决一下问题:
1 每个被代理类对应一个代理类。
2 每个需要特殊处理的方法对应代理类的方法。

上述就是动态代理需要实现。

动态代理的范例

package dynamic.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy {
	public static void main(String[] args) {	
		// 实例化InvocationHandler  	
		PepoleInvocationHander handler = new PepoleInvocationHander(new User());
		// 根据目标对象生成代理对象  
		Pepole p = (Pepole) handler.getProxy();
		// 调用代理对象的方法  
		p.printName();
	}
}
//代理类 实现 InvocationHandler,类似于PepolePoxy实现Pepole接口
class PepoleInvocationHander implements InvocationHandler{
	//持有被代理对象
	private Object target;	
	public PepoleInvocationHander(Object target){
		this.target = target;
	}
	//被代理类需要特殊处理的方法
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if(method.getName().startsWith("print")){
			System.out.println("执行前处理");
		}
		//注意invoke方法中,传递的对象是代理对象的实例,即target,而不是proxy
		method.invoke(target, args);
		if(method.getName().startsWith("print")){
			System.out.println("执行后处理");
		}
		return null;
	}
	public Object getProxy(){
		//生成代理对象,即User对象的代理对象。
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}	
}

java的动态代理,就是根据被代理类由java生成一个代理类class,省去了我们写代理类,该代理类自动提供方法接口中对应的方法,省去了我们自己写方法的步骤,而方法内部就是调用InvocationHandler的invoke方法。实现动态代理只需要创建一个自有的MyInvocationHandler实现InvocationHandler接口即可,实现方法invoke即可。

Proxy源码查看

** 
 * loader:类加载器 
 * interfaces:目标对象实现的接口 
 * h:InvocationHandler的实现类 
 */  
public static Object newProxyInstance(ClassLoader loader,  
                      Class<?>[] interfaces,  
                      InvocationHandler h)  
    throws IllegalArgumentException  
    {  
    if (h == null) {  
        throw new NullPointerException();  
    }  
  
    /* 
     * Look up or generate the designated proxy class. 
     */  
     //获取代理类的Class对象,这是Proxy中最重要的方法
    Class cl = getProxyClass(loader, interfaces);  
  
    /* 
     * Invoke its constructor with the designated invocation handler. 
     */  
    try {  
        // 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))  
        Constructor cons = cl.getConstructor(constructorParams);  
        // 生成代理类的实例并把MyInvocationHandler的实例传给它的构造方法
        return (Object) cons.newInstance(new Object[] { h });  
    } catch (NoSuchMethodException e) {  
        throw new InternalError(e.toString());  
    } catch (IllegalAccessException e) {  
        throw new InternalError(e.toString());  
    } catch (InstantiationException e) {  
        throw new InternalError(e.toString());  
    } catch (InvocationTargetException e) {  
        throw new InternalError(e.toString());  
    }  
 }  

进入getProxyClass()方法中

public static Class<?> getProxyClass(ClassLoader loader,   
                                         Class<?>... interfaces)  
    throws IllegalArgumentException  
    {  
    // 如果目标类实现的接口数大于65535个则抛出异常  
    if (interfaces.length > 65535) {  
        throw new IllegalArgumentException("interface limit exceeded");  
    }  
  
    // 声明代理对象所代表的Class对象(有点拗口),即代理对象的Class对象。  
    Class proxyClass = null;    
    String[] interfaceNames = new String[interfaces.length];    
    Set interfaceSet = new HashSet();   // for detecting duplicates    
    // 遍历目标类所实现的接口  
    for (int i = 0; i < interfaces.length; i++) {  
          
        // 拿到目标类实现的接口的名称  
        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");  
        }  
  
        // 中间省略了一些无关紧要的代码 .......  
          
        // 把目标类实现的接口代表的Class对象放到Set中  
        interfaceSet.add(interfaceClass);    
        interfaceNames[i] = interfaceName;  
    }  
  
    // 把目标类实现的接口名称作为缓存(Map)中的key  
    Object key = Arrays.asList(interfaceNames);  
  
    Map cache;  
      
    synchronized (loaderToCache) {  
        // 从缓存中获取cache  
        cache = (Map) loaderToCache.get(loader);  
        if (cache == null) {  
        // 如果获取不到,则新建地个HashMap实例  
        cache = new HashMap();  
        // 把HashMap实例和当前加载器放到缓存中  
        loaderToCache.put(loader, cache);  
        }  
  
    }  
  
    synchronized (cache) {  
  
        do {  
        // 根据接口的名称从缓存中获取对象,即代理对象实现的所有接口作为key,从缓存中获取代理对象的Class对象,如果接口的顺序发生变换,那么代理对象的Class对象就不是同一个。
        Object value = cache.get(key);  
        if (value instanceof Reference) {  
            proxyClass = (Class) ((Reference) value).get();  
        }  
        if (proxyClass != null) {  
            // 如果代理对象的Class实例已经存在,则直接返回  
            return proxyClass;  
        } else if (value == pendingGenerationMarker) {  
            try {  
            cache.wait();  
            } catch (InterruptedException e) {  
            }  
            continue;  
        } else {  
            cache.put(key, pendingGenerationMarker);  
            break;  
        }  
        } while (true);  
    }  
  
    try {  
        // 中间省略了一些代码 .......  
          
        // 缓存没有命中Class对象,需要生成Class对象,这里就是动态生成代理对象的最关键的地方  
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
            proxyName, interfaces);  
        try {  
            // 根据代理类的字节码生成代理类的实例  
            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);  
  
    }   
    // 中间省略了一些代码 .......  
      
    return proxyClass;  
 }  

进去ProxyGenerator类的静态方法generateProxyClass,这里是真正生成代理类class字节码的地方。

public static byte[] generateProxyClass(final String name,  
                                           Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
    // 这里动态生成代理类的字节码,由于比较复杂就不进去看了  
       final byte[] classFile = gen.generateClassFile();  
  
    // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上,因此需要查看代理类的class文件,就可以直接设置saveGeneratedFiles属性为true(System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);),那么系统就会把代理对象的class文件写到硬盘中。
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }    
    // 返回代理类的字节码  
       return classFile;  
   }  

调用InvocationHandler的invoke方法

invoke方法的调用是在代理类的生成的方法中,看一看代理类。

如何生成代理类的class文件
1 在上述main方法中添加:System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);//告诉系统把代理类的class文件写到磁盘上。
运行main方法后,会提示:java.io.FileNotFoundException: dynamic\proxy$Proxy0.class (系统找不到指定的路径,无法找到代理类所在的dynamic\proxy路径,这其实是被代理对象的报名变成了路径,需要在项目的根目录下新建上述目录。例如我的项目在workspace\CompsiteTest中,那么在目录下建立相关的目录即可。

生成的class文件如下

package dynamic.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

//动态代理生成的class文件,类名直接是$Proxy,后面的数字是自增的,同时继承自Proxy类,实现了代理类的所有接口,这就是jdk动态代理的一个缺陷,被代理类不能继承其他类,因为它必须继承Proxy类。同时被代理类必须实现一个接口
public final class $Proxy0 extends Proxy
  implements Pepole
{
  private static Method m1;
  private static Method m0;
  private static Method m3;
  private static Method m2;
 //构造函数: 看到这儿InvocationHandler就会发现,定义的MyInvocationHandler作为这个的参数
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
	 //调用Poxy的构造方法,把自定义的InvocationHandler设置为父类的handler属性的值。
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    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 int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  //需要特殊处理的方法 printName
  public final void printName()
    throws 
  {
    try
    {
	  //这儿就是调用 invoke方法,通过InvocationHandler对象h,调用invoke,之所以传入Method属性,是因为通过动态代理生成的对象,并不持有被代理对象的实例,因此只能通过Method进行调用,同时传入Method属性,也可以让MyInvocationHandler对方法进行特殊处理,例如:spring中,只对save开头的方法开启事务。
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    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就能看出代理类的equals方法是调用的Object对象的equals方法。同样hashCode,soString同样都是调用Object的方法。
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      //通过Class.forName获取Class对象,反射获取printName方法。
      m3 = Class.forName("dynamic.proxy.Pepole").getMethod("printName", 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());
    }
  }
}

jdk动态代理总结

1 被代理对象不能有继承父类,因为如果被代理对象有父类,那么生成的代理对象也需要有父类,但是代理对象必须有父类Proxy,因此被代理对象不能有父类。
2 被代理对象必须实现至少一个接口。
3 被代理对象生成的class文件与被代理对象是在同一个位置(包名相同),当然如果被代理对象的接口有私有的,那么生成的代理对象就会在私有接口同位置。
4 被代理对象的命名是固定格式:$ProxyNum,Num是自增的,例如:$Proxy0,$Proxy1

参考文献:
1 https://www.zhihu.com/question/20794107 主要是讨论为什么要变静态代理为动态代理。
2 http://rejoy.iteye.com/blog/1627405 动态代理的实现源码,本文内容也是参考该文章,虽然文章内容差不多,但是也有一些自己的体验,同时打字的过程也是加深记忆的过程。
3 http://www.cnblogs.com/ctgulong/p/5011614.html 比较好的有jdk动态代理和cglib代理,同时提出几种本地生成代理类class文件
4 http://www.cnblogs.com/hujunzheng/p/5134478.html?utm_source=tuicool&utm_medium=referral 介绍了3中动态代理的方式:jdk动态代理,动态字节码生成(cglib)、javassist生成动态代理
5 http://blog.csdn.net/sadfishsc/article/details/9999169 JAVAssis学习的推荐文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值