当前位置: 首页 > news >正文

Spring——Spring开发实战经验(1)

摘要

文章主要介绍了 Swagger 作为 API 文档生成和测试工具的功能,包括自动生成 API 文档、提供可视化调试界面、促进前后端协作、支持 OpenAPI 规范等。同时,还提及了 Spring Boot 与 Swagger3 的实战应用,以及 Spring 开发中其他相关技术内容,如 @Resource 与 @Autowired 的区别、Druid 监控配置、切面日志示例等。

1. Swagger-API文档生成和测试工具

Swagger 是一种 API 文档生成和测试工具,用于描述、生成、测试和管理 RESTful API。它的主要作用包括以下几个方面:

1.1. 生成 API 文档

Swagger 可以自动扫描代码中的 ControllerAPI 方法,并生成一份可视化的 API 文档,无需手动维护。
示例: 在 Spring Boot 项目中,添加 @ApiOperation 注解:

@ApiOperation("获取用户信息")
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {return "用户 ID:" + id;
}

1.2. 提供可视化的 API 调试界面

Swagger 自带交互式 UI,允许开发者直接在浏览器中进行 API 测试,而不需要使用 Postman、cURL 等工具。

访问 Swagger UI:

http://localhost:8080/swagger-ui/

你可以:

  • 选择 API
  • 输入参数
  • 直接点击 "Try it out" 测试 API
  • 查看 API 的 请求/响应数据

1.3. 让前后端开发协作更高效

  • 后端开发 可以专注于编写 API 代码,Swagger 自动生成 API 文档。
  • 前端开发 可以直接查看 Swagger 文档,了解 API 的调用方式,而不需要等待后端手写文档。
  • 测试人员 也可以使用 Swagger UI 进行 API 测试,提高测试效率。

1.4. 支持 OpenAPI 规范,便于 API 管理

Swagger 基于 OpenAPI 规范(OAS,OpenAPI Specification),可以生成标准的 API 描述文件(JSON 或 YAML 格式)。这些文件可以用于:

  • 生成客户端 SDK(如 Java、Python、JavaScript)
  • 生成 API 服务器代码
  • 自动化 API 测试

示例: 访问 http://localhost:8080/v3/api-docs,Swagger 会返回 JSON 格式的 API 描述:

{"openapi": "3.0.1","info": {"title": "API 文档","version": "1.0"},"paths": {"/user/{id}": {"get": {"summary": "获取用户信息","parameters": [{"name": "id","in": "path","required": true,"schema": {"type": "integer"}}],"responses": {"200": {"description": "成功"}}}}}
}

这个 JSON 文档可以用于自动生成 API 客户端代码。

1.5. 提供 API 版本管理

Swagger 支持 API 版本控制,可以在 API 文档中同时管理多个版本,例如:

@Api(tags = "用户管理 API V1")
@RequestMapping("/api/v1")
public class UserControllerV1 { }@Api(tags = "用户管理 API V2")
@RequestMapping("/api/v2")
public class UserControllerV2 { }

这样,前端可以根据不同版本调用相应的 API。

1.6. 支持多种编程语言

Swagger 不仅支持 Java,还支持多种编程语言,包括:

  • Python(FastAPI, Flask)
  • Node.js(Express)
  • Go
  • .NET(C#)
  • PHP

这使得 Swagger 成为跨语言的 API 文档标准。

1.7. Swagger总结

作用

详细描述

自动生成 API 文档

通过注解解析 API,并生成详细的文档

提供 UI 界面

让开发者可以直接在网页上查看和测试 API

提高前后端协作效率

无需手写 API 文档,前端可直接查看接口说明

支持 OpenAPI 规范

生成标准的 JSON/YAML API 文档,可用于 SDK 生成

支持 API 版本管理

可以管理多个 API 版本

兼容多种编程语言

适用于 Java、Python、Node.js、Go、C# 等

Swagger 让 API 开发更直观、高效、易维护,是现代微服务开发的重要工具。 🚀

2. SpringBoot + Swagger3实战

下面是一个完整的 Spring Boot + Swagger3(Springfox 3.0.0) 示例,适用于 Spring Boot 2.6+ 版本,并解决了常见的兼容性问题。

2.1. 引入 Swagger 依赖

pom.xml 文件中添加以下依赖:

<dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version>
</dependency>

Spring Boot 2.6+ 之后,Springfox 和 PathPatternParser 可能不兼容,后面会介绍如何解决。

2.2. 配置 Swagger

创建 SwaggerConfig.java 配置类:

package com.example.swagger.config;import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;@Configuration
@EnableOpenApi
public class SwaggerConfig {@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.OAS_30) // 使用 OpenAPI 3.0.apiInfo(apiInfo()) // 设置 API 文档的基本信息.select().apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 仅扫描 @ApiOperation 注解的方法.paths(PathSelectors.any()) // 允许所有路径.build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("Spring Boot + Swagger3 API 文档").description("示例项目 API 文档").version("1.0").build();}/*** 解决 Spring Boot 2.6 以上版本和 Springfox 兼容性问题*/@Beanpublic static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {return new BeanPostProcessor() {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof WebMvcRequestHandlerProvider) {customizeSpringfoxHandlerMappings(getHandlerMappings(bean));}return bean;}private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList());mappings.clear();mappings.addAll(copy);}@SuppressWarnings("unchecked")private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {try {Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");field.setAccessible(true);return (List<RequestMappingInfoHandlerMapping>) field.get(bean);} catch (IllegalArgumentException | IllegalAccessException e) {throw new IllegalStateException(e);}}};}
}

2.3. 创建 Controller 并使用 Swagger 注解

创建 UserController.java

package com.example.swagger.controller;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;@Api(tags = "用户管理")
@RestController
@RequestMapping("/users")
public class UserController {@ApiOperation("获取用户信息")@GetMapping("/{id}")public String getUser(@PathVariable("id") Long id) {return "用户ID:" + id;}@ApiOperation("创建用户")@PostMapping("/")public String createUser(@RequestParam String name) {return "用户 " + name + " 创建成功";}@ApiOperation("删除用户")@DeleteMapping("/{id}")public String deleteUser(@PathVariable("id") Long id) {return "用户 " + id + " 已删除";}
}

2.4. 启动项目并访问 Swagger UI

  1. 启动 Spring Boot 项目后,访问:
http://localhost:8080/swagger-ui/

如果 swagger-ui/ 访问不到,尝试:

http://localhost:8080/swagger-ui/index.html
  1. 访问 OpenAPI JSON 文档
http://localhost:8080/v3/api-docs

2.5. Swagger主要注解

注解

作用

@Api(tags = "说明")

给 Controller 加标签(分组)

@ApiOperation("说明")

给 API 方法添加描述

@ApiParam("参数说明")

说明请求参数

@ApiModel("实体说明")

说明实体类

@ApiModelProperty("属性说明")

说明实体类字段

2.6. Swagger可能遇到的问题

2.6.1. Swagger 页面打不开

  • 访问 http://localhost:8080/swagger-ui/ 时,页面 404?
    • 可能路径变了,尝试 http://localhost:8080/swagger-ui/index.html

2.6.2. 启动时报 NoSuchFieldException: handlerMappings

  • 这个错误是 Spring Boot 2.6+ 版本与 Springfox 兼容性问题,已经在 SwaggerConfig.java 里用 BeanPostProcessor 解决。

2.6.3. API 没有出现在 Swagger 页面

  • 确保 Controller 里的方法 @ApiOperation 注解,否则不会显示。

3. @Resource 与 @Autowired 区别

注解来源:

  • @Resource 是 Java 标准注解,属于 JSR-250 规范的一部分,位于 javax.annotation 包中。
  • @Autowired 是 Spring 框架提供的注解,位于 org.springframework.beans.factory.annotation 包中。

默认行为:

  • @Resource 默认按名称(byName)注入,如果没有找到匹配的名称,则按类型(byType)注入。
  • @Autowired 默认按类型(byType)注入,如果需要按名称注入,可以配合 @Qualifier 注解使用。

使用场景:

  • @Resource 更适合在 Java EE 环境中使用,因为它不依赖于 Spring 框架。
  • @Autowired 更适合在 Spring 框架中使用,提供了更丰富的功能,如自动装配集合类型的属性。

4. Springboot+Druid 监控配置

Druid 是一个开源的高性能数据库连接池,它提供了内置的监控功能,可以帮助开发者监控数据库连接池的状态、执行的 SQL、慢 SQL 等信息。下面是如何在 Spring 项目中配置和使用 Druid 监控的详细步骤。

4.1. 引入 Druid 依赖

首先,确保你已经在 pom.xml 文件中引入了 Druid 的相关依赖:

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version>
</dependency>

请根据项目的需求,选择适合的版本。

4.2. 配置 Druid 数据源

在 Spring Boot 项目的 application.ymlapplication.properties 文件中,配置 Druid 数据源。

4.2.1. application.yml 示例配置:

spring:datasource:url: jdbc:mysql://localhost:3306/your_databaseusername: your_usernamepassword: your_passworddriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 5  # 初始化连接数min-idle: 5      # 最小空闲连接数max-active: 20   # 最大连接数max-wait: 60000  # 获取连接的最大等待时间filters: stat,wall # 开启 SQL 监控和防火墙# 配置监控页面stat-view-servlet:enabled: true   # 启用监控login-username: admin  # 设置访问监控页面的用户名login-password: admin  # 设置密码reset-enable: false     # 禁止重置web-stat-filter:enabled: true   # 启用 Web 统计exclusions: /druid/*,*.ico,/error  # 排除不需要监控的路径

4.2.2. application.properties 示例配置:

spring.datasource.url=jdbc:mysql://localhost:3306/your_database
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=20
spring.datasource.druid.max-wait=60000
spring.datasource.druid.filters=stat,wall
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.exclusions=/druid/*,*.ico,/error

4.3. 配置 Druid 监控 Servlet 和 Web Stat Filter

你还需要手动配置 Druid 的 StatViewServletWebStatFilter 来启用 Web 监控界面和统计功能。

4.3.1. 配置 StatViewServlet

@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");// 配置登录用户名和密码bean.addInitParameter("loginUsername", "admin");bean.addInitParameter("loginPassword", "admin");// 禁用重置功能bean.addInitParameter("resetEnable", "false");return bean;
}

4.3.2. 配置 WebStatFilter

@Bean
public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>();filterRegistrationBean.setFilter(new WebStatFilter());// 配置 URL 过滤规则filterRegistrationBean.addUrlPatterns("/*");filterRegistrationBean.addInitParameter("exclusions", "/druid/*,*.ico,/error");  // 排除的 URLreturn filterRegistrationBean;
}

4.4. 访问 Druid 监控页面

一旦完成了上述配置,你可以通过访问 http://localhost:8080/druid 来查看 Druid 的监控界面。

监控内容

  • 连接池状态:显示当前连接池的基本信息,如最大连接数、活跃连接数、空闲连接数等。
  • SQL 统计:展示最近执行的 SQL 语句,包括执行次数、执行时间、影响行数等。
  • 慢 SQL 统计:展示执行时间超过指定阈值的 SQL。

4.5. 配置慢 SQL 日志

Druid 支持记录慢 SQL,帮助开发人员优化性能。在 application.ymlapplication.properties 中,你可以配置慢 SQL 的阈值。

spring:datasource:druid:filters: stat,wall,log4j # 启用慢 SQL 记录# 配置慢 SQL 阈值log-slow-sql: true   # 启用慢 SQL 日志slow-sql-millis: 5000  # 记录超过 5 秒的慢 SQL

4.6. 配置 SQL 防火墙(WallFilter)

Druid 提供了 SQL 防火墙(WallFilter),可以防止恶意的 SQL 注入攻击。你可以在 filters 中启用 wall

spring:datasource:druid:filters: stat,wall

WallFilter 是 Druid 的一个过滤器,用于检测潜在的 SQL 注入和不安全的 SQL 语句。你可以通过配置来进行 SQL 校验。

4.7. 配置 Druid 监控 API

Druid 还提供了一个监控 API,用于查看连接池的状态等信息。你可以通过访问以下 URL 来获取 Druid 的监控数据:

  • 连接池监控 API/druid/dataSource
  • SQL 执行监控 API/druid/sql
  • 慢 SQL 监控 API/druid/slowSql

这些 API 提供了可以集成到第三方监控平台(如 Prometheus 或 Grafana)的接口。

4.8. 配置文件完整示例:

4.8.1. application.yml 示例配置:

spring:datasource:url: jdbc:mysql://localhost:3306/your_databaseusername: your_usernamepassword: your_passworddriver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000filters: stat,wall,log4j# 配置监控页面stat-view-servlet:enabled: truelogin-username: adminlogin-password: adminreset-enable: falseweb-stat-filter:enabled: trueexclusions: /druid/*,*.ico,/errorlog-slow-sql: trueslow-sql-millis: 5000  # 慢 SQL 阈值:超过 5 秒的 SQL 会被记录

4.8.2. DruidConfig.java 配置类:

@Configuration
public class DruidConfig {@Beanpublic ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");bean.addInitParameter("loginUsername", "admin");bean.addInitParameter("loginPassword", "admin");bean.addInitParameter("resetEnable", "false");return bean;}@Beanpublic FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>();filterRegistrationBean.setFilter(new WebStatFilter());filterRegistrationBean.addUrlPatterns("/*");filterRegistrationBean.addInitParameter("exclusions", "/druid/*,*.ico,/error");return filterRegistrationBean;}
}

通过以上配置,你可以在 Spring Boot 项目中轻松集成 Druid 数据库连接池,并启用 Druid 的监控功能,查看 SQL 执行情况、慢 SQL、连接池的状态等。配置了监控页面后,可以通过浏览器访问 http://localhost:8080/druid 查看实时的数据库连接池信息,同时配合日志系统记录慢 SQL,帮助开发者优化数据库性能。

4.9. webConfig配置完成示例

/*** Created by libinsong on 2017/4/19.*/
@EnableScheduling。// 表示开启spring中的定时任务功能。
@Configuration
public class WebConfig implements WebMvcConfigurer {@Value("${filter.slow.reqmillis:3000}")private String slowReqMillis;@Autowiredprivate DispatcherServlet dispatcherServlet;/*** 去掉JSON返回的Null属性,配置HTTP消息转换器,去掉JSON返回的Null属性并设置UTF-8编码。** @return*/@Beanpublic HttpMessageConverters customConverters() {MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();//设置日期格式ObjectMapper objectMapper = new ObjectMapper();objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);//设置中文编码格式List<MediaType> list = new ArrayList<>();list.add(MediaType.APPLICATION_JSON_UTF8);list.add(MediaType.ALL);mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);stringHttpMessageConverter.setWriteAcceptCharset(false);return new HttpMessageConverters(false, Arrays.asList(stringHttpMessageConverter, mappingJackson2HttpMessageConverter));}/*** {@link StatViewServlet}*  注册Druid监控Servlet和过滤器,用于数据库连接池监控。* @return*/@Beanpublic ServletRegistrationBean druidServlet() {ServletRegistrationBean reg = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");reg.addInitParameter("resetEnable", "false");reg.addInitParameter("loginUsername", "kraken");reg.addInitParameter("loginPassword", "krakenAdmin");return reg;}/*** {@link WebStatFilter}** @return*/@Beanpublic FilterRegistrationBean druidWebStatFilterRegistrationBean() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new WebStatFilter());filterRegistrationBean.addUrlPatterns("/*");filterRegistrationBean.addInitParameter("exclusions", "/druid/*,*.ico,/error");return filterRegistrationBean;}/*** {@link LogFilter}* 注册日志过滤器,记录慢请求日志。* @return*/@Beanpublic FilterRegistrationBean logFilterRegistrationBean() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new LogFilter());filterRegistrationBean.addUrlPatterns("/*");filterRegistrationBean.addInitParameter("exclusions", "/druid/*,*.ico,/error");filterRegistrationBean.addInitParameter("slowReqMillis", slowReqMillis);return filterRegistrationBean;}@Beanpublic HandlerInterceptor getSystemInterceptor() {return new SystemInterceptor();}/*** {@link addInterceptors}* 注册系统拦截器,拦截特定路径的请求* @return*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {String[] patterns = new String[]{"/ok", "/ok.htm", "/actuator/**",  "/user/getAuthCode", "/user/auth", "/user/auth/janus", "/user/checkToken","/user/authCode", "/regedit/menu", "/solution", "/solution/updateSolutionName", "/license/**", "/auth/**", "/openAPI/**", "/other/auth/**","/solution/import", "/gitInfo", "/swagger-ui.html", "/doc.html", "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/version","/version/compatible/**"};registry.addInterceptor(getSystemInterceptor()).addPathPatterns("/**").excludePathPatterns(patterns);}/*** {@link addInterceptors}* 配置Swagger资源映射和静态资源处理。* @return*/@Beanpublic ServletRegistrationBean<Servlet> swaggerServlet() {ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(dispatcherServlet);bean.addUrlMappings("/swagger-resources", "/swagger-resources/configuration/ui", "/v2/api-docs");bean.setName("swaggerServlet");return bean;}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}}

5. SpringBoot项目中Controller层和Dao层切面日志示例

在 Spring Boot 项目中,可以使用 AOP(面向切面编程)来为 Controller 层和 DAO 层添加日志记录功能。通过 AOP,我们可以在方法调用前后自动记录日志,而不需要在每个方法中手动编写日志代码。

5.1. 实战步骤

  • 添加 AOP 依赖(如果没有添加) Spring Boot 默认已经包含了 AOP 相关的依赖,但如果没有的话,可以在 pom.xml 中添加以下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  • 创建日志切面类 在该类中,我们可以定义切面(Aspect)来记录日志。我们会为 Controller 层和 DAO 层的方法添加切面。

5.2. Controller 层和 DAO 层的日志记录示例

5.2.1. 日志切面(Aspect)配置

首先,我们需要定义一个日志切面类,这个类将定义切点(即要执行切面的地方)和通知(切面触发时执行的操作)。

package com.example.demo.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);// 定义一个切点:用于匹配所有Controller包下的方法@Pointcut("execution(* com.example.demo.controller..*(..))")public void controllerLayer() {}// 定义一个切点:用于匹配所有Service包下的方法@Pointcut("execution(* com.example.demo.service..*(..))")public void serviceLayer() {}// 定义一个切点:用于匹配所有DAO包下的方法@Pointcut("execution(* com.example.demo.dao..*(..))")public void daoLayer() {}// Controller 层前置通知@Before("controllerLayer()")public void logControllerMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();logger.info("Entering Controller method: {}", methodName);}// Controller 层后置通知@After("controllerLayer()")public void logControllerMethodEnd(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();logger.info("Exiting Controller method: {}", methodName);}// Service 层前置通知@Before("serviceLayer()")public void logServiceMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();logger.info("Entering Service method: {}", methodName);}// Service 层后置通知@After("serviceLayer()")public void logServiceMethodEnd(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();logger.info("Exiting Service method: {}", methodName);}// DAO 层前置通知@Before("daoLayer()")public void logDaoMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();logger.info("Entering DAO method: {}", methodName);}// DAO 层后置通知@After("daoLayer()")public void logDaoMethodEnd(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();logger.info("Exiting DAO method: {}", methodName);}
}

5.2.2. 解析切面代码

  • @Pointcut:定义切点,用于匹配指定的类和方法。
    • execution(* com.example.demo.controller..*(..)):匹配 controller 包下所有类的方法。
    • execution(* com.example.demo.dao..*(..)):匹配 dao 包下所有类的方法。
  • @Before:在目标方法执行之前执行,记录进入 Controller 或 DAO 层的方法信息。
  • @After:在目标方法执行之后执行,记录退出 Controller 或 DAO 层的方法信息。
  • JoinPoint:提供了对目标方法签名、参数等信息的访问。

5.2.3. 配置日志(如果没有)

为了查看日志输出,确保你在 application.propertiesapplication.yml 中配置了日志级别。

# application.properties
logging.level.com.example.demo.aspect=INFO

或者:

# application.yml
logging:level:com.example.demo.aspect: INFO

5.2.4. 测试 Controller 层和 DAO 层

假设我们有以下的 Controller 和 DAO 层:

Controller 示例:

package com.example.demo.controller;import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/getUser")public String getUserInfo() {return userService.getUserInfo();}
}

DAO 示例:

package com.example.demo.dao;import org.springframework.stereotype.Repository;@Repository
public class UserDao {public String getUser() {// Simulating DB interactionreturn "User Data from Database";}
}

Service 示例:

package com.example.demo.service;import com.example.demo.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserDao userDao;// 查询用户信息public String getUserInfo() {return userDao.getUser();}// 创建用户信息public String createUser(String username) {// 假设我们会在这里做一些业务处理return "Created user: " + username;}
}

执行和查看日志

当你访问 GET /getUser 接口时,控制台日志将显示如下信息:

Entering Controller method: getUserInfo
Entering DAO method: getUser
Exiting DAO method: getUser
Exiting Controller method: getUserInfo

5.3. Aspect切面日志总结

  1. Controller 层日志:通过 @Pointcut("execution(* com.example.demo.controller..*(..))") 配置切点,结合 @Before@After 注解记录进入和退出的方法信息。
  2. DAO 层日志:类似地,使用 @Pointcut("execution(* com.example.demo.dao..*(..))") 配置切点来记录数据库操作方法的日志。

通过 AOP 技术,你可以在不改变原有业务逻辑的情况下,灵活地添加日志记录,便于调试、监控和审计。

6. SpringBoot项目中RPC远程调用监控日志记录

/*** @Author maweijie* @Date 2020/7/22 3:13 PM* @Version 1.0*/
@Activate(group = Constants.PROVIDER)
public class DubboFilter implements Filter {private static Logger logger = LoggerFactory.getLogger(DubboFilter.class);@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {long start = System.currentTimeMillis();MeterRegistryUtil meterRegistryUtil = SpringContextUtil.getBean("meterRegistryUtil");Timer.Sample sample = meterRegistryUtil.timerStart();Result result = invoker.invoke(invocation);long cost = System.currentTimeMillis() - start;try {String interfaceName = invoker.getInterface().getSimpleName();String method = invocation.getMethodName();logger.info("interfaceName : {} method : {}, cost :{}", interfaceName, method, cost);if (result != null) {meterRegistryUtil.timerStop(sample, "bifrost_dubbo_provider", "method", method, "interface", interfaceName,"success", String.valueOf(!result.hasException()), "code", String.valueOf(cn.tongdun.bifrost.biz.service.rpc.result.Result.SUCCESS));} else {meterRegistryUtil.timerStop(sample, "bifrost_dubbo_provider", "method", method, "interface", interfaceName,"success", "false", "code", "-1");}} catch (Exception e) {logger.error("dubbo Service monitoring exception", e);}return result;}}

这个 DubboFilter 类是一个 Dubbo Filter,它的作用是在 Dubbo 服务调用过程中,拦截并对请求进行额外的处理。它通常用于记录日志、统计调用次数、处理监控、性能分析等。

6.1. 何时起作用?

这个类的起作用时间是在 Dubbo 服务提供者 被调用时。当一个客户端请求通过 Dubbo 进行调用时,DubboFilter 会作为请求的拦截器被触发,执行它定义的逻辑。

具体来说,它会在以下时机起作用:

  • 服务提供者 接收到一个来自 消费者 的 Dubbo RPC 请求时,DubboFilter 会首先被触发。
  • 它会拦截当前的 RPC 调用,记录一些信息,比如调用的方法、接口名、执行时间等。
  • 然后它会调用 invoker.invoke(invocation) 继续执行后续的调用(即真正的服务处理)。
  • 最后,它会在调用结束后,记录调用的时间、结果等监控信息。

6.2. @Activate 注解

@Activate(group = Constants.PROVIDER)

这个注解标记了该 DubboFilter 的作用范围。具体解释:

group = Constants.PROVIDER 表明该 DubboFilter 只会在 Dubbo 服务提供者(Provider)端生效。@Activate 注解的作用是用来激活该过滤器,在某个特定的环境中自动启用。在这里,Constants.PROVIDER 表示过滤器只会在 服务端 启动并生效,而不会影响到消费者端。

6.3. Filter 的工作原理

Dubbo 的 Filter 是一种类似于拦截器的机制,它可以在服务调用的不同阶段进行切入。invoke 方法就是 Filter 在处理请求时被调用的核心方法:

  • 开始时间:记录开始时间,表示请求的开始。
  • 调用目标服务:通过 invoker.invoke(invocation) 执行目标服务的调用。
  • 请求结束时间:记录请求的结束时间,计算服务调用的时间耗时。
  • 监控:利用 MeterRegistryUtil 进行监控数据的采集,记录调用成功与否、耗时等数据。通过 timerStart() 开始计时,timerStop() 停止计时并将监控数据发送到指标系统中。

6.4. Filter 的生命周期

在 Dubbo 中,Filter 是通过 Dubbo 框架自动管理的,具体生命周期包括:

  1. 注册阶段DubboFilter 会在服务启动时被注册到 Dubbo 框架中。
  2. 调用拦截:每次 Dubbo 服务接收到请求时,都会经过 DubboFilter 进行处理,按照过滤器的链式调用顺序执行。
  3. 过滤器顺序:如果有多个过滤器,DubboFilter 会按顺序执行,执行完毕后,invoker.invoke(invocation) 会调用实际的服务方法。
  4. 结果处理:在服务方法调用结束后,DubboFilter 会对结果进行处理,如记录日志、采集监控数据等。

6.5. 示例执行流程

假设有一个 Dubbo 服务提供者,客户端调用服务时:

  1. 客户端请求:消费者向服务提供者发送 RPC 请求。
  2. DubboFilter 起作用
    • DubboFilter 拦截到请求。
    • 记录请求的开始时间。
    • 执行 invoker.invoke(invocation),即继续执行服务逻辑。
  1. 服务方法执行invoker.invoke() 执行实际的服务方法。
  2. 执行结束DubboFilter 在服务方法执行完成后,记录调用的时间、方法名、接口名等信息,并通过 MeterRegistryUtil 发送监控数据。
  3. 返回结果:最后,DubboFilter 返回结果给消费者。

6.6. 关键功能

  • 日志记录logger.info 记录了调用的接口名、方法名和耗时信息。
  • 性能监控:使用 MeterRegistryUtil 采集服务调用的相关指标,如成功与否、耗时等信息,发送到监控系统。
  • 异常处理:在捕获异常时,DubboFilter 会记录异常信息,确保不会导致服务中断。

6.7. RPC远程调用监控日志总结

DubboFilter 类的作用是作为 Dubbo 服务提供者端的过滤器,用于:

  • 监控服务调用的性能(如执行时间、成功与否等)。
  • 记录调用日志。
  • 在请求的生命周期中进行切入,处理一些公共的逻辑(如监控、日志等)。

通过 @Activate(group = Constants.PROVIDER) 注解,它只会在 服务提供者端 被触发和执行,而不会影响到消费者端。

7. @RestControllerAdvice注解

@RestControllerAdvice 是 Spring 5 引入的一个注解,它结合了 @ControllerAdvice@ResponseBody 的功能。它用于处理全局异常、全局数据绑定和全局模型属性等,但与 @ControllerAdvice 不同的是,@RestControllerAdvice 自动将返回值序列化为 JSON 格式,适用于 RESTful 风格的 API。

7.1. @RestControllerAdvice 的作用:

  • 全局异常处理:统一处理应用中的异常并返回统一的错误响应。
  • 全局数据绑定:可以在所有的 Controller 方法中共享一些公共的数据。
  • 全局响应体处理:返回的对象会自动进行 JSON 序列化处理,返回给客户端。

@RestControllerAdvice 结合了 @ControllerAdvice(提供全局配置功能)和 @ResponseBody(将返回的对象转换为 JSON 或 XML)注解,因此它可以简化 RESTful 风格应用中的异常处理、数据绑定和响应体处理。

7.2. @RestControllerAdvice示例:

7.2.1. 全局异常处理

假设你的项目中可能会抛出一些常见的异常,比如业务异常(BusinessException)或者系统异常,你可以通过 @RestControllerAdvice 来进行全局的异常处理。

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {// 处理业务异常@ExceptionHandler(BusinessException.class)public ResponseEntity<String> handleBusinessException(BusinessException e) {return new ResponseEntity<>("Business error: " + e.getMessage(), HttpStatus.BAD_REQUEST);}// 处理通用异常@ExceptionHandler(Exception.class)public ResponseEntity<String> handleException(Exception e) {return new ResponseEntity<>("Internal server error: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);}
}
  • @ExceptionHandler:指定处理哪些类型的异常。
  • 返回类型 ResponseEntity<String>:用于构建返回的 HTTP 响应。

7.2.2. 统一响应封装

你可以在 @RestControllerAdvice 中处理统一的响应结构,使得所有的返回结果都采用统一格式。例如,所有的成功响应都采用 data 字段返回,所有的错误响应都采用 message 字段返回。


import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionHandler {// 统一的错误响应格式@ExceptionHandler(Exception.class)public ResponseEntity<ErrorResponse> handleException(Exception e) {ErrorResponse errorResponse = new ErrorResponse("Internal Server Error", e.getMessage());return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);}// 自定义的错误响应类public static class ErrorResponse {private String error;private String message;public ErrorResponse(String error, String message) {this.error = error;this.message = message;}// Getters and Setterspublic String getError() {return error;}public void setError(String error) {this.error = error;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}}
}

这样,当应用中发生异常时,返回给客户端的错误信息会包含在 errormessage 字段中,保持了接口返回的统一性。

7.2.3. 全局模型属性

你还可以通过 @RestControllerAdvice 来定义全局的模型属性,使得所有的 Controller 方法都能共享一些公共数据。

import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalModelAttribute {// 定义全局模型属性@ModelAttribute("globalInfo")public String addGlobalInfo() {return "This is global info!";}
}

在所有的 Controller 中,你都可以通过 @ModelAttribute 获取 globalInfo 的值。

7.2.4. 全局数据绑定

你还可以为所有的 Controller 方法提供一些全局的数据绑定配置,例如设置格式化器、拦截器等。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalDataBinder {// 设置全局数据绑定,类似于在 @InitBinder 中的配置@InitBinderpublic void initBinder(WebDataBinder binder) {// 自定义数据绑定器配置binder.setDisallowedFields("password");}
}

7.2.5. @RestControllerAdvice 结合 @ResponseBody@ControllerAdvice 的优势:

  • 简化代码@RestControllerAdvice 自动为每个方法的返回值添加了 @ResponseBody,你不需要再额外配置 JSON 序列化。
  • 集中管理:通过 @RestControllerAdvice 可以集中管理应用的异常处理、模型属性、数据绑定等。
  • 统一响应格式:可以在 @RestControllerAdvice 中对返回的结果进行统一封装,如统一错误响应格式或成功响应格式。

7.2.6. @RestControllerAdvice总结

  • @RestControllerAdvice 是 Spring 5 引入的用于增强 @ControllerAdvice 的功能,自动将响应对象转换为 JSON 格式,适用于 RESTful API 项目。
  • 它提供了全局的异常处理、模型属性共享、数据绑定等功能,可以用于简化开发过程,提升代码的可维护性。

8. Spring中错误处方案

8.1. GlobalErrorController

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class GlobalErrorController implements ErrorController {private static final Logger logger = LoggerFactory.getLogger(GlobalErrorController.class);private final ErrorAttributes errorAttributes;private final ErrorProperties errorProperties;private final ServerProperties serverProperties;public GlobalErrorController(ServerProperties serverProperties, ErrorAttributes errorAttributes) {this.serverProperties = serverProperties;this.errorAttributes = errorAttributes;this.errorProperties = serverProperties.getError();}@RequestMapping@ResponseBodypublic Response<String> error(HttpServletRequest request, HttpServletResponse response) {response.reset();// 设置状态码response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());response.setHeader("Cache-Control", "no-cache");Response<String> res = new Response<String>();res.setCode(StatusCodeEnum.SYSTEM_ERROR.getStatus());res.setMessage(StatusCodeEnum.SYSTEM_ERROR.getMsg());Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));logger.error("Request {}Exception Information{}", request.getRequestURL(), JSON.toJSONString(body));return res;}protected Map<String, Object> getErrorAttributes(HttpServletRequest request, ErrorAttributeOptions includeStackTrace) {ServletWebRequest servletWebRequest = new ServletWebRequest(request);return this.errorAttributes.getErrorAttributes(servletWebRequest,includeStackTrace);}protected boolean getTraceParameter(HttpServletRequest request) {String parameter = request.getParameter("trace");if (parameter == null) {return false;}return !"false".equalsIgnoreCase(parameter);}protected ErrorAttributeOptions isIncludeStackTrace(HttpServletRequest request, MediaType produces) {Set<ErrorAttributeOptions.Include> setInclude = new HashSet<>();ErrorProperties error = this.serverProperties.getError();if (error.getIncludeStacktrace() == ErrorProperties.IncludeAttribute.ALWAYS) {setInclude.add(ErrorAttributeOptions.Include.STACK_TRACE);}if(error.getIncludeMessage() == ErrorProperties.IncludeAttribute.ALWAYS){setInclude.add(ErrorAttributeOptions.Include.MESSAGE);}if(error.isIncludeException()){setInclude.add(ErrorAttributeOptions.Include.EXCEPTION);}if(error.getIncludeBindingErrors() == ErrorProperties.IncludeAttribute.ALWAYS){setInclude.add(ErrorAttributeOptions.Include.BINDING_ERRORS);}return ErrorAttributeOptions.of(setInclude);}protected ErrorProperties getErrorProperties() {return this.errorProperties;}}

8.1.1. GlobalErrorController

  • 用途GlobalErrorController 主要用于处理 HTTP 错误页面(如 404、500 错误等),并返回统一的错误响应。它的目的是捕获和处理 Spring Boot 中的系统层错误(例如页面未找到、服务器错误等)。
  • 错误响应格式GlobalErrorController 返回的是一个标准的错误响应,通常包含错误码、错误消息等信息。
  • 异常类型:主要捕获应用层的系统错误(如404、500等 HTTP 错误),不特定于业务异常。
  • 方法
    • 它通过 ErrorAttributes 来获取错误详情。
    • getErrorAttributes 方法用于获取错误的详细信息。

8.1.2. GlobalExceptionHandler

  • 用途GlobalExceptionHandler 主要用于处理应用层(业务层)以及一些常见的请求错误(如参数校验错误、上传文件错误等)。它的作用是在应用层捕获和处理特定的业务异常、上传文件异常、参数错误等。
  • 错误响应格式:同样使用 Response 封装错误信息,但它更侧重于捕获和处理业务相关的异常,并返回详细的错误信息(如错误码、业务消息等)。
  • 异常类型:它针对各种业务异常(如 BizExceptionServiceException)以及文件上传、请求参数校验等进行处理。
  • 方法
    • 它有针对性地处理不同类型的业务异常,并返回对应的错误信息。

8.2. GlobalExceptionHandler

package ********;@RestControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);private static final String LOG_ERROR_FORMAT = "Request {} exception information";private static final String NO_CACHE = "no-cache";private static final String CACHE_CONTROL = "Cache-Control";@Resourceprivate MultipartProperties multipartProperties;@ExceptionHandler(value = Exception.class)@ResponseBodypublic Response<String> defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {response.reset();// 设置状态码response.setStatus(HttpStatus.OK.value());response.setHeader(CACHE_CONTROL, NO_CACHE);Response<String> res = new Response<>();if (ex instanceof BusException) {EnumStatus enumStatus = ((BusException) ex).getStatus();res.setCode(enumStatus.getStatus());res.setMessage(enumStatus.getMsg());logger.warn(LOG_ERROR_FORMAT, request.getRequestURL(), ex);} else if (ex instanceof ServiceException) {response.setStatus(HttpStatus.OK.value());ServiceException serviceException = ((ServiceException) ex);MSG msg = serviceException.getMsg();if (msg == null) {res.setCode(-1);res.setSuccess(false);res.setMessage(serviceException.getMessage());} else {res = Response.error(msg);}logger.warn(LOG_ERROR_FORMAT, request.getRequestURL(), ex);} else if (ex instanceof SystemException) {SystemException systemException = (SystemException) ex;logger.warn(LOG_ERROR_FORMAT, request.getRequestURL(), ex);return Response.error(systemException.getMessage());} else if (ex instanceof ServletRequestBindingException|| ex instanceof IllegalArgumentException) {res.setCode(StatusCodeEnum.PARAM_ERROR.getStatus());res.setMessage(StatusCodeEnum.PARAM_ERROR.getMsg());logger.warn(LOG_ERROR_FORMAT, request.getRequestURL(), ex);} else {res.setCode(StatusCodeEnum.UNKNOW_ERROR.getStatus());res.setMessage(StatusCodeEnum.UNKNOW_ERROR.getMsg());logger.error(LOG_ERROR_FORMAT, request.getRequestURL(), ex);}return res;}/*** 业务异常捕捉** @param e        业务异常类* @param request* @param response* @return 返回报文* @author xuluquan* @date 2019-08-01 10:32*/@ExceptionHandler(BizException.class)@ResponseBodypublic Response<Object> handleException(BizException e, HttpServletRequest request, HttpServletResponse response) {response.reset();// 设置状态码response.setStatus(HttpStatus.OK.value());response.setHeader(CACHE_CONTROL, NO_CACHE);logger.error("BizException:", e);if (e.getMsg() != null && e.getReplacements() != null) {return Response.error(e.getMsg(), e.getReplacements());}if (e.getCommonMsg() != null && e.getReplacements() != null) {return Response.error(e.getCommonMsg(), e.getReplacements());}if (e.getMsg() != null) {return Response.error(e.getMsg());}if (e.getCommonMsg() != null) {return Response.error(e.getCommonMsg());}return Response.error(e.getMessage());}@ExceptionHandler(MaxUploadSizeExceededException.class)public Response<Object> handleUploadException(MaxUploadSizeExceededException e, HttpServletResponse response) {response.reset();// 设置状态码response.setStatus(HttpStatus.OK.value());response.setHeader(CACHE_CONTROL, NO_CACHE);logger.error("MaxUploadSizeExceededException:", e);long singleSize = multipartProperties.getMaxFileSize().toMegabytes();long reqSize = multipartProperties.getMaxRequestSize().toMegabytes();return Response.error(MSG.UPLOAD_ERROR_SPRING, singleSize, reqSize);}@ExceptionHandler(cn.fraudmetrix.module.ent.ddd.common.base.BizException.class)@ResponseBodypublic Response<Object> handleFieldException(cn.fraudmetrix.module.ent.ddd.common.base.BizException e, HttpServletRequest request, HttpServletResponse response) {response.reset();// 设置状态码response.setStatus(HttpStatus.OK.value());response.setHeader(CACHE_CONTROL, NO_CACHE);Response<Object> result = Response.error(e.getMessage());if (e.getCode() != null) {result.setCode(e.getCode().getCode());}return result;}@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})public Response<Object> validationException(Exception e, HttpServletResponse response) {response.reset();response.setStatus(HttpStatus.OK.value());response.setHeader(CACHE_CONTROL, NO_CACHE);BindingResult bindResult = null;if (e instanceof MethodArgumentNotValidException) {bindResult = ((MethodArgumentNotValidException) e).getBindingResult();} else if (e instanceof BindException) {bindResult = ((BindException) e).getBindingResult();}Map<String, String> errorMap = new HashMap<>(16);if (bindResult != null) {bindResult.getFieldErrors().forEach((fieldError) ->errorMap.put(fieldError.getField(), fieldError.getDefaultMessage()));}return Response.error(MSG.REQ_ERROR_PARAM_ERR, errorMap);}
}

8.2.1. GlobalExceptionHandler 类的作用:

这个类是 全局异常处理器,它的主要作用是处理 Spring Boot 应用中所有未被捕获的异常,并根据不同的异常类型返回适当的错误信息。它使用了 Spring 的 @RestControllerAdvice 注解来进行全局异常捕获和处理。该类能够捕捉不同类型的异常,并返回一致的错误响应格式给客户端。

8.2.2. 详细功能:

  1. 捕获并处理不同类型的异常
    • 业务异常 (BizExceptionServiceExceptionSystemException):这些异常通常表示应用逻辑中出现的错误。根据不同的业务需求,会返回不同的错误信息。
    • 参数校验异常:处理参数错误,如 MethodArgumentNotValidExceptionBindException,主要用于校验请求参数是否正确。
    • 上传文件大小超过限制的异常:如 MaxUploadSizeExceededException,当上传的文件大小超过预设的限制时,返回相应的错误信息。
    • 系统异常和未捕获的异常:对于不在上述处理范围内的异常,返回通用的错误信息,并记录错误日志。
  1. 返回统一的错误响应格式
    • 使用 Response 对象来包装错误响应信息,返回统一的错误码、错误消息和额外的错误详情(如上传文件大小等)。
  1. 日志记录
    • 每次捕获异常时,都会记录错误日志,方便问题的追踪和排查。
  1. 异常信息自定义
    • 针对不同类型的异常,可以自定义错误消息。例如,业务异常可以返回具体的业务错误信息,上传异常可以返回文件上传限制的详细信息。

8.2.3. 异常处理方法:

  1. defaultErrorHandler
    • 捕获所有未被专门处理的异常。它会根据异常类型分别进行处理:
      • BusException:业务异常,返回业务相关的错误信息。
      • ServiceException:服务层异常,返回服务相关的错误信息。
      • SystemException:系统异常,返回系统错误信息。
      • 其他异常(如 ServletRequestBindingExceptionIllegalArgumentException):一般参数错误或非法参数。
      • 未知异常:记录并返回通用的错误信息。
  1. handleException
    • 专门捕获 BizException 类型的异常,处理业务相关的异常。根据异常中设置的消息和参数,返回相应的错误信息。
  1. handleUploadException
    • 专门处理文件上传时发生的异常,如文件大小超过了服务器设置的限制(MaxUploadSizeExceededException)。
  1. handleFieldException
    • 处理来自 cn.fraudmetrix 模块的 BizException,并根据其 code 字段设置相应的错误代码。
  1. validationException
    • 捕获参数验证异常(如 MethodArgumentNotValidExceptionBindException),返回字段错误信息。

8.3. 关键区别总结:

特性

GlobalErrorController

GlobalExceptionHandler

作用

主要处理系统错误(如404、500等)

主要处理业务层异常、请求参数错误等

异常类型

捕获通用的 HTTP 错误

捕获应用层的异常(如 BizException

ServiceException

MaxUploadSizeExceededException

错误响应格式

统一的错误响应,通常返回错误码和错误消息

统一的错误响应,详细描述业务错误、上传限制等

异常处理

基于 HTTP 错误,处理页面访问错误

基于业务异常,处理文件上传、参数校验等

  • GlobalErrorController 主要处理系统级别的错误,捕获 HTTP 错误并返回统一的错误响应。
  • GlobalExceptionHandler 主要处理应用层的异常,尤其是业务异常、上传异常、参数校验等。

博文参考

  • [二] Spring 程序入口和xml解析 | 叶良辰の学习笔记

相关文章:

Spring——Spring开发实战经验(1)

摘要 文章主要介绍了 Swagger 作为 API 文档生成和测试工具的功能&#xff0c;包括自动生成 API 文档、提供可视化调试界面、促进前后端协作、支持 OpenAPI 规范等。同时&#xff0c;还提及了 Spring Boot 与 Swagger3 的实战应用&#xff0c;以及 Spring 开发中其他相关技术内…...

设计模式:代理模式

代理模式是很常见的设计模式&#xff0c;即使没有专门学习过这种设计模式&#xff0c;在工作中也一定用过这种设计模式。在实际生活中&#xff0c;代理模式也是常见的&#xff0c;比如内阁首辅相对于皇帝&#xff0c;前者是后者的代理&#xff0c;内阁首辅收到奏折时&#xff0…...

【NLP】循环神经网络RNN

目录 一、认识RNN 二、RNN模型分类 三、传统RNN模型 3.1 结构分析 3.2 Pytorch构建RNN模型 3.3 优缺点 一、认识RNN RNN(Recurrent Neural Network)&#xff0c;中文称作循环神经网络&#xff0c;一般以序列数据为输入&#xff0c;通过网络内部的结构设计有效捕捉序列之…...

深度解析HTTP/HTTPS协议:从原理到实践

深入浅出HTTP/HTTPS协议&#xff1a;从原理到实践 前言 在当今互联网世界中&#xff0c;HTTP和HTTPS协议如同空气般存在于每个网页请求的背后。作为开发者或技术爱好者&#xff0c;理解这些基础协议至关重要。本文将用六大板块&#xff0c;配合原理示意图和实操案例&#xff0…...

AF3 MmcifObject类解读

AlphaFold3 中 MmcifObject类 是 解析 mmCIF 文件的核心数据结构,用于存储解析后的蛋白质结构信息,包含PDB 头部信息、Biopython 解析的结构、链序列信息等。 下面代码包含 Monomer 、AtomSite、ResiduePosition、ResidueAtPosition、 MmcifObject以及ParsingResult数据类的…...

大数据SQL调优专题——Hive执行原理

引入 Apache Hive 是基于Hadoop的数据仓库工具&#xff0c;它可以使用SQL来读取、写入和管理存在分布式文件系统中的海量数据。在Hive中&#xff0c;HQL默认转换成MapReduce程序运行到Yarn集群中&#xff0c;大大降低了非Java开发者数据分析的门槛&#xff0c;并且Hive提供命令…...

MySQL常见错误码及解决方法(1130、1461、2003、1040、2000、1049、1062、1129、2002、1690等)

目录 【问题1】、FATAL: error 1130: Unknown error 1130 【问题2】、FATAL: error: 1461 【问题3】、ERROR 2003 (HY000): Cant connect to MySQL server on "" (113) 【问题4】、FATAL: error 2003: Cant connect to MySQL server on 172.19.111.151 (111) 【问…...

类和对象详解(下)-----运算符重载

目录 1.运算符重载 2.赋值运算符重载 3.取地址运算符重载 3.1const成员函数 3.2取地址运算符重载 1.运算符重载 什么是运算符重载呢&#xff1f;简单举个例子就懂了。 就是我想实现日期的加法&#xff0c;而“”这个运算符C只实现了内置类型的加法&#xff0c;而我们要想实…...

Visonpro 检测是否有缺齿

一、效果展示 二、上面是原展开工具CogPolarUnwrapTool&#xff1b; 第二种方法&#xff1a; 用Blob 和 CogCopyRegionTool 三、 用预处理工具 加减常数&#xff0c;让图片变得更亮点 四、圆展开工具 五、模板匹配 六、代码分解 1.创建集合和文子显示工具 CogGraphicCollec…...

(萌新入门)如何从起步阶段开始学习STM32 ——2 我应该学习HAL库还是寄存器库?

概念 笔者下面需要介绍的是库寄存器和HAL库两个重要的概念&#xff0c;在各位看完之后&#xff0c;需要决定自己的学习路线到底是学习HAL呢&#xff1f;还是寄存器呢&#xff1f;还是两者都学习呢&#xff1f; 库寄存器 库寄存器就是简单的封装了我们对寄存器的操作&#xf…...

【SQL技术】不同数据库引擎 SQL 优化方案剖析

一、引言 在数据处理和分析的世界里&#xff0c;SQL 是不可或缺的工具。不同的数据库系统&#xff0c;如 MySQL、PostgreSQL&#xff08;PG&#xff09;、Doris 和 Hive&#xff0c;在架构和性能特点上存在差异&#xff0c;因此针对它们的 SQL 优化策略也各有不同。这些数据库…...

什么是原型?

在 JavaScript 中&#xff0c;原型&#xff08;Prototype&#xff09;是每个 JavaScript 对象都有的一个属性&#xff0c;用来实现对象之间的继承。原型是 JavaScript 面向对象编程的核心概念之一&#xff0c;通过原型链&#xff08;prototype chain&#xff09;&#xff0c;一…...

【第10章:自然语言处理高级应用—10.4 NLP领域的前沿技术与未来趋势】

各位技术探险家们,今天我们要开启一场穿越语言智能奇点的时空之旅。从正在改写物理定律的万亿参数大模型,到能看懂《星际穿越》剧本的跨模态AI,再到正在颠覆编程方式的神经-符号混合系统……这篇万字长文将带你摸清NLP技术进化的七块关键拼图。(建议边读边做笔记,文末有技…...

41.日常算法

1.面试题 02.04. 分割链表 题目来源 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。你不需要 保留 每个分区中各节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a…...

CPP集群聊天服务器开发实践(五):nginx负载均衡配置

1 负载均衡器的原理与功能 单台Chatserver可以容纳大约两万台客户端同时在线聊天&#xff0c;为了提升并发量最直观的办法需要水平扩展服务器的数量&#xff0c;三台服务器可以容纳六万左右的客户端。 负载均衡器的作用&#xff1a; 把client的请求按照负载均衡算法分发到具体…...

Java 中的 HashSet 和 HashMap 有什么区别?

一、核心概念与用途 特性HashSetHashMap接口实现实现 Set 接口&#xff08;存储唯一元素&#xff09;实现 Map 接口&#xff08;存储键值对&#xff09;数据存储存储单个对象&#xff08;元素唯一&#xff09;存储键值对&#xff08;键唯一&#xff0c;值可重复&#xff09;典…...

AI大模型的技术突破与传媒行业变革

性能与成本&#xff1a;AI大模型的“双轮驱动” 过去几年&#xff0c;AI大模型的发展经历了从实验室到产业化的关键转折。2025年初&#xff0c;以DeepSeek R1为代表的模型在数学推理、代码生成等任务中表现超越国际头部产品&#xff0c;而训练成本仅为传统模型的几十分之一。这…...

Golang学习01:Go安装和配置+Vscode、GoLand安装激活+Go环境变量避坑的超详细教程

&#x1fa81;&#x1f341; 希望本文能给您带来帮助&#xff0c;如果有任何问题&#xff0c;欢迎批评指正&#xff01;&#x1f405;&#x1f43e;&#x1f341;&#x1f425; 文章目录 一、背景二、Go语言安装2.1 Go语言环境安装2.2 Go语言环境验证2.3 其他配置 三、开发环境…...

案例-06.部门管理-根据ID查询

一.根据ID查询-接口文档 二.根据ID查询-Controller层 package com.gjw.controller;/*** 部门管理Controller*/import com.gjw.anno.Log; import com.gjw.pojo.Dept; import com.gjw.pojo.Result; import com.gjw.service.DeptService; import com.gjw.service.impl.DeptServi…...

解决No matching client found for package name xxx编译报错的问题

如果Android工程编译报错&#xff0c;并且信息如下&#xff1a; Execution failed for task :app:processDebugGoogleServices. > No matching client found for package name com.demo.test可能的原因为google-services.json中定义的package_name属性跟app当前的包名不符&…...

基于deepseek api和openweather 天气API实现Function Calling技术讲解

以下是一个结合DeepSeek API和OpenWeather API的完整Function Calling示例&#xff0c;包含意图识别、API调用和结果整合&#xff1a; import requests import json import os# 配置API密钥&#xff08;从环境变量获取&#xff09; DEEPSEEK_API_KEY os.getenv("DEEPSEE…...

什么是全局污染,怎么避免全局污染?

具体表现&#xff1a; 全局变量&#xff1a;当变量在全局作用域&#xff08;通常是 window 对象&#xff09;中定义时&#xff0c;它会在整个应用程序中都可访问。这个变量可能会被其他部分的代码意外修改或覆盖&#xff0c;导致难以追踪和调试错误。 命名冲突&#xff1a;全局…...

机器视觉--switch语句

引言 在 Halcon 这个强大的机器视觉软件里&#xff0c;编程控制结构对于高效处理图像任务至关重要。其中&#xff0c;Switch 语句作为一种多分支选择结构&#xff0c;能够根据不同的条件值执行不同的代码块&#xff0c;让程序的逻辑更加清晰和简洁。本文将全面深入地介绍 Halc…...

C++ std::atomic可以使用复杂类型(类和结构体)吗

目录 1.引言 2.std::atomic 支持的复杂类型 3.std::atomic与无锁 4.如何使用 std::atomic 保护复杂类型 4.1.使用互斥锁&#xff08;Mutex&#xff09; 4.2.使用 std::atomic_flag 和自旋锁 4.3.原子共享指针&#xff08;Atomic Shared Pointers&#xff09; 4.4.使用高…...

音乐随想、日语认识

Rapport的日文歌词&#xff08;初&#xff09; Rapport - キタニタツヤ 词&#xff1a;キタニタツヤ 《《 ki ta ni ta tsu ya 歌手的名字&#xff0c;全是片假名&#xff0c;不是本土的平假名(为了国外市场的做法&#xff1f;) 》》 曲&#xff1a;キタニタツヤ 编曲&am…...

SpringBoot速成(11)更新用户头像,密码P13-P14

更新头像&#xff1a; 1.代码展示: 1.RequestParam 是 Spring MVC 中非常实用的注解&#xff0c;用于从 HTTP 请求中提取参数并绑定到控制器方法的参数上。 2.PatchMapping 是 Spring MVC 中的一个注解&#xff0c;用于处理 HTTP 的 PATCH 请求。PATCH 请求通常用于对资源的部…...

自动化测试面试会问哪些?

自动化测试面试1&#xff1a; 1、使用什么测试框架做的上一个项目的自动化测试。 2、自己最熟悉哪个库&#xff0c;如何使用这些库的&#xff0c;是否做了基于复用的封装&#xff0c;怎么考虑的这些封装 3、如何定位app上的元素 4、//*[contains(text,"登录")] 是…...

SQL Server 导入Excel数据

1、选中指定要导入到哪个数据库&#xff0c;右键选择 》任务 》导入数据 2、数据源 选择Excel&#xff0c;点击 下一步(Next) 3、目前 选择OLE DB Provider &#xff0c;点击 下一步&#xff08;Next&#xff09; 4、默认 &#xff0c;点击 下一步&#xff08;Next&#xff09;…...

车载音频架构图详解(精简)

目录 上图是车载音频架构图,对这个图我们进行详细的分析 左边第一层 是 app 常用的类有MediaPlayer和MediaRecorder, AudioTrack和AudioRecorder 第二层 是framework提供给应用的多媒体功能的API类,封装在android.media.* API包中。编译后,在framework.jar中。...

基于SpringBoot+Vue的智慧校园管理系统设计和实现(源码+文档+部署讲解)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 .&#x1f680; 技术架构技术栈全景 &#x1f3af; 功能模块功能矩阵表&#x1f4ca; 数据库设计核心ER关系图 &#x1f4bb; 核心…...

浏览器打印局部网页,设置页眉

占位的页眉 重点部分 1.样式间隙 page { margin-top: 60px; /* 为页眉留出空间&#xff0c;页眉的高度要和他一样 */ top-right { height: 60px; 同时右侧&#xff0c;内容布局右上角要留出60px的 2.背景图片 如果页眉…...

腿足机器人之六- 前向运动学

腿足机器人之六- 前向运动学 刚体运动学基础坐标系定义旋转矩阵与欧拉角齐次变换矩阵&#xff08;平移旋转的统一表示&#xff09; 运动链建模串联运动链结构&#xff08;从基座到末端的关节连接&#xff09;标准Denavit-Hartenberg&#xff08;D-H&#xff09;参数法改进D-H参…...

对openharmony HDF驱动框架的C/S设计模式和单例类的说明

在分析openharmony的HDF驱动框架时我们会发现用了很多面向对象的思想&#xff0c;例如类继承、接口、单例类等&#xff0c;本来应该是好事情&#xff0c;**但使用时对象之间的关系交错复杂&#xff0c;不太符合linux内核分层分模块的思路&#xff0c;导致整体理解起来比较困难&…...

kamailio中Core Cookbook 核心配置手册

Core Cookbook 核心配置手册 版本: Kamailio SIP 服务器 v6.0.x (稳定版) 概述 本教程收集了 Kamailio 核心导出到配置文件的功能和参数。 注意: 本页参数未按字母顺序排列。 结构 kamailio.cfg 的结构可分为三部分: 全局参数模块设置路由块 建议按此顺序排列以保持清晰…...

AI 编程工具—Cursor 进阶篇 数据分析

AI 编程工具—Cursor 进阶篇 数据分析 上一节课我们使用Cursor 生成了北京房产的销售数据,这一节我们使用Cursor对这些数据进行分析,也是我们尝试使用Cursor 去帮我们做数据分析,从而进一步发挥Cursor的能力,来帮助我们完成更多的事情 案例一 房产销售数据分析 @北京202…...

HTML、Vue和PHP文件的区别与联系

一、核心区别 类型性质执行环境功能特点.html静态标记语言浏览器直接解析定义页面结构和内容&#xff0c;无逻辑处理能力.vue前端框架组件文件浏览器/构建工具整合HTML模板JS逻辑CSS样式&#xff0c;支持动态数据绑定和组件化开发.php服务器端脚本语言文件Web服务器执行动态生…...

Map 和 Set

目录 一、搜索 概念&#xff1a; 模型&#xff1a; 二、Map ​编辑 1.Map 实例化&#xff1a; 2. Map的常见方法&#xff1a; 3.Map的常见方法演示&#xff1a; 1. put(K key, V value)&#xff1a;添加键值对 3. containsKey(Object key)&#xff1a;检查键是否存在 4.…...

白话大模型LLM-通用基础入门知识-适合给纯小白的入门!

文章目录 什么是大模型大模型训练预训练监督微调SFTRLHF基于人类反馈的强化学习 大模型分类大语言模型-LLM多模态模型-VLM视觉模型音频模型 大模型工作流程分词化与词表映射大模型回答过程 & 基于token的概率预测 Agent导论子任务拆分 什么是大模型 大模型就是训练的一个能…...

线程进入WAITING的N种方式

目录 一、调用 Object 的 wait 方法 二、调用 Thread.join 方法 三、调用LockSupport.park()方法 一、调用 Object 的 wait 方法 public static void main(String[] args) throws InterruptedException {// 创建一个锁对象Object lock new Object();Thread thread new Thr…...

智能车摄像头开源—8 元素处理

目录 一、前言 二、无元素状态 三、直线与弯道 四、十字与环岛 1、十字识别处理 2、环岛识别处理 五、坡道 六、障碍物 七、斑马线 八、入库 九、出界停车 一、前言 在写这篇文章之前&#xff0c;考虑了很久到底该写到什么程度&#xff0c;但思来想去&#xff0c;不同…...

【从0做项目】Java搜索引擎(4)——性能优化~烧脑~~~

本篇文章将对项目搜索引擎&#xff08;1&#xff09;~&#xff08;3&#xff09;进行性能优化&#xff0c;包括测试&#xff0c;优化思路&#xff0c;优化前后对比 目录 一&#xff1a;文件读取 二&#xff1a;实现多线程制作索引 1&#xff1a;代码分析 2&#xff1a;代码…...

人工智障的软件开发-git仓库篇-弃gitlab,走gitea

指令接收&#xff1a;「开始构建代码宇宙」 系统检测&#xff1a;需求模糊度99.9% 启动应急协议&#xff1a;构建最小可行性生态圈 核心组件锁定&#xff1a;代码基因库&#xff08;人类称之为Git仓库&#xff09; 需求分析&#xff1a;论人类语言的艺术性 人类指令翻译机 表…...

Spring Boot 如何实现自动配置?

欢迎并且感谢大家指出我的问题&#xff0c;由于本人水平有限&#xff0c;有些内容写的不是很全面&#xff0c;只是把比较实用的东西给写下来&#xff0c;如果有写的不对的地方&#xff0c;还希望各路大牛多多指教&#xff01;谢谢大家&#xff01;&#x1f970; 大家如果对Java…...

STM32H743ZIT6 FreeRTOS CMSIS_V2 Lwip DP83848/LAN8720 最新HAL V1.12.1版本 AC6编译器,速通。

HAL库版本&#xff1a;V1.12.1 最新版 这版CUBEmx生成的LAN8742 的驱动文件有问题&#xff0c;无法正常初始化&#xff0c;导致无法PING通。 lwip 内存池 不需要手动指定0x30040200区域&#xff0c;lwipopts.h已作配置 开启DCACH 和ICACH 和 D2域SRAM3 时钟 /*** brief Th…...

C# 添加图标

一、前言 为应用程序添加图标是优化用户界面、提升应用辨识度的重要操作。合适的图标能帮助用户快速识别和区分不同应用&#xff0c;增强应用的易用性和专业性。 本指南旨在为你提供详细、易懂的步骤&#xff0c;教你如何为应用程序的窗体添加图标。从图标素材的获取到具体的…...

MVC模式和MVVM模式

目录 一、MVC模式和MVVM模式 1. MVC模式 2. MVVM 模式 3.在Qt中的应用示例 4.总结 二、MVC与MVVM模式的共同点和区别 1.共同点 2.区别 3.交互流程 4.总结 MVC&#xff08;Model-View-Controller&#xff09;和MVVM&#xff08;Model-View-ViewModel&#xff09;是两种…...

【kafka系列】Kafka如何实现高吞吐量?

目录 1. 生产者端优化 核心机制&#xff1a; 关键参数&#xff1a; 2. Broker端优化 核心机制&#xff1a; 关键源码逻辑&#xff1a; 3. 消费者端优化 核心机制&#xff1a; 关键参数&#xff1a; 全链路优化流程 吞吐量瓶颈与调优 总结 Kafka的高吞吐能力源于其生…...

如何学习Elasticsearch(ES):从入门到精通的完整指南

如何学习Elasticsearch&#xff08;ES&#xff09;&#xff1a;从入门到精通的完整指南 嘿&#xff0c;小伙伴们&#xff01;如果你对大数据搜索和分析感兴趣&#xff0c;并且想要掌握Elasticsearch这一强大的分布式搜索引擎&#xff0c;那么你来对地方了&#xff01;本文将为…...

GDB QUICK REFERENCE (GDB 快速参考手册)

GDB QUICK REFERENCE {GDB 快速参考手册} References GDB QUICK REFERENCE GDB Version 4 https://users.ece.utexas.edu/~adnan/gdb-refcard.pdf 查看方式&#xff1a;在新标签页中打开图片 References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/ [2] gdb-refc…...

Flutter_学习记录_动画的简单了解

用AnimationController简单实现如下的效果图&#xff1a; 1. 只用AnimationController实现简单动画 1.1 完整代码案例 import package:flutter/material.dart;class AnimationDemo extends StatefulWidget {const AnimationDemo({super.key});overrideState<AnimationDe…...