前言:Java 注解,对于不少人都不陌生了,可是在公司的实际开发中,可能让咱们本身去定义注解并应用到生产环境中的机会比较少,因此会致使一部分人对注解的理解比较浅,在看到一些框架或者别人的代码中有注解的代码就会头大,不知道这表明的是什么意思。其实究其缘由,最主要的仍是咱们对注解的这个概念没搞清楚,因此本文通篇会串插着这些重要的概念,从几个主题入手,从浅到深的带领你们进入注解的世界,让你们在从此的代码旅程中轻松应对注解。html
注解是 Java 5 的一个新特性。先给你们梳理一个在生产环境中注解的经常使用使用流程。第一步: 定义一个注解;第二步: 在类上面或方法上面等地方使用注解;第三步: 经过注解处理器获取注解信息并作相应的处理(这个处理也就是注解真正起做用的地方了)。本篇文章的整个脉络大体也是按照这三步来写的。java
先来看几个 Java 内置的注解,让你们热热身。express
class Parent { public void run() { } } class Son extends Parent { /** * 这个注解是为了检查此方法是否真的是重写父类的方法 * 这时候就不用咱们用肉眼去观察究竟是不是重写了 */ @Override public void run() { } }
class Parent { /** * 此注解表明过期了,可是若是能够调用到,固然也能够正常使用 * 可是,此方法有可能在之后的版本升级中会被慢慢的淘汰 * 能够放在类,变量,方法上面都起做用 */ @Deprecated public void run() { } } public class JDKAnnotationDemo { public static void main(String[] args) { Parent parent = new Parent(); parent.run(); // 在编译器中此方法会显示过期标志 } }
class Parent { // 由于定义的 name 没有使用,那么编译器就会有警告,这时候使用此注解能够屏蔽掉警告 // 即任意不想看到的编译时期的警告均可以用此注解屏蔽掉,可是不推荐,有警告的代码最好仍是处理一下 @SuppressWarnings("all") private String name; }
/** * 此注解是 Java8 提出的函数式接口,接口中只容许有一个抽象方法 * 加上这个注解以后,类中多一个抽象方法或者少一个抽象方法都会报错 */ @FunctionalInterface interface Func { void run(); }
这里先提供一个比较完整的注解,即此注解内容基本包含了咱们在生产环境中经常使用的信息了,而后根据这个注解来介绍枚举的重要概念。首先看到下面这个完整注解包含三部份内容,第一部分就是 @interface 来定义注解,全部注解隐式继承 java.lang.annotation.Annotation;第二部分就是这个注解的内部信息;第三部分就是这个注解的头部,即元注解。api
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.RUNTIME) @Inherited // 此注解应用的相对较少,本文很少作介绍,可自行研究 @Documented // 此注解应用的相对较少,本文很少作介绍,可自行研究 public @interface ApiAuthAnnotation { int age(); String name() default "lyf"; AnnotationDemo annotattion() default @AnnotationDemo; Season Sason() default Season.SPRING; int[] nums() default {1, 2, 3}; Class clazz() default String.class; }
所谓注解内部信息,也就是这个注解里面都写什么,经过如上的完整注解能够很直观的看到都写了什么,其实就是一种比较特殊的方法了,带有返回值类型,没有具体实现,后面能够选填默认值。大概也就这么点内容了。其支持的数据类型有:数组
元注解是指用于注解类型上的注解。通俗来说,元注解的做用就是,规范咱们定义的注解,给这个注解一些规则。好比说: 我在注解头部没有写元注解,那么咱们自定义的这个注解在使用的时候放在类上面,方法上面,或者放在成员变量上面,没有规则,想放哪里就放哪里;可是当咱们在注解头部写了元注解,并把元注解的 Target 设置为 ElementType.METHOD,那么咱们自定义的这个注解在使用的时候就只能放在方法上面,放在类上面或者成员变量上面就会报错。浏览器
元注解的注解类型主要有以下几种:app
@Target: 表示定义的注解的做用域,其值包括:框架
@Retention: 自定义注解的生命周期,其值包括:ide
这个固然就至关简单了,哪一个地方须要使用咱们自定义的注解,那咱们在其地方添加上注解就 ok 了。以下代码: 若是要做用到类,那就在类头上加上注解;若是要做用到方法,那就在方法上加上注解。函数
@ApiAuthAnnotation(age = 16, name = "lyf") @RestController public class ApiController { @ApiAuthAnnotation(age = 20, name = "lisi") @RequestMapping(value = "/topic1", method = RequestMethod.GET) public void getByTopic1() { System.out.println("年龄大于 18 岁的能够访问此 API"); } @ApiAuthAnnotation(age = 16, name = "zhangsan") @RequestMapping(value = "/topic2", method = RequestMethod.GET) public void getByTopic2() { System.out.println("年龄小于 18 岁的不能够访问此 API"); } }
从如上代码来看,使用注解的时候则是至关的简单了,咱们这里是在类上和方法上都加上了自定义的注解了,至于这些注解加上之后怎么起做用,那就跟注解处理器有关了。
【注: 在使用注解的时候其实还有不少小的语法技巧,不少时候能够简写一些代码,不过有没有必要简写代码,这个本身去衡量,这个不是重点,就是语法,能够自行去研究下。就好比当注解只要一个元素,且其名字是 value 时,赋值的时候能够简写】
注解处理器才是使用注解整个流程中最重要的一步了。全部在代码中出现的注解,它到底起了什么做用,都是在注解处理器中定义好的。
概念:注解自己并不会对程序的编译方式产生影响,而是注解处理器起的做用;注解处理器可以经过在运行时使用反射获取在程序代码中的使用的注解信息,从而实现一些额外功能。前提是咱们自定义的注解使用的是 RetentionPolicy.RUNTIME 修饰的。这也是咱们在开发中使用频率很高的一种方式。
咱们先来了解下如何经过在运行时使用反射获取在程序中的使用的注解信息。以下类注解和方法注解。
Class aClass = ApiController.class; Annotation[] annotations = aClass.getAnnotations(); for(Annotation annotation : annotations) { if(annotation instanceof ApiAuthAnnotation) { ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation; System.out.println("name: " + apiAuthAnnotation.name()); System.out.println("age: " + apiAuthAnnotation.age()); } }
Method method = ... //经过反射获取方法对象 Annotation[] annotations = method.getDeclaredAnnotations(); for(Annotation annotation : annotations) { if(annotation instanceof ApiAuthAnnotation) { ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation; System.out.println("name: " + apiAuthAnnotation.name()); System.out.println("age: " + apiAuthAnnotation.age()); } }
此部份内容可参考: 经过反射获取注解信息
接下来我经过在公司中的一个实战改编来演示一下注解处理器的真实使用场景。
需求: 网站后台接口只能是年龄大于 18 岁的才能访问,不然不能访问
前置准备: 定义注解(这里使用上文的完整注解),使用注解(这里使用上文中使用注解的例子)
接下来要作的事情: 写一个切面,拦截浏览器访问带注解的接口,取出注解信息,判断年龄来肯定是否能够继续访问。
在 dispatcher-servlet.xml 文件中定义 aop 切面
<aop:config> <!--定义切点,切的是咱们自定义的注解--> <aop:pointcut id="apiAuthAnnotation" expression="@annotation(cn.caijiajia.devops.aspect.ApiAuthAnnotation)"/> <!--定义切面,切点是 apiAuthAnnotation,切面类即注解处理器是 apiAuthAspect,主处理逻辑在方法名为 auth 的方法中--> <aop:aspect ref="apiAuthAspect"> <aop:around method="auth" pointcut-ref="apiAuthAnnotation"/> </aop:aspect> </aop:config>
切面类处理逻辑即注解处理器代码如
@Component("apiAuthAspect") public class ApiAuthAspect { public Object auth(ProceedingJoinPoint pjp) throws Throwable { Method method = ((MethodSignature) pjp.getSignature()).getMethod(); ApiAuthAnnotation apiAuthAnnotation = method.getAnnotation(ApiAuthAnnotation.class); Integer age = apiAuthAnnotation.age(); if (age > 18) { return pjp.proceed(); } else { throw new RuntimeException("你未满18岁,禁止访问"); } } }
注解其实在咱们的代码中用的也愈来愈多了,特别是框架里面都会提供不少的注解,把注解的概念搞清楚以后,对于之后看别人的代码和框架源码都不少帮助,因此平时没事也能够多了解了解。好比咱们常用的 @Autowired 注解,其背后作了什么事情,有兴趣均可以去了解一下。再好比,权限框架 Shiro,里面也提供不少注解来管理权限,他们是怎么使用注解和处理注解的,均可以去研究下。有什么问题,欢迎老铁们一块儿交流。。。