Java 自定义注解及使用场景

转载:

https://www.jianshu.com/p/a7bedc771204

Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。
本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP。

一. 什么是注解(Annotation)

Java注解是什么,以下是引用自维基百科的内容

Java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。
Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。 当然它也支持自定义Java标注。

二. 注解体系图

元注解:java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。主要使用的是Target和Retention注解

 
元注解

注解处理类:既然上面定义了注解,那得有办法拿到我们定义的注解啊。java.lang.reflect.AnnotationElement接口则提供了该功能。注解的处理是通过java反射来处理的。如下,反射相关的类Class, Method, Field都实现了AnnotationElement接口。

 
反射处理
 
AnnotationElement接口方法

因此,只要我们通过反射拿到Class, Method, Field类,就能够通过getAnnotation(Class<T>)拿到我们想要的注解并取值。

三. 常用元注解

Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:

  • METHOD:用于描述方法
  • PACKAGE:用于描述包
  • PARAMETER:用于描述方法变量
  • TYPE:用于描述类、接口或enum类型

Retention: 表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为:

  • SOURCE:在源文件中有效,编译过程中会被忽略
  • CLASS:随源文件一起编译在class文件中,运行时忽略
  • RUNTIME:在运行时有效

只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。
所以,假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用。可以定义如下,代码见我的GitHub

  1. @Target(ElementType.FIELD) // 注解用于字段上
  2. @Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
  3. public @interface MyField {
  4. String description();
  5. int length();
  6. }
四. 示例-反射获取注解

先定义一个注解:

  1. @Target(ElementType.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface MyField {
  4. String description();
  5. int length();
  6. }

通过反射获取注解

  1. public class MyFieldTest {
  2. //使用我们的自定义注解
  3. @MyField(description = "用户名", length = 12)
  4. private String username;
  5. @Test
  6. public void testMyField(){
  7. // 获取类模板
  8. Class c = MyFieldTest.class;
  9. // 获取所有字段
  10. for(Field f : c.getDeclaredFields()){
  11. // 判断这个字段是否有MyField注解
  12. if(f.isAnnotationPresent(MyField.class)){
  13. MyField annotation = f.getAnnotation(MyField.class);
  14. System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]");
  15. }
  16. }
  17. }
  18. }

运行结果

 
运行结果
应用场景一:自定义注解+拦截器 实现登录校验

接下来,我们使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。
首先定义一个LoginRequired注解

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface LoginRequired {
  4. }

然后写两个简单的接口,访问sourceA,sourceB资源

  1. @RestController
  2. public class IndexController {
  3. @GetMapping("/sourceA")
  4. public String sourceA(){
  5. return "你正在访问sourceA资源";
  6. }
  7. @GetMapping("/sourceB")
  8. public String sourceB(){
  9. return "你正在访问sourceB资源";
  10. }
  11. }

没添加拦截器之前成功访问

 
sourceB

实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:

  1. public class SourceAccessInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. System.out.println("进入拦截器了");
  5. return true;
  6. }
  7. @Override
  8. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  9. }
  10. @Override
  11. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  12. }
  13. }

实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中

  1. @Configuration
  2. public class InterceptorTrainConfigurer implements WebMvcConfigurer {
  3. @Override
  4. public void addInterceptors(InterceptorRegistry registry) {
  5. registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");
  6. }
  7. }

拦截成功如下

 
拦截了

在sourceB方法上添加我们的登录注解@LoginRequired

  1. @RestController
  2. public class IndexController {
  3. @GetMapping("/sourceA")
  4. public String sourceA(){
  5. return "你正在访问sourceA资源";
  6. }
  7. @LoginRequired
  8. @GetMapping("/sourceB")
  9. public String sourceB(){
  10. return "你正在访问sourceB资源";
  11. }
  12. }

简单实现登录拦截逻辑

  1. @Override
  2. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  3. System.out.println("进入拦截器了");
  4. // 反射获取方法上的LoginRequred注解
  5. HandlerMethod handlerMethod = (HandlerMethod)handler;
  6. LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
  7. if(loginRequired == null){
  8. return true;
  9. }
  10. // 有LoginRequired注解说明需要登录,提示用户登录
  11. response.setContentType("application/json; charset=utf-8");
  12. response.getWriter().print("你访问的资源需要登录");
  13. return false;
  14. }

运行成功,访问sourceB时需要登录了,访问sourceA则不用登录,完整代码见我的GitHub

 
sourceA
 
sourceB
应用场景二:自定义注解+AOP 实现日志打印

先导入切面需要的依赖包

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>

定义一个注解@MyLog

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface MyLog {
  4. }

定义一个切面类,见如下代码注释理解:

  1. @Aspect // 1.表明这是一个切面类
  2. @Component
  3. public class MyLogAspect {
  4. // 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
  5. // 切面最主要的就是切点,所有的故事都围绕切点发生
  6. // logPointCut()代表切点名称
  7. @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")
  8. public void logPointCut(){};
  9. // 3. 环绕通知
  10. @Around("logPointCut()")
  11. public void logAround(ProceedingJoinPoint joinPoint){
  12. // 获取方法名称
  13. String methodName = joinPoint.getSignature().getName();
  14. // 获取入参
  15. Object[] param = joinPoint.getArgs();
  16. StringBuilder sb = new StringBuilder();
  17. for(Object o : param){
  18. sb.append(o + "; ");
  19. }
  20. System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString());
  21. // 继续执行方法
  22. try {
  23. joinPoint.proceed();
  24. } catch (Throwable throwable) {
  25. throwable.printStackTrace();
  26. }
  27. System.out.println(methodName + "方法执行结束");
  28. }
  29. }

在步骤二中的IndexController写一个sourceC进行测试,加上我们的自定义注解:

  1. @MyLog
  2. @GetMapping("/sourceC/{source_name}")
  3. public String sourceC(@PathVariable("source_name") String sourceName){
  4. return "你正在访问sourceC资源";
  5. }

启动springboot web项目,输入访问地址

 
访问项目
 
切面成功

注解的使用、拦截器使用、AOP切面使用的更多相关文章

  1. 过滤器、拦截器和AOP的分析与对比

    目录 一.过滤器(Filter) 1.1 简介 1.2 应用场景 1.3 源码分析 二.拦截器(Interceptor) 2.1 简介 2.2 应用场景 2.2 源码分析 三.面向切面编程(AOP) ...

  2. 三种实现日志过滤器的方式 (过滤器 (Filter)、拦截器(Interceptors)和切面(Aspect))

    1.建立RequestWrapper类 import com.g2.order.server.utils.HttpHelper; import java.io.BufferedReader; impo ...

  3. spring自定义注解实现登陆拦截器

    1.spring自定义注解实现登陆拦截器 原理:定义一个注解和一个拦截器,拦截器拦截所有方法请求,判断该方法有没有该注解.没有,放行:有,要进行验证.从而实现方法加注解就需要验证是否登陆. 2.自定义 ...

  4. 过滤器,拦截器,aop区别与使用场景

    1. 什么是过滤器 过滤器,顾名思义就是起到过滤筛选作用的一种事物,只不过相较于现实生活中的过滤器,这里的过滤器过滤的对象是客户端访问的web资源,也可以理解为一种预处理手段,对资源进行拦截后,将其中 ...

  5. 基于springmvc开发注解式ip拦截器

    一.注解类 @Documented @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) ...

  6. 过滤器、拦截器、AOP的区别

    过滤器 过滤器可以拦截到方法的请求和响应(ServletRequest request, SetvletResponse response),并对请求响应做出响应的过滤操作,比如设置字符编码.鉴权操作 ...

  7. Spring实现自定义注解并且配置拦截器进行拦截

    有时候我们会自定义注解,并且需要配置拦截器对请求方法含有该自定义注解的方法进行拦截操作 自定义注解类 NeedToken.java import java.lang.annotation.Docume ...

  8. 【转】 AOP(面向切面编程)、Filter(过虑器)、Interceptor(拦截器)

    AOP(面向切面编程) 面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承.多态和封装.而封装就要求将功能分散到不同的对象中去,这在软 ...

  9. .net core 3.1 过滤器(Filter) 与中间件与AOP面向切面 与拦截器及其应用

    Filter(过滤器) 总共有五种,Authorization Filter,Resource Filter,Exception Filter,Action Filter,Result Filter ...

随机推荐

  1. Swoole 中使用 TCP 异步服务器、TCP 协程服务器、TCP 同步客户端、TCP 协程客户端

    TCP 异步风格服务器 异步风格服务器通过监听事件的方式来编写程序.当对应的事件发生时底层会主动回调指定的函数. 由于默认开启协程化,在回调函数内部会自动创建协程,遇到 IO 会产生协程调度,异步风格 ...

  2. ConfigParser_读取配置文件信息

    ConfigParse简介 ConfigParser 在python中是用来解析配置文件的内置模块,直接导入使用 import configparser 使用该模块可以对配置文件进行增.读.改.删操作 ...

  3. Python 利用@property装饰器和property()方法将一个方法变成属性调用

    在创建实例属性时,如果直接把实例属性暴露出去,虽然写起来简单,但是存在一些风险,比如实例属性可以在外部被修改. 为了限制外部操作,可以通过一个set_score()方法来设置成绩,再通过一个get_s ...

  4. Zuul网关 @EnableZuulProxy 和 @EnableZuulServer 的区别

    1. @EnableZuulProxy 2. @EnableZuulServer 3.解释 1)@EnableZuulProxy简单理解为@EnableZuulServer的增强版, 当Zuul与Eu ...

  5. Windows系统上搭建Clickhouse开发环境

    Windows系统上搭建Clickhouse开发环境 总体思路 微软的开发IDE是很棒的,有两种:Visual Studio 和 VS Code,一个重量级,一个轻量级.近年来VS Code越来越受欢 ...

  6. 【基因组学】maker的安装和注释

    本文默认读者有一定的生信基础,没有基础的可以阅读以前的笔记内容. maker作为比较受人认可的基因组注释软件,其流程较为清晰简单. 不知何故,我的conda无法安装maker,故而采用手动安装方式. ...

  7. Lyft 宣布开源基础设施工具管理平台 Clutch!

    今天我们很高兴地宣布,Lyft 的基础设施工具可扩展 UI 和 API 平台clutch已开放源代码,clutch使工程团队能够构建.运行和维护用户友好的工作流,这些工作流还包含特定于域的安全机制和访 ...

  8. Jmeter中用例禁用

    1.在线程组下创建2个http请求(blogs和baidu),并且在Thread Group 添加[View Results Tree]和[View Results in Table] 2.选择[ba ...

  9. C语言字幕从外向中间汇聚

    演示数据中多个字符,从两端向中间移动,向中间汇聚 简单,粗暴,先上代码: Sleep()函数属于<windows.h>头文件中. sizeof()函数求右下标:数组内是数字时,求右下标要- ...

  10. 【刷题-PAT】A1101 Quick Sort (25 分)

    1101 Quick Sort (25 分) There is a classical process named partition in the famous quick sort algorit ...