一文搞懂Java注解

2023-11-20 22:33:57 浏览数 (1)

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

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

在这里插入图片描述在这里插入图片描述

翻译:

代码语言:markdown复制
- 元数据(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

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

代码语言:java复制
public enum RetentionPolicy {
    /**
     * 只在源码中可见,编译时丢弃
     */
    SOURCE,

    /**
     * 默认值,编译时被编译器记录在类文件中,但在运行时不被虚拟机保留
     */
    CLASS,

    /**
     * 编译记录在类文件中由虚拟机在运行时保留,因此它们可能被反射式读取
     */
    RUNTIME
}
2.2 @Documented

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

2.3 @Target

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

代码语言:java复制
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 简单使用

自定义注解规则

代码语言:java复制
[元注解]
public @interface [注解名称] {

    [值类型] [值的key]() default [key的默认值];

    [值类型] [值的key]();
    
    ...
}

自定义注解实践

代码语言:java复制
/**
 * @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";
}
代码语言:java复制
/**
 * @desc: 方法注解
 * @author: YanMingXin
 * @create: 2022/4/5-8:07
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface YmxMethod {

    boolean isVip() default true;

}
代码语言:java复制
/**
 * @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;
}

使用自定义注解

代码语言:java复制
/**
 * @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";
    }

}

验证方法

代码语言:java复制
/**
 * @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

代码语言: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

代码语言: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

代码语言: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 创建自定义注解

如下,无需多言了吧

代码语言:java复制
/**
 * @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方法,实现上面的规则:

代码语言:java复制
@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注解:

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

测试:

在这里插入图片描述在这里插入图片描述

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

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

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

在这里插入图片描述在这里插入图片描述

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

代码语言:java复制
@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方法(为了方便起见,这里只演示放在方法上的注解):

代码语言:java复制
@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;
}

测试:

在这里插入图片描述在这里插入图片描述

就是这么简单~

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞