一、什么是注解
注解(Annotation)是Java中的一种特殊形式的标记,它可以在代码中添加元数据信息。
注解提供了一种在源代码中添加、修改、删除元数据的方式,这些元数据可以用来在编译时进行验证、在运行时进行解析或者在部署时进行配置。
注解本身并不影响代码的执行逻辑,它只是对代码的补充说明。
注解可以用于修饰类、方法、字段等程序元素,它们以 @ 符号开头,紧随其后的是注解的名称和属性(如果有的话)。
二、什么是元注解
元注解是指用来修饰其他注解的注解
JDK中提供了四个元注解,分别是:@Retention、@Target、@Documented和@Inherited。
- @Retention:用来指定注解的保留策略,即注解在什么时候有效。
它有一个属性value,可以取以下三个值:
RetentionPolicy | 作用 |
---|---|
RetentionPolicy.SOURCE | 注解只在源代码阶段保留,编译时会被忽略,不会包含在编译后的class文件中。 |
RetentionPolicy.CLASS | 注解在编译时保留,会包含在编译后的class文件中,但在运行时无法获取。 |
RetentionPolicy.RUNTIME | 注解在运行时保留,可以通过反射获取注解信息。 |
- @Target:用来指定注解的目标对象,即注解可以修饰哪些程序元素。
它有一个属性value,可以取以下值之一或者多个值的组合:
ElementType | 作用 |
---|---|
ElementType.TYPE | 可以修饰类、接口、枚举。 |
ElementType.FIELD | 可以修饰字段。 |
ElementType.METHOD | 可以修饰方法。 |
ElementType.PARAMETER | 可以修饰方法参数。 |
ElementType.CONSTRUCTOR | 可以修饰构造函数。 |
ElementType.LOCAL_VARIABLE | 可以修饰局部变量。 |
ElementType.ANNOTATION_TYPE | 可以修饰注解。 |
ElementType.PACKAGE | 可以修饰包。 |
-
@Documented:用来指定注解是否会包含在Java文档中,默认情况下,注解是不会出现在生成的文档中的。
通过使用@Documented注解,可以让注解的信息包含在生成的文档中,方便开发者查阅。 -
@Inherited:用来指定注解是否可以被继承,默认情况下,注解是不会被继承的。
通过使用@Inherited注解,可以让注解被子类继承,即父类上的注解会被子类继承并生效。 -
这四个元注解在定义自定义注解时经常会用到,通过设置不同的元注解,可以灵活地控制注解的保留策略、目标对象、文档显示和继承性。
三、注解的大致分类
注解的大致作用分类可以分为三类:编译时注解、运行时注解和源码级注解。
1、编译时注解:
编译时注解是在编译阶段对代码进行处理的注解。它可以用来检查代码的合法性、生成一些辅助代码等。
常用的编译时注解有:
- @Override:用来标记方法重写父类方法的注解,如果方法上使用了该注解但没有重写父类方法,会编译错误。
- @Deprecated:用来标记过时的方法或类的注解,编译器会在使用过时的方法或类时给出警告。
- @SuppressWarnings:用来抑制编译器产生的警告信息的注解,可以用来消除一些不必要的警告。
示例代码:
@Override
public void myMethod() {
// do something
}
@Deprecated
public void oldMethod() {
// do something
}
@SuppressWarnings("unchecked")
public void myMethod() {
// do something
}
2、运行时注解:
运行时注解是在程序运行时通过反射机制对代码进行处理的注解。它可以动态地获取注解的信息,并根据注解信息做出相应的操作。
常用的运行时注解有:
- @Test:用于标记测试方法的注解,测试框架可以通过反射来识别被标记的方法,并进行自动化测试。
- @Autowired:用于自动装配依赖的注解,可以通过反射来自动注入依赖对象。
- @RequestMapping:用于映射请求路径和处理方法的注解,可以通过反射来找到对应的处理方法。
示例代码:
@Test
public void testMethod() {
// do test
}
@Autowired
private MyService myService;
@RequestMapping("/hello")
public String hello() {
// handle request
}
3、源码级注解:
源码级注解是对源代码进行标记的注解,不会对源代码进行任何处理,仅仅是为了在源代码级别上提供一些额外的信息。
常用的源码级注解有:
- @SuppressWarnings:用来抑制编译器产生的警告信息的注解,可以用来消除一些不必要的警告。
- @Documented:用来指定注解是否包含在Java文档中的注解,如果一个注解有@Documented注解,它的注解信息将包含在生成的Java文档中。
示例代码:
@SuppressWarnings("unchecked")
public void myMethod() {
// do something
}
@Documented
public @interface MyAnnotation {
// annotation details
}
案例分析:假设有一个需求,要求在编译时检查某个类的方法是否符合命名规范,方法名必须以大写字母开头。可以使用自定义的编译时注解来实现。
首先,定义一个编译时注解:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface MethodNameCheck {
}
然后,在需要检查的方法上使用该注解:
public class MyClass {
@MethodNameCheck
public void myMethod() {
// do something
}
}
最后,编写一个注解处理器,在编译时检查被标记的方法是否符合命名规范:
@SupportedAnnotationTypes("MethodNameCheck")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MethodNameCheckProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement method = (ExecutableElement) element;
String methodName = method.getSimpleName().toString();
if (!Character.isUpperCase(methodName.charAt(0))) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Method name must start with an uppercase letter.", method);
}
}
}
}
return true;
}
}
编译时,注解处理器会在编译过程中拦截被标记的方法,并检查方法名是否符合要求。如果不符合,编译过程会报错提示。