Java AOP自定义注解
一、背景
在之前文章:Java注解详解中,主要介绍了注解的含义、作用、以及常用的各类注解。今天主要介绍在Springboot中如何实现一个自定义注解,通过自定义注解去实现一些定制化的需求。
二、了解元注解
『元注解』是用于修饰注解的注解,通常用在注解的定义上,例如:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
这是我们 @Override 注解的定义,你可以看到其中的 @Target,@Retention 两个注解就是我们所谓的『元注解』,『元注解』一般用于指定某个注解生命周期以及作用目标等信息。
JAVA 中有以下几个『元注解』:
@Target:注解的作用目标
@Retention:注解的生命周期
@Documented:注解是否应当被包含在 JavaDoc 文档中
@Inherited:是否允许子类继承该注解
@Target:用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。语法如下:
@Target(value = {ElementType.METHOD})
- ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
- ElementType.FIELD:允许作用在属性字段上
- ElementType.METHOD:允许作用在方法上
- ElementType.PARAMETER:允许作用在方法参数上
- ElementType.CONSTRUCTOR:允许作用在构造器上
- ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
- ElementType.ANNOTATION_TYPE:允许作用在注解上
- ElementType.PACKAGE:允许作用在包上
@Retention: 注解指定了被修饰的注解的生命周期。语法如下:
@Retention(value = RetentionPolicy.RUNTIME)
- RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
- RetentionPolicy.CLASS:注解只被保留到编译进行的时候,不会被加载到JVM中
- RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,会被加载到JVM中,所以程序运行时可以获取到它
剩下两种类型的注解我们日常用的不多,也比较简单,需要知道他们各自的作用即可:
- @Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。
- @Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
三、创建Springboot AOP自定义注解
假设需求是每个方法调用的时候,我们都希望打印出方法名称,并且打印出发放调用的耗时时间。每个方法都去写代码实现就会显得比较耗时和臃肿。
这个时候我们自定义一个注解,然后只需要在有这个需求的方法上加上注解就OK了,这样实现起来就会非常方便。
AOP:在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中(AOP),我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、日志操作等公用操作处理的过程就是面向切面编程的思想。
新建annotation包,然后下面新建InterfaceLog注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 接口日志注解* @see InterfaceLogAspect* */@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InterfaceLog {String value() default "";
}
定义了该注解是运行时生效,注解作用在method方法上。
新建InterfaceLogAspect,通过AOP切面实现自定义注解InterfaceLog的代码逻辑:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 该类中编写InterfaceLog注解的代码逻辑*/@Aspect
@Component
@Slf4j
public class InterfaceLogAspect {private long startTime;private long endTime;/*** PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名* 切面最主要的就是切点,所有的故事都围绕切点发生* logPointCut()代表切点名称*/@Pointcut("@annotation(InterfaceLog)")private void logPointCut(){}/*** 目标方法调用之前执行* 注意这里不能使用 ProceedingJoinPoint* @param joinPoint*/@Before("logPointCut()")public void doBefore(JoinPoint joinPoint){log.info("Before Test");}/*** 目标方法调用之后执行* 注意这里不能使用 ProceedingJoinPoint* @param joinPoint*/@After("logPointCut()")public void doAfter(JoinPoint joinPoint){log.info("End Test");}/*** 环绕通知* @param proceedingJoinPoint*/@Around("logPointCut()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//方法调用前MethodSignature methodSignature =(MethodSignature) proceedingJoinPoint.getSignature();//获取方法名称String methodName=methodSignature.getName();//获取@InterfaceLog注解入参的值Method method = methodSignature.getMethod();InterfaceLog interfaceLog=method.getAnnotation(InterfaceLog.class);String value=interfaceLog.value();startTime=System.currentTimeMillis();//根据入参值不同,使用不同的日志打印级别打印日志if(value==null || value.equals("")){log.info("==================开始打印日志==================");log.info("方法名为:"+methodName);}else if(value.equals("info")){log.info("==================开始打印日志==================");log.info("方法名为:"+methodName);}else if(value.equals("warn")){log.warn("==================开始打印日志==================");log.warn("方法名为:"+methodName);}else if(value.equals("error")){log.error("==================开始打印日志==================");log.error("方法名为:"+methodName);}else{log.error("自定义注解入参不正确!");}//继续执行方法Object result=proceedingJoinPoint.proceed();//方法调用后,打印方法耗时endTime = System.currentTimeMillis();if(value==null || value.equals("")){log.info("方法耗时为:"+(endTime -startTime));log.info("==================结束打印日志==================");}else if(value.equals("info")){log.info("方法耗时为:"+(endTime -startTime));log.info("==================结束打印日志==================");}else if(value.equals("warn")){log.warn("方法耗时为:"+(endTime -startTime));log.warn("==================结束打印日志==================");}else if(value.equals("error")){log.error("方法耗时为:"+(endTime -startTime));log.error("==================结束打印日志==================");}else{log.error("自定义注解入参不正确!");}return result;}
}
切面类实现了记录方法调用前的时间、调用后的时间,两者相减得到方法的执行耗时。获取注解的入参value的值,根据入参的值来决定打印哪种级别的日志。
UserController类的register方法上,加上上面我们自定义的注解@InterfaceLog,注解的参数value设置值为warn:
@InterfaceLog(value = "warn")
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String register(String name, Integer age, String pwd, Model model, HttpServletRequest request, HttpServletResponse response)throws Exception{try{//打印日志log.info(name+","+age+","+pwd);//获取注册的结果User result = userServices.register(name, age, pwd);if(result.isSuccess()){//将结果存到model里面,用于前端view层展示model.addAttribute("result",result);//跳转至注册结果页面return "/registerResult";}else{response.setContentType("application/json; charset=utf-8");response.getWriter().print("{\"code\":\"0002\",\"msg\":\"用户名已存在,注册失败!\"}");}}catch (Exception e){e.printStackTrace();}return null;
}
启动项目,调用register接口,可以看到自定义注解正常生效,doAround中打印的日志级别为注解入参传的Warn级别:
设置@InterfaceLog(value = “error”), 调用register接口,系统就打印error级别的日志:
设置@InterfaceLog不传参, 默认参数就是空,调用register接口,系统就默认打印info级别的日志:
Java AOP自定义注解的使用场景有很多,多数都是用于一些增强功能,比如上面我们举例的用于日志打印,还有常用的如统计方法耗时、多数据源切换、防重等等。
================================================================================================
以上就是本次的全部内容,都看到这里了,如果对你有帮助,麻烦点个赞+收藏+关注,一键三连啦~
欢迎下方关注我的公众号:程序员杨叔,各类文章都会第一时间在上面发布,持续分享全栈测试知识干货,你的支持就是作者更新最大的动力~
Java AOP自定义注解相关推荐
- spring AOP自定义注解方式实现日志管理
转:spring AOP自定义注解方式实现日志管理 今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接 ...
- Spring AOP自定义注解并获取注解的参数
环境 springboot:1.5 Intellij IDEA:2021.1 序言 最近有个需求,要做方法层面的权限控制.以前在公司使用的是spring security,然后使用注解 如下: @Pr ...
- JPOM - AOP+自定义注解实现操作日志记录
文章目录 地址 版本 源码解析-AOP+自定义注解实现操作日志记录 地址 Gitee: https://gitee.com/dromara/Jpom 官网: https://jpom.io/ 一款简而 ...
- springboot+aop+自定义注解,打造通用的全局异常处理和参数校验切面(通用版)
springboot+aop+自定义注解,打造通用的全局异常处理和参数校验切面(通用版) 参考文章: (1)springboot+aop+自定义注解,打造通用的全局异常处理和参数校验切面(通用版) ( ...
- java之自定义注解的完整使用
小坏java自定义注解的完整使用 一.何为java注解之道 1.java 注解的理解之道 2.java 注解的使用示例之道 3.Java 如何自定义注解之道 4.java 元注解之道 5.java 如 ...
- spring aop 自定义注解配合swagger注解保存操作日志到mysql数据库含(源码)
spring aop 自定义注解保存操作日志到mysql数据库 一.思路 二.自定义注解 三.编写操作日志 四.编写操作日志切面\增强 五.使用 六.`注意` 一.思路 利用spring aop 对方 ...
- Java中自定义注解的使用
Java中自定义注解的使用 一般来说,市面上有一些的框架,企业都不会直接拿过来就用,通过会做二次开发或封装,为了更加适配自己的开发规范和业务.那么在封装或适配的过程中,自定义注解就起着比较重要的作用. ...
- Java实现自定义注解
前言 (1)Java实现自定义注解其实很简单,跟类定义差不多,只是属性的定义可能跟我们平时定义的属性略有不同,这里会给大家详解,先来看代码: @Target(ElementType.FIELD) @R ...
- AOP+自定义注解实现字典翻译
目录 需求: 结果展示 : 代码实现 : 需求: 一般情况下数据库表中字段对应的是字典值,但在查询或导出时需要展示字典名,若每次字典转换时都需要关联字典表查询,而且导出时还需要在做一次数据转换,这样处 ...
最新文章
- 天平游码读数例题_量筒、天平经典习题
- VS2017 Cordova Ionic2 移动开发-环境搭建
- Cookie、Session、Token那点事儿
- mysql root 权限注入_Mysql注入root权限直接写一句话马
- 全球及中国七氟二甲基辛二酸铜(II)行业竞争策略与投资前景研究报告2022版
- 随机手机号码_骗妹子手机号码,还记得iPhone自带计算器的小魔术吗?,看教程...
- 【网络编程】用Socket实现聊天小程序
- struts2配置中Action的name 和package的name和namespace作用
- 4.maven中常用的构建命令
- python机器视觉教材_基于Python的机器视觉实验教学平台设计
- php7.0不出结合项,帝国CMS结合项提示“您来自的链接不存在”
- java 的数据类型转换_java数据类型转换汇总
- 力扣——最长公共前缀
- NYOJ31 - 5个数求最值
- 2-9 装箱问题 (20 分)
- 安装mysql提示:由于找不到 MSVCR100.dll
- vivo平台sdk php说明书,vivo
- jy-12-SPRINGMYBATIS02——云笔记07-刘苍松
- postgresql实现存在则更新,不存在则插入
- 无锡室内设计——流行的几种室内装饰风格
热门文章
- 【无标题】ChatGPT的官网是多少,在国内能使用吗
- 什么是VOLTE(2)
- 魅族手机安装Google Play
- 树莓派pico 最新固件地址以及无法进入BOOT模式(U盘挂载)解决方法
- 计算机注销之后一直黑屏,Win7旗舰版电脑注销完黑屏怎么解决?
- [转载]上班的同志你看过来
- C++11 的 运行时类型识别type_info
- SSL2811 2017年10月30日提高组T2 摘Galo(树形dp)
- 详解c语言main函数、printf函数、scanf函数与va家族
- PAT 乙级 1040 有几个PAT (25分)