简介】
项目中经常使用自定义注解和切面,实现操作日志、权限、统计执行时间等功能,本文以操作日志为例
自定义注解类编写的一些规则:
1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
2. 参数成员只能用public或默认(default)这两个访问权修饰
3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
5. 注解也可以没有定义成员, 不过这样注解就没啥用了
1.首先自己创建一个maven的springboot project 在线创建可参考在线编辑swagger编辑器创建
2.创建工程后,编辑pom文件,文件中添加对aop的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.定义注解:
其中注解定以前,先了解定义注解的几种解释:
/**
* java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
* @Documented:注解是否将包含在JavaDoc中
* @Retention:什么时候使用该注解 指明修饰的注解的生存周期,即会保留到哪个阶段。
* SOURCE:源码级别保留,编译后即丢弃。
CLASS:编译级别保留,编译后的class文件中存在,在jvm运行时丢弃,这是默认值。
RUNTIME: 运行级别保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用。
* @Target:注解用于什么地方 指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里。
* ElementType的取值包含以下几种:
TYPE:类,接口或者枚举
FIELD:域,包含枚举常量
METHOD:方法
PARAMETER:参数
CONSTRUCTOR:构造方法
LOCAL_VARIABLE:局部变量
ANNOTATION_TYPE:注解类型
PACKAGE:包
* @Inherited:是否允许子类继承该注解
依据以上解释。,编写自己的注解MyAnnotation,代码如下:
package io.swagger.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
* @Documented:注解是否将包含在JavaDoc中
* @Retention:什么时候使用该注解 指明修饰的注解的生存周期,即会保留到哪个阶段。
* SOURCE:源码级别保留,编译后即丢弃。
CLASS:编译级别保留,编译后的class文件中存在,在jvm运行时丢弃,这是默认值。
RUNTIME: 运行级别保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用。
* @Target:注解用于什么地方 指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里。
* ElementType的取值包含以下几种:
TYPE:类,接口或者枚举
FIELD:域,包含枚举常量
METHOD:方法
PARAMETER:参数
CONSTRUCTOR:构造方法
LOCAL_VARIABLE:局部变量
ANNOTATION_TYPE:注解类型
PACKAGE:包
* @Inherited:是否允许子类继承该注解
* @author Jia.Huang
*
*
* 几个注解的解释
@Around 环绕切点,在进入切点前,跟切点后执行
@After 在切点后,return前执行,
@Before 在切点前执行方法,内容为指定的切点
@Aspect 将一个类定义为一个切面类
*
*/
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface MyAnnotation {
String value() default "first one";
}
4.编写需要注入的方法:
package io.swagger.AscpetTest;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import io.swagger.annotation.MyAnnotation;
@Component
@Aspect
public class TestAspect {
@Pointcut("@annotation(io.swagger.annotation.MyAnnotation)")
public void addAdvice() {}
@Around("addAdvice()")
public Object Interceptor(ProceedingJoinPoint joinPoint) {
System.out.println("====Interceptor====");
System.out.println("通知之开始");
Object retmsg = null;
try {
retmsg = joinPoint.proceed();
System.err.println("++++++++ 最后执行,执行完后结束 -->" + retmsg);
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("通知之结束 +retmsg -->" + retmsg);
Object result = null;
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
String deviceId = (String) args[0];
if (!"03".equals(deviceId)) {
return "no anthorization";
}
}
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
@Before("addAdvice()")
public void before(JoinPoint joinPoint) {
MethodSignature sign = (MethodSignature) joinPoint.getSignature();
Method method = sign.getMethod();
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println("打印:" + annotation.value() + " 开始前");
// System.out.println("===开始前===");
}
@After("addAdvice()")
public void after() {
System.out.println("after方法执行后");
}
}
5.编写controller类:
package io.swagger.api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotation.MyAnnotation;
@RestController
public class TestController {
@MyAnnotation("test11")
@RequestMapping("/add1")
public String addData1(String deviceId) {
System.out.println("=====addData1=====");
return "success";
}
@MyAnnotation()
@RequestMapping("/add2")
public String addData2(String deviceId) {
System.out.println("=====addData2=====");
return "success";
}
@MyAnnotation()
@RequestMapping("/add3")
public String addData3(String deviceId) {
System.out.println("=====addData3=====");
return "success";
}
@MyAnnotation()
@RequestMapping("/update1")
public String updateData1(String deviceId) {
System.out.println("=====updateData1=====");
return "success";
}
}
6.修改springboot的启动项:
在启动项上加入@EnableAspectJAutoProxy注解 让springboot project能开启自动注解功能
7.启动服务,测试:
依据以上结果 则实现了自定义注解方法:
其中 执行过程为:前端Api发送请求到后端,
1.会先执行方法中joinPoint.proceed();之前的逻辑
2运行到joinPoint.proceed();时,先执行@Befor注解的逻辑
3.执行joinPoint.proceed();的逻辑
4.执行@After注解的逻辑
5.执行joinPoint.proceed();剩下的逻辑
至此执行完毕
附:
实现Log的监控
1.编写Log的annotation
package io.swagger.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
/**
* 记录操作描述
*/
String description() default "";
/**
* 增删改的数据的类型
*/
Class<?> clazz();
}
2.编写Log的切入点及逻辑
package io.swagger.AscpetTest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Aspect
@Component
public class LogAspect {
//切入点签名:
@Pointcut("@annotation(io.swagger.annotation.LogAnnotation)")
public void cut() {}
//前置通知
@Before("cut()")
public void beforCall() {
log.info("====前置通知start");
log.info("====前置通知end");
}
//环绕通知
@Around(value="cut()")
public Object AroundCall(ProceedingJoinPoint joinPoint) throws Throwable{
log.info("====环绕通知start");
//注解所切的方法所在类的全类名
String typeName = joinPoint.getTarget().getClass().getName();
log.info("====目标对象:[{}]",typeName);
//注解所切的方法名
String methodName = joinPoint.getSignature().getName();
log.info("所切的方法名:[{}]",methodName);
StringBuilder sb = new StringBuilder();
//获取参数
Object[] arguments = joinPoint.getArgs();
for(Object argument:arguments) {
sb.append(argument.toString());
}
log.info("所切方法入参:{}",sb.toString());
//统计方法执行时间
long start = System.currentTimeMillis();
//执行目标方法。并获得对应方法的返回值
Object result = joinPoint.proceed();
log.info("返回结果:[{}]",result);
long end = System.currentTimeMillis();
log.info("====执行方法共用时间:[{}]",(end-start));
log.info("====环绕通知之结束");
return result;
}
//后置通知
@After("cut()")
public void AfterCall() {
log.info("=====后置通知start");
log.info("=====后置通知end");
}
//最终通知
@AfterReturning("cut()")
public void AfterReturningCall() {
log.info("=====最终通知start");
log.info("=====最终通知end");
}
//异常通知
@AfterThrowing(value = "cut()",throwing="ex")
public void afterThtrowing(Throwable ex) {
throw new RuntimeException(ex);
}
}
3.在controller层切入log
package io.swagger.api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotation.LogAnnotation;
import io.swagger.annotation.MyAnnotation;
@RestController
public class TestController {
//@MyAnnotation("test11")
@LogAnnotation(description="获取所有用户信息",clazz = TestController.class)
@RequestMapping("/add1")
public String addData1(String deviceId) {
System.out.println("=====addData1=====");
return "success";
}
@MyAnnotation()
@RequestMapping("/add2")
public String addData2(String deviceId) {
System.out.println("=====addData2=====");
return "success";
}
@MyAnnotation()
@RequestMapping("/add3")
public String addData3(String deviceId) {
System.out.println("=====addData3=====");
return "success";
}
@MyAnnotation()
@RequestMapping("/update1")
public String updateData1(String deviceId) {
System.out.println("=====updateData1=====");
return "success";
}
}
4.执行:结果显示: