Spring Boot测试框架详解

文章目录

    • [1. 测试框架概述](#1. 测试框架概述)
      • [1.1 测试类型](#1.1 测试类型)
      • [1.2 核心依赖](#1.2 核心依赖)
    • [2. 单元测试](#2. 单元测试)
      • [2.1 服务层测试](#2.1 服务层测试)
      • [2.2 工具类测试](#2.2 工具类测试)
    • [3. 集成测试](#3. 集成测试)
      • [3.1 数据访问层测试](#3.1 数据访问层测试)
      • [3.2 服务层集成测试](#3.2 服务层集成测试)
    • [4. Web层测试](#4. Web层测试)
      • [4.1 控制器测试](#4.1 控制器测试)
      • [4.2 完整Web测试](#4.2 完整Web测试)
    • [5. 安全测试](#5. 安全测试)
      • [5.1 认证测试](#5.1 认证测试)
      • [5.2 JWT测试](#5.2 JWT测试)
    • [6. 测试配置](#6. 测试配置)
      • [6.1 测试配置文件](#6.1 测试配置文件)
      • [6.2 测试配置类](#6.2 测试配置类)
    • [7. 测试最佳实践](#7. 测试最佳实践)
      • [7.1 测试数据管理](#7.1 测试数据管理)
      • [7.2 测试基类](#7.2 测试基类)
    • [8. 总结](#8. 总结)

1. 测试框架概述

Spring Boot提供了完整的测试解决方案,支持单元测试、集成测试、Web测试等多种测试类型。通过Spring Boot Test模块,可以快速构建可靠的测试套件。

1.1 测试类型

  • 单元测试:测试单个组件或方法
  • 集成测试:测试多个组件协作
  • Web测试:测试Web层功能
  • 数据访问测试:测试数据访问层
  • 安全测试:测试安全功能

1.2 核心依赖

xml 复制代码
<dependencies>
    <!-- Spring Boot Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- TestContainers -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- Mockito -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 单元测试

2.1 服务层测试

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setId(1L);
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setPassword("password");
    }
    
    @Test
    void testCreateUser() {
        // Given
        when(userRepository.save(any(User.class))).thenReturn(testUser);
        
        // When
        User result = userService.createUser(testUser);
        
        // Then
        assertNotNull(result);
        assertEquals("testuser", result.getUsername());
        assertEquals("test@example.com", result.getEmail());
        verify(userRepository, times(1)).save(testUser);
    }
    
    @Test
    void testGetUserById() {
        // Given
        when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
        
        // When
        Optional<User> result = userService.getUserById(1L);
        
        // Then
        assertTrue(result.isPresent());
        assertEquals("testuser", result.get().getUsername());
        verify(userRepository, times(1)).findById(1L);
    }
    
    @Test
    void testGetUserByIdNotFound() {
        // Given
        when(userRepository.findById(999L)).thenReturn(Optional.empty());
        
        // When
        Optional<User> result = userService.getUserById(999L);
        
        // Then
        assertFalse(result.isPresent());
        verify(userRepository, times(1)).findById(999L);
    }
    
    @Test
    void testUpdateUser() {
        // Given
        User updatedUser = new User();
        updatedUser.setId(1L);
        updatedUser.setUsername("updateduser");
        updatedUser.setEmail("updated@example.com");
        
        when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
        when(userRepository.save(any(User.class))).thenReturn(updatedUser);
        
        // When
        User result = userService.updateUser(1L, updatedUser);
        
        // Then
        assertNotNull(result);
        assertEquals("updateduser", result.getUsername());
        verify(userRepository, times(1)).findById(1L);
        verify(userRepository, times(1)).save(any(User.class));
    }
    
    @Test
    void testDeleteUser() {
        // Given
        when(userRepository.existsById(1L)).thenReturn(true);
        
        // When
        userService.deleteUser(1L);
        
        // Then
        verify(userRepository, times(1)).existsById(1L);
        verify(userRepository, times(1)).deleteById(1L);
    }
}

2.2 工具类测试

java 复制代码
package com.example.demo.util;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;

class StringUtilsTest {
    
    @Test
    void testIsEmpty() {
        assertTrue(StringUtils.isEmpty(null));
        assertTrue(StringUtils.isEmpty(""));
        assertFalse(StringUtils.isEmpty("hello"));
    }
    
    @Test
    void testIsBlank() {
        assertTrue(StringUtils.isBlank(null));
        assertTrue(StringUtils.isBlank(""));
        assertTrue(StringUtils.isBlank("   "));
        assertFalse(StringUtils.isBlank("hello"));
    }
    
    @ParameterizedTest
    @ValueSource(strings = {"hello", "world", "test"})
    void testIsNotEmpty(String input) {
        assertTrue(StringUtils.isNotEmpty(input));
    }
    
    @Test
    void testTrim() {
        assertEquals("hello", StringUtils.trim("  hello  "));
        assertEquals("", StringUtils.trim("   "));
        assertNull(StringUtils.trim(null));
    }
}

3. 集成测试

3.1 数据访问层测试

java 复制代码
package com.example.demo.repository;

import com.example.demo.entity.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest
@ActiveProfiles("test")
class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setPassword("password");
        testUser.setFullName("Test User");
    }
    
    @Test
    void testFindByUsername() {
        // Given
        entityManager.persistAndFlush(testUser);
        
        // When
        Optional<User> result = userRepository.findByUsername("testuser");
        
        // Then
        assertTrue(result.isPresent());
        assertEquals("testuser", result.get().getUsername());
    }
    
    @Test
    void testFindByEmail() {
        // Given
        entityManager.persistAndFlush(testUser);
        
        // When
        Optional<User> result = userRepository.findByEmail("test@example.com");
        
        // Then
        assertTrue(result.isPresent());
        assertEquals("test@example.com", result.get().getEmail());
    }
    
    @Test
    void testFindByStatus() {
        // Given
        testUser.setStatus(User.UserStatus.ACTIVE);
        entityManager.persistAndFlush(testUser);
        
        // When
        List<User> result = userRepository.findByStatus(User.UserStatus.ACTIVE);
        
        // Then
        assertEquals(1, result.size());
        assertEquals(User.UserStatus.ACTIVE, result.get(0).getStatus());
    }
    
    @Test
    void testCustomQuery() {
        // Given
        entityManager.persistAndFlush(testUser);
        
        // When
        Long count = userRepository.countActiveUsers();
        
        // Then
        assertTrue(count >= 0);
    }
}

3.2 服务层集成测试

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ActiveProfiles("test")
@Transactional
class UserServiceIntegrationTest {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private UserRepository userRepository;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setPassword("password");
        testUser.setFullName("Test User");
    }
    
    @Test
    void testCreateAndRetrieveUser() {
        // When
        User createdUser = userService.createUser(testUser);
        
        // Then
        assertNotNull(createdUser.getId());
        assertEquals("testuser", createdUser.getUsername());
        
        // When
        Optional<User> retrievedUser = userService.getUserById(createdUser.getId());
        
        // Then
        assertTrue(retrievedUser.isPresent());
        assertEquals("testuser", retrievedUser.get().getUsername());
    }
    
    @Test
    void testUpdateUser() {
        // Given
        User createdUser = userService.createUser(testUser);
        
        // When
        User updatedUser = new User();
        updatedUser.setFullName("Updated User");
        updatedUser.setEmail("updated@example.com");
        
        User result = userService.updateUser(createdUser.getId(), updatedUser);
        
        // Then
        assertEquals("Updated User", result.getFullName());
        assertEquals("updated@example.com", result.getEmail());
    }
    
    @Test
    void testDeleteUser() {
        // Given
        User createdUser = userService.createUser(testUser);
        
        // When
        userService.deleteUser(createdUser.getId());
        
        // Then
        Optional<User> deletedUser = userService.getUserById(createdUser.getId());
        assertFalse(deletedUser.isPresent());
    }
}

4. Web层测试

4.1 控制器测试

java 复制代码
package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setId(1L);
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setFullName("Test User");
    }
    
    @Test
    void testGetUserById() throws Exception {
        // Given
        when(userService.getUserById(1L)).thenReturn(Optional.of(testUser));
        
        // When & Then
        mockMvc.perform(get("/api/users/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1))
                .andExpect(jsonPath("$.username").value("testuser"))
                .andExpect(jsonPath("$.email").value("test@example.com"));
    }
    
    @Test
    void testGetUserByIdNotFound() throws Exception {
        // Given
        when(userService.getUserById(999L)).thenReturn(Optional.empty());
        
        // When & Then
        mockMvc.perform(get("/api/users/999"))
                .andExpect(status().isNotFound());
    }
    
    @Test
    void testCreateUser() throws Exception {
        // Given
        when(userService.createUser(any(User.class))).thenReturn(testUser);
        
        // When & Then
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(testUser)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.username").value("testuser"));
    }
    
    @Test
    void testUpdateUser() throws Exception {
        // Given
        User updatedUser = new User();
        updatedUser.setFullName("Updated User");
        updatedUser.setEmail("updated@example.com");
        
        when(userService.updateUser(1L, any(User.class))).thenReturn(updatedUser);
        
        // When & Then
        mockMvc.perform(put("/api/users/1")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(updatedUser)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.fullName").value("Updated User"));
    }
    
    @Test
    void testDeleteUser() throws Exception {
        // When & Then
        mockMvc.perform(delete("/api/users/1"))
                .andExpect(status().isNoContent());
    }
}

4.2 完整Web测试

java 复制代码
package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
@Transactional
class UserControllerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setPassword("password");
        testUser.setFullName("Test User");
    }
    
    @Test
    void testCreateAndRetrieveUser() throws Exception {
        // When
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(testUser)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.username").value("testuser"));
        
        // Then
        User savedUser = userRepository.findByUsername("testuser").orElse(null);
        assertNotNull(savedUser);
        
        // When
        mockMvc.perform(get("/api/users/" + savedUser.getId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value("testuser"));
    }
    
    @Test
    void testUpdateUser() throws Exception {
        // Given
        User savedUser = userRepository.save(testUser);
        
        // When
        User updatedUser = new User();
        updatedUser.setFullName("Updated User");
        updatedUser.setEmail("updated@example.com");
        
        mockMvc.perform(put("/api/users/" + savedUser.getId())
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(updatedUser)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.fullName").value("Updated User"));
    }
    
    @Test
    void testDeleteUser() throws Exception {
        // Given
        User savedUser = userRepository.save(testUser);
        
        // When
        mockMvc.perform(delete("/api/users/" + savedUser.getId()))
                .andExpect(status().isNoContent());
        
        // Then
        assertFalse(userRepository.existsById(savedUser.getId()));
    }
}

5. 安全测试

5.1 认证测试

java 复制代码
package com.example.demo.security;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
@Transactional
class SecurityTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private UserRepository userRepository;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setPassword("password");
        testUser.setEnabled(true);
        userRepository.save(testUser);
    }
    
    @Test
    void testUnauthenticatedAccess() throws Exception {
        mockMvc.perform(get("/api/users"))
                .andExpect(status().isUnauthorized());
    }
    
    @Test
    @WithMockUser(username = "testuser", roles = "USER")
    void testAuthenticatedAccess() throws Exception {
        mockMvc.perform(get("/api/users"))
                .andExpect(status().isOk());
    }
    
    @Test
    @WithMockUser(username = "admin", roles = "ADMIN")
    void testAdminAccess() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
                .andExpect(status().isOk());
    }
    
    @Test
    @WithMockUser(username = "user", roles = "USER")
    void testUserCannotAccessAdmin() throws Exception {
        mockMvc.perform(get("/api/admin/users"))
                .andExpect(status().isForbidden());
    }
}

5.2 JWT测试

java 复制代码
package com.example.demo.security;

import com.example.demo.util.JwtTokenUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.util.ArrayList;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
class JwtSecurityTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    private String validToken;
    
    @BeforeEach
    void setUp() {
        UserDetails userDetails = new User("testuser", "password", new ArrayList<>());
        validToken = jwtTokenUtil.generateToken(userDetails);
    }
    
    @Test
    void testValidJwtToken() throws Exception {
        mockMvc.perform(get("/api/users")
                .header("Authorization", "Bearer " + validToken))
                .andExpect(status().isOk());
    }
    
    @Test
    void testInvalidJwtToken() throws Exception {
        mockMvc.perform(get("/api/users")
                .header("Authorization", "Bearer invalid-token"))
                .andExpect(status().isUnauthorized());
    }
    
    @Test
    void testMissingJwtToken() throws Exception {
        mockMvc.perform(get("/api/users"))
                .andExpect(status().isUnauthorized());
    }
}

6. 测试配置

6.1 测试配置文件

yaml 复制代码
# application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  
  security:
    user:
      name: test
      password: test
      roles: USER

logging:
  level:
    com.example.demo: DEBUG
    org.springframework.security: DEBUG

6.2 测试配置类

java 复制代码
package com.example.demo.config;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@TestConfiguration
public class TestSecurityConfig {
    
    @Bean
    @Primary
    public PasswordEncoder testPasswordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

7. 测试最佳实践

7.1 测试数据管理

java 复制代码
package com.example.demo.util;

import com.example.demo.entity.User;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

@Component
public class TestDataFactory {
    
    public static User createTestUser() {
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setPassword("password");
        user.setFullName("Test User");
        user.setEnabled(true);
        user.setCreatedAt(LocalDateTime.now());
        return user;
    }
    
    public static User createTestUser(String username, String email) {
        User user = createTestUser();
        user.setUsername(username);
        user.setEmail(email);
        return user;
    }
    
    public static User createAdminUser() {
        User user = createTestUser();
        user.setUsername("admin");
        user.setEmail("admin@example.com");
        user.setFullName("Admin User");
        return user;
    }
}

7.2 测试基类

java 复制代码
package com.example.demo.base;

import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
@Transactional
public abstract class BaseIntegrationTest {
    
    @Autowired
    protected MockMvc mockMvc;
    
    @BeforeEach
    void setUp() {
        // 通用测试设置
    }
}

8. 总结

Spring Boot测试框架提供了完整的测试解决方案:

  1. 单元测试:使用Mockito进行组件隔离测试
  2. 集成测试:测试组件协作和数据访问
  3. Web测试:使用MockMvc测试Web层功能
  4. 安全测试:测试认证和授权功能
  5. 测试配置:灵活的测试环境配置
  6. 最佳实践:测试数据管理和测试基类

通过合理使用这些测试技术,可以构建出可靠的测试套件,确保应用质量。


下一篇:Spring Boot微服务架构详解

相关推荐
豐儀麟阁贵4 小时前
基本数据类型
java·算法
_extraordinary_5 小时前
Java SpringMVC(二) --- 响应,综合性练习
java·开发语言
你的人类朋友5 小时前
什么是断言?
前端·后端·安全
程序员 Harry5 小时前
深度解析:使用ZIP流式读取大型PPTX文件的最佳实践
java
程序员小凯6 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
wxweven6 小时前
校招面试官揭秘:我们到底在寻找什么样的技术人才?
java·面试·校招
i学长的猫6 小时前
Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
后端·ruby on rails·ruby
陈陈爱java6 小时前
新知识点背诵
java
失散136 小时前
分布式专题——39 RocketMQ客户端编程模型
java·分布式·架构·rocketmq