文章目录
前言复习注解元注解@Retention 存活时间@Documented 文档@Target 目标@Inherited 继承@Repeatable 可重复注解属性注解实现-反射反射获取注解方法注解的使用场景后续导航前言
这两天在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
会直接将其从源码中剔除。
RetentionPolicy.SOURCE注解制备保留到编译进行的时候,也就是能保证在编译期间注解也存在,但是不能进入JVM中。
注解可以保留到程序中,它会被加载到JVM中从而在服务中起作用。
@Documented 文档
这个注解,翻译过来就是文档。那他并没有其他功能上的作用,主要作用就是能讲注解中的元素包含到Javadoc中去。
@Target 目标
Target 有目标的意思,他的意思就是说,这个注解能够使用的地方是哪里,是方法上、类上还是参数上。我们通过它的参数就可以进行设置。
@Inherited 继承
Inherited 意思为继承,这个元注解的作用有点绕。就是说如果@Inherited
注解作用与自定义注解@MyAnnotation
上,然后在A类上使用了自定义注解,然后A类的子类,就相当于也拥有@MyAnnotation
注解。
/*自定义注解*/@Inherited public @interface MyAnnotation{}---------------------------/*A类*/@MyAnnotationpublic class A{}/*B类继承A类*/public class B extent A{}/*//此时,B类也继承了A类的注解。相当于@MyAnnotationpublic 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(){}
如果定义的这个注解中没有属性值,我们在使用注解的时候就不用在写括号了
@MyAnnotationpublic 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 {@Jiancepublic void suanShu(){System.out.println("1234567890");}@Jiancepublic void jiafa(){System.out.println("1+1="+1+1);}@Jiancepublic void jiefa(){System.out.println("1-1="+(1-1));}@Jiancepublic void chengfa(){System.out.println("3 x 5="+ 3*5);}@Jiancepublic 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=1112345678901-1=03 x 5=15**************日志****************chufa:error:/ by zerocheshi has 1 error
这样我们就成了这小的案例。我们通过我们自定义的注解,来检测所有cheshi
类中有错的方法。
注解的作用主要取决于你想用它做什么。
后续
利用反射获取注解并实现功能通过上面已经实现了,但是还有一种方式也可以获取注解,并实现对应的功能逻辑。
那就是利用Spring框架的Aop来实现,AOP面向切面编程,我们可以利用AOP做很多事情。那如果AOP遇到注解会发生什么那。
下一篇我们利用自定义注解和Aop实现接口防刷的功能。也就是在一段时间内如果大量访问接口,就会触发保护的一个案例。
End!!!如有疑问请留言评论!
导航
接口防刷案例【传送门】