文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

天天都在使用的 Java 注解,你真的了解它吗?

2024-12-03 17:09

关注

本文转载自微信公众号「Java极客技术」,作者鸭血粉丝 。转载本文请联系Java极客技术公众号。 

Hello,大家好,我是阿粉,Java 的注解相信大家天天都在用,但是关于注解的原理,大家都了解吗?这篇文章通过意见简单的示例给大家演示一下注解的使用和原理。

Java 元注解

注解(Annotation)是一种可以放在 Java 类上,方法上,属性上,参数前面的一种特殊的注释,用来注释注解的注解叫做元注解。元注解我们平常不会编写,只需要添加到我们自己编写的注解上即可,。

Java 自带的常用的元注解有@Target,@Retention,@Documented,@Inherited 分别有如下含义

  1. @Target:标记这个注解使用的地方,取值范围在枚举 java.lang.annotation.ElementType:TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE。
  2. @Retention :标识这个注解的生命周期,取值范围在枚举 java.lang.annotation.RetentionPolicy,SOURCE,CLASS,RUNTIME,一般定义的注解都是在运行时使用,所有要用 @Retention(RetentionPolicy.RUNTIME);
  3. @Documented:表示注解是否包含到文档中。
  4. @Inherited :使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效。

定义注解

上面介绍了几个元注解,下面我们定义一个日志注解来演示一下,我们通过定义一个名为OperationLog 的注解来记录一些通用的操作日志,比如记录什么时候什么人查询的哪个表的数据或者新增了什么数据。编写注解我们用的是 @interface 关键字,相关代码如下:

  1. package com.api.annotation; 
  2.  
  3. import java.lang.annotation.*; 
  4.  
  5.  
  6. @Target({ElementType.METHOD}) 
  7. @Retention(RetentionPolicy.RUNTIME) 
  8. @Documented 
  9. public @interface OperationLog { 
  10.  
  11.      
  12.     String type() default OperationType.SELECT
  13.  
  14.      
  15.     String desc() default ""
  16.  
  17.      
  18.     String path() default ""
  19.  
  20.      
  21.     boolean write() default true
  22.  
  23.      
  24.     boolean auth() default true
  25.     
  26.     String primaryKey() default ""
  27.  
  28.      
  29.     Class defaultServiceClass() default Object.class; 

说明

上面的注解,我们增加了@Target({ElementType.METHOD}) , @Retention(RetentionPolicy.RUNTIME), @Documented 三个元注解,表示我们这个注解是使用在方法上的,并且生命周期是运行时,而且可以记录到文档中。然后我们可以看到定义注解采用的u是@interface 关键字,并且我们给这个注解定义了几个属性,同时设置了默认值。主要注意的是平时我们编写的注解一般必须设置@Target和@Retention,而且 @Retention一般设置为RUNTIME,这是因为我们自定义的注解通常要求在运行期读取,另外一般情况下,不必写@Inherited。

使用

上面的动作只是把注解定义出来了,但是光光定义出来是没有用的,必须有一个地方读取解析,才能提现出注解的价值,我们就采用 Spring 的 AOP 拦截这个注解,将所有携带这个注解的方法所进行的操作都记录下来。

  1. package com.api.config; 
  2.  
  3. import lombok.extern.slf4j.Slf4j; 
  4. import org.aspectj.lang.ProceedingJoinPoint; 
  5. import org.aspectj.lang.annotation.Around; 
  6. import org.aspectj.lang.annotation.Aspect; 
  7. import org.aspectj.lang.annotation.Pointcut; 
  8. import org.aspectj.lang.reflect.MethodSignature; 
  9. import org.springframework.beans.factory.annotation.Autowired; 
  10. import org.springframework.core.annotation.Order
  11. import org.springframework.stereotype.Component; 
  12. import org.springframework.web.bind.annotation.GetMapping; 
  13. import org.springframework.web.bind.annotation.PostMapping; 
  14. import org.springframework.web.bind.annotation.RequestMapping; 
  15.  
  16. import javax.servlet.http.HttpServletRequest; 
  17. import java.lang.reflect.Field; 
  18. import java.lang.reflect.Method; 
  19. import java.util.*; 
  20.  
  21.  
  22. @Aspect 
  23. @Component 
  24. @Order(-5) 
  25. @Slf4j 
  26. public class LogAspect { 
  27.      
  28.     @Pointcut("within(com.xx.yy.controller..*) && @annotation(com.api.annotation.OperationLog)"
  29.     public void logAspect() { 
  30.     } 
  31.  
  32.      
  33.     @Around("logAspect()"
  34.     public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 
  35.         Object proceed = null
  36.         String classType = joinPoint.getTarget().getClass().getName(); 
  37.         Class targetCls = Class.forName(classType); 
  38.         MethodSignature ms = (MethodSignature) joinPoint.getSignature(); 
  39.         Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes()); 
  40.         OperationLog operation = targetMethod.getAnnotation(OperationLog.class); 
  41.         if (null != operation && operation.write()) { 
  42.             SysMenuOpLogEntity opLogEntity = new SysMenuOpLogEntity(); 
  43.             StringBuilder change = new StringBuilder(); 
  44.             if (StrUtil.isNotBlank(operation.type())) { 
  45.                 switch (operation.type()) { 
  46.                     case OperationType.ADD
  47.                         proceed = joinPoint.proceed(); 
  48.                         String addString = genAddData(targetCls, operation.defaultServiceClass(), joinPoint.getArgs()); 
  49.                         opLogEntity.setAfterJson(addString); 
  50.                         change.append(OperationType.ADD); 
  51.                         break; 
  52.                     case OperationType.DELETE
  53.                         String deleteString = autoQueryDeletedData(targetCls, operation.primaryKey(), operation.defaultServiceClass(), joinPoint.getArgs()); 
  54.                         opLogEntity.setBeforeJson(deleteString); 
  55.                         change.append(OperationType.DELETE); 
  56.                         proceed = joinPoint.proceed(); 
  57.                         break; 
  58.                     case OperationType.EDIT: 
  59.                         change.append(OperationType.EDIT); 
  60.                         setOpLogEntity(opLogEntity, targetCls, operation.primaryKey(), operation.defaultServiceClass(), joinPoint.getArgs()); 
  61.                         proceed = joinPoint.proceed(); 
  62.                         break; 
  63.                     case OperationType.SELECT
  64.                         opLogEntity.setBeforeJson(getQueryString(targetCls, operation.defaultServiceClass(), joinPoint.getArgs())); 
  65.                         change.append(operation.type()); 
  66.                         proceed = joinPoint.proceed(); 
  67.                         break; 
  68.                     case OperationType.SAVE: 
  69.                         savedDataOpLog(opLogEntity, targetCls, operation.primaryKey(), operation.defaultServiceClass(), joinPoint.getArgs()); 
  70.                         change.append(operation.type()); 
  71.                         proceed = joinPoint.proceed(); 
  72.                         break; 
  73.                     case OperationType.EXPORT: 
  74.                     case OperationType.DOWNLOAD: 
  75.                         change.append(operation.type()); 
  76.                         proceed = joinPoint.proceed(); 
  77.                         break; 
  78.                     default
  79.                 } 
  80.                 opLogEntity.setExecType(operation.type()); 
  81.             } 
  82.             StringBuilder changing = new StringBuilder(); 
  83.             if (StrUtil.isNotBlank(opLogEntity.getExecType())) { 
  84.                 if (operation.auth()) { 
  85.                     LoginUserVO loginUser = getLoginUser(); 
  86.                     if (null != loginUser) { 
  87.                         opLogEntity.setUserId(loginUser.getUserId()); 
  88.                         opLogEntity.setUserName(loginUser.getUserName()); 
  89.                         changing.append(loginUser.getUserName()).append("-"); 
  90.                     } else { 
  91.                         log.error("用户未登录"); 
  92.                     } 
  93.                 } 
  94.                 opLogEntity.setCreateTime(DateUtils.getCurDate()); 
  95.                 opLogEntity.setRemark(getOperateMenuName(targetMethod, operation.desc())); 
  96.                 opLogEntity.setPath(getPath(targetMethod, targetMethod.getName())); 
  97.                 opLogEntity.setChanging(changing.append(change).toString()); 
  98.                 menuOpLogService.save(opLogEntity); 
  99.             } 
  100.         } 
  101.         return proceed; 
  102.     } 
  103.  
  104.      
  105.     private String queryByCurrentUserId(Class targetCls, Class defaultServiceClass) throws Exception { 
  106.         BaseService baseService = getBaseService(targetCls, defaultServiceClass); 
  107.         LoginUserVO loginUser = dspBaseService.getLoginUser(); 
  108.         if (null != loginUser) { 
  109.             Object o = baseService.queryId(loginUser.getUserId()); 
  110.             return JsonUtils.obj2Json(o); 
  111.         } 
  112.         return null
  113.     } 
  114.  
  115.      
  116.     private String getQueryString(Class targetCls, Class defaultServiceClass, Object[] args) { 
  117.         if (args.length > 0) { 
  118.             Class entityClz = getEntityClz(targetCls, defaultServiceClass); 
  119.             for (Object arg : args) { 
  120.                 if (arg.getClass().equals(entityClz) || arg instanceof BaseModel) { 
  121.                     return JsonUtils.obj2Json(arg); 
  122.                 } 
  123.             } 
  124.         } 
  125.         return null
  126.     } 
  127.  
  128.      
  129.     private void savedDataOpLog(SysMenuOpLogEntity opLogEntity, Class targetCls, String primaryKey, Class defaultServiceClass, Object[] args) throws Exception { 
  130.         Class entityClz = getEntityClz(targetCls, defaultServiceClass); 
  131.         BaseService baseService = getBaseService(targetCls, defaultServiceClass); 
  132.         for (Object arg : args) { 
  133.             if (arg.getClass().equals(entityClz)) { 
  134.                 if (StrUtil.isNotBlank(primaryKey)) { 
  135.                     Field declaredField = entityClz.getDeclaredField(primaryKey); 
  136.                     declaredField.setAccessible(true); 
  137.                     Object primaryKeyValue = declaredField.get(arg); 
  138.                     //if primary key is not null that means edit, otherwise is add 
  139.                     if (null != primaryKeyValue) { 
  140.                         //query data by primary key 
  141.                         Object o = baseService.queryId(primaryKeyValue); 
  142.                         opLogEntity.setBeforeJson(JsonUtils.obj2Json(o)); 
  143.                     } 
  144.                 } 
  145.                 opLogEntity.setAfterJson(JsonUtils.obj2Json(arg)); 
  146.             } 
  147.         } 
  148.     } 
  149.  
  150.      
  151.     private void setOpLogEntity(SysMenuOpLogEntity opLogEntity, Class targetCls, String primaryKey, Class defaultServiceClass, Object[] args) throws Exception { 
  152.         Map saveMap = autoQueryEditedData(targetCls, primaryKey, defaultServiceClass, args); 
  153.         if (null != saveMap) { 
  154.             if (saveMap.containsKey(ASPECT_LOG_OLD_DATA)) { 
  155.                 opLogEntity.setBeforeJson(saveMap.get(ASPECT_LOG_OLD_DATA)); 
  156.             } 
  157.             if (saveMap.containsKey(ASPECT_LOG_NEW_DATA)) { 
  158.                 opLogEntity.setBeforeJson(saveMap.get(ASPECT_LOG_NEW_DATA)); 
  159.             } 
  160.         } 
  161.     } 
  162.  
  163.      
  164.     private Map autoQueryEditedData(Class targetCls, String primaryKey, Class defaultServiceClass, Object[] args) throws Exception { 
  165.         if (StrUtil.isBlank(primaryKey)) { 
  166.             throw new Exception(); 
  167.         } 
  168.         Map map = new HashMap<>(16); 
  169.         Class entityClz = getEntityClz(targetCls, defaultServiceClass); 
  170.         BaseService baseService = getBaseService(targetCls, defaultServiceClass); 
  171.         for (Object arg : args) { 
  172.             if (arg.getClass().equals(entityClz)) { 
  173.                 Field declaredField = entityClz.getDeclaredField(primaryKey); 
  174.                 declaredField.setAccessible(true); 
  175.                 Object primaryKeyValue = declaredField.get(arg); 
  176.                 //query the data before edit 
  177.                 if (null != primaryKeyValue) { 
  178.                     //query data by primary key 
  179.                     Object o = baseService.queryId(primaryKeyValue); 
  180.                     map.put(ASPECT_LOG_OLD_DATA, JsonUtils.obj2Json(o)); 
  181.                     map.put(ASPECT_LOG_NEW_DATA, JsonUtils.obj2Json(arg)); 
  182.                     return map; 
  183.                 } 
  184.             } 
  185.         } 
  186.         return null
  187.     } 
  188.  
  189.      
  190.     private String genAddData(Class targetCls, Class defaultServiceClass, Object[] args) throws Exception { 
  191.         List parameter = new ArrayList<>(); 
  192.         for (Object arg : args) { 
  193.             if (arg instanceof HttpServletRequest) { 
  194.             } else { 
  195.                 parameter.add(arg); 
  196.             } 
  197.         } 
  198.         return JsonUtils.obj2Json(parameter); 
  199.     } 
  200.  
  201.      
  202.     private String autoQueryDeletedData(Class targetCls, String primaryKey, Class defaultServiceClass, Object[] ids) throws Throwable { 
  203.         if (StrUtil.isBlank(primaryKey)) { 
  204.             throw new OriginException(TipEnum.LOG_ASPECT_PRIMARY_KEY_NOT_EXIST); 
  205.         } 
  206.         //get service 
  207.         BaseService baseService = getBaseService(targetCls, defaultServiceClass); 
  208.         //get entity 
  209.         Class entityClz = getEntityClz(targetCls, defaultServiceClass); 
  210.         //query deleted data by primary key 
  211.         Query query = new Query(); 
  212.         WhereOperator whereOperator = new WhereOperator(entityClz); 
  213.         Set set = new HashSet<>(Arrays.asList((Object[]) ids[0])); 
  214.         whereOperator.and(primaryKey).in(set.toArray()); 
  215.         query.addWhereOperator(whereOperator); 
  216.         List list = baseService.queryList(query); 
  217.         return JsonUtils.obj2Json(list); 
  218.     } 
  219.  
  220.  
  221.      
  222.     private BaseService getBaseService(Class targetCls, Class defaultServiceClass) throws Exception { 
  223.         //根据类名拿到对应的 service 名称 
  224.         String serviceName = getServiceName(targetCls, defaultServiceClass); 
  225.         BaseService baseService; 
  226.         if (null != defaultServiceClass) { 
  227.             baseService = (BaseService) ApplicationContextProvider.getBean(serviceName, defaultServiceClass); 
  228.         } else { 
  229.             Class type = targetCls.getDeclaredField(serviceName).getType(); 
  230.             baseService = (BaseService) ApplicationContextProvider.getBean(serviceName, type); 
  231.         } 
  232.         return baseService; 
  233.     } 
  234.  
  235.      
  236.     private String getServiceName(Class targetCls, Class defaultServiceClass) { 
  237.         if (null != defaultServiceClass && Object.class != defaultServiceClass) { 
  238.             return StrUtil.left(defaultServiceClass.getSimpleName(), 1).toLowerCase() + defaultServiceClass.getSimpleName().substring(1); 
  239.         } 
  240.         return StrUtil.left(targetCls.getSimpleName(), 1).toLowerCase() + targetCls.getSimpleName().substring(1).replace("Controller""Service"); 
  241.     } 
  242.  
  243.  
  244.      
  245.     private Class getEntityClz(Class targetCls, Class defaultServiceClass) { 
  246.         try { 
  247.             Class type; 
  248.             if (null != defaultServiceClass && Object.class != defaultServiceClass) { 
  249.                 type = defaultServiceClass; 
  250.             } else { 
  251.                 type = targetCls.getDeclaredField(getServiceName(targetCls, null)).getType(); 
  252.             } 
  253.             String entityName = type.getName().replace("service""entity").replace("Service""Entity"); 
  254.             Class entityClz = Class.forName(entityName); 
  255.             return entityClz; 
  256.         } catch (Exception e) { 
  257.             log.error("获取 class 失败"); 
  258.         } 
  259.         return null
  260.     } 
  261.  
  262.  
  263.      
  264.     private String getPath(Method targetMethod, String defaultPath) { 
  265.         String path = defaultPath; 
  266.         PostMapping postMapping = targetMethod.getAnnotation(PostMapping.class); 
  267.         GetMapping getMapping = targetMethod.getAnnotation(GetMapping.class); 
  268.         RequestMapping requestMapping = targetMethod.getAnnotation(RequestMapping.class); 
  269.         if (null != postMapping) { 
  270.             path = postMapping.value()[0]; 
  271.         } else if (null != getMapping) { 
  272.             path = getMapping.value()[0]; 
  273.         } else if (null != requestMapping) { 
  274.             path = requestMapping.value()[0]; 
  275.         } 
  276.         return path; 
  277.     } 
  278.  
  279. 上面的代码中我们定义了一个切面指定需要拦截的包名和注解,因为涉及到很多业务相关的代码,所以不能完整的提供出来,但是整个思路就是这样的,在每种操作类型前后将需要记录的数据查询出来进行记录。代码很长主要是用来获取相应的参数值的,大家使用的时候可以根据自己的需要进行取舍。比如在新增操作的时候,我们将新增的数据进行记录下来;编辑的时候将编辑前的数据查询出来和编辑后的数据一起保存起来,删除也是一样的,在删除前将数据查询出来保存到日志表中。

    同样导出和下载都会记录相应信息,整个操作类型的代码如下:

    1. package com.api.annotation; 
    2.  
    3.  
    4. public interface OperationType { 
    5.      
    6.     String ADD = "add"
    7.      
    8.     String DELETE = "delete"
    9.      
    10.     String EDIT = "edit"
    11.      
    12.     String SELECT = "select"
    13.  
    14.      
    15.     String SAVE = "save"
    16.  
    17.      
    18.     String EXPORT = "export"
    19.  
    20.      
    21.     String DOWNLOAD = "download"
    22.  

    后续在使用的时候只需要在需要的方法上加上注解,填上相应的参数即可@OperationLog(desc = "查询单条记录", path = "/data")

    总结

    注解一个我们天天再用的东西,虽然不难,但是我们却很少自己去写注解的代码,通过这篇文章能给大家展示一下注解的使用逻辑,希望对大家有帮助。Spring 中的各种注解本质上也是这种逻辑都需要定义使用和解析。很多时候我们可以通过自定义注解去解决很多场景,比如日志,缓存等。

     

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    软考中级精品资料免费领

    • 历年真题答案解析
    • 备考技巧名师总结
    • 高频考点精准押题
    • 资料下载
    • 历年真题
    • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

      难度     813人已做
      查看
    • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

      难度     354人已做
      查看
    • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

      难度     318人已做
      查看
    • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

      难度     435人已做
      查看
    • 2024年上半年系统架构设计师考试综合知识真题

      难度     224人已做
      查看

    相关文章

    发现更多好内容

    猜你喜欢

    AI推送时光机
    咦!没有更多了?去看看其它编程学习网 内容吧