因为最近项目中经常有java反射的使用,而其中的IOC、动态代理用到了反射,因此趁这个机会来总结一下关于Java反射的一些知识,复习一下。本篇基于JDK 1.8。
Java反射机制(Java Reflection) 是 Java 的特征之一,是Java语言中一种动态(运行时)访问、检测和修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息、调用对象的方法。简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:
重点: 是运行时而不是编译时
多数情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过 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实例对象,常见的三种方式分别为:
这几种方式,最终在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类图
静态代理:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,目的是通过引入代理对象方式,来间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性,可以对对象的原有的业务进行增强。
举例:保存用户功能的静态代理实现
public interface IUserDao { public void save();}
public class UserDao implements IUserDao{ @Override public void save() { System.out.println("保存数据"); }}
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("提交事务"); }}
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 API 的 java.lang.reflect.Proxy 和
java.lang.relfect.InnvocationHandler 这两个类来实现。通过 java.lang.reflect.Proxy 代理类的 newProxyInstance方法,传递3个参数,分别是:
代码实现:
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 (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主要api
Proxy | 描述 |
getProxyClass(ClassLoader, Class...) : Class | 获取实现目标接口的代理类 Class 对象 |
newProxyInstance(ClassLoader,Class<?>[],InvocationHandler) : Object | 获取实现目标接口的代理对象 |
isProxyClass(Class<?>) : boolean | 判断一个 Class 对象是否属于代理类 |
getInvocationHandler(Object) : InvocationHandler | 获取代理对象内部的 InvocationHandler |
核心源码 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.proxy$Proxy[从0开始的数字](例如:com.sun.proxy$Proxy0),这个类继承自 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()); } }}
查看反编译后的代码,总结一下
作者:京东物流 梁宝彬
来源:京东云开发者社区