注解使代码简单易读,提供编译器类型检查,并且可以通过注解构造代码处理工具。
日常开发中我们会遇到很多注解,如@RestController
、@GetMapping
、@Autowired
、@Service
等等。
那这些注解是怎么定义的?怎么使用的?为什么要这些注解?
接下来我们从基础知识来慢慢揭开注解的面纱。
注解的基础知识
注解的格式
注解以@
符号开头,如下:
@Entity
注解被用在什么地方
注解可以应用于声明:类、字段、方法和方法参数等。如下:
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass { ... }
@Override
void mySuperMethod() { ... }
class UnmodifiableList<T> implements
@Readonly List<@Readonly T> { ... }
定义注解
用@interface
关键字定义注解,和定义接口有点像,多了个@
符号。
注解里面的属性跟接口方法有点像,但没有方法体。属性可以有默认值,如currentRevision
默认值为1。
// 注解 类注释
@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassPreamble {
String name();
String author();
String date();
int currentRevision() default 1;
}
使用注解
@ClassPreamble(
name = "学生类",
author = "llh",
date = "2022-08-01",
currentRevision = 2)
public class Student {
}
spring 的@Service
是这么定义的。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
元注解
自定义注解一定要用到元注解,元注解就是定义注解的注解。有以下五个元注解。
@Retention
@Retention
注解指定标记注解的存储方式:
- RetentionPolicy.SOURCE:注解只保留在源代码级别,编译器会忽略它。
- RetentionPolicy.CLASS:注解在编译时由编译器保留,但被 Java 虚拟机 (JVM) 忽略。
- RetentionPolicy.RUNTIME:注解由 JVM 保留,以便运行时环境使用。
@Target
@Target
注解标记另一个注解,以限制该注解可以应用于哪种Java元素。目标注释指定以下元素类型之一作为其值:
- ElementType.ANNOTATION_TYPE:可以应用于注释类型。
- ElementType.CONSTRUCTOR:可以应用于构造函数。
- ElementType.FIELD:可以应用于字段或属性。
- ElementType.LOCAL_VARIABLE:可以应用于局部变量。
- ElementType.METHOD:可以应用于方法级别的注释。
- ElementType.PACKAGE:可以应用于包声明。
- ElementType.PARAMETER:可以应用于方法的参数。
- ElementType.TYPE:可以应用于类、接口(包括注释类型)或枚举声明
@Inherited
@Inherited注解表示注解类型可以继承自超类。(默认情况下不是这样。)当用户查询注解类型并且该类没有该类型的注解时,将查询该类的超类的注解类型。此注释仅适用于类声明。
@Documented
@Documented注解表示无论何时使用指定的注解,都应使用 Javadoc 工具记录这些元素
@Repeatable
在 Java SE 8 中引入,表示标记的注解可以多次应用于同一个声明或类型使用。
编写注解处理器
如果没有用于读取注解的工具,那么注解不会比注释更有用。使用注解中一个很重要的部分就是,创建与使用注解处理器。Java 拓展了反射机制的 API 用于帮助你创造这类工具。
如下是个很简单注解处理工具,利用反射获取类的注解,然后打印注解信息。
public class Demo {
public static void main(String[] args) {
Class<?> c = Student.class;
ClassPreamble annotation = c.getAnnotation(ClassPreamble.class);
System.out.println(annotation.name());
System.out.println(annotation.author());
System.out.println(annotation.date());
System.out.println(annotation.currentRevision());
}
}
模拟 Spring 的 @Service
自定义MyService
注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
public interface UserService {
}
@MyService("userService")
public class UserServiceImpl implements UserService {
}
这里模拟Spring容器扫描有MyService
注解的类,并自动生成该类的一个实例对象保存到容器中。
public class Demo {
public static void main(String[] args) {
try {
// 模拟 spring 容器
Map<String, Object> map = new ConcurrentHashMap<>();
// 模拟 扫描所有有MyService注解的类
Class<?> c = UserServiceImpl.class;
MyService annotation = c.getAnnotation(MyService.class);
if (annotation != null) {
// 模拟 有MyService注解的类生成该类的一个实例对象
Object obj = c.newInstance();
// 模拟 保存到容器中 key就是注解的值 userService
map.put(annotation.value(), obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
实际要比这个复杂的多得多,我这里就是示意说明一下注解的作用,让大家容易理解。
结语
注解我们日常开发经常用,但是很多同学从来没有自己手动去创建过一个自定义的注解类。
1万小时定律:1万小时的锤炼是任何人从平凡变成世界级大师的必要条件。但是写1万小时的代码你就能变成大师吗?非也!不要重复性的工作,要思考要深入!
关注微信公众号:小虎哥的技术博客,每天一篇文章,让你我都成为更好的自己。
网友评论