文章主要介绍了 Swagger 作为 API 文档生成和测试工具的功能,包括自动生成 API 文档、提供可视化调试界面、促进前后端协作、支持 OpenAPI 规范等。同时,还提及了 Spring Boot 与 Swagger3 的实战应用,以及 Spring 开发中其他相关技术内容,如 @Resource 与 @Autowired 的区别、Druid 监控配置、切面日志示例等。
Swagger 是一种 API 文档生成和测试工具,用于描述、生成、测试和管理 RESTful API。它的主要作用包括以下几个方面:
Swagger 可以自动扫描代码中的 Controller 和 API 方法,并生成一份可视化的 API 文档,无需手动维护。
示例: 在 Spring Boot 项目中,添加 @ApiOperation
注解:
@ApiOperation("获取用户信息")
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {
return "用户 ID:" + id;
}
Swagger 自带交互式 UI,允许开发者直接在浏览器中进行 API 测试,而不需要使用 Postman、cURL 等工具。
访问 Swagger UI:
http://localhost:8080/swagger-ui/
你可以:
Swagger 基于 OpenAPI 规范(OAS,OpenAPI Specification),可以生成标准的 API 描述文件(JSON 或 YAML 格式)。这些文件可以用于:
示例: 访问 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 客户端代码。
Swagger 支持 API 版本控制,可以在 API 文档中同时管理多个版本,例如:
@Api(tags = "用户管理 API V1")
@RequestMapping("/api/v1")
public class UserControllerV1 { }
@Api(tags = "用户管理 API V2")
@RequestMapping("/api/v2")
public class UserControllerV2 { }
这样,前端可以根据不同版本调用相应的 API。
Swagger 不仅支持 Java,还支持多种编程语言,包括:
这使得 Swagger 成为跨语言的 API 文档标准。
作用 |
详细描述 |
自动生成 API 文档 |
通过注解解析 API,并生成详细的文档 |
提供 UI 界面 |
让开发者可以直接在网页上查看和测试 API |
提高前后端协作效率 |
无需手写 API 文档,前端可直接查看接口说明 |
支持 OpenAPI 规范 |
生成标准的 JSON/YAML API 文档,可用于 SDK 生成 |
支持 API 版本管理 |
可以管理多个 API 版本 |
兼容多种编程语言 |
适用于 Java、Python、Node.js、Go、C# 等 |
Swagger 让 API 开发更直观、高效、易维护,是现代微服务开发的重要工具。
下面是一个完整的 Spring Boot + Swagger3(Springfox 3.0.0) 示例,适用于 Spring Boot 2.6+ 版本,并解决了常见的兼容性问题。
在 pom.xml
文件中添加以下依赖:
io.springfox
springfox-boot-starter
3.0.0
Spring Boot 2.6+ 之后,Springfox 和 PathPatternParser
可能不兼容,后面会介绍如何解决。
创建 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 {
@Bean
public 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 兼容性问题
*/
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private void customizeSpringfoxHandlerMappings(
List mappings) {
List copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
}
创建 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 + " 已删除";
}
}
http://localhost:8080/swagger-ui/
如果 swagger-ui/
访问不到,尝试:
http://localhost:8080/swagger-ui/index.html
http://localhost:8080/v3/api-docs
注解 |
作用 |
|
给 Controller 加标签(分组) |
|
给 API 方法添加描述 |
|
说明请求参数 |
|
说明实体类 |
|
说明实体类字段 |
http://localhost:8080/swagger-ui/
时,页面 404?http://localhost:8080/swagger-ui/index.html
NoSuchFieldException: handlerMappings
SwaggerConfig.java
里用 BeanPostProcessor
解决。@ApiOperation
注解,否则不会显示。注解来源:
默认行为:
使用场景:
Druid 是一个开源的高性能数据库连接池,它提供了内置的监控功能,可以帮助开发者监控数据库连接池的状态、执行的 SQL、慢 SQL 等信息。下面是如何在 Spring 项目中配置和使用 Druid 监控的详细步骤。
首先,确保你已经在 pom.xml
文件中引入了 Druid 的相关依赖:
com.alibaba
druid-spring-boot-starter
1.2.8
请根据项目的需求,选择适合的版本。
在 Spring Boot 项目的 application.yml
或 application.properties
文件中,配置 Druid 数据源。
application.yml
示例配置:spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
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 # 排除不需要监控的路径
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
你还需要手动配置 Druid 的 StatViewServlet 和 WebStatFilter 来启用 Web 监控界面和统计功能。
@Bean
public ServletRegistrationBean druidStatViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 配置登录用户名和密码
bean.addInitParameter("loginUsername", "admin");
bean.addInitParameter("loginPassword", "admin");
// 禁用重置功能
bean.addInitParameter("resetEnable", "false");
return bean;
}
@Bean
public FilterRegistrationBean druidWebStatFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new WebStatFilter());
// 配置 URL 过滤规则
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "/druid/*,*.ico,/error"); // 排除的 URL
return filterRegistrationBean;
}
一旦完成了上述配置,你可以通过访问 http://localhost:8080/druid
来查看 Druid 的监控界面。
监控内容:
Druid 支持记录慢 SQL,帮助开发人员优化性能。在 application.yml
或 application.properties
中,你可以配置慢 SQL 的阈值。
spring:
datasource:
druid:
filters: stat,wall,log4j # 启用慢 SQL 记录
# 配置慢 SQL 阈值
log-slow-sql: true # 启用慢 SQL 日志
slow-sql-millis: 5000 # 记录超过 5 秒的慢 SQL
Druid 提供了 SQL 防火墙(WallFilter
),可以防止恶意的 SQL 注入攻击。你可以在 filters
中启用 wall
。
spring:
datasource:
druid:
filters: stat,wall
WallFilter 是 Druid 的一个过滤器,用于检测潜在的 SQL 注入和不安全的 SQL 语句。你可以通过配置来进行 SQL 校验。
Druid 还提供了一个监控 API,用于查看连接池的状态等信息。你可以通过访问以下 URL 来获取 Druid 的监控数据:
/druid/dataSource
/druid/sql
/druid/slowSql
这些 API 提供了可以集成到第三方监控平台(如 Prometheus 或 Grafana)的接口。
application.yml
示例配置:spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
filters: stat,wall,log4j
# 配置监控页面
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
reset-enable: false
web-stat-filter:
enabled: true
exclusions: /druid/*,*.ico,/error
log-slow-sql: true
slow-sql-millis: 5000 # 慢 SQL 阈值:超过 5 秒的 SQL 会被记录
DruidConfig.java
配置类:@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean druidStatViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
bean.addInitParameter("loginUsername", "admin");
bean.addInitParameter("loginPassword", "admin");
bean.addInitParameter("resetEnable", "false");
return bean;
}
@Bean
public FilterRegistrationBean druidWebStatFilter() {
FilterRegistrationBean 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,帮助开发者优化数据库性能。
/**
* Created by libinsong on 2017/4/19.
*/
@EnableScheduling。// 表示开启spring中的定时任务功能。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${filter.slow.reqmillis:3000}")
private String slowReqMillis;
@Autowired
private DispatcherServlet dispatcherServlet;
/**
* 去掉JSON返回的Null属性,配置HTTP消息转换器,去掉JSON返回的Null属性并设置UTF-8编码。
*
* @return
*/
@Bean
public HttpMessageConverters customConverters() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
//设置日期格式
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
//设置中文编码格式
List 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
*/
@Bean
public 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
*/
@Bean
public FilterRegistrationBean druidWebStatFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "/druid/*,*.ico,/error");
return filterRegistrationBean;
}
/**
* {@link LogFilter}
* 注册日志过滤器,记录慢请求日志。
* @return
*/
@Bean
public FilterRegistrationBean logFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "/druid/*,*.ico,/error");
filterRegistrationBean.addInitParameter("slowReqMillis", slowReqMillis);
return filterRegistrationBean;
}
@Bean
public HandlerInterceptor getSystemInterceptor() {
return new SystemInterceptor();
}
/**
* {@link addInterceptors}
* 注册系统拦截器,拦截特定路径的请求
* @return
*/
@Override
public 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
*/
@Bean
public ServletRegistrationBean swaggerServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean<>(dispatcherServlet);
bean.addUrlMappings("/swagger-resources", "/swagger-resources/configuration/ui", "/v2/api-docs");
bean.setName("swaggerServlet");
return bean;
}
@Override
public 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/");
}
}
在 Spring Boot 项目中,可以使用 AOP(面向切面编程)来为 Controller 层和 DAO 层添加日志记录功能。通过 AOP,我们可以在方法调用前后自动记录日志,而不需要在每个方法中手动编写日志代码。
pom.xml
中添加以下依赖:
org.springframework.boot
spring-boot-starter-aop
首先,我们需要定义一个日志切面类,这个类将定义切点(即要执行切面的地方)和通知(切面触发时执行的操作)。
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);
}
}
@Pointcut
:定义切点,用于匹配指定的类和方法。execution(* com.example.demo.controller..*(..))
:匹配 controller
包下所有类的方法。execution(* com.example.demo.dao..*(..))
:匹配 dao
包下所有类的方法。@Before
:在目标方法执行之前执行,记录进入 Controller 或 DAO 层的方法信息。@After
:在目标方法执行之后执行,记录退出 Controller 或 DAO 层的方法信息。JoinPoint
:提供了对目标方法签名、参数等信息的访问。为了查看日志输出,确保你在 application.properties
或 application.yml
中配置了日志级别。
# application.properties
logging.level.com.example.demo.aspect=INFO
或者:
# application.yml
logging:
level:
com.example.demo.aspect: INFO
假设我们有以下的 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 {
@Autowired
private 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 interaction
return "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 {
@Autowired
private 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
@Pointcut("execution(* com.example.demo.controller..*(..))")
配置切点,结合 @Before
和 @After
注解记录进入和退出的方法信息。@Pointcut("execution(* com.example.demo.dao..*(..))")
配置切点来记录数据库操作方法的日志。通过 AOP 技术,你可以在不改变原有业务逻辑的情况下,灵活地添加日志记录,便于调试、监控和审计。
/**
* @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);
@Override
public 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 服务调用过程中,拦截并对请求进行额外的处理。它通常用于记录日志、统计调用次数、处理监控、性能分析等。
这个类的起作用时间是在 Dubbo 服务提供者 被调用时。当一个客户端请求通过 Dubbo 进行调用时,DubboFilter
会作为请求的拦截器被触发,执行它定义的逻辑。
具体来说,它会在以下时机起作用:
DubboFilter
会首先被触发。invoker.invoke(invocation)
继续执行后续的调用(即真正的服务处理)。@Activate(group = Constants.PROVIDER)
这个注解标记了该 DubboFilter
的作用范围。具体解释:
group = Constants.PROVIDER
表明该 DubboFilter
只会在 Dubbo 服务提供者(Provider)端生效。@Activate
注解的作用是用来激活该过滤器,在某个特定的环境中自动启用。在这里,Constants.PROVIDER
表示过滤器只会在 服务端 启动并生效,而不会影响到消费者端。
Dubbo 的 Filter 是一种类似于拦截器的机制,它可以在服务调用的不同阶段进行切入。invoke
方法就是 Filter 在处理请求时被调用的核心方法:
invoker.invoke(invocation)
执行目标服务的调用。MeterRegistryUtil
进行监控数据的采集,记录调用成功与否、耗时等数据。通过 timerStart()
开始计时,timerStop()
停止计时并将监控数据发送到指标系统中。在 Dubbo 中,Filter
是通过 Dubbo 框架自动管理的,具体生命周期包括:
DubboFilter
会在服务启动时被注册到 Dubbo 框架中。DubboFilter
进行处理,按照过滤器的链式调用顺序执行。DubboFilter
会按顺序执行,执行完毕后,invoker.invoke(invocation)
会调用实际的服务方法。DubboFilter
会对结果进行处理,如记录日志、采集监控数据等。假设有一个 Dubbo 服务提供者,客户端调用服务时:
DubboFilter
拦截到请求。invoker.invoke(invocation)
,即继续执行服务逻辑。invoker.invoke()
执行实际的服务方法。DubboFilter
在服务方法执行完成后,记录调用的时间、方法名、接口名等信息,并通过 MeterRegistryUtil
发送监控数据。DubboFilter
返回结果给消费者。logger.info
记录了调用的接口名、方法名和耗时信息。MeterRegistryUtil
采集服务调用的相关指标,如成功与否、耗时等信息,发送到监控系统。DubboFilter
会记录异常信息,确保不会导致服务中断。DubboFilter
类的作用是作为 Dubbo 服务提供者端的过滤器,用于:
通过 @Activate(group = Constants.PROVIDER)
注解,它只会在 服务提供者端 被触发和执行,而不会影响到消费者端。
@RestControllerAdvice
是 Spring 5 引入的一个注解,它结合了 @ControllerAdvice
和 @ResponseBody
的功能。它用于处理全局异常、全局数据绑定和全局模型属性等,但与 @ControllerAdvice
不同的是,@RestControllerAdvice
自动将返回值序列化为 JSON 格式,适用于 RESTful 风格的 API。
@RestControllerAdvice
的作用:@RestControllerAdvice
结合了 @ControllerAdvice
(提供全局配置功能)和 @ResponseBody
(将返回的对象转换为 JSON 或 XML)注解,因此它可以简化 RESTful 风格应用中的异常处理、数据绑定和响应体处理。
假设你的项目中可能会抛出一些常见的异常,比如业务异常(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 handleBusinessException(BusinessException e) {
return new ResponseEntity<>("Business error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
// 处理通用异常
@ExceptionHandler(Exception.class)
public ResponseEntity handleException(Exception e) {
return new ResponseEntity<>("Internal server error: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@ExceptionHandler
:指定处理哪些类型的异常。ResponseEntity
:用于构建返回的 HTTP 响应。你可以在 @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 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 Setters
public 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;
}
}
}
这样,当应用中发生异常时,返回给客户端的错误信息会包含在 error
和 message
字段中,保持了接口返回的统一性。
你还可以通过 @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
的值。
你还可以为所有的 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 中的配置
@InitBinder
public void initBinder(WebDataBinder binder) {
// 自定义数据绑定器配置
binder.setDisallowedFields("password");
}
}
@RestControllerAdvice
结合 @ResponseBody
和 @ControllerAdvice
的优势:@RestControllerAdvice
自动为每个方法的返回值添加了 @ResponseBody
,你不需要再额外配置 JSON 序列化。@RestControllerAdvice
可以集中管理应用的异常处理、模型属性、数据绑定等。@RestControllerAdvice
中对返回的结果进行统一封装,如统一错误响应格式或成功响应格式。@RestControllerAdvice
是 Spring 5 引入的用于增强 @ControllerAdvice
的功能,自动将响应对象转换为 JSON 格式,适用于 RESTful API 项目。@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
@ResponseBody
public Response error(HttpServletRequest request, HttpServletResponse response) {
response.reset();
// 设置状态码
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setHeader("Cache-Control", "no-cache");
Response res = new Response();
res.setCode(StatusCodeEnum.SYSTEM_ERROR.getStatus());
res.setMessage(StatusCodeEnum.SYSTEM_ERROR.getMsg());
Map body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
logger.error("Request {}Exception Information{}", request.getRequestURL(), JSON.toJSONString(body));
return res;
}
protected Map 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 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;
}
}
GlobalErrorController
:GlobalErrorController
主要用于处理 HTTP 错误页面(如 404、500 错误等),并返回统一的错误响应。它的目的是捕获和处理 Spring Boot 中的系统层错误(例如页面未找到、服务器错误等)。GlobalErrorController
返回的是一个标准的错误响应,通常包含错误码、错误消息等信息。ErrorAttributes
来获取错误详情。getErrorAttributes
方法用于获取错误的详细信息。GlobalExceptionHandler
:GlobalExceptionHandler
主要用于处理应用层(业务层)以及一些常见的请求错误(如参数校验错误、上传文件错误等)。它的作用是在应用层捕获和处理特定的业务异常、上传文件异常、参数错误等。Response
封装错误信息,但它更侧重于捕获和处理业务相关的异常,并返回详细的错误信息(如错误码、业务消息等)。BizException
、ServiceException
)以及文件上传、请求参数校验等进行处理。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";
@Resource
private MultipartProperties multipartProperties;
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Response defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
response.reset();
// 设置状态码
response.setStatus(HttpStatus.OK.value());
response.setHeader(CACHE_CONTROL, NO_CACHE);
Response 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)
@ResponseBody
public Response
GlobalExceptionHandler
类的作用:这个类是 全局异常处理器,它的主要作用是处理 Spring Boot 应用中所有未被捕获的异常,并根据不同的异常类型返回适当的错误信息。它使用了 Spring 的 @RestControllerAdvice
注解来进行全局异常捕获和处理。该类能够捕捉不同类型的异常,并返回一致的错误响应格式给客户端。
BizException
、ServiceException
、SystemException
):这些异常通常表示应用逻辑中出现的错误。根据不同的业务需求,会返回不同的错误信息。MethodArgumentNotValidException
、BindException
,主要用于校验请求参数是否正确。MaxUploadSizeExceededException
,当上传的文件大小超过预设的限制时,返回相应的错误信息。Response
对象来包装错误响应信息,返回统一的错误码、错误消息和额外的错误详情(如上传文件大小等)。defaultErrorHandler
:BusException
:业务异常,返回业务相关的错误信息。ServiceException
:服务层异常,返回服务相关的错误信息。SystemException
:系统异常,返回系统错误信息。ServletRequestBindingException
和 IllegalArgumentException
):一般参数错误或非法参数。handleException
:BizException
类型的异常,处理业务相关的异常。根据异常中设置的消息和参数,返回相应的错误信息。handleUploadException
:MaxUploadSizeExceededException
)。handleFieldException
:cn.fraudmetrix
模块的 BizException
,并根据其 code
字段设置相应的错误代码。validationException
:MethodArgumentNotValidException
和 BindException
),返回字段错误信息。 特性 |
|
|
作用 |
主要处理系统错误(如404、500等) |
主要处理业务层异常、请求参数错误等 |
异常类型 |
捕获通用的 HTTP 错误 |
捕获应用层的异常(如 、 、 ) |
错误响应格式 |
统一的错误响应,通常返回错误码和错误消息 |
统一的错误响应,详细描述业务错误、上传限制等 |
异常处理 |
基于 HTTP 错误,处理页面访问错误 |
基于业务异常,处理文件上传、参数校验等 |
GlobalErrorController
主要处理系统级别的错误,捕获 HTTP 错误并返回统一的错误响应。GlobalExceptionHandler
主要处理应用层的异常,尤其是业务异常、上传异常、参数校验等。