1. Java 反射机制 1.1. 什么是反射? 反射是一种可以间接操作目标对象的机制。当使用反射时,JVM 在运行的时候才动态加载类,对于任意类,知道其属性和方法,并不需要提前在编译期知道运行的对象是谁,允许运行时的 Java 程序获取类的信息并对其进行操作。
对象的类型在编译期就可以确定,但程序运行时可能需要动态加载一些类(之前没有用到,故没有加载进 jvm),使用反射可以在运行期动态生成对象实例并对其进行操作。
简单来说通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
Java 的反射 API 提供了一系列的类和接口来操作 Class 对象。主要的类包括:
java.lang.Class:表示类的对象。提供了方法来获取类的字段、方法、构造函数等。
java.lang.reflect.Field:表示类的字段(属性)。提供了访问和修改字段的能力。
java.lang.reflect.Method:表示类的方法。提供了调用方法的能力。
java.lang.reflect.Constructor:表示类的构造函数。提供了创建对象的能力。
1.2. 反射的应用场景 像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。 这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。 Java 中的一大利器注解的实现也用到了反射。
1.3. 反射的优缺点
优点:可以让代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点。
2. 反射的基本使用 创建一个学生类进行操作:
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 package test;import lombok.Data;import java.util.Objects;@Data public class Student { public String name; public int age; private String address; static { System.out.println("Static block executed" ); } public void printInfo () { System.out.println("I am a student" ); } private String printInfo2 (String name) { System.out.println(name + ":I am not a student" ); return "print info 2" ; } public Student (String name, int age, String address) { this .name = name; this .age = age; this .address = address; } public Student () { this .name = "Tom" ; this .age = 20 ; this .address = "ChengHuaDaDao" ; } private Student (String name, int age) { this .name = name; this .age = age; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}' + "\n" ; } @Override public boolean equals (Object o) { if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name) && Objects.equals(address, student.address); } @Override public int hashCode () { return Objects.hash(name, age, address); } }
2.1. 获取 Class 对象 通过这四种方式获取的 Class 对象是一样的
1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) throws Exception { Class<Student> studentClass1 = Student.class; Class<?> studentClass2 = Class.forName("test.Student" ); Student student = new Student (); Class<?> studentClass3 = student.getClass(); Class<?> studentClass4 = ClassLoader.getSystemClassLoader().loadClass("test.Student" ); }
2.2 反射的基本操作 2.2.1. 获取类的构造方法并创建对象 2.2.1.1. 获取构造方法 1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) throws Exception { Class<Student> studentClass = Student.class; Constructor<?>[] constructors = studentClass.getConstructors(); Constructor<?>[] declaredConstructors = studentClass.getDeclaredConstructors(); Constructor<Student> constructor = studentClass.getConstructor(String.class, int .class, String.class); Constructor<Student> declaredConstructor = studentClass.getDeclaredConstructor(String.class, int .class); }
2.2.1.2. 创建对象 1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) throws Exception { Class<Student> studentClass = Student.class; Constructor<Student> declaredPublicConstructor = studentClass.getDeclaredConstructor(String.class, int .class, String.class); declaredPublicConstructor.newInstance("Tom" , 20 , "ChengHuaDaDao" ); Constructor<Student> declaredPrivateConstructor = studentClass.getDeclaredConstructor(String.class, int .class); declaredPrivateConstructor.setAccessible(true ); declaredPrivateConstructor.newInstance("Tom" , 20 ); }
2.2.2. 获取类的成员变量并操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static void main (String[] args) throws Exception { Class<Student> studentClass = Student.class; Field[] fields = studentClass.getFields(); Field[] declaredFields = studentClass.getDeclaredFields(); Field name = studentClass.getField("name" ); Field address = studentClass.getDeclaredField("address" ); Student student = new Student (); System.out.println(name.get(student)); address.setAccessible(true ); System.out.println(address.get(student)); name.set(student, "Alice" ); address.setAccessible(true ); address.set(student, "ChengDu" ); }
2.2.3. 获取类的方法并操作 2.2.3.1 获取全部公共方法 1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) throws Exception { Class<Student> studentClass = Student.class; Method[] methods = studentClass.getMethods(); for (Method method : methods) { System.out.println(method); } }
输出结果如下,可以看到除了 Student 类中定义的公共方法外,还有一些 Object 类(父类)中公共的方法:
1 2 3 4 5 6 7 8 9 10 public void test.Student.printInfo() public boolean test.Student.equals(java.lang.Object) public java.lang.String test.Student.toString() public int test.Student.hashCode() public final void java.lang.Object.wait (long,int) throws java.lang.InterruptedException public final void java.lang.Object.wait () throws java.lang.InterruptedException public final native void java.lang.Object.wait (long) throws java.lang.InterruptedException public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll()
2.2.3.2 获取全部类型的所有方法 1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) throws Exception { Class<Student> studentClass = Student.class; Method[] methods = studentClass.getDeclaredMethods(); for (Method method : methods) { System.out.println(method); } }
输出结果如下,只有 Student 类中定义的方法:
1 2 3 4 5 public boolean test.Student.equals(java.lang.Object) public java.lang.String test.Student.toString() public int test.Student.hashCode() private void test.Student.printInfo2(java.lang.String) public void test.Student.printInfo()
2.2.3.3 获取指定方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) throws Exception { Class<Student> studentClass = Student.class; Method method = studentClass.getDeclaredMethod("printInfo2" , String.class); System.out.println(method.getModifiers()); System.out.println(method.getName()); System.out.println(method.getReturnType()); System.out.println(Arrays.toString(method.getParameterTypes())); }
2.2.3.4 调用方法 1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) throws Exception { Class<Student> studentClass = Student.class; Method method = studentClass.getDeclaredMethod("printInfo2" , String.class); Student student = new Student (); method.setAccessible(true ); System.out.println(method.invoke(student, "Tom" )); }
3. 注解 3.1 什么是注解? 注解 (Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。 简单来说就是 Java 代码中的特殊标记比如@Override、@Test 等,作用是让其他程序根据注解信息决定如何执行该程序。
3.2 自定义注解 创建一个注解类 MyAnnotation.java 格式为:
1 2 3 4 public @interface 注解名 { public 属性类型 属性名() default 默认值; }
默认 public 可以省略,默认值也可以不写,但是如果不写默认值,那么在使用注解时必须给属性赋值。
1 2 3 4 5 6 7 package test;public @interface MyAnnotation { String aaa () ; boolean bbb () default true ; String[] ccc(); }
上面注解本质是一个接口,Java 所有注解都继承了 Annotation 接口反编译后的代码如下
1 2 3 4 5 6 7 package test;import java.lang.annotation.Annotation;public interface MyAnnotation extends Annotation { public abstract String aaa () ; public abstract boolean bbb () ; public abstract String[] ccc(); }
1 2 3 4 5 6 7 @MyAnnotation(aaa = "cxk", ccc = {"zhangsan", "lisi"}) public static void main (String[] args) { } @注解(...)其实就是实现类对象,实现了该注解以及Annotation接口。
另外有一个特殊属性名为 value,可以省略属性名,直接赋值。 即只有一个 value 属性或者剩余的属性都有默认值时,可以省略属性名。
1 2 3 4 public @interface MyAnnotation { String value () ; boolean bbb () default true ; }
1 2 3 4 @MyAnnotation("cxk") public static void main (String[] args) { }
3.3 元注解 指的是可以注解到注解上的注解,Java 提供了 5 个元注解,专门负责注解其他注解。
3.3.1 @Target 作用:声明被修饰的注解只能在哪些位置使用(默认是全部范围生效)
Type:类、接口、枚举
Field:字段
Method:方法
Parameter:参数
Constructor:构造方法
Local_Variable:局部变量
1 2 3 4 @Target(ElementType.Type, ElementType.Field) public @interface MyAnnotation { String value () ; }
3.3.2 @Retention 声明注解的生命周期@Retention(RetentionPolicy.RUNTIME):注解会在 class 文件中存在,在运行时可以通过反射获取到。
1 2 3 4 5 6 7 8 9 10 11 - SOURCE(源码级别,编译后丢弃) 用于只在代码编辑或编译期间提供辅助信息,不需要出现在生成的字节码中。 例子: Java 内置的注解 @SuppressWarnings 就采用 SOURCE 策略。它只用于指示编译器忽略特定的警告,编译后便不保留在 class 文件中。 - CLASS(编译时注解,运行时丢弃,默认值) 注解会被编译进字节码,但在运行时通过反射无法访问。适合那些需要在编译期或打包时被工具处理,但运行期不需要的场景。 例子: 如果你定义一个注解,仅用于代码生成或静态检查工具(如代码质量检测、自动生成文档等),可以选择 CLASS 策略,这样相关信息会保留在 class 文件中供工具分析,但不会增加运行时开销。 - RUNTIME(运行时注解,运行时保留) 注解在编译后会保留在 class 文件中,并可在运行时通过反射读取。适合依赖动态探测注解信息的场景。 例子: Spring 框架中的 @Autowired 注解,就是运行时注解。容器通过反射检查、解析这些注解,完成依赖注入等动态操作。
3.3.3 @Documented @Documented 是一个简单的标记注解,表示是否将注解信息添加在 Java 文档,即 Javadoc 中。
3.3.4 @Inherited Inherited 是指继承,@Inherited 定义了一个注释与子类的关系。如果一个超类带有 @Inherited 注解,那么对于该超类,它的子类如果没有被任何注解应用的话,那么这个子类就继承了超类的注解。 例如下面的例子,如果 A 类上有 @Test 注解,那么 B 类也会继承 @Test 注解。
1 2 3 4 5 6 7 8 @Inherited @Retention(RetentionPolicy.RUNTIME) @interface Test {}@Test public class A {}public class B extends A {}
3.3.5 @Repeatable @Repeatable 是 Java 8 中加入的,是指可重复的意思。通常使用 @Repeatable 的时候指注解的值可以同时取多个。 例如一个人可以有多种身份
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @interface Persons { Person[] value(); }@Repeatable(Persons.class) @interface Person { String role default "" ; }@Person(role="artist") @Person(role="coder") @Person(role="PM") public class SuperMan { ... }
3.4 注解的解析 判断类、方法、字段上是否有注解,如果有则获取注解对象并解析其中的内容。
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 import org.junit.Test;import test.MyAnnotation;import java.lang.reflect.Method;public class Demo { @Test public void parseClass () { Class clazz = Main.class; if (clazz.isAnnotationPresent(MyAnnotation.class)){ MyAnnotation annotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class); System.out.println(annotation.address()); System.out.println(annotation.value()); } } @Test public void parseMethod () throws NoSuchMethodException { Class clazz = Main.class; Method method = clazz.getDeclaredMethod("main" , String[].class); if (method.isAnnotationPresent(MyAnnotation.class)){ MyAnnotation annotation = (MyAnnotation) method.getAnnotation(MyAnnotation.class); System.out.println(annotation.address()); System.out.println(annotation.value()); } } }
4. 动态代理 动态代理是一种设计模式,它是在运行时创建一个实现了一组给定接口的新类的对象。动态代理类的实例将通常用于替换对其他对象的直接调用。通过使用动态代理,可以将调用分派到不同的调用处理程序,这些处理程序可以将调用连接到实际的服务对象。
创建一个接口类
1 2 3 4 5 6 7 8 package test;public interface Star { String sing (String name) ; void dance () ; void rap () ; void basketball () ; }
创建一个实现类
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 package test;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class BigStar implements Star { public String name; @Override public String sing (String name) { return "唱歌" ; } @Override public void dance () { System.out.println("跳" ); } @Override public void rap () { System.out.println("rap" ); } @Override public void basketball () { 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 package test;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class ProxyUtil { public static Star createProxy (BigStar bigStar) { Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), new Class []{Star.class}, new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("sing" )) { System.out.println("准备唱歌" ); } if (method.getName().equals("dance" )) { System.out.println("准备跳舞" ); } if (method.getName().equals("rap" )) { System.out.println("准备rap" ); } if (method.getName().equals("basketball" )) { System.out.println("准备打篮球" ); } return method.invoke(bigStar, args); } }); return starProxy; } }
调用测试
1 2 3 4 5 6 7 8 9 10 11 12 13 package test;public class Test { public static void main (String[] args) { BigStar bigStar = new BigStar ("cxk" ); Star starProxy = ProxyUtil.createProxy(bigStar); System.out.println(starProxy.sing("cxk" )); } }