前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简单易懂讲注解

简单易懂讲注解

作者头像
冬夜先生
修改2021-10-28 10:02:24
3330
修改2021-10-28 10:02:24
举报
文章被收录于专栏:csicocsico

注解是什么

简单的说,注解就是一种将元数据信息从 xml 剥离开来,然后保存在 java 源代码中,这将使得代码更加清晰易懂,无需维护两个地方: java 源代码以及 xml 配置文件。

典型的场景就是 spring 框架,我们都知道,spring 框架将一个 bean 保存在容器里有两种方式,一种是采用配置文件的方式生成 bean 并且保存在容器中,使用的时候通过 bean 工厂拿对应的 bean 实例即可。这种方式很繁琐,不仅需要维护 java 源代码,还需要在 xml 配置里再维护一遍。另一种方式是采用注解的方式,在类名上使用 @Component或者@Service(当然还有其他方式,但不是本篇文章的重点)。然后在使用的时候采用 @Autowired 形式注入即可。这样就无需繁琐的 xml 配置。(例子)

当然,采用传统 xml 维护元素据还是使用注解,各有优劣,需要根据实际场景进行评估。

如何使用注解

接下来我们先从一个简单的注解定义开始,然后介绍一些注解的关键属性

定义注解

如下例子,Test 注解看起来很像接口的定义,注解和其他接口和类一样,都会被编译成 class 文件。像这种不含任何元素的被称为标记注解,如 java 8 新加入的用于声明一个接口时函数式接口的注解:@FunctionalInterface。

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    
}

当然,注解也是可以定义一些属性的。如下:

代码语言:javascript
复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    int value();
    String name() default "-- default --";
}

其中 value() 以及 name() 就是该注解的属性,其中 value() 没有默认值,那么在使用该注解的时候,必须指定 value 属性,name 有个默认值,使用的时候可以不需要指定默认值。

如下例子,就是该注解的使用方式,在 1 处,由于没有指定 value 属性,所以编译失败。

代码语言:javascript
复制
public class AnnotationDemo {

    @Test(1)
    private int value;
    
    @Test() // 1 编译失败
    private int withoutValue;

    @Test(2)
    private String withoutName;

    @Test(value = 3, name = "name")
    private String name;
}

这个注解现在来说是没有一丝丝意义的,因为我们还没有为其编写注释处理器,注释处理器在后面会介绍。

@Test 注解中使用到的 @Target 注解、@Retention 注解以及他们的参数枚举,会在下文的元注解中介绍。

常见注解

常见的注解这里主要介绍 jdk 的注解

  • @Override:表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示。
  • @Deprecated:表示当前类 or 方法 or 字段被弃用了,不应该再使用了,使用会产生告警
  • @SuppressWarnings:关闭不当的编译器警告信息。
  • @FunctionalInterface:Java 8 中加入用于表示类型声明为函数式接口

元注解

上文的 @Test 注解中,我们使用到了 @Target 注解、@Retention 注解,这两个注解为元注解,

目前一共有 5 个元注解:

注解

解释

@Target

表示注解可以用于哪些地方。可能的 ElementType 参数包括:CONSTRUCTOR:构造器的声明 FIELD:字段声明(包括 enum 实例) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数声明 TYPE:类、接口(包括注解类型)或者 enum 声明

@Retention

表示注解信息保存的时长。可选的 RetentionPolicy 参数包括: SOURCE:注解将被编译器丢弃 CLASS:注解在 class 文件中可用,但是会被 VM 丢弃。 RUNTIME:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。

@Documented

将此注解保存在 Javadoc 中

@Inherited

允许子类继承父类的注解

@Repeatable

允许一个注解可以被使用一次或者多次(Java 8)

使用最多的其实就是 @Target 以及 @Retention。

@Targe:注解中指定的每一个 ElementType 就是一个约束,它告诉编译器,这个自定义的注解只能用于指定的类型。你可以指定 enum ElementType 中的一个值,或者以逗号分割的形式指定多个值。如果想要将注解应用于所有的 ElementType,那么可以省去 @Target 注解,但是这并不常见。 @Retention:表明注解存在的时长,使用最多的是 RUNTIME,使用 RUNTIME 的时候,注解在运行期也保留着,这时就可以通过反射机制读取注解信息,如果使用 SOURCE,CLASS,那么就无法通过反射获取。

注解处理器

单独定义一个注释是没什么意义的,我们要给一个注释赋予意义,那么就得 coding,给这个注释编写一个注解处理器。这里我仅演示最简单的注解处理器。

这个列子很简单,定义了一个注解 @Test,该注解可以在方法上使用,可以被带入到运行时。AnnotationDemo 类实现了 Interface 接口,demo1()、demo2()、demo3()使用了注解,其中 demo3() 使用默认值,demo4() 没有引入注解。这里实现接口的原因是为了使用动态代理来调用方法,处理注解的逻辑写在动态代理里。动态代理类 InvokeClass,可以看到 invoke 方法里拿到 obj 对应的方法(这里不直接用入参的 method 字段是因为该字段代表接口方法,接口方法没有加注解,获取到的 Test annotation 会为空),这里拿到了方法上的注解信息后可以编写自己想要的处理逻辑,我这边就简单把 @Test 注解的 value() 值打印出来。

动态代理文章可以看:简单易懂将反射

(这里字符串判空写的有点丑了,是因为我没引入对应工具类)

代码语言:javascript
复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value() default "--- default ---";
}
interface Interface {

    void demo1();

    void demo2();

    void demo3();

    void demo4();
}


public class AnnotationDemo implements Interface {

    @Test("demo1")
    @Override
    public void demo1() {

    }

    @Test("demo2")
    @Override
    public void demo2() {

    }

    @Test
    @Override
    public void demo3() {

    }

    @Override
    public void demo4() {

    }
}
代码语言:javascript
复制
class InvokeClass implements InvocationHandler {

    Object obj;

    public InvokeClass(Object obj) {
        this.obj = obj;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method objectMethod = obj.getClass().getMethod(method.getName());
        Test annotation = objectMethod.getAnnotation(Test.class);
        if (Objects.nonNull(annotation) && !"".equals(annotation.value())) {
            System.out.println(annotation.value());
        }
        return method.invoke(obj, args);
    }
}


public class Main {

    public static void main(String[] args) {

        Interface anInterface = (Interface) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Interface.class}, new InvokeClass(new AnnotationDemo()));
        anInterface.demo1();
        anInterface.demo2();
        anInterface.demo3();
        anInterface.demo4();
    }

}

输出:

代码语言:javascript
复制
demo1
demo2
--- default ---

Spring 如何自定义注解

在 spring 中使用自定义注解一般是配合 aop 使用的。

如下,还是注解 @Test ,有个 AnnotationDemo 类,在方法上使用了注解,并且将自身注入 spring 容器 (@Service),并且通过实现 BeanFactoryAware 接口,在初始化的时候调用 setBeanFactory 方法,这里通过传入的 bean 工厂获取到 bean 并且调用方法。

定义一个切面 AspectDemo,切点 pointcut 为我们自定义的注解类,增强 advice 是打印了 @Test 注解的 value() 信息。这样当调用了使用了 @Test 的注解的方法的时候,就是会打印对应的 value() 信息。启动项目,由于在 setBeanFactory 方法中调用了 AnnotationDemo 类的几个方法,因此打印出了对应的注解的 value 信息。

代码语言:javascript
复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value() default "--- default ---";
}
@Service
public class AnnotationDemo implements BeanFactoryAware {

    @Test("demo1")
    public void demo1() {

    }

    @Test("demo2")
    public void demo2() {

    }

    @Test
    public void demo3() {

    }

    public void demo4() {

    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        AnnotationDemo demo = beanFactory.getBean(AnnotationDemo.class);
        demo.demo1();
        demo.demo2();
        demo.demo3();
        demo.demo4();
    }
}
@Component
@Aspect
public class AspectDemo {

    @Pointcut("@annotation(com.example.spring_project.Test)")
    private void pointcut() {}

    @Before("pointcut() && @annotation(test)")
    public void advice(Test test) {
        System.out.println(test.value());
    }

}

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 注解是什么
  • 如何使用注解
    • 定义注解
      • 常见注解
        • 元注解
          • 注解处理器
          • Spring 如何自定义注解
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档