写在前面

        你有没有想过一个问题:为什么我们创建一个 Spring Boot 项目,什么都不用配置,就能直接启动一个 Web 服务器?为什么我们把接口命名为 findAllUsers,Spring Data JPA 就能自动生成对应的 SQL?为什么我们把配置文件命名为 application.yml,Spring 就能自动读取?

这一切的背后,都源于一种设计哲学——“约定大于配置”(Convention over Configuration)

        我第一次接触这个概念时,觉得它像是一种“魔法”。后来慢慢理解,它其实是框架在“替你思考”:如果你没有特别的要求,那就按我最擅长的方式来做;如果你有特殊需求,随时可以覆盖我的约定。

        这种思想,让 Java 开发从早期 SSH 框架动辄几百行 XML 配置的“黑暗时代”,迈入了“开箱即用”的黄金时代。今天,我们就来聊聊,在 Java 生态中,约定大于配置是如何体现的,它给我们带来了什么,又隐藏了哪些陷阱。

一、什么是“约定大于配置”?从租房说起

想象你去租房:

  • 传统配置模式:你告诉中介:我要 3 米宽、4 米长、朝南、有飘窗、地板是木头的……中介帮你找。这很精确,但很累。

  • 约定大于配置:中介说,我们这儿的“标准主卧”就是 12 平、朝南、有飘窗。你觉得行,就直接签合同。如果你想要落地窗,再单独提。

        框架也一样。它定义了一套“标准规则”,只要你遵循这些规则(比如类名、方法名、文件位置),它就能自动完成大量工作,无需显式配置。

        这个概念最早由 Ruby on Rails 发扬光大,后来被 Maven、JPA、Spring Boot 等 Java 技术广泛借鉴。

二、Java 中“约定大于配置”的经典案例

2.1 Maven:目录结构即配置

在没有 Maven 的年代,我们手动管理 jar 包、配置构建路径。Maven 出现后,它约定:

src/
  main/
    java/     → 存放 Java 源码
    resources/ → 存放配置文件
  test/
    java/     → 存放测试代码

        只要你按这个结构放文件,Maven 就能自动编译、测试、打包。你不需要告诉它“源码在哪里”,约定已经帮你做了。

覆盖方式:在 pom.xml 中修改 <build> 标签,可以改变目录路径,但很少有人这么做。

2.2 JPA/Hibernate:命名策略即映射

JPA 中,实体类名默认映射到数据库表名(首字母小写,驼峰转下划线)。例如:

@Entity
public class UserProfile {
    private Long id;
    private String firstName;
}

默认对应的表名是 user_profile,字段 id 映射到 idfirstName 映射到 first_name

你不需要写 @Table(name = "user_profile") 和 @Column(name = "first_name")——框架通过命名策略自动完成了。

覆盖方式:显式使用 @Table@Column 注解即可。

2.3 Spring Boot:自动配置的巅峰

        这是最震撼的例子。一个空的 Spring Boot 项目,加上 spring-boot-starter-web 依赖,启动后就有了一个内嵌的 Tomcat,监听 8080 端口。

为什么?因为 Spring Boot 的约定是:

  • 你大概率需要 Web 容器 → 默认启动 Tomcat

  • 你大概率需要端口 8080 → 就监听 8080

  • 你大概率需要一个 /error 映射 → 自动配置 BasicErrorController

        这些约定都在 spring-boot-autoconfigure 包的 META-INF/spring.factories 中定义。你可以通过 application.yml 中的 server.port 等配置来覆盖。

2.4 Spring MVC:视图解析器的默认路径

传统的 Spring MVC 需要配置 InternalResourceViewResolver

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

Spring Boot 约定:视图文件放在 src/main/resources/templates/(对于 Thymeleaf)或 src/main/webapp/WEB-INF/views/(对于 JSP),解析器自动配置,无需你写一行 XML。

2.5 Spring Cloud:服务发现与负载均衡

使用 Netflix Eureka 时,你只需在 application.yml 中配置:

spring:
  application:
    name: user-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

你的服务就会自动注册到 Eureka。其他服务通过 @FeignClient(name = "user-service") 就能调用它——约定使用服务名作为路由标识。

三、约定大于配置的“双刃剑”:优点与陷阱

3.1 优点

3.2 陷阱与注意事项

四、如何正确驾驭“约定大于配置”——最佳实践

4.1 遵循约定,除非你有充分的理由

不要为了“炫技”而去覆盖默认约定。比如,强行把 Maven 的源码目录改成 src/code,只会让所有人困惑。

4.2 了解约定背后的原理

  • 读 Spring Boot 的 spring.factories 文件

  • 理解 @ConditionalOnMissingBean 等条件注解

  • 启动时加上 -Ddebug=true 查看自动配置报告

4.3 使用显式配置覆盖约定,但保持最小化

server:
  port: 9000  # 覆盖默认 8080
spring:
  mvc:
    view:
      prefix: /custom/  # 覆盖默认视图路径

4.4 团队内部统一约定规范

约定大于配置的前提是团队对约定有共识。比如:

  • 统一使用 @RestController 而非 @Controller + @ResponseBody

  • 统一使用 @Service 注解业务层

  • 统一使用 camelCase 命名 API 字段,即使数据库是下划线

4.5 利用约定简化测试

        Spring Boot Test 约定:测试类放在 src/test/java 下,类名以 Test 结尾,使用 @SpringBootTest 注解,就会自动加载上下文。你也可以继承 AbstractTestNGSpringContextTests 来复用。

五、延伸思考:约定是银弹吗?

        不是。约定大于配置最适合标准化、通用性强的场景,比如 Web 开发、ORM、构建工具。但在某些领域,显式配置更安全:

  • 金融系统:每条 SQL 都想显式写出,避免自动生成的 SQL 性能意外

  • 遗留系统集成:数据库命名不规范,必须手动映射

  • 多租户/动态路由:数据源无法通过默认约定决定

        好的框架会允许你渐进式地从约定迁移到显式配置。Spring Boot 的 @Conditional 体系就是最好的例子——约定是起点,但不是终点。

总结:约定的本质是“有原则的偷懒”

“约定大于配置”不是让开发者变笨,而是让框架聪明到可以猜中你的意图。

        它解放了我们从重复的配置劳动中,让我们更关注业务逻辑。但它的存在,也要求我们花时间去理解框架背后的“思维模式”。

        当你下次启动一个 Spring Boot 项目,看到控制台输出“Started Application in 2.3 seconds”时,不妨想一想:这 2.3 秒里,框架默默地为你做了多少约定好的工作。

最后留一个问题供你思考:
        如果让你设计一个业务框架(比如一个内部 RPC 框架),你会设定哪些“约定”来简化开发?这些约定又可能带来什么限制?

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐