1

自定义注解

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

springboot实现JessionId 自定义domain springboot如何自定义注解_@aspect注解

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为何那么重要呢?在我们的程序中经常会存在一些系统性的需求,比如权限校验、日志记录、统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护。AOP将这些非业务代码完全提取出来,与业务代码分离并寻找节点切入业务代码中。

简单地去理解,其实AOP要做三类事:

  • 在哪里切入,也就是这些非业务操作在哪些业务代码中执行
  • 在什么时候切入,是业务代码执行前还是执行后
  • 切入后做什么事,比如做权限校验、日志记录、统计等

简单画了个体系图梳理下

springboot实现JessionId 自定义domain springboot如何自定义注解_@aspect注解_02

  • Pointcut:切点,决定处理如日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
  • Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
  • Aspect:切面,即Pointcut和Advice。
  • Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
  • Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

3

日志记录的实现


1.自定义注解

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})@Documentedpublic @interface ApiOperateLog {    String logName() default "操作日志";    String logType();    String logModule();}


2.注解用到的类型

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";    }}


3.核心切入类

@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;    }}


注:@Around功能虽然强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturning就能解决的问题,就没有必要使用Around了。如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用Around。尤其是需要使用增强处理阻止目标的执行,或需要改变目标方法的返回值时,则只能使用Around增强处理了。例如案例中我们统计方法执行的时间。 4.接口使用注解测试

@RestController@RequestMapping("/user")public class UserController {    @Autowired    private 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);    }}


浏览器访问:

springboot实现JessionId 自定义domain springboot如何自定义注解_@select注解_03

控制台打印日志:

springboot实现JessionId 自定义domain springboot如何自定义注解_@aspect注解类不生效_04

通过控制台打印的日志我们还可以看出这几种通知的执行顺序:


进入环绕通知--->前置通知--->执行ProceedingJoinPoint.proceed--->退出环绕通知--->后置通知--->后置返回通知 喜欢就加个关注吧,

springboot实现JessionId 自定义domain springboot如何自定义注解_@aspect注解_05