
前言
注解是在JDK1.5之后引入的新特性位于java.lang.annotation,注解其实就是对代码进行一种特殊的标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理。本文主要分析如何自定义注解和注解的一些基础知识,然后在配合这AOP在实际运用中玩出新花样。
本文分为三部分
原本可以分为两篇文章,但是想来想去还是写一篇。趁热打铁。
想要自定义注解就要知道注解是怎么构成的,结合着项目中常用的注解来分析一下注解到底是怎么工作的。
看一下@Override注解 其主要作用是编译时进行格式检查。点进去看一下@Override实现。

点进去发现里面是空的除了两个元注解什么都没有,那么它到底是怎么实现的呢
其实@Override可以理解为是一个标签,它并没有实际的逻辑处理,而实现逻辑的就是注解的用户。它本质就是一个 『标记式注解』,仅被编译器可知 。
举个例子你的老板让你整理一下重要的文档,但是文档太多了你肯定需要把一下重要的文档给标记出来,然后你交给你老板的时候,老板会怎么做?老板当然是看到有标记的文档就去检查一下。
结合着上面的例子使用@Override注解的就是你,你的老板就是 JVM虚拟机,在编译的时候就是你的老板进行检查的时候,JVM发现了这个注解(标记)则就会进行处理 其处理机制主要是JVM内部处理。
总结下来就是:
定义注解,扫描注解,执行逻辑
在自定义注解之前我们要知道几个JDK为我们提供的“元注解”,元注解就是定义注解的注解,下面看看都有什么作用。
元注解一共有四个,都可以在java.lang.annotation下找到
@Target注解主要用于定义注解使用的位置,被描述的注解可以用在什么地方 。@Target的参数是ElementType枚举类,下面详解都有什么作用。
| 枚举? | 作用? | 
|---|---|
| ElementType.PACKAGE | 注解用在包 | 
| ElementType.TYPE | 注解作用于类型(类,接口,注解,枚举) | 
| ElementType.ANNOTATION_TYPE | 注解作用于注解 | 
| ElementType.CONSTRUCTOR | 注解作用于构造方法 | 
| ElementType.METHOD | 注解作用于方法 | 
| ElementType.PARAMETER | 注解作用于方法参数 | 
| ElementType.FIELD | 注解作用于属性 | 
| ElementType.LOCAL_VARIABLE | 注解作用于局部变量 | 
@Target如果不设置范围的话默认可以作用于所有目标上面
看一下@Target的源码

看一下里面有一个Value参数,它的返回值为ElementType[] , ElementType就是上面的枚举类。
@Retention注解的作用就是指定注解的生命周期。比如在编译时可以处理运行时可以处理等。它的枚举类为RetentionPolicy
| 枚举? | 作用? | 
|---|---|
| RetentionPolicy.SOURCE | 源码中保留,编译期可以处理 | 
| RetentionPolicy.CLASS | Class文件中保留,Class加载时可以处理 | 
| RetentionPolicy.RUNTIME | 运行时保留,运行中可以处理 | 
@Retention的默认值为 RetentionPolicy.CLASS即在Class加载时处理
@Retention源码

@Documented注解的话就比较简单,主要作用就是描述注解文档化。就是在 在生成javadoc的时候,是不包含注释的,但是如果注解被@Documented修饰,则生成的文档就包含该注解。此注解在以后版本可能会被删除这里就不详细的看了。

@Inherited 注解修饰的注解时具有可继承性的,就是说我们用 @Inherited 修饰了一个类,那么这个类的子类也会默认继承此注解。
源码

上面介绍了注解的元注解,那现在就开始实战自定义注解。
GIT项目地址:https://github.com/scramblecode/project-demos
和往常套路一样先创建项目,上面是本文章的示例可以下载下来看。
首先先写一个简单的例子。然后实战在SpringBoot中使用自定义注解加拦截器获取到请求参数。
这里介绍两个例子 一个是编译时注解,第二个例子是运行时注解。最后在配合着SpringBoot+AOP写一个项目中非常实用的例子
创建编译时注解我们首先要创建一个依赖项目作为注解处理器。

首先先创建一个注解接口,使用IDEA创建可以选择创建注解。

在创建一个DataTest注解,这里定义注解的目的就是如果使用了该注解在编译时打印出Hello World!。
然后编写注解处理器,这里使用的AbstractProcessor,本文只限简单使用,如果有机会写一篇文章研究AbstractProcessor。
具体代码

然后需要创建META-INF文件,这里推荐使用谷歌的auto-service可以自动生成META-INF/services/javax.annotation.processing.Processor。
加入依赖即可
<dependency>
    <groupId>com.google.auto.service</groupId>
    <artifactId>auto-service</artifactId>
    <version>1.0-rc5</version>
</dependency>
定义完后在你的主项目引入注解处理器。在POM文件中加入本地注解处理器的依赖
添加完成之后创建一个简单的类,然后加上@DataTest注解

运行开始编译,就会发现控制台输出以下信息。

编译时注解可以写一些生成工具比如lombok这种生成代码的工具可以使用。
简单创建一个注解来获取被注解标识的名称和包路径。
首先创建注解,定义为运行时注解目标为类属性等。

使用注解
@GetClassName(value = "测试注解")
public class Student {
}
然后创建一个注解处理类,运行

控制台输出。

在Web开发中经常要输出日志,然后还有接口的运行时间。现在我们就用自定义注解加AOP实现这种功能。
首先把项目完善一下,增加一个测试接口

然后创建log注解。

然后定义切面类
@Aspect
@Component
@Slf4j
public class LoggerAspect {
 private static final Logger logger = LoggerFactory.getLogger(LoggerAspect.class);
    @Pointcut("@annotation(com.lqcoder.annotationdemo.annotation.OutputLog)")
    public void weblog(){
    }
    @Around("weblog()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        List<Object> logArgs = Arrays.stream(point.getArgs())
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        try {
            logger.info("请求url={}, 请求参数={}", request.getRequestURI(), JSON.toJSONString(logArgs));
        } catch (Exception e) {
            logger.error("请求参数获取异常", e);
        }
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        try {
            logger.info("请求耗时={}ms, 返回结果={}", time, JSON.toJSONString(result));
        } catch (Exception e) {
            logger.error("返回参数获取异常", e);
        }
        return result;
    }
}
定义好之后重启项目,然后调用一下接口

运行结果可以看到已经生效。
本文结束。