玩转Java注解

简介: 玩转Java注解

1 为什么需要注解

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

链接

https://docs.oracle.com/javase/1.5.0/docs/relnotes/features.html#annotations

https://docs.oracle.com/javase/1.5.0/docs/guide/apt/index.html

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

翻译:

- 元数据(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;
}

测试:

就是这么简单~

相关文章
|
30天前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
87 43
Java学习十六—掌握注解:让编程更简单
|
25天前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
46 14
|
25天前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
32 12
|
19天前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
29 0
|
1月前
|
JSON Java 数据库
java 常用注解大全、注解笔记
关于Java常用注解的大全和笔记,涵盖了实体类、JSON处理、HTTP请求映射等多个方面的注解使用。
35 0
java 常用注解大全、注解笔记
|
2月前
|
Arthas Java 测试技术
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
Java字节码文件、组成、详解、分析;常用工具,jclasslib插件、阿里arthas工具;如何定位线上问题;Java注解
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
|
1月前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
16 0
|
2月前
|
Java 编译器 程序员
Java注解,元注解,自定义注解的使用
本文讲解了Java中注解的概念和作用,包括基本注解的用法(@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface),Java提供的元注解(@Retention, @Target, @Documented, @Inherited),以及如何自定义注解并通过反射获取注解信息。
Java注解,元注解,自定义注解的使用
|
1月前
|
XML Java 数据格式
Java-spring注解的作用
Java-spring注解的作用
23 0
|
2月前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit