java如何让代码变得优雅——自定义注解

一、什么是注解

java中,注解分两种,元注解和自定义注解。
我们常用的一些注解,如:@Autowired、@Override等都是自定义注解。

二、java的元注解

可以理解为描述注解的注解,除了这几个元注解,所有注解都是自定义注解。

  • @Document:表示是否将注解信息添加在java文档中
  • @Target:表示注解用于什么地方。
    • ElementType.CONSTRUCTOR: 用于描述构造器
    • ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
    • ElementType.LOCAL_VARIABLE: 用于描述局部变量
    • ElementType.METHOD: 用于描述方法
    • ElementType.PACKAGE: 用于描述包
    • ElementType.PARAMETER: 用于描述参数
    • ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
  • @Retention:定义该注解的生命周期
    • RetentionPolicy.SOURCE:在编译阶段丢弃。不写入字节码。如:@Override,@SuppressWarnings都属于这类注解。
    • RetentionPolicy.CLASS:在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
    • RetentionPolicy.RUNTIME:始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解信息。
  • @Inherited:定义该注释和子类的关系
    • 是一个标记注解,阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

三、自定义注解

自定义注解编写规则:

  • Annotation型定义为@interface,所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。
  • 参数成员只能用public或默认(default)这两个反问权修饰
  • 参数成员只能用八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这些类型的数组。
  • 要获取类方法和字段的注解信息,必须通过java的反射技术来获取Annotation对象,除此之外没有别的获取注解对象的方法

四、注解实现:记录访问日志

4.1 pom文件

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>30.1-jre</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

4.2 自定义一个注解

/**
 * @Author: KD
 * @Date: 2021/1/1 1:02 下午
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TheLog {

    /**
     * 业务对象名称
     *
     * @return
     */
    public String name();

    /**
     * 描述了如何获取id的表达式
     *
     * @return
     */
    public String idExpression();
}

这个类中定义了注解的两个属性,第一个是name,第二个是一个Spel表达式。

4.3 切面

/**
 * @Author: KD
 * @Date: 2021/1/1 1:02 下午
 */
@Aspect
@Component
public class TheAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(TheAspect.class);

    @Autowired
    HttpServletRequest request;

    @Around("@annotation(com.example.zhujie.anno.TheLog)")//对标注了TheLog注解的方法设置切面
    public Object log(ProceedingJoinPoint pjp) throws Exception {

        Method method = ((MethodSignature)pjp.getSignature()).getMethod();
        TheLog theLog = method.getAnnotation(TheLog.class);

        Object response = null;

        try {
            // 目标方法执行
            response = pjp.proceed();
        } catch (Throwable throwable) {
            throw new Exception(throwable);
        }

        if (StringUtils.isNotEmpty(theLog.idExpression())) {
            SpelExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(theLog.idExpression());

            EvaluationContext context = new StandardEvaluationContext();
            // 获取参数值
            Object[] args = pjp.getArgs();

            // 获取运行时参数的名称
            LocalVariableTableParameterNameDiscoverer discoverer
                    = new LocalVariableTableParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);

            // 将参数绑定到context中
            if (parameterNames != null) {
                for (int i = 0; i < parameterNames.length; i++) {
                    context.setVariable(parameterNames[i], args[i]);
                }
            }

            // 将方法的resp当做变量放到context中,变量名称为该类名转化为小写字母开头的驼峰形式
            if (response != null) {
                context.setVariable(
                        CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()),
                        response);
            }

            // 解析表达式,获取结果
            String itemId = String.valueOf(expression.getValue(context));

            // 执行日志记录
            handle(theLog.name(), itemId);
        }

        return response;
    }


    private void handle(String name, String theId) {
        // 通过日志打印输出
        LOGGER.info("theType = " + name + ",theId = " + theId);
    }
}

这里主要是通过aspectj中的类库来实现反射,获取注解属性,然后解析Spel表达式,最后在handle方法中实现日志的打印输出。

4.4 使用注解

/**
 * @Author: KD
 * @Date: 2021/1/1 12:41 下午
 */
@Controller
public class TestController {

    @GetMapping("test")
    @ResponseBody
    @TheLog(name="test1",idExpression="#id")
    public String test(String id){
        return id;
    }

}

其中idExpression是Spel表达式,"#id"表示获取方法中名为id的局部变量。

访问url:

localhost:8080/test?id=123123

日志打印如下:

2021-01-01 13:43:38.157  INFO 52624 --- [nio-8080-exec-1] com.example.zhujie.anno.TheAspect        : theType = test1,theId = 123123
上一篇:Android的注解(Annotation)学习之路


下一篇:springboot自定义注解