从零搭建SpringBoot项目:我的学习笔记与实践分享 我决定从零开始搭建一个SpringBoot项目然后我崩溃了三次第一次接触SpringBoot时我天真地以为它不过是Spring的“简化版”写个Controller、配个数据库就能跑起来。结果第一天我就卡在了“如何让application.yml生效”这个问题上——整整三个小时我把配置改成了application.properties又改回YAML重启了八次IDEA最终发现只是缩进多了一个空格。那一刻我忽然明白SpringBoot的精髓不在“自动配置”而在“你知道它为什么能自动配置”。这个项目是我给自己布置的作业用SpringBoot搭建一个完整的博客系统后端包含用户注册登录、文章CRUD、分类标签、评论、权限控制以及一个最简单的Redis缓存。我没有照着任何现成的教程走而是像拼乐高一样每添加一个依赖就先理解它的自动配置原理再动手写代码。下面就是我从一个连“starter”都拼错的菜鸟到能说出“EnableAutoConfiguration到底干了什么”的完整过程。第一步我连项目骨架都拧巴了半天IDEA的Spring Initializr确实方便勾选Web、JPA、MySQL Driver、Security、Redis、Thymeleaf生成下载。但问题来了生成的项目里有三个诡异的文件夹main、test和resources而资源文件夹里只有一个空的application.properties。按照网上老教程我写了个Controller用RestController标记跑起来访问localhost:8080/hello返回了404。检查了半天发现SpringBoot项目启动类必须放在所有子包的根包之上否则组件扫描会扑空。这是我的第一个教训SpringBootApplication注解包含了ComponentScan默认扫描启动类所在包及其子包。我把启动类放在了com.example.demo下而Controller在com.example.demo.controller下这没问题。但诡异的是我还多了一个com.example.demo.config包里面空无一物却因为包名拼写错误写成了com.exmaple.demo.config结果整个扫描路径都乱了。删除多余包重建结构项目终于跑起来了。这个过程中我意识到所谓的“零配置”其实是“约定优于配置”。如果连package命名这么基础的事都不规范SpringBoot的自动配置会直接短路。配置数据库连接时的“魔幻时刻”博客系统需要MySQL。我在application.yml中写下spring: datasource: url: jdbc:mysql://localhost:3306/blog?useSSLfalseserverTimezoneUTC username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver启动控制台报错Failed to configure a DataSource。排查后才知道SpringBoot的DataSourceAutoConfiguration需要依赖一个线程池实现而我在pom.xml里只引入了spring-boot-starter-web和spring-boot-starter-data-jpa。spring-boot-starter-data-jpa会间接引入HikariCP但它必须在classpath中存在。我的情况是因为手贱删除了一个没用的依赖导致JPA的starter没被真正引入。重新添加后报错消失。但是紧接着我看到了一行红色警告HHH000342: Could not obtain connection to query metadata。这是JPA的Hibernate在尝试自动建表时发现MySQL的时区设置不匹配。解决方案是在url后面加个serverTimezoneAsia/Shanghai。我加了重启依然报错。我以为是SpringBoot版本兼容问题换了几个版本都不行。最后发现是application.yml的缩进错了driver-class-name和password平级了导致url读取的是空值。修复缩进后一切正常。这件事让我学到YAML对缩进极度敏感几乎可以作为一个独立的“陷阱单元”。后来我用IntelliJ的插件检查缩进才根治了这类问题。同时我记下了DataSource的自动配置原理EnableAutoConfiguration会读取spring.factories文件找到DataSourceAutoConfiguration它又依赖DataSourceProperties类通过ConfigurationProperties将前缀为spring.datasource的属性绑定到对象。所以只要你的配置符合约定自动配置就能工作。写实体类时我掉进了JPA的“懒加载”坑按照设计我有User、Article、Category、Tag、Comment五个实体。User和Article是一对多Article和Category是多对一Article和Tag是多对多Article和Comment是一对多。我采用JPA的Entity和Table注解加上Lombok的Data简化代码。起初一切都好直到我想查询一篇文章时遇到了著名的“no session”异常org.hibernate.LazyInitializationException: failed to lazily initialize a collection of field: com.example.demo.entity.Article.comments, could not initialize proxy - no Session原因是在Service层的事务中我通过articleRepository.findById(id)拿到了Article对象然后返回给Controller。此时事务已提交Session关闭。当Controller试图访问article.getComments()时Hibernate的懒加载代理无法获取数据库连接。解决方案有几种在Repository方法上加Transactional(Transactional.TxType.REQUIRED)使Controller也能参与事务或者使用JOIN FETCH显式抓取关联集合再或者全局配置spring.jpa.open-in-viewtrue。但open-in-view会导致长时事务占用数据库连接生产环境严禁使用。我选择了在Service层的方法上使用Transactional并设计了一个DTO对象来传递数据避免在Controller层直接触发懒加载。这个坑让我意识到JPA的懒加载是一把双刃剑。它节省了不必要的查询但如果你不了解Session的生命周期代码会在运行时猝不及防地崩溃。我开始认真阅读JPA的“持久化上下文”概念并把相关笔记贴在工位旁EntityManager是线程不安全的每个事务对应一个上下文。增删改查的“重复劳动”让我想写代码生成器写了UserController、ArticleController、CategoryController……每个Controller都要有CRUD方法每个方法都要写日志、校验参数、封装返回结果。我试着用泛型和反射封装一个BaseController但发现Spring的依赖注入和AOP让我无法简单通过new来获取Service实例。更好的做法是使用Spring Data JPA的通用Repository它已经预置了save、findById、findAll、delete等基本方法。我定义了一个BaseEntity类包含id、createdAt、updatedAt字段使用MappedSuperclass注解并在项目启动时通过JPA的审计功能自动填充时间。这需要添加EnableJpaAuditing和配置审计感知接口。完成后所有实体都自动获得了创建时间和更新时间。针对文章查询我需要按标题模糊搜索按分类过滤按时间排序。我开始学习Spring Data JPA的Specification动态查询配合Pageable分页。写了一个ArticleSpecificationBuilder可以根据前端传递的查询条件动态构建Predicate数组。虽然代码量增加但可维护性远胜于在Repository接口里写一堆Query。权限控制Spring Security让我怀疑人生给博客加上登录注册和角色管理第一反应就是Spring Security。我在pom里加上了spring-boot-starter-security启动后直接访问localhost:8080跳转到了一个默认的登录页面——这让我瞬间意识到Spring Security默认会拦截所有请求。我先配置了一个WebSecurityConfigurerAdapter的子类放行/login、/register、/api/public/, 以及静态资源。但真正麻烦的是密码加密和用户认证。我实现了UserDetailsService从MySQL读取用户信息但默认的PasswordEncoder是BCryptPasswordEncoder而我存到数据库的密码是明文。所以我需要先写一个注册接口在注册时对密码进行BCrypt加密。同时在配置类中声明Bean返回PasswordEncoder。权限控制更复杂。我需要区分普通用户和管理员。我在User实体中添加了role字段使用String表示“ROLE_USER”和“ROLE_ADMIN”。然后通过PreAuthorize注解控制只有管理员才能删除文章只有作者才能修改文章。但这里有个坑当你用PreAuthorize时必须启用全局方法安全性即在启动类或配置类上加EnableGlobalMethodSecurity(prePostEnabled true)。接下来是JWT无状态认证。我决定抛弃Session使用JWT Token。写了一个JwtUtils工具类在登录成功后生成Token并返回前端。前端每次请求在Header中附带Authorization: Bearer xxx。我写了一个JwtAuthenticationFilter继承OncePerRequestFilter从请求头解析Token验证后设置SecurityContextHolder。这个过程非常琐碎我甚至写了一个单元测试来确保Token的有效期和签名逻辑正确。Spring Security的过滤器链是核心。你必须理解它由多个Filter构成比如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、ExceptionTranslationFilter等。自定义的JWT Filter需要在某个顺序插入。我把它放在UsernamePasswordAuthenticationFilter之前因为前端不会提交表单方式的登录而是通过API接口用JSON提交。测试过程中我发现自己写的JWT Filter在Token过期后返回的错误信息不够友好导致前端无法判断是权限不足还是Token失效。我修改了Filter的doFilterInternal方法捕获JwtException设置响应的状态码和JSON消息同时清除SecurityContext。这个阶段我写了一整篇笔记Spring Security认证流程详解里面记录了我每次调试时打印的日志。纸上得来终觉浅绝知此事要躬行——这句古语放在安全框架的学习上再合适不过。缓存提速Redis的“一击命中”与“雪崩”博客首页需要显示最近10篇文章和热门标签。每次请求都查数据库在高并发下肯定不行。我引入Redis配置spring.cache.typeredis然后使用Cacheable注解。但第一个错误就来了Cacheable默认使用JdkSerializationRedisSerializer会导致对象不可读。我改为Jackson2JsonRedisSerializer并配置ObjectMapper支持LocalDateTime序列化。缓存击穿是个经典问题。我模拟了一个场景一个热点文章缓存失效瞬间大量请求打到数据库。为了解决这个问题我编写了一个互斥锁在获取缓存失败时尝试获取一个分布式锁通过Redis的SETNX只有一个线程能拿到锁并查询数据库其他线程自旋等待或直接返回旧缓存。虽然实现不复杂但它让我理解了高并发系统设计中的权衡一致性与可用性之间总要做出选择。我还增加了缓存过期时间的随机偏移防止大面积缓存同时失效导致缓存雪崩。所有这些配置都写在application.yml里通过ConfigurationProperties绑定到业务类。测试环节我写了80个测试用例项目功能基本完成但不敢上线。我开始写单元测试和集成测试。使用Spring Boot的WebMvcTest测试Controller用MockBean模拟Service重点验证权限拦截和返回格式。使用SpringBootTest测试Service层和Repository层配合AutoConfigureTestDatabase使用H2内存数据库避免污染MySQL。测试中发现了几个逻辑漏洞比如未登录用户不能查看文章详情应该允许查看管理员删除文章时没有校验文章是否存在导致空指针异常。单元测试就像是代码的保险丝你越早发现毛病修理成本越低。部署生产我目睹了jar包与环境的博弈本地运行完美用mvn package打成jar包后放到服务器上执行java -jar blog.jar启动报错端口被占用。原来服务器上已经有个Tomcat占用了8080端口。我修改application.yml中server.port8081并添加了外部配置覆盖java -jar blog.jar --server.port8081。或者更好的方式是使用环境变量SPRING_PROFILES_ACTIVEprod然后在application-prod.yml里覆盖端口和数据库配置。日志问题开发时我用的System.out.println上生产后需要改成Logback。Spring Boot默认集成了Logback我只需创建一个logback-spring.xml定义不同profile下的日志级别和输出目的地。将日志写入文件并用logrotate管理滚动。我还配置了健康检查、线程池监控和优雅关闭。Spring Boot Actuator提供了/actuator/health和/actuator/metrics端点我在配置中开放了部分端点并通过Spring Security进行保护。复盘这些笔记改变了我对“框架”的理解从零搭建这个项目前后用了两个星期。崩溃过三次踩过的坑超过20个。但收获远不止一个能用的博客系统。我总结了几条核心认知第一理解自动配置原理比记住注解更重要。比如EnableAutoConfiguration会读取META-INF/spring.factories然后按条件Conditional系列注解加载Bean。当你遇到“明明加了依赖却报BeanNotFound”时就应该检查是否有某个条件不满足。第二约定优于配置不是让你忽略约定。包命名、配置缩进、注入方式都必须严格遵守否则自动配置就会走样。第三框架不会帮你写业务逻辑。它只是替你处理了基础设施层面的重复劳动。你依然要自己设计实体关系、写查询语句、做幂等处理。第四测试是质量的唯一保证。没有测试的项目重构时就像在雷区里跑步。Spring Boot对测试的友好度极高DataJpaTest、WebMvcTest、SpringBootTest可以让你精确控制测试范围。第五学习一门新技术最好的方式就是用它做一个完整的项目。看一百篇教程不如亲手写一个能运行的Hello World然后一点一点加功能它出了Bug你看日志、查文档、问社区最后把它修好。这个过程积累的经验值远超任何速成课。现在我打开那个博客项目点击发布文章展示在首页。那一刻的成就感是所有“搭积木”式的教程无法给予的。而我知道这才刚刚开始——下一个迭代我要加入消息队列、搜索引擎和微服务拆分。毕竟真正的学习永远在路上。