注解概念
Java注解是一种元数据,可以为程序中的类、方法、变量等元素添加额外的信息。注解不可以继承,所有注解默认拓展
java.lang.annotation.Annotation
接口。
使用注解分为两步:
- 自定义一个注解,这个注解可以放在指定的位置,如下例子可以放在类上,此时标注的类不会产生效果。
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 可以有默认值,如果没有默认值强制填入值,否则编译错误,且不能为null
int vlaue() default 0;
}
- 创建并使用注解处理器,Java为反射API提供了拓展,同时也提供了
javac
编译器的钩子,用于编译时使用注解。
元注解
元注解是为了注解进行注解。Java中有5个元注解
注解 | 效果 |
---|---|
@Target |
指明该注解可使用的位置,ElementType 枚举相关参数。 |
@Retention |
注解信息可以保存周期,RetentionPolicy 枚举有三种策略:SOURCE、CLASS、RUNTIME。SOUCE: 注解在编译完之后被丢弃;CLASS:注解在类文件中,但是虚拟机不需要将它们载入;RUNTIME:注解在运行时任被虚拟机保留,可以使用反射读取到注解的相关信息。 |
@Documented |
在Javadoc中引入该注解,将注解归档 |
@Inherited |
允许子类继承父类注解,当这个注解应用在类上时候,子类可以继承该注解 |
@Repeatable |
多次应用同一个声明 |
@Native |
表示可以从本地代码引用定义常量值的字段 |
@Repeatable
注解示例:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(AnimeCharacters.class)
public @interface AnimeCharacter {
String character();
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimeCharacters {
AnimeCharacter[] value();
}
其中AnimeCharacters
注解的value类型只能是AnimeCharacter[]
数组类型,这是@Repeatable
注解限制的。@Repeatable
所声明的注解,其元注解@Target
的使用范围要比@Repeatable
的值声明的注解中的@Target
的范围要大或相同,否则编译器错误
标准注解
注解 | 应用场合 | 目的 |
---|---|---|
@Deprecated |
全部 | 将项标记为过时的 |
@SuppressWarnings |
除了包和注解之外的所有情况 | 阻止某个给定类型的警告信息 |
@SafeVarargs |
方法和构造器 | 断言varargs参数可安全使用的 |
@Override |
方法 | 检查该方法是否覆盖了某个超类中的方法 |
@FunctionInterface |
接口 | 将接口标记为只有一个抽象方法的函数式接口 |
@Resource |
方法、接口、类、作用域 | 在类或接口上:标记为在其他地方要用到的资源;在方法或域上:为‘注入’而标记 |
@Resources |
类、接口 | 一个资源数组 |
@Generated |
全部 | 生成的注释用于标记已生成的源代码。它还可以用来区分用户编写的代码和单个文件中生成的代码 |
@PostConstruct |
方法 | 被标记的方法在构造器之后执行,在servlet中使用 |
@PreDestroy |
方法 | 被标记的方法在移除之前使用,在servlet中使用 |
@SupportedAnnotationTypes |
类 | 用于指示注释处理器支持的注释类型 |
@SupportedSourceVersion |
类 | 用于指示注释处理器支持的最新JDK版本 |
@SupportedOptions |
类 | 用于指示注释处理器支持哪些选项 |
其中前五种为java.lang
包中的注解,其余的为javax.annotation
包中的注解。
注解使用
RetentionPolicy.RUNTIME
策略使用反射处理注解
示例1:使用@Repeatable
注解
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(AnimeCharacters.class)
public @interface AnimeCharacter {
String character();
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimeCharacters {
AnimeCharacter[] value();
}
public class Anime {
private String amineName;
@AnimeCharacters({@AnimeCharacter(character = "加藤惠"), @AnimeCharacter(character = "安艺伦也")})
// @AnimeCharacter(character = "霞之丘诗羽")
// @AnimeCharacter(character = "英梨梨")
public String[] animeCharacters;
public String getAmineName() {
return amineName;
}
public void setAmineName(String amineName) {
this.amineName = amineName;
}
public String[] getAnimeCharacters() {
return animeCharacters;
}
public void setAnimeCharacters(String[] animeCharacters) {
this.animeCharacters = animeCharacters;
}
@Override
public String toString() {
return "Anime{" +
"amineName='" + amineName + ''' +
", animeCharacters=" + Arrays.toString(animeCharacters) +
'}';
}
}
public class AnimeCharactersResolver {
public static void main(String[] args) {
Anime anime = new Anime();
anime.setAmineName("路人女主的养成方法");
resolver(anime);
System.out.println(anime);
}
public static void resolver(Object source) {
Field[] declaredFields = source.getClass().getDeclaredFields();
try {
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
AnimeCharacter[] annotationsByType = declaredField.getAnnotationsByType(AnimeCharacter.class);
if (annotationsByType.length == 0) {
continue;
}
if (!declaredField.getType().equals(String[].class)) {
throw new IllegalArgumentException("参数不正确!");
}
List<String> list = new ArrayList<>();
for (AnimeCharacter animeCharacter : annotationsByType) {
list.add(animeCharacter.character());
}
String[] names = list.toArray(new String[0]);
declaredField.set(source, names);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
示例2: 所有类型注解类型示例
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
// 字符串
String hello();
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
// 基本类型
int value() default 1;
// 枚举类型
Status status();
// 类
Class<Anime> anime();
// 字符串数组类型
String[] names();
// 注解类型
Hello hello() default @Hello(hello = "MyAnnotation");
/** 枚举类**/
enum Status{
/**
* 枚举类型
*/
SUCCESS("成功"),FAILED("失败");
private final String status;
Status(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
}
}
@MyAnnotation(value = 24, status = MyAnnotation.Status.SUCCESS,
anime = Anime.class, names = {"加藤惠", "拂晓银砾"})
public class HelloWord {
public String status;
private int value;
private Anime anime;
private String[] names;
private String hello;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Anime getAnime() {
return anime;
}
public void setAnime(Anime anime) {
this.anime = anime;
}
public String[] getNames() {
return names;
}
public void setNames(String[] names) {
this.names = names;
}
public String getHello() {
return hello;
}
public void setHello(String hello) {
this.hello = hello;
}
@Override
public String toString() {
return "HelloWord{" +
"status='" + status + ''' +
", value=" + value +
", anime=" + anime +
", names=" + Arrays.toString(names) +
", hello='" + hello + ''' +
'}';
}
@Hello(hello = "SonHelloWord")
public static class SonHelloWord extends HelloWord {
private int sonValue;
public int getSonValue() {
return sonValue;
}
public void setSonValue(int sonValue) {
this.sonValue = sonValue;
}
@Override
public String toString() {
return "SonHelloWord{" +
"sonValue=" + sonValue +
", status='" + status + ''' +
", value=" + getValue() +
", anime=" + getAnime() +
", names=" + Arrays.toString(getNames()) +
", hello='" + getHello() + ''' +
'}';
}
}
}
public class HelloWordResolver {
public static void main(String[] args) {
HelloWord helloWord = new HelloWord();
resolver(helloWord);
System.out.println(helloWord);
HelloWord.SonHelloWord sonHelloWord = new HelloWord.SonHelloWord();
resolver(sonHelloWord);
System.out.println(sonHelloWord);
}
private static void resolver(Object sourceObject) {
// 获取父类以及自身注解,如果没有 @Inherited 将获取不到 MyAnnotation注解
MyAnnotation annotation = sourceObject.getClass().getAnnotation(MyAnnotation.class);
// 获取自身的注解,这里获取不到父类的注解
// MyAnnotation annotation = sourceObject.getClass().getDeclaredAnnotation(MyAnnotation.class);
Hello hello = sourceObject.getClass().getDeclaredAnnotation(Hello.class);
if (annotation == null) {
System.out.println("没有该注解");
return;
}
// =================通过域的方式注入值==========================
// 通过域无法修改父类的私有域,属于反射的内容
// 获取公共域以及父类公共域
// Field[] fields = sourceObject.getClass().getFields();
// 获取私有域以及自身的公共域
Field[] fields = sourceObject.getClass().getDeclaredFields();
// setValueByField(fields, sourceObject, annotation, hello);
// ==================通过方法反射注入值===================================
// 通过方法可以修改父类的私有域
Method[] methods = sourceObject.getClass().getMethods();
setValueByMethod(methods, sourceObject, annotation, hello);
}
private static void setValueByMethod(Method[] methods, Object sourceObject,
MyAnnotation annotation, Hello hello) {
if (methods.length == 0) {
return;
}
String prefix = "set";
try {
for (Method method : methods) {
if (method.getName().equalsIgnoreCase(prefix + "value")) {
method.invoke(sourceObject, annotation.value());
continue;
}
if (method.getName().equalsIgnoreCase(prefix + "hello")) {
if (hello != null) {
method.invoke(sourceObject, hello.hello());
} else {
method.invoke(sourceObject, annotation.hello().hello());
}
continue;
}
if (method.getName().equalsIgnoreCase(prefix + "sonValue")) {
method.invoke(sourceObject, annotation.value());
continue;
}
if (method.getName().equalsIgnoreCase(prefix + "status")) {
method.invoke(sourceObject, annotation.status().getStatus());
continue;
}
if (method.getName().equalsIgnoreCase(prefix + "names")) {
method.invoke(sourceObject, (Object) annotation.names());
continue;
}
if (method.getName().equalsIgnoreCase(prefix + "value")) {
method.invoke(sourceObject, annotation.value());
continue;
}
if (method.getName().equalsIgnoreCase(prefix + "anime")) {
Anime anime = annotation.anime().newInstance();
anime.setAmineName("路人女主的养成方法");
AnimeCharactersResolver.resolver(anime);
method.invoke(sourceObject, anime);
}
}
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
private static void setValueByField(Field[] fields, Object sourceObject,
MyAnnotation annotation, Hello hello) {
try {
for (Field declaredField : fields) {
declaredField.setAccessible(true);
// 子类拥有的私有域
if ("hello".equals(declaredField.getName())) {
if (hello != null) {
declaredField.set(sourceObject, hello.hello());
} else {
declaredField.set(sourceObject, annotation.hello().hello());
}
continue;
}
if ("sonValue".equals(declaredField.getName())) {
declaredField.set(sourceObject, annotation.value());
continue;
}
// value 域为公共的域,使用 sourceObject.getClass().getFields(); 可以获取
if ("value".equals(declaredField.getName())) {
declaredField.set(sourceObject, annotation.value());
continue;
}
if ("anime".equals(declaredField.getName())) {
Anime anime = annotation.anime().newInstance();
anime.setAmineName("路人女主的养成方法");
AnimeCharactersResolver.resolver(anime);
declaredField.set(sourceObject, anime);
continue;
}
if ("names".equals(declaredField.getName())) {
declaredField.set(sourceObject, annotation.names());
continue;
}
if ("status".equals(declaredField.getName())) {
declaredField.set(sourceObject, annotation.status().getStatus());
}
}
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
RetentionPolicy.SOURCE
策略继承AbstractProcessor
类创建编译时注释处理器
通过javac,可以创建编译时处理器,并将注解应用于Java源文件,而不是编译后的类文件。有个重要限制: 那就是无法通过注释处理器来修改源代码,唯一的影响就是创建新文件。 不过在JDK8时候,javac修改源文件会报错但还是修改了文件。
lombok
中的注解@Data
、@ToString
、@Getter
、@Setter
等注解的保留策略都是RetentionPolicy.SOURCE
,在编译期间进行行为处理,减少系统运行的反射性能消耗,在JDK11之后的JDK将lib
文件夹下中的tools.jar
以及dt.jar
包删除了,无法使用com.sun.tools
包。学识尚浅,还搞不明白lombok
如何在编译时修改源文件。
示例3: 注释处理器
- 自定义注解
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Comment {
String value();
}
- 新建一个类继承
AbstractProcessor
类并实现process()
方法
package com.example.annotation;
@Comment("Info")
public class Info {
private String name;
private String info;
private Integer age;
private String content;
public String printInfo(){
return " private String name; private String info; private Integer age; private String content;";
}
}
package com.example.annotation;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* description:
* 这是个错误的示范,修改源文件,仅供参考
* @author DawnStar
* date: 2023/6/12
*/
@SupportedAnnotationTypes("com.example.annotation.Comment")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CommentProcessor extends AbstractProcessor {
private ProcessingEnvironment processingEnv;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
this.processingEnv = processingEnv;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(Comment.class);
for (Element element : elementsAnnotatedWith) {
Comment annotation = element.getAnnotation(Comment.class);
List<String> fields = new ArrayList<>();
List<String> importPackages = new ArrayList<>();
for (Element enclosedElement : element.getEnclosedElements()) {
StringBuilder stringBuilder = new StringBuilder();
if (enclosedElement.getKind().equals(ElementKind.FIELD)) {
String type = enclosedElement.asType().toString();
importPackages.add(type.substring(0, type.lastIndexOf(".")));
for (Modifier modifier : enclosedElement.getModifiers()) {
stringBuilder.append(modifier.name().toLowerCase()).append(" ");
}
stringBuilder.append(type.substring(type.lastIndexOf(".") + 1)).append(" ");
stringBuilder.append(enclosedElement.getSimpleName());
fields.add(stringBuilder.toString());
}
}
PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(element);
writeFile(fields, packageOf.toString(), annotation.value(), importPackages);
}
return false;
}
private void writeFile(List<String> fields, String packageName, String fileName, List<String> importPackages) {
// createSourceFile通过.分隔符来创建文件夹
try (Writer writer = processingEnv.getFiler().createSourceFile(packageName + "." + fileName).openWriter()) {
writer.write("package " + packageName + ";\n\n");
List<String> targetPackages = importPackages.stream().distinct().filter(s -> !s.startsWith("java.lang")).collect(Collectors.toList());
for (String targetPackage : targetPackages) {
writer.write("import " + targetPackage + ";\n");
}
writer.write("\n");
writer.write("@Comment("" + fileName + "")\n");
writer.write("public class " + fileName + " {\n\n");
for (String field : fields) {
writer.write(" " + field + ";\n");
}
writer.write("\n public String printInfo(){\n");
writer.write(" return "");
for (String field : fields) {
writer.write(" " + field + ";");
}
writer.write("";");
writer.write("\n }");
writer.write("\n}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- 如果不打包成jar包,则执行以下命令即可看到效果,如下图
javac com/example/annotation/CommentProcessor.java
javac -processor com.exmple.annotation.CommentProcessor com/example/annotation/Info.java
- 在resources/META-INF/services文件下添加文件名
javax.annotation.processing.Processor
- 如果是MAVEN,在
pom.xml
添加以下配置:
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.version>3.8.1</maven.compiler.version>
</properties>
<build>
<!--打包的jar包名称-->
<finalName>my-java-base</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<source>17</source>
<target>17</target>
</configuration>
</execution>
<execution>
<id>default-testCompile</id>
<configuration>
<!--Java版本--->
<source>17</source>
<target>17</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
- package,在其他模块引入,编译的时候,有
@Comment
注解的Java文件就会被修改。
参考文档
《ON JAVA中文版 进阶卷》
《Java核心技术 卷Ⅱ》
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END