学一学SpringBoot中的自定义Starter

260 阅读8分钟

前言


自己动手简单的封装、应用一个starter

该starter的作用是被引入后,会对项目中Controller出现的异常做统一的处理及反馈

starter的思想在实际开发过程被大量的应用


一、新建starter项目

使用IDE创建一个maven构建方式的空白项目

1.1pom.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 ​
     <groupId>com.lazy.snail</groupId>
     <artifactId>exception-handler-spring-boot-starter</artifactId>
     <version>1.0.0</version>
     <packaging>jar</packaging>
 ​
     <properties>
         <java.version>17</java.version>
         <encoding>UTF-8</encoding>
     </properties>
 ​
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <version>2.7.12</version>
         </dependency>
     </dependencies>
 ​
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.8.1</version>
                 <configuration>
                     <source>17</source>
                     <target>17</target>
                 </configuration>
             </plugin>
         </plugins>
     </build>
 ​
 </project>
  • 自定义的starter命名为xxx-spring-boot-starter
  • spring-boot-starter-web依赖的引入是由于开发starter过程中会使用到相关的类或注解,如:@RestControllerAdvice、@ExceptionHandler等
  • 构建安装时(clean install)可以去掉spring-boot-starter-web依赖
  • exception-handler-spring-boot-starter的应用场景就是基于springboot的web项目,所以相关的依赖在应用项目中都会有

1.2自定义一个Api异常

 package com.lazy.snail.exceptionhandler;
 ​
 /**
  * @ClassName ApiException
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/7 15:06
  * @Version 1.0
  */
 public class ApiException extends RuntimeException {
     private int code;
     private String message;
 ​
     public ApiException(int code, String message) {
         super(message);
         this.code = code;
         this.message = message;
     }
 ​
     public int getCode() {
         return code;
     }
 ​
     @Override
     public String getMessage() {
         return message;
     }
 }
  • 引入项目中可以根据实际业务抛出该异常

1.3自定义反馈结果

 package com.lazy.snail.exceptionhandler;
 ​
 /**
  * @ClassName ApiResponse
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/7 15:06
  * @Version 1.0
  */
 public class ApiResponse<T> {
     private int status;   // 状态码
     private String message;  // 错误信息或成功提示
     private T data;  // 业务数据
 ​
     public ApiResponse(int status, String message, T data) {
         this.status = status;
         this.message = message;
         this.data = data;
     }
 ​
     public int getStatus() {
         return status;
     }
 ​
     public void setStatus(int status) {
         this.status = status;
     }
 ​
     public String getMessage() {
         return message;
     }
 ​
     public void setMessage(String message) {
         this.message = message;
     }
 ​
     public T getData() {
         return data;
     }
 ​
     public void setData(T data) {
         this.data = data;
     }
 }

出现异常情况时,简单的封装一个反馈对象

1.4异常处理配置类

 package com.lazy.snail.exceptionhandler;
 ​
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 ​
 /**
  * @ClassName ExceptionHandlerAutoConfiguration
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/7 15:32
  * @Version 1.0
  */
 @Configuration
 public class ExceptionHandlerAutoConfiguration {
 ​
     @Bean
     @ConditionalOnMissingBean(GlobalExceptionHandler.class)
     public GlobalExceptionHandler globalExceptionHandler() {
         return new GlobalExceptionHandler();
     }
 }

普通的配置类,当springboot启动过程中,容器中没有GlobalExceptionHandler类型的bean时,将创建一个

1.5异常处理类

 package com.lazy.snail.exceptionhandler;
 ​
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 ​
 /**
  * @ClassName GlobalExceptionHandler
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/7 15:06
  * @Version 1.0
  */
 @RestControllerAdvice
 public class GlobalExceptionHandler {
 ​
     @ExceptionHandler(ApiException.class)
     public ResponseEntity<ApiResponse<Object>> handleApiException(ApiException ex) {
         // 捕获 ApiException,返回指定的状态码和信息
         ApiResponse<Object> response = new ApiResponse<>(ex.getCode(), ex.getMessage(), null);
         return new ResponseEntity<>(response, HttpStatus.valueOf(ex.getCode()));
     }
 ​
     @ExceptionHandler(Exception.class)
     public ResponseEntity<ApiResponse<Object>> handleException(Exception ex) {
         // 捕获其他未处理的异常,返回500状态码
         ApiResponse<Object> response = new ApiResponse<>(500, "Internal Server Error", null);
         return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
     }
 }

@RestControllerAdvice

  • Spring 提供的注解,用于定义全局的异常处理器、数据绑定和数据格式化。它是 @ControllerAdvice@ResponseBody 的组合,表示该类中的方法返回的对象会自动被序列化为 JSON 或 XML 格式,直接返回给客户端。
  • @RestControllerAdvice 的作用范围是整个应用的控制层,它会捕获所有标注了 @RequestMapping 的控制器中的异常。

@ExceptionHandler(ApiException.class)

  • 该注解标注的方法用于捕获 ApiException 类型的异常。通过将异常类型作为参数传入,Spring 可以在控制器中抛出 ApiException 时自动调用此方法。
  • @ExceptionHandler 注解的作用是捕获指定类型的异常,使得不同的异常处理逻辑可以分开,实现精细化处理。

@ExceptionHandler(Exception.class)

  • 该注解标注的方法用于捕获 Exception 类型的异常,这意味着它将处理所有未被其他 @ExceptionHandler 方法处理的异常,起到“兜底”作用。
  • 当出现任何未预料的异常时,Spring 会调用这个方法,为这些异常提供一个默认的错误响应,避免未处理的异常暴露到前端。

1.6spring.factories

 # META-INF/spring.factories
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.lazy.snail.exceptionhandler.ExceptionHandlerAutoConfiguration

实现自动配置机制

  • Spring Boot 会在启动时扫描 META-INF/spring.factories 文件中的 EnableAutoConfiguration 配置,加载所有指定的自动配置类。这里指定了 ExceptionHandlerAutoConfiguration 作为自动配置类,因此 Spring Boot 会在启动时自动加载 com.lazy.snail.exceptionhandler.ExceptionHandlerAutoConfiguration

简化项目中的配置

  • 通过将异常处理逻辑配置为自动配置类,可以在不同项目中只需要引入该 starter,就可以自动获得其中定义的异常处理逻辑,而不需要手动配置。模块化的设计方便了复用和维护。

按需加载组件

  • ExceptionHandlerAutoConfiguration 中,通常会定义与异常处理相关的 @Bean,如 GlobalExceptionHandler 或其他配置项。通过这种自动配置机制,Spring Boot 会自动加载这些 Bean,确保全局异常处理器在项目启动时生效。
  • ExceptionHandlerAutoConfiguration 还可以通过条件注解(例如 @ConditionalOnMissingBean)判断是否需要创建某些 Bean,可以在主项目中进行定制,避免重复配置或冲突。

支持条件加载

  • Spring Boot 的自动配置类通常可以配置成启用或禁用。通过在 ExceptionHandlerAutoConfiguration 中使用条件注解(如 @ConditionalOnProperty),可以控制自动配置的加载,比如通过属性开关来启用或禁用异常处理功能。

二、构建打包

2.1命令行

 mvn clean install

2.2IDE

image-20241107194541834

2.3说明

clean:执行清理操作

  • mvn clean 命令会删除项目的 target 目录,这个目录包含了之前构建生成的所有文件(如 .class 文件、JAR 包、WAR 包等)。这一步的作用是确保项目在全新的环境下进行构建,避免使用上一次构建中遗留的文件。

install:构建并安装项目

  • mvn install 命令会触发 Maven 的完整构建流程(编译、测试、打包等),并将生成的构件(例如 JAR、WAR 文件)安装到本地 Maven 仓库(默认在 ~/.m2/repository 目录下,也可自定义)。安装到本地仓库后,其他 Maven 项目可以通过依赖管理使用该构件。

  • 具体来说,install 命令会顺序执行以下几个阶段:

    • validate:检查项目是否正确,所有必要的信息是否完整。
    • compile:编译源代码。
    • test:运行测试代码。
    • package:将编译后的代码打包成 JAR、WAR 等文件。
    • install:将打包后的文件安装到本地 Maven 仓库,以便其他项目可以使用。

2.4结果

本地仓库中打包好了exception-handler-spring-boot-starter

image-20241107194810347

三、应用

3.1新建一个springboot项目

3.1.1pom文件

 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.7.12</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.lazy.snail</groupId>
     <artifactId>SpringBoot</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>SpringBoot</name>
     <description>SpringBoot</description>
     <properties>
         <java.version>17</java.version>
     </properties>
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <exclusions>
                 <exclusion>
                     <groupId>org.springframework.boot</groupId>
                     <artifactId>spring-boot-starter-logging</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
 ​
         <dependency>
             <groupId>com.lazy.snail</groupId>
             <artifactId>exception-handler-spring-boot-starter</artifactId>
             <version>1.0.0</version>
         </dependency>
 ​
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-log4j2</artifactId>
         </dependency>
 ​
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>
 ​
     <build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
 ​
 </project>
  • pom中引入了exception-handler-spring-boot-starter

3.1.2测试controller

 package com.lazy.snail.controller;
 ​
 import com.lazy.snail.exceptionhandler.ApiException;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 ​
 /**
  * @ClassName TestController
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/7 15:44
  * @Version 1.0
  */
 @RestController
 public class TestController {
 ​
     @GetMapping("/test")
     public String test() {
         // 手动抛出一个自定义的异常
         throw new ApiException(400, "Bad Request");
     }
 ​
     @GetMapping("/error")
     public String error() {
         int a = 1/0;
         return "error";
     }
 ​
 }
  • "/test"模拟ApiException
  • "/error"模拟不可预见的异常

3.2postman测试结果

  • /test

image-20241107200144601

  • /error

image-20241107200227738

四、总结

4.1Starter的作用

  1. 简化配置

    通过自动配置,减少了手动配置的工作量,特别是在常见功能(如数据库连接、消息队列等)的配置上。

  2. 模块化功能

    可以将某些通用功能封装到独立的模块中(例如日志、缓存、事务、消息队列等),使得其他项目可以通过引入该 starter 直接使用。

  3. 增强可复用性

    将项目中的一些通用代码和配置抽象成 starter,方便在多个项目中复用,提升开发效率。

4.2Starter的简要开发流程

  1. 定义功能模块

    明确 starter 要解决的具体功能,例如统一的异常处理、日志管理、缓存配置等。

  2. 创建自动配置类

    在starter中编写自动配置类(通常使用 @Configuration 注解)。这个类包含自动配置所需的 bean 和逻辑,比如 @Bean 定义一些默认的配置或功能。

    使用 @Conditional 注解来控制某些配置是否生效,例如 @ConditionalOnProperty 可根据配置文件中的属性来启用某些功能。

  3. 提供依赖项

    确定starter所需的依赖项,并将其添加到 pom.xml 中。例如,如果starter需要连接数据库,就需要引入相关的数据库驱动。

  4. 包装和发布

    将 starter 打包为一个可发布的 JAR 文件,并发布到本地或远程 Maven 仓库。

    在META-INF/spring.factories中注册自动配置类,使得Spring Boot能够自动识别和加载该starter。

4.3Starter的应用场景

  1. 统一配置和功能封装:

    统一的异常处理:将异常处理封装到starter中,其他项目只需引入即可自动获得一致的异常处理机制。

    统一的日志配置:将日志配置封装为starter,让不同的项目能够使用统一的日志配置和日志格式。

    统一的响应格式:提供统一的 API 响应封装类,确保所有项目的返回格式一致。

    统一的安全配置:例如身份认证、授权、权限管理等功能的配置封装。

  2. 通用功能模块:

    缓存管理:封装Spring Cache的配置,统一管理缓存的实现,减少每个项目单独配置的工作量。

    数据库连接配置:封装常见的数据源配置,如 HikariCP 或 DBCP2,简化数据源的初始化和管理。

    消息队列配置:封装对消息队列(如 RabbitMQ、Kafka)的配置,简化消息队列的使用和管理。

  3. 跨项目共享功能:

    在多个项目中有相同的需求时,使用 starter 可以将功能抽象出来,减少每个项目重复编写相同代码的工作。比如,团队内部有多个微服务,可能都需要统一的异常处理和响应格式,这时可以开发一个starter让每个项目都能复用这部分功能。

  4. 支持不同的模块或服务:

    适用于微服务架构中,每个微服务通过引入不同的starter来简化配置和功能实现。例如,可以为每个服务创建一个starter,统一管理日志、异常处理、监控等功能。

  5. 简化常见配置的开发和维护:

    健康检查、审计日志、事务管理、定时任务 等常见功能可以通过starter提供自动化配置,无需重复编写。