写点什么

深入理解 java 反射机制及应用 | 京东物流技术团队

  • 2024-05-09
    北京
  • 本文字数:13478 字

    阅读完需:约 44 分钟

因为最近项目中经常有 java 反射的使用,而其中的 IOC、动态代理用到了反射,因此趁这个机会来总结一下关于 Java 反射的一些知识,复习一下。本篇基于 JDK 1.8。

java 反射机制是什么

反射原理

Java 反射机制(Java Reflection) 是 Java 的特征之一,是 Java 语言中一种动态(运行时)访问、检测和修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息、调用对象的方法。简单点的说就是 Java 程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:


  • 在运行时判断任意一个对象所属的类;

  • 在运行时构造任意一个类的对象;

  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用 private 方法);

  • 在运行时调用任意一个对象的方法


重点: 是运行时而不是编译时


多数情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过 new 实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射。反射则是一开始并不知道要初始化的是什么类,无法使用 new 来实例化创建对象,主要是通过 JDK 提供的反射 API 来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射。


举例:


public class Dog {    private int id;
public void setId(int id) { this.id = id; } public int getId() { return id; }
public static void main(String[] args) throws Exception{ //一、正射调用过程 Dog dog = new Dog(); dog.setId(1); System.out.println("这是一个正射调用过程Dog id:" + dog.getId()); //二、反射调用过程 Class clz = Class.forName("com.learning.java.Dog"); Constructor dogConstructor = clz.getConstructor(); Object dogObj = dogConstructor.newInstance(); //方法调用 Method setIdMethod = clz.getMethod("setId", int.class); setIdMethod.invoke(dogObj, 2); Method getIdMethod = clz.getMethod("getId"); System.out.println("这是一个反射调用过程Dog id:" + getIdMethod.invoke(dogObj)); }}
复制代码


输出结果:


这是一个正射调用过程Dog id:1这是一个反射调用过程Dog id:2
复制代码


Java 语言是一种面向对象的语言,在面向对象的世界里,万事万物皆对象,那我们写的 class 类也是对象,他们都是 java.lang.Class 的对象。我们在写类的时候,并没有显式的写这个对象,我们写的类会编译成一个类,生成一个 class 文件,而编译器就把 java.lang.Class 的这个对象存放在 class 文件的末尾,里面保存了类的元数据信息,这些元数据信息都包括类的所有信息,比如它是类还是接口、集成和实现了哪些类和接口,有什么属性,有什么方法,我们在 new 一个对象的时候,可以 new 很多对象,但是这个类生成的 class 对象只能有一个(在不同的类加载器,可能有多个,这里涉及到虚拟机的知识了)。我们在实例化 Dog 这个的类对象的时候,虚拟机会去检查,在虚拟机里面,这个类有没有被加载过,如果没有,虚拟机会先加载 Dog 对应的这个 class 对象,加载完之后,才会轮到 Dog 实例化本身的对象。

获取类的 java.lang.Class 实例对象常见的三种方式

获取类的 java.lang.Class 实例对象,常见的三种方式分别为:


  • 通过 ObjectClass.class 获取,这里的 ObjectClass 指具体类,JVM 会使用 ClassLoader 类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回 java.lang.Class 对象。

  • 通过 Class.forName ("类的全局定名")获取,全局定名为包名+类名,类会被 JVM 加载到内存中,并且会进行类的静态初始化工作,返回 java.lang.Class 对象。

  • 通过 new 通过 ObjectClass().getClass() 获取,这里的 ObjectClass 指具体类,使用了 new 进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass 方法属于顶级 Object 类中的方法,任何子类对象都可以调用,调用时返回子类的 java.lang.Class 对象。


这几种方式,最终在 JVM 堆区对应类的 java.lang.Class 对象都属于同一个,也就是内存地址相同,进行双等号比较结果为 true,原因是 JVM 类加载过程中使用的是同一个 ClassLoader 类加载器加载某个类,不论加载多少次,生成到堆区的 java.lang.Class 对象始终只有一个,除非自定义类加载器,破坏 JVM 的双亲委派机制,使得同一个类被不同类加载器加载,JVM 才会把它当做两个不同的 java.lang.Class 对象。


下面创建一个实体类,分别在实体类中创建类的静态代码块、动态代码块、有参构造方法、无参构造方法,方便测试几种方式的区别及内存地址是否相同


public class ObjectClass {    private static final String staticStr = "Hi";    private static int staticInt = 2024;    private static Class<?> class1;    private String id;
static { System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt); }
{ System.out.println("动态代码块~"); } public ObjectClass() { System.out.println("无参构造方法~"); }
public ObjectClass(String id) { System.out.println("有参构造方法~"); this.id = id; } public void setId(String id) { this.id = id; } public static void main(String[] args) throws ClassNotFoundException { System.out.println("1====================================="); System.out.println("一、ObjectClass.class方式========="); Class<?> class1 = ObjectClass.class;
System.out.println("2=====================================");
System.out.println("二、Class.forName方式========="); Class class2 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");
System.out.println("3=====================================");
System.out.println("三、new ObjectClass().getClass方式========="); Class class3 = new ObjectClass().getClass();
System.out.println("11=====================================");
System.out.println("一、ObjectClass.class方式========="); Class<?> class11 = ObjectClass.class; System.out.println("二、Class.forName方式========="); Class class12 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");
System.out.println("22=====================================");
System.out.println("一、ObjectClass.class方式========="); Class<?> class21 = ObjectClass.class; System.out.println("三、new ObjectClass().getClass方式========="); Class class23 = new ObjectClass().getClass();
System.out.println("33=====================================");
System.out.println("二、Class.forName方式========="); Class class31 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass"); System.out.println("三、new ObjectClass().getClass方式========="); Class class33 = new ObjectClass().getClass();
System.out.println("44=====================================");
System.out.println("四、三种方式内存地址比较========="); Class<?> class41 = ObjectClass.class; Class class42 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass"); Class class43 = new ObjectClass().getClass(); System.out.println("比较结果========="); System.out.println("ObjectClass.class和Class.forName内存地址比较是否相同:" + (class41 == class42)); System.out.println("ObjectClass.class和new ObjectClass().getClass内存地址比较是否相同:" + (class41 == class43)); System.out.println("Class.forName和new ObjectClass().getClass内存地址比较是否相同:" + (class42 == class43)); }}
复制代码


输出结果:


静态代码块:staticStr=Hi,staticInt=20241=====================================一、ObjectClass.class方式=========2=====================================二、Class.forName方式=========3=====================================三、new ObjectClass().getClass方式=========动态代码块~无参构造方法~11=====================================一、ObjectClass.class方式=========二、Class.forName方式=========22=====================================一、ObjectClass.class方式=========三、new ObjectClass().getClass方式=========动态代码块~无参构造方法~33=====================================二、Class.forName方式=========三、new ObjectClass().getClass方式=========动态代码块~无参构造方法~44=====================================四、三种方式内存地址比较=========动态代码块~无参构造方法~比较结果=========ObjectClass.class和Class.forName内存地址比较是否相同:trueObjectClass.class和new ObjectClass().getClass内存地址比较是否相同:trueClass.forName和new ObjectClass().getClass内存地址比较是否相同:true
复制代码

获取构造器对象

public class Cat {    String name;    private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; System.out.println("this is setName"); }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; System.out.println("this is setAge"); }
/*** * 包含一个带参的构造方法和不带参的构造方法 * @param name * @param age */ public Cat(String name, int age) { this.name = name; this.age = age; }
public Cat() { }
//私有方法 private void privateMethod() { System.out.println("我是私有方法"); }
public static void testConstructor() throws Exception { String className = "cn.learning.java.reflect.classtest.test.Cat"; Class<Cat> clazz = (Class<Cat>) Class.forName(className); System.out.println("获取全部Constructor对象-----"); Constructor<Cat>[] constructors = (Constructor<Cat>[]) clazz.getConstructors(); for (Constructor<Cat> constructor : constructors) { System.out.println(constructor); } System.out.println("获取某一个Constructor对象 需要参数列表----"); Constructor<Cat> constructor = clazz.getConstructor(String.class, int.class); System.out.println(constructor);
System.out.println("调用Constructor的newInstance方法创建对象----"); Cat cat1 = constructor.newInstance("小名", 18); System.out.println(cat1.getName()); }
public static void main(String[] args) throws Exception { testConstructor(); }}
复制代码


执行结果:


获取全部Constructor对象-----public cn.learning.java.reflect.classtest.test.Cat()public cn.learning.java.reflect.classtest.test.Cat(java.lang.String,int)获取某一个Constructor对象 需要参数列表----public cn.learning.java.reflect.classtest.test.Cat(java.lang.String,int)调用Constructor的newInstance方法创建对象----小名
复制代码


这里需要说一下因为我们的构造方法的参数类型是 int 型的,所以我们再获取构造器的时候传入的参数一定是 int.class 而不能是 Integer.class,不然会报没有找到方法异常。

获取方法并执行相对应的方法

public static void testMethod() throws Exception {        String className = "cn.learning.java.reflect.classtest.test.Cat";        Class clazz = Class.forName(className);        System.out.println("获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法");        Method[] methods = clazz.getMethods();        for (Method method : methods) {            System.out.println(method.getName() + "()");        }        System.out.println("=====================================");        System.out.println("获取所有方法,包括私有方法、所有声明的方法,且获取当前类方法");        methods = clazz.getDeclaredMethods();        for (Method method : methods) {            System.out.println(method.getName() + "()");        }        System.out.println("=====================================");        System.out.println("获取指定方法,和获取构造器的差不多,需要方法名称 和参数列表 无参则不写");        Method method = clazz.getDeclaredMethod("setName", String.class);        System.out.println(method);        method = clazz.getDeclaredMethod("setAge", int.class);        System.out.println(method);        System.out.println("=====================================");        System.out.println("执行我们获取的方法");        Object object = clazz.newInstance();        //第一个参数 这个方法所在类的实例,可变参数 参数列表        method.invoke(object, 18);        System.out.println("=====================================");        System.out.println("执行私有方法======");        method = clazz.getDeclaredMethod("privateMethod");        //在执行私有方法之前 一定要 执行这句代码。把Accessible设成true        method.setAccessible(true);        method.invoke(object);
}
复制代码


运行结果:


获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法main()getName()setName()testConstructor()testMethod()getAge()setAge()wait()wait()wait()equals()toString()hashCode()getClass()notify()notifyAll()=====================================获取所有方法,包括私有方法、所有声明的方法,且获取当前类方法main()getName()setName()privateMethod()testConstructor()testMethod()getAge()setAge()=====================================获取指定方法,和获取构造器的差不多,需要方法名称 和参数列表 无参则不写public void cn.learning.java.reflect.classtest.test.Cat.setName(java.lang.String)public void cn.learning.java.reflect.classtest.test.Cat.setAge(int)=====================================执行我们获取的方法this is setAge=====================================执行私有方法======我是私有方法
复制代码


反射获取调用类可以通过 Class.forName(),反射获取类实例要通过 newInstance(),相当于 new 一个新对象,反射获取方法要通过 getMethod(),获取到类方法之后使用 invoke() 对类方法进行调用。如果是类方法为私有方法的话,则需要通过 setAccessible(true) 来修改方法的访问限制。

通过反射访问成员变量

public static void testFiled() throws Exception {        String className = "cn.learning.java.reflect.classtest.test.Cat";        Class clazz = Class.forName(className);        System.out.println("获取共有和私有的所有字段,但不能获取父类字段");        Field[] fields = clazz.getDeclaredFields();        for (Field field : fields) {            System.out.println(field.getName());        }        System.out.println("=====================================");        System.out.println("获取指定字段");        Field field = clazz.getDeclaredField("name");        System.out.println(field.getName());
System.out.println("====================================="); System.out.println("获取指定字段的值"); Cat cat = new Cat("铭儿", 18); //第一个参数 这个方法所在类的实例 Object object = field.get(cat); System.out.println(field.getName() + "=" + object); System.out.println("====================================="); System.out.println("设置指定对象的值"); field.set(cat, "名儿猫猫"); System.out.println(field.getName() + "=" + cat.getName()); //访问私有字段 field = clazz.getDeclaredField("age"); field.setAccessible(true); field.get(cat); field.set(cat, 20); System.out.println(field.getName() + "=" + cat.getAge()); }
复制代码


执行结果:


获取共有和私有的所有字段,但不能获取父类字段nameage=====================================获取指定字段name=====================================获取指定字段的值name=铭儿=====================================设置指定对象的值name=名儿猫猫age=20
复制代码

动态代理模式

代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。


代理模式 UML 类图


静态代理

静态代理:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,目的是通过引入代理对象方式,来间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性,可以对对象的原有的业务进行增强。


  • 优点:可以在不修改目标对象的前提下扩展目标对象的功能。

  • 缺点:冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。违反开闭原则:扩展能力差,可维护性差。一旦接口增加方法,目标对象与代理对象都要进行修改。

  • 静态代理属于代理模式的一种代理方式,需要代理对象和目标对象实现相同的接口。

  • 静态代理的代理类是由程序员编写源码,编译后即可获取到代理类的 class 字节码文件,也就是在程序运行前就已经得到实际的代理类 class 字节码文件了。


举例:保存用户功能的静态代理实现


  • 接口类: IUserDao


public interface IUserDao {    public void save();}
复制代码


  • 目标对象:UserDao


public class UserDao implements IUserDao{
@Override public void save() { System.out.println("保存数据"); }}
复制代码


  • 静态代理对象:UserDapProxy 需要实现 IUserDao 接口!


public class UserDaoProxy implements IUserDao{
private IUserDao target; public UserDaoProxy(IUserDao target) { this.target = target; } @Override public void save() { System.out.println("开启事务");//扩展了额外功能 target.save(); System.out.println("提交事务"); }}
复制代码


  • 测试类:TestProxy


import org.junit.Test;
public class StaticUserProxy { @Test public void testStaticProxy(){ //目标对象 IUserDao target = new UserDao(); //代理对象 UserDaoProxy proxy = new UserDaoProxy(target); proxy.save(); }}
复制代码


  • 输出结果


开启事务保存数据提交事务
复制代码

动态代理

动态代理也属于代理模式的一种代理方式,不过只需要目标对象实现接口,代理对象不需要实现接口。动态代理的代理类编译后是没有 class 字节码文件的,而是在运行时利用 Java 反射机制动态的生成代理类的 class 字节码文件。动态代理被广为人知的使用场景是 Spring 中的面向切面编程(AOP)。例如,依赖注入 @Autowired 和事务注解 @Transactional 等,都是利用动态代理实现的。动态代理还可以封装一些 RPC 调用,也可以通过代理实现一个全局拦截器等。


动态代理是指代理关系在运行时确定的代理模式。需要注意,JDK 动态代理并不等价于动态代理,前者只是动态代理的实现之一,其它实现方案还有:CGLIB 动态代理、Javassist 动态代理和 ASM 动态代理等。因为代理类在编译前不存在,代理关系到运行时才能确定,因此称为动态代理。

JDK 原生动态代理

JDK 原生动态代理,主要利用了 JDK API 的 java.lang.reflect.Proxy 和 java.lang.relfect.InnvocationHandler 这两个类来实现。通过 java.lang.reflect.Proxy 代理类的 newProxyInstance 方法,传递 3 个参数,分别是:


  • 目标对象的加载器,通过 MyClass.getClass().getClassLoader 方式获取。

  • 目标对象的实现接口类型,通过 Object.getClass().getInterfaces() 方式获取。

  • InnvocationHandler 事件处理器,通过 new 实例化对象并重写 invoke 方法方式获取。


代码实现:



interface Animal { void eat();}
class Dog implements Animal { @Override public void eat() { System.out.println("The dog is eating"); }}// JDK 代理类 class AnimalProxy implements InvocationHandler { private Object target; // 代理对象
public Object getInstance(Object target) { this.target = target; // 取得代理对象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("调用前"); Object result = method.invoke(target, args); // 方法调用 System.out.println("调用后"); return result; } public static void main(String[] args) { // JDK 动态代理调用 AnimalProxy proxy = new AnimalProxy(); Animal dogProxy = (Animal) proxy.getInstance(new Dog()); dogProxy.eat(); }}
复制代码


注意: JDK Proxy 只能代理实现接口的类(即使是 extends 继承类是不可以代理的)。

cglib 动态代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。要是用 cglib 实现要添加对 cglib 的引用,如果是 maven 项目的话,直接添加以下代码:


<dependency>       <groupId>cglib</groupId>       <artifactId>cglib</artifactId>       <version>3.2.12</version>  </dependency>
复制代码


cglib 的具体实现,请参考以下代码:



class Panda { public void eat() { System.out.println("The panda is eating"); }}
class CglibProxy implements MethodInterceptor { private Object target; // 代理对象
public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); // 设置父类为实例类 enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); }
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("调用前"); Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用 System.out.println("调用后"); return result; }}class Test{ public static void main(String[] args) { // cglib 动态代理调用 CglibProxy proxy = new CglibProxy(); Panda panda = (Panda) proxy.getInstance(new Panda()); panda.eat(); }}
复制代码


执行结果为:


调用前 The panda is eating 调用后 
复制代码


cglib 的调用通过实现 MethodInterceptor 接口的 intercept 方法,调用 invokeSuper 进行动态代理的。它可以直接对普通类进行动态代理,并不需要像 JDK 代理那样,需要通过接口来完成,值得一提的是 Spring 的动态代理也是通过 cglib 实现的。


注意: cglib 底层是通过子类继承被代理对象的方式实现动态代理的,因此代理类不能是最终类(final),否则就会报错 java.lang.IllegalArgumentException: Cannot subclass final class xxx。

Proxy 如何生成的代理类

proxy 主要 api



核心源码 Proxy.java


//1、获取代理类 Class 对象public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces){    final Class<?>[] intfs = interfaces.clone();    ...    1.1 获得代理类 Class 对象    return getProxyClass0(loader, intfs);}
//2、实例化代理类对象public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){ ... final Class<?>[] intfs = interfaces.clone(); //2.1 获得代理类 Class对象 Class<?> cl = getProxyClass0(loader, intfs); ... //2.2 获得代理类构造器 (接收一个 InvocationHandler 参数) // private static final Class<?>[] constructorParams = { InvocationHandler.class }; final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; ... //2.3 反射创建实例 return newInstance(cons, ih);}
复制代码


可以看到,实例化代理对象也需要先通过 getProxyClass0(...) 获取代理类 Class 对象,而 newProxyInstance(...) 随后会获取参数为 InvocationHandler 的构造函数实例化一个代理类对象。


我们先看下代理类 Class 对象是如何获取的:


Proxy.java


//-> 1.1、2.1 获得代理类 Class对象private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {    ...    //从缓存中获取代理类,如果缓存未命中,则通过ProxyClassFactory生成代理类    return proxyClassCache.get(loader, interfaces);}
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
//3.1 代理类命名前缀 private static final String proxyClassNamePrefix = "$Proxy";
//3.2 代理类命名后缀,从 0 递增(原子 Long) private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); //3.3 参数校验 for (Class<?> intf : interfaces) { // 验证参数 interfaces 和 ClassLoder 中加载的是同一个类 // 验证参数 interfaces 是接口类型 // 验证参数 interfaces 中没有重复项 // 否则抛出 IllegalArgumentException } // 验证所有non-public接口来自同一个包
//3.4(一般地)代理类包名 String proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
//3.5 代理类的全限定名 long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num;
//3.6 生成字节码数据 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
//3.7 从字节码生成 Class 对象 return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); }}
//-> 3.6 生成字节码数据public static byte[] generateProxyClass(final String var0, Class[] var1) { ProxyGenerator var2 = new ProxyGenerator(var0, var1); ... final byte[] var3 = var2.generateClassFile(); return var3;}
复制代码


ProxyGenerator.java


private byte[] generateClassFile() {    //3.6.1 只代理Object的hashCode、equals和toString    this.addProxyMethod(hashCodeMethod, Object.class);    this.addProxyMethod(equalsMethod, Object.class);    this.addProxyMethod(toStringMethod, Object.class);
//3.6.2 代理接口的每个方法 ... for(var1 = 0; var1 < this.interfaces.length; ++var1) { ... } //3.6.3 添加带有 InvocationHandler 参数的构造器 this.methods.add(this.generateConstructor()); var7 = this.proxyMethods.values().iterator(); while(var7.hasNext()) { ... //3.6.4 在每个代理的方法中调用InvocationHandler#invoke() }
//3.6.5 输出字节流 ByteArrayOutputStream var9 = new ByteArrayOutputStream(); DataOutputStream var10 = new DataOutputStream(var9); ... return var9.toByteArray();}
复制代码


以上代码已经非常简化了,主要关注核心流程:JDK 动态代理生成的代理类命名为 com.sun.proxyProxy0),这个类继承自 java.lang.reflect.Proxy。其内部还有一个参数为 InvocationHandler 的构造器,对于代理接口的方法调用都会分发到 InvocationHandler#invoke()。 可以看到,ProxyGenerator#generateProxyClass() 其实是一个静态 public 方法,所以我们直接调用,并将代理类 Class 的字节流写入磁盘文件,使用 IntelliJ IDEA 的反编译功能查看源代码。


输出 Animal 字节码:


...byte[] classFile = ProxyGenerator.generateProxyClass("$proxy0", new Class[]{Animal.class});// 直接写入项目路径下,方便使用IntelliJ IDEA的反编译功能String path = "/Users/*/src/proxy/Animal.class";try(FileOutputStream fos = new FileOutputStream(path)){    fos.write(classFile);    fos.flush();    System.out.println("success");} catch(Exception e){    e.printStackTrace();    System.out.println("fail");}...
复制代码


反编译结果:


public final class $proxy0 extends Proxy implements Animal {    //反射的元数据Method存储起来,避免重复创建    private static Method m1;    private static Method m2;    private static Method m3;    ...    private static Method m0;
public $proxy0(InvocationHandler var1) throws ... { super(var1); }
/** * Object#hashCode() * Object#equals(Object) * Object#toString() */
// 实现了Animal接口 public final String eat() throws ... { try { //转发到Invocation#invoke() return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
static { try { //Object#hashCode() //Object#equals(Object) //Object#toString() ... m3 = Class.forName("Animal").getMethod("eat"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}
复制代码


查看反编译后的代码,总结一下


  1. AnimalProxy 实现了 InvocationHandler。

  2. 调用 AnimalProxy getProxyInstance 的时候将业务接口的 Class 信息传给 Proxy.newProxyInstance()。

  3. newProxyInstance 利用反射生成一个 $Proxy+number 的一个类。

  4. newProxyInstance,生成一个代理类的实例 将 InvocationHandler,也就是 AnimalProxy 传进去。

  5. 调用这个代理类的实例的 eat 方法 ,也就调用了 InvocationHandler,也就是 AnimalProxy 的 invoke 方法,完成了代理的对象方法的增强。

参考

  1. https://www.sczyh30.com/posts/Java/java-reflection-1/

  2. https://bbs.huaweicloud.com/blogs/301716#H17

  3. https://juejin.cn/post/6926090916939694088


作者:京东物流 梁宝彬


来源:京东云开发者社区

发布于: 刚刚阅读数: 4
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
深入理解java反射机制及应用 | 京东物流技术团队_京东科技开发者_InfoQ写作社区