详解反射机制和常见应用场景

462 阅读5分钟

1.什么是反射?

动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。 通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

2.反射的应用场景:

正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。

Java 中的一大利器 注解 的实现也用到了反射。 为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。

3.反射机制的优缺点:

优点:可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利

缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点。

4.反射能做什么?

我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!

5.反射的原理:

image.png

1.反射机制相关的类:

image.png 必须先获得Class才能获取Method、Constructor、Field。 要想通过反射获取一个类的信息,首先要获取该类的class对象,class对象代表了正在运行中的Java应用的类和接口。

2.怎么获取该类对应的Class对象:

获取一个类的Class对象有三种方法,对应的通过Class对象创建类实例也有三种方法:

//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
//  类型的对象,而我不知道你具体是什么类,用这种方法
  Person p1 = new Person();
  Class c1 = p1.getClass();

//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
//  这说明任何一个类都有一个隐含的静态成员变量 class
  Class c2 = Person.class;

//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
//   但可能抛出 ClassNotFoundException 异常
  Class c3 = Class.forName("com.ys.reflex.Person");
  
  // newInstance() 这个方法会调用Person这个类的无参数构造方法,完成对象的创建。
  // 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的! 
  Object obj = c.newInstance();

2.通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等:

    getName():获得类的完整名字。  
  getFields():获得类的public类型的属性。  
  getDeclaredFields():获得类的所有属性。包括private 声明的和继承类  
  getMethods():获得类的public类型的方法。  
  getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类  
  getDeclaredMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。返回Method方法对象。
  getConstructors():获得类的public类型的构造方法。  
  getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。  
  newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

下面是示例代码:

//获得类完整的名字:getName()
String className = c2.getName();
System.out.println(className);//输出com.ys.reflex.Person

//获得类的public类型的属性:getFields()
Field[] fields = c2.getFields();
for(Field field : fields){
   System.out.println(field.getName());//age
}

//获得类的所有属性,包括私有的:getDeclaredFields()
Field [] allFields = c2.getDeclaredFields();
for(Field field : allFields){
    System.out.println(field.getName());//name    age
}

//获得类的public类型的方法。这里包括 Object 类的一些方法:getMethods()
Method [] methods = c2.getMethods();
for(Method method : methods){
    System.out.println(method.getName());//work waid equls toString hashCode等
}

//获得类的所有方法:getDeclaredMethods()
Method [] allMethods = c2.getDeclaredMethods();
for(Method method : allMethods){
    System.out.println(method.getName());//work say
}

//获得类的某个方法:
//getDeclaredMethod(name,parameterTypes),name参数指定方法的名字,parameterTypes 参数指定方法的参数类型
Methood method = c2.getDeclaredMethod("show",String.class)
System.out.println(method.getName());//show
 
//获得指定的属性:getField(属性名)
Field f1 = c2.getField("age");
System.out.println(f1);
//获得指定的私有属性
Field f2 = c2.getDeclaredField("name");

//启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
f2.setAccessible(true);
System.out.println(f2);

//创建这个类的一个对象
Object p2 =  c2.newInstance();
//将 p2 对象的  f2 属性赋值为 Bob,f2 属性即为 私有属性 name
f2.set(p2,"Bob");
//使用反射机制可以打破封装性,导致了java对象的属性不安全。
System.out.println(f2.get(p2)); //Bob

//获取构造方法
Constructor [] constructors = c2.getConstructors();
for(Constructor constructor : constructors){
    System.out.println(constructor.toString());//public com.ys.reflex.Person()
}

3.反射调用方法:invoke(Object obj, Object... args):

对于方法(Method)反射,我们主要关心如何通过反射调用目标方法,Method类提供了invoke(Object obj, Object... args)方法,允许我们通过反射调用方法。

Method.invoke(Object obj, Object... args)方法第一个参数是一个类实例,第二个参数是目标方法的参数列表,如果第一个参数为null,则表示调用类静态方法。

public static void main(String[] args) {
    try {
        TestClass t = new TestClass();
        //通过TestClass类的class属性获取其Class对象,再通过getDeclaredMethod方法获取名为"getInt"、参数类型为String的方法对象m
        Method m = TestClass.class.getDeclaredMethod("getInt", String.class);
         //使用invoke方法调用TestClass对象t的"getInt"方法,传入参数"123"。invoke方法会返回调用结果           
        System.out.println(m.invoke(t, "123"));                    
    } catch (Exception e) {
        e.printStackTrace();
    }
}