玩转Java注解

简介: 玩转Java注解

1 为什么需要注解

因为注解起源与JDK1.5,所以先带你们去挖一挖Sun官方当时刚刚发行JDK1.5时的文档,目前在Oracle官网

链接

docs.oracle.com/javase/1.5.…


docs.oracle.com/javase/1.5.…

如果你不愿意自己看的话,我截取了一些重点的内容并且翻译了下:

网络异常,图片无法展示
|


翻译:

- 元数据(Annotations)
此语言功能允许您通过启用工具从源代码中的注释生成样板代码来避免在许多情况下编写样板代码。这导致了一种“声明式”编程风格,在这种风格中,程序员说应该做什么,而工具会发出代码来做这件事。它还消除了维护“边文件”的需要,这些文件必须随着源文件的变化而保持最新。相反,信息可以保存在源文件中。请参阅JSR 175(https://jcp.org/en/jsr/detail?id=175)。
复制代码

因此,JDK1.5中引入注解首先是为了避免在许多情况下编写样板代码,增强了“声明式”编程风格。总的来说,注解就是继类的继承、接口之后的又一个增强类和抽象化的方式

2 JDK元注解

所谓元注解,可以理解为JDK内部自带的注解,就好比几个包装类一样(String、Integer等),是一切注解的依赖注解,并且在JDK1.5之后可以直接使用,以下罗列了这几个注解:

  • @Retention
  • @Documented
  • @Target
  • @Inherited
  • @Repeatable

具体每个注解都有什么作用,请看下文

2.1 @Retention

它的作用说明这个注解的存活时间

public enum RetentionPolicy {
    /**
     * 只在源码中可见,编译时丢弃
     */
    SOURCE,
    /**
     * 默认值,编译时被编译器记录在类文件中,但在运行时不被虚拟机保留
     */
    CLASS,
    /**
     * 编译记录在类文件中由虚拟机在运行时保留,因此它们可能被反射式读取
     */
    RUNTIME
}
复制代码

2.2 @Documented

它的作用是能够将注解中的元素包含到 Javadoc 中去。

2.3 @Target

指定了注解运用的地方,比如是只能放在方法上还是类上还是都能放。

public enum ElementType {
    /** 能放在类、接口、枚举上 */
    TYPE,
    /** 能放在字段上 */
    FIELD,
    /** 能放在方法上 */
    METHOD,
    /** 能放在方法的参数上 */
    PARAMETER,
    /** 能放在构造器上 */
    CONSTRUCTOR,
    /** 能放在局部变量上 */
    LOCAL_VARIABLE,
    /** 能放在注解上 */
    ANNOTATION_TYPE,
    /** 能放在包上 */
    PACKAGE,
    /**
     * 只针对类型参数TypeParameterClass<@TypeParameterAnnotation T>
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * 能在局部变量、泛型类、父类和接口的实现处使用,甚至能在方法上声明异常的地方使用
     * @since 1.8
     */
    TYPE_USE
}
复制代码

2.4 @Inherited

@Inherited修饰的注解,只有作用在类上时,会被子类继承此自定义的注解,其余情况都不会继承。

2.5 @Repeatable

@Repeatable是java1.8加进来的,表示的是可重复。

2.6 其他常见的原生注解

  • @Override:用于修饰此方法覆盖了父类的方法;
  • @Deprecated:用于修饰已经过时的方法;
  • @SuppressWarnnings:用于通知java编译器禁止特定的编译警告。

3 自定义注解

3.1 简单使用

自定义注解规则

[元注解]
public @interface [注解名称] {
    [值类型] [值的key]() default [key的默认值];
    [值类型] [值的key]();
    ...
}
复制代码

自定义注解实践

/**
 * @desc: 类注解
 * @author: YanMingXin
 * @create: 2022/4/3-10:52
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface YmxClazz {
    int level() default 10;
    String name() default "vip";
}
复制代码
/**
 * @desc: 方法注解
 * @author: YanMingXin
 * @create: 2022/4/5-8:07
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface YmxMethod {
    boolean isVip() default true;
}
复制代码
/**
 * @desc: 字段注解
 * @author: YanMingXin
 * @create: 2022/4/3-10:52
 **/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface YmxValue {
    String strValue() default "";
    int intValue() default 0;
}
复制代码

使用自定义注解

/**
 * @desc:
 * @author: YanMingXin
 * @create: 2022/4/3-10:52
 **/
@YmxClazz(name = "ymx", level = 999)
public class StudentController {
    @YmxValue(strValue = "yyy")
    private String val;
    @YmxMethod
    public String methodA() {
        return "ymx";
    }
}
复制代码

验证方法

/**
 * @desc: 验证自定义注解
 * @author: YanMingXin
 * @create: 2022/4/3-10:52
 **/
public class Main {
    public static void main(String[] args) throws Exception {
        StudentController controller = new StudentController();
        isYmxClass(controller);
        isYmxFiled(controller);
        isYmxMethod(controller);
    }
    public static void isYmxClass(Object obj) {
        Class<?> clazz = obj.getClass();
        String name = null;
        int level = -1;
        //获取类模板
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof YmxClazz) {
                name = ((YmxClazz) annotation).name();
                level = ((YmxClazz) annotation).level();
            }
        }
        System.out.println("name=" + name + ",level=" + level);
    }
    public static void isYmxFiled(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            int intValue = 0;
            String strValue = null;
            for (Annotation annotation : field.getAnnotations()) {
                if (annotation instanceof YmxValue) {
                    intValue = ((YmxValue) annotation).intValue();
                    strValue = ((YmxValue) annotation).strValue();
                }
            }
            System.out.println("intVal=" + intValue + ",strVal=" + strValue);
        }
    }
    public static void isYmxMethod(Object obj) {
        Method[] methods = obj.getClass().getDeclaredMethods();
        for (Method method : methods) {
            boolean vip = false;
            for (Annotation annotation : method.getAnnotations()) {
                if (annotation instanceof YmxMethod) {
                    vip = ((YmxMethod) annotation).isVip();
                }
            }
            System.out.println(vip);
        }
    }
}
复制代码

运行结果

网络异常,图片无法展示
|


3.2 总结说明

以上的演示仅为了能体现出获取注解值的流程,在实际的项目使用中可能会比以上稍微复杂,但归根结底都是利用的Java反射机制,我们可以理解为Java的注解和反射是不一定是相辅相成的,没有注解的反射还是反射,但是没有反射的注解可能就没用用武之地。

对于反射机制的使用,欢迎移步我的另外一篇文章《玩转Java反射机制》

对于注解的框架项目中的使用,Spring的IOC源码中使用的非常优雅,欢迎移步我的另外一篇文章《Spring IoC原理解读》,当然下文的实际使用演示也会很不错哦。

4 实战:自定义注解实现拦截器判断

4.1 回顾Spring Boot自定义拦截器

实现详情请读者转到这篇文章 《一文搞懂Spring Boot自定义拦截器》

这里只粘贴代码:

4.1.1 需求

我们首先定义一个Controller,设置三个方法,分别为thank()、please()、sorry(),为什么要这三个方法呢?

因为:

网络异常,图片无法展示
|


然后我们自定义拦截器,拦截全部请求,除了一个sorry的请求,因为这个要在用户被拦截时让他们知道,代码如下

4.1.2 代码

UserController.java

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/thank")
    public String thank() {
        return "Thanks!";
    }
    @RequestMapping("/please")
    public String please() {
        return "Please!";
    }
    @RequestMapping("/sorry")
    public String sorry() {
        return "Sorry,You've been intercepted~";
    }
}
复制代码

AppWebInterceptor.java

@Component
public class AppWebInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        response.sendRedirect("/user/sorry");
        return false;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
复制代码

AppWebInterceptorConfig.java

@Configuration
public class AppWebInterceptorConfig extends WebMvcConfigurationSupport {
    /**
     * 注入自定义拦截器
     */
    @Autowired
    private AppWebInterceptor appWebInterceptor;
    /**
     * 配置拦截器和拦截、放行路径
     *
     * @param registry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(appWebInterceptor)
                .excludePathPatterns("/user/sorry")
                .addPathPatterns("/**");
    }
}
复制代码

这样的话我们无论请求/user/thank还是/user/please都会被拦截然后跳转到/user/sorry,所以自定义注解登场!

4.2 创建自定义注解

如下,无需多言了吧

/**
 * @desc: 自定义注解
 * @author: YanMingXin
 * @create: 2022/4/5-11:01
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface NoIntercept {
    /**
     * 该参数表示再次确认:
     * 1.加上@NoIntercept表示不拦截
     * 2.isReal()会进行再次确认,就好比问一句“确定不拦截吗?”
     * 默认的回答是‘true’代表‘确定’,值为‘false’时是‘不确定’
     *
     * @return
     */
    boolean isReal() default true;
}
复制代码

4.3 配置拦截规则

4.3.1 规则定义

因为这个注解的@Target({ElementType.TYPE, ElementType.METHOD}),所以它既能在类上使用也能在方法上使用,因此我们定义下规则:

  • @NoIntercept标注的Controller类下所有方法均不拦截。
  • 没有@NoIntercept标注的Controller类或者@NoIntercept(isReal=false)情况下方法上有@NoIntercept标注则不拦截,否则进行拦截。
4.3.2 规则代码

我们修改AppWebInterceptor类的preHandle方法,实现上面的规则:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    boolean clazzIsAccess = false;
    //为什么要分割下,见下图
    String[] str = handler.toString().split("#");
    Class<?> clazz = Class.forName(str[0]);
    if (str[1].length() <= 2) {
        return false;
    }
    String handlerMethodName = str[1].substring(0, str[1].length() - 2);
    Annotation[] clazzAnnotations = clazz.getDeclaredAnnotations();
    for (Annotation annotation : clazzAnnotations) {
        if (annotation instanceof NoIntercept) {
            clazzIsAccess = ((NoIntercept) annotation).isReal() ? true : false;
        }
    }
    if (clazzIsAccess) {
        return true;
    }
    Method[] clazzDeclaredMethods = clazz.getDeclaredMethods();
    for (Method method : clazzDeclaredMethods) {
        if (method.getName().equals(handlerMethodName)) {
            Annotation[] annotations = method.getDeclaredAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation instanceof NoIntercept) {
                    return ((NoIntercept) annotation).isReal() || clazzIsAccess ? true : false;
                }
            }
        }
    }
    response.sendRedirect("/user/sorry");
    return false;
}
复制代码

网络异常,图片无法展示
|


4.3.3 验证规则

(1)我们将UserController类打上@NoIntercept注解:

@NoIntercept
@RestController
@RequestMapping("/user")
public class UserController {
    ......
}
复制代码

测试:

网络异常,图片无法展示
|


(2)我们将UserController类打上@NoIntercept(isReal = false)注解:

@NoIntercept(isReal = false)
@RestController
@RequestMapping("/user")
public class UserController {
    ......
}
复制代码

测试(什么都没有显示就是被拦截了,因为包含了重定向,终端不支持):

网络异常,图片无法展示
|


(3)我们将UserController类打上@NoIntercept(isReal = false)注解,将please方法打上@NoIntercept注解:

@NoIntercept(isReal = false)
@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/thank")
    public String thank() {
        return "Thanks!";
    }
    @NoIntercept
    @RequestMapping("/please")
    public String please() {
        return "Please!";
    }
    @RequestMapping("/sorry")
    public String sorry() {
        return "Sorry,You've been intercepted~";
    }
}
复制代码

测试:

网络异常,图片无法展示
|


4.4 探究Spring内置注解解析方式

以上的代码和案例是不是很优雅,但是这件事可能早就被Spring知道了,因此在Spring中有更加简便的方式,我们来实现下:

还是修改AppWebInterceptor类的preHandle方法(为了方便起见,这里只演示放在方法上的注解):

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    NoIntercept annotation;
    if (handler instanceof HandlerMethod) {
        annotation = ((HandlerMethod) handler).getMethodAnnotation(NoIntercept.class);
    } else {
        return true;
    }
    if(annotation!=null) {
        return true;
    }
    response.sendRedirect("/user/sorry");
    return false;
}
复制代码

测试:

网络异常,图片无法展示
|


就是这么简单~


相关文章
|
16天前
|
Java API 数据库
Java一分钟之-JPA注解:@Entity, @Table, @Id等
【6月更文挑战第14天】Java Persistence API (JPA) 是Java开发中的ORM框架,通过注解简化数据访问层。本文介绍了三个核心注解:`@Entity`标识实体类,`@Table`自定义表名,`@Id`定义主键。易错点包括忘记添加`@Entity`、未正确设置主键。建议使用`@GeneratedValue`和`@Column`细化主键策略和字段映射。正确理解和应用这些注解能提高开发效率和代码质量。
30 3
|
1天前
|
Java Spring
JAVA注解:传统与现代的完美结合,你的代码值得拥有!
【6月更文挑战第29天】Java注解,作为连接传统与现代的编程工具,简化企业级应用开发,提升代码可读性和维护性。通过自定义注解如`@Loggable`,可以将行为(如日志记录)与方法实现分离,减少模板代码。使用AOP(如Spring)处理注解,实现行为拦截,增强代码灵活性和可扩展性。拥抱Java注解,让代码更现代、更高效!
26 16
|
1天前
|
IDE Java 程序员
JAVA注解大揭秘:为何程序员都爱它如命?
【6月更文挑战第29天】Java注解是元数据机制,用于在代码中嵌入信息供编译器、IDE和工具使用。它们以`@`标识,可用于类、方法等,用于编译时检查、代码生成(如Lombok的`@Getter`、`@Setter`)、框架集成(如Spring的`@Autowired`)。程序员喜欢注解因其简洁性、可读性和可扩展性,能减少冗余代码并增强代码的可理解性。
22 15
|
1天前
|
IDE Java 编译器
JAVA注解,你的代码需要的一次“心灵按摩”!
【6月更文挑战第29天】Java注解是提升代码可维护性的关键,它们是编译器和IDE理解代码意图的特殊标记,不同于仅作解释的注释。注解可用于编译时检查(如@Override、@NotNull)、自动生成代码(Lombok的@Getter、@Setter)、框架集成(Spring的@Autowired、MyBatis的@Mapper)。通过注解,代码变得更简洁、功能更强大,为项目带来效率提升。尝试使用注解,赋予代码新生命!
21 12
|
1天前
|
IDE Java 编译器
深入解析JAVA注解:元数据如何改变编程世界
【6月更文挑战第29天】Java注解,作为元数据机制,为代码增添上下文信息,改变编程方式。注解标记在类、方法等上,不直接影响执行,但为编译器等提供额外信息。分为元注解、编译时和运行时注解,用于元数据提供、代码简化、提高可读性及自动化。示例展示了定义`@Loggable`注解来标记日志记录方法。注解广泛应用于依赖注入、ORM、Web服务等,提升效率和灵活性,是现代Java开发的关键。未来其应用将更广泛。
12 3
|
1天前
|
Java 编译器 开发者
JAVA注解,让代码“开口说话”的魔法术!
【6月更文挑战第29天】Java注解,一种元数据机制,让代码“开口”传达意图。它们增强可读性,提供编译器与框架处理代码的额外信息。例如,@Description注解描述方法功能,@Autowired在Spring中自动装配Bean,自定义注解如@MyCustomAnnotation允许定义独特行为。注解提升开发效率,是理解与使用Java的关键。
|
1天前
|
Java 编译器 数据库连接
JAVA注解:代码界的“隐形翅膀”?!
【6月更文挑战第29天】Java注解,编程的“隐形翅膀”,提供编译检查、框架集成及自定义元数据功能。如@Override确保方法重写正确,@Autowired在Spring中自动装配Bean。通过自定义注解,开发者能创造独特代码逻辑。例如,定义@SpecialProcessing注解标记需特殊处理的方法,增强代码可读性和可扩展性。利用注解,让代码飞翔在更广阔的世界。
11 1
|
1天前
|
XML Java 编译器
JAVA注解大揭秘:元数据,你真的了解它吗?
【6月更文挑战第29天】Java注解是元数据机制,用于为代码添加不改变逻辑的额外信息。它们在编译检查(如`@Override`)、配置(如Spring的`@Autowired`)和自定义元数据中发挥作用。自定义注解如`@Loggable`可配合AOP实现日志记录,简化代码并增强可维护性。通过定义切面类和使用`@Before`、`@After`,可以拦截并处理带注解的方法,展示注解在实际应用中的灵活性。
|
2天前
|
Java
Java自定义注解:优雅的代码标记
Java自定义注解:优雅的代码标记
9 1
|
1天前
|
IDE Java 数据库连接
JAVA注解:元数据,代码的“身份证”?!
【6月更文挑战第29天】Java注解,作为代码的“身份证”,提供元数据,用于编译时检查、自动生成代码和框架集成。例如,@Override确保方法重写正确,@Deprecated标记过时,@Autowired在Spring中实现依赖注入。Lombok的@Getter/@Setter简化getter/setter。注解提升代码质量和效率,是现代Java开发的关键实践。
7 0