1
自定义注解
1.元注解:java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。主要使用的是Target和Retention注解。

2.常用元注解
@Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:
METHOD:应用于描述方法
TYPE:应用于描述类、接口或enum类型
PACKAGE:应用于描述包
FIELD:应用于属性(包括枚举中的常量)
@Retention: 表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为:
SOURCE:编译时被丢弃,不包含在类文件中
CLASS:JVM加载时被丢弃,包含在类文件中,默认值
RUNTIME:由JVM 加载,包含在类文件中,在运行时可以被获取到
注:只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解
@Document:表明该注解标记的元素可以被Javadoc 或类似的工具文档化
例:
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})@Documentedpublic @interface ApiOperateLog {}
2
Aop理解
简单地去理解,其实AOP要做三类事:
在哪里切入,也就是这些非业务操作在哪些业务代码中执行
在什么时候切入,是业务代码执行前还是执行后
切入后做什么事,比如做权限校验、日志记录、统计等
简单画了个体系图梳理下

Pointcut:切点,决定处理如日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
Aspect:切面,即Pointcut和Advice。
Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
3
日志记录的实现
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})@Documentedpublic @interface ApiOperateLog {String logName() default "操作日志";String logType();String logModule();}
public interface LogConst {/*** 日志类型* 增 add* 删 del* 改 update* 查 select*/interface LogType{String LogType_ADD = "add";String LogType_DEL = "del";String LogType_UPDATE = "update";String LogType_SELECT = "select";}/*** 模块名称常量_评论管理*/interface LogModule {/*** 用户信息管理*/String LOG_USER_INFO = "userInfo";}}
@Slf4j@Aspect@Componentpublic class LogAspect {/*** 此处的切点是注解的方式(注解类路径),也可以用包名的方式达到相同的效果* '@Pointcut("execution(* com.diku.provider.service.impl.*.*(..))")'*/@Pointcut("@annotation(com.diku.provider.annotion.ApiOperateLog)")public void execute(){}/*** 后置返回通知* 处理完请求,返回内容*/@AfterReturning(returning = "result", value = "execute()")public void afterReturning(JoinPoint point, Object result) throws Throwable {Object[] args = point.getArgs();//得到注解对象ApiOperateLog annotation = ((MethodSignature)point.getSignature()).getMethod().getAnnotation(ApiOperateLog.class);String name = annotation.logName();String type = annotation.logType();String logModule = annotation.logModule();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String createTime = simpleDateFormat.format(new Date());//获取方法返回值log.info(" @AfterReturning:{}",result);saveLog(name, type, logModule, args, createTime);}private void saveLog(String name, String type, String logModule, Object[] args, String createTime) {//保存相关日志操作,调用kafka(其他消息中间件),通知Es保存相关日志记录log.info("{}->类型:{}->模块:{}->参数:{}->创建时间:{}",name,type,logModule,Arrays.toString(args), createTime);}/*** 前置通知*/@Before("execute()")public void doBeforeAdvice(JoinPoint joinPoint){log.info("测试@Before.....");}/*** 后置异常通知*/@AfterThrowing("execute()")public void afterThrowing(JoinPoint joinPoint){log.info("测试@AfterThrowing.....");}/*** 后置通知*/@After("execute()")public void after(JoinPoint joinPoint){log.info("测试@After.....");}/*** 环绕通知*/@Around("execute()")public Object doAround(ProceedingJoinPoint point) throws Throwable {log.info("进入环绕通知");long start=System.currentTimeMillis();Object proceed = point.proceed(point.getArgs());log.info("环绕通知.....统计方法执行的时间为:{}ms",String.valueOf(System.currentTimeMillis()-start));log.info("退出环绕通知");return proceed;}}
@RestController@RequestMapping("/user")public class UserController {@Autowiredprivate UserMapper userMapper;@ApiOperateLog(logName = "查询日志",logType = LogConst.LogType.LogType_SELECT,logModule = LogConst.LogModule.LOG_USER_INFO)@GetMapping("/{id}")public User selectById(@PathVariable("id") int id) {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}return userMapper.selectById(id);}}

控制台打印日志:

通过控制台打印的日志我们还可以看出这几种通知的执行顺序:
喜欢就加个关注吧,


往期精选




