专栏名称: Java专栏
一个Java、Python、数据库、中间件、业内资讯、面试、学习资源等干货的知识分享社区。
目录
相关文章推荐
lolapola  ·  选择比努力更重要 - ... ·  2 天前  
LADYMAX  ·  Hugo Boss的亚太市场难题 ·  5 天前  
51好读  ›  专栏  ›  Java专栏

自定义注解!绝对是程序员装逼的利器!!

Java专栏  · 公众号  ·  · 2020-11-12 12:20

正文

点击上方 Java专栏 ”, 选择“置顶或者星标”


第一时间阅读精彩文章!


点击这段文字获取: 5个可以写到简历的项目实战视频教程(含源码)

作者 l Hollis

来源 l Hollis(ID:hollischuang)
相信很多人对Java中的注解都很熟悉,比如我们经常会用到的一些如@Override、@Autowired、@Service等,这些都是JDK或者诸如Spring这类框架给我们提供的。
在以往的面试过程中,我发现,关于注解的知识很多程序员都仅仅停留在使用的层面上,很少有人知道注解是如何实现的,更别提使用自定义注解来解决实际问题了。
但是其实,我觉得一个好的程序员的标准就是懂得如何优化自己的代码,那在代码优化上面,如何精简代码,去掉重复代码就是一个至关重要的话题,在这个话题领域,自定义注解绝对可以算得上是一个大大的功臣。
所以, 在我看来,会使用自定义注解 ≈ 好的程序员。
那么,本文,就来介绍几个,作者在开发中实际用到的几个例子,向你介绍下如何使用注解来提升你代码的逼格。



基本知识
在Java中,注解分为两种,元注解和自定义注解。
很多人误以为自定义注解就是开发者自己定义的,而其它框架提供的不算,但是其实上面我们提到的那几个注解其实都是自定义注解。
关于"元"这个描述,在编程世界里面有都很多,比如"元注解"、"元数据"、"元类"、"元表"等等,这里的"元"其实都是从meta翻译过来的。
一般我们把 元注解理解为描述注解的注解 元数据理解为描述数据的数据 元类理解为描述类的类
所以,在Java中,除了有限的几个固定的"描述注解的注解"以外,所有的注解都是自定义注解。
在JDK中提供了4个标准的用来对注解类型进行注解的注解类(元注解),他们分别是:

@Target

@Retention

@Documented

@Inherited

除了以上这四个,所有的其他注解全部都是自定义注解。
这里不准备深入介绍以上四个元注解的作用,大家可以自行学习。
本文即将提到的几个例子,都是作者在日常工作中真实使用到的场景,这例子有一个共同点,那就是都用到了Spring的AOP技术。
什么是AOP以及他的用法相信很多人都知道,这里也就不展开介绍了。



使用自定义注解做日志记录
不知道大家有没有遇到过类似的诉求,就是希望在一个方法的入口处或者出口处做统一的日志处理,比如记录一下入参、出参、记录下方法执行的时间等。
如果在每一个方法中自己写这样的代码的话,一方面会有很多代码重复,另外也容易被遗漏。
这种场景,就可以使用自定义注解+切面实现这个功能。
假设我们想要在一些web请求的方法上,记录下本次操作具体做了什么事情,比如新增了一条记录或者删除了一条记录等。
首先我们自定义一个注解:

/**

 * Operate Log 的自定义注解

 */


@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface  OpLog {

    /**

     * 业务类型,如新增、删除、修改

     * @return

     */


    public OpType opType();

    /**

     * 业务对象名称,如订单、库存、价格

     * @return

     */


    public String opItem();

    /**

     * 业务对象编号表达式,描述了如何获取订单号的表达式

     * @return

     */


    public String opItemIdExpression();

}

因为我们不仅要在日志中记录本次操作了什么,还需要知道被操作的对象的具体的唯一性标识,如订单号信息。
但是每一个接口方法的参数类型肯定是不一样的,很难有一个统一的标准,那么我们就可以借助Spel表达式,即在表达式中指明如何获取对应的对象的唯一性标识。
有了上面的注解,接下来就可以写切面了。主要代码如下:

/**

 * OpLog的切面处理类,用于通过注解获取日志信息,进行日志记录

 * @author Hollis

 */


@Aspect

@Component

public class OpLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(OpLogAspect.class);

    @Autowired

    HttpServletRequest request;

    @Around("@annotation(com.hollis.annotation.OpLog)")

    public Object log(ProceedingJoinPoint pjp) throws Exception {

        Method method = ((MethodSignature)pjp.getSignature()).getMethod();

        OpLog opLog = method.getAnnotation(OpLog.class);

        Object response = null;

        try {

            // 目标方法执行

            response = pjp.proceed();

        } catch (Throwable throwable) {

            throw new Exception(throwable);

        } 

        if (StringUtils.isNotEmpty(opLog.opItemIdExpression())) {

            SpelExpressionParser parser = new SpelExpressionParser();

            Expression expression = parser.parseExpression(opLog.opItemIdExpression());

            EvaluationContext context = new StandardEvaluationContext();

            // 获取参数值

            Object[] args = pjp.getArgs();

            // 获取运行时参数的名称

            LocalVariableTableParameterNameDiscoverer discoverer

                = new LocalVariableTableParameterNameDiscoverer();

            String[] parameterNames = discoverer.getParameterNames(method);

            // 将参数绑定到context中

            if (parameterNames != null) {

                for (int i = 0; i 
                    context.setVariable(parameterNames[i], args[i]);

                }

            }

            // 将方法的resp当做变量放到context中,变量名称为该类名转化为小写字母开头的驼峰形式

            if (response != null) {

                context.setVariable(

                    CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()),

                    response);

            }

            // 解析表达式,获取结果

            String itemId = String.valueOf(expression.getValue(context));

            // 执行日志记录

            handle(opLog.opType(), opLog.opItem(), itemId);

        }

        return response;

    }


    private void handle(OpType opType,  String opItem, String opItemId) {

      // 通过日志打印输出

      LOGGER.info("opType = " + opType.name() +",opItem = " +opItem + ",opItemId = " +opItemId);

    }

}

以上切面中,有几个点需要大家注意的:
  • 1、使用@Around注解来指定对标注了OpLog的方法设置切面。
  • 2、使用Spel的相关方法,通过指定的表示,从对应的参数中获取到目标对象的唯一性标识。
  • 3、再方法执行成功后,输出日志。
有了以上的切面及注解后,我们只需要在对应的方法上增加注解标注即可,如:

@RequestMapping(method = {RequestMethod.GETRequestMethod.POST})

@OpLog(opType = OpType.QUERY, opItem = "order", opItemIdExpression = "#id")

public @ResponseBody

HashMap view(@RequestParam(name = "id") String id)

    throws Exception {

}

上面这种是入参的参数列表中已经有了被操作的对象的唯一性标识,直接使用 #id






请到「今天看啥」查看全文