简单易懂讲注解
注解是什么
简单的说,注解就是一种将元数据信息从 xml 剥离开来,然后保存在 java 源代码中,这将使得代码更加清晰易懂,无需维护两个地方: java 源代码以及 xml 配置文件。
典型的场景就是 spring 框架,我们都知道,spring 框架将一个 bean 保存在容器里有两种方式,一种是采用配置文件的方式生成 bean 并且保存在容器中,使用的时候通过 bean 工厂拿对应的 bean 实例即可。这种方式很繁琐,不仅需要维护 java 源代码,还需要在 xml 配置里再维护一遍。另一种方式是采用注解的方式,在类名上使用 @Component或者@Service(当然还有其他方式,但不是本篇文章的重点)。然后在使用的时候采用 @Autowired 形式注入即可。这样就无需繁琐的 xml 配置。(例子)
当然,采用传统 xml 维护元素据还是使用注解,各有优劣,需要根据实际场景进行评估。
如何使用注解
接下来我们先从一个简单的注解定义开始,然后介绍一些注解的关键属性
定义注解
如下例子,Test 注解看起来很像接口的定义,注解和其他接口和类一样,都会被编译成 class 文件。像这种不含任何元素的被称为标记注解,如 java 8 新加入的用于声明一个接口时函数式接口的注解:@FunctionalInterface。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
当然,注解也是可以定义一些属性的。如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
int value();
String name() default "-- default --";
}
其中 value() 以及 name() 就是该注解的属性,其中 value() 没有默认值,那么在使用该注解的时候,必须指定 value 属性,name 有个默认值,使用的时候可以不需要指定默认值。
如下例子,就是该注解的使用方式,在 1 处,由于没有指定 value 属性,所以编译失败。
public class AnnotationDemo {
@Test(1)
private int value;
@Test() // 1 编译失败
private int withoutValue;
@Test(2)
private String withoutName;
@Test(value = 3, name = "name")
private String name;
}
这个注解现在来说是没有一丝丝意义的,因为我们还没有为其编写注释处理器,注释处理器在后面会介绍。
@Test 注解中使用到的 @Target 注解、@Retention 注解以及他们的参数枚举,会在下文的元注解中介绍。
常见注解
常见的注解这里主要介绍 jdk 的注解
- @Override:表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示。
- @Deprecated:表示当前类 or 方法 or 字段被弃用了,不应该再使用了,使用会产生告警
- @SuppressWarnings:关闭不当的编译器警告信息。
- @FunctionalInterface:Java 8 中加入用于表示类型声明为函数式接口
元注解
上文的 @Test 注解中,我们使用到了 @Target 注解、@Retention 注解,这两个注解为元注解,
目前一共有 5 个元注解:
注解 | 解释 |
---|---|
@Target | 表示注解可以用于哪些地方。可能的 ElementType 参数包括:CONSTRUCTOR:构造器的声明 FIELD:字段声明(包括 enum 实例) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数声明 TYPE:类、接口(包括注解类型)或者 enum 声明 |
@Retention | 表示注解信息保存的时长。可选的 RetentionPolicy 参数包括: SOURCE:注解将被编译器丢弃 CLASS:注解在 class 文件中可用,但是会被 VM 丢弃。 RUNTIME:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 |
@Documented | 将此注解保存在 Javadoc 中 |
@Inherited | 允许子类继承父类的注解 |
@Repeatable | 允许一个注解可以被使用一次或者多次(Java 8) |
使用最多的其实就是 @Target 以及 @Retention。
@Targe:注解中指定的每一个 ElementType 就是一个约束,它告诉编译器,这个自定义的注解只能用于指定的类型。你可以指定 enum ElementType 中的一个值,或者以逗号分割的形式指定多个值。如果想要将注解应用于所有的 ElementType,那么可以省去 @Target 注解,但是这并不常见。
@Retention:表明注解存在的时长,使用最多的是 RUNTIME,使用 RUNTIME 的时候,注解在运行期也保留着,这时就可以通过反射机制读取注解信息,如果使用 SOURCE,CLASS,那么就无法通过反射获取。
注解处理器
单独定义一个注释是没什么意义的,我们要给一个注释赋予意义,那么就得 coding,给这个注释编写一个注解处理器。这里我仅演示最简单的注解处理器。
这个列子很简单,定义了一个注解 @Test,该注解可以在方法上使用,可以被带入到运行时。AnnotationDemo 类实现了 Interface 接口,demo1()、demo2()、demo3()使用了注解,其中 demo3() 使用默认值,demo4() 没有引入注解。这里实现接口的原因是为了使用动态代理来调用方法,处理注解的逻辑写在动态代理里。动态代理类 InvokeClass,可以看到 invoke 方法里拿到 obj 对应的方法(这里不直接用入参的 method 字段是因为该字段代表接口方法,接口方法没有加注解,获取到的 Test annotation 会为空),这里拿到了方法上的注解信息后可以编写自己想要的处理逻辑,我这边就简单把 @Test 注解的 value() 值打印出来。
动态代理文章可以看:简单易懂将反射
(这里字符串判空写的有点丑了,是因为我没引入对应工具类)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value() default "--- default ---";
}
interface Interface {
void demo1();
void demo2();
void demo3();
void demo4();
}
public class AnnotationDemo implements Interface {
@Test("demo1")
@Override
public void demo1() {
}
@Test("demo2")
@Override
public void demo2() {
}
@Test
@Override
public void demo3() {
}
@Override
public void demo4() {
}
}
class InvokeClass implements InvocationHandler {
Object obj;
public InvokeClass(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method objectMethod = obj.getClass().getMethod(method.getName());
Test annotation = objectMethod.getAnnotation(Test.class);
if (Objects.nonNull(annotation) && !"".equals(annotation.value())) {
System.out.println(annotation.value());
}
return method.invoke(obj, args);
}
}
public class Main {
public static void main(String[] args) {
Interface anInterface = (Interface) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Interface.class}, new InvokeClass(new AnnotationDemo()));
anInterface.demo1();
anInterface.demo2();
anInterface.demo3();
anInterface.demo4();
}
}
输出:
demo1
demo2
--- default ---
Spring 如何自定义注解
在 spring 中使用自定义注解一般是配合 aop 使用的。
如下,还是注解 @Test ,有个 AnnotationDemo 类,在方法上使用了注解,并且将自身注入 spring 容器 (@Service),并且通过实现 BeanFactoryAware 接口,在初始化的时候调用 setBeanFactory 方法,这里通过传入的 bean 工厂获取到 bean 并且调用方法。
定义一个切面 AspectDemo,切点 pointcut 为我们自定义的注解类,增强 advice 是打印了 @Test 注解的 value() 信息。这样当调用了使用了 @Test 的注解的方法的时候,就是会打印对应的 value() 信息。启动项目,由于在 setBeanFactory 方法中调用了 AnnotationDemo 类的几个方法,因此打印出了对应的注解的 value 信息。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value() default "--- default ---";
}
@Service
public class AnnotationDemo implements BeanFactoryAware {
@Test("demo1")
public void demo1() {
}
@Test("demo2")
public void demo2() {
}
@Test
public void demo3() {
}
public void demo4() {
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
AnnotationDemo demo = beanFactory.getBean(AnnotationDemo.class);
demo.demo1();
demo.demo2();
demo.demo3();
demo.demo4();
}
}
@Component
@Aspect
public class AspectDemo {
@Pointcut("@annotation(com.example.spring_project.Test)")
private void pointcut() {}
@Before("pointcut() && @annotation(test)")
public void advice(Test test) {
System.out.println(test.value());
}
}
输出:
demo1
demo2
--- default ---
文章为本人学习过程中的一些个人见解,漏洞是必不可少的,希望各位大佬多多指教,帮忙修复修复漏洞!!!
进入本人语雀文档体验更好哦
https://www.yuque.com/docs/share/d1d3f7bb-0918-4844-a870-fc50eb0da707?# 《注解》
参考资料:java 编程思想