`
jameswxx
  • 浏览: 776415 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java动态代理随笔二

    博客分类:
  • java
阅读更多

        jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。看了一下jdk带的动态代理 api,发现没有例子实在是很容易走弯路,所以这里写一个加法器的简单示例。
// Adder.java

package test;
public interface Adder {
     int add( int a, int b);
}

 

// AdderImpl.java

package test;
public class AdderImpl implements Adder {
 @Override
  public int add( int a, int b) {
     return a + b;
 }
}

 

现在我们有一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test测试一下。
// Test.java

package test;

public class Test {
 public static void main(String[] args) throws Exception {
 Adder calc = new AdderImpl();
int result = calc.add( 1 , 2 );
 System.out.println( "The result is " + result);
 }
}

 

很显然,控制台会输出:
The result is 3
然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:
Proxy: invoke add() at 2009-12-16 17:18:06
The result is 3

         动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口 java.lang.reflect.InvokationHandler),然后使用类java.lang.reflect.Proxy中的静态方法生成Adder的代理类,并把这个代理类当做原先的Adder使用就可以。
第一步:实现InvokationHandler,定义调用方法时应该执行的动作。
自定义一个类MyHandler实现接口 java.lang.reflect.InvokationHandler,需要重写的方法只有一个:
// AdderHandler.java

package test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
class AdderHandler implements InvocationHandler {
         /**
          * @param proxy 接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl
          * @param method 调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例
          * @param args 调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数
           * @return 使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值
          */
         @Override
         public Object invoke(Object proxy, Method method, Object[] args)
                 throws Throwable {
              // ...
         }
}

 

使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用 adder.add()方法时,实际执行的将是invoke()。所以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这时我们马上会想到这样做:
    Object returnValue = method.invoke(proxy, args);
如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后 adder引用所指向的对象。由于我们调用了adder.add(1, 2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy, args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为 MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:

    // 被代理的对象
    private Object target;
    public AdderHandler(Object target) {
          this .target = target;
    }

 

喜欢的话还可以加上getter/setter。接着,invoke()就可以这么用了:

// AdderHandler.java

package test;
import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Date;

    class AdderHandler implements InvocationHandler {
          // 被代理的对象
         private Object target;
   
          public AdderHandler(Object target) {
              this .target = target;
         }
          
         @Override
          public Object invoke(Object proxy, Method method, Object[] args)
                 throws Throwable {
             // 调用被代理对象的方法并得到返回值
             Object returnValue = method.invoke(target, args);
             // 调用方法前后都可以加入一些其他的逻辑
             System.out.println( "Proxy: invoke " + method.getName() + "() at " + new Date().toLocaleString());
             // 可以返回任何想要返回的值
             return returnValue;
          }
     }

 

    第二步:使用jdk提供的java.lang.reflect.Proxy生成代理对象。
使用newProxyInstance()方法就可以生成一个代理对象。把这个方法的签名拿出来:

/**
  * @param loader 类加载器,用于加载生成的代理类。
  * @param interfaces 需要代理的接口。这些接口的所有方法都会被代理。
  * @param h 第一步中我们建立的Handler类的实例。
  * @return 代理对象,实现了所有要代理的接口。
  */
    public static Object newProxyInstance(ClassLoader loader,
        Class<?>[] interfaces,
         InvocationHandler h)
    throws IllegalArgumentException

 

这个方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现,所有的接口中的方法都重写为调用 InvocationHandler.invoke()方法。这个类的代码类似于这样:

// 模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。
class AdderProxy extends Proxy implements Adder {
 protected AdderProxy(InvocationHandler h) {
   super (h);
 }

 @Override
 public int add( int a, int b) {
 try {
        Method m = Adder. class .getMethod( "add" , new Class[] { int . class , int . class });
    Object[] args = {a, b};
    return (Integer) h.invoke( this , m, args);
   } catch (Throwable e) {
     throw new RuntimeException(e);
   }
 }
}

 

据api说,所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api 生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。
看明白这个方法后,我们来改造一下main()方法。
// Test.java

package test;

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

public class Test {
     public static void main(String[] args) throws Exception {
         Adder calc = new AdderImpl();
              
             // 类加载器
             ClassLoader loader = Test. class .getClassLoader();
             // 需要代理的接口
             Class[] interfaces = {Adder. class };
             // 方法调用处理器,保存实际的AdderImpl的引用
             InvocationHandler h = new AdderHandler(calc);
              // 为calc加上代理
              calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h);
              
             /* 什么?你说还有别的需求? */
             // 另一个处理器,保存前处理器的引用
             // InvocationHandler h2 = new XXOOHandler(h);
             // 再加代理
             // calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2);
             
             int result = calc.add( 1 , 2 );
             System.out.println( "The result is " + result);
         }
    }

 

输出结果会是什么呢?
Proxy: invoke add() at 2009-12-16 18:21:33
The result is 3

对比一下之前的结果,你会发现这点东西写了我一个多小时。再来看看JDK有多懒:target完全可以在代理类中生成。
实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:return xxx.invoke(ooo);不过这么写也有他的理由,target自己管理,方法你爱调不调 ﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。

分享到:
评论

相关推荐

    Java完整随笔(学习)

    2. **面向对象编程**:Java的核心特性是面向对象,可能的笔记内容有类、对象、封装、继承、多态等概念,以及如何创建和使用这些概念进行程序设计。 3. **异常处理**:Java中的异常处理机制是编程时必须掌握的部分,...

    Java的动态代理机制详解

    对于IoC,依赖注入不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理是java的动态代理机制,所以本篇随笔是对java的...

    java记录随笔

    Java 记录随笔 Java 记录随笔是关于 Java 软件架构设计的笔记,涵盖了软件架构的基本原则、当前流行的技术、数据库存储结构、Web 界面用户接口层、业务层架构、持久层技术、XML 结构化信息传输和存储的重要性等多个...

    Java学习随笔6(Debug).docx

    2. 如何运行加了断点的程序:在代码区域右键 Debug 执行。 3. 看哪里:看 Debugger 窗口,看 console 窗口(程序执行过程中的结果显示)。 4. 点哪里:点 step into (F7)这个箭头,也可以直接按 F7,点 stop 结束...

    Java学习随笔4(数组)

    Java中的数组是一种基础且重要的数据结构,用于存储同种数据类型的多个值。数组提供了一种高效的方式来管理和操作一组数据,可以是整数、浮点数、字符或自定义对象。在Java中,数组的定义有两种格式: 1. `数据类型...

    【Java - 框架 - Knife4j】随笔

    【Java - 框架 - Knife4j】随笔 在Java Web开发中,文档的生成与维护是一项重要但繁琐的工作。Knife4j,作为一款专门为Java RESTful API设计的文档增强工具,它极大地简化了这个过程,为开发者带来了诸多便利。本篇...

    JAVA笔记随笔

    之前上传了JAVA笔记1 那么这次是JAVA笔记2 衔接上一次的

    Java学习随笔5(方法)

    Java中的方法是编程中至关重要的概念,它封装了一段可重复使用的代码,使得程序更加模块化,提高了代码的复用性和可读性。方法的定义和调用是编写Java程序的基础。 方法的定义通常包括访问修饰符(如public)、静态...

    Java学习随笔7(进制).docx

    例如,二进制数`0b100`转换为十进制,就是`0*2^0 + 0*2^1 + 1*2^2 = 0 + 0 + 4 = 4`。 十六进制到十进制的转换也类似,如`0x100`转换为十进制,即`0*16^0 + 0*16^1 + 1*16^2 = 0 + 0 + 256 = 256`。 对于任意进制...

    基于Java与跨平台技术的crostitch涂鸦随笔设计与实现源码

    本项目是一款基于Java和跨平台技术的crostitch涂鸦随笔设计与实现源码,包含65个文件,其中包括30个Java源文件、16个PNG图片文件、7个JavaScript文件、2个XML文件、1个Git忽略文件、1个Markdown文件、1个Maven项目...

    java课堂随笔

    java课堂随笔,我这还有很多,有需要的话可以联系我,我会为大家提供很多的资料

    Java学习随笔3(IDEA的概述和功能).docx

    二、IDEA的项目结构 在IDEA中,项目(Project)是最顶层的组织单位,它可以包含一个或多个模块(Module)。模块是项目中的独立部分,每个模块可以负责特定的业务功能。包(Package)则用于进一步组织类(Class),...

    ubuntu搭建Java项目运行环境-随笔记录.rar

    在本文中,我们将深入探讨如何在Ubuntu操作系统上搭建一个完整的Java项目运行环境,包括安装Java Development Kit (JDK) 1.8、MySQL 5.7数据库服务以及Apache Tomcat 9应用服务器。这个过程对于任何希望在Linux环境...

    深入Java虚拟机第二版chm英文

    9. **JVM与语言特性**:讨论JVM如何支持现代Java语言特性,如lambda表达式、动态类型、模块化系统等。 通过学习《深入Java虚拟机第二版》,开发者可以深入了解JVM的内部运作,从而更好地编写高效、可靠的Java程序。...

    二年级数学上册的教学随笔.docx

    二年级数学上册的教学随笔.docx

    Java代码调用HTML5中的JS函数算法

    6. **JSONP(JSON with Padding)**:适用于同源策略限制的场景,Java服务器端返回一个回调函数调用,JS通过动态插入`&lt;script&gt;`标签来执行这个回调。 具体到给定的文件中,"des.html"可能是一个关于数据解密的HTML...

    最新小学二年级道德与法治教学随笔.pdf

    最新小学二年级道德与法治教学随笔.pdf

    小学二年级道德与法治教学随笔.docx

    小学二年级道德与法治教学随笔.docx

    深入Java虚拟机第二版清晰版(带书签)

    7. **反射与动态代理**:Java的反射机制允许在运行时检查和修改程序行为,动态代理则提供了创建动态类型和接口实现的能力,这些在框架和库的开发中非常常见。 8. **安全机制**:JVM也扮演着安全管理的角色,书中...

    【Java-框架-SpringSecurity】单点登录(认证和授权)- 随笔

    SpringSecurity是Java领域中一款强大的安全框架,主要用于Web应用程序的安全管理,包括认证和授权。它提供了全面的安全控制,从简单的密码编码到复杂的访问控制策略,为开发者提供了丰富的功能。本随笔将深入探讨...

Global site tag (gtag.js) - Google Analytics