# Java教程 - 4 反射

Java中的反射(Reflection)是 Java 语言的一个强大特性,它允许程序在运行的时候,检查类、接口、字段和方法的信息,并可以 动态 地创建和操作对象。

在 Java 虚拟机 JVM 加载类之后,会在 JVM 的方法区生成一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象中包含了类的完整信息,包括属性和方法等。以前创建对象是通过类结构创建对象,现在是通过 Class 对象获取类的结构,所以叫反射。

通过反射可以实现的主要功能:

  1. 动态地创建和加载类:通过反射,可以在运行时加载并初始化类,而无需事先知道类名。
  2. 获取类的详细信息:可以获取类的名称、修饰符、超类、实现的接口、注解等。
  3. 获取类的成员信息:可以获取类的字段、方法、构造器等成员的信息。
  4. 动态调用方法:可以动态地调用对象的方法,即使这些方法在编译时是未知的。

# 4.1 反射初体验

首先先看一下以前,或者说是正常情况下如何创建对象,访问类中的属性以及方法。

首先创建一个 Java 类,在类中有公有和私有的构造方法、属性、方法。

class Student {
    private String name;
    public int age;

    private Student(String name) {
        this.name = name;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void study() {
        System.out.println("我爱学习");
    }

    public void sayHello(String target) {
        System.out.println("Hello " + target);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

然后创建一个测试类,测试一下:

public class ReflectTest {
    public static void main(String[] args) {
        Student stu = new Student("doubi", 12);
        System.out.println(stu.age);
        stu.sayHello("zhangsan");

        // Student stu2 = new Student("doubi");  // 无法调用私有构造方法
        // System.out.println(stu2.name);  // 无法访问私有属性
        // stu2.study();  // 无法调用私有方法
    }
}
1
2
3
4
5
6
7
8
9
10
11

正常情况下,我们只能调用非私有的构造方法、属性和方法。这就是封装的作用。


下面来使用反射来实现上面的功能:

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 首先获取类的 class 对象,主要通过 class 对象来完成,这是反射的关键
        Class clazz = Student.class;

        // 1.使用反射创建Student对象
        // 获取Student类的(String, int)两个参数的构造方法
        Constructor cons = clazz.getConstructor(String.class, int.class);
        // 使用构造方法创建对象
        Object obj = cons.newInstance("doubi", 12);
        // 将对象强转为Student类型
        Student stu = (Student) obj;
        System.out.println(stu.toString());

        // 2.通过反射,访问对象指定的属性
        // 通过属性名称获取属性
        Field age = clazz.getField("age");
        // 设置属性值,需要传递对象
        age.set(stu, 12);
        System.out.println(stu.toString());

        // 3.通过反射,调用对象的方法
        // 通过方法名和参数类型获取方法,因为方法可能有重载
        Method sayHelloMethod = clazz.getMethod("sayHello", String.class);
        // 调用方法,需要传递对象和方法的参数
        sayHelloMethod.invoke(stu, "zhangsan");
    }
}
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

上面使用反射创建了 Student 类的对象,并访问属性,为属性赋值,并调用了类中的方法。

基本的使用步骤是:需要先获取类中的元素,然后才能调用类中的元素。


反射除了能访问公有的构造器、属性、方法,还可以访问私有的元素。


public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 首先获取类的 class 对象,主要通过 class 对象来完成,这是反射的关键
        Class clazz = Student.class;

        // 1.使用反射创建Student对象
        // 获取Student类的(String)一个参数的构造方法
        Constructor cons = clazz.getDeclaredConstructor(String.class);
        // 需要设置为允许访问
        cons.setAccessible(true);
        // 使用构造方法创建对象
        Object obj = cons.newInstance("doubi");
        // 将对象强转为Student类型
        Student stu = (Student) obj;
        System.out.println(stu.toString());

        // 2.通过反射,访问对象指定的属性
        // 通过属性名称获取属性
        Field name = clazz.getDeclaredField("name");
        // 需要设置为允许访问
        name.setAccessible(true);
        // 设置属性值,需要传递对象
        name.set(stu, "niubi");
        System.out.println(stu.toString());

        // 3.通过反射,调用对象的方法
        // 方法没有参数,所以不用传递参数类型
        Method studyMethod = clazz.getDeclaredMethod("study");
        studyMethod.setAccessible(true);
        // 调用方法,没有参数,不用传递参数
        studyMethod.invoke(stu);
    }
}
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

和访问类中公有的元素不同,获取私有的构造器、属性、方法的,都是 getDeclaredXxxx() ,通过 getDeclaredXxxx() 也可以获取到公有元素,另外访问私有元素需要先调用 setAccessible(true) 设置私有元素为可以访问。


普通创建对象的方式和反射的方式,在开发中应该用哪个呢?

正常情况下,我们都是使用 new 来创建对象的,除非需要动态创建对象,在代码运行前无法知道要创建哪个对象,才会使用反射。

例如服务器接收到用户的请求,发现是登录请求,就使用反射创建登录的类,并调用其中的方法,如果是注册请求,就使用反射创建注册的类,并调用其中的方法。还有我们在注解的时候演示了读取项目中的配置文件,使用反射给属性进行初始化。

# 4.2 关于Class类和Class实例

Java 源代码在使用 javac 编译后,每个源文件会生成一个或多个 .class 文件,然后运行 java 命令对 .class 文件进行解释执行,此过程 .class 文件会被加载入内存,加载入内存中的类我们称之为 运行时类 ,运行时类也是一个对象,所以在 Java 中万事万物皆对象,类也是对象,每个 运行时类 是 Class类 的一个实例,反过来,一个 Class类 的实例就是一个 运行时类


我们上面在使用反射的时候,主要就是使用 Class 的实例来完成的,获取 Class 的实例主要有以下4种方式。

package com.doubibiji.reflect;

class Student {
}

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 方式1:通过运行时类的属性.class
        Class<Student> clazz1 = Student.class;
        System.out.println(clazz1);  // class com.doubibiji.reflect.Student

        // 方式2:通过运行时类的对象,调用getClass()
        Student stu = new Student();
        Class clazz2 = stu.getClass();
        System.out.println(clazz2);

        // 方式3:调用Class的静态方法forName(String classPath)
        // 需要指定类的全类名,包括包名
        Class clazz3 = Class.forName("com.doubibiji.reflect.Student");
        System.out.println(clazz3);

        // 方式4:使用类的加载器:ClassLoader (了解)
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.doubibiji.reflect.Student");
        System.out.println(clazz4);

      	// 它们是同一个对象
        System.out.println(clazz1 == clazz2);  // true
        System.out.println(clazz1 == clazz3);  // true
        System.out.println(clazz1 == clazz4);  // true
    }
}
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

最常用的是方式3,因为前两种都知道了是哪个类和对象了,那就可以直接操作方法属性就可以了,就不用反射了。反射主要是发挥代码在运行时的动态性。方式4中,类加载器作用是用来把类加载载进内存的。

其实不光是普通的类,下列的类型都是 Class 类的实例:

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类;

  • interface:接口;

  • []:数组;

  • enum:枚举;

  • annotation:注解@interface

  • primitivetype:基本数据类型;

  • void

下面的例子可以说明:

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 获取运行时类
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class;
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = int.class;
        Class c8 = void.class;
        Class c9 = Class.class;

        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        // 只要数组的元素类型与维度一样,就是同一个Class
        System.out.println(c10 == c11);  // true
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.3 获取运行时类的结构

通过运行时类的对象,可以获取到类的完整结构,包括属性(名称、类型、修饰符、注解)、方法(名称、参数、返回值类型、修饰符、异常、注解)、父类(名称、泛型等)等等,类中的信息都可以获取,但是我们在平时可能用不用获取这么详细,不过需要的时候在过来查就可以。

先准备一个注解,待会添加到类上:

package com.doubibiji.reflect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}
1
2
3
4
5
6
7
8
9
10
11
12

注解的保留策略需要指定 RetentionPolicy.RUNTIME ,这样在运行时才能获取到,另外可以指定在哪些元素上可以使用这个注解。

准备一个接口:

package com.doubibiji.reflect;

public interface MyInterface {
    void showInfo();
}
1
2
3
4
5

准备一个父类:

package com.doubibiji.reflect;

import java.io.Serializable;

public abstract class Person <T> implements Serializable {
    private String gender;
    protected float height;
    public double weight;

    private void breath() {
        System.out.println("人要呼吸");
    }

    public void eat() {
        System.out.println("人要吃饭");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

准备一个子类:

待会通过注解,主要获取该类的结构。

package com.doubibiji.reflect;

@MyAnnotation("www")
class Student extends Person<String> implements MyInterface, Comparable<Student> {

    private int id;
    protected String name;
    public int age;

    public Student() {
    }

    @MyAnnotation("doubibiji")
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private Student(String name) {
        this.name = name;
    }

    @MyAnnotation("study")
    private String study(String course) {
        System.out.println("我在学习:" + course);
        return course;
    }

    @MyAnnotation("study")
    public String sayHello(String target) {
        System.out.println("你好, " + target);
        return target;
    }

    @Override
    public void showInfo() {

    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return 0;
    }
}
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

类已经准备好了,下面通过反射来获取上面 Student 类中的结构。

# 1 获取属性结构

获取运行类中的属性,代码如下:

import java.lang.reflect.Field;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 获取Student类
        Class clazz = Student.class;

        // 获取属性结构
        // getFields()获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for(Field field : fields){
            System.out.println(field);
        }

        System.out.println("---------");

        // getDeclaredFields()获取当前运行时类中声明的所有属性,不包含父类中声明的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • getFields() :获取当前运行时类及其 父类 中声明为 public 访问权限的属性;
  • getDeclaredFields() :获取当前运行时类中声明的所有属性,不包含父类中声明的属性。

执行结果:

public int com.doubibiji.reflect.Student.age
public double com.doubibiji.reflect.Person.weight
---------
private int com.doubibiji.reflect.Student.id
protected java.lang.String com.doubibiji.reflect.Student.name
public int com.doubibiji.reflect.Student.age
1
2
3
4
5
6

还可以获取属性的修饰符,类型等信息:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Student.class;

        // 获取属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field field : declaredFields){
            // 获取权限修饰符
            int modifier = field.getModifiers();
            System.out.print(Modifier.toString(modifier) + "\t");
          
            // 获取数据类型
            Class type = field.getType();
            System.out.print(type.getName() + "\t");
          
            // 获取变量名
            String fName = field.getName();
            System.out.println(fName);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

执行结果:

private	int	id
protected	java.lang.String	name
public	int	age
1
2
3

# 2 获取方法结构

获取运行类中的方法,举个栗子:

package com.doubibiji.reflect;

import java.lang.reflect.Method;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Student.class;

        // getMethods():获取当前运行时类及其所有父类中声明为 public 权限的方法
        Method[] methods = clazz.getMethods();
        for(Method method : methods){
            System.out.println(method);
        }
      
        System.out.println("----------");
      
        // getDeclaredMethods():获取当前运行时类中声明的所有方法,不包含父类中声明的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method method : declaredMethods){
            System.out.println(method);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • getMethods() :获取当前运行时类及其所有父类中声明为 public 权限的方法;
  • getDeclaredMethods() :获取当前运行时类中声明的所有方法,不包含父类中声明的方法。

还可以获取方法的修饰符、返回值类型、方法名等信息:

package com.doubibiji.reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 获取Student类
        Class clazz = Student.class;

        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            // 1.获取方法声明的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }

            // 2.获取权限修饰符
            System.out.print(Modifier.toString(method.getModifiers()) + "\t");

            // 3.获取返回值类型
            System.out.print(method.getReturnType().getName() + "\t");

            // 4.获取方法名
            System.out.print(method.getName());
            System.out.print("(");
            // 5.获取形参列表
            Class[] parameterTypes = method.getParameterTypes();
            if(!(parameterTypes == null && parameterTypes.length == 0)){
                for(int i = 0;i < parameterTypes.length;i++){
                    if(i == parameterTypes.length - 1){
                        System.out.print(parameterTypes[i].getName() + " args_" + i);
                        break;
                    }
                    System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
                }
            }
            System.out.print(")");

            // 6.获取抛出的异常
            Class[] exceptionTypes = method.getExceptionTypes();
            if(exceptionTypes.length > 0){
                System.out.print("throws ");
                for(int i = 0;i < exceptionTypes.length;i++){
                    if(i == exceptionTypes.length - 1){
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }
                    System.out.print(exceptionTypes[i].getName() + ",");
                }
            }

            System.out.println("");
            System.out.println("----------");
        }
    }
}
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

# 3 获取构造方法

获取运行时类的构造方法:

import java.lang.reflect.Constructor;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 获取Student类
        Class clazz = Student.class;

        // getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor constructor : constructors){
            System.out.println(constructor);
        }
      
        System.out.println("---------");
      
        // getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor constructor : declaredConstructors){
            System.out.println(constructor);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • getConstructors():获取当前运行时类中声明为public的构造器;
  • getDeclaredConstructors() :获取当前运行时类中声明的所有的构造器。

执行结果:

public com.doubibiji.reflect.Student(java.lang.String,int)
public com.doubibiji.reflect.Student()
---------
private com.doubibiji.reflect.Student(java.lang.String)
public com.doubibiji.reflect.Student(java.lang.String,int)
public com.doubibiji.reflect.Student()
1
2
3
4
5
6

# 4 获取父类及父类的泛型

获取父类及父类的泛型:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Student.class;

        // 获取父类
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);

        // 获取带泛型的父类
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);

        // 将带泛型的父类转换为ParameterizedType,用于获取泛型
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        // 获取泛型类型,是个数组,因为可能有多个泛型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        // 获取第一个泛型类型
        System.out.println(actualTypeArguments[0].getTypeName());

        // 还可以将type转换为class,通过class来获取
        Class typeClass = (Class) actualTypeArguments[0];
        System.out.println(typeClass.getName());
    }
}
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

执行结果:

class com.doubibiji.reflect.Person
com.doubibiji.reflect.Person<java.lang.String>
java.lang.String
java.lang.String
1
2
3
4

# 5 获取注解

获取运行时类的注解:

package com.doubibiji.reflect;

import java.lang.annotation.Annotation;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Student.class;

        // 获取类所有的注解
        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annotation : annotations){
            System.out.println(annotation);
        }

        System.out.println("---------");

        // 获取类所有的注解
        Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
        for(Annotation annotation : declaredAnnotations){
            System.out.println(annotation);
        }


        // 判断类是否有MyAnnotation注解
        boolean hasMyAnnotation = clazz.isAnnotationPresent(MyAnnotation.class);
        if (hasMyAnnotation) {
            // 获取制定的注解
            MyAnnotation myAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
            System.out.println(myAnnotation);
        }
    }
}
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
  • getAnnotations():此方法获取当前类声明的注解以及从父类或接口继承的注解。
  • getDeclaredAnnotations():此方法仅获取当前类声明的注解,不包括从父类或接口继承的注解。

# 6 获取接口

获取运行时类的接口:

package com.doubibiji.reflect;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Student.class;

        // 获取接口
        Class[] interfaces = clazz.getInterfaces();
        for(Class interfa : interfaces){
            System.out.println(interfa);
        }
        System.out.println("----------");

        // 获取父类实现的接口,其实是先获取了父类
        Class[] superInterfaces = clazz.getSuperclass().getInterfaces();
        for(Class interfa : superInterfaces){
            System.out.println(interfa);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

执行结果:

interface com.doubibiji.reflect.MyInterface
interface java.lang.Comparable
----------
interface java.io.Serializable
1
2
3
4

# 7 获取包

获取运行时类的包:

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Student.class;

      	// 获取类所在的包
        Package pack = clazz.getPackage();
        System.out.println(pack);
    }
}
1
2
3
4
5
6
7
8
9

执行结果:

package com.doubibiji.reflect
1

# 4.4 调用运行时类的结构

# 1 调用构造方法创建对象

调用构造方法就是用来创建对象。

首先获取到构造方法,然后可以调用构造方法来创建类的对象:

package com.doubibiji.reflect;

import java.lang.reflect.Constructor;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 获取运行时类的对象
        Class<Student> clazz = Student.class;

        // 1.获取无参构造方法,并调用无参构造方法创建对象
        Student stu1 = clazz.getConstructor().newInstance();

        // 2.通过参数类型获取到公有构造函数,然后传递参数创建对象
        Student stu2 = clazz.getConstructor(String.class, int.class).newInstance("doubi", 12);

        // 3.通过参数类型获取到私有构造函数,然后传递参数创建对象
        Constructor<Student> constructor = clazz.getDeclaredConstructor(String.class);
        // 设置构造方法允许访问
        constructor.setAccessible(true);
        Student stu3 = constructor.newInstance("niubi");

        System.out.println(stu1);  // Student{name='null', age=0}
        System.out.println(stu2);  // Student{name='doubi', age=12}
        System.out.println(stu3);  // Student{name='niubi', age=0}
    }
}
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

之前有可以直接通过 clazz.newInstance(); 来调用创建对象,现在这个方法在 JDK9 过期了,不建议使用了。

一般在创建 Java 类的时候,如果没有特殊的要求,即使有其他的构造函数,建议还是保留一个无参的构造函数,这样方便通过反射来创建对象,另外如果有子类继承该类的时候,通过 super() 来调用这个类的构造方法也比较方便。


只要获取到 运行时类 就可以通过运行时类来创建了,之前有多种获取运行时类的方式,都可以使用。

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 通过类的全名创建对象
        Student stu = (Student) getInstance("com.doubibiji.reflect.Student");

        System.out.println(stu);  // Student{name='null', age=0}
    }

    /**
     * 通过类全名创建对象
     */
    public static Object getInstance(String classPath) {
        try {
            Class clazz = Class.forName(classPath);
            return clazz.getConstructor().newInstance();
        } catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2 调用属性

首先获取到属性,然后通过属性的 get 和 set 方法可以访问和设置属性值,设置的时候需要指定对哪个属性进行设置。

举个栗子:

import java.lang.reflect.Field;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class<Student> clazz = Student.class;

        // 首先创建一个对象
        Student stu = clazz.getConstructor().newInstance();

        // 获取指定的属性,getField只能获取public的属性
        // Field idField = clazz.getField("id");
        // 可以访问私有属性,一般使用这个方法
        Field idField = clazz.getDeclaredField("id");
        // 设置属性允许访问
        idField.setAccessible(true);

        // 设置属性的值:set()参数1指明设置哪个对象的属性;参数2将此属性值设置为多少
        idField.set(stu, 1000);

        // 获取属性的值,get()参数1:获取哪个对象的当前属性值
        int stuId = (int) idField.get(stu);
        System.out.println(stuId);  // 1000
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3 调用方法

调用方法,首先获取到运行时类的方法,然后通过 invoke() 方法来调用。

举个栗子:

package com.doubibiji.reflect;

import java.lang.reflect.Method;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class<Student> clazz = Student.class;

        // 首先创建一个对象
        Student stu = clazz.getConstructor().newInstance();

        // 1.获取指定的某个方法,参数1:指定获取的方法的名称,参数2:指定获取的方法的形参类型
        Method studyMethod = clazz.getDeclaredMethod("study", String.class);

        // 2.设置方法是可访问的
        studyMethod.setAccessible(true);

        // 3.通过invoke()来调用方法:参数1,指定方法的调用者,参数2:传递参数,如果有多个,可以传递多个
        // invoke()的返回值即为调用方法的返回值,如果没有返回值,返回为null
        Object returnValue = studyMethod.invoke(stu,"Chinese");
        System.out.println(returnValue);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

上面调用的是非静态方法,静态方法如何调用呢?

首先将上面调用的方法改为 static 静态的,然后使用如下方式进行调用:

package com.doubibiji.reflect;

import java.lang.reflect.Method;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class<Student> clazz = Student.class;

        // 1.首先获取到方法
        Method studyStaticMethod = clazz.getDeclaredMethod("study", String.class);
        // 2.设置允许访问
        studyStaticMethod.setAccessible(true);

        // 调用的时候,指定为当前运行时类的对象
        Object returnValue = studyStaticMethod.invoke(clazz, "Chinese");
      
        // 不过也可以不指定,传递null,因为方法本身就是当前类对象的,肯定知道是哪个运行时类
        // Object returnValue = studyStaticMethod.invoke(null, "Chinese");
        System.out.println(returnValue);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

调用静态方法,invoke() 方法的第一个参数为运行时类的对象,不过也可以不传递,因为方法就属于该运行时类,肯定知道是哪个运行时类了,就可以不传递了。

# 4.5 代理模式

反射有哪些应用呢,最常用的就是代理模式。

什么是代理模式?

代理模式是一种设计模式中的一种,简单来说就是 **我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。**例如在方法执行开始之前或开始之后打印一下日志,在不修改原来方法的基础上如何实现。

代理模式有两种方式:静态代理和动态代理。

# 1 静态代理

静态模式由 代理类被代理类 两部分组成,被代理类其实就是具体实现业务逻辑的类。

代理类负责对被代理类的调用进行拦截、过滤、预处理,我们在需要调用被代理类实现的业务功能时,不是直接通过调用被代理类来实现,而是通过调用代理类的同名方法来调用被代理类的方法来实现。

具体实现:

1 首先定义一个接口,规定要实现的业务逻辑功能

/**
 * 定义一个账户接口
 */
public interface Account {
    // 查询账户
    void queryAccount();

    // 修改账户
    void updateAccount();
}
1
2
3
4
5
6
7
8
9
10

2 创建被代理类,也就是具体的业务实现类,实现具体的业务功能

public class AccountImpl implements Account {

    @Override
    public void queryAccount() {
        System.out.println("查看账户...");
    }

    @Override
    public void updateAccount() {
        System.out.println("修改账户...");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13

3 创建代理类,在代理类中调用被代理类

在代理类中创建一个被代理类的对象来调用具体的业务方法。

通过实现业务逻辑接口,来统一业务方法;在代理类中实现业务逻辑接口中的方法时,进行预处理操作、通过业务实现类对象调用真正的业务方法、进行调用后操作的定义。

package com.doubibiji.proxy;

public class AccountProxy implements Account {

    //组合一个业务实现类对象来进行真正的业务方法的调用
    private AccountImpl countImpl;

    /**
     * 覆盖默认构造器
     */
    public AccountProxy(AccountImpl countImpl) {
        this.countImpl = countImpl;
    }

    @Override
    public void queryAccount() {
        System.out.println("查询账户的预处理----");
        // 调用真正的查询账户方法
        countImpl.queryAccount();
        System.out.println("查询账户之后----");
    }

    @Override
    public void updateAccount() {
        System.out.println("修改账户之前的预处理----");
        // 调用真正的修改账户操作
        countImpl.updateAccount();
        System.out.println("修改账户之后----");
    }
}
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

4 使用

首先创建被代理类对象,然后把被代理类对象作构造参数,用来创建一个代理类对象,最后通过代理类对象进行业务方法的调用。

public class ProxyTest {
    public static void main(String[] args) {
        // 创建被代理类的对象
        AccountImpl countImpl = new AccountImpl();
        // 创建代理类对象
        AccountProxy countProxy = new AccountProxy(countImpl);

        // 调用业务方法,通过代理类来调用被代理类
        countProxy.updateAccount();
        countProxy.queryAccount();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

静态代理模式中,代理类和被代理类在编译的时候就已经确定下来了,谁来代理谁。所以缺点也很明显:一个代理类只能对一个业务实现类进行代理,如果有多个业务接口的话就要定义很多业务实现类(被代理类)和代理类才行。而且,如果代理类对业务方法的预处理、调用后操作都是一样的(比如:调用前输出提示、调用后自动关闭连接),则多个代理类就会有很多重复代码。

所以静态代理实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

如果我们可以定义一个代理类,它能代理所有业务实现类(被代理类)的方法调用:根据传进来的业务实现类(被代理类)和方法名进行具体调用,那就好了,这就需要使用动态代理。

# 2 JDK动态代理

动态代理其实在日常开发中用的也不多,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

动态代理主要需要解决两个问题:

  • 根据加载的被代理类,动态的创建一个代理类及其对象;
  • 调用代理类对象的方式时,去动态的调用被代理类中的同名方法;

实现动态代理有两种方式: JDK动态代理 、 CGlib动态代理。

下面介绍 JDK 动态代理的实现方式,一般情况 JDK 动态代理的实现方式比CGlib动态代理实现方式效率更高一些。


首先也是要定义业务逻辑接口和具体的业务实现类。

1 定义业务逻辑接口

/**
 * 定义一个账户接口
 */
public interface Account {
    // 查询账户
    void queryAccount();

    // 修改账户
    void updateAccount();
}
1
2
3
4
5
6
7
8
9
10

2 创建业务实现类(也就是被代理类),实现业务逻辑接口

public class AccountImpl implements Account {

    @Override
    public void queryAccount() {
        System.out.println("查看账户...");
    }

    @Override
    public void updateAccount() {
        System.out.println("修改账户...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

3 创建代理类工厂

首先创建一代理类工厂,用代理类工厂,通过被代理类来创建动态代理类。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory {

    /**
     * 通过反射机制,创建一个代理类对象实例并返回
     */
    public static Object getProxyInstance(Object obj) {

        // InvocationHandler主要用来处理方法的调用,当代理类方法被调用就会触发invoke()方法
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), handler);
    }

}

class MyInvocationHandler implements InvocationHandler {

    // 被代理类的对象
    private Object target;

    public void bind(Object target) {
        this.target = target;
    }

    /**
     * 包装调用方法:进行预处理、调用后处理
     */
    @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;
    }
}
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

4 测试

在使用时,首先创建被代理类对象(业务实现类对象),然后通过被代理类对象,使用代理类工厂创建一个代理类对象,然后定义接口引用指向的是一个绑定了业务类的代理类对象,所以通过接口方法名调用的是被代理的方法。

public class ProxyTest {
    public static void main(String[] args) {
        // 创建被代理类对象
        AccountImpl accountImpl = new AccountImpl();
        // 获取代理类对象
        Account proxy = (Account) ProxyFactory.getProxyInstance(accountImpl);

        // 通过代理类对象来调用方法
        proxy.queryAccount();
        proxy.updateAccount();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。

**JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。**我们需要做的,只需指定代理类的预处理、调用后操作即可。

动态代理的作用是什么:

  1. Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;如果有其他的业务实现类,使用 ProxyFactory 也可以获取代理类对象。
  2. 可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
  3. 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。