在之前文章: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})
@Retention: 注解指定了被修饰的注解的生命周期。语法如下:
@Retention(value = RetentionPolicy.RUNTIME)
剩下两种类型的注解我们日常用的不多,也比较简单,需要知道他们各自的作用即可:
假设需求是每个方法调用的时候,我们都希望打印出方法名称,并且打印出发放调用的耗时时间。每个方法都去写代码实现就会显得比较耗时和臃肿。
这个时候我们自定义一个注解,然后只需要在有这个需求的方法上加上注解就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自定义注解的使用场景有很多,多数都是用于一些增强功能,比如上面我们举例的用于日志打印,还有常用的如统计方法耗时、多数据源切换、防重等等。
================================================================================================
以上就是本次的全部内容,都看到这里了,如果对你有帮助,麻烦点个赞+收藏+关注,一键三连啦~
欢迎下方关注我的公众号:程序员杨叔,各类文章都会第一时间在上面发布,持续分享全栈测试知识干货,你的支持就是作者更新最大的动力~