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 方法的调用,获取到类的任意信息。

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

后记

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

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

今天又是好值的一天啊~

各位下次见!

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


目录
相关文章
|
5天前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
17天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
48 3
|
19天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
36 2
|
20天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
13 2
|
1月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
300 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
|
25天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
16 1
|
1月前
|
Java 数据库
案例一:去掉数据库某列中的所有英文,利用java正则表达式去做,核心:去掉字符串中的英文
这篇文章介绍了如何使用Java正则表达式从数据库某列中去除所有英文字符。
46 15
|
1月前
|
缓存 Java 数据库连接
使用MyBatis缓存的简单案例
MyBatis 是一种流行的持久层框架,支持自定义 SQL 执行、映射及复杂查询。本文介绍了如何在 Spring Boot 项目中集成 MyBatis 并实现一级和二级缓存,以提高查询性能,减少数据库访问。通过具体的电商系统案例,详细讲解了项目搭建、缓存配置、实体类创建、Mapper 编写、Service 层实现及缓存测试等步骤。
|
1月前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
【10月更文挑战第8天】本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
35 5
|
2月前
|
SQL Java 数据库连接
解决mybatis-plus 拦截器不生效--分页插件不生效
本文介绍了在使用 Mybatis-Plus 进行分页查询时遇到的问题及解决方法。依赖包包括 `mybatis-plus-boot-starter`、`mybatis-plus-extension` 等,并给出了正确的分页配置和代码示例。当分页功能失效时,需将 Mybatis-Plus 版本改为 3.5.5 并正确配置拦截器。
607 6
解决mybatis-plus 拦截器不生效--分页插件不生效