Java动态代理的实现机制

一、概述

十载专注成都网站制作,成都定制网页设计,个人网站制作服务,为大家分享网站制作知识、方案,网站设计流程、步骤,成功服务上千家企业。为您提供网站建设,网站制作,网页设计及定制高端网站建设服务,专注于成都定制网页设计,高端网页制作,对搬家公司等多个领域,拥有丰富的网站营销经验。

  代理是一种设计模式,其目的是为其他对象提供一个代理以控制对某个对象的访问,代理类负责为委托类预处理消息,过滤消息并转发消息以及进行消息被委托类执行后的后续处理。为了保持行为的一致性,代理类和委托类通常会实现相同的接口。

  按照代理的创建时期,代理类可分为两种:

  • 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,也就是说在程序运行前代理类的.class文件就已经存在。

  • 动态代理:在程序运行时运用反射机制动态创建生成。

  下面在将动态代理的实现机制之前先简单介绍一下静态代理。

二、静态代理

  上面说过,代理类和委托类一般都要实现相同的接口,下面先定义这个接口:

  
 
  1. public interface Service
  2. {    
  3.     public void add();
  4. }

  委托类作为接口的一种实现,定义如下:

 
 
  1. public class ServiceImpl implements Service
  2. {
  3.     public void add()
  4.     {
  5.         System.out.println("添加用户!");
  6.         
  7.     }
  8. }

  假如我们要对委托类加一些日志的操作,代理类可做如下定义:

 
 
  1. public class ServiceProxy implements Service
  2. {
  3.     private Service service;
  4.     public ServiceProxy(Service service)
  5.     {
  6.         super();
  7.         this.service = service;
  8.     }
  9.     public void add()
  10.     {
  11.         System.out.println("服务开始");
  12.         service.add();
  13.         System.out.println("服务结束");
  14.     }
  15. }

  编写测试类:

 
 
  1. public class TestMain
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         Service serviceImpl=new ServiceImpl();
  6.         Service proxy=new ServiceProxy(serviceImpl);
  7.         proxy.add();
  8.     }
  9. }

  运行测试程序,结果如下图:

  从上面的代码可以看到,静态代理类只能为特定的接口服务,如果要服务多类型的对象,就要为每一种对象进行代理。我们就会想是否可以通过一个代理类完成全部的代理功能,于是引入的动态代理的概念。

三、动态代理

  Java的动态代理主要涉及两个类,Proxy和InvocationHandler。

  Proxy:提供了一组静态方法来为一组接口动态地生成代理类及其对象。

 
 
  1. // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
  2. static InvocationHandler getInvocationHandler(Object proxy) 
  3. // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
  4. static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
  5. // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
  6. static boolean isProxyClass(Class cl) 
  7. // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
  8. static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)

  InvocationHandler:它是调用处理器接口,自定义了一个invok方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问

  
 
  1. // 该方法负责集中处理动态代理类上的所有方法调用。***个参数既是代理类实例,第二个参数是被调用的方法对象// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
  2. Object invoke(Object proxy, Method method, Object[] args)

  实现Java的动态代理,具体有以下四个步骤:

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

  2. 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类

  3. 通过反射机制获得动态代理类的构造函数,其***参数类型是调用处理器类接口类型

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

  下面根据上述的四个步骤来实现自己的动态代理的示例:

  接口和接口的实现类(即委托类)跟上面静态代理的代码一样,这里我们来实现InvocationHandler接口创建自己的调用处理器

 
 
  1. public class ServiceHandle implements InvocationHandler
  2. {
  3.     private Object s;
  4.     
  5.     public ServiceHandle(Object s)
  6.     {
  7.         this.s = s;
  8.     }
  9.     public Object invoke(Object proxy, Method method, Object[] args)
  10.             throws Throwable
  11.     {
  12.         System.out.println("服务开始");
  13.         //invoke表示对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
  14.         Object result=method.invoke(s, args);
  15.         System.out.println("服务结束");
  16.         return result;
  17.     }
  18. }

  编写测试类:

 
 
  1. public class TestMain
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         Service service=new ServiceImpl();
  6.         InvocationHandler handler=new ServiceHandle(service);
  7.         Service s=(Service) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), handler);
  8.         s.add();
  9.     }
  10. }

  运行测试程序,结果同静态代理。我们可以看到上述代码并没有我们之前说的步骤2和3,这是因为Prox的静态方法newProxyInstance已经为我们封装了这两个步骤。具体的内部实现如下:

 
 
  1. // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
  2. Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 
  3. // 通过反射从生成的类对象获得构造函数对象
  4. Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 
  5. // 通过构造函数对象创建动态代理类实例
  6. Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

  newProxyInstance函数的内部实现为:

 
 
  1. public static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)throws IllegalArgumentException
  2.   {
  3.              //检查h不为空,否则抛异常
  4.             Objects.requireNonNull(h);
  5.             //获得与制定类装载器和一组接口相关的代理类类型对象
  6.             final Class[] intfs = interfaces.clone();
  7.             
  8.             //检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的
  9.             final SecurityManager sm = System.getSecurityManager();    
  10.             if (sm != null) 
  11.             {
  12.                 checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
  13.             }
  14.             //获得与制定类装载器和一组接口相关的代理类类型对象
  15.             Class cl = getProxyClass0(loader, intfs);
  16.             try
  17.             {
  18.                 if (sm != null) 
  19.                 {
  20.                     checkNewProxyPermission(Reflection.getCallerClass(), cl);
  21.                 }
  22.                 // 通过反射获取构造函数对象并生成代理类实例
  23.                 final Constructor cons = cl.getConstructor(constructorParams);
  24.                 final InvocationHandler ih = h;
  25.                 if (!Modifier.isPublic(cl.getModifiers())) 
  26.                 {
  27.                     AccessController.doPrivileged(new PrivilegedAction() 
  28.                     {
  29.                         public Void run() 
  30.                         {
  31.                         cons.setAccessible(true);
  32.                         return null;
  33.                         }
  34.                     });
  35.                 }
  36.                 return cons.newInstance(new Object[]{h});
  37.             } 
  38.             catch (IllegalAccessException|InstantiationException e)
  39.             {
  40.                 throw new InternalError(e.toString(), e);
  41.             }
  42.             catch (InvocationTargetException e) 
  43.             {
  44.                 Throwable t = e.getCause();
  45.                 if (t instanceof RuntimeException) 
  46.                 {
  47.                     throw (RuntimeException) t;
  48.                 }
  49.                 else 
  50.                 {
  51.                     throw new InternalError(t.toString(), t);
  52.                 }
  53.             } 
  54.             catch (NoSuchMethodException e) 
  55.             {
  56.                 throw new InternalError(e.toString(), e);
  57.            }
  58.  }

四、模拟实现Proxy类

  根据上面的原理介绍,我们可以自己模拟实现Proxy类:

 
 
  1. public class Proxy
  2. {
  3.     public static Object newProxyInstance(Class inface,InvocationHandle h) throws Exception
  4.     {
  5.         String rt="\r\n";
  6.         String methodStr="";
  7.         Method[] methods=inface.getMethods();
  8.         for(Method m:methods)
  9.         {
  10.             methodStr+="@Override"+rt+
  11.                      "public void "+m.getName()+"()"+rt+"{" + rt +
  12.                     "   try {"+rt+
  13.                     "  Method md="+inface.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
  14.                         "h.invoke(this,md);"+rt+
  15.                     "   } catch(Exception e){e.printStackTrace();}"+rt+
  16.                     
  17.                     "}";
  18.         }
  19.         String src="package test;"+rt+
  20.                 "import java.lang.reflect.Method;"+rt+
  21.                 "public class ServiceImpl2 implements "+inface.getName()+ rt+
  22.                 "{"+rt+
  23.                     "public ServiceImpl2(InvocationHandle h)"+rt+
  24.                     "{"+rt+
  25.                         "this.h = h;"+rt+
  26.                     "}"+rt+
  27.                     " test.InvocationHandle h;"+rt+
  28.                     methodStr+
  29.                 "}";
  30.         String fileName="d:/src/test/ServiceImpl2.java";
  31.         //compile
  32.         compile(src, fileName);
  33.         //load into memory and create instance
  34.         Object m = loadMemory(h);
  35.         
  36.         return m;
  37.     }
  38.     private static void compile(String src, String fileName) throws IOException
  39.     {
  40.         File f=new File(fileName);
  41.         FileWriter fileWriter=new FileWriter(f);
  42.         fileWriter.write(src);
  43.         fileWriter.flush();
  44.         fileWriter.close();
  45.         //获取此平台提供的Java编译器
  46.         JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();
  47.         //获取一个标准文件管理器实现的新实例
  48.         StandardJavaFileManager fileManager=compiler.getStandardFileManager(null,null, null);
  49.         //获取表示给定文件的文件对象
  50.         Iterable units=fileManager.getJavaFileObjects(fileName);
  51.         //使用给定组件和参数创建编译任务的 future
  52.         CompilationTask t=compiler.getTask(null, fileManager, null, null, null, units);
  53.         //执行此编译任务
  54.         t.call();    
  55.         fileManager.close();
  56.     }
  57.     private static Object loadMemory(InvocationHandle h)
  58.             throws MalformedURLException, ClassNotFoundException,
  59.             NoSuchMethodException, InstantiationException,
  60.             IllegalAccessException, InvocationTargetException
  61.     {
  62.         URL[] urls=new URL[] {new URL("file:/"+"d:/src/")};
  63.         //从路径d:/src/加载类和资源
  64.         URLClassLoader ul=new URLClassLoader(urls);
  65.         Class c=ul.loadClass("test.ServiceImpl2");
  66.         //返回Class对象所表示的类的指定公共构造方法。    
  67.         Constructor ctr=c.getConstructor(InvocationHandle.class);
  68.         //使用此 Constructor对象ctr表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例
  69.         Object m = ctr.newInstance(h);
  70.         return m;
  71.     }
  72. }

五、总结

  1、所谓的动态代理就是这样一种class,它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后改 class就宣称它实现了这些interface,但是其实它不会替你作实质性的工作,而是根据你在生成实例时提供的参数handler(即 InvocationHandler接口的实现类),由这个Handler来接管实际的工作。

  2、Proxy的设计使得它只能支持interface的代理,Java的继承机制注定了动态代理类无法实现对class的动态代理,因为多继承在Java中本质上就行不通。

当前文章:Java动态代理的实现机制
本文地址:http://www.gawzjz.com/qtweb2/news48/29748.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联