文章大致思路:
编写的过程中,即使书写完已阅读过,但难免可能会出现遗漏,如有发现问题请及时联系修正,非常感谢你的阅读,希望我们都能成为技术道路上的朋友。
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
不过要想解剖一个类,就要先获取到该类的字节码文件对应的Class类型的对象.
稍后就会讲到~
“反射之所以被称为框架的灵魂”,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力,这种能力也就是我们常说的动态性,利用这种性质可以使编写的程序更灵活更通用。
反射机制可以用来:
反射是一种功能强大且复杂的机制,在开发Java工具或框架方面,反射更是不可缺少的一部分。
之前说到了,如果要分析一个类,就必须要获取到该类的字节码文件对应 Class 类型对象。
另外如果有听到类模板对象,这个说的其实就是Class对象,大家不要误会了。
得到Class的方式总共有四种:
- /**
- * @description:
- * @author: Ning Zaichun
- * @date: 2022年09月15日 22:49
- */
- public class ClassDemo01 {
-
- @Test
- public void test1() throws Exception {
- //1、通过对象调用 getClass() 方法来获取
- // 类型的对象,而我不知道你具体是什么类,用这种方法
- Student student = new Student();
- Class studentClass1 = student.getClass();
- System.out.println(studentClass1);
- // out: class com.nzc.Student
-
- //2、直接通过`类名.class` 的方式得到
- // 任何一个类都有一个隐含的静态成员变量 class
- // Class studentClass2 = Student.class;
- Class<?> studentClass2 = Student.class;
- System.out.println(studentClass2);
- // out: class com.nzc.Student
- System.out.println("studentClass1和studentClass2 是否相等==>" + (studentClass1 == studentClass2));
- // studentClass1和studentClass2 是否相等==>true
-
- //3、通过 Class 对象的 forName() 静态方法来获取,使用的最多
- // 但需要抛出或捕获 ClassNotFoundException 异常
- Class<?> studentClass3 = Class.forName("com.nzc.Student");
- System.out.println(studentClass3);
- // out: class com.nzc.Student
- System.out.println("studentClass1和studentClass3 是否相等==>" + (studentClass1 == studentClass3));
- //studentClass1和studentClass3 是否相等==>true
-
- //4、 使用类的加载器:ClassLoader 来获取Class对象
- ClassLoader classLoader = ClassDemo01.class.getClassLoader();
- Class studentClass4 = classLoader.loadClass("com.nzc.Student");
- System.out.println(studentClass4);
- System.out.println("studentClass1和studentClass4 是否相等==>" + (studentClass1 == studentClass4));
- //studentClass1和studentClass4 是否相等==>true
- }
- }
-
在这四种方式中,最常使用的是第三种方式,第一种都直接new对象啦,完全没有必要再使用反射了;第二种方式也已经明确了类的名称,相当于已经固定下来,失去了一种动态选择加载类的效果;而第三种方式,只要传入一个字符串,这个字符串可以是自己传入的,也可以是写在配置文件中的。
像在学 JDBC 连接的时候,大家肯定都使用过Class对象的forName()静态方法来获取Class对象,再加载数据库连接对象,但可能那时候只是匆匆而过罢了。
注意:不知道大家有没有观察,我把各种方式所获取到的class对象,都进行了一番比较,并且结果都为true,这是因为一个类在 JVM 中只会有一个 Class 实例,为了解释此点,我把类加载过程也简单的做了一个陈述。
当我们需要使用某个类时,如果该类还未被加载到内存中,则会经历下面的过程对类进行初始化。
即类的加载 —> 链接 —> 初始化三个阶段。
在这里我只着眼于类的加载过程了,想要了解更为详细的,就需要大家去找找资料看看啦~
加载过程:
1、在我们进行编译后(javac.exe命令),会生成一个或多个字节码文件(就是项目中编译完会出现的 target 目录下的以.class结尾的文件)
2、接着当我们使用 java.exe 命令对某个字节码文件进行解释运行时。
3、加载过程
4、这个加载的过程还需要类加载器参与,关于类加载器的类型大家可以去了解了解,还有双亲委派机制等,此处我便不再多言
(图片说明:为更好的描述JVM中只有一个Class对象,而画下此图,希望通过这张简图,让你记忆更为深刻)
通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
另外就还有反射包下的几个常用的对象Constructor、Filed、Method等,分别表示类的构造器、字段属性、方法等等
这些都会在下文慢慢陈述出来~
所谓运行时类,就是程序运行时所创建出来的类,你直接理解为通过反射获取到的类也可。大体意思是如此。
在讲述这一小节时,先要理解Java中一切皆对象这句话。
我们平常编写一个类,我们会将它称为一个Java对象,但是在反射这里将此概念再次向上抽象了。
类的基本信息:构造方法、成员变量,方法,类上的注解,方法注解,成员变量注解等等,这些都是Java对象,也是证明了Java中一切皆对象这句话。
其中Constructor就表示构造方法的对象,他包含了构造方法的一切信息,
Field、Method等等都是如此。
不要太过于麻烦和重复书写,我将案例中操作的所有相关代码,都放在此处了,案例中的依赖全部基于此。
- public interface TestService {
- }
-
- @Target({ElementType.TYPE,ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface MarkAnnotation {
-
- String value() default "";
-
- }
-
- public class Generic<T> {
- }
-
-
- public interface GenericInterface<T> {
- }
-
-
- // TYPE 表示可以标记在类上
- // PARAMETER 表示可以标记在方法形式参数
- // METHOD 方法
- // FIELD 成员属性上
- @Target({ElementType.TYPE,ElementType.METHOD, ElementType.FIELD,ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME) // 这里表明是运行时注解
- @Documented
- public @interface LikeAnnotation {
-
- String value() default "";
-
-
- String[] params() default {};
- }
-
- @Data
- @MarkAnnotation
- public class Person implements TestService{
- private String sex;
-
- public Integer age;
-
- private void mySex(String sex){
- System.out.println("我的性别"+sex);
- }
-
- public void myAge(Integer age){
- System.out.println("我的年龄"+age);
- }
-
-
- }
-
- @ToString(callSuper = true) // 增加这行是为了打印时将父类属性也打印出来,方便查看~
- @Data
- @LikeAnnotation(value = "123")
- public class Student extends Person implements Serializable {
- @LikeAnnotation
- private String username;
-
- @LikeAnnotation
- public String school;
-
- private Double score;
-
- public Student() {
- }
-
- private Student(String username) {
- this.username = username;
- }
-
- public Student(String username, String school) {
- this.username = username;
- this.school = school;
- }
-
- @LikeAnnotation
- public void hello() {
- System.out.println("世界,你好");
- }
-
- public void say( String username) {
- System.out.println("你好,我叫" + username);
- }
-
- private void myScore(Double score) {
- System.out.println("我的分数是一个私密东西," + score);
- }
-
- public void annotationTest(@LikeAnnotation String username,@MarkAnnotation String str){
- System.out.println( "测试获取方法参数中的注解信息");
- }
- }
-
class获取构造方法的相关API
- // 获取所有的构造函数
- Constructor<?>[] getConstructors()
-
- // 获取 public或 private 修饰的狗赞函数,只要参数匹配即可
- Constructor<?>[] getDeclaredConstructors()
-
- // 获取所有 public 修饰的 构造函数
- Constructor<T> getConstructor(Class<?>... parameterTypes)
-
- //调用此方法,创建对应的运行时类的对象。
- public T newInstance(Object ... initargs)
-
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
为什么要 javabean 中要求提供一个 public 的空参构造器?
原因: 1、便于通过反射,创建运行时类的对象;2、便于子类继承此运行时类时,默认调用 super() 时,保证父类有此构 造器。
想要更详细的了解,建议去看生成的字节码文件,在那里能够给出你答案。
测试:
- /**
- * @description:
- * @author: Ning Zaichun
- * @date: 2022年09月17日 1:17
- */
- public class ConstructorTest {
-
- /**
- * 获取公有、私有的构造方法 并调用创建对象
- */
- @Test
- public void test1() throws Exception {
- Class<?> aClass = Class.forName("com.nzc.Student");
-
- System.out.println("======获取全部public 修饰的构造方法=========");
- Constructor<?>[] constructors = aClass.getConstructors();
- for (Constructor<?> constructor : constructors) {
- System.out.println(constructor);
- }
- System.out.println("======获取 public、private 修饰的构造方法,只要参数匹配即可=========");
- /**
- * 这里的参数 Class<?>... parameterTypes 填写的是参数的类型,而并非某个准确的值信息
- */
- Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
- System.out.println(constructor);
- // 因为此构造函数是 private 修饰,如果不设置暴力反射,则没有权限访问。
- // 这里setAccessible(true) 是设置暴力反射,如果不设置,则会报错,
- constructor.setAccessible(true);
- // 这里调用的有参构造,所以在调用 newInstance 方法时,也需要填写参数
- Object o1 = constructor.newInstance("宁在春");
- constructor.setAccessible(false);
- System.out.println("o1===>"+o1);
-
- /**
- * 如果需要获取有参构造,只需要填写对应的参数类型即可,
- * 获取无参构造,填null或不填都可。
- */
- Constructor<?> constructor1 = aClass.getConstructor();
- Constructor<?> constructor2 = aClass.getConstructor(String.class,String.class);
- System.out.println("无参构造==>"+constructor1);
- System.out.println("有参构造==>"+constructor2);
- Object o2 = constructor1.newInstance();
- Object o3 = constructor2.newInstance("宁在春2","xxxx社会");
- System.out.println("o2===>"+o2);
- System.out.println("o3===>"+o3);
- }
- }
-
既然能够获取到构造方法,那么也就是可以使用的,用Constructor.newInstance()方法来调用构造方法即可,在下列的打印信息中,也可以看出来确实如此,如果明确要获取为Student对象的话,进行强转即可。
- ======获取全部public 修饰的构造方法=========
- public com.nzc.Student(java.lang.String,java.lang.String)
- public com.nzc.Student()
- ======获取 public、private 修饰的构造方法,只要参数匹配即可=========
- private com.nzc.Student(java.lang.String)
- o1===>Student(username=宁在春, school=null, age=null)
- 无参构造==>public com.nzc.Student()
- 有参构造==>public com.nzc.Student(java.lang.String,java.lang.String)
- o2===>Student(username=null, school=null, age=null)
- o3===>Student(username=宁在春2, school=xxxx社会, age=null)
-
class对象中获取类成员信息使用到的API
- Field[] getFields();
-
- Field[] getDeclaredFields();
-
- Field getDeclaredField(String name);
-
- public native Class<? super T> getSuperclass();
-
这里的Field类对象,其实就是表示类对象中的成员属性,不过还有多了很多其他在反射时需要用到的属性和API罢了~
为了有更好的对比,我先编写了一段不使用反射时的正常操作。
- /**
- * 不使用反射的进行操作
- */
- @Test
- public void test1() {
- Student student = new Student();
- student.setUsername("username");
- student.setSchool("xxxx社会");
- student.setScore(100.0);
- // 永远三岁的小伙子 哈哈 🤡
- student.setAge(3);
- student.setSex("男");
-
- System.out.println("student信息===>" + student);
- // out:student信息===>Student(super=Person(sex=男, age=3), username=username, school=xxxx社会, score=100.0)
- }
-
现在我再使用反射来实现上述操作~
在实现之前,还是先来看看如何获取公有、私有的成员变量吧
- @Test
- public void test2() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
-
- System.out.println("========获取所有 public 修饰的成员属性(包括父类属性)=====");
- Field[] fields = stuClass.getFields();
- for (Field field : fields) {
- System.out.println(field);
- }
- //public java.lang.String com.nzc.Student.school
- //public java.lang.Integer com.nzc.Person.age
-
- System.out.println("========获取所有(public、private、protected等修饰的)属性成员(不包括父类成员属性)=====");
- Field[] fields1 = stuClass.getDeclaredFields();
- for (Field field : fields1) {
- System.out.println(field);
- }
- //private java.lang.String com.nzc.Student.username
- //public java.lang.String com.nzc.Student.school
- //private java.lang.Double com.nzc.Student.score
-
- System.out.println("========通过反射,获取对象指定的属性=====");
- Field username = stuClass.getDeclaredField("username");
- System.out.println("username===>" + username);
- }
-
但是你发现没有,这无法获取到父类的成员变量信息,父类的信息,我们该如何获取呢?
其实一样的,我们也是要获取到父类的Class对象,在Class API中有一个getSuperClass()方法可以获取到父类的class对象,其他的操作都是一样的~
- @Test
- public void test5() throws ClassNotFoundException {
- Class<?> stuClass = Class.forName("com.nzc.Student");
- System.out.println("========获取所有属性成员(包括父类成员属性)=====");
- Class clazz = stuClass;
- List<Field> fieldList = new ArrayList<>();
- while (clazz != null) {
- fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
- clazz = clazz.getSuperclass();
- }
- Field[] fields2 = new Field[fieldList.size()];
- fieldList.toArray(fields2);
- for (Field field : fields2) {
- System.out.println(field);
- }
- //private java.lang.String com.nzc.Student.username
- //public java.lang.String com.nzc.Student.school
- //private java.lang.Double com.nzc.Student.score
- //private java.lang.String com.nzc.Person.sex
- //public java.lang.Integer com.nzc.Person.age
- }
-
到这里我们已经知道如何获取到类的成员变量信息了,就可以来看看如何给它们设置值,或者通过反射的方式来获取到值了。
- @Test
- public void test3() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
- System.out.println("获取到 filed 后,设置值或修改值");
- Constructor<?> constructor = stuClass.getConstructor();
- Object o = constructor.newInstance();
-
- //操作pubilc 属性
- Field school = stuClass.getDeclaredField("school");
- school.set(o,"xxxx社会");
- System.out.println(o);
- //Student(super=Person(sex=null, age=null), username=null, school=xxxx社会, score=null)
- // 另外既然可以设置,那么自然也是可以获取的
- System.out.println(school.get(o));
- //xxxx社会
-
- // 操作 private 修饰的成员属性
- Field name = stuClass.getDeclaredField("username");
- // setAccessible(true) 因为是获取private 修饰的成员变量
- // 不设置暴力反射 是无法获取到的,会直接报 IllegalAccessException 异常
- name.setAccessible(true);
- name.set(o,"Ningzaichun");
- name.setAccessible(false);
- System.out.println(o);
- //Student(super=Person(sex=null, age=null), username=Ningzaichun, school=xxxx社会, score=null)
- }
-
其实看到这里对于反射已经有个大概的印象了,并且这都是比较平常且实用的一些方法。
- @Test
- public void test4() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
- Field school = stuClass.getDeclaredField("school");
-
- LikeAnnotation annotation = school.getAnnotation(LikeAnnotation.class);
- System.out.println(annotation);
- //@com.nzc.LikeAnnotation(params=[], value=)
-
- Annotation[] annotations = school.getAnnotations();
- for (Annotation annotation1 : annotations) {
- System.out.println(annotation1);
- }
- //@com.nzc.LikeAnnotation(params=[], value=)
-
-
- LikeAnnotation declaredAnnotation = school.getDeclaredAnnotation(LikeAnnotation.class);
- System.out.println(declaredAnnotation);
- //@com.nzc.LikeAnnotation(params=[], value=)
-
- Annotation[] declaredAnnotations = school.getDeclaredAnnotations();
- for (Annotation declaredAnnotation1 : declaredAnnotations) {
- System.out.println(declaredAnnotation1);
- }
- //@com.nzc.LikeAnnotation(params=[], value=)
- }
-
关于getAnnotation、getDeclaredAnnotation 的分析大家可以看看下面这篇文章@Repeatable详解-getAnnotation、getDeclaredAnnotation获取不到对象
对于想要更详细的了解Java注解的朋友,大家可以继续找找相关资料。
Class对象中使用的相关API
- Method[] getMethods();
-
- Method getMethod(String name, Class<?>... parameterTypes);
-
- Method[] getDeclaredMethods();
-
- Method getDeclaredMethod(String name, Class<?>... parameterTypes);
-
- // 获取方法返回值类型
- Class<?> getReturnType();
-
- // obj – 调用底层方法的对象
- // args – 用于方法调用的参数
- // return 使用参数args在obj上调度此对象表示的方法的结果
- Object invoke(Object obj, Object... args)
-
获取公有、私有方法
- @Test
- public void test1() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
-
- System.out.println("========获取所有public修饰的方法======");
- Method[] methods = stuClass.getMethods();
- for (Method method : methods) {
- System.out.println(method);
- }
- //public boolean com.nzc.Student.equals(java.lang.Object)
- //public java.lang.String com.nzc.Student.toString()
- //public int com.nzc.Student.hashCode()
- //public void com.nzc.Student.hello()
- //public void com.nzc.Student.say(java.lang.String)
- //public java.lang.Double com.nzc.Student.getScore()
- //public void com.nzc.Student.setScore(java.lang.Double)
- //public void com.nzc.Student.setSchool(java.lang.String) .... 还有一些没写出来了
-
- System.out.println("========获取对象【指定的public修饰的方法】======");
- // 第一个参数为方法名,此处是获取public修饰的无参方法
- Method hello = stuClass.getMethod("hello");
- System.out.println("hello===>"+hello);
- // 带参方法,第二个参数填写【参数类型】
- Method say = stuClass.getMethod("say", String.class);
- System.out.println("say===>"+say);
- //hello===>public void com.nzc.Student.hello()
- //say===>public void com.nzc.Student.say(java.lang.String)
-
-
- // 参数为 方法名,此处是获取 private 修饰的无参方法
- // stuClass.getDeclaredMethod("");
- Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);
- System.out.println("myScore==>"+myScore);
- //myScore==>private void com.nzc.Student.myScore(java.lang.Double)
-
- System.out.println("=======获取所有的方法=======");
- Method[] declaredMethods = stuClass.getDeclaredMethods();
- for (Method declaredMethod : declaredMethods) {
- System.out.println(declaredMethod);
- }
- //public void com.nzc.Student.say(java.lang.String)
- //private void com.nzc.Student.myScore(java.lang.Double)
- //public java.lang.Double com.nzc.Student.getScore()
- //public void com.nzc.Student.setScore(java.lang.Double)
- //protected boolean com.nzc.Student.canEqual(java.lang.Object)
- }
-
既然能获取到,那么也是能够调用的啦~,
还记得经常能看到的一个invoke()方法吗,这里就是调用method.invoke()方法来实现方法的调用。
- @Test
- public void test2() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
- Constructor<?> constructor = stuClass.getConstructor();
- Object o = constructor.newInstance();
-
- Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);
- myScore.setAccessible(true);
- // 调用方法
- myScore.invoke(o, 99.0);
- // 相当于 Student student= new Student();
- // student.myScore(99.0);
- myScore.setAccessible(false);
- }
-
之前阐述了Method它本身就记录了方法的一切信息,我们实现调用也就是第一步罢了,说它可以获取到当时定义方法的一切都不为过,接下来一步一步来说吧。
- @Test
- public void test2() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
- System.out.println("==== 获取成员变量上指定的注解信息===");
-
- Field username = stuClass.getDeclaredField("username");
- System.out.println(username);
- //private java.lang.String com.nzc.Student.username
- Annotation annotation = username.getAnnotation(LikeAnnotation.class);
- System.out.println(annotation);
- //@com.nzc.LikeAnnotation(params=[], value=)
-
- Method hello = stuClass.getDeclaredMethod("hello");
- LikeAnnotation annotation1 = hello.getAnnotation(LikeAnnotation.class);
- System.out.println(hello+"===="+annotation1);
- // public void com.nzc.Student.hello()====@com.nzc.LikeAnnotation(params=[], value=)
- }
-
不过在写项目时,有可能还会要获取方法参数上的注解,那该如何获取方法参数呢?又该如何获取方法参数的注解信息呢?
- @Test
- public void test3() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
- System.out.println("==== 获取方法参数中的注解信息===");
-
- Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);
-
- // 获取方法的返回值类型
- Class<?> returnType = annotationTest.getReturnType();
- // 获取权限修饰符
- System.out.println(Modifier.toString(annotationTest.getModifiers()) );
- // 获取到全部的方法参数
- Parameter[] parameters = annotationTest.getParameters();
- for (Parameter parameter : parameters) {
- Annotation annotation = parameter.getAnnotation(LikeAnnotation.class);
- if(annotation!=null){
- // 参数类型
- Class<?> type = parameter.getType();
- // 参数名称
- String name = parameter.getName();
- System.out.println("参数类型"+type+" 参数名称==>"+name+" 参数上的注解信息"+annotation);
- //参数类型class java.lang.String 参数名称==>arg0 参数上的注解信息@com.nzc.LikeAnnotation(params=[], value=)
- }
- }
- // 获取参数上全部的注解信息
- Annotation[][] parameterAnnotations = annotationTest.getParameterAnnotations();
- for (int i = 0; i < parameterAnnotations.length; i++) {
- for (int i1 = 0; i1 < parameterAnnotations[i].length; i1++) {
- System.out.println(parameterAnnotations[i][i1]);
- }
- }
- // @com.nzc.LikeAnnotation(params=[], value=)
- //@com.nzc.MarkAnnotation(value=)
- int parameterCount = annotationTest.getParameterCount();
- System.out.println("获取参数个数==>"+parameterCount);
- }
-
-
-
- @Test
- public void test33() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
-
- Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);
- // 获取方法的返回值类型
- Class<?> returnType = annotationTest.getReturnType();
- System.out.println(returnType);
- //void
- // 获取权限修饰符
- System.out.println(Modifier.toString(annotationTest.getModifiers()) );
- //public
- }
-
- /**
- * 获取运行时类实现的接口
- */
- @Test
- public void test5() {
- Class clazz = Student.class;
- Class[] interfaces = clazz.getInterfaces();
- for (Class c : interfaces) {
- System.out.println(c);
- }
- System.out.println("====================");
- // 获取运行时类的父类实现的接口
- Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
- for (Class c : interfaces1) {
- System.out.println(c);
- }
- }
-
- /**
- * 获取运行时类所在的包
- */
- @Test
- public void test6() {
- Class clazz = Person.class;
- Package pack = clazz.getPackage();
- System.out.println(pack);
- // out:package com.nzc
- }
-
- @Test
- public void test1() throws Exception {
- Class<?> stuClass = Class.forName("com.nzc.Student");
- System.out.println("==== 获取类上指定的注解信息===");
-
- LikeAnnotation annotation = stuClass.getAnnotation(LikeAnnotation.class);
- System.out.println(annotation);
- //@com.nzc.LikeAnnotation(params=[], value=123)
- System.out.println("获取注解上的值信息==>"+annotation.value());
- //获取注解上的值信息==>123
- //annotation.params(); 注解有几个属性,就可以获取几个属性
-
- System.out.println("==== 获取类上全部注解信息===");
-
- Annotation[] annotations = stuClass.getAnnotations();
- for (Annotation annotation1 : annotations) {
- System.out.println(annotation1);
- //@com.nzc.LikeAnnotation(params=[], value=123)
- }
-
- LikeAnnotation declaredAnnotation = stuClass.getDeclaredAnnotation(LikeAnnotation.class);
- System.out.println("declaredAnnotation==>"+declaredAnnotation);
- //declaredAnnotation==>@com.nzc.LikeAnnotation(params=[], value=123)
- Annotation[] declaredAnnotations = stuClass.getDeclaredAnnotations();
-
- }
-
注意:lombok 相关的注解是无法获取到的,因为 lombok 注解为编译时注解,并非是运行时注解,在编译完成后,lombok 注解并不会保留于class文件中,因此是无法通过反射获取到的。
@Data也标明了它的存在级别为源码级别,而运行时存在注解@Retention为@Retention(RetentionPolicy.RUNTIME)。
- @Test
- public void test8(){
- Class clazz = Person.class;
-
- // 获取泛型父类信息
- Type genericSuperclass = clazz.getGenericSuperclass();
- System.out.println(genericSuperclass);
- // com.nzc.Generic<java.lang.String>
- // 获取泛型接口信息
- Type[] genericInterfaces = clazz.getGenericInterfaces();
-
- for (Type genericInterface : genericInterfaces) {
- System.out.println(genericInterface);
- //interface com.nzc.TestService
- //com.nzc.GenericInterface<java.lang.String>
- }
- }
-
一句话说它的应用场景就是:确定不下来到底使用哪个类的时候,比如你要开发一个通用工具类,为了达到通用性,传入的参数对象,一般都是无法限制的,这个时候就是要用到反射啦~。
反射的特征:动态性
需求如下:我现在引入了一个第三方 jar 包,里面有一个 MyBatis-Plus 查询构造器,其中构造LIKE条件查询的条件是当前端传过来的参数带有逗号时,拼接为LIKE查询条件。
关键代码:
标识在对象的某个成员属性上
- @Target({ElementType.METHOD, ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface LikeAnnotation {
-
- String value() default "";
- }
-
- ```java
-
- 标识在Controller层上,以此来判断那些请求是需要被切入的。
-
- ```java
- @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface MarkAnnotation {
-
- String value() default "";
-
- }
-
一个非常简单的Javabean
- @Data
- public class Login {
-
- @LikeAnnotation(value = "username")
- private String username;
-
- private String password;
-
- public Login() {
- }
-
- public Login(String username, String password) {
- this.username = username;
- this.password = password;
- }
- }
-
-
Controller 类
- @Slf4j
- @RestController
- public class HelloController {
-
- @MarkAnnotation
- @GetMapping("/search")
- public Login getLikeLogin(Login login){
- System.out.println(login);
- return login;
- }
-
- }
-
重点重点,切面类LikeAnnotationAspect,处理逻辑全部在此处
- @Aspect
- @Component
- @Slf4j
- public class LikeAnnotationAspect {
-
- // 标记了 @MarkAnnotation 注解的才切入 降低性能消耗
- @Pointcut("@annotation(com.nzc.annotation.MarkAnnotation)")
- public void pointCut() {
-
- }
-
- // 获取当前类及父类所有的成员属性
- private static Field[] getAllFields(Object object) {
- Class<?> clazz = object.getClass();
- List<Field> fieldList = new ArrayList<>();
- while (clazz != null) {
- fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
- clazz = clazz.getSuperclass();
- }
- Field[] fields = new Field[fieldList.size()];
- fieldList.toArray(fields);
- return fields;
- }
-
- @Around("pointCut()")
- public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
- long start = System.currentTimeMillis();
- Object[] args = pjp.getArgs();
- // 获取到第一个参数
- Object parameter = args[0];
-
- log.info("parameter==>{}", parameter);
- if (parameter == null) {
- return pjp.proceed();
- }
-
- Field[] fields = getAllFields(parameter);
-
- for (Field field : fields) {
- log.debug("------field.name------" + field.getName());
- if (field.getAnnotation(LikeAnnotation.class) != null) {
- try {
- field.setAccessible(true);
- Object username = field.get(parameter);
- field.setAccessible(false);
- if (username != null && !username.equals("")) {
- field.setAccessible(true);
- field.set(parameter, "," + username + ",");
- field.setAccessible(false);
- }
- } catch (Exception e) {
- }
- }
- }
- // 调用方法
- Object result = pjp.proceed();
- long end = System.currentTimeMillis();
- log.debug("修改耗时==>" + (end - start) + "ms");
- return result;
- }
-
- }
-
实现效果:
看似好像写业务的我们,没有怎么接触Java反射,但实际上可能处处都隐含着反射。
使用过 Mybatis-Plus 的朋友,应该知道,可以设置自动填充数据(创建时间、更新时间、创建人、更新人等),不过那个是实现MetaObjectHandler接口进行处理的。
但是今天的话,我用Mybatis 原生的拦截器来进行一番实现,实现每次更新、添加时自动填充创建人、更新人等,表里没时间字段,就没演示时间了,但实现原理都一致。
- import lombok.extern.slf4j.Slf4j;
- import org.apache.ibatis.binding.MapperMethod.ParamMap;
- import org.apache.ibatis.executor.Executor;
- import org.apache.ibatis.mapping.MappedStatement;
- import org.apache.ibatis.mapping.SqlCommandType;
- import org.apache.ibatis.plugin.*;
- import org.springframework.stereotype.Component;
-
- import java.lang.reflect.Field;
- import java.util.*;
-
- /**
- * mybatis拦截器,自动注入创建人、创建时间、修改人、修改时间
- * @author Ning Zaichun
- */
- @Slf4j
- @Component
- @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
- public class MybatisInterceptor implements Interceptor {
-
- public static Field[] getAllFields(Object object) {
- Class<?> clazz = object.getClass();
- List<Field> fieldList = new ArrayList<>();
- while (clazz != null) {
- fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
- clazz = clazz.getSuperclass();
- }
- Field[] fields = new Field[fieldList.size()];
- fieldList.toArray(fields);
- return fields;
- }
-
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
- String sqlId = mappedStatement.getId();
- log.info("------sqlId------" + sqlId);
- //2022-09-17 17:16:50.714 INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor : ------sqlId------com.nzc.tree.mapper.CategoryMapper.updateById
- SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
- Object parameter = invocation.getArgs()[1];
- log.info("------sqlCommandType------" + sqlCommandType);
- //2022-09-17 17:16:50.714 INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor : ------sqlCommandType------UPDATE
-
- if (parameter == null) {
- return invocation.proceed();
- }
- if (SqlCommandType.INSERT == sqlCommandType) {
- Field[] fields = getAllFields(parameter);
- for (Field field : fields) {
- log.info("------field.name------" + field.getName());
- try {
- if ("createBy".equals(field.getName())) {
- field.setAccessible(true);
- Object local_createBy = field.get(parameter);
- field.setAccessible(false);
- if (local_createBy == null || local_createBy.equals("")) {
- field.setAccessible(true);
- field.set(parameter, "nzc-create");
- field.setAccessible(false);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- if (SqlCommandType.UPDATE == sqlCommandType) {
- Field[] fields = null;
- if (parameter instanceof ParamMap) {
- ParamMap<?> p = (ParamMap<?>) parameter;
- if (p.containsKey("et")) {
- parameter = p.get("et");
- } else {
- parameter = p.get("param1");
- }
- if (parameter == null) {
- return invocation.proceed();
- }
-
- fields = getAllFields(parameter);
- } else {
- fields = getAllFields(parameter);
- }
-
- for (Field field : fields) {
- log.info("------field.name------" + field.getName());
- try {
- if ("updateBy".equals(field.getName())) {
- field.setAccessible(true);
- field.set(parameter,"nzc-update");
- field.setAccessible(false);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- return invocation.proceed();
- }
-
- @Override
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
-
- @Override
- public void setProperties(Properties properties) {
- // TODO Auto-generated method stub
- }
- }
-
里面牵扯到的一些 Mybatis 中的一些对象,我没细说了,大家打印出来的时候都可以看到的。
测试:
执行的SQL语句打印信息
结果:
反射的特性,看似和我们天天写业务没啥关系,但是它其实一直伴随着我们,这也是 Java 开发者的基础知识,基础不牢,地动山摇~
优点: 反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。 它允许程序创建和控制任何类的对象,无需提前硬编码目标类;对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;
缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
1.反射大概比直接调用慢50~100倍,但是需要你在执行100万遍的时候才会有所感觉
2.判断一个函数的性能,你需要把这个函数执行100万遍甚至1000万遍
3.如果你只是偶尔调用一下反射,请忘记反射带来的性能影响
4.如果你需要大量调用反射,请考虑缓存。
5.你的编程的思想才是限制你程序性能的最主要的因素
仔细阅读下来你会发现,
正如文中所说,所谓Class对象,也称为类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、 类字段、类方法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获取 Java 类中的任意信息,能够对 Java 类的成员变量进行遍历,也能进行 Java 方法的调用,获取到类的任意信息。
反射也就是这样啦,不知道你会使用啦吗,如果你还没有的话,我觉得可以再读上一遍,顺带自己验证一遍,希望你能有所收获。