【Spring】Spring MVC核心原理与RESTful最佳实践详解

Spring MVC核心原理与RESTful最佳实践详解

一、DispatcherServlet:请求处理中枢

1.1 核心职责与架构

DispatcherServlet是Spring MVC的前端控制器(Front Controller) ,所有HTTP请求的统一入口。它继承自HttpServlet,遵循Servlet规范,是整个MVC架构的调度核心。

关键特性

  • 单例模式:每个应用一个实例,线程安全
  • 职责分离:仅负责调度,不处理具体业务
  • 可扩展性:通过策略模式支持多种Handler、ViewResolver等组件

1.2 请求处理完整流程

java 复制代码
// DispatcherServlet核心方法伪代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    
    try {
        // 1. 文件上传请求预处理
        processedRequest = checkMultipart(request);
        
        // 2. 获取HandlerExecutionChain(Handler+拦截器链)
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }
        
        // 3. 获取HandlerAdapter(适配不同Controller类型)
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        
        // 4. 执行拦截器preHandle
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return; // 拦截器返回false,终止请求
        }
        
        // 5. 调用Handler处理请求(核心)
        ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        
        // 6. 执行拦截器postHandle
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        
    } catch (Exception ex) {
        // 异常记录
        dispatchException = ex;
    }
    
    // 7. 结果处理(视图渲染或异常处理)
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    // 8. 执行拦截器afterCompletion(无论成功失败)
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

流程图

复制代码
HTTP Request
    │
    ▼
DispatcherServlet.doDispatch()
    │
    ├─▶ 1. MultipartResolver.resolveMultipart()  [文件上传]
    │
    ├─▶ 2. HandlerMapping.getHandler()          [路由查找]
    │      └─▶ HandlerExecutionChain
    │            ├─ HandlerMethod
    │            └─ HandlerInterceptor[] (preHandle/postHandle/afterCompletion)
    │
    ├─▶ 3. HandlerAdapter.supports()            [适配器选择]
    │
    ├─▶ 4. HandlerInterceptor.preHandle()       [前置拦截]
    │
    ├─▶ 5. HandlerAdapter.handle()              [执行业务逻辑]
    │      ├─ HandlerMethodArgumentResolver     [参数解析]
    │      ├─ 调用@Controller方法              [业务处理]
    │      └─ HandlerMethodReturnValueHandler   [返回值处理]
    │
    ├─▶ 6. HandlerInterceptor.postHandle()      [后置拦截]
    │
    ├─▶ 7. ViewResolver.resolveViewName()       [视图解析]
    │      └─ View.render()                     [视图渲染]
    │
    └─▶ 8. HandlerInterceptor.afterCompletion() [最终清理]

二、HandlerMapping:智能路由决策

2.1 核心实现类

HandlerMapping将请求映射到HandlerExecutionChain,Spring提供多种实现:

实现类 用途 匹配规则 优先级
RequestMappingHandlerMapping 注解驱动 @RequestMapping等注解 最高
SimpleUrlHandlerMapping 静态URL映射 URL路径
BeanNameUrlHandlerMapping Bean名称映射 Bean名称以/开头
RouterFunctionMapping 函数式路由 RouterFunction WebFlux

2.2 RequestMappingHandlerMapping工作原理

初始化阶段(启动时)

java 复制代码
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping {
    @Override
    public void afterPropertiesSet() {
        // 扫描所有@Controller/@RestController Bean
        String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
        for (String beanName : beanNames) {
            Class<?> beanType = obtainApplicationContext().getType(beanName);
            if (beanType != null && isHandler(beanType)) {
                // 探测所有@RequestMapping方法
                detectHandlerMethods(beanName);
            }
        }
    }
    
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = handler.getClass();
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        
        // 反射获取所有方法
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
        
        // 注册到MappingRegistry
        methods.forEach((method, mapping) -> {
            registerHandlerMethod(handler, method, mapping);
        });
    }
}

请求阶段(运行时)

java 复制代码
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = initLookupPath(request);
    // 从MappingRegistry中精确匹配
    return lookupHandlerMethod(lookupPath, request);
}

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    
    // 查找所有匹配的RequestMappingInfo
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    
    if (matches.isEmpty()) {
        // 无直接匹配,尝试模糊匹配
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }
    
    if (!matches.isEmpty()) {
        // 选择最佳匹配(根据模式精确度、参数数量等)
        Match bestMatch = matches.get(0);
        return bestMatch.handlerMethod;
    }
    return null;
}

2.3 自定义HandlerMapping示例

需求:为API添加版本前缀

java 复制代码
// 1. 定义版本注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    String value();
}

// 2. 自定义HandlerMapping
@Component
public class ApiVersionHandlerMapping extends RequestMappingHandlerMapping {
    
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
        if (info == null) return null;
        
        // 检查类或方法上的版本注解
        ApiVersion typeVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
        ApiVersion methodVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        
        String version = (methodVersion != null) ? methodVersion.value() : 
                         (typeVersion != null) ? typeVersion.value() : null;
        
        if (version != null) {
            // 构建新路径条件:/api/v{version}/path
            PatternsRequestCondition oldCondition = info.getPathPatternsCondition();
            PatternsRequestCondition versionedCondition = new PatternsRequestCondition(
                oldCondition.getPatternValues().stream()
                    .map(pattern -> "/api/v" + version + pattern)
                    .toArray(String[]::new)
            );
            
            return RequestMappingInfo.paths(versionedCondition.getPatternValues())
                    .methods(info.getMethodsCondition().getMethods())
                    .params(info.getParamsCondition().getExpressions())
                    .headers(info.getHeadersCondition().getHeaders())
                    .consumes(info.getConsumesCondition().getConsumableMediaTypes())
                    .produces(info.getProducesCondition().getProducibleMediaTypes())
                    .build();
        }
        return info;
    }
}

// 3. 使用示例
@RestController
@ApiVersion("2")
@RequestMapping("/users")
public class UserControllerV2 {
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) { ... }
}
// 实际路径:/api/v2/users/{id}

三、参数解析:从请求到方法入参

3.1 HandlerMethodArgumentResolver架构

Spring MVC通过策略模式支持30+种参数类型,核心流程:

java 复制代码
public class RequestMappingHandlerAdapter {
    private List<HandlerMethodArgumentResolver> argumentResolvers;
    
    protected Object[] getMethodArgumentValues(NativeWebRequest request, 
                                               ModelAndViewContainer mavContainer,
                                               Object... providedArgs) throws Exception {
        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            // 查找支持的解析器
            HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
            // 解析参数
            args[i] = resolver.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
        }
        return args;
    }
}

3.2 核心解析器详解

解析器 支持注解/类型 数据转换机制
RequestParamMethodArgumentResolver @RequestParam Converter/PropertyEditor
PathVariableMethodArgumentResolver @PathVariable 字符串强制转换
RequestBodyArgumentResolver @RequestBody HttpMessageConverter
ModelAttributeMethodProcessor @ModelAttribute WebDataBinder
ServletRequestMethodArgumentResolver HttpServletRequest 直接传递

3.3 参数解析实战示例

示例1:复杂查询参数封装

java 复制代码
@Data
public class UserQuery {
    private String username;
    private Integer minAge;
    private Integer maxAge;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate createdSince;
}

@GetMapping("/search")
public Page<User> searchUsers(UserQuery query, Pageable pageable) {
    // URL: /users/search?username=jack&minAge=18&page=0&size=20
    return userService.search(query, pageable);
}

示例2:自定义解析器(当前登录用户)

java 复制代码
// 1. 定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {}

// 2. 实现解析器
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class) 
               && parameter.getParameterType().equals(User.class);
    }
    
    @Override
    public Object resolveArgument(MethodParameter parameter, 
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, 
                                  WebDataBinderFactory binderFactory) throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null) {
            throw new UnauthorizedException("用户未登录");
        }
        return userService.getByUsername(auth.getName());
    }
}

// 3. 注册解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CurrentUserArgumentResolver());
    }
}

// 4. 使用
@GetMapping("/profile")
public UserProfile getProfile(@CurrentUser User user) {
    return userService.getProfile(user.getId());
}

四、响应处理:从返回值到HTTP响应

4.1 HandlerMethodReturnValueHandler体系

Spring MVC通过返回值处理器将方法结果转换为HTTP响应,支持多种返回类型。

处理器 支持类型 处理方式
RequestResponseBodyMethodProcessor @ResponseBody JSON/XML序列化
ViewNameMethodReturnValueHandler String(视图名) 视图渲染
ModelAndViewMethodReturnValueHandler ModelAndView 模型+视图
HttpEntityMethodProcessor ResponseEntity 完整HTTP响应控制
CallableMethodReturnValueHandler Callable 异步处理

4.2 @ResponseBody与HttpMessageConverter

java 复制代码
@GetMapping("/users/{id}")
@ResponseBody  // 标识返回值直接写入HTTP body
public User getUser(@PathVariable Long id) {
    return userService.findById(id);
}

转换流程

java 复制代码
// 1. RequestResponseBodyMethodProcessor处理
public void handleReturnValue(Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, 
                              NativeWebRequest webRequest) throws Exception {
    
    // 标记请求已处理
    mavContainer.setRequestHandled(true);
    
    // 2. 选择合适的HttpMessageConverter
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    writeWithMessageConverters(returnValue, returnType, outputMessage);
}

// 3. 使用Jackson序列化(MappingJackson2HttpMessageConverter)
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException {
    ObjectMapper objectMapper = getObjectMapper();
    objectMapper.writeValue(outputMessage.getBody(), object);
}

4.3 ResponseEntity:完全响应控制

java 复制代码
// 场景1:201 Created + Location头
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserDTO dto) {
    User created = userService.create(dto);
    URI location = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(created.getId())
            .toUri();
    
    return ResponseEntity.created(location).body(created);
    // 状态码: 201
    // Location: /api/v1/users/123
}

// 场景2:204 No Content
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
    userService.delete(id);
    return ResponseEntity.noContent().build();
    // 状态码: 204,body为空
}

// 场景3:条件响应
@GetMapping("/orders/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id, 
                                      @RequestHeader(value = "If-None-Match", required = false) String etag) {
    Order order = orderService.findById(id);
    String currentEtag = generateEtag(order);
    
    if (currentEtag.equals(etag)) {
        return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build(); // 304
    }
    
    return ResponseEntity.ok()
            .eTag(currentEtag)
            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
            .body(order);
}

4.4 文件下载与流式响应

java 复制代码
// 文件下载
@GetMapping("/reports/{id}/download")
public ResponseEntity<Resource> downloadReport(@PathVariable Long id) {
    Report report = reportService.getReport(id);
    Resource resource = new ByteArrayResource(report.getContent());
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename=\"" + report.getFilename() + "\"")
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .contentLength(report.getSize())
            .body(resource);
}

// SSE(Server-Sent Events)推送
@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamEvents() {
    SseEmitter emitter = new SseEmitter();
    
    // 异步发送事件
    CompletableFuture.runAsync(() -> {
        try {
            for (int i = 0; i < 10; i++) {
                emitter.send(SseEmitter.event()
                        .id(String.valueOf(i))
                        .name("message")
                        .data("Event " + i));
                Thread.sleep(1000);
            }
            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });
    
    return emitter; // 保持连接,持续推送
}

五、RESTful API最佳实践

5.1 URL设计黄金法则

1. 资源命名规范

  • 使用名词复数表示集合
  • 避免动词(用HTTP方法表达动作)
  • 层级关系用/,过滤条件用?
java 复制代码
// ✅ 良好设计
GET    /api/v1/users              // 获取用户列表
GET    /api/v1/users?role=admin   // 过滤
GET    /api/v1/users/{id}         // 获取单个用户
POST   /api/v1/users              // 创建用户
PUT    /api/v1/users/{id}         // 全量更新
PATCH  /api/v1/users/{id}         // 部分更新
DELETE /api/v1/users/{id}         // 删除用户
GET    /api/v1/users/{id}/orders  // 子资源(用户的订单)

// ❌ 反模式
GET    /api/v1/getUser            // 动词
GET    /api/v1/users/getInfo      // 冗余动词
POST   /api/v1/users/create       // 动词,应直接用POST
GET    /api/v1/deleteUser         // 动词,应使用DELETE

2. 版本管理

java 复制代码
// 方式1:URL版本化(推荐,简单明确)
@RequestMapping("/api/v1/users")  // 稳定版本
@RequestMapping("/api/v2/users")  // 新功能版本

// 方式2:Accept头版本化(更RESTful)
@GetMapping(value = "/users", 
            produces = "application/vnd.company.v1+json")
public ResponseEntity<List<User>> getUsersV1() { ... }

@GetMapping(value = "/users",
            produces = "application/vnd.company.v2+json")
public ResponseEntity<List<User>> getUsersV2() { ... }

5.2 HTTP状态码使用指南

HTTP方法 成功 创建 无内容 错误 认证失败 无权限 未找到
GET 200 - - 400 401 403 404
POST - 201 204 400 401 403 404
PUT 200 - 204 400 401 403 404
PATCH 200 - 204 400 401 403 404
DELETE 204 - - 400 401 403 404

Controller实现

java 复制代码
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
    
    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrder(@PathVariable Long id) {
        return orderService.findById(id)
                .map(ResponseEntity::ok)  // 200
                .orElse(ResponseEntity.notFound().build()); // 404
    }
    
    @PostMapping
    public ResponseEntity<Order> createOrder(@Valid @RequestBody OrderDTO dto) {
        Order created = orderService.create(dto);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(created.getId())
                .toUri();
        return ResponseEntity.created(location).body(created); // 201 + Location头
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
        orderService.delete(id);
        return ResponseEntity.noContent().build(); // 204
    }
}

5.3 统一错误响应格式

java 复制代码
// 错误响应DTO
@Data
@AllArgsConstructor
public class ErrorResponse {
    private LocalDateTime timestamp;
    private int status;
    private String error;
    private String message;
    private String path;
}

// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex, HttpServletRequest request) {
        log.error("系统异常", ex);
        
        ErrorResponse error = new ErrorResponse(
            LocalDateTime.now(),
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            "Internal Server Error",
            "系统繁忙,请稍后重试",
            request.getRequestURI()
        );
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(
            MethodArgumentNotValidException ex, HttpServletRequest request) {
        
        String message = ex.getBindingResult().getFieldErrors().stream()
                .map(err -> err.getField() + ": " + err.getDefaultMessage())
                .collect(Collectors.joining(", "));
        
        ErrorResponse error = new ErrorResponse(
            LocalDateTime.now(),
            HttpStatus.BAD_REQUEST.value(),
            "Validation Failed",
            message,
            request.getRequestURI()
        );
        
        return ResponseEntity.badRequest().body(error);
    }
}

响应示例

json 复制代码
// 400 Bad Request
{
    "timestamp": "2024-01-07T10:30:00",
    "status": 400,
    "error": "Validation Failed",
    "message": "age: 必须大于0, email: 格式不正确",
    "path": "/api/v1/users"
}

5.4 HATEOAS:超媒体驱动API

通过_links提供资源导航关系,实现API自描述。

java 复制代码
// 添加spring-boot-starter-hateoas依赖
@RestController
@RequestMapping("/api/v1/orders")
public class OrderHateoasController {
    
    @GetMapping("/{id}")
    public EntityModel<Order> getOrder(@PathVariable Long id) {
        Order order = orderService.findById(id)
                .orElseThrow(() -> new OrderNotFoundException(id));
        
        // 包装资源+链接
        return EntityModel.of(order,
            linkTo(methodOn(OrderController.class).getOrder(id)).withSelfRel(),
            linkTo(methodOn(OrderController.class).cancelOrder(id)).withRel("cancel"),
            linkTo(methodOn(OrderController.class).getOrderItems(id)).withRel("items")
        );
    }
}

// 响应示例
{
    "id": 123,
    "status": "PENDING",
    "amount": 99.99,
    "_links": {
        "self": {
            "href": "https://api.example.com/api/v1/orders/123"
        },
        "cancel": {
            "href": "https://api.example.com/api/v1/orders/123/cancel"
        },
        "items": {
            "href": "https://api.example.com/api/v1/orders/123/items"
        }
    }
}

5.5 生产级最佳实践

1. 请求追踪与日志

java 复制代码
@Component
public class TraceIdFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) throws ServletException, IOException {
        
        String traceId = request.getHeader("X-Trace-Id");
        if (traceId == null) {
            traceId = UUID.randomUUID().toString();
        }
        
        MDC.put("traceId", traceId);
        response.setHeader("X-Trace-Id", traceId);
        
        try {
            filterChain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

2. API限流

java 复制代码
@Component
public class RateLimitFilter extends OncePerRequestFilter {
    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain filterChain) throws ServletException, IOException {
        
        String clientId = getClientId(request);
        Bucket bucket = buckets.computeIfAbsent(clientId, this::createBucket);
        
        if (bucket.tryConsume(1)) {
            filterChain.doFilter(request, response);
        } else {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("Rate limit exceeded");
        }
    }
    
    private Bucket createBucket(String key) {
        Bandwidth bandwidth = Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1)));
        return Bucket.builder().addLimit(bandwidth).build();
    }
}

3. 接口文档(OpenAPI)

java 复制代码
@Configuration
@SecurityScheme(
    name = "bearerAuth",
    type = SecuritySchemeType.HTTP,
    bearerFormat = "JWT",
    scheme = "bearer"
)
public class OpenAPIConfig {
    
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("订单服务API")
                .version("v1")
                .description("提供订单创建、查询、修改等功能")
                .contact(new Contact().name("技术部").email("tech@example.com")))
            .addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
    }
}

// Controller注解
@Operation(summary = "创建订单", description = "创建新的订单记录")
@ApiResponses(value = {
    @ApiResponse(responseCode = "201", description = "订单创建成功"),
    @ApiResponse(responseCode = "400", description = "请求参数无效"),
    @ApiResponse(responseCode = "401", description = "未认证")
})
@PostMapping
public ResponseEntity<Order> createOrder(@Valid @RequestBody OrderDTO dto) {
    return ResponseEntity.created(...).body(created);
}

六、总结

Spring MVC通过DispatcherServlet 的统筹调度,实现了请求从接收到响应的全生命周期管理。掌握其内部机制(HandlerMapping、参数解析、返回值处理)能帮助开发者更好地调试和扩展。遵循RESTful最佳实践(URL规范、状态码、HATEOAS、统一错误处理)可构建出高可维护性、自描述、面向演进的生产级API。

核心口诀:DispatcherServlet管调度,HandlerMapping做路由,ArgumentResolver解析参,ReturnValueHandler管响应;URL用复数名词,动词交给HTTP方法,状态码精确表达,错误响应统一格式。

相关推荐
大爱编程♡17 小时前
Spring IoC&DI
数据库·mysql·spring
zhglhy18 小时前
Spring Data Slice使用指南
java·spring
阿杰 AJie20 小时前
Token 管理工具
java·spring
czlczl2002092520 小时前
从 SSO 登录到跨系统资源访问:OAuth2 全链路交互详解
java·spring boot·后端·spring·架构
廋到被风吹走21 小时前
【Spring】IoC容器深度解析:Bean生命周期与循环依赖三级缓存
java·spring·缓存
用户0203388613141 天前
手写Spring(2):实现AOP与JdbcTemplate
spring
用户0203388613141 天前
手写Spring框架(3):实现MVC
spring
麦兜*1 天前
Spring Boot 整合 Apache Doris:实现海量数据实时OLAP分析实战
大数据·spring boot·后端·spring·apache