Java 注解实战

前言:Java 注解,对于不少人都不陌生了,可是在公司的实际开发中,可能让咱们本身去定义注解并应用到生产环境中的机会比较少,因此会致使一部分人对注解的理解比较浅,在看到一些框架或者别人的代码中有注解的代码就会头大,不知道这表明的是什么意思。其实究其缘由,最主要的仍是咱们对注解的这个概念没搞清楚,因此本文通篇会串插着这些重要的概念,从几个主题入手,从浅到深的带领你们进入注解的世界,让你们在从此的代码旅程中轻松应对注解。html

注解是 Java 5 的一个新特性。先给你们梳理一个在生产环境中注解的经常使用使用流程。第一步: 定义一个注解;第二步: 在类上面或方法上面等地方使用注解;第三步: 经过注解处理器获取注解信息并作相应的处理(这个处理也就是注解真正起做用的地方了)。本篇文章的整个脉络大体也是按照这三步来写的。java

JDK 内置注解

先来看几个 Java 内置的注解,让你们热热身。express

  • @Override 演示
class Parent {
    public void run() {
    }
}

class Son extends Parent {
    /**
     * 这个注解是为了检查此方法是否真的是重写父类的方法
     * 这时候就不用咱们用肉眼去观察究竟是不是重写了
     */
    @Override
    public void run() {
    }
}
  • @Deprecated 演示
class Parent {

    /**
     * 此注解表明过期了,可是若是能够调用到,固然也能够正常使用
     * 可是,此方法有可能在之后的版本升级中会被慢慢的淘汰
     * 能够放在类,变量,方法上面都起做用
     */
    @Deprecated
    public void run() {
    }
}

public class JDKAnnotationDemo {
    public static void main(String[] args) {
        Parent parent = new Parent();
        parent.run(); // 在编译器中此方法会显示过期标志
    }
}
  • @SuppressWarnings 演示
class Parent {

    // 由于定义的 name 没有使用,那么编译器就会有警告,这时候使用此注解能够屏蔽掉警告
    // 即任意不想看到的编译时期的警告均可以用此注解屏蔽掉,可是不推荐,有警告的代码最好仍是处理一下
    @SuppressWarnings("all")
    private String name;
}
  • @FunctionalInterface 演示
/**
 * 此注解是 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;
}

注解内部信息

所谓注解内部信息,也就是这个注解里面都写什么,经过如上的完整注解能够很直观的看到都写了什么,其实就是一种比较特殊的方法了,带有返回值类型,没有具体实现,后面能够选填默认值。大概也就这么点内容了。其支持的数据类型有:数组

  • 基本数据类型
  • java.lang.String
  • 注解
  • 枚举
  • 一维数组
  • java.lang.Class

元注解

元注解是指用于注解类型上的注解。通俗来说,元注解的做用就是,规范咱们定义的注解,给这个注解一些规则。好比说: 我在注解头部没有写元注解,那么咱们自定义的这个注解在使用的时候放在类上面,方法上面,或者放在成员变量上面,没有规则,想放哪里就放哪里;可是当咱们在注解头部写了元注解,并把元注解的 Target 设置为 ElementType.METHOD,那么咱们自定义的这个注解在使用的时候就只能放在方法上面,放在类上面或者成员变量上面就会报错。浏览器

元注解的注解类型主要有以下几种:app

  • @Target: 表示定义的注解的做用域,其值包括:框架

    • CONSTRUCTOR: 构造方法声明;
    • FIELD: 属性/字段声明;
    • LOCAL_VARIABLE: 局部变量声明;
    • METHOD: 方法声明;
    • PACKAGE: 包声明;
    • PARAMETER: 参数声明;
    • TYPE: 类接口声明;
    • ANNOTATION_TYPE: 注解类型声明;
    • TYPE_PARAMETER: 类型参数声明(jdk1.8 提供);
    • TYPE_USE: 类型使用(jdk1.8 提供)
  • @Retention: 自定义注解的生命周期,其值包括:ide

    • SOURCE: 只在源码显示,编译时丢弃;(好比 jdk 自带的 @SuppressWarnings 和 @Override)
    • CLASS: 编译时记录到.class中,运行时忽略;
    • RUNTIME: 运行时存在,可经过反射来读取。(很重要,生产中咱们开发经常使用此值)
  • @Inherited: 表示注解是否可被子元素继承。
  • @Documented: 表示 javadoc 是否为实例产生文档

注意事项

  • 注解不能继承另外一个注解
  • 方法不能有参数
  • 方法不能抛出异常
  • 不能定义 Object 或 Annotation 接口中的方法(由于注解都隐式继承 Annotation 接口)
  • 注解不能是泛型
  • 使用 default 指定属性的默认值
  • 不能使用 null 做为属性的默认值

使用注解

这个固然就至关简单了,哪一个地方须要使用咱们自定义的注解,那咱们在其地方添加上注解就 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,里面也提供不少注解来管理权限,他们是怎么使用注解和处理注解的,均可以去研究下。有什么问题,欢迎老铁们一块儿交流。。。

相关文章
相关标签/搜索