如何在 Spring Boot 中创建自定义注释以获得更清晰的代码

您是否曾经发现自己不得不一遍又一遍地编写相同的代码,并希望有一种方法可以简化这个过程?如果您正在使用 Spring Boot,那么您可能熟悉注释在使代码更简洁、更高效方面有多么强大。但是,如果现有的注释不太符合您的需求,会发生什么?

这就是自定义注释发挥作用的地方。想象一下,能够创建自己的注释来封装重复的代码模式,使您的代码不仅更干净,而且更易于维护。在本文中,我们将探讨如何在 Spring Boot 中制作自定义注释,以简化您的开发过程并帮助您避免冗余代码的麻烦。无论您是希望实施最佳实践、添加自定义验证还是只是简化逻辑,自定义注释可能正是您所需要的工具。让我们深入研究并开始让您的代码更智能地工作,而不是更努力地工作!

让我分享一个例子:我遇到过一种情况,我需要将应用程序中的所有错误记录到错误日志表中。反复向每个需要它的方法添加相同的日志代码很快就变得令人厌烦。我将向您展示如何使用自定义注释解决这个问题。

此自定义注释背后的想法是消除方法中重复错误处理代码的需要。您只需添加自定义注释,而不必手动将每个方法包装在 try-catch 块中并记录异常。此注释将充当标志,表示方法中发生的任何错误都应自动记录。在底层,注释将利用 Spring 的面向方面编程 (AOP) 功能来拦截方法调用、捕获任何异常并根据预定义的格式记录它们。

这种方法不仅可以减少样板代码,还可以确保整个应用程序中错误处理和记录方式的一致性。通过将错误处理逻辑集中在注释中,您可以轻松管理和更新日志记录策略,而无需触及各个方法本身。这是一种保持代码库整洁、可维护且没有冗余错误处理代码的有效方法。

让我们开始吧!

步骤 1:将 Spring AOP 依赖项添加到您的项目

您可以访问 Maven 来查找最新的依赖项,或者可以使用下面提供的依赖项:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.1.12</version>
</dependency>

步骤 2:创建自定义注释

定义一个自定义注释,用于标记错误处理方法。以下是创建ErrorHandler注释的方法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ErrorHandler {
}

创建自定义注释的第一步是使用@interface关键字进行声明。在此实例中为 ErrorHandler。下一步是提供有关代码的元数据,@Retention 和 @Target 是定义如何以及在何处使用这些注释的两个重要元素。

@Retention

注释@Retention指定了注释应保留多长时间:

  • RetentionPolicy.SOURCE:注释仅保留在源代码中,并在编译期间被编译器丢弃。它不包含在编译.class文件中或在运行时可用。
  • RetentionPolicy.CLASS:注释保留在编译的.class文件中,但在运行时不可用。如果@Retention未指定,这是默认行为。
  • RetentionPolicy.RUNTIME:注释保留在编译后的.class文件中,并可通过反射在运行时使用。这允许框架和库在运行时使用该注释。

在我们的ErrorHandler注释中,@Retention(RetentionPolicy.RUNTIME)使用。这意味着注释将在运行时可用,这对于需要在应用程序执行期间检查注释的框架或自定义逻辑来说是必需的。

@Target

注释@Target指定了可以应用注释的元素类型:

  • ElementType.TYPE:该注释可应用于类、接口或枚举。
  • ElementType.FIELD:该注解可以作用于字段(变量)。
  • ElementType.METHOD:该注解可以应用于方法。
  • ElementType.PARAMETER:该注解可以应用于方法参数。
  • ElementType.CONSTRUCTOR:该注解可应用于构造函数。
  • ElementType.LOCAL_VARIABLE:该注解可以应用于局部变量。
  • ElementType.ANNOTATION_TYPE:该注释可以应用于其他注释。
  • ElementType.PACKAGE:该注解可应用于包声明。

对于ErrorHandler注释,@Target(ElementType.METHOD)指定注释只能应用于方法。这意味着您可以使用@ErrorHandler它来标记要应用自定义错误处理逻辑的方法,但不能应用于类、字段或其他元素。

现在让我们定义这个ErrorHandler方面。

import com.maheshbabu11.spring_custom_annotations.service.ErrorLog;
import com.maheshbabu11.spring_custom_annotations.service.ErrorLogRepository;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.UUID;

@Aspect
@Component
public class ErrorHandlerAspect {

    private final ErrorLogRepository errorLogRepository;

    public ErrorHandlerAspect(ErrorLogRepository errorLogRepository) {
        this.errorLogRepository = errorLogRepository;
    }

    @Pointcut("@annotation(com.maheshbabu11.spring_custom_annotations.annotations.ErrorHandler)")
    public void handleException() {
    }

    @AfterThrowing(pointcut = "handleException()", throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("Exception occurred: " + ex.getMessage());
        ErrorLog errorLog = new ErrorLog();
        errorLog.setErrorLogId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE); // Generate a unique ID
        errorLog.setExceptionMessage(ex.getMessage());
        errorLog.setExceptionStackTrace(getStackTraceAsString(ex));
        errorLogRepository.save(errorLog);
    }

    private String getStackTraceAsString(Exception ex) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        ex.printStackTrace(pw);
        return sw.toString();
    }
}
  • @Aspect 注释将类标记为一个方面,这使得它包含将应用于切入点定义的连接点的建议。
  • 注释@Pointcut定义了一个名为 的切入点handleException,它与任何用 注释的方法匹配@ErrorHandler。这是建议的目标。
  • @AfterThrowing注解指定在与切入点匹配的方法抛出异常afterThrowing后调用该方法。该参数将抛出的异常绑定到通知方法的参数上。handleExceptionthrowingex
  • afterThrowing 方法将异常消息记录到控制台并创建一个ErrorLog对象,用异常的详细信息(包括唯一 ID 和堆栈跟踪)填充它,然后ErrorLog使用 保存该对象errorLogRepository
  • getStackTraceAsString 方法将异常的堆栈跟踪转换为字符串格式,这对于日志记录和调试很有用。

现在让我们将这个注释添加到我们的方法中并看看它是如何工作的。

步骤3:向所需方法添加自定义注释。

让我们将自定义注释添加到我们的方法中,看看当该方法发生异常时会发生什么。

   @ErrorHandler @ErrorHandler 
    public  void  testExceptionLogging ( ) { 

        //模拟异常
        if ( true ) { 
            throw  new  RuntimeException ( "发生异常" ); 
        } 
    }

一旦调用该方法,就会抛出一个新的运行时异常,从而触发@AfterThrowing建议ErrorHandlerAspect。这反过来会将异常消息打印到控制台并将错误记录在错误日志表中。