Java注解

157 阅读3分钟

怎么理解注解?

注解就是代码中特殊的标记,这些标记可以在编译、类加载、运行时被读取,并执行对应的处理


了解过哪些注解?

注解在开发中比较常见,比如Java常用的框架Spring和MyBatis中的注解@Controller、@Service、@Param、@Select等等,有些项目使用Lombok了时,会添加@Data、@Slf4j注解等等


Java也有原生注解,比如@Override、@Function、@Deprecated等等,Java原生的注解大部分用于【标记】和【检查】,原生的Java注解除了这些基本注解外还有一种叫做元Annotation(元注解),元注解就是用来修饰注解的注解


常用的元注解有@Retention和@Target


@Retention注解可以简单理解为设置注解的生命周期


@Target注解用来注明这个注解可以修饰哪些地方(比如方法、成员变量、还是包等等)


如何自定义一个注解然后使用?


要自定义注解,首要考虑我们要在什么时候去解析这个注解


这就是需要用到前面提到的元注解@Retention,这个注解会修饰自定义注解的生命周期


@Retention注解传入的是RetentionPolicy枚举,该枚举有三个常量,分别是SOURCE、CLASS和RUNTIME


SOURCE:当@Retention传入SOURCE时,表示这个自定义注解只会保留在源文件,当Java文件编译成class文件时该自定义注解就会被遗弃失效


CLASS:当@Retention传入CLASS时,表示这个自定义注解会被保留到class文件,当JVM加载class文件时,这个自定义注解就是被遗弃失效,这也是@Retention默认传入的值


RUNTIME:当@Retention传入RUNTIME时,表示这个自定义注解不仅保存在class文件中,JVM加载完class文件后仍然存在,这就意味着我们可以通过反射去配合这类自定义注解的使用


使用RUNTIME级别的自定义注解开发


平时在自定义注解开发的时候,通常都是使用RUNTIME级别的注解,然后通过SpringAOP+反射机制去监控是否有这个注解,然后再进行后续逻辑处理


使用RUNITIME自定义注解的案例

pom依赖

     <!--SpringTest-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--SpringAOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

定义一个注解

/**
 * @author 小埋
 * FUCK注解,RUNTIME级别,作用在方法上
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Fuck {


    /**
     * 描述
     *
     * @return
     */
    String description() default "描述";

}

然后通过AOP去截取它

@Component
@Aspect
public class CustomizeAspect {

    private Logger logger = LoggerFactory.getLogger(CustomizeAspect.class);


    /**
     * 在方法前记录日志
     *
     * @param proceedingJoinPoint
     * @description ProceedingJoinPoint is only supported for around advice
     */
    @Around("@annotation(com.xiaomai.other.customizeAnnotation.Fuck)")
    public void advice(ProceedingJoinPoint proceedingJoinPoint) {
        //获取执行的方法
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        logger.info("方法 : {}", method);
        //获取类名
        String className = proceedingJoinPoint.getSignature().getDeclaringTypeName();
        logger.info("类名:{}", className);
        //获取方法注解
        Fuck annotation = method.getAnnotation(Fuck.class);
        logger.info("注解:{}", annotation);
        //获取注解参数
        String description = annotation.description();
        logger.info("注解参数:{}", description);
        //获取方法参数名
        String[] argsName = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        logger.info("方法参数名:{}", argsName[0]);
        //获取方法参数值
        Object[] args = proceedingJoinPoint.getArgs();
        logger.info("参数值:{}", args[0]);
    }

}


写个业务方法使用注解

@Component
public class Go {

    @Fuck(description = "这是一个描述")
    public void go(int param1) {
        System.out.println(param1);
    }

}

测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class DoTest {

    @Autowired
    private Go go;

    @Test
    public void test() {
        go.go(1);
    }
}

已经可以通过注解去获取方法值了

image.png


自定义的注解传入的是SOURCE和CLASS时要怎么自定义操作?


想要做到这一步首先就要理解.java文件到编译为.class文件的过程


image.png


在【注解抽象语法树】这个步骤就会解析注解,然后做处理的逻辑,所以想要在编译期间处理注解相关的逻辑,就需要继承AbstractProcessor类并实现process方法,而Lombok就是这样做的