面试系列之Java自定义注解

注解定义

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<? extends Payload>[] payload() default{};
    
}

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

public class StatusValidator implements ConstraintValidator<StatusVerify,Integer> {
    List<Integer> 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 自定义注解及使用场景 - 简书

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义注解的应运场景有很多,以下是一些常见的场景: 1. 日志记录:通过自定义注解可以在需要记录日志的方法上添加注解,然后通过AOP技术实现对这些方法的拦截,从而实现日志的记录。 2. 权限控制:通过自定义注解可以在需要进行权限控制的方法上添加注解,然后通过AOP技术实现对这些方法的拦截,在方法执行前进行权限验证,从而实现权限控制的功能。 3. 参数校验:通过自定义注解可以在方法的参数上添加注解,然后通过AOP技术实现对方法的拦截,在方法执行前对参数进行校验,从而实现参数的合法性验证。 4. 缓存管理:通过自定义注解可以在需要进行缓存管理的方法上添加注解,然后通过AOP技术实现对这些方法的拦截,在方法执行前先从缓存中查询结果,如果存在则直接返回,如果不存在则执行方法并将结果存入缓存中。 5. 事务管理:通过自定义注解可以在需要进行事务管理的方法上添加注解,然后通过AOP技术实现对这些方法的拦截,在方法执行前开启事务,在方法执行后根据方法的执行结果进行事务的提交或回滚。 这些场景都可以通过使用自定义注解来实现特定的功能,提高代码的可读性和可维护性。同时,通过AOP技术的支持,可以很方便地实现对注解的解析和处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [2023年JAVA最新面试题](https://blog.csdn.net/CSDN_Admin0/article/details/131719225)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值