文章目录
前言
这两天在gitHub上看一个开源项目,发现项目中有许多自定义注解。虽然在学JavaSE的时候学个注解,但是主要讲的是如何自定义注解,和一些元注解的知识点。并没有涉及到注解如何实现具体的功能。直到看到这个项目,突然醍醐灌顶。
由于篇幅过长,分成两篇来写。
复习注解
注解是在Java 1.5 的时候被引入的,注解的创建与接口十分相似。就是在interface
关键字前面加一个@
符号。
public @interface MyAnnotation{
}
创建玩注解之后就可以在想要添加主机的地方使用了,但是想要让注解能够正常工作,还需要给它化化妆。什么是化妆那,这里就要引入元注解的一个概念了,元注解他也是一个注解,只不过它是用来修饰注解的注解。有点迷?不慌咱慢慢看。
元注解
元注解一共有五种分别为:
- @Retention
- @Documented
- @Target
- @Inherited
- @Repeatable
他们的使用方法就是在创建注解的时候在,所要创建的注解上使用他们:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
... ...
public @interface MyAnnotation{
}
@Retention 存活时间
Retention中文意思是保留的意思。当@Retention
应用到一个注解上的时候 ,通过参数可以设置这个注解的存活时间。什么是存活时间,就是说通过不同参数可以确定@Retention
修饰的注解在那种情况下会消失,是保留到源码阶段、还是编译阶段、还是加载到JVM那。
下面看一下它的参数:
- RetentionPolicy.SOURCE
注解只被保留到编译阶段,当编译器编译到它的时候看到参数为
RetentionPolicy.SOURCE
会直接将其从源码中剔除。
- RetentionPolicy.SOURCE
注解制备保留到编译进行的时候,也就是能保证在编译期间注解也存在,但是不能进入JVM中。
- RetentionPolicy.SOURCE
注解可以保留到程序中,它会被加载到JVM中从而在服务中起作用。
@Documented 文档
这个注解,翻译过来就是文档。那他并没有其他功能上的作用,主要作用就是能讲注解中的元素包含到Javadoc中去。
@Target 目标
Target 有目标的意思,他的意思就是说,这个注解能够使用的地方是哪里,是方法上、类上还是参数上。我们通过它的参数就可以进行设置。
参数 | 作用 |
---|---|
ElementType.ANNOTATION_TYPE | 可以使用在注解上 |
ElementType.CONSTRUCTOR | 可以在构造方法上使用 |
ElementType.FIELD | 可以在属性上使用注解 |
ElementType.LOCAL_VARIABLE | 作用在局部变量上 |
ElementType.METHOD | 作用在方法上 |
ElementType.PACKAGE | 作用在包上 |
ElementType.PARAMETER | 作用在方法内的参数上 |
ElementType.TYPE | 可以给类型进行注解,比如类、接口、枚举 |
@Inherited 继承
Inherited 意思为继承,这个元注解的作用有点绕。就是说如果@Inherited
注解作用与自定义注解@MyAnnotation
上,然后在A类上使用了自定义注解,然后A类的子类,就相当于也拥有@MyAnnotation
注解。
/*自定义注解*/
@Inherited
public @interface MyAnnotation{
}
---------------------------
/*A类*/
@MyAnnotation
public class A{
}
/*B类继承A类*/
public class B extent A{
}
/*
//此时,B类也继承了A类的注解。相当于
@MyAnnotation
public class B extent A{
}
*/
@Repeatable 可重复
这个注解是在Java 1.8的时候加进来的。那可重复是什么意思那,就是说使用注解的时候可以同时使用多次注解。
public class A{
@MyAnnotation("教师")
@MyAnnotation("公务员")
public void test(){
}
}
如果想实现这样的效果,那么就需要使用@Repeatable
注解修饰@MyAnnotation
注解。@MyAnnotation
才可以在一个方法或其它地方使用多次。
注解属性
注解的属性其实就好像实体类里面的属性,只是写法上有稍微的不同。但是注解是没有方法的,
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
... ...
public @interface MyAnnotation{
int id();
String value();
}
注意这里id()
和value()
表示的不是方法而是属性也可叫做注解的变量。
注解的赋值方法就是在使用注解的时候,通过注解中的对应属性="xxx"
的形式
@MyAnnotation(id=101,value="Hello Annotation")
public void test(){
}
注解属性可以设置默认值,也就是如果使用注解时没有赋予特定的值,就使用默认值。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
... ...
public @interface MyAnnotation{
int id() default 101;
String value();
}
这里还有一个注意点,如果注解中只有一个名字为value的属性的时候,应用这个注解的时候可以直接填写属性值。
pulic @interface MyAnnotation{
String value;
}
@MyAnnotation("Hi")
public void test(){
}
如果定义的这个注解中没有属性值,我们在使用注解的时候就不用在写括号了
@MyAnnotation
public void test(){
}
注解实现-反射
注解的知识经过上面的内容,应该能够有个了解。那么我们定义好后注解,也是用了,但是并没有什么实质的效果。哪有人该说了那定义有什么用?其实不然,如果真的没有用那么Java官方就不会定义注解了。
好下面我们就给自定义的注解注入灵魂,实现当使用注解后完成相应的功能。那该如何实现那,注解中又不能写方法,之有属性值, 我们也不能通过new获取注解。这里就要提到一个比较重要的知识点就是反射。
我们可以通过反射获取作用在类或者方法上的注解让,后进行对应的处理。
反射获取注解方法
注解通过反射获取。首先通过class对象的isAnnotationPresent()
方法判断他是否应用了某注解。
public Boolean isAnnotationPresent(Class<? extent Annotation> annotationClass) {}
然后通过getAnnotation()方法来获取Annotation对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
或者是getAnnotations()方法
public Annotation[] getAnnotations(){}
前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
如果获取到的Annotation如果不为null,则就可以调用他们的属性方法了。比如
@MyAnnotation()
public class Test{
public static void main(String[] agrs){
boolean hasAnnotation = Test.class.isAnnotationPressent(TestAnnotation.class);
if(hasAnnotation){
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println('id:'+testAnnotation.id());
}
}
}
运行结果:
id:101
上面是获取类上的注解,其实方法、属性的注解都是可以通过反射进行获取的。
自定注解:
package com.zhao.annotationaop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "Hello Annotation";
}
编写测试类
public class TestAnnotation {
@MyAnnotation("Hi")
public void testMethod(){
System.out.println("fun");
}
public static void main(String[] args) throws NoSuchMethodException {
Class<TestAnnotation> aClass = TestAnnotation.class;
Method msg = aClass.getDeclaredMethod("testMethod");
msg.setAccessible(true);
MyAnnotation annotation = msg.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value());
}
}
运行结果
Hi
这里需要注意一下,如果想要注解在运行时能够获取到,那么必须加上@Retention(RetentionPolicy.RUNTIME)
这个注解参数,因为这个参数是指,注解能在jvm中被执行。
注解的使用场景
到这里应该大家对注解都有一定的了解了,但是还是可能会有疑惑注解到底有什么用呐。
我们不妨先看看官方的回答:
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
值得注意的是,注解不是代码本身的一部分。
从官方的话我们可以看出注解的作用并不是来写主要业务的,而是通过注解实现一些对代码的处理。
如果大家用过LomBok在实体类上面加一个@Data
就可以帮助我们生成实体类的get和set的方法,在或者说Swagger生成接口文档,在对应的接口上加上对应的注解就可以实现生成对应的接口文档。注解只是起到到了标签的作用,具体的实现方式是有对应的程序获取到注解这个标识,然后去处理产生的。
那么我们可以用注解做什么呢?
现在我们就动手写一个自己的注解,通过这个注解来检测程序是否报错。这是一个简单的小案例。
首先写一个注解@Jiance
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Jiance {
}
要测试的程序Cheshi
并在方法上加上注解
public class Cheshi {
@Jiance
public void suanShu(){
System.out.println("1234567890");
}
@Jiance
public void jiafa(){
System.out.println("1+1="+1+1);
}
@Jiance
public void jiefa(){
System.out.println("1-1="+(1-1));
}
@Jiance
public void chengfa(){
System.out.println("3 x 5="+ 3*5);
}
@Jiance
public void chufa(){
System.out.println("6 / 0="+ 6 / 0);
}
public void ziwojieshao(){
System.out.println("我写的程序没有 bug!");
}
}
运行检测程序TestJiance
public class TestJiance {
public static void main(String[] args) {
Cheshi cheshi = new Cheshi();
Class<Cheshi> cheshiClass = Cheshi.class;
Method[] declaredMethods = cheshiClass.getDeclaredMethods();
StringBuilder log = new StringBuilder();
log.append("**************日志****************\n");
int num = 0;
for (Method declaredMethod : declaredMethods) {
Jiance annotation = declaredMethod.getAnnotation(Jiance.class);
if (annotation!=null){
try {
declaredMethod.setAccessible(true);
declaredMethod.invoke(cheshi,null);
} catch (Exception e) {
num++;
log.append(declaredMethod.getName()+":error:"+e.getCause().getMessage()+"\n");
}
}
}
log.append("cheshi has "+num+ " error");
System.out.println(log);
}
}
执行结果:
1+1=11
1234567890
1-1=0
3 x 5=15
**************日志****************
chufa:error:/ by zero
cheshi has 1 error
这样我们就成了这小的案例。我们通过我们自定义的注解,来检测所有cheshi
类中有错的方法。
注解的作用主要取决于你想用它做什么。
后续
利用反射获取注解并实现功能通过上面已经实现了,但是还有一种方式也可以获取注解,并实现对应的功能逻辑。
那就是利用Spring框架的Aop来实现,AOP面向切面编程,我们可以利用AOP做很多事情。那如果AOP遇到注解会发生什么那。
下一篇我们利用自定义注解和Aop实现接口防刷的功能。也就是在一段时间内如果大量访问接口,就会触发保护的一个案例。
End!!! 如有疑问请留言评论!