整体逻辑

  • 控制层(Controller)
  • 业务逻辑层(Service) → 服务层实现类(ServiceImpl)
  • 数据访问层(Repository/DAO) → 数据访问实现类(RepositoryImpl)

各层之间的分离 和 松耦合,每个层次只关注自己的职责,并通过接口与实现层进行解耦。

控制层

控制层(Controller)是应用的入口,负责接收来自客户端的请求,执行一些简单的输入验证,然后将请求转发到业务逻辑层(Service)进行处理,最后将处理结果返回给客户端。

控制层应尽量保持简洁,只负责接受请求、验证输入、调用业务层方法并返回结果。复杂的业务逻辑、事务管理等应该委托给业务逻辑层。

业务逻辑层

Bean是实现逻辑层的载体,其通常采用单例的模式,这些单例Bean通常不会包含会话或请求特有的数据,而是包含无状态的业务逻辑、常量以及通用功能。

而在业务逻辑中的变量通常是随着某次请求变化的值,因此Bean的输入数据通常通过一个专门的数据模型(如DTO,数据传输对象)来封装,然后作为参数传递给业务层的服务方法。这个数据模型包含了业务逻辑所需的动态数据,它是业务逻辑处理的输入

逻辑层的职责包括对外提供服务、协调数据访问、事务管理等,因此通常会由一组或多个服务类(Bean)来实现。

数据访问层

基于 MyBatis 的一种常见数据库访问层架构设计,涉及了 (repo impl)、数据访问层(repository)、映射器接口(Mapper) 和 SQL 映射文件(Mapper.xml)。 映射器接口是映射器能作为java类被其他模块使用的前提,使用Mapper.xml的方式可以完全自定义 SQL,提高 SQL 和 Java 代码的清晰分离

模块管理

主要借助Maven或者是Gradle,类似于pip的管理机制,都可以用来自动管理和下载第三方库(依赖)。你在配置文件中声明了项目所需的库及其版本,Maven/Gradle 会自动从中央仓库或私有仓库下载并将其集成到项目中,甚至其还有更加 丰富的类似于项目的构建、版本控制、生命周期管理等多个方面。

依赖注入(DI)

依赖注入(Dependency Injection,DI)是一种设计模式,它将对象的依赖交给容器管理,由容器在运行时自动创建和注入依赖,从而降低组件间耦合、提高可测试性和可维护性。

简单说,依赖注入是一种让对象无需自行创建依赖,由容器自动提供所需组件的设计模式。

Spring中的依赖注入

Spring采用注解来简化依赖注入的复杂度,有一个全局可以访问的 IoC 容器,其遵循以下步骤:

扫描注册 Bean → 实例化 Bean → 解析依赖 → 注入依赖 → 初始化完成。

扫描和注册 Bean 的类

Bean类包括:

  • @Component(通用组件)
  • @Service(业务逻辑层)
  • @Repository(数据访问层)
  • @Controller(Web 控制器)
  • 等等

以及一些配置类 (@Configuration) 中的 Bean方法,@Bean 方法返回的对象也会被注册为 Bean。

@Configuration
public class AppConfig {
    @Bean
    public UserService userService(UserRepository repo) {
        return new UserService(repo);
    }
}

解析依赖关系

读取注入点:

  • 构造器注入:Bean类的构造器 (无需标注)
  • 字段注入:字段带有 @Autowired 标注
  • 方法注入:在Bean类的方法上标注 @Autowired

并且被注入的点必须属于一个被 Spring 管理的 Bean

实例化单例 Bean

对我们注册的Bean类进行下一步操作:

对默认单例 Bean,容器会在启动时提前创建实例(Eager Initialization),对原型 Bean 或延迟加载 Bean,则在第一次使用时才创建。

注入依赖

按照依赖关系将 Bean 注入到其他 Bean 的构造器、字段或 setter 中,并处理循环依赖(字段/Setter 注入可以,构造器注入可能报错)

过滤器Fliter使用

先编写继承自OncePerRequestFilter 的 Fliter 类

Fliter类编写

可以编写 doFilterInternal 来说明Fliter逻辑,编写 shouldNotFilter 来决定哪些可以跳过而不经过过滤器。

/**
 * JwtAuthFilter 用于在每次 HTTP 请求中处理 JWT 认证。
 * 它会在请求到达 Controller 前被调用,验证请求中的 JWT 并设置安全上下文。
 */
public class JwtAuthFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 功能说明:
        // 1. 从请求头获取 JWT(通常从 Authorization 字段)
        // 2. 检查 Token 是否有效或是否在黑名单中
        // 3. 验证 Token 并解析出用户信息
        // 4. 将认证信息设置到 SecurityContext 中,以便后续鉴权
        // 5. 调用 filterChain.doFilter 放行请求,无论认证是否成功
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        // 功能说明:
        // 用于判断哪些请求无需经过该过滤器
        // 例如公开路径(login、register)或静态资源等
        // 如果返回 true,doFilterInternal 将不会被执行
        return false; // 默认全部请求都执行过滤
    }
}

Config编写

再在 config 中设立一个Bean方法:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth.requestMatchers(SecurityConstants.PUBLIC_PATHS).permitAll()
                    .anyRequest()
                    .authenticated())
            .addFilterBefore(new JwtAuthFilter(jwtCoreUtil, tokenBlacklistService),
                    UsernamePasswordAuthenticationFilter.class);

    return http.build();

authorizeHttpRequests 是专门用于鉴权的机制,用来控制每个 HTTP 请求是否允许访问某个资源,和身份认证(JWT 校验)配合使用。鉴权机制下添加了我们上面的Fliter,并还可以通过 requestMatchers 来直接决定是否经过这个Fliter。

一个是内部的跳过,一个是从更上层根本跳过。

其中例如用到的PUBLIC_PATHS,设置哪些是公开的路径,可以写到constant里作为运行时常量。

DTO(Data Transfer Object)

PO、VO、DTO 对比整理

类型 全称 主要作用 特点
PO Persistent Object(持久化对象) 与数据库表结构一一对应 - 通常与 ORM 框架(MyBatis、Hibernate)配合使用- 一个 PO 对应数据库中的一张表- 包含表的所有字段及 getter/setter- 一般不包含业务逻辑
VO Value Object(值对象/视图对象) 用于展示层,向前端传递数据 - 根据前端页面需求定制数据结构- 可以组合多个 PO 的信息- 可以包含格式化后的数据- 通常用于接口返回给前端
DTO Data Transfer Object(数据传输对象) 在不同层或不同系统之间传输数据 - 封装多个数据对象,减少网络传输次数- 可以聚合多个 PO 的数据- 用于服务间调用或前后端数据传输- 可以包含业务相关的数据转换逻辑

通常在数据层与逻辑层之间依旧采用PO,对于数据层和控制层之间,或者是控制层与请求之间的交互则使用DTO。

record 用于定义一种 不可变的数据载体类,也叫 记录类。它主要用于简化只存储数据的类的写法。