Skip to content

Java 框架技术栈笔记 ​

1. Spring 框架 ​

1.1 Spring 框架概述 ​

Spring 是一个轻量级的 Java 企业级应用框架,提供了全面的基础设施支持,用于开发 Java 应用程序。

Spring 核心特性:

  • 依赖注入(DI)
  • 控制反转(IoC)
  • 面向切面编程(AOP)
  • 事务管理
  • 数据访问
  • Web 开发
  • 消息传递
  • 测试支持

1.2 Spring Framework 6 ​

Spring Framework 6 是 Spring 框架的最新版本,于 2022 年 11 月发布。

新特性:

  • 基于 Java 17+ 构建
  • 支持 Jakarta EE 10
  • 响应式编程增强
  • 原生镜像支持(GraalVM)
  • 改进的 AOT 处理
  • 简化的配置

核心模块:

  • spring-core:核心容器
  • spring-beans:Bean 管理
  • spring-context:应用上下文
  • spring-aop:面向切面编程
  • spring-tx:事务管理
  • spring-jdbc:JDBC 支持
  • spring-orm:ORM 支持
  • spring-web:Web 支持
  • spring-webmvc:MVC 框架

示例:

java
// 配置类
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserServiceImpl(userRepository);
    }
}

// 组件类
@Component
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    
    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id);
    }
}

// 启动类
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        User user = userService.getUserById(1L);
        System.out.println(user);
    }
}
// 配置类
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserServiceImpl(userRepository);
    }
}

// 组件类
@Component
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    
    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id);
    }
}

// 启动类
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        User user = userService.getUserById(1L);
        System.out.println(user);
    }
}

2. Spring Boot 3 ​

2.1 Spring Boot 概述 ​

Spring Boot 是 Spring 框架的扩展,用于快速创建基于 Spring 的应用程序,简化了 Spring 应用的配置和部署。

核心特性:

  • 自动配置
  • 起步依赖(Starter Dependencies)
  • 嵌入式服务器
  • 生产就绪特性
  • 无代码生成和 XML 配置

2.2 Spring Boot 3 ​

Spring Boot 3 是 Spring Boot 的最新版本,于 2022 年 11 月发布,基于 Spring Framework 6 构建。

新特性:

  • 基于 Java 17+ 构建
  • 支持 Jakarta EE 10
  • 原生镜像支持(GraalVM)
  • 改进的 AOT 处理
  • 响应式编程增强
  • 简化的配置
  • 新的 actuator 端点

示例:

1. 创建 Spring Boot 项目

使用 Spring Initializr 创建项目,添加以下依赖:

  • Spring Web
  • Spring Data JPA
  • H2 Database
  • Spring Boot Actuator

2. 应用配置

application.properties:

properties
# 应用名称
spring.application.name=myapp

# 服务器端口
server.port=8080

# H2 数据库配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

# H2 控制台
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# Actuator 配置
management.endpoints.web.exposure.include=*
# 应用名称
spring.application.name=myapp

# 服务器端口
server.port=8080

# H2 数据库配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

# H2 控制台
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# Actuator 配置
management.endpoints.web.exposure.include=*

3. 实体类

java
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "first_name", nullable = false)
    private String firstName;
    
    @Column(name = "last_name", nullable = false)
    private String lastName;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    // 构造函数、getter、setter
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "first_name", nullable = false)
    private String firstName;
    
    @Column(name = "last_name", nullable = false)
    private String lastName;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    // 构造函数、getter、setter
}

4. 数据访问层

java
public interface UserRepository extends JpaRepository<User, Long> {
    // 自定义查询方法
    List<User> findByLastName(String lastName);
    
    @Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
    List<User> findByEmailContaining(@Param("email") String email);
}
public interface UserRepository extends JpaRepository<User, Long> {
    // 自定义查询方法
    List<User> findByLastName(String lastName);
    
    @Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
    List<User> findByEmailContaining(@Param("email") String email);
}

5. 服务层

java
@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
    }
    
    @Override
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    @Override
    public User updateUser(Long id, User userDetails) {
        User user = getUserById(id);
        user.setFirstName(userDetails.getFirstName());
        user.setLastName(userDetails.getLastName());
        user.setEmail(userDetails.getEmail());
        return userRepository.save(user);
    }
    
    @Override
    public void deleteUser(Long id) {
        User user = getUserById(id);
        userRepository.delete(user);
    }
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
    }
    
    @Override
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    @Override
    public User updateUser(Long id, User userDetails) {
        User user = getUserById(id);
        user.setFirstName(userDetails.getFirstName());
        user.setLastName(userDetails.getLastName());
        user.setEmail(userDetails.getEmail());
        return userRepository.save(user);
    }
    
    @Override
    public void deleteUser(Long id) {
        User user = getUserById(id);
        userRepository.delete(user);
    }
}

6. 控制器层

java
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
        User updatedUser = userService.updateUser(id, userDetails);
        return ResponseEntity.ok(updatedUser);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
        User updatedUser = userService.updateUser(id, userDetails);
        return ResponseEntity.ok(updatedUser);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

7. 启动类

java
@SpringBootApplication
public class MyAppApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyAppApplication.class, args);
    }
}
@SpringBootApplication
public class MyAppApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyAppApplication.class, args);
    }
}

8. 测试

java
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private UserRepository userRepository;
    
    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
        User user = new User();
        user.setFirstName("John");
        user.setLastName("Doe");
        user.setEmail("john.doe@example.com");
        userRepository.save(user);
    }
    
    @Test
    void testGetUserById() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/users/1"))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.firstName").value("John"))
            .andExpect(MockMvcResultMatchers.jsonPath("$.lastName").value("Doe"));
    }
    
    @Test
    void testCreateUser() throws Exception {
        String userJson = "{\"firstName\":\"Jane\",\"lastName\":\"Smith\",\"email\":\"jane.smith@example.com\"}";
        
        mockMvc.perform(MockMvcRequestBuilders.post("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content(userJson))
            .andExpect(MockMvcResultMatchers.status().isCreated())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").isNumber())
            .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("jane.smith@example.com"));
    }
}
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private UserRepository userRepository;
    
    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
        User user = new User();
        user.setFirstName("John");
        user.setLastName("Doe");
        user.setEmail("john.doe@example.com");
        userRepository.save(user);
    }
    
    @Test
    void testGetUserById() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/api/users/1"))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.firstName").value("John"))
            .andExpect(MockMvcResultMatchers.jsonPath("$.lastName").value("Doe"));
    }
    
    @Test
    void testCreateUser() throws Exception {
        String userJson = "{\"firstName\":\"Jane\",\"lastName\":\"Smith\",\"email\":\"jane.smith@example.com\"}";
        
        mockMvc.perform(MockMvcRequestBuilders.post("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content(userJson))
            .andExpect(MockMvcResultMatchers.status().isCreated())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").isNumber())
            .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("jane.smith@example.com"));
    }
}

2.3 SpringMVC 请求流程(面试高频) ​

DispatcherServlet 完整处理链路:

用户请求 → DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ViewResolver → 响应
用户请求 → DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ViewResolver → 响应
┌────────────────────────────────────────────────────────────────┐
│                     DispatcherServlet                          │
│  ┌─────────────────┐                                          │
│  │ 1. HandlerMapping│ → 找到处理请求的 Controller 方法           │
│  └────────┬────────┘                                          │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 2. HandlerAdapter│ → 调用 Controller 方法(参数绑定、类型转换) │
│  └────────┬────────┘                                          │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 3. Controller    │ → 执行业务逻辑,返回 ModelAndView/数据      │
│  └────────┬────────┘                                          │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 4. ViewResolver  │ → 解析视图名 → 渲染 HTML                   │
│  └────────┬────────┘  (前后端分离项目中,返回 JSON 跳过此步)      │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 5. 拦截器后处理   │ → afterCompletion 等                      │
│  └─────────────────┘                                          │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│                     DispatcherServlet                          │
│  ┌─────────────────┐                                          │
│  │ 1. HandlerMapping│ → 找到处理请求的 Controller 方法           │
│  └────────┬────────┘                                          │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 2. HandlerAdapter│ → 调用 Controller 方法(参数绑定、类型转换) │
│  └────────┬────────┘                                          │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 3. Controller    │ → 执行业务逻辑,返回 ModelAndView/数据      │
│  └────────┬────────┘                                          │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 4. ViewResolver  │ → 解析视图名 → 渲染 HTML                   │
│  └────────┬────────┘  (前后端分离项目中,返回 JSON 跳过此步)      │
│           ▼                                                    │
│  ┌─────────────────┐                                          │
│  │ 5. 拦截器后处理   │ → afterCompletion 等                      │
│  └─────────────────┘                                          │
└────────────────────────────────────────────────────────────────┘

关键组件:

  • HandlerMapping:根据请求 URL 找到对应的 Handler(Controller 方法)
  • HandlerAdapter:执行 Handler,处理参数绑定、类型转换、数据校验
  • HandlerInterceptor:拦截器,前置/后置处理(登录校验、日志等)
  • ViewResolver:解析视图名到具体视图(前后端分离项目中通常不涉及)
  • HttpMessageConverter:将 Controller 返回值转换为 JSON/XML(@ResponseBody)

面试回答模板:

请求先到达 DispatcherServlet,它通过 HandlerMapping 找到对应的 Controller 方法,HandlerAdapter 负责调用并处理参数绑定,执行完后如果有 @ResponseBody 则通过 HttpMessageConverter 把返回值转成 JSON 写回客户端。

2.4 Bean 完整生命周期(面试高频) ​

┌─────────────────────────────────────────────────────────────────┐
│                    Spring Bean 生命周期                           │
│                                                                  │
│  1. 实例化(Instantiation)                                       │
│     └→ 通过构造方法或工厂方法创建 Bean 实例                          │
│                                                                  │
│  2. 属性填充(Populate Properties)                               │
│     └→ 注入 @Autowired 依赖、@Value 值                            │
│                                                                  │
│  3. Aware 回调                                                   │
│     └→ BeanNameAware.setBeanName()                               │
│     └→ BeanFactoryAware.setBeanFactory()                         │
│     └→ ApplicationContextAware.setApplicationContext()            │
│                                                                  │
│  4. BeanPostProcessor 前置处理                                    │
│     └→ postProcessBeforeInitialization()                         │
│                                                                  │
│  5. 初始化(Initialization)                                      │
│     └→ @PostConstruct 方法                                       │
│     └→ InitializingBean.afterPropertiesSet()                     │
│     └→ init-method(XML 配置)                                    │
│                                                                  │
│  6. BeanPostProcessor 后置处理                                    │
│     └→ postProcessAfterInitialization()  ← AOP 代理在此生成!      │
│                                                                  │
│  7. Bean 就绪(Ready)                                            │
│     └→ 可以被使用                                                 │
│                                                                  │
│  8. 销毁(Destruction)                                           │
│     └→ @PreDestroy 方法                                          │
│     └→ DisposableBean.destroy()                                  │
│     └→ destroy-method(XML 配置)                                 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                    Spring Bean 生命周期                           │
│                                                                  │
│  1. 实例化(Instantiation)                                       │
│     └→ 通过构造方法或工厂方法创建 Bean 实例                          │
│                                                                  │
│  2. 属性填充(Populate Properties)                               │
│     └→ 注入 @Autowired 依赖、@Value 值                            │
│                                                                  │
│  3. Aware 回调                                                   │
│     └→ BeanNameAware.setBeanName()                               │
│     └→ BeanFactoryAware.setBeanFactory()                         │
│     └→ ApplicationContextAware.setApplicationContext()            │
│                                                                  │
│  4. BeanPostProcessor 前置处理                                    │
│     └→ postProcessBeforeInitialization()                         │
│                                                                  │
│  5. 初始化(Initialization)                                      │
│     └→ @PostConstruct 方法                                       │
│     └→ InitializingBean.afterPropertiesSet()                     │
│     └→ init-method(XML 配置)                                    │
│                                                                  │
│  6. BeanPostProcessor 后置处理                                    │
│     └→ postProcessAfterInitialization()  ← AOP 代理在此生成!      │
│                                                                  │
│  7. Bean 就绪(Ready)                                            │
│     └→ 可以被使用                                                 │
│                                                                  │
│  8. 销毁(Destruction)                                           │
│     └→ @PreDestroy 方法                                          │
│     └→ DisposableBean.destroy()                                  │
│     └→ destroy-method(XML 配置)                                 │
└─────────────────────────────────────────────────────────────────┘

关键面试点:

  • AOP 代理对象是在 postProcessAfterInitialization 阶段通过 BeanPostProcessor 生成的
  • 先执行 @PostConstruct,再执行 InitializingBean.afterPropertiesSet()
  • 依赖注入(@Autowired)发生在属性填充阶段,在初始化之前

2.5 SpringBoot 自动配置原理(面试高频) ​

核心注解链:

@SpringBootApplication
  └─ @EnableAutoConfiguration
       └─ @Import(AutoConfigurationImportSelector.class)
            └─ 读取 spring-boot-autoconfigure.jar/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)
               或 spring.factories(Spring Boot 2.x)
            └─ 加载所有自动配置类
@SpringBootApplication
  └─ @EnableAutoConfiguration
       └─ @Import(AutoConfigurationImportSelector.class)
            └─ 读取 spring-boot-autoconfigure.jar/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)
               或 spring.factories(Spring Boot 2.x)
            └─ 加载所有自动配置类

条件注解过滤: 加载后会根据 @ConditionalOnXxx 决定是否生效:

条件注解生效条件
@ConditionalOnClassclasspath 中存在指定类
@ConditionalOnMissingBean容器中没有指定 Bean
@ConditionalOnProperty配置文件中有指定属性
@ConditionalOnBean容器中存在指定 Bean
@ConditionalOnMissingClassclasspath 中不存在指定类
@ConditionalOnWebApplication当前是 Web 应用

以 DataSource 自动配置为例:

java
@AutoConfiguration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean  // 用户自己配了就不创建
    public DataSource dataSource(DataSourceProperties properties) {
        // 创建 HikariCP 连接池
        return properties.initializeDataSourceBuilder().build();
    }
}
@AutoConfiguration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean  // 用户自己配了就不创建
    public DataSource dataSource(DataSourceProperties properties) {
        // 创建 HikariCP 连接池
        return properties.initializeDataSourceBuilder().build();
    }
}

面试回答模板:

@SpringBootApplication 包含 @EnableAutoConfiguration,它通过 AutoConfigurationImportSelector 读取 spring-boot-autoconfigure 包中的自动配置类列表,然后根据 @ConditionalOnXxx 条件注解判断哪些配置生效。用户自定义的 Bean 优先于自动配置(@ConditionalOnMissingBean)。

3. Spring Security 6 ​

3.1 Spring Security 概述 ​

Spring Security 是 Spring 框架的安全模块,用于提供身份验证、授权和其他安全功能。

3.2 Spring Security 6 ​

Spring Security 6 是 Spring Security 的最新版本,于 2022 年 11 月发布。

新特性:

  • 基于 Java 17+ 构建
  • 支持 Jakarta EE 10
  • 简化的配置
  • 改进的 OAuth 2.0 支持
  • 原生镜像支持
  • 改进的测试支持

示例:

1. 添加依赖

在 pom.xml 中添加:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

2. 安全配置

java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // 开发环境禁用 CSRF
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/h2-console/**").permitAll() // 允许访问 H2 控制台
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .permitAll()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                )
            );
        
        // 允许 H2 控制台使用 iframe
        http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()));
        
        return http.build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        // 配置 JWT 解码器
        return NimbusJwtDecoder.withPublicKey(rsaPublicKey()).build();
    }
    
    @Bean
    public RSAPublicKey rsaPublicKey() {
        // 加载公钥
        // 这里简化处理,实际应从配置或密钥库加载
        return (RSAPublicKey) keyPair().getPublic();
    }
    
    @Bean
    public KeyPair keyPair() {
        // 生成密钥对(实际应用中应使用已有的密钥)
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        return keyPairGenerator.generateKeyPair();
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        // 内存用户存储
        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("password"))
            .roles("USER")
            .build();
        
        UserDetails admin = User.builder()
            .username("admin")
            .password(passwordEncoder().encode("admin123"))
            .roles("ADMIN")
            .build();
        
        return new InMemoryUserDetailsManager(user, admin);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // 开发环境禁用 CSRF
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/h2-console/**").permitAll() // 允许访问 H2 控制台
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .permitAll()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                )
            );
        
        // 允许 H2 控制台使用 iframe
        http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()));
        
        return http.build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        // 配置 JWT 解码器
        return NimbusJwtDecoder.withPublicKey(rsaPublicKey()).build();
    }
    
    @Bean
    public RSAPublicKey rsaPublicKey() {
        // 加载公钥
        // 这里简化处理,实际应从配置或密钥库加载
        return (RSAPublicKey) keyPair().getPublic();
    }
    
    @Bean
    public KeyPair keyPair() {
        // 生成密钥对(实际应用中应使用已有的密钥)
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        return keyPairGenerator.generateKeyPair();
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        // 内存用户存储
        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("password"))
            .roles("USER")
            .build();
        
        UserDetails admin = User.builder()
            .username("admin")
            .password(passwordEncoder().encode("admin123"))
            .roles("ADMIN")
            .build();
        
        return new InMemoryUserDetailsManager(user, admin);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3. 控制器

java
@RestController
@RequestMapping("/api")
public class ApiController {
    
    @GetMapping("/public/hello")
    public String publicHello() {
        return "Hello from public endpoint!";
    }
    
    @GetMapping("/user/hello")
    public String userHello() {
        return "Hello from user endpoint!";
    }
    
    @GetMapping("/admin/hello")
    public String adminHello() {
        return "Hello from admin endpoint!";
    }
}
@RestController
@RequestMapping("/api")
public class ApiController {
    
    @GetMapping("/public/hello")
    public String publicHello() {
        return "Hello from public endpoint!";
    }
    
    @GetMapping("/user/hello")
    public String userHello() {
        return "Hello from user endpoint!";
    }
    
    @GetMapping("/admin/hello")
    public String adminHello() {
        return "Hello from admin endpoint!";
    }
}

4. Jakarta EE 10 ​

4.1 Jakarta EE 概述 ​

Jakarta EE 是 Java EE 的继任者,是一个用于构建企业级应用的开源平台。

4.2 Jakarta EE 10 ​

Jakarta EE 10 是 Jakarta EE 的最新版本,于 2022 年 9 月发布。

核心规范:

  • Jakarta Servlet 6.0
  • Jakarta JSP 3.1
  • Jakarta EL 5.0
  • Jakarta CDI 4.0
  • Jakarta EJB 4.0
  • Jakarta JPA 3.1
  • Jakarta RESTful Web Services 3.1
  • Jakarta Security 3.0
  • Jakarta Bean Validation 3.0
  • Jakarta Persistence 3.1
  • Jakarta WebSocket 2.1

示例:

1. 创建 Jakarta EE 项目

使用 Maven 创建 Jakarta EE 10 项目:

xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>jakartaee-example</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>
    
    <properties>
        <jakartaee.version>10.0.0</jakartaee.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakartaee.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>jakartaee-example</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>
    
    <properties>
        <jakartaee.version>10.0.0</jakartaee.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakartaee.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>

2. REST 资源

java
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
    
    @Inject
    private UserService userService;
    
    @GET
    @Path("/{id}")
    public Response getUserById(@PathParam("id") Long id) {
        User user = userService.getUserById(id);
        return Response.ok(user).build();
    }
    
    @GET
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @POST
    public Response createUser(User user) {
        User createdUser = userService.createUser(user);
        return Response.status(Response.Status.CREATED)
            .header("Location", "/api/users/" + createdUser.getId())
            .entity(createdUser)
            .build();
    }
    
    @PUT
    @Path("/{id}")
    public Response updateUser(@PathParam("id") Long id, User user) {
        User updatedUser = userService.updateUser(id, user);
        return Response.ok(updatedUser).build();
    }
    
    @DELETE
    @Path("/{id}")
    public Response deleteUser(@PathParam("id") Long id) {
        userService.deleteUser(id);
        return Response.noContent().build();
    }
}
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
    
    @Inject
    private UserService userService;
    
    @GET
    @Path("/{id}")
    public Response getUserById(@PathParam("id") Long id) {
        User user = userService.getUserById(id);
        return Response.ok(user).build();
    }
    
    @GET
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @POST
    public Response createUser(User user) {
        User createdUser = userService.createUser(user);
        return Response.status(Response.Status.CREATED)
            .header("Location", "/api/users/" + createdUser.getId())
            .entity(createdUser)
            .build();
    }
    
    @PUT
    @Path("/{id}")
    public Response updateUser(@PathParam("id") Long id, User user) {
        User updatedUser = userService.updateUser(id, user);
        return Response.ok(updatedUser).build();
    }
    
    @DELETE
    @Path("/{id}")
    public Response deleteUser(@PathParam("id") Long id) {
        userService.deleteUser(id);
        return Response.noContent().build();
    }
}

3. CDI 服务

java
@ApplicationScoped
public class UserServiceImpl implements UserService {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public User getUserById(Long id) {
        User user = entityManager.find(User.class, id);
        if (user == null) {
            throw new WebApplicationException("User not found with id: " + id, Response.Status.NOT_FOUND);
        }
        return user;
    }
    
    @Override
    public List<User> getAllUsers() {
        TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u", User.class);
        return query.getResultList();
    }
    
    @Override
    @Transactional
    public User createUser(User user) {
        entityManager.persist(user);
        return user;
    }
    
    @Override
    @Transactional
    public User updateUser(Long id, User user) {
        User existingUser = getUserById(id);
        existingUser.setFirstName(user.getFirstName());
        existingUser.setLastName(user.getLastName());
        existingUser.setEmail(user.getEmail());
        return existingUser;
    }
    
    @Override
    @Transactional
    public void deleteUser(Long id) {
        User user = getUserById(id);
        entityManager.remove(user);
    }
}
@ApplicationScoped
public class UserServiceImpl implements UserService {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public User getUserById(Long id) {
        User user = entityManager.find(User.class, id);
        if (user == null) {
            throw new WebApplicationException("User not found with id: " + id, Response.Status.NOT_FOUND);
        }
        return user;
    }
    
    @Override
    public List<User> getAllUsers() {
        TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u", User.class);
        return query.getResultList();
    }
    
    @Override
    @Transactional
    public User createUser(User user) {
        entityManager.persist(user);
        return user;
    }
    
    @Override
    @Transactional
    public User updateUser(Long id, User user) {
        User existingUser = getUserById(id);
        existingUser.setFirstName(user.getFirstName());
        existingUser.setLastName(user.getLastName());
        existingUser.setEmail(user.getEmail());
        return existingUser;
    }
    
    @Override
    @Transactional
    public void deleteUser(Long id) {
        User user = getUserById(id);
        entityManager.remove(user);
    }
}

4. 实体类

java
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "first_name", nullable = false)
    private String firstName;
    
    @Column(name = "last_name", nullable = false)
    private String lastName;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    // 构造函数、getter、setter
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "first_name", nullable = false)
    private String firstName;
    
    @Column(name = "last_name", nullable = false)
    private String lastName;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    // 构造函数、getter、setter
}

5. Spring Data ​

5.1 Spring Data 概述 ​

Spring Data 是 Spring 框架的一个子项目,用于简化数据访问层的开发。

核心模块:

  • Spring Data JPA
  • Spring Data MongoDB
  • Spring Data Redis
  • Spring Data Elasticsearch
  • Spring Data Cassandra
  • Spring Data REST

5.2 Spring Data JPA ​

Spring Data JPA 简化了 JPA 的使用,提供了自动生成查询方法的功能。

示例:

1. 自定义查询方法

java
public interface UserRepository extends JpaRepository<User, Long> {
    // 自动生成查询:SELECT u FROM User u WHERE u.lastName = ?1
    List<User> findByLastName(String lastName);
    
    // 自动生成查询:SELECT u FROM User u WHERE u.firstName = ?1 AND u.lastName = ?2
    List<User> findByFirstNameAndLastName(String firstName, String lastName);
    
    // 自动生成查询:SELECT u FROM User u WHERE u.email LIKE %?1%
    List<User> findByEmailContaining(String email);
    
    // 自动生成查询:SELECT u FROM User u WHERE u.age > ?1
    List<User> findByAgeGreaterThan(int age);
    
    // 排序
    List<User> findByLastNameOrderByFirstNameAsc(String lastName);
    
    // 分页
    Page<User> findByLastName(String lastName, Pageable pageable);
    
    // 限制结果数量
    List<User> findTop5ByOrderByAgeDesc();
    
    // 使用 @Query 注解
    @Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
    List<User> findByEmailLike(@Param("email") String email);
    
    // 使用原生 SQL
    @Query(value = "SELECT * FROM users WHERE email LIKE %:email%", nativeQuery = true)
    List<User> findByEmailNative(@Param("email") String email);
    
    // 更新操作
    @Modifying
    @Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
    int updateEmail(@Param("id") Long id, @Param("email") String email);
}
public interface UserRepository extends JpaRepository<User, Long> {
    // 自动生成查询:SELECT u FROM User u WHERE u.lastName = ?1
    List<User> findByLastName(String lastName);
    
    // 自动生成查询:SELECT u FROM User u WHERE u.firstName = ?1 AND u.lastName = ?2
    List<User> findByFirstNameAndLastName(String firstName, String lastName);
    
    // 自动生成查询:SELECT u FROM User u WHERE u.email LIKE %?1%
    List<User> findByEmailContaining(String email);
    
    // 自动生成查询:SELECT u FROM User u WHERE u.age > ?1
    List<User> findByAgeGreaterThan(int age);
    
    // 排序
    List<User> findByLastNameOrderByFirstNameAsc(String lastName);
    
    // 分页
    Page<User> findByLastName(String lastName, Pageable pageable);
    
    // 限制结果数量
    List<User> findTop5ByOrderByAgeDesc();
    
    // 使用 @Query 注解
    @Query("SELECT u FROM User u WHERE u.email LIKE %:email%")
    List<User> findByEmailLike(@Param("email") String email);
    
    // 使用原生 SQL
    @Query(value = "SELECT * FROM users WHERE email LIKE %:email%", nativeQuery = true)
    List<User> findByEmailNative(@Param("email") String email);
    
    // 更新操作
    @Modifying
    @Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
    int updateEmail(@Param("id") Long id, @Param("email") String email);
}

2. 分页和排序

java
// 分页和排序
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt"));
Page<User> users = userRepository.findAll(pageable);

// 获取分页信息
int totalPages = users.getTotalPages();
long totalElements = users.getTotalElements();
List<User> content = users.getContent();
boolean hasNext = users.hasNext();
boolean hasPrevious = users.hasPrevious();
// 分页和排序
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt"));
Page<User> users = userRepository.findAll(pageable);

// 获取分页信息
int totalPages = users.getTotalPages();
long totalElements = users.getTotalElements();
List<User> content = users.getContent();
boolean hasNext = users.hasNext();
boolean hasPrevious = users.hasPrevious();

3. 条件查询

java
// 使用 Example 进行条件查询
ExampleMatcher matcher = ExampleMatcher.matching()
    .withIgnoreCase()
    .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);

User example = new User();
example.setLastName("Smith");
Example<User> userExample = Example.of(example, matcher);
List<User> users = userRepository.findAll(userExample);
// 使用 Example 进行条件查询
ExampleMatcher matcher = ExampleMatcher.matching()
    .withIgnoreCase()
    .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);

User example = new User();
example.setLastName("Smith");
Example<User> userExample = Example.of(example, matcher);
List<User> users = userRepository.findAll(userExample);

6. 响应式编程 ​

6.1 什么是响应式编程? ​

响应式编程是一种面向数据流和变化传播的编程范式,用于处理异步数据流。

6.2 Spring WebFlux ​

Spring WebFlux 是 Spring 框架的响应式 Web 框架,基于 Reactor 库构建。

示例:

1. 添加依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
</dependency>

2. 配置

application.properties:

properties
spring.r2dbc.url=r2dbc:h2:mem:///testdb?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.r2dbc.username=sa
spring.r2dbc.password=
spring.sql.init.mode=always
spring.r2dbc.url=r2dbc:h2:mem:///testdb?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.r2dbc.username=sa
spring.r2dbc.password=
spring.sql.init.mode=always

3. 实体类

java
@Table("users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    // 构造函数、getter、setter
}
@Table("users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    // 构造函数、getter、setter
}

4. 仓库接口

java
public interface UserRepository extends ReactiveCrudRepository<User, Long> {
    Flux<User> findByLastName(String lastName);
}
public interface UserRepository extends ReactiveCrudRepository<User, Long> {
    Flux<User> findByLastName(String lastName);
}

5. 控制器

java
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserRepository userRepository;
    
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<User>> getUserById(@PathVariable Long id) {
        return userRepository.findById(id)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public Flux<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    @PostMapping
    public Mono<ResponseEntity<User>> createUser(@RequestBody User user) {
        return userRepository.save(user)
            .map(u -> ResponseEntity.status(HttpStatus.CREATED).body(u));
    }
    
    @PutMapping("/{id}")
    public Mono<ResponseEntity<User>> updateUser(@PathVariable Long id, @RequestBody User user) {
        return userRepository.findById(id)
            .flatMap(existingUser -> {
                existingUser.setFirstName(user.getFirstName());
                existingUser.setLastName(user.getLastName());
                existingUser.setEmail(user.getEmail());
                return userRepository.save(existingUser);
            })
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> deleteUser(@PathVariable Long id) {
        return userRepository.findById(id)
            .flatMap(existingUser -> userRepository.delete(existingUser)
                .then(Mono.just(ResponseEntity.noContent().build())))
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
}
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserRepository userRepository;
    
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<User>> getUserById(@PathVariable Long id) {
        return userRepository.findById(id)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public Flux<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    @PostMapping
    public Mono<ResponseEntity<User>> createUser(@RequestBody User user) {
        return userRepository.save(user)
            .map(u -> ResponseEntity.status(HttpStatus.CREATED).body(u));
    }
    
    @PutMapping("/{id}")
    public Mono<ResponseEntity<User>> updateUser(@PathVariable Long id, @RequestBody User user) {
        return userRepository.findById(id)
            .flatMap(existingUser -> {
                existingUser.setFirstName(user.getFirstName());
                existingUser.setLastName(user.getLastName());
                existingUser.setEmail(user.getEmail());
                return userRepository.save(existingUser);
            })
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> deleteUser(@PathVariable Long id) {
        return userRepository.findById(id)
            .flatMap(existingUser -> userRepository.delete(existingUser)
                .then(Mono.just(ResponseEntity.noContent().build())))
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
}

7. 工程实践专题(参数校验 / 鉴权 / 缓存 / 日志 / SSE) ​

7.1 参数校验 @Valid / @Validated(面试高频) ​

Spring Boot 集成 Bean Validation(JSR-380),配合 Hibernate Validator 实现参数校验。

基础用法:

java
@Data
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Min(value = 18, message = "年龄不能小于18")
    @Max(value = 120, message = "年龄不能大于120")
    private Integer age;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
}

@RestController
public class UserController {

    @PostMapping("/users")
    public Result createUser(@Valid @RequestBody UserDTO dto) {
        // @Valid 校验失败会抛出 MethodArgumentNotValidException
        return Result.success(userService.create(dto));
    }
}
@Data
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Min(value = 18, message = "年龄不能小于18")
    @Max(value = 120, message = "年龄不能大于120")
    private Integer age;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
}

@RestController
public class UserController {

    @PostMapping("/users")
    public Result createUser(@Valid @RequestBody UserDTO dto) {
        // @Valid 校验失败会抛出 MethodArgumentNotValidException
        return Result.success(userService.create(dto));
    }
}

分组校验:

java
// 定义分组接口
public interface Create {}
public interface Update {}

@Data
public class UserDTO {
    @NotNull(groups = Update.class)  // 只在更新时校验
    private Long id;

    @NotBlank(groups = {Create.class, Update.class})
    private String username;
}

// 使用 @Validated 指定分组
@PostMapping("/users")
public Result create(@Validated(Create.class) @RequestBody UserDTO dto) { }

@PutMapping("/users")
public Result update(@Validated(Update.class) @RequestBody UserDTO dto) { }
// 定义分组接口
public interface Create {}
public interface Update {}

@Data
public class UserDTO {
    @NotNull(groups = Update.class)  // 只在更新时校验
    private Long id;

    @NotBlank(groups = {Create.class, Update.class})
    private String username;
}

// 使用 @Validated 指定分组
@PostMapping("/users")
public Result create(@Validated(Create.class) @RequestBody UserDTO dto) { }

@PutMapping("/users")
public Result update(@Validated(Update.class) @RequestBody UserDTO dto) { }

全局异常处理:

java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.joining(", "));
        return Result.error(msg);
    }
}
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.joining(", "));
        return Result.error(msg);
    }
}

@Valid 与 @Validated 区别:

对比@Valid@Validated
来源JSR-380(javax/jakarta.validation)Spring 框架
分组校验不支持支持
嵌套校验支持不支持
使用场景Controller 层Service 层方法参数

常用校验注解:

注解说明
@NotNull不为 null
@NotBlank不为 null 且不为空字符串
@NotEmpty不为 null 且 size > 0(集合/字符串)
@Size(min, max)字符串/集合/数组长度范围
@Min / @Max数值最小值/最大值
@Email邮箱格式
@Pattern正则匹配

7.2 Cookie、Session、JWT 对比(面试高频) ​

维度CookieSessionJWT
存储位置浏览器服务端(内存/Redis)客户端
安全性可被篡改,需配合 HttpOnly相对安全(服务端存)无状态,签名防篡改
跨域受同源策略限制可配合 Cookie天然支持跨域
扩展性无状态需要 Session 共享(Redis)无状态,天然分布式
性能不占用服务端服务端占用内存不占用服务端
注销清除 Cookie删除 Session无法主动失效(需配合黑名单)
适用场景传统 Web 应用单体应用前后端分离、微服务、移动端

JWT 结构: Header.Payload.Signature

java
// 生成 JWT
String token = Jwts.builder()
    .setSubject(userId.toString())
    .claim("role", "admin")
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24h
    .signWith(secretKey)
    .compact();

// 验证 JWT
Claims claims = Jwts.parserBuilder()
    .setSigningKey(secretKey)
    .build()
    .parseClaimsJws(token)
    .getBody();
// 生成 JWT
String token = Jwts.builder()
    .setSubject(userId.toString())
    .claim("role", "admin")
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24h
    .signWith(secretKey)
    .compact();

// 验证 JWT
Claims claims = Jwts.parserBuilder()
    .setSigningKey(secretKey)
    .build()
    .parseClaimsJws(token)
    .getBody();

JWT 缺点:

  • token 签发后无法主动注销(需配合 Redis 黑名单)
  • payload 只 Base64 编码,不加密(不要放敏感信息)
  • token 越长,每次请求的带宽开销越大

面试回答模板:

Cookie 存在浏览器,Session 存在服务端,JWT 是无状态的 Token。前后端分离项目推荐 JWT(无状态、跨域方便),但要注意 JWT 无法主动注销,需要配合 Redis 黑名单或短过期时间 + refresh token 方案。


7.3 Spring Cache 抽象(@Cacheable / @CacheEvict) ​

Spring 提供声明式缓存抽象,支持 Redis、Caffeine、EhCache 等多种缓存后端。

java
@Service
public class UserService {

    // 查询缓存:缓存中有就直接返回,没有就执行方法并存入缓存
    @Cacheable(value = "user", key = "#id", unless = "#result == null")
    public User getById(Long id) {
        return userMapper.selectById(id);
    }

    // 更新后清除缓存
    @CacheEvict(value = "user", key = "#user.id")
    public User update(User user) {
        userMapper.updateById(user);
        return user;
    }

    // 每次方法执行前清除缓存(与 @Cacheable 区别:始终执行方法)
    @CachePut(value = "user", key = "#result.id")
    public User create(User user) {
        userMapper.insert(user);
        return user;
    }

    // 清除所有 user 缓存
    @CacheEvict(value = "user", allEntries = true)
    public void delete(Long id) {
        userMapper.deleteById(id);
    }
}
@Service
public class UserService {

    // 查询缓存:缓存中有就直接返回,没有就执行方法并存入缓存
    @Cacheable(value = "user", key = "#id", unless = "#result == null")
    public User getById(Long id) {
        return userMapper.selectById(id);
    }

    // 更新后清除缓存
    @CacheEvict(value = "user", key = "#user.id")
    public User update(User user) {
        userMapper.updateById(user);
        return user;
    }

    // 每次方法执行前清除缓存(与 @Cacheable 区别:始终执行方法)
    @CachePut(value = "user", key = "#result.id")
    public User create(User user) {
        userMapper.insert(user);
        return user;
    }

    // 清除所有 user 缓存
    @CacheEvict(value = "user", allEntries = true)
    public void delete(Long id) {
        userMapper.deleteById(id);
    }
}

常用注解:

注解说明
@Cacheable先查缓存,有则返回,无则执行方法并缓存结果
@CacheEvict清除缓存
@CachePut始终执行方法,结果存入缓存
@Caching组合多个缓存操作

常用 SpEL 表达式:

表达式说明
#id方法参数 id
#user.id参数 user 对象的 id 属性
#result方法返回值
#root.methodName方法名

配置 Redis 作为缓存后端:

yaml
spring:
  cache:
    type: redis
    redis:
      time-to-live: 3600000  # 默认过期时间(毫秒)
      cache-null-values: false  # 是否缓存 null 值(防止缓存穿透建议开启)
spring:
  cache:
    type: redis
    redis:
      time-to-live: 3600000  # 默认过期时间(毫秒)
      cache-null-values: false  # 是否缓存 null 值(防止缓存穿透建议开启)

面试回答模板:

@Cacheable 先查缓存,缓存命中直接返回;不命中执行方法,结果自动缓存。@CacheEvict 用于更新/删除时清除缓存。配合 Redis 作为缓存后端,还可以设置过期时间兜底,防止缓存穿透可以配置 cache-null-values=true。


7.4 日志体系 SLF4J + Logback(面试高频) ​

SLF4J 是门面(Facade),Logback 是默认实现。Spring Boot 内置了 spring-boot-starter-logging。

日志级别(从低到高): TRACE < DEBUG < INFO < WARN < ERROR

配置示例(application.yml):

yaml
logging:
  level:
    root: INFO
    com.example: DEBUG           # 自己的包打 DEBUG
    com.example.mapper: WARN     # MyBatis Mapper 只打 WARN
  file:
    name: logs/app.log           # 日志文件路径
  logback:
    rollingpolicy:
      max-file-size: 10MB        # 单个文件最大 10MB
      max-history: 30            # 保留 30 天
logging:
  level:
    root: INFO
    com.example: DEBUG           # 自己的包打 DEBUG
    com.example.mapper: WARN     # MyBatis Mapper 只打 WARN
  file:
    name: logs/app.log           # 日志文件路径
  logback:
    rollingpolicy:
      max-file-size: 10MB        # 单个文件最大 10MB
      max-history: 30            # 保留 30 天

代码中使用(推荐用 Lombok @Slf4j):

java
@Slf4j
@Service
public class UserService {

    public User getById(Long id) {
        log.info("查询用户: id={}", id);    // 占位符方式,避免字符串拼接开销
        User user = userMapper.selectById(id);
        if (user == null) {
            log.warn("用户不存在: id={}", id);
        }
        return user;
    }
}
@Slf4j
@Service
public class UserService {

    public User getById(Long id) {
        log.info("查询用户: id={}", id);    // 占位符方式,避免字符串拼接开销
        User user = userMapper.selectById(id);
        if (user == null) {
            log.warn("用户不存在: id={}", id);
        }
        return user;
    }
}

为什么不直接拼接字符串?

java
// 反例:即使 debug 不输出,也会执行字符串拼接
log.debug("查询用户: " + id + ", 姓名: " + name);

// 正例:占位符方式,debug 不输出时不会拼接
log.debug("查询用户: id={}, 姓名={}", id, name);
// 反例:即使 debug 不输出,也会执行字符串拼接
log.debug("查询用户: " + id + ", 姓名: " + name);

// 正例:占位符方式,debug 不输出时不会拼接
log.debug("查询用户: id={}, 姓名={}", id, name);

MDC 链路追踪(StackTrace 排查利器):

java
// 拦截器中设置 traceId
MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));

// logback-spring.xml 中配置 pattern 包含 traceId 和请求耗时
// %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - [%X{traceId}] - %msg%n
// 拦截器中设置 traceId
MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));

// logback-spring.xml 中配置 pattern 包含 traceId 和请求耗时
// %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - [%X{traceId}] - %msg%n

面试回答模板:

SLF4J 是门面,Logback 是默认实现。用占位符 {} 代替字符串拼接,避免不必要的性能开销。排查问题时通过 MDC 设置 traceId 贯穿整个请求链路,在日志中直接搜索 traceId 就能看到完整调用链。


7.5 SSE 原理(Server-Sent Events) ​

SSE 是 HTTP 长连接技术,服务端向客户端单向推送数据。你的心理健康 AI 助手使用 SSE 实现流式对话。

核心原理:

客户端                       服务端
  │                            │
  │  GET /api/chat/stream      │
  │  Accept: text/event-stream │
  │ ──────────────────────────>│
  │                            │
  │  Content-Type: text/event-stream
  │  Cache-Control: no-cache
  │  Connection: keep-alive
  │ <────────────────────────── │
  │                            │
  │  data: {"chunk":"你"}       │
  │ <────────────────────────── │
  │                            │
  │  data: {"chunk":"好"}       │
  │ <────────────────────────── │
  │                            │
  │  data: [DONE]              │
  │ <────────────────────────── │
客户端                       服务端
  │                            │
  │  GET /api/chat/stream      │
  │  Accept: text/event-stream │
  │ ──────────────────────────>│
  │                            │
  │  Content-Type: text/event-stream
  │  Cache-Control: no-cache
  │  Connection: keep-alive
  │ <────────────────────────── │
  │                            │
  │  data: {"chunk":"你"}       │
  │ <────────────────────────── │
  │                            │
  │  data: {"chunk":"好"}       │
  │ <────────────────────────── │
  │                            │
  │  data: [DONE]              │
  │ <────────────────────────── │

SSE 消息格式:

data: 消息内容
id: 消息ID(可选,断线重连时从 Last-Event-ID 续传)
event: 事件类型(可选,默认 message)
retry: 重连间隔(毫秒,可选)

data: {"chunk": "你好"}
data: {"chunk": ",我是AI助手"}

data: [DONE]
data: 消息内容
id: 消息ID(可选,断线重连时从 Last-Event-ID 续传)
event: 事件类型(可选,默认 message)
retry: 重连间隔(毫秒,可选)

data: {"chunk": "你好"}
data: {"chunk": ",我是AI助手"}

data: [DONE]

Spring Boot 实现(SseEmitter):

java
@RestController
public class ChatController {

    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter chat(@RequestParam String message) {
        SseEmitter emitter = new SseEmitter(60000L); // 60 秒超时
        // 异步执行,避免阻塞主线程
        CompletableFuture.runAsync(() -> {
            try {
                for (String chunk : aiService.chat(message)) {
                    emitter.send(SseEmitter.event().data(chunk));
                }
                emitter.send(SseEmitter.event().data("[DONE]"));
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}
@RestController
public class ChatController {

    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter chat(@RequestParam String message) {
        SseEmitter emitter = new SseEmitter(60000L); // 60 秒超时
        // 异步执行,避免阻塞主线程
        CompletableFuture.runAsync(() -> {
            try {
                for (String chunk : aiService.chat(message)) {
                    emitter.send(SseEmitter.event().data(chunk));
                }
                emitter.send(SseEmitter.event().data("[DONE]"));
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

Nginx 配置要点(SSE 必须配置):

nginx
location /chat/stream {
    proxy_pass http://backend;
    proxy_buffering off;           # 关闭缓冲,否则数据会积压
    proxy_cache off;               # 关闭缓存
    proxy_read_timeout 3600s;      # 延长超时,防止长连接被断开
    proxy_set_header Connection '';
    proxy_http_version 1.1;
    chunked_transfer_encoding on;
}
location /chat/stream {
    proxy_pass http://backend;
    proxy_buffering off;           # 关闭缓冲,否则数据会积压
    proxy_cache off;               # 关闭缓存
    proxy_read_timeout 3600s;      # 延长超时,防止长连接被断开
    proxy_set_header Connection '';
    proxy_http_version 1.1;
    chunked_transfer_encoding on;
}

SSE vs WebSocket 对比:

维度SSEWebSocket
通信方向单向(服务端 → 客户端)双向
协议HTTP独立协议 ws://
断线重连浏览器内置需手动实现
复杂度低高
适用场景流式推送、通知、AI 对话即时通讯、游戏

面试回答模板:

SSE 基于 HTTP 长连接,客户端发起 GET 请求后服务端持续推送数据。我用 SseEmitter 实现流式 AI 对话,重点注意 Nginx 要关闭 proxy_buffering 否则数据会积压,延长 proxy_read_timeout 防止长连接超时断开。


8. Spring AI(大模型接入) ​

8.1 为什么后端要自己接模型 ​

前端直连模型有三个硬伤:API Key 泄露、无法管控成本、业务数据出不了内网。所以标准做法是后端做一层「模型网关」:

浏览器 ──SSE──> 自己的服务 ──HTTP──> 模型服务(OpenAI / 通义 / 硅基流动 / 本地 vLLM)
                   │
                   ├── 鉴权 & 配额(谁能用、用多少)
                   ├── Prompt 组装(系统提示词、RAG 片段、历史消息)
                   ├── 工具调用(查库、查订单、调内部 API)
                   ├── 流式转发(SSE 逐 token 推给前端)
                   └── 兜底(超时、限流、内容安全、降级话术)
浏览器 ──SSE──> 自己的服务 ──HTTP──> 模型服务(OpenAI / 通义 / 硅基流动 / 本地 vLLM)
                   │
                   ├── 鉴权 & 配额(谁能用、用多少)
                   ├── Prompt 组装(系统提示词、RAG 片段、历史消息)
                   ├── 工具调用(查库、查订单、调内部 API)
                   ├── 流式转发(SSE 逐 token 推给前端)
                   └── 兜底(超时、限流、内容安全、降级话术)

8.2 依赖与配置 ​

xml
<!-- 1.0 之后的命名;1.0 之前是 spring-ai-openai-spring-boot-starter -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 1.0 之后的命名;1.0 之前是 spring-ai-openai-spring-boot-starter -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
yaml
spring:
  ai:
    openai:
      base-url: https://api.siliconflow.cn      # 换成任意 OpenAI 兼容网关
      api-key: ${AI_API_KEY}                    # 绝不写进代码和仓库
      chat:
        options:
          model: Qwen/Qwen2.5-72B-Instruct
          temperature: 0.7
          max-tokens: 2048
spring:
  ai:
    openai:
      base-url: https://api.siliconflow.cn      # 换成任意 OpenAI 兼容网关
      api-key: ${AI_API_KEY}                    # 绝不写进代码和仓库
      chat:
        options:
          model: Qwen/Qwen2.5-72B-Instruct
          temperature: 0.7
          max-tokens: 2048

8.3 核心抽象 ​

抽象作用
ChatModel最底层,一次对话调用
ChatClient门面(类似 RestTemplate 之于 HttpClient),链式 API,日常只用它
Prompt / MessageSystem / User / Assistant / Tool 四种消息
Advisor切面式拦截器:记忆、RAG、日志、内容审核都挂在这里
ChatMemory多轮上下文,默认 MessageWindowChatMemory(保留最近 N 条)
VectorStore向量库抽象:SimpleVectorStore(内存)、PgVectorStore、MilvusVectorStore
@Tool把 Java 方法暴露给模型当函数调用
java
@Bean
ChatClient chatClient(ChatModel model) {
    return ChatClient.builder(model)
            .defaultSystem("你是心理健康助手,回答不超过 150 字,遇到自伤倾向要引导求助热线。")
            .defaultAdvisors(
                    MessageChatMemoryAdvisor.builder(new MessageWindowChatMemory(20)).build(),
                    new SimpleLoggerAdvisor())
            .build();
}
@Bean
ChatClient chatClient(ChatModel model) {
    return ChatClient.builder(model)
            .defaultSystem("你是心理健康助手,回答不超过 150 字,遇到自伤倾向要引导求助热线。")
            .defaultAdvisors(
                    MessageChatMemoryAdvisor.builder(new MessageWindowChatMemory(20)).build(),
                    new SimpleLoggerAdvisor())
            .build();
}

8.4 流式输出(SSE) ​

java
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestParam String q) {
    return chatClient.prompt()
            .user(q)
            .stream()          // 关键:stream 而不是 call
            .content();        // Flux<String>,逐 token 推给前端
}

// 需要拿完整文本做后处理时:
String full = chatClient.prompt().user(q).call().content();
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestParam String q) {
    return chatClient.prompt()
            .user(q)
            .stream()          // 关键:stream 而不是 call
            .content();        // Flux<String>,逐 token 推给前端
}

// 需要拿完整文本做后处理时:
String full = chatClient.prompt().user(q).call().content();

前端用 EventSource 接收即可。SSE 是单向的(服务端 → 浏览器),要双向就上 WebSocket。

8.5 RAG 最小实现 ​

java
// 1. 灌库:文档 → 切块 → 向量化
vectorStore.add(new TokenTextSplitter().apply(new Document(kbText)));

// 2. 检索增强:QuestionAnswerAdvisor 自动完成「检索 + 拼进 Prompt」
ChatResponse resp = chatClient.prompt()
        .advisors(QuestionAnswerAdvisor.builder(vectorStore)
                .searchRequest(SearchRequest.builder().topK(4).similarityThreshold(0.7).build())
                .build())
        .user("年假怎么申请?")
        .call();
// 1. 灌库:文档 → 切块 → 向量化
vectorStore.add(new TokenTextSplitter().apply(new Document(kbText)));

// 2. 检索增强:QuestionAnswerAdvisor 自动完成「检索 + 拼进 Prompt」
ChatResponse resp = chatClient.prompt()
        .advisors(QuestionAnswerAdvisor.builder(vectorStore)
                .searchRequest(SearchRequest.builder().topK(4).similarityThreshold(0.7).build())
                .build())
        .user("年假怎么申请?")
        .call();

完整链路是:切块 → 向量化 → 存库 → 检索 topK → 重排 → 拼 Prompt → 生成 → 引用标注。工程上真正决定效果的是切块策略和检索召回,模型反而是最不重要的那环。

8.6 工具调用 ​

java
public class OrderTools {
    @Tool(description = "根据订单号查询订单状态,订单号形如 SO20240101001")
    public String queryOrder(String orderNo) {
        return orderService.getStatus(orderNo);   // 模型自己决定什么时候调
    }
}

chatClient.prompt().user("帮我查下 SO20240101001 到哪了").tools(new OrderTools()).call();
public class OrderTools {
    @Tool(description = "根据订单号查询订单状态,订单号形如 SO20240101001")
    public String queryOrder(String orderNo) {
        return orderService.getStatus(orderNo);   // 模型自己决定什么时候调
    }
}

chatClient.prompt().user("帮我查下 SO20240101001 到哪了").tools(new OrderTools()).call();

Spring AI 负责把 @Tool 转成 JSON Schema 发给模型,模型返回 tool_calls 后由框架反射调用、再把结果回传。注意:工具方法要自己做参数校验和权限校验,模型给出的参数不可信。

8.7 生产踩坑清单 ​

坑现象处理
Nginx 缓冲SSE 一次吐出全部内容proxy_buffering off; proxy_read_timeout 300s;
网关超时长回答被截断服务端心跳 + 前端断线重连(带 lastEventId)
全局异常处理器异常被 @RestControllerAdvice 吞掉,SSE 流挂住不结束对 Flux 返回类型单独处理,直接 onErrorResume 发 error 事件
上下文超限长对话报 token 超限滑动窗口 + 摘要压缩;限制单条输入长度
成本失控账单暴涨按用户配额、max-tokens 上限、便宜模型兜底、缓存高频问题
线程池流式占线程WebFlux / SSE 用 Netty 事件循环,别用 @Async 默认池
内容安全违规输出输入输出双向过滤,命中走固定话术

8.8 和其他方案对比 ​

方案定位
Spring AISpring 官方,抽象统一、Advisor 机制好扩展,适合已有 Spring 体系
Spring AI Alibaba在 Spring AI 之上补了通义/DashScope、Graph 编排,国内落地更顺
LangChain4j社区活跃、AI Service 注解式声明很省事,但生态耦合度低于 Spring
自己封装 HTTP只调一两个接口、不打算做 RAG/工具调用时,反而最简单

面试口径:别说「用了 Spring AI」就完了,要说清「为什么是后端接而不是前端直连」(Key 安全 + 成本管控 + 数据不出内网)、SSE 怎么保证不中断、RAG 的召回率怎么评、token 成本怎么控。

9. 其他流行框架 ​

9.1 Quarkus ​

Quarkus 是一个 Kubernetes 原生 Java 框架,用于构建轻量级、快速启动的应用程序。

特点:

  • 快速启动时间
  • 低内存占用
  • 原生镜像支持(GraalVM)
  • 响应式和命令式编程模型
  • 与 Spring API 兼容
  • 内置 Kubernetes 支持

示例:

java
@Path("/hello")
public class HelloResource {
    
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello from Quarkus!";
    }
    
    @GET
    @Path("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public String hello(@PathParam("name") String name) {
        return "Hello, " + name + "!";
    }
}
@Path("/hello")
public class HelloResource {
    
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello from Quarkus!";
    }
    
    @GET
    @Path("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public String hello(@PathParam("name") String name) {
        return "Hello, " + name + "!";
    }
}

9.2 Micronaut ​

Micronaut 是一个现代、轻量级的 Java 框架,用于构建模块化、易于测试的微服务应用程序。

特点:

  • 快速启动时间
  • 低内存占用
  • 编译时依赖注入
  • 原生镜像支持(GraalVM)
  • 响应式编程支持
  • 内置服务发现和配置管理

示例:

java
@Controller("/api/users")
public class UserController {
    
    @Inject
    private UserService userService;
    
    @Get("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    
    @Get
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @Post
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public User createUser(@Body User user) {
        return userService.createUser(user);
    }
}
@Controller("/api/users")
public class UserController {
    
    @Inject
    private UserService userService;
    
    @Get("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    
    @Get
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @Post
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public User createUser(@Body User user) {
        return userService.createUser(user);
    }
}

10. 总结 ​

Java 框架技术栈是 Java 企业级应用开发的重要组成部分,包括:

  1. Spring Framework 6:提供了全面的基础设施支持
  2. Spring Boot 3:简化了 Spring 应用的开发和部署
  3. Spring Security 6:提供了强大的安全功能
  4. Jakarta EE 10:企业级应用开发平台
  5. Spring Data:简化了数据访问层的开发
  6. Spring WebFlux:响应式 Web 框架
  7. Quarkus:Kubernetes 原生 Java 框架
  8. Micronaut:现代、轻量级 Java 框架

这些框架和技术栈各有特点,开发者可以根据项目需求选择合适的框架。Spring 生态系统是 Java 企业级开发的主流选择,而 Quarkus 和 Micronaut 则适合构建云原生应用。

随着 Java 技术的不断发展,这些框架也在不断演进,支持最新的 Java 特性和云原生技术,为开发者提供了更高效、更便捷的开发体验。