自定义注解

在我们实际开发过程中如果能合理的运用自定义注解,则会大大减少我们代码的开发量。那怎么才能实现自定义注解呢?废话不多说,直接上干货!

一、创建注解

这一步呢,我们可以理解成对应的实体类,我们要自定义注解,也需要这么一个东西,注解的名称,有哪些属性等等。此时小伙伴说那我也不会写啊,我都没见过啊。

好好好,你说得对,接下来,让我们来抄一个来学习吧!@RestController,大家也可以找几个常见的注解,点进代码中查看一下别人是怎么写的

此时我们就依葫芦画瓢一个吧。

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AdminAnnotation {
    boolean admin() default true;  // 用来校验访问接口时必须要是admin权限,否则禁止访问
}

@Retention: 表示该注解的生命周期,是RetentionPolicy类型的,该类型是一个枚举类型,可提供三个值选择,分别是:CLASS、RUNTIME、SOURCE

RetentionPolicy.CLASS: 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;

RetentionPolicy.RUNTIME: 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

RetentionPolicy.SOURCE: 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃

由此可见生命周期关系:SOURCE < CLASS < RUNTIME,我们一般用RUNTIME

@Target: 表示该注解的作用范围,是ElementType类型的,该类型是一个枚举类型,一共提供了10个值选择,我们最常用的几个:FIELD、TYPE、PARAMETER、METHOD

ElementType.FIELD:用于字段、枚举的常量

ElementType.TYPE:用于接口、类、枚举、注解

ElementType.PARAMETER:用于方法参数

ElementType.METHOD:用于方法

注意点:在Java注解中定义的属性,其定义格式是类似于方法的,但是不需要大括号

此时我们自定义注解写好了,接下来干嘛呢?当然是来实现我们的注解了,来帮我们干活简化代码。接下来提供两种实现自定义注解的方式。

使用拦截器实现自定义注解

拦截器的作用就在这里简单介绍一下,不太了解的小伙伴可以先去补充一下基础知识或者是直接跳过看第二种方式。

Java中的拦截器(Interceptor)是一个强大的工具,它允许在请求到达目标方法之前或之后执行特定的逻辑。在Java Web开发中,拦截器常常用于拦截和处理HTTP请求。例如,在Spring MVC框架中,拦截器可以拦截用户的请求,并在请求处理之前或之后执行特定的操作。

自定义拦截器

java 复制代码
@Slf4j
@Component
public class AdminInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        log.info("进入了拦截器");
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 获取方法上面的注解
        AdminAnnotation adminAnnotation = handlerMethod.getMethod().getAnnotation(AdminAnnotation.class);
        Class<?> declaringClass = handlerMethod.getMethod().getDeclaringClass();
        System.out.println("调用方法的类:==>" + declaringClass);
        Method method = handlerMethod.getMethod();
        System.out.println("调用方法:==>" + method.getName());
        if (adminAnnotation != null && adminAnnotation.admin()) {
            // TODO 需要管理员权限  do something 如果判断该用户无此权限,直接返回false,如果该用户拥有此权限,返回true
        }
        // 不需要权限
        return true;
    }
}

自定义拦截器定义之后,就能立马使用了吗?那肯定是不可以滴,还没有注册这个拦截器,并指定它应该拦截哪些URL或请求呢。

java 复制代码
@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer{
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    // 把我们自定义的拦截器加入其中,并且配置上需要拦截的路径
        registry.addInterceptor(new AdminInterceptor()).addPathPatterns("/admin/test");
    }
}

controller

less 复制代码
@RestController
@RequestMapping("/admin")
public class AdminController {

    @GetMapping("/test")
    @AdminAnnotation
    public String login(){
        return "访问成功";
    }

    @GetMapping("/tes2")
    @AdminAnnotation(admin=false)
    public String login2(){
        return "访问成功";
    }
}

这时候启动项目并且访问localhost:8889/admin/test 小伙伴们可以自己修改地址哦就能被此拦截器拦截(小伙伴们可以自行更改代码中TODO,来模拟用户是否拥有权限访问时的场景),也可以调用login2()看看是否有什么区别。 好了,到此第一种方式实现自定义注解就结束了,小伙伴们学废了嘛?是不是感觉好麻烦,怎么还牵扯出来拦截器了,好复杂。这时我们就来看看第二种方式吧。

使用AOP来实现自定义注解

先来解释一下什么是aop

AOP,即面向切面编程。在AOP中,程序员可以定义所谓的"切面",这些切面实际上是一些跨多个类的通用功能或关注点,如日志记录、事务管理、性能监控等。AOP允许在程序执行流程中的适当位置"拦截"方法调用,将预处理和后期处理逻辑交给特定的拦截器或通知(Advice)来完成。

AOP中有几个核心概念:

  • Aspect(切面) :通常指用@Aspect标识的类,它包含了多个通知和切点定义。
  • Join point(连接点) :在Spring AOP中,连接点通常代表一个方法执行。目标对象中的方法就是一个连接点。
  • Advice(通知) :定义了切面在何时以及如何应用其逻辑。例如,@Before、@AfterReturning、@AfterThrowing、@After和@Around都是不同类型的通知。
  • Pointcut(切点) :是连接点的集合,它定义了哪些连接点应该被通知所影响。

先引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

直接上代码

方法上添加注解

java 复制代码
package com.example.bucket.自定义注解.aspect;

import com.example.bucket.自定义注解.myannotation.AdminAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

@Component
@Aspect
public class AdminAspect {
// ①    @Pointcut("@annotation(com.example.bucket.自定义注解.myannotation.AdminAnnotation)")
// ①   public void adminPointCut(){}


// ①   @Around("adminPointCut()")

    // 这里介绍两种写法,说白了就是定义切入的点,告诉在什么情况切入而已
    @Around("@annotation(adminAnnotation)") // ②
    public Object logAround(ProceedingJoinPoint joinPoint,AdminAnnotation adminAnnotation) throws Throwable {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();  
        //获取传入目标方法的参数
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            System.out.println("第" + (i+1) + "个参数为:" + args[i]);
        }
        // 执行目标方法  也就是被调用借口的那个方法
        // System.out.println(joinPoint.proceed());

        Method method = methodSignature.getMethod();
        // 判断该方法上是否有该注解
        boolean present = method.isAnnotationPresent(AdminAnnotation.class);
        // 获取方法上面的注解
        AdminAnnotation annotation = method.getAnnotation(AdminAnnotation.class);
        if (annotation.admin()){
            // TODO DO SOMETHING  业务逻辑,进行判断是否拥有权限
        }
        return joinPoint.proceed();
    }
}

类上添加注解

此时能够打在方法上面的注解就讲解告一段落了。此时有人就又再想了,那我要是有一个Controller中所有的方法,都必须是管理员或者是拥有某些权限才能登录,那我每个方法挨个写上该注解岂不是要累死了?此时就可以更改上面的注解的作用范围。更改如下

java 复制代码
@Target(ElementType.TYPE) // 更改为TYPE此时可以作用在类和接口上面。
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AdminAnnotation {
    boolean admin() default true;  // 用来校验访问接口时必须要是admin权限,否则禁止访问
}

此时可以稍微简化一下上面的Controller了。

less 复制代码
@RestController
@RequestMapping("/admin")
@AdminAnnotation  // 此时但凡是访问了改类中的方法,都必须要有权限才能进行访问。
public class AdminController {

    @GetMapping("/test")
    public String login(){
        return "访问成功";
    }

    @GetMapping("/tes2")
    public String login2(){
        return "访问成功";
    }
}
java 复制代码
@Component
@Aspect
public class AdminAspect {



    @Around("@within(adminAnnotation)") // within 只能匹配类这级
    public Object logAround(ProceedingJoinPoint joinPoint,AdminAnnotation adminAnnotation) throws Throwable {
        // 只写一些比较关键性的代码,其余代码跟上述相同
        
        
        Class aClass = methodSignature.getDeclaringType();
        
        // 拿到该类所有的方法(包括私有方法)
        // Method[] methods = methodSignature.getDeclaringType().getDeclaredMethods();
        
        // 判断类上面有没有打该注解
        boolean annotationPresent = aClass.isAnnotationPresent(AdminAnnotation.class);
        if (annotationPresent){
        AdminAnnotation annotation = (AdminAnnotation) aClass.getAnnotation(AdminAnnotation.class);
        if (annotation.admin()){
            // TODO 进行校验.
            }
        }
    }
}

其他

还有一些其他AOP切入点表达式大家可以参考这篇文章,讲解的很详细。AOP切入点表达式 今日摸鱼结束。下班。

相关推荐
Victor35614 分钟前
MongoDB(2)MongoDB与传统关系型数据库的主要区别是什么?
后端
JaguarJack15 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端·php·服务端
BingoGo15 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端
Victor35617 分钟前
MongoDB(3)什么是文档(Document)?
后端
牛奔2 小时前
Go 如何避免频繁抢占?
开发语言·后端·golang
想用offer打牌7 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
KYGALYX9 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法9 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端