首页 / Spring Boot 入门教程 / 项目结构:约定优于配置

Spring Boot 入门教程

项目结构:约定优于配置

本教程共 48 篇 · 第 4 篇 · 更新于 2026-08-13 · 约 5 分钟阅读

包结构约定优于配置@SpringBootApplication组件扫描分层项目骨架

本节目标:掌握 Spring Boot 的标准包结构,搞懂 @SpringBootApplication 的组成与扫描机制,学完你会搭出规范的项目骨架。

约定优于配置

Spring Boot 的设计哲学,一句话:约定优于配置(Convention over Configuration)。

框架先把常规情况下的最佳实践定成默认值。你按约定放文件、命名、分包,框架就少让你配置;你要搞特殊,再显式覆盖。

项目结构是第一课。目录怎么建、主类放哪里,都有约定。

两条铁律

第一,不要用默认包。 Java 允许类不写 package 声明,但 Spring Boot 应用这么干会出大问题。自动配置和组件扫描都要扫包,默认包里所有 jar 的类都会被读一遍,扫描范围失控,各种奇怪报错。

第二,包名用反域名。 比如公司域名是 example.com,包名就是 com.example。这是 Java 世界的通用惯例,保证包名全球不重名。包名全小写,多级包用点连接,比如 com.example.myapplication.customer;类名用大驼峰。命名惯例从第一天就养成。

主类放在根包

主类(带 main 方法和 @SpringBootApplication 的类)应该放在包结构的最顶层,也就是根包。

为什么?@SpringBootApplication 默认扫描主类所在包及其所有子包。主类放根包,扫描范围正好覆盖整个项目,不多不少。

官方推荐的布局长这样:

com
└── example
    └── myapplication
        ├── MyApplication.java          # 主类,放根包
        ├── customer
        │   ├── Customer.java           # 实体:描述数据
        │   ├── CustomerController.java # 控制器:收请求
        │   ├── CustomerService.java    # 服务:写业务
        │   └── CustomerRepository.java # 仓库:管数据
        └── order
            ├── Order.java
            ├── OrderController.java
            ├── OrderService.java
            └── OrderRepository.java

主类本身很简单:

package com.example.myapplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@SpringBootApplication 到底是什么

它是个组合注解,一个顶三个:

  • @SpringBootConfiguration:声明这是一个配置类
  • @EnableAutoConfiguration:开启自动配置,按 classpath 依赖猜测你要什么
  • @ComponentScan:扫描主类所在包及子包,把带 @Component 等注解的类注册成 Bean

拆开写,等价于:

package com.example.myapplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
Note

拆开写和 @SpringBootApplication 效果相同,但没人这么写。一个注解更省事,理解它由三部分组成,是为了搞懂背后的机制。

组件扫描:Spring 怎么找到你的类

@ComponentScan 是这套机制的核心。它会扫主类所在包及子包,把符合条件的类注册成 Bean,放进 Spring 容器统一管理。

什么样的类会被扫到?带这些「组件注解」的类:

  • @Component:通用组件
  • @Service:业务服务层
  • @Repository:数据访问层
  • @Controller / @RestController:控制层

Bean 是 Spring 对受管对象的称呼,先记住这个词,第 7 章细讲。

Tip

这几个注解的功能本质相同,区别是语义标签。Spring 和开发者都能一眼看出类的角色。这就是为什么你随手写的控制器,Spring 会自动接管,不用手动 new。

分层思想:包按业务切

Spring Boot 对包结构没有强制要求,但社区沉淀了两套组织方式。

按技术分层:controller、service、repository、entity 各建一个包。小项目很清晰,项目一大就乱——一个业务功能要跨四个包翻找。

按业务分包(package-by-feature):每个业务模块一个包,包内自含控制器、服务、数据访问。官方推荐的正是这种,比如上面的 customer 包和 order 包,模块之间高内聚、低耦合。

组织方式优点缺点
按技术分层结构直观,新人好上手项目大了跨包翻找,功能分散
按业务分包模块内聚,功能集中需要先理解业务边界

不管哪种方式,三层职责是固定的:

  • Controller:接收 HTTP 请求,校验参数,返回响应
  • Service:写业务逻辑,组织多个数据操作
  • Repository:访问数据库,增删改查
Tip

新手学分层,先记职责:Controller 收请求、Service 写业务、Repository 管数据。按业务分包后,这些类自然落在各自的模块包里,三层关系一目了然。

一个最小分层的例子

把上一章的 Hello World 拆开,感受一下分层。控制器单独一个类:

package com.example.myapplication.customer;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomerController {

    private final CustomerService customerService;

    public CustomerController(CustomerService customerService) {
        this.customerService = customerService;
    }

    @GetMapping("/customers")
    public String list() {
        return customerService.buildGreeting();
    }
}

服务类负责业务:

package com.example.myapplication.customer;

import org.springframework.stereotype.Service;

@Service
public class CustomerService {

    public String buildGreeting() {
        return "Hello from Customer Service!";
    }
}

注意两个细节:CustomerService@Service 标注,会被组件扫描自动发现;控制器通过构造器拿到服务,没有手动 new。这就是依赖注入的雏形,第 7 章会系统讲。

为什么不手动 new

新手常问:服务类直接 new 一个不就完了,为什么要让 Spring 管?

手动 new 的问题在「依赖关系」:CustomerController 依赖 CustomerService,CustomerService 又依赖别的东西。项目一大,对象之间的依赖链全靠手写组装,改一处崩一片。

Spring 的做法是反转:你声明依赖,容器负责创建和注入。构造器写上参数,Spring 自动把现成的 Bean 送进来。代码只关心「我要什么」,不关心「去哪找」。这就是控制反转(IoC),第 7 章展开讲。

主类放错的后果

主类放进子包,组件扫描范围就缩到子包。根包里的其他组件统统扫不到:接口 404、Bean 找不到,排查半天。

Warning

新手最常见的坑:把主类挪进 controller 之类的子包。@SpringBootApplication 一挪,扫描范围就变了。主类必须留在根包。

实在要挪,可以显式指定扫描包:

@SpringBootApplication(scanBasePackages = "com.example.myapplication")

但这是补救措施,不是常规做法。默认结构最省心。

实体类放哪

实体类(Customer)放在业务包内,和它的控制器、服务放一起。第 26 章起讲数据访问时,实体类会频繁出现,位置约定和现在一样:跟着业务模块走。

小结

骨架是约定,更是习惯。按规范摆文件,团队协作才不吵架。

  • 不用默认包,包名用反域名
  • 主类放根包,@SpringBootApplication 一个注解管扫描和自动配置
  • 包按业务切分,模块内自含控制层、服务层、数据层
  • 主类乱放是高频翻车点,守住根包

骨架搭对了,后面写代码才能顺。下一章看 Spring Boot 有哪几种启动方式。