相信大部分的开发者都用过注解,尤为是对使用过Spring的开发者来讲,注解是现代Spring中不可获取的一部分。Spring从最开始的xml配置到后面的注解配置,不管是从编程习惯仍是项目的构建,都对咱们程序员产生了很是重要的影响。 java
除了使用Spring自带的注解以外,咱们还能够自定义注解。而后经过AOP来对注解进行拦截从而处理相应的业务逻辑。git
除了Spring以外,其实JDK自己自带注解,本文将会深刻探讨注解的起源和两种不一样的使用方式。程序员
更多精彩内容且看:github
更多内容请访问 www.flydean.com
先看一个最简单的注解:apache
@CustUserAnnotation public class CustUser { }
上面咱们将CustUser标记为一个自定义的注解@CustUserAnnotation。编程
注解实际上是在JDK 5中引入的。那么在JDK 5以前,注解是用什么方式来表示的呢?答案就是marker interfaces。数组
marker interfaces中文翻译叫作标记接口,标记接口就是说这个接口使用来作标记用的,内部并无提供任何方法或者字段。app
在java中有不少标记接口,最多见的就是Cloneable,Serializable,还有java.util包中的EventListener和RandomAccess。dom
以Cloneable为例:maven
/* * @since 1.0 */ public interface Cloneable { }
该接口从java1.0就开始有了。实现该接口的类才可以调用Object中的clone方法。
咱们在代码中如何判断类是否实现了Cloneable接口呢?
public Object clone() throws CloneNotSupportedException { if (this instanceof Cloneable) { return super.clone(); } else { throw new CloneNotSupportedException(); } }
很简单,经过instanceof来判断是不是Cloneable便可。
marker interfaces好用是好用,可是有一些缺点,好比没有额外的元数据信息,功能太过单一,而且会和正常的interface混淆。实现起来也比通常的interface复杂。
正式因为这些缘由,在JDK5中,引入了注解Annotation。
注解是由@interface来定义的。建立一个annotation须要指定其target和retention,并能够自定义参数。
咱们举个例子:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CustUserAnnotation { int value(); String name(); String[] addresses(); }
上面是我自定义的一个注解。
Retention表示注解将会在什么阶段可见。它有三个可选值:
SOURCE 表示只在源代码可见,编译的时候就会被丢弃。
CLASS 表示在class可见,也就是说编译的时候可见,可是运行时候不可见。
RUNTIME 表示运行时候可见。何时才须要运行时可见呢?那就是使用到反射的时候。咱们会在后面的例子中具体的描述这种状况。
Retention自己也是一个注解:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
Target表示这个注解将会用到什么地方。它有12个值。
TYPE 表示用在Class,interface,enum或者record上。
FIELD 表示用在class的字段上。
METHOD 表示用在方法上。
PARAMETER 表示用在方法上面。
CONSTRUCTOR 用在构造函数上。
LOCAL_VARIABLE 用在本地变量上。
ANNOTATION_TYPE 用在注解上。
PACKAGE 用在package上。
TYPE_PARAMETER 用在类型参数上。
TYPE_USE 用在任何TYPE使用上。
TYPE_PARAMETER和TYPE_USE有什么区别呢?
TYPE_USE用在任何类型的使用上面,好比申明,泛型,转换:
@Encrypted String data List<@NonNull String> strings MyGraph = (@Immutable Graph) tmpGraph;
而TYPE_PARAMETER用在类型参数上:
class MyClass<T> {...}
MODULE 用在module上。
RECORD_COMPONENT 预览功能,和records相关。
Target和Retention同样也是一个注解。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
注解也能够自定参数,参数能够是下的类型:
上面咱们的自定义类型定义了三个参数:
int value(); String name(); String[] addresses();
咱们看下怎么使用:
@CustUserAnnotation(value = 100, name="jack ma",addresses = {"人民路","江西路"}) public class CustUser { }
在使用中,咱们须要传入自定义的参数,固然你也可使用default在注解中提供默认值,这样就不须要从外部传入。
在运行时,咱们可使用反射的API来得到注解,并获取注解中的自定义变量,从而进行相应的业务逻辑处理。
CustUser custUser= new CustUser(); Annotation[] annotations= custUser.getClass().getAnnotations(); Stream.of(annotations).filter(annotation -> annotation instanceof CustUserAnnotation) .forEach(annotation -> log.info(((CustUserAnnotation) annotation).name()));
仍是刚才的例子,咱们经过getAnnotations方法获取到注解的值。
在运行时是用注解固然是个不错的主意,可是反射用的太多的话其实会影响程序的性能。
那么咱们能够不能够将运行时的注解提早到编译时呢?答案是确定的。
要想在编译时使用注解,就要介绍今天咱们的最后一部份内容annotation processors。
自定义processors须要实现javax.annotation.processing.Processor接口。
接下来咱们自定义一个Processor:
@SupportedAnnotationTypes("com.flydean.*") @SupportedSourceVersion(SourceVersion.RELEASE_14) public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { System.out.println("process annotation!"); annotations.forEach(annotation -> { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation); elements.stream() .filter(TypeElement.class::isInstance) .map(TypeElement.class::cast) .map(TypeElement::getQualifiedName) .map(name -> "Class " + name + " is annotated with " + annotation.getQualifiedName()) .forEach(System.out::println); }); return true; } }
SupportedAnnotationTypes表示支持的注解类型。
SupportedSourceVersion表示支持的源代码版本。
最后咱们在process方法中,获取了注解类的一些信息。
有了processor咱们怎么在maven环境中使用呢?
最简单的办法就是在maven的maven-compiler-plugin插件中添加annotationProcessors,以下所示:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>14</source> <target>14</target> <annotationProcessors> <annotationProcessor>com.flydean.MyProcessor</annotationProcessor> </annotationProcessors> </configuration> </plugin> </plugins> </build>
若是不添加,默认状况下编译器会从classpath中去寻找META-INF/services/javax.annotation.processing.Processor文件,这个文件里面列出了对外提供的注解处理器。编译器会加载这些注解处理器去处理当前项目的注解。
lombok应该你们都用过吧,它实际上为咱们提供了两个注解处理器:
很不幸的是,由于我在CustUser中使用了lombok中的log,若是像上面同样显示指定annotationProcessor则会将覆盖默认的查找路径,最后会致使lombok失效。
那应该怎么处理才能兼容lombok和自定义的processor呢?
咱们能够把自定义processor单独成一个模块,也作成lombok这样的形式:
这个processor的模块编译参数须要加上一个proc none的参数:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>14</source> <target>14</target> <proc>none</proc> </configuration> </plugin> </plugins> </build>
proc是设置是否须要在本项目中启用processor。对于processor项目来讲,它自己尚未编译,若是启用就会出现找不到类的错误。因此这里咱们须要将proc设置为none。
最后咱们的annotation-usage项目能够不须要annotationProcessors的配置就能够自动从classpath中读取到自定义的processor了。
本文介绍了marker interface,annotation和annotation processor,并详细讲解了如何在maven程序中使用他们。
本文的例子[https://github.com/ddean2009/
learn-java-base-9-to-20](https://github.com/ddean2009/...
本文做者:flydean程序那些事本文连接:http://www.flydean.com/marker-interface-annotation-processor/
本文来源:flydean的博客
欢迎关注个人公众号:程序那些事,更多精彩等着您!