1 简介

  • JDK1.5引入。
  • 用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。
  • 以“@注解名”在代码中存在的,
  • 根据注解参数个数,分为:标记注解、单值注解、完整注解三类
  • 不直接影响到程序的语义,只是作为注解(标识)存在
  • 通过反射机制实现对元数据的访问
  • 注解级别:源代码(SOURCE)级、class级、运行时(UNTIME)

2 分类

  • JDK基本注解
  • JDK元注解
  • 自定义注解

2.1 基本注解

JDK内置的注解类。如

@Override // 表示继承和覆写
@Deprecated // 表示废弃
@SuppressWarnings(value = "unchecked") // 压制警告

2.2 元注解

使用元注解来定义自己的注解

常用元注解:

  • @Retention: 注解的保留策略(SOURCE、CLASS、RUNTIME)
  • @Target:注解的作用范围
  • @Inherited:注解是否可被继承
  • @Documented:注解是否可以被javadoc工具提取成文档

2.2.1 @Retention

@Retention(RetentionPolicy.SOURCE)     // 源码级别。不存在于CLASS字节文件中
@Retention(RetentionPolicy.CLASS)      // CLASS级别(默认策略)。存在于CLASS字节文件中。无法在运行时获得
@Retention(RetentionPolicy.RUNTIME)    // 运行时级别。存在于CLASS字节文件中,可在运行时通过反射获取

2.2.2 @Target

@Target(ElementType.PACKAGE)             // 包
@Target(ElementType.MODULE)              // 模块。@since 9
@Target(ElementType.TYPE)                // 类、接口(包含注解接口)、enum、record
@Target(ElementType.FIELD)               // 属性(包含enum constants)
@Target(ElementType.CONSTRUCTOR)         // 构造函数
@Target(ElementType.METHOD)              // 方法
@Target(ElementType.PARAMETER)           // 方法参数
@Target(ElementType.LOCAL_VARIABLE)      // 局部变量
@Target(ElementType.ANNOTATION_TYPE)     // 注解类
@Target(ElementType.TYPE_PARAMETER)      // 类型参数。 @since 1.8
@Target(ElementType.TYPE_USE)            // 类型使用。@since 1.8
@Target(ElementType.RECORD_COMPONENT)    // record组件。@since 16 

可以指定多个位置,如:

@Target({ElementType.METHOD, ElementType.TYPE}) // 表示此注解可以在方法和类上面使用

2.2.3 @Inherited

2.2.4 @Documented

3 示例

github: https://github.com/hi-cooper/cz-tutorials/tree/main/aspect-demo

结合AOP的示例

3.1 代码结构

3.2 pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <name>${project.artifactId}</name>
    <groupId>com.taiwii</groupId>
    <artifactId>aspect-demo</artifactId>
    <version>1.0.0</version>
    <description>aspect-demo</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- aspectj -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.20.1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.20.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3.3 声明自定义注解/aspect/CustomAspect

package com.taiwii.aspectdemo.aspect;


import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE,
        ElementType.MODULE,
        ElementType.TYPE,
        ElementType.FIELD,
        ElementType.CONSTRUCTOR,
        ElementType.METHOD,
        ElementType.PARAMETER,
        ElementType.LOCAL_VARIABLE,
        ElementType.ANNOTATION_TYPE,
        ElementType.TYPE_PARAMETER,
        ElementType.TYPE_USE,
        ElementType.RECORD_COMPONENT})
@Inherited
@Documented
public @interface CustomAspect {
    /** 描述 */
    String description();
}

3.4 /service/package-info.java

@CustomAspect(description = "This is PACKAGE")
package com.taiwii.aspectdemo.service;

import com.taiwii.aspectdemo.aspect.CustomAspect;

3.5 /service/UserService.java

package com.taiwii.aspectdemo.service;

import com.taiwii.aspectdemo.aspect.CustomAspect;

@CustomAspect(description = "This is TYPE")
public class UserService<T> {

    @CustomAspect(description = "This is FIELD")
    public String name;

    @CustomAspect(description = "This is CONSTRUCTOR")
    public UserService() {}

    @CustomAspect(description = "This is METHOD")
    public void test(@CustomAspect(description = "This is PARAMETER") String name) {
        @CustomAspect(description = "This is LOCAL_VARIABLE")
        String prefix = "Hello, ";
        System.out.println(prefix + name);
    }
}

3.6 /config/BeanConfig.java

package com.taiwii.aspectdemo.config;

import com.taiwii.aspectdemo.aspect.CustomAspect;
import com.taiwii.aspectdemo.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {

    @Bean
    public UserService userService() {
        UserService<@CustomAspect(description = "This is TYPE_PARAMETER") String> userService =
                new @CustomAspect(description = "This is TYPE_USE") UserService();
        return userService;
    }
}

3.7 /module-info.java

import com.taiwii.aspectdemo.aspect.CustomAspect;

@CustomAspect(description = "This is MODULE")
open module aspect.demo {
    requires spring.boot.autoconfigure;
    requires spring.boot;
    requires lombok;
    requires org.aspectj.weaver;
    requires org.slf4j;
    requires spring.context;
}

3.8 /aop/UserServiceAOP.java

package com.taiwii.aspectdemo.aop;


import com.taiwii.aspectdemo.aspect.CustomAspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.*;
import java.util.Arrays;

@Slf4j
@Aspect
@Configuration
public class UserServiceAOP {

    @Pointcut("execution(* com.taiwii..service..*.*(..))")
    public void pointCutMethod() {
    }

    /**
     * getDeclaredAnnotation(): 忽略父类继承
     * getAnnotation(): 包含继承
     *
     * @param joinPoint
     * @throws Throwable
     */
    @After("pointCutMethod()")
    public void doAfter(JoinPoint joinPoint) throws Throwable {
        CustomAspect pkg = joinPoint.getSignature().getDeclaringType().getPackage().getAnnotation(CustomAspect.class);
        if (null != pkg) {
            System.out.println(pkg.description()); // This is PACKAGE
        }

        CustomAspect module = joinPoint.getSignature().getDeclaringType().getModule().getAnnotation(CustomAspect.class);
        if (null != module) {
            System.out.println(module.description()); // This is MODULE
        }

        CustomAspect cls = (CustomAspect) joinPoint.getSignature().getDeclaringType().getAnnotation(CustomAspect.class);
        if (null != cls) {
            System.out.println(cls.description()); // This is TYPE
        }

        Field[] fields = joinPoint.getSignature().getDeclaringType().getFields();
        Arrays.asList(fields).forEach((field) -> {
            CustomAspect item = field.getAnnotation(CustomAspect.class);
            if (null != item) {
                System.out.println(item.description()); // This is FIELD
            }
        });

        Constructor[] constructors = joinPoint.getSignature().getDeclaringType().getConstructors();
        Arrays.asList(constructors).forEach((constructor) -> {
            CustomAspect item = (CustomAspect) constructor.getAnnotation(CustomAspect.class);
            if (null != item) {
                System.out.println(item.description()); // This is CONSTRUCTOR
            }
        });

        CustomAspect method = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(CustomAspect.class);
        if (null != method) {
            System.out.println(method.description()); // This is METHOD
        }

        Parameter[] params = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
        Arrays.asList(params).forEach((param) -> {
            CustomAspect item = param.getAnnotation(CustomAspect.class);
            if (null != item) {
                System.out.println(item.description()); // This is PARAMETER
            }
        });
    }
}

3.9 测试类

package com.taiwii.aspectdemo;

import com.taiwii.aspectdemo.aspect.CustomAspect;
import com.taiwii.aspectdemo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService<@CustomAspect(description = "This is TYPE_PARAMETER") String> userService;

    @Test
    public void test() {
        userService.test("Tom");
    }
}

4 示例结果

Hello, Tom
This is PACKAGE
This is MODULE
This is TYPE
This is FIELD
This is CONSTRUCTOR
This is METHOD
This is PARAMETER