Java自定义注解

Java自定义注解,第1张

注解定义

Java注解是使用在类、方法、变量、参数和包等地方的一种特殊注释,可以被编译器打包进class文件,它可以通过反射的方式获取到注解的的内容,利用获取的内容进行一些校验,如参数必填的校验,还可以用在拦截器中,实现全局的权限校验,也可以结合AOP进行日志添加等功能。

我们常见的注解:

注解在类上:@Controller 、@Service、@Component

注解在方法上:@Override、@RequestMapping

注解在参数上:@RequestBody、@RequestParam、 @PathVariable

自定义注解

Java用@interface来定义注解,在注解上添加@Target和@Retention,添加一些参数,最常用的参数应当命名为value,最好设定一个默认值。

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckLogin {

   boolean LoginFlag() default true;
   String  value() default “info”;

}

元注解

有一些注解可以修饰其他注解,这部分注解是Java标准库已经定义好的,不需要手动编写,可以直接使用的注解,就称为元注解。

这些元注解存在于java.lang.Annotation包中,如下图:

元注解以下几种:

@Target

@Retention

@Documented

@Repeatable

@Inherited

其中@Target和@Retention是定义注解时必须的,其他可以选择使用。

@Target

描述了注解修饰的对象范围,可以使用在类、方法、字段、接口等,

类型为ElementType的枚举值:

  • TYPE:类、接口或者枚举值
  • FIELD:字段
  • METHOD:方法
  • PARAMETER:参数
  • CONSTRUCTOR:构造方法
  • ANNOTATION_TYPE:注解类型

如果注解在类上,用@Target(ElementType.TYPE)

如果注解在类和方法上,用@Target({ElementType.TYPE,ElementType.METHOD})

如果注解在方法和字段,用@Target({ElementType.METHOD,ElementType.FIELD})

Target的参数是ElementType[]数组,因此可以根据需要组合使用。

@Retention

表示注解的生命周期,取值为RetentionPolicy的枚举值:

  • SOURCE:编译期存在
  • CLASS:保存在class文件
  • RUNTIME:运行时,可以通过反射读取

我们定义为@Retention(RetentionPolicy.RUNTIME),可通过反射获取。

如果Retention不存在,默认SOURCE;

获取注解:

注解定义后也是一种Class,所有的注解都继承自Annotation,因此读取注解,需要使用反射API,通过反射获取,AnnotationElement接口提供了获取注解的方法,反射相关的类Class、Method、Field都实现了AnnotationElement接口,AnnotationElement提供了几种获取注解的接口如下:

判断某个注解是否存在于Class、Field、Method、或者Constructor:

  • Class.isAnnotationPresent(CheckLogin.class)
  • Field.isAnnotationPresent(CheckLogin.class)
  • Method.isAnnotationPresent(CheckLogin.class)
  • Constructor.isAnnotationPresent(CheckLogin.class)

利用反射获取注解的方法:

CheckLogin check = CheckLogin.class.getAnnotation(CheckLogin.class)

读取方法、字段、构造方法的注解和Class类似,但是获取方法参数的注解稍微麻烦一点,因为方法参数本身可以看成一个数组,每个参数又可以定义多个注解,所以方法参数的注解必须用一个二维数组表示。

知识学习了就要进行实践,理论加实践才是技术成长的方式。

应用场景一:自定义注解+拦截器实现权限校验

校验用户是否进行了登陆,即校验token或者cookie,没有登录或者登录超时的需要跳转到登陆页。

第一步:

自定义注解CheckLogin,添加@Target、@Retention以及LoginFlag参数

package com.garin.springboot.demo.config;
import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckLogin {

   boolean LoginFlag() default true;
}

第二步:

创建拦截器,对请求的接口进行拦截,利用反射获取注解,根据注解的信息判断需不需要进行校验登陆

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
             return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();
        //获取注解
        CheckLogin methodLogin = method.getAnnotation(CheckLogin.class);
        if (!methodLogin.LoginFlag()) {
            return true;
        }
        //需要校验登陆信息
        String token = request.getHeader("token");
        if (token == null ) {
            //需要登陆
        }
        return false;
    }
}
第三步:

定义一个controller,并使用自定义的注解CheckLogin 

@RestController
public class BuyController {

    @RequestMapping("/buy")
    @CheckLogin(LoginFlag = true)
    public String buy(Product product) {
        return "我购买了水果和蔬菜";
    }
}

第四步:

把定义的拦截器注入到Spring中

@Configuration
public class LoginConfigure implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor);
    }
}

应用场景二:自定义注解+特定的校验器实现校验功能

一些复杂的参数校验,现有的校验框架有时候无法满足需求,这个时候就需要我们自己手动定义注解来实现校验,会用到ConstraintValidator。

首先定义一个注解StatusVerify:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Constraint(validatedBy = StatusValidator.class) //指定我们定义的校验器
public @interface StatusVerify {

    int [] values();
    
    String tip() default "请传入正确的状态值";
    
    Class[] groups() default {};

    Class[] payload() default{};
    
}

然后定义一个校验器,实现ConstraintValidator 接口,需要指定两个参数,第一个是自定义注解,第二个是需要校验的数据类型,重写了2个方法,initialize进行了一些初始化 *** 作,它的参数是我们使用到的注解,可以获取运行时的注解信息。isValid方法就是实现的校验逻辑,被注解的对象会传入此方法中。

public class StatusValidator implements ConstraintValidator {
    List list = new ArrayList<>();
    //初始化
    @Override
    public void initialize(StatusVerify constraintAnnotation) {
        int [] values = constraintAnnotation.values();
        for (int aa :values) {
            list.add(aa);
        }
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        System.out.println("list:"+ list.toString());
        if (list.contains(value)) {
            System.out.println("校验成功!");
            return true;
        }
        return false;
    }

}

然后使用注解,在需要校验的字段上添加StatusVerify注解

@Data
public class Product {

    @StatusVerify(values ={10,20,30},tip ="请传入正确的状态值")
    private String id;
    private String name;
    private String price;
    private String weight;
    

}

在controller层进行调用:

@RequestMapping("/check")
    public void checkStatus(@RequestBody Product product) {
        System.out.println("这个状态合法");
    }

参考文档:

定义注解 - 廖雪峰的官方网站

Java 自定义注解及使用场景 - 简书

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/730992.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存