资讯专栏INFORMATION COLUMN

Java 注解实战

Jochen / 1765人阅读

摘要:注解是的一个新特性。很重要,生产中我们开发常用此值表示注解是否可被子元素继承。类注解方法注解通过反射获取方法对象此部分内容可参考通过反射获取注解信息注解处理器实战接下来我通过在公司中的一个实战改编来演示一下注解处理器的真实使用场景。

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

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

JDK 内置注解

先来看几个 Java 内置的注解,让大家热热身。

@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;第二部分就是这个注解的内部信息;第三部分就是这个注解的头部,即元注解。

@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,那么我们自定义的这个注解在使用的时候就只能放在方法上面,放在类上面或者成员变量上面就会报错。

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

@Target: 表示定义的注解的作用域,其值包括:

CONSTRUCTOR: 构造方法声明;

FIELD: 属性/字段声明;

LOCAL_VARIABLE: 局部变量声明;

METHOD: 方法声明;

PACKAGE: 包声明;

PARAMETER: 参数声明;

TYPE: 类接口声明;

ANNOTATION_TYPE: 注解类型声明;

TYPE_PARAMETER: 类型参数声明(jdk1.8 提供);

TYPE_USE: 类型使用(jdk1.8 提供)

@Retention: 自定义注解的生命周期,其值包括:

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 切面


    
    
    
    
        
    

切面类处理逻辑即注解处理器代码如

@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,里面也提供很多注解来管理权限,他们是怎么使用注解和处理注解的,都可以去研究下。有什么问题,欢迎老铁们一起交流。。。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70707.html

相关文章

  • Spring Boot 最核心的 3 个注解详解

    摘要:核心注解讲解最大的特点是无需配置文件,能自动扫描包路径装载并注入对象,并能做到根据下的包自动配置。所以最核心的个注解就是这是添加的一个注解,用来代替配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在类来进行注册。 最近面试一些 Java 开发者,他们其中有些在公司实际用过 Spring Boot, 有些是自己兴趣爱好在业余自己学习过。然而,当我问他们 Spring Boo...

    hzx 评论0 收藏0
  • Spring AOP就是这么简单啦

    摘要:是一种特殊的增强切面切面由切点和增强通知组成,它既包括了横切逻辑的定义也包括了连接点的定义。实际上,一个的实现被拆分到多个类中在中声明切面我们知道注解很方便,但是,要想使用注解的方式使用就必须要有源码因为我们要 前言 只有光头才能变强 上一篇已经讲解了Spring IOC知识点一网打尽!,这篇主要是讲解Spring的AOP模块~ 之前我已经写过一篇关于AOP的文章了,那篇把比较重要的知...

    Jacendfeng 评论0 收藏0
  • 第12章 元编程与注解、反射 《Kotlin 项目实战开发》

    摘要:第章元编程与注解反射反射是在运行时获取类的函数方法属性父类接口注解元数据泛型信息等类的内部信息的机制。本章介绍中的注解与反射编程的相关内容。元编程本质上是一种对源代码本身进行高层次抽象的编码技术。反射是促进元编程的一种很有价值的语言特性。 第12章 元编程与注解、反射 反射(Reflection)是在运行时获取类的函数(方法)、属性、父类、接口、注解元数据、泛型信息等类的内部信息的机...

    joyqi 评论0 收藏0
  • SpringBoot 实战 (十五) | 服务端参数校验之一

    摘要:前言估计很多朋友都认为参数校验是客户端的职责,不关服务端的事。轻则导致服务器宕机,重则泄露数据。所以,这时就需要设置第二道关卡,服务端验证了。老项目的服务端校验不能为空不能为空看以上代码,就一个的校验就如此麻烦。 前言 估计很多朋友都认为参数校验是客户端的职责,不关服务端的事。其实这是错误的,学过 Web 安全的都知道,客户端的验证只是第一道关卡。它的参数验证并不是安全的,一旦被有心人...

    QiShare 评论0 收藏0
  • SpringBoot 实战 (十) | 声明式事务

    摘要:前言如题,今天介绍的声明式事务。提供一个注解在配置类上来开启声明式事务的支持。而在配置里还开启了对声明式事务的支持,代码如下所以在中,无须显式开启使用注解。源码下载后语以上为声明式事务的教程。 微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。 前言 如题,今天介绍 SpringBoot 的 声明式事务。 Spring 的事务机制 所有的数据访问技术都有事务处...

    ygyooo 评论0 收藏0

发表评论

0条评论

Jochen

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<