# Java教程 - 4 反射
Java中的反射(Reflection)是 Java 语言的一个强大特性,它允许程序在运行的时候,检查类、接口、字段和方法的信息,并可以 动态 地创建和操作对象。
在 Java 虚拟机 JVM 加载类之后,会在 JVM 的方法区生成一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象中包含了类的完整信息,包括属性和方法等。以前创建对象是通过类结构创建对象,现在是通过 Class 对象获取类的结构,所以叫反射。
通过反射可以实现的主要功能:
- 动态地创建和加载类:通过反射,可以在运行时加载并初始化类,而无需事先知道类名。
- 获取类的详细信息:可以获取类的名称、修饰符、超类、实现的接口、注解等。
- 获取类的成员信息:可以获取类的字段、方法、构造器等成员的信息。
- 动态调用方法:可以动态地调用对象的方法,即使这些方法在编译时是未知的。
# 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);
}
}
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(); // 无法调用私有方法
}
}
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");
}
}
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);
}
}
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
}
}
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
}
}
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();
}
2
3
4
5
6
7
8
9
10
11
12
注解的保留策略需要指定 RetentionPolicy.RUNTIME
,这样在运行时才能获取到,另外可以指定在哪些元素上可以使用这个注解。
准备一个接口:
package com.doubibiji.reflect;
public interface MyInterface {
void showInfo();
}
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("人要吃饭");
}
}
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;
}
}
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);
}
}
}
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
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);
}
}
}
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
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);
}
}
}
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("----------");
}
}
}
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);
}
}
}
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()
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());
}
}
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
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);
}
}
}
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);
}
}
}
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
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);
}
}
2
3
4
5
6
7
8
9
执行结果:
package com.doubibiji.reflect
# 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}
}
}
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;
}
}
}
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
}
}
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);
}
}
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);
}
}
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();
}
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("修改账户...");
}
}
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("修改账户之后----");
}
}
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();
}
}
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();
}
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("修改账户...");
}
}
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;
}
}
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。
**JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。**我们需要做的,只需指定代理类的预处理、调用后操作即可。
动态代理的作用是什么:
- Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;如果有其他的业务实现类,使用 ProxyFactory 也可以获取代理类对象。
- 可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
- 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。
← 03-注解 05-Java7新特性 →