Java注解、元注解、利用拦截器实现自定义注解

常用的注解,for example:

注解作用
@Override检查该方法是否被重写。如果其父类或者是引用的接口中并没有该方法时,会报编译错误,一般在接口实现和类继承时候用的比较多
@Resourcejavax提供,作用是bean注入,可以根据bean的name和type进行指定,默认是通过Java反射通过name自动注入
@Autowiredspring提供,作用也是bean注入,通过bean的type进行注入,可以搭配@Qualifier按照name进行注入
介绍

​ Java注解是Java5引入的一种机制,其原理还是Java的反射。划重点了,注解常用最多的地方是参数、类和方法,但不仅仅只是这些地方,它甚至可以作用在包上。为什么这样说呢,接下来通过一个概念为大家进行说明-元注解。你没有听错,就是元注解——用于修饰注解的注解。

​ 在Idea上,随便点击一个注解查看,这里以@Autowired为例进行说明:

在这里插入图片描述

​ 通过这张图,我们可以清楚的看到一些东西:

  1. @Autowired的定义是@interface,聪明的同学大概可以猜出来,interface不就是接口嘛,在这里我简单的提一句,感兴趣的同学可以下来慢慢了解,在java.lang.annotation.Annotation接口中存在这么一句话用来描述注解:The common interface extended by all annotation types,翻译过来的意思就是所有注解类型都继承自这个常用接口。稍微理解起来有点抽象,但说出了注解的本质,注解就是一个接口,可以这么理解。有关这一点,任意反编译一个注解类都可以得到这个结果。

  2. 这个注解被@Target、@Retention、@Documented所修饰,不只是@Autowired注解,其他注解也可以被它们所修饰,这类注解我们称其为元注解(meta-annotation),更为通俗易懂的说法:Java内置的,且用于修饰注解的注解就是元注解。接下来介绍下Java5定义的4个标准的meta-annotation

元注解(meta-annotation)作用
@Target定义注解的作用目标,包括TYPE/FIELD/METHOD/PARAMETER/CONSTRUCTOR/LOCAL_VARIABLE/PACKAGE/TYPE_PARAMETER/TYPE_USE
@Retention定义注解的保留策略,具体有SOURC/CLASS/RUNTIME,一般我们用RUNTIME保留在运行时就可以了,根据使用场景的不同来选择
@Documented设置所修饰注解是否包含在javadoc中
@Inherited定义该注解是否可以被继承使用

​ 每个具体修饰字符的含义都可以通过jdk自带的注释进行了解,这里我们以@Target这个元注解的作用域为例:
在这里插入图片描述
​ 使用TYPE,可以令当前注解在类、接口、甚至枚举上生效;使用Field,令该注解在类的属性上生效。这就对应了上文我们提到的注解的作用范围,相信大家应该可以理解了,一个注解在哪个地方生效其实就是@Target这个元注解的功劳。

  1. @Autowired这个注解中存在required这个属性,且默认为true。通过注释我们可以清楚的知道这个属性的作用是声明@Autowired这个注释依赖是必要的。为什么我要提这个呢?通过官方的例子,我们可以大致勇敢的推测一下,注解的定义声明等是否和类的定义声明有着共通之处,可以通过自定义属性和方法的方式来让这个类为我们做特定的一些处理逻辑呢?答案是显而易见的,Absolutely true!
自义定注解

​ 说了这么多,最重要的还是要学会如何使用,相信这是大部分同学最为关心的地方吧。接下来,我将以一个简单的SpringBoot Demo来为大家演示如何自定义一个注解(不会SprintBoot的同学没关系,博主力求每个人都能看懂)

  1. 创建自定义注解
package com.ypc.base.annotation;

import java.lang.annotation.*;

@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamsChecking {
    String value() default "";
}


​ 这里我们定义了一个名为ParamsChecking的注解,可以看到@Target我们制定范围为Type,即当前这个注解在类、接口和枚举生效;value属性,赋默认值""。

  1. 定义一个请求类用于封装请求参数
package com.ypc.base.request;

import com.ypc.base.annotation.ParamsChecking;

/**
 * @ClassName: BaseReq
 * @Author: Administrator
 * @Date: 2020/12/22
 * @Description: TODO
 **/
@ParamsChecking
public class BaseReq {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

​ 这里我们采用生成getter/setter的方式,有的同学可能喜欢用lombok的@Data等注解,这种方式确实对自己而言是非常方便的,不过对团队开发来说还是会有很多的问题。

  1. 新建一个BaseController控制器,用于我们接口测试
package com.ypc.base.controller;

import com.jiufang.widget.utils.resp.RestResult;
import com.ypc.base.request.BaseReq;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: BaseController
 * @Author: Administrator
 * @Date: 2020/12/22
 * @Description: TODO
 **/

@RestController
@RequestMapping("base")
public class BaseController {

    @RequestMapping("/hello")
    public RestResult test(BaseReq words){
        RestResult restResult = RestResult.create();
        restResult.setText(words.getName());
        return restResult;
    }

}

​ 定义controller的时候有几处地方做下简单说明:

  • @RestController,这是Spring为我们提供的一个注解,相当于@Controller和@ResponseBody的组合。这里我们的不需要返回页面,我们希望/base/hello作为一个纯粹的接口返回JSON格式的数据,使用@RestController,视图解析器在这种情况就不会解析为视图页面了。

  • @RequestMapping,这也是Spring为我们提供的注解,作用是处理请求地址映射,类似常用的还有@GetMapping、@PostMapping。这里我们不指定具体的请求方式,此时get/post请求都可以。

  • RestResult,是自己封装的一个Java类,遵循resultful风格。

    小伙伴们可以验证一下,如果我们在请求参数这里加上我们的自定义注解,代码编译是有错误的,原因就是我们在@Target中并未指定ElementType.PARAMETER
    在这里插入图片描述

    手动修改我们的注解@Target({ElementType.TYPE, ElementType.PARAMETER})

  1. 定义一个请求拦截器
package com.ypc.base.interceptor;

import com.ypc.base.annotation.ParamsChecking;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Set;

/**
 * @ClassName: RequestInterceptor
 * @Author: Administrator
 * @Date: 2020/12/22
 * @Description: TODO
 **/
@Component
public class RequestInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new AnnotationTypeFilter(ParamsChecking.class));
        Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("com.ypc.base.request");
        //此处写法并不规范,只是为了方便才直接beanDefinitionSet.iterator().next()
        Class clazz = Class.forName(beanDefinitionSet.iterator().next().getBeanClassName());
        ParamsChecking paramsCheckingAnnotation = (ParamsChecking) clazz.getAnnotation(ParamsChecking.class);
        //此处逻辑只是为了演示效果,小伙伴们可以根据场景自行设计
        if(!paramsCheckingAnnotation.value().contains("ypc")){
            throw new RuntimeException("error!");
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

​ ClassPathScanningCandidateComponentProvider是Spring提供的扫描类,可以在指定范围内根据过滤的规则来进行扫描。这里有个小陷阱,扫描的范围一定是使用到我们自定义注解的路径,而不是自定义注解路径。有一点点绕,通俗来说就是注解的使用范围,而不是定义范围。直观反映到我们的代码中就是BaseReq的路径,而不是 @ParamsChecking 的路径

  1. 使我们的拦截器生效
package com.ypc.base.config;

import com.ypc.base.interceptor.RequestInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @ClassName: WebMvcConfig
 * @Author: Administrator
 * @Date: 2020/12/22
 * @Description: TODO
 **/

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    RequestInterceptor requestInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截范围设置,*是通配符代表所有
        registry.addInterceptor(requestInterceptor).addPathPatterns("/base/*");
    }
}

  1. 实际效果

​ 启动我们的项目,在浏览器中访问我们的接口http://localhost:8000/base/hello,会提示我们type=Internal Server Error, status=500,此时再回过头来看看我们的程序,控制台中抛出了RuntimeException:error! 说明符合我们的预期,一个简单的注解运用就好啦!

在这里插入图片描述

总结

​ 自定义注解的本质就是个接口,一个特殊解析处理过的结果,其核心用到了Java的反射机制,我们可以通过Java反射为我们做许多的事情。Java内置的可用于修饰注解的注解就是元注解,强大的元注解可以为注解提供丰富多样的功能。

​ 对啦,这里再顺便提一句,我们演示的效果采用的是拦截器的方式,Listenr,Fileter,Aop都是可以的哦,另外自定义的注解用于验证属性的时候@Constraint可是一个很强大的帮手哦!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值