00.反射-基本入门

概念
反射具备在运行时分析类以及执行类中方法的能力,很多框架都使用了反射,通过反射可以获取任意一个类的所有属性和方法,可以调用这些方法和属性。
应用场景
一、Spring、Spring Boot、Mybatis等框架中都使用了反射机制;
Spring为了保持通用性,通过配置文件加载不同的对象,调用不同的方法;
二、 动态代理
在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
同时,在上述的框架中,也大量使用了动态代理,而动态代理依赖反射机制;
以下为JDK实现动态代理的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class DebugInvocationHandler implements InvocationHandler {
private final Object target;
public DebugInvocationHandler(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); System.out.println("after method " + method.getName()); return result; } }
|
三、Java的注解也用到了反射。
为什么你使用 Spring 的时候 ,一个@Component
注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value
注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理.
反射的优缺点
优点:代码灵活,为各种框架提供开箱即用的功能;
缺点:
破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题,除此之外,反射可以忽略Java泛型参数的安全检查(泛型参数检查发生在编译期)。
性能开销:由于反射涉及到动态解析,因此无法执行Java虚拟机优化。
反射的基本使用方法

第一步,获取反射类的Class对象:
1
| Class clazz = Class.forName("全限定包名+类名");
|
第二步,通过Class对象获取构造方法Constructor对象:
1
| Object object = constructor.newInstance();
|
第三步,通过Constructor对象初始化反射对象
1
| Object object = constructor.newInstance();
|
第四步,获取要调用的方法的Method对象
1 2
| Method setNameMethod = clazz.getMethod("setName", String.class); Method getNameMethod = clazz.getMethod("getName");
|
第五步,执行invoke方法:
1 2
| setNameMethod.invoke(object, "沉默王二"); getNameMethod.invoke(object)
|
反射原理
引入前提:熟悉《JVM类加载机制》
使用反射的前提是得到该反射类的Class对象,每一个类,不过它最终生成了多少个对象,这些对象只会对应一个Class对象(注意,同一个Class对象),这个Class对象是JVM生成的,包含了整个类的结构信息。
换句话说,java.lang.Class是所有反射API的入口,而方法的反射调用的是由Method对象的invoke方法完成的,源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args); }
|
上述的两个if语句是用来进行权限检查的。invoke方法实际上委派给MethodAccessor接口执行:

而MethodAccessor接口有三个实现类,其中MethodAccessorImpl是抽象类,另外两个具体的实现类继承这个抽象类:

- NativeMethodAccessorImpl:通过本地方法来实现反射调用;
- DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;
执行到invoke()方法之后,可以看到第一次反射调用会生成一个委派实现DelegatingMethodAccessorImpl,传递一个本地实现NativeMethodAccessorImpl。
也就是说,invoke()
方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法。
为什么不直接调用本地方法NativeMethodAccessorImpl实现反射呢?
回答:
之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 Java 到 C/C++ 再到 Java 之间的切换过程,而动态实现不需要;但如果反射调用的次数比较少,反而本地实现更快一些。
临界点为15次,可以通过-Dsun.reflect.inflationThreshold设置。
反射的常用API
获取反射类的Class对象
1. 知道具体类的情况下:
1
| Class<Person> personClass = Person.class;
|
但是一般情况下,是不知道具体类的,基本通过遍历包下面的类来获取Class对象,通过此方式获取Class对象并不会初始化。(这时候并没有通过反射进行实例化对象,应该获取类的Constructor,然后newInstance初始化对象)。
2. 通过Class.forName()传入类的全路径:
1
| Class<?> aClass = Class.forName("com.example.basejava.reflect.Person");
|
3. 通过对象实例instance.getClass()获取:
1 2
| Person person = new Person(); Class<? extends Person> personClass1 = person.getClass();
|
4. 通过类加载器传入类路径获取
1
| Class<?> personClass2 = ClassLoader.getSystemClassLoader().loadClass("com.example.basejava.reflect.Person");
|
注意:通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行。
创建反射类的对象
两种方式:
- 用Class对象的newInstance()方法。
- 用构造方法Constructor对象的newInsta()方法。
示例:
1 2 3 4 5 6
| Class c1 = Writer.class; Writer writer = (Writer) c1.newInstance();
Class c2 = Class.forName("com.itwanger.s39.Writer"); Constructor constructor = c2.getConstructor(); Object object = constructor.newInstance();
|
获取构造方法
Class对象提供了以下方法来获取构造方法对象:
getConstructor()
:返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
getDeclaredConstructor()
:返回反射类的特定构造方法,不限定于 public 的。
getConstructors()
:返回类的所有 public 构造方法。
getDeclaredConstructors()
:返回类的所有构造方法,不限定于 public 的。
示例:
1 2 3 4 5 6 7
| Class<?> personClass2 = ClassLoader.getSystemClassLoader().loadClass("com.example.basejava.reflect.Person"); Constructor constructor = personClass2.getConstructor();
Constructor[] constructors1 = String.class.getDeclaredConstructors(); for (Constructor c : constructors1) { System.out.println(c); }
|
获取成员变量
获取Field对象即可:
1 2
| Method setNameMethod = clazz.getMethod("setName", String.class); Method getNameMethod = clazz.getMethod("getName");
|
获取成员方法
获取Method方法即可:
1 2
| Method[] methods1 = System.class.getDeclaredMethods(); Method[] methods2 = System.class.getMethods();
|
反射Demo
创建一个基本的Person类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package com.example.basejava.reflect;
import lombok.Data;
import java.util.Date;
public class Person { private String id; private String name; private Integer age; private String gender; private Date birthDate;
private void privateMethod(){ System.out.println("这是private方法"); }
public Person(String id, String name, Integer age, String gender, Date birthDate) { this.id = id; this.name = name; this.age = age; this.gender = gender; this.birthDate = birthDate; }
public Person() { }
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { System.out.println("执行getName方法"); return name; }
public void setName(String name) { System.out.println("执行set的方法,name属性值为:" + name); this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public Date getBirthDate() { return birthDate; }
public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } }
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.example.basejava.reflect;
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class Main { public static void main(String[] args) {
Person person = new Person(); try {
Class<?> personClass2 = ClassLoader.getSystemClassLoader().loadClass("com.example.basejava.reflect.Person"); Method getName = personClass2.getMethod("getName"); getName.invoke(personClass2.newInstance()); Method setName = personClass2.getMethod("setName", String.class); setName.invoke(personClass2.newInstance(), "chenym"); setName.invoke(person, "通过new实例后的setName:chenym"); Method privateMethod = personClass2.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(personClass2.newInstance()); } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | ClassNotFoundException | InstantiationException e) { throw new RuntimeException(e); } } }
|
注:getDeclaredMethod:获取当前类的所有声明的方法,包括public、protected和private修饰的方法。需要注意的是,这些方法一定是在当前类中声明的,从父类中继承的不算,实现接口的方法由于有声明所以包括在内。
getMethod:获取当前类和父类的所有public的方法。这里的父类,指的是继承层次中的所有父类。比如说,A继承B,B继承C,那么B和C都属于A的父类。
执行结果:
1 2 3 4
| 执行getName方法 执行set的方法,name属性值为:chenym 执行set的方法,name属性值为:通过new实例后的setName:chenym 这是private方法
|
参考
Java 反射机制详解
getDeclaredMethod、getMethod
Java 反射详解:动态创建实例、调用方法和访问字段
强烈推荐!!!深入理解Java反射和动态代理