
点击上方蓝字关注我们

大家好,今天我们来聊一聊反射~

Java编程中,反射可谓如雷贯耳,Java内部运行机制以及许多优秀开源框架都离不开反射。反射是什么?反射的底层原理是怎样的?反射有哪些优缺点?反射在程序中如何应用的?
带着这几个问题,让我们来开启一段反射之旅吧~



反射概念

接触反射前,大家首先来思考一个问题 🤔
当我们已知某个类的路径,并想要以此去创建对象时,你会去怎样编写代码?
例如Spring支持在xml文件里通过配置类路径来创建bean
也许初学Java的你会这样写👇,直接通过入参来枚举对应的实例:
public class Person {
public void getJobInfo(String className){
if("student".equals(className)){
Student s = new Student();
s.getJob();
}
if("teacher".equals(className)){
Teacher t = new Teacher();
t.getJob();
}
}
}
class Student{
public void getJob(){
System.out.println("I am a student!");
}
}
class Teacher{
public void getJob(){
System.out.println("I am a teacher!");
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person();
p1.getJobInfo("student");
Person p2 = new Person();
p2.getJobInfo("teacher");
}
}

这样的写法简单易懂,只不过每当我们想要新增一个像 Student.java 这样的 XXX.java 类时,都需要修改 Person.java 代码,代码耦合度较高;
且当类似 Teacher.java 这样的已有类被删除时,如果没有更改 Person.java 相关逻辑,程序会编译失败,维护成本较高;

我们看下面的一种写法👇
public interface PersonInterface {
void getJob();
}
class Student implements PersonInterface {
@Override
public void getJob() {
System.out.println("I am a student!");
}
}
class Teacher implements PersonInterface{
@Override
public void getJob(){
System.out.println("I am a teacher!");
}
}
public class Main {
public static void main(String[] args) {
try{
Class c1 = Class.forName("com.company.Student");
PersonInterface person1 = (PersonInterface) c1.newInstance();
person1.getJob();
Class c2 = Class.forName("com.company.Teacher");
PersonInterface person2 = (PersonInterface) c2.newInstance();
person2.getJob();
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序运行结果:

在这段代码中,我们首先使用Java的多态降低了代码耦合度;

另外在通过类路径获取 Class 信息并创建对象过程中,无需关注像 Student.java 这样具体的子类是否存在,即使子类被删除,也不会产生编译错误;
但是删除后代码会在运行时抛出ClassNotFoundException异常
这里我们实现了需要哪个类的对象就在程序运行时动态创建,体现了Java语言动态特性中反射的灵活性。

Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。
这种动态获取程序信息以及动态调用对象的功能就是Java语言的反射机制,所以反射被视为动态语言的关键。


反射原理

Java程序运行前,首先会通过编译将Java代码生成 .class文件;
程序运行过程中使用某个类时,如果该类还未被加载到内存中,系统会将类的.class字节码文件读入内存,同时JVM会产生一个 java.lang.Class 对象代表该.class字节码文件。

通过Class对象可以得到大量的Method、Constructor、Field等对象,这些对象分别代表该类所包括的方法、构造器和属性等,反射的工作原理就是通过这些对象来执行实际的功能,例如调用方法、创建实例等。
(画一张图假装自己讲明白了
)



反射常用方法
示例:
//使用 Class.forName() 静态方法
Class clz = Class.forName("java.lang.String");
//需要知道类的全路径名
//使用 .class 方法
Class clz = String.class;
//需要在编译前就知道Class信息
//使用类对象的 getClass()方法
Person p = new Person();
Class clz = p.getClass();
1、通过 Class 对象的 newInstance() 方法
Class clz = Person.class;
Person p = (Person)clz.newInstance();
2、通过 Constructor 对象的 newInstance() 方法
Class clz = Person.class;
Constructor constructor = clz.getConstructor();
Person p = (Person)constructor.newInstance();
通过 Constructor 对象创建类对象可以选择特定的构造方法,而通过 Class 对象则只能使用默认的无参构造方法。
Class clz = Person.class;
//获取自身和父类的公有属性(不包括私有的)
Field[] fields = clz.getFields();
//获取自身的公有和私有属性
Field[] declaredFields = clz.getDeclaredFields();
//获取自身和父类的公有方法(不包括私有的)
Method[] methods = clz.getMethods();
//获取自身的公有和私有方法
Method[] declaredMethods = clz.getDeclaredMethods();
//获取自身和父类的构造方法(不包括私有的)
Constructor[] constructors = clz.getConstructors();
//获取自身的公有和私有的构造方法
Constructor[] declaredConstructors = clz.getDeclaredConstructors();
//定义Engineer类
class Engineer{
private String name = "小明";
public void getJob(String name){
System.out.println(name + " is an engineer!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
try{
Class clz = Engineer.class;
Object obj = clz.newInstance();
Field field = clz.getDeclaredField("name");
//私有属性或方法,必须设置accessible=true,否则抛出IllegalAccessException异常
field.setAccessible(true);
System.out.println("Field: name-" + field.get(obj));
Method method = clz.getDeclaredMethod("getJob", String.class);
method.setAccessible(true);
method.invoke(obj, "小张");
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果




反射优势与劣势



优势
反射增加了程序的灵活性。
例如Spring框架中利用xml配置解析bean的过程就是反射的典型应用场景。
如果我们想修改一个bean的属性,但是代码工程中有几十个地方都引用了这个bean,通过反射我们只需要在xml中修改bean的定义就可以使所有对象生效,而无需去手动一个个修改对象属性;
反射可以让程序有很好的拓展性。
反射可以在不知道会运行哪一个类的情况下,获取到类的信息,创建对象以及操作对象;我们无需将类型硬编码写死,方便程序拓展,降低耦合度。

劣势
反射更容易出现运行时错误。
使用显式的类和接口,编译器能帮我们做类型检查,但使用反射需要运行时才知道类型,此时编译器也爱莫能助。
反射性能不高。
反射是一种解释操作,在访问字段和调用方法前,需要查找到对应的 Field 和 Method,性能要低一些。
因此反射机制主要应用在对灵活性和拓展性要求较高的系统框架上。
反射会带来安全性问题。
反射可以随意修改私有属性和访问私有方法,破坏了类的封装,可能导致逻辑错误或者存在安全隐患。



反射的应用场景


Spring实例化
Spring 在实例化过程中,如果没有需要动态改变的方法,直接使用反射实例化就好了。
如果有需要覆盖或者动态替换的方法,需要使用cglib 进行动态代理,因为可以在创建代理的同时将动态方法织入类中。
Spring 源码
SimpleInstantiationStrategy.java
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
if (!bd.hasMethodOverrides()) {
//如果有需要覆盖或者动态替换的方法则当然需要使用cglib进行动态代理,因为可以在创建代理的同时将动态方法织入类中,
//但是如果没有需要动态改变的方法,为了方便直接反射就可以了
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
}
else {
//通过反射获取构造器
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
//通过反射进行实例化
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// Must generate CGLIB subclass.
return instantiateWithMethodInjection(bd, beanName, owner);
}
}


动态代理
动态代理中也随处可见反射的身影。在使用 Proxy.newProxyInstance 静态方法来创建代理时,首先获取代理类Class对象,再通过构造函数生成代理对象。
Proxy 源码
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
...
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
cons.setAccessible(true);
// END Android-removed: Excluded AccessController.doPrivileged call.
}
return cons.newInstance(new Object[]{h});
...
}
今天「反射」就讲到这里了,你学废了吗 



点个在看你最好看




