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

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

java反射机制是什么

反射原理

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

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

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

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

举例:

1public class Dog { 2 private int id; 3 4 public void setId(int id) { 5 this.id = id; 6 } 7 public int getId() { 8 return id; 9 } 10 11 public static void main(String[] args) throws Exception{ 12 //一、正射调用过程 13 Dog dog = new Dog(); 14 dog.setId(1); 15 System.out.println("这是一个正射调用过程Dog id:" + dog.getId()); 16 //二、反射调用过程 17 Class clz = Class.forName("com.learning.java.Dog"); 18 Constructor dogConstructor = clz.getConstructor(); 19 Object dogObj = dogConstructor.newInstance(); 20 //方法调用 21 Method setIdMethod = clz.getMethod("setId", int.class); 22 setIdMethod.invoke(dogObj, 2); 23 Method getIdMethod = clz.getMethod("getId"); 24 System.out.println("这是一个反射调用过程Dog id:" + getIdMethod.invoke(dogObj)); 25 } 26}

输出结果:

1这是一个正射调用过程Dog id:1 2这是一个反射调用过程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 对象。

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

1public class ObjectClass { 2 private static final String staticStr = "Hi"; 3 private static int staticInt = 2024; 4 private static Class<?> class1; 5 private String id; 6 7 static { 8 System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt); 9 } 10 11 { 12 System.out.println("动态代码块~"); 13 } 14 public ObjectClass() { 15 System.out.println("无参构造方法~"); 16 } 17 18 public ObjectClass(String id) { 19 System.out.println("有参构造方法~"); 20 this.id = id; 21 } 22 public void setId(String id) { 23 this.id = id; 24 } 25 public static void main(String[] args) throws ClassNotFoundException { 26 System.out.println("1====================================="); 27 System.out.println("一、ObjectClass.class方式========="); 28 Class<?> class1 = ObjectClass.class; 29 30 System.out.println("2====================================="); 31 32 System.out.println("二、Class.forName方式========="); 33 Class class2 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass"); 34 35 System.out.println("3====================================="); 36 37 System.out.println("三、new ObjectClass().getClass方式========="); 38 Class class3 = new ObjectClass().getClass(); 39 40 System.out.println("11====================================="); 41 42 System.out.println("一、ObjectClass.class方式========="); 43 Class<?> class11 = ObjectClass.class; 44 System.out.println("二、Class.forName方式========="); 45 Class class12 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass"); 46 47 System.out.println("22====================================="); 48 49 System.out.println("一、ObjectClass.class方式========="); 50 Class<?> class21 = ObjectClass.class; 51 System.out.println("三、new ObjectClass().getClass方式========="); 52 Class class23 = new ObjectClass().getClass(); 53 54 System.out.println("33====================================="); 55 56 System.out.println("二、Class.forName方式========="); 57 Class class31 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass"); 58 System.out.println("三、new ObjectClass().getClass方式========="); 59 Class class33 = new ObjectClass().getClass(); 60 61 System.out.println("44====================================="); 62 63 System.out.println("四、三种方式内存地址比较========="); 64 Class<?> class41 = ObjectClass.class; 65 Class class42 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass"); 66 Class class43 = new ObjectClass().getClass(); 67 System.out.println("比较结果========="); 68 System.out.println("ObjectClass.class和Class.forName内存地址比较是否相同:" + (class41 == class42)); 69 System.out.println("ObjectClass.class和new ObjectClass().getClass内存地址比较是否相同:" + (class41 == class43)); 70 System.out.println("Class.forName和new ObjectClass().getClass内存地址比较是否相同:" + (class42 == class43)); 71 } 72}

输出结果:

1静态代码块:staticStr=Hi,staticInt=2024 21===================================== 3一、ObjectClass.class方式========= 42===================================== 5二、Class.forName方式========= 63===================================== 7三、new ObjectClass().getClass方式========= 8动态代码块~ 9无参构造方法~ 1011===================================== 11一、ObjectClass.class方式========= 12二、Class.forName方式========= 1322===================================== 14一、ObjectClass.class方式========= 15三、new ObjectClass().getClass方式========= 16动态代码块~ 17无参构造方法~ 1833===================================== 19二、Class.forName方式========= 20三、new ObjectClass().getClass方式========= 21动态代码块~ 22无参构造方法~ 2344===================================== 24四、三种方式内存地址比较========= 25动态代码块~ 26无参构造方法~ 27比较结果========= 28ObjectClass.class和Class.forName内存地址比较是否相同:true 29ObjectClass.class和new ObjectClass().getClass内存地址比较是否相同:true 30Class.forName和new ObjectClass().getClass内存地址比较是否相同:true

获取构造器对象

1public class Cat { 2 String name; 3 private int age; 4 5 public String getName() { 6 return name; 7 } 8 9 public void setName(String name) { 10 this.name = name; 11 System.out.println("this is setName"); 12 } 13 14 public int getAge() { 15 return age; 16 } 17 18 public void setAge(int age) { 19 this.age = age; 20 System.out.println("this is setAge"); 21 } 22 23 /*** 24 * 包含一个带参的构造方法和不带参的构造方法 25 * @param name 26 * @param age 27 */ 28 public Cat(String name, int age) { 29 this.name = name; 30 this.age = age; 31 } 32 33 public Cat() { 34 } 35 36 //私有方法 37 private void privateMethod() { 38 System.out.println("我是私有方法"); 39 } 40 41 public static void testConstructor() throws Exception { 42 String className = "cn.learning.java.reflect.classtest.test.Cat"; 43 Class<Cat> clazz = (Class<Cat>) Class.forName(className); 44 System.out.println("获取全部Constructor对象-----"); 45 Constructor<Cat>[] constructors = (Constructor<Cat>[]) clazz.getConstructors(); 46 for (Constructor<Cat> constructor : constructors) { 47 System.out.println(constructor); 48 } 49 System.out.println("获取某一个Constructor对象 需要参数列表----"); 50 Constructor<Cat> constructor = clazz.getConstructor(String.class, int.class); 51 System.out.println(constructor); 52 53 System.out.println("调用Constructor的newInstance方法创建对象----"); 54 Cat cat1 = constructor.newInstance("小名", 18); 55 System.out.println(cat1.getName()); 56 } 57 58 public static void main(String[] args) throws Exception { 59 testConstructor(); 60 } 61}

执行结果:

1获取全部Constructor对象----- 2public cn.learning.java.reflect.classtest.test.Cat() 3public cn.learning.java.reflect.classtest.test.Cat(java.lang.String,int) 4获取某一个Constructor对象 需要参数列表---- 5public cn.learning.java.reflect.classtest.test.Cat(java.lang.String,int) 6调用Constructor的newInstance方法创建对象---- 7小名

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

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

1public static void testMethod() throws Exception { 2 String className = "cn.learning.java.reflect.classtest.test.Cat"; 3 Class clazz = Class.forName(className); 4 System.out.println("获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法"); 5 Method[] methods = clazz.getMethods(); 6 for (Method method : methods) { 7 System.out.println(method.getName() + "()"); 8 } 9 System.out.println("====================================="); 10 System.out.println("获取所有方法,包括私有方法、所有声明的方法,且获取当前类方法"); 11 methods = clazz.getDeclaredMethods(); 12 for (Method method : methods) { 13 System.out.println(method.getName() + "()"); 14 } 15 System.out.println("====================================="); 16 System.out.println("获取指定方法,和获取构造器的差不多,需要方法名称 和参数列表 无参则不写"); 17 Method method = clazz.getDeclaredMethod("setName", String.class); 18 System.out.println(method); 19 method = clazz.getDeclaredMethod("setAge", int.class); 20 System.out.println(method); 21 System.out.println("====================================="); 22 System.out.println("执行我们获取的方法"); 23 Object object = clazz.newInstance(); 24 //第一个参数 这个方法所在类的实例,可变参数 参数列表 25 method.invoke(object, 18); 26 System.out.println("====================================="); 27 System.out.println("执行私有方法======"); 28 method = clazz.getDeclaredMethod("privateMethod"); 29 //在执行私有方法之前 一定要 执行这句代码。把Accessible设成true 30 method.setAccessible(true); 31 method.invoke(object); 32 33 }

运行结果:

1获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法 2main() 3getName() 4setName() 5testConstructor() 6testMethod() 7getAge() 8setAge() 9wait() 10wait() 11wait() 12equals() 13toString() 14hashCode() 15getClass() 16notify() 17notifyAll() 18===================================== 19获取所有方法,包括私有方法、所有声明的方法,且获取当前类方法 20main() 21getName() 22setName() 23privateMethod() 24testConstructor() 25testMethod() 26getAge() 27setAge() 28===================================== 29获取指定方法,和获取构造器的差不多,需要方法名称 和参数列表 无参则不写 30public void cn.learning.java.reflect.classtest.test.Cat.setName(java.lang.String) 31public void cn.learning.java.reflect.classtest.test.Cat.setAge(int) 32===================================== 33执行我们获取的方法 34this is setAge 35===================================== 36执行私有方法====== 37我是私有方法

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

通过反射访问成员变量

1public static void testFiled() throws Exception { 2 String className = "cn.learning.java.reflect.classtest.test.Cat"; 3 Class clazz = Class.forName(className); 4 System.out.println("获取共有和私有的所有字段,但不能获取父类字段"); 5 Field[] fields = clazz.getDeclaredFields(); 6 for (Field field : fields) { 7 System.out.println(field.getName()); 8 } 9 System.out.println("====================================="); 10 System.out.println("获取指定字段"); 11 Field field = clazz.getDeclaredField("name"); 12 System.out.println(field.getName()); 13 14 System.out.println("====================================="); 15 System.out.println("获取指定字段的值"); 16 Cat cat = new Cat("铭儿", 18); 17 //第一个参数 这个方法所在类的实例 18 Object object = field.get(cat); 19 System.out.println(field.getName() + "=" + object); 20 System.out.println("====================================="); 21 System.out.println("设置指定对象的值"); 22 field.set(cat, "名儿猫猫"); 23 System.out.println(field.getName() + "=" + cat.getName()); 24 //访问私有字段 25 field = clazz.getDeclaredField("age"); 26 field.setAccessible(true); 27 field.get(cat); 28 field.set(cat, 20); 29 System.out.println(field.getName() + "=" + cat.getAge()); 30 }

执行结果:

1获取共有和私有的所有字段,但不能获取父类字段 2name 3age 4===================================== 5获取指定字段 6name 7===================================== 8获取指定字段的值 9name=铭儿 10===================================== 11设置指定对象的值 12name=名儿猫猫 13age=20

动态代理模式

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

代理模式UML类图

静态代理

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

  • 优点:可以在不修改目标对象的前提下扩展目标对象的功能。
  • 缺点:冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。违反开闭原则:扩展能力差,可维护性差。一旦接口增加方法,目标对象与代理对象都要进行修改。
  • 静态代理属于代理模式的一种代理方式,需要代理对象和目标对象实现相同的接口。
  • 静态代理的代理类是由程序员编写源码,编译后即可获取到代理类的 class 字节码文件,也就是在程序运行前就已经得到实际的代理类 class 字节码文件了。

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

  • 接口类: IUserDao
1public interface IUserDao { 2 public void save(); 3}
  • 目标对象:UserDao
1public class UserDao implements IUserDao{ 2 3 @Override 4 public void save() { 5 System.out.println("保存数据"); 6 } 7}
  • 静态代理对象:UserDapProxy 需要实现IUserDao接口!
1public class UserDaoProxy implements IUserDao{ 2 3 private IUserDao target; 4 public UserDaoProxy(IUserDao target) { 5 this.target = target; 6 } 7 8 @Override 9 public void save() { 10 System.out.println("开启事务");//扩展了额外功能 11 target.save(); 12 System.out.println("提交事务"); 13 } 14}
  • 测试类:TestProxy
1import org.junit.Test; 2 3public class StaticUserProxy { 4 @Test 5 public void testStaticProxy(){ 6 //目标对象 7 IUserDao target = new UserDao(); 8 //代理对象 9 UserDaoProxy proxy = new UserDaoProxy(target); 10 proxy.save(); 11 } 12}
  • 输出结果
1开启事务 2保存数据 3提交事务

动态代理

动态代理也属于代理模式的一种代理方式,不过只需要目标对象实现接口,代理对象不需要实现接口。动态代理的代理类编译后是没有 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 方法方式获取。

代码实现:

1 2interface Animal { 3 void eat(); 4} 5 6class Dog implements Animal { 7 @Override 8 public void eat() { 9 System.out.println("The dog is eating"); 10 } 11} 12// JDK 代理类 13class AnimalProxy implements InvocationHandler { 14 private Object target; // 代理对象 15 16 public Object getInstance(Object target) { 17 this.target = target; 18 // 取得代理对象 19 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); 20 } 21 22 @Override 23 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 24 System.out.println("调用前"); 25 Object result = method.invoke(target, args); // 方法调用 26 System.out.println("调用后"); 27 return result; 28 } 29 public static void main(String[] args) { 30 // JDK 动态代理调用 31 AnimalProxy proxy = new AnimalProxy(); 32 Animal dogProxy = (Animal) proxy.getInstance(new Dog()); 33 dogProxy.eat(); 34 } 35}

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

cglib动态代理

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

1<dependency> 2 <groupId>cglib</groupId> 3 <artifactId>cglib</artifactId> 4 <version>3.2.12</version> 5</dependency>

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

1 2class Panda { 3 public void eat() { 4 System.out.println("The panda is eating"); 5 } 6} 7 8class CglibProxy implements MethodInterceptor { 9 private Object target; // 代理对象 10 11 public Object getInstance(Object target) { 12 this.target = target; 13 Enhancer enhancer = new Enhancer(); 14 // 设置父类为实例类 15 enhancer.setSuperclass(this.target.getClass()); 16 // 回调方法 17 enhancer.setCallback(this); 18 // 创建代理对象 19 return enhancer.create(); 20 } 21 22 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 23 System.out.println("调用前"); 24 Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用 25 System.out.println("调用后"); 26 return result; 27 } 28} 29class Test{ 30 public static void main(String[] args) { 31 // cglib 动态代理调用 32 CglibProxy proxy = new CglibProxy(); 33 Panda panda = (Panda) proxy.getInstance(new Panda()); 34 panda.eat(); 35 } 36}

执行结果为:

1调用前 2The panda is eating 3调用后

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

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

Proxy如何生成的代理类

proxy主要api

Proxy描述
getProxyClass(ClassLoader, Class...) : Class获取实现目标接口的代理类 Class 对象
newProxyInstance(ClassLoader,Class<?>[],InvocationHandler) : Object获取实现目标接口的代理对象
isProxyClass(Class<?>) : boolean判断一个 Class 对象是否属于代理类
getInvocationHandler(Object) : InvocationHandler获取代理对象内部的 InvocationHandler

核心源码 Proxy.java

1//1、获取代理类 Class 对象 2public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces){ 3 final Class<?>[] intfs = interfaces.clone(); 4 ... 5 1.1 获得代理类 Class 对象 6 return getProxyClass0(loader, intfs); 7} 8 9//2、实例化代理类对象 10public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){ 11 ... 12 final Class<?>[] intfs = interfaces.clone(); 13 //2.1 获得代理类 Class对象 14 Class<?> cl = getProxyClass0(loader, intfs); 15 ... 16 //2.2 获得代理类构造器 (接收一个 InvocationHandler 参数) 17 // private static final Class<?>[] constructorParams = { InvocationHandler.class }; 18 final Constructor<?> cons = cl.getConstructor(constructorParams); 19 final InvocationHandler ih = h; 20 ... 21 //2.3 反射创建实例 22 return newInstance(cons, ih); 23}

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

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

Proxy.java

1//-> 1.1、2.1 获得代理类 Class对象 2private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) { 3 ... 4 //从缓存中获取代理类,如果缓存未命中,则通过ProxyClassFactory生成代理类 5 return proxyClassCache.get(loader, interfaces); 6} 7 8private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{ 9 10 //3.1 代理类命名前缀 11 private static final String proxyClassNamePrefix = "$Proxy"; 12 13 //3.2 代理类命名后缀,从 0 递增(原子 Long) 14 private static final AtomicLong nextUniqueNumber = new AtomicLong(); 15 16 @Override 17 public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { 18 Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); 19 //3.3 参数校验 20 for (Class<?> intf : interfaces) { 21 // 验证参数 interfaces 和 ClassLoder 中加载的是同一个类 22 // 验证参数 interfaces 是接口类型 23 // 验证参数 interfaces 中没有重复项 24 // 否则抛出 IllegalArgumentException 25 } 26 // 验证所有non-public接口来自同一个包 27 28 //3.4(一般地)代理类包名 29 String proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; 30 31 //3.5 代理类的全限定名 32 long num = nextUniqueNumber.getAndIncrement(); 33 String proxyName = proxyPkg + proxyClassNamePrefix + num; 34 35 //3.6 生成字节码数据 36 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces); 37 38 //3.7 从字节码生成 Class 对象 39 return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); 40 } 41} 42 43//-> 3.6 生成字节码数据 44public static byte[] generateProxyClass(final String var0, Class[] var1) { 45 ProxyGenerator var2 = new ProxyGenerator(var0, var1); 46 ... 47 final byte[] var3 = var2.generateClassFile(); 48 return var3; 49}

ProxyGenerator.java

1private byte[] generateClassFile() { 2 //3.6.1 只代理Object的hashCode、equals和toString 3 this.addProxyMethod(hashCodeMethod, Object.class); 4 this.addProxyMethod(equalsMethod, Object.class); 5 this.addProxyMethod(toStringMethod, Object.class); 6 7 //3.6.2 代理接口的每个方法 8 ... 9 for(var1 = 0; var1 < this.interfaces.length; ++var1) { 10 ... 11 } 12 13 //3.6.3 添加带有 InvocationHandler 参数的构造器 14 this.methods.add(this.generateConstructor()); 15 var7 = this.proxyMethods.values().iterator(); 16 while(var7.hasNext()) { 17 ... 18 //3.6.4 在每个代理的方法中调用InvocationHandler#invoke() 19 } 20 21 //3.6.5 输出字节流 22 ByteArrayOutputStream var9 = new ByteArrayOutputStream(); 23 DataOutputStream var10 = new DataOutputStream(var9); 24 ... 25 return var9.toByteArray(); 26}

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

输出Animal字节码:

1... 2byte[] classFile = ProxyGenerator.generateProxyClass("$proxy0", new Class[]{Animal.class}); 3// 直接写入项目路径下,方便使用IntelliJ IDEA的反编译功能 4String path = "/Users/*/src/proxy/Animal.class"; 5try(FileOutputStream fos = new FileOutputStream(path)) 6{ 7 fos.write(classFile); 8 fos.flush(); 9 System.out.println("success"); 10} catch(Exception e) 11{ 12 e.printStackTrace(); 13 System.out.println("fail"); 14} 15...

反编译结果:

1public final class $proxy0 extends Proxy implements Animal { 2 //反射的元数据Method存储起来,避免重复创建 3 private static Method m1; 4 private static Method m2; 5 private static Method m3; 6 ... 7 private static Method m0; 8 9 public $proxy0(InvocationHandler var1) throws ... { 10 super(var1); 11 } 12 13 /** 14 * Object#hashCode() 15 * Object#equals(Object) 16 * Object#toString() 17 */ 18 19 // 实现了Animal接口 20 public final String eat() throws ... { 21 try { 22 //转发到Invocation#invoke() 23 return (String)super.h.invoke(this, m3, (Object[])null); 24 } catch (RuntimeException | Error var2) { 25 throw var2; 26 } catch (Throwable var3) { 27 throw new UndeclaredThrowableException(var3); 28 } 29 } 30 31 static { 32 try { 33 //Object#hashCode() 34 //Object#equals(Object) 35 //Object#toString() 36 ... 37 m3 = Class.forName("Animal").getMethod("eat"); 38 } catch (NoSuchMethodException var2) { 39 throw new NoSuchMethodError(var2.getMessage()); 40 } catch (ClassNotFoundException var3) { 41 throw new NoClassDefFoundError(var3.getMessage()); 42 } 43 } 44}

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

  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

作者:京东物流 梁宝彬

来源:京东云开发者社区

点赞
收藏

评论区

加载中...

相关推荐

java中 什么是反射?

JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言(https://www.oschina.net/act

java中的反射和代理

  Java反射机制可以动态地获取类的结构,动态地调用对象的方法,是java语言一个动态化的机制。java动态代理可以在不改变被调用对象源码的前提下,在被调用方法前后增加自己的操作,极大地降低了模块之间的耦合性。这些都是java的基础知识,要想成为一名合格的程序猿,必须掌握!Java反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知

java面试(反射)05

1.什么是反射JAVA反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类信息以及动态调用对象内容就称为java语言的反射机制。2.反射的作用在运行时判断任意一个对象所属的

java反序列化——apache

看了好久的文章才开始分析调试java的cc链,这个链算是java反序列化漏洞里的基础了。分析调试的shiro也是直接使用了cc链。首先先了解一些java的反射机制。一、什么是反射反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性

RPC框架手撕之路---java反射以及动态代理机制

在上一篇文章中,我们提到了,RPC框架所需要的java基础,第一点就是java的动态代理机制,动态代理机制的基础是反射,无论是在实际编程或者是面试时,都是java知识的重中之重。java反射:定义:在运行状态中,对于任意一个类,都能够知道这一个类的所有属性和方法,对于任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态获取类信息以及动态调用类方法

工厂模式实例(顺便回忆反射机制的应用)

一、原理反射机制的原理JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。工厂模式自述所谓工厂模式,是说由某个产品类接口、产品实现类、工厂类、客户端(单元测试主类)构成的一个模式,大程度的降低了代码的