Java

玩转Java注解

2022-07-23  本文已影响0人  Java熬夜党

1 JDK元注解

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

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

1.1 @Retention

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

public enum RetentionPolicy {
    /**
     * 只在源码中可见,编译时丢弃
     */
    SOURCE,

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

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

1.2 @Documented

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

1.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
}

1.4 @Inherited

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

1.5 @Repeatable

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

1.6 其他常见的原生注解

2自定义注解

2.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);
        }
    }
}

运行结果

2.2 总结说明

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

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

3.1 回顾Spring Boot自定义拦截器

3.1.1 需求

我们首先定义一个Controller,设置三个方法,分别为thank()、please()、sorry()
然后我们自定义拦截器,拦截全部请求,除了一个sorry的请求,因为这个要在用户被拦截时让他们知道,代码如下

3.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,所以自定义注解登场!

3.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;

}

3.3 配置拦截规则

3.3.1 规则定义

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

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

3.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~";
    }
}

测试:


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

测试:


就是这么简单~

上一篇 下一篇

猜你喜欢

热点阅读