浅析JAVA设计模式之代理模式(五)

本文详细介绍了Java JDK提供的动态代理支持,包括动态代理的基本概念、使用步骤、代码生成原理及注意事项。

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

1. JDK提供代理支持   

        JAVAJDK为我们提供了一个比较完美的动态代理的支持,上几篇文章就是为了更好理解JDK提供的动态代理模式,而对其做了简单的模拟实现,有了前几篇文章的基础,接下来我们使用一下JDK提供的动态代理。

1.1 JDK动态代理简介

          首先JDK也提供了一个处理器接口java.lang.reflect.InvocationHandler,它自定义了一个 invoke方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数是代理类对象,第二个参数是委托类被调用的方法对象
// 第三个是该方法的参数。
Object invoke(Object proxy, Method method, Object[] args)

         然后,JDK也提供了一个生成代理类的类java.lang.reflect.Proxy,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。Proxy的静态方法如下:

//获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 

//获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 

//判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) 

//为指定类装载器、接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
    我们常用到newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)这个方法来生成一个代理对象。该方法的参数具体含义如下:
 java.lang.ClassLoader:指定一个被代理类装载器对象。这是类装载器类,负责将类的字节码装载到 JVM中并为其定义类 对象,然后该类才能被使用。newProxyInstance()方法生成代理类需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
 Class[] interfaces:指定一组被代理类实现的所有接口的类对象
 InvocationHandler h:指定一个实现了处理器接口的对象。

1.2 如何使用 Java 动态代理:简单解释为五个步骤

1.   通过实现 InvocationHandler 接口创建自己的处理器;

2.   通过给Proxy类的newProxyInstance()方法传进被代理类的ClassLoader对象、被代理类实现的interface类对象、实现了InvocationHandler接口的处理器,得到一个代理类对象。

3.   其中Proxy类通过反射机制获得代理类的构造函数,其唯一参数类型是处理器接口类型。

4.   Proxy类再通过构造函数对象创建动态代理类实例,构造时处理器对象作为参数被传入。

5.   当客户端调用了代理接口的方法时,该方法调用了处理器的invoke()方法并给invoke()方法传进委托类方法的类对象,invoke方法再调用委托类的真实方法。

1)新建一个包jdkDynamicProxy

2)建一个抽象接口。

package jdkDynamicProxy;
public  interface  Subject {
	public  void  print(String words);
}

3)建一个被代理类(RealSubject.java)实现抽象接口。

package jdkDynamicProxy;
public  class  RealSubject implements Subject{
	public  void  print(String words) {
		System.out.println("被代理的人郭襄说:\""+words+"!\"");
	}
}

4)写一个工具类LonHanderReflectTool.java,用来反射生成的代理类的信息。

package jdkDynamicProxy;
import java.lang.reflect.*;

//设计用来反射代理类,因为动态生成的代理类有声明所在包,所以可以获得包信息
//自定义的类如果没有package语句,也是没有声明包(也是默认包),用反射也获取不了包名

public class LonHanderReflectTool {
	//反射整个类
	public static void ReflectClass(String className){
		try{
			//获取类对象
			Class c=Class.forName(className);
			
        //打印所在包
         //使用反射JDK生成的代理类时,抽象接口是非public就需要这句,可以获得包信息
         //抽象接口如果是public,就不需要这句
         //printPackage(c);
			
		  //打印类声明、继承父类、所实现的接口
		   printClassSuperClassInterface(c);
		    
		    //打印成员变量
		    printAllFields(c);
		    System.out.println();
		    //打印构造方法
		    printAllConstructor(c);
		    System.out.println();
		    //打印其他方法
		    printAllmenthods(c);
		}catch(ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	//打印类声明、继承父类、所实现的接口
	public static void printClassSuperClassInterface(Class c){
       //打印类修饰符
		System.out.print(Modifier.toString(c.getModifiers())+" ");
		//打印类名
		System.out.print("class "+c.getSimpleName());
		//获取类的父类
		Class superClass=c.getSuperclass();
		//打印 "extends" 关键字
		System.out.print(" extends ");
		//打印父类名
	    System.out.print(superClass.getSimpleName());
       //打印"implements"关键字
	    System.out.print(" implements ");
	    //获取实现接口数组
	    Class []is=c.getInterfaces();
	    for(int i=0;i<is.length;i++){
	    	//打印接口名字
	    	System.out.print(is[i].getSimpleName());
	    	//如果不是打印到最后一个参数就打印一个逗号分隔
			if(i<is.length-1){
				System.out.print(",");
			}
	    }
	    //打印左大括号
	    System.out.println("{");
	}
	//打印所在包
	public static void printPackage(Class c){
		//获取包名
		Package p=c.getPackage();
		//打印"packgage"关键字
		System.out.print("package ");
		//打印包名
		System.out.print(p.getName());
		//打印包名最后的分号
		System.out.println(";");
	}
	//打印所有成员变量
	public static void printAllFields(Class c){
		//获取成员变量数组
		Field []fi=c.getDeclaredFields();
		for(Field f:fi){
			//打印每个成员变量的修饰符
			System.out.print(" "+Modifier.toString(f.getModifiers())+" ");
			//获取每个成员变量的类型
			Class c2=f.getType();
			//打印每个成员变量的类型
			System.out.print(c2.getSimpleName()+" ");
			//打印每个成员变量的变量名
			System.out.print(f.getName());
			//打印每个成员变量最后的分号
			System.out.println(";");
		}
	}
	
	//打印所有的构造方法
	public static void printAllConstructor(Class cz){
		//获取构造方法数组
		Constructor[]cs=cz.getConstructors();
		for(Constructor c:cs){
			//打印构造方法的修饰符
			System.out.print(" "+Modifier.toString(c.getModifiers())+" ");
			//打印构造方法的方法名
			System.out.print(cz.getSimpleName());
            //也是打印构造方法的方法名,(全限定名)
		  //System.out.print(c.getName());  // 因为是全限定名,所以不用这个方法
			//打印构造方法参数列表的左括号
			System.out.print("(");
			//获取构造方法里面的参数类型数组
			Class[] ps=c.getParameterTypes();
			//打印构造方法里面的参数类型
			for(int i=0;i<ps.length;i++){
				System.out.print(ps[i].getSimpleName());
				System.out.print(" arg"+i);
			//如果不是打印到最后一个参数就打印一个逗号分隔
				if(i<ps.length-1){
					System.out.print(",");
				}
			}//打印构造方法里面的参数的for循环的结尾
			//打印构造方法参数的右括号
			System.out.print(")");
			//获取方法中的异常类数组
			Class[] exceptions=c.getExceptionTypes();
			//打印"throws"关键字
			if(exceptions.length!=0){
				System.out.print(" throws ");
			}
			//打印异常类型
			for(int i=0;i<exceptions.length;i++){
				System.out.print(exceptions[i].getSimpleName());
				//如果不是打印到最后一个异常就打印一个逗号分隔
				if(i<exceptions.length-1){
					System.out.print(",");
				}
			}
			System.out.println("{}");
		}//foreach循环结尾
	}
	//打印其他方法
	public static void printAllmenthods(Class cz){
		//获取方法数组
		//Method[]ms=cz.getMethods(); 这个只能获取共有的方法
		Method[]ms=cz.getDeclaredMethods();//不管私有、公有、保护类型的方法都获取
		for(Method m:ms){
            //打印方法的修饰符
			System.out.print(Modifier.toString(m.getModifiers())+" ");
			//获取泛型方法的类型数组
			TypeVariable[]yvs=m.getTypeParameters();
			//如果该方法定义为了泛型方法
			if(yvs.length!=0){
			//打印泛型左尖括号"<"
			System.out.print(" <");
			for(TypeVariable yv:yvs){
				//打印泛型定义标识符
				System.out.print(yv.getName());
			}
            //打印泛型右尖括号">"
			System.out.print("> ");
			}
			//获取方法返回类型
			Type ts=m.getGenericReturnType();
			//如果返回类型有泛型标识符E或者T
			if(ts.toString().charAt(0)=='E'||ts.toString().charAt(0)=='T'){
				//打印泛型返回类型标识符
				 System.out.print(ts.toString());
			//没有的话,打印普通的返回类型
			}else{
			//获取方法返回类型
            Class mt=m.getReturnType();			
            //打印方法返回类型
            System.out.print(" "+mt.getSimpleName());
			}
            //打印方法名
            System.out.print(" "+m.getName());
            //打印方法参数列表的左括号
            System.out.print("(");
           //获取方法参数类型数组(包含泛型)
            Type [] gms=m.getGenericParameterTypes();
            if(gms.length!=0){
                for(int i=0;i<gms.length;i++){
                	//打印方法参数类型
                	if(gms.toString().startsWith("E")||gms.toString().startsWith("T")){
                		System.out.print(gms[i]);
                		//打印方法参数名
                		System.out.print(" args"+i);
                	}else{
                		//如果不是泛型则取全限定名最后一个点后面的类名
                		//获取最后一个点的位置
                    	int lastPoint=gms[i].toString().lastIndexOf('.');
                    	//获取最后一个点后面的类的名字
                    	String ClassSimpleName=gms[i].toString().substring(lastPoint+1);
                    	System.out.print(ClassSimpleName);
                    	//打印方法参数名
                    	System.out.print(" args"+i);
                	}
                	//如果不是打印到最后一个参数就打印一个逗号分隔
                	if(i<gms.length-1){
                		System.out.print(",");
                	}
                }
            }
            //打印方法参数列表右括号
            System.out.print(")");
            //获取方法异常类型数组
            Class[] exceptions=m.getExceptionTypes();
            //打印"throws"关键字
            if(exceptions.length!=0){
            	System.out.print("throws");
            }
            //打印方法异常类型
            for(int i=0;i<exceptions.length;i++){
            	System.out.print(" "+exceptions[i].getSimpleName());
            }
            //打印方法方法体大括号
            System.out.println("{}");
		}
	}
}

5)建一个处理器类(LogHandler.java)实现java.lang.reflect. InvocationHandler接口。

package jdkDynamicProxy;
import java.lang.reflect.*;
public class LogHandler implements InvocationHandler{  
private Object delegate; //被代理类的对象   
	//绑定被代理类的对象
	public Object bind(Object delegate)throws Exception{
		this.delegate=delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
	}  
    public  Object invoke(Object proxy, Method method , Object[] args) throws Exception{
		Object result=null;
		System.out.println("我是代理人郭靖,开始代理");
		result=method.invoke(delegate,args);
		System.out.println("我是代理人郭靖,代理完毕");
		//调用工具类反射jdk的Proxy生成的代理类
		LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
		return result;
	 }
}

6)建立测试客户端(TestJdkDynamicDynamicProxy.java)。

package jdkDynamicProxy;
public class TestJdkDynamicDynamicProxy {
	public static void main(String[] args)throws Exception {
		Subject sub1=new RealSubject();
		LogHandler hander=new LogHandler();
		Subject sub2=(Subject)hander.bind(sub1);
		sub2.print("你好");
	}
}

输出结果:

我是代理人郭靖,开始代理

被代理的人郭襄说:"你好!"

我是代理人郭靖,代理完毕

public final class $Proxy0 extends Proxy implements Subject{

 private static Method m1;

 private static Method m3;

 private static Method m0;

 private static Method m2;

 

 public $Proxy0(InvocationHandler arg0){}

 

public final boolean equals(Object args0){}

public final String toString(){}

public final int hashCode(){}

public final void print(String args0){}

         结果可以看出,成功使用JDK提供的支持实现了代理。并且生成了没有声明包的代理类$Proxy0,而且也没有具体的.class文件直接加载在内存里面。我们分析一下JDK提供的代理支持特点:

1) 如果所代理的接口都是 public的,生成的代理类将被定义在顶层包(即包路径为空)。(可看查阅Proxy的源码得知)

2)如果所代理的接口中有非 public的接口(因为接口不能被定义为 protect private,除 public外就是默认的 package访问级别),生成的代理类将被定义在该接口所在包。比如把jdkDynamicProxy包中的Subject接口改成非public,那么生成代理类所在的包(通过上面自己写的反射工具LonHanderReflectTool反射看到)就是jdkDynamicProxy。这样设计可以最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问。(可看查阅Proxy的源码得知)

3)生成的代理类具有 final public修饰符,意味着它可以被所有的类访问,但不能被继承。我们看到生成的代理类名字是“$ProxyN”N是一个逐一递增的阿拉伯数字,代表 Proxy类第 N 次生成的动态代理类,并不是每次调用 Proxy的静态方法创建动态代理类都会使得 N值增加,如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会返回先前已经创建好的代理类的类,不会再创建一个新的代理类,这样可以避免代码重复生成,提高代理类的创建效率。大概原理是,代理类对象一旦被创立,会被Proxy放进一个Map里面,创建前检查Map里面如果已经有了相同的代理对象,则直接返回,不再创建(可看查阅Proxy的源码得知)。

4)生成的代理类的继承关系如图:

图1.1

        由反射出的代码和上图看出,Proxy类是所有它创建的代理类的父类,代理类实现了被代理类所实现的接口,如果被代理类有多个接口,那么代理类就会实现多个接口,本文例子中,被代理类只实现了一个Subject接口,所以代理类也只实现一个接口。所以代理类能够安全地强制转型为其所代理的某接口。

7)我们再分析一下代理类对象的一些特点

1每个代理类对象都有一个处理器对象,可以通过 Proxy提供的静态方法 getInvocationHandler()获得。

2)调用代理类对象所代理的接口中所声明的方法时,这些方法会调用处理器的invoke()方法,invole()方法再调用委托类托的真实方法。

3)我们从反射出的代码又观察到,委托类继承java.lang.Object中的三个方法hashCode()equals() toString(),也被代理了,可能的原因有:

一、是因为这些方法为 public且非 final类型,能够被代理类覆盖;

二、是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。

4)当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给处理器,而无论代理类对象是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

5)创建代理类所在的包,其原则如前所述,如果都为public接口,则包名为空字符串表示顶层包;如果所有非 public接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。

6)另外在Proxy源码中,我们可以看出,还考虑到了很多其他细节问题,如代理对象的回收等,值得大家一一研究,不过真正生成代理类代码的sun.misc.ProxyGenerator类源码SUN公司并没有公开,还有动态类的定义,则由 Proxy native静态方法 defineClass0执行。

 8)再来分析需要被代理的类所实现的那些接口特点:

1)被代理的类不能有重复的接口,以避免动态代理类代码生成时的编译错误。

2)这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。

3被代理的类所实现的接口中如果有非public的接口,这些接口必须在同一个包中,否则代理类生成会失败。

4)接口的数目不能超过 65535(可去查看JDK中的Proxy类的源代码)。

 9)分析一下生成的代理类的异常处理:

1)处理器接口InvocationHandler声明的方法抛出的是Throwable,理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但要求抽象接口中的方法支持Throwable异常,即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以处理器接口经常受限制,除非抽象接口中的方法支持抛 Throwable异常。

2)抽象接口方法可能声明支持一个异常列表,而调用处理器 invoke方法又可能抛出与接口方法不支持的异常,对于在 invoke方法中产生了抽象接口方法声明中不支持的异常情况, Java动态代理类已经设计好解决方法:对于不支持的异常,它将会抛出 UndeclaredThrowableException异常。这个异常是一个 RuntimeException类型,所以不会引起编译错误。通过该异常的 getCause方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

3)假设生成如下代理类:

import java.lang.reflect.Method;
public class $Proxy implements Subject {
	private dynamicProxy.InvocationHandler h;
	public $Proxy(InvocationHandler h) {
		super();
		this.h = h;
	}
	public void print() {
		Method md = null;
		try {
			md = Subject.class.getMethod("print");
		} catch (Exception e) {
			//异常处理 1
		}
		try {
			h.invoke(this, md);
		} catch (Exception e) {
			//异常处理2
		}
	}
}

        在异常处理 1处,由于我们有理由确保所有的信息如接口名、方法名和参数类型都准确无误,所以这部分异常发生的概率基本为零,所以基本可以忽略。

        在异常处理 2处,接口方法可能声明支持一个异常列表,而调用处理器 invoke方法又可能抛出与接口方法不支持的异常,对于不支持的异常,必须抛 UndeclaredThrowableException运行时异常。所以可以得出一个更加清晰的生成的代理类的异常处理 2的大概情况:

try {
			h.invoke(this, md);
		} catch (ExceptionA e) {
			// 接口方法支持 ExceptionA,可以抛出
			throw e;
		} catch (ExceptionB e) {
			// 接口方法支持 ExceptionB,可以抛出
			throw e;
		} catch (Throwable  e) {
			// 其他不支持的异常,一律抛 UndeclaredThrowableException
			throw new UndeclaredThrowableException(e);
		}
	}

        JDK提供的代理支持比我们自己模拟编写的完美很多,考虑了很多细节,通过前面几篇文章的分析,我们大概知道了关键了步骤和技术,所涉及的工作无非包括几个反射调用,以及对原始类型数据装箱或拆箱过程(接口的方法返回类型是原始类型则需要进行拆箱操作:

        假设抽象接口某一个方法有long返回值,则在生成的代理类代码如下:

import java.lang.reflect.Method;
public class $Proxy implements Subject{
 private dynamicProxy.InvocationHandler h;
   public $Proxy(InvocationHandler h) {
       super();
       this.h = h;
   }
  public long  print(){
	  Object result =null;
     try{ 
 	   Method md=Subject.class.getMethod("print");
 	  result=h.invoke(this,md);
       }catch (Exception e){ 
           e.printStackTrace();
       }
//返回类型是原始类型则需要进行拆箱操作
      return ((Long)result).longValue();
     }
}

        前几篇的文章的接口方法没有考虑到返回值,实现这个功能也不难,这里就不再实现了,读者可参考前面所有的分析,自行实现。

       有了以上的分析和理解,我们从代理模式中学到了编程的美和技巧,并且让我们使用JDK提供的代理支持时会更加得心应手。


 推荐文章:

浅析JAVA设计模式之代理模式(一)

http://blog.youkuaiyun.com/minidrupal/article/details/24807835

浅析JAVA设计模式之代理模式(二)

http://blog.youkuaiyun.com/minidrupal/article/details/24888271

浅析JAVA设计模式之代理模式(三)

http://blog.youkuaiyun.com/minidrupal/article/details/24985737

浅析JAVA设计模式之代理模式(四)

http://blog.youkuaiyun.com/minidrupal/article/details/25058433

浅析JAVA设计模式之代理模式(六)

http://blog.youkuaiyun.com/minidrupal/article/details/27948355

浅析JAVA设计模式之代理模式(七)

http://blog.youkuaiyun.com/minidrupal/article/details/28588507

Author: Piano
Introduction: 师

Sign:
读书得可道之道,实践悟不可道之道





 



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值