可以将注解理解为代码块的注释,但是这种注释却又不同于普通的注释,它可以像Java关键字那样对程序产生影响。那么它的原理是什么呢?先来看看注解的分类,注解按程序从源码到运行阶段可以分为三类:
- 不会编译到
.class
文件中的注解 - 不会被加载到虚拟机的注解
- 会被编译到
.class
文件并且会被加载到虚拟机的注解
而这篇文章主要说明第三种情况,会被加载到JVM
中的注解。首先要介绍一下两个关键的元注解,所谓元注解就是注解类的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface myAnnotation {
String value();
}
现在已经创建好了一个自定注解,现在对这个注解类上的两个元注解做下解释:
@Target(ElementType.METHOD)
这个注解的作用是指定自定义的注解的作用范围,说白了就是你创建的注解能应用在哪些项上,就比如上面创建的注解只能应用在方法上,如下所示:
class ceshi{
@myAnnotation("hello")
private void hello(){}
}
上面的代码没有问题,但是如果注解到ceshi
类上,编译器就会报错。
@Retention(RetentionPolicy.RUNTIME)
这个注解则是用来指定自定义的注解应该保存到什么时候,比如RetentionPolicy.RUNTIME
就表示将注解保留到运行时。
现在注解也定义好了,元注解上的参数选项也指定好了,那自定义的注解现在能使用了吗?其实现在还缺少一样重要的东西:注解处理器。要知道,注解本身并不会做任何处理,它只是一种标记用来告诉注解处理器被标记的地方需要被处理。现在来开始创建一个完整的自定义注解
第一步:创建一个注解接口,用来对Base64编码的字符串进行解码操作
/**
* 创建一个注解,用来对Base64编码的字符串进行解码操作
*/
//这个注解可以被应用到方法上
@Target(ElementType.METHOD)
//这个注解会被一只保留到运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
第二步:编写一个接口,接口里面有一个方法,用来返回Base64
加密的字符串
/**
* 响应信息的接口
*/
public interface ResponseInterface {
//用来返回被加密的信息
@MyAnnotation("decode")
String getMessage(String ss);
void print();
}
getMessage
被自定义注解注释了,我会用它来处理方法的返回值
第三步:编写实现类
/**
* 响应信息实现类
*/
public class ResponseMessage implements ResponseInterface{
//这里返回一条加密过的信息
@Override
public String getMessage(String ss) {
return Base64.getEncoder().encodeToString("这是一条加密信息".getBytes(StandardCharsets.UTF_8));
}
@Override
public void print() {
System.out.println("hello");
}
}
第四步:创建代理对象,对所有被@MyAnnotation("decode")
注解注释的方法进行处理
/**
* 自定义注解处理类
*/
public class AnnotationHandler {
public static <T> T MyAnnotationHandler(T t) {
Class<?> cl = t.getClass();
//创建一个代理对象对加密的方法返回结果进行处理
return (T) Proxy.newProxyInstance(cl.getClassLoader(), cl.getInterfaces(), (proxy, method, args) -> {
MyAnnotation a = method.getAnnotation(MyAnnotation.class);
if (a!=null&&a.value().equals("decode")) {
String baseStr = (String) method.invoke(t, args);
System.out.printf("加密前信息:%s,开始解码...\n",baseStr);
return new String(Base64.getDecoder().decode(baseStr), StandardCharsets.UTF_8);
} else {
return method.invoke(t, args);
}
});
}
}
注意:这里使用的代理是JDK动态代理,这个代理是基于接口实现的,所以要将注解应用到接口的抽象方法上,否则代理方法中这行method.getAnnotation(MyAnnotation.class);
代码获取不到注解信息。
第五步:测试
/**
* 自定义注解测试
*/
public class AnnotationTest {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
ResponseInterface message = AnnotationHandler.MyAnnotationHandler(new ResponseMessage());
//这里的参数没有任何意义,忽视就好
System.out.println(message.getMessage("ss"));
message.print();
}
}
运行结果:
加密前信息:6L+Z5piv5LiA5p2h5Yqg5a+G5L+h5oGv,开始解码…
这是一条加密信息
hello
运行成功!
通过编写运行时可保留的自定义注解,并提供一个机遇反射或动态代理的注解处理器,这种形式是开发者比较常用的自定义注解方式。但是前面说过,自定义注解可以应用于三个阶段,这只是其中一种,只需要知道无论在编译前、编译后、运行时都可以自定义对应的注解和相应的处理器。
长路漫漫,加油。