Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)3

简介: Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)

Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)2:https://developer.aliyun.com/article/1394601

3.3.3、获取方法上的注解信息

 @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=)
 }

3.3.4、获取方法参数及参数注解信息

不过在写项目时,有可能还会要获取方法参数上的注解,那该如何获取方法参数呢?又该如何获取方法参数的注解信息呢?

 @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);
 }
 ​
 ​

3.3.5、获取方法返回参数和方法权限修饰符

 @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
 }

3.4、反射获取运行时类信息、接口信息、包信息

3.4.1、获取运行时类的接口信息

 /**
      *  获取运行时类实现的接口
      */
 @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);
     }
 }

3.4.2、获取类所在包的信息

 /**
      * 获取运行时类所在的包
      */
 @Test
 public void test6() {
     Class clazz = Person.class;
     Package pack = clazz.getPackage();
     System.out.println(pack);
     // out:package com.nzc
 }

3.4.3、获取类上注解信息

 @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文件中,因此是无法通过反射获取到的。

image.png

@Data 也标明了它的存在级别为源码级别,而运行时存在注解@Retention@Retention(RetentionPolicy.RUNTIME)

3.5、反射获取运行时类的父类的泛型信息、接口泛型信息

 @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>
     }
 }

四、反射应用场景及实战案例

5.1、那到底什么时候会使用反射

一句话说它的应用场景就是:确定不下来到底使用哪个类的时候,比如你要开发一个通用工具类,为了达到通用性,传入的参数对象,一般都是无法限制的,这个时候就是要用到反射啦~。

反射的特征:动态性

5.2、AOP + 自定义注解 修改前端传递的参数信息

需求如下:我现在引入了一个第三方 jar 包,里面有一个 MyBatis-Plus 查询构造器,其中构造 LIKE条件查询的条件是当前端传过来的参数带有逗号时,拼接为LIKE查询条件。

关键代码:

标识在对象的某个成员属性上

 @Target({ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface LikeAnnotation {
 ​
     String value() default "";
 }
 ​

标识在Controller层上,以此来判断那些请求是需要被切入的。

 @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;
     }
 ​
 }

实现效果:

image.png


5.3、Mybatis 拦截器实现自动填充创建人、修改人信息

看似好像写业务的我们,没有怎么接触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 中的一些对象,我没细说了,大家打印出来的时候都可以看到的。

测试:

image.png

执行的SQL语句打印信息

image.png

结果:

image.png

反射的特性,看似和我们天天写业务没啥关系,但是它其实一直伴随着我们,这也是 Java 开发者的基础知识,基础不牢,地动山摇~

五、反射的优缺点

优点: 反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。 它允许程序创建和控制任何类的对象,无需提前硬编码目标类;对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法

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

以下文字来自于 反射是否真的会让你的程序性能降低吗?

1.反射大概比直接调用慢50~100倍,但是需要你在执行100万遍的时候才会有所感觉

2.判断一个函数的性能,你需要把这个函数执行100万遍甚至1000万遍

3.如果你只是偶尔调用一下反射,请忘记反射带来的性能影响

4.如果你需要大量调用反射,请考虑缓存。

5.你的编程的思想才是限制你程序性能的最主要的因素

小结

仔细阅读下来你会发现,

正如文中所说,所谓Class对象,也称为类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、 类字段、类方法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获取 Java 类中的任意信息,能够对 Java 类的成员变量进行遍历,也能进行 Java 方法的调用,获取到类的任意信息。

反射也就是这样啦,不知道你会使用啦吗,如果你还没有的话,我觉得可以再读上一遍,顺带自己验证一遍,希望你能有所收获。

后记

其实想写这篇文章时间已经不短了,但偶尔发作(我又经常偶尔),所以总是一拖再拖,终于把它完成了,我也不知道你会不会读到此处,看到我发的牢骚。

只是非常简单的希望每一位阅读者能够有所收获,这应该是我持续写文的快乐吧~

今天又是好值的一天啊~

各位下次见!

明人不说暗话,我还是想要一键三连的,哈哈,走过路过还是可以点点赞的啦,要是喜欢,也可以给个关注啦,有收获可以再点点收藏的😊


目录
相关文章
|
1月前
|
SQL XML Java
mybatis Mapper的概念与实战
MyBatis 是一个流行的 Java 持久层框架,它提供了对象关系映射(ORM)的功能,使得Java对象和数据库中的表之间的映射变得简单。在MyBatis中,Mapper是一个核心的概念,它定义了映射到数据库操作的接口。简而言之,Mapper 是一个接口,MyBatis 通过这个接口与XML映射文件或者注解绑定,以实现对数据库的操作。
38 1
|
21天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
4天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
24 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——实体层(User.java)
mybatis简单案例源码详细【注释全面】——实体层(User.java)
13 0
|
2天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
5天前
|
Java 数据库
Javaweb之SpringBootWeb案例之AOP案例的详细解析
Javaweb之SpringBootWeb案例之AOP案例的详细解析
11 0
|
8天前
|
存储 Java 数据库连接
java DDD 领域驱动设计思想的概念与实战
【4月更文挑战第19天】在Java开发中,领域驱动设计(Domain-Driven Design, DDD) 是一种软件设计方法论,强调以领域模型为中心的软件开发。这种方法通过丰富的领域模型来捕捉业务领域的复杂性,并通过软件满足核心业务需求。领域驱动设计不仅是一种技术策略,而且还是一种与业务专家紧密合作的思维方式
30 2
|
18天前
|
Java API 开发者
Java 8新特性之函数式编程实战
【4月更文挑战第9天】本文将深入探讨Java 8的新特性之一——函数式编程,通过实例演示如何运用Lambda表达式、Stream API等技术,提高代码的简洁性和执行效率。
|
29天前
|
存储 安全 Java
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(不可变集合篇)
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(不可变集合篇)
30 1
|
29天前
|
Java API Apache
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(基础编程篇)
【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(基础编程篇)
43 0