Java 注解机制和应用

总结

  • Java注解是一种很常见的开发辅助模式,Java语言中的类、方法、变量、参数和包等都可以被标注。
  • 通过自定义注解的使用可以优化业务开发的使用。

概述

Java注解又称Java标注,是Java语言5.0版本开始支持加入源代码的特殊语法元数据。为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。

Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取注解内容。在编译器生成类文件时,注解可以被嵌入到字节码中。Java虚拟机可以保留注解内容,在运行时可以获取到注解内容。

常见注解

Java内置注解

Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

1、作用在代码的注解是

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

2、作用在其他注解的注解(或者说元注解)是:

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

3、从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

常见的库中的注解

日常开发使用的库中也有着大量的注解,例如Jackson、SpringMvc等,下面就简单介绍下常见库中的常见注解使用

  • Jackson
    Jackson是一个通用的序列化库,程序员使用过程中可以使用它提供的注解机制对序列化进行定制化操作,比如:

  • 使用@JsonIgnore和@JsonIgnoreProperties配置序列化的过程中忽略部分字段

  • 使用@JsonManagedReference和@JsonBackReference可以配置实例之间的互相引用

  • 使用@JsonProperty和@JsonFormat配置序列化的过程中字段名称和属性字段的格式等

  • Servlet3.0

随着web开发技术的发展,Java web已经发展到了Servlet3.0,在早期使用Servlet的时候,我们只能在web.xml中配置,但是当我们使用Servlet3.0的时候开始,已经开始支持注解了,比如我们可以使用@WebServlet配置一个类为Servlet类。

  • SpringMvc

同样的,在web开发中,我们往往还会使用SpringMvc框架来简化开发,其框架的大量注解可以帮助我们减少大量的业务代码,例如一个请求的参数和字段/实例之间的映射关系,一个方法使用的是Http的什么请求方法,对应请求的某个路径,同样的请求如何解析,返回的响应报文格式定义等,这些都可以使用注解来简化实现,一个简单的Mvc操作如下:

其中@Controller注解标明当前的类是SpringMvc接管的一个Bean实例,@RequestMapping(“/hello”)则是代表当前Bean的前置请求路径比如是/hello开头, @GetMapping(“/test”)则是表示test方法被访问必须是Http请求的get请求,并且路径必须是/hello/test为路径前置,@ResponseBody注解则是标明了当前请求的相应信息按照默认的格式返回(根据后缀名来确定格式)

注解创建

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Label {
    String value() default "";
}

@Target注解表示当前注解可以使用在什么类型的元素上,这里的值可以多选,即一个注解可以作用在多种不同类型的元素上,具体的可选值在ElementType枚举类中,值如下:

取值解释
TYPE表示作用在类、接口上
FIELD表示作用在字段,包括枚举常量中
METHOD表示作用在方法中
PARAMETER表示作用在方法中的参数中
CONSTRUCTOR表示作用在构造方法中
LOCAL_VARIABLE表示作用在本地常量中
MODULE表示作用在部分模块中(Java9引入的概念)
ANNOTATION_TYPE表示当前注解作用在定义其他注解中,即元注解
PACKAGE表示当前注解使用在包的申明中
TYPE_PARAMETER表明当前注解使用在类型参数的申明中(Java8新增)
TYPE_USE表明当前注解使用在具体使用类型中(Java8新增)

当使用多个作用域范围的时候,使用{}包裹多个参数,比如@SuppressWarnings注解的Target就有多个,在Java7中的定义为:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings {    String[] value();
}

@Retention

@Retention注解则是表明了当前注解可以保留到Java多个阶段的哪一个阶段,参数类型为RetentionPolicy枚举类,可取值如下:

取值解释
SOURCE此注解仅在源代码阶段保留,编译后即丢失注解部分
CLASS表示编译后依然保留在Class字节码中,但是加载时不一定会在内存中
RUNTIME表示不仅保留在Class字节码中,一直到内存使用时仍然存在

此注解有默认值,即当我们没有申明@Retention的时候,默认则是Class取值范围

@Documented

@Documented注解没有具体的参数,使用此元注解,则表示带有类型的注解将由javadoc记录

@Inherited

  • @Inherited 注解与注解的继承有关系,具体关系为如果使用了当前的元注解,则表示此注解可以被其他的注解的子类直接继承,但是需要注意的是对已实现接口上的注解将没有作用。
  • @Inherited 注释表明注释类型可以从超类继承。当用户查询注释类型并且该类没有此类型的注释时,将查询类的超类以获取注释类型(默认情况下不是这样)。此注释仅适用于类声明。

应用

业务开发的使用

基于Spring提供的AOP开发方法,可以简化业务代码开发中冗余的业务代码,对接口调用过程的前置处理、过程处理、后置处理。

  • 金融借贷系统对接了很多第三方的风控接口。调用接口前需要校验报文体中的签名字段 sign
    • 每个方法开头都写一份签名验签的代码。
    • 将验签代码抽取成方法,方便复用。
    • 新建 @SignCheck 注解,在切面里面写业务逻辑。
  • 应用开发过程的日志记录需要保存到消息中间件或者数据库。
    • 每个方法开头都写一份记录日志的代码。
    • 将记录日志抽取成方法,方便复用。
    • 新建 @LogAccess 注解,在切面里面写日志记录逻辑。

工具开发的使用

  • Lombok 通过注解 @Getter @Setter 等主机,在源码编译时添加对应的模板方法。
  • Fastjson 通过 @JSONField 定制序列化方法。指定JSON代码文本生成的别名。

自定义注解例子


import lombok.Getter;
import lombok.Setter;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class AnnotationTestAll {
    /**
     * 自定义注解 Format
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Format {
        String pattern() default "yyyy-MM-dd HH:mm:ss";

        String timezone() default "GMT+8";
    }
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Label {
        String value() default "";
    }
    /**
     * 通过反射 Format Label,并且获取值做运算
     */
    public static class SimpleFormatter {
        public static String format(Object obj) {
            try {
                Class<?> cls = obj.getClass();
                StringBuilder builder = new StringBuilder();
                for (Field field : cls.getDeclaredFields()) {
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
                    // 获取Label注解-输出的字段名称
                    Label label = field.getAnnotation(Label.class);
                    String name = null == label ? field.getName() : label.value();
                    Object value = field.get(obj);
                    if (value != null && field.getType() == Date.class) {
                        value = formatter(field, value);
                    }
                    builder.append(String.format("%s ? %s \n", name, value));
                }
                return builder.toString();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("格式化输出失败:" + e.getMessage());
            }
        }

        private static Object formatter(Field field, Object value) {
            Format format = field.getAnnotation(Format.class);
            if (null == format) {
                return value;
            }
            String pattern = format.pattern();
            String timezone = format.timezone();
            SimpleDateFormat sdf = new SimpleDateFormat(pattern);
            sdf.setTimeZone(TimeZone.getTimeZone(timezone));
            return sdf.format(value);
        }
    }

    /**
     * 测试的一个bo
     */
    @Getter
    @Setter
    public static class Student {
        @Label("姓名")
        private String name;
        @Label("出生日期")
        @Format(pattern = "yyyy/MM/dd")
        private Date born;
        @Label("分数")
        private Double score;
    }

    /**
     * 测试运算结果
     * @param args
     */
    public static void main(String[] args) {
        Student student = new Student();
        student.setBorn(new Date());
        student.setName("张三");
        student.setScore(244.0);
        System.out.println(SimpleFormatter.format(student));
        /**
         * 输出:
         * 姓名 ? 张三
         * 出生日期 ? 2023/03/12
         * 分数 ? 244.0 
         */
    }
}

参考

posted @ 2023-03-12 15:23  落叶微风  阅读(1)  评论(0编辑  收藏  举报  来源