IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 玩转Java注解 -> 正文阅读

[Java知识库]玩转Java注解

作者:token annotation punctuation

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;
}

测试:
在这里插入图片描述
就是这么简单~

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-06 16:04:04  更:2022-04-06 16:08:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:18:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码