Spring Boot 入门全流程
Spring Boot 入门全流程 分层解耦 核心概念IOC / DI / AOP1️⃣ Spring Boot 项目启动的“大致流程”┌─────────────────────┐ │ main() 方法 (入口) │ │ SpringApplication.run│ └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 创建 SpringApplication │ (读取 application.properties/yml) └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 初始化 ApplicationContext │ ← IoC 容器Bean 工厂 │ ├─ 扫描 Component、Service、Repository、Controller │ ├─ 读取并应用自动配置Spring‑Boot‑Starter │ └─ 注册所有 Bean对象到容器中 └───────│─────────────┘ ▼ ┌─────────────────────┐ │ Bean 的依赖注入DI │ ← Autowired/Inject/Resource │ 把需要的对象自动塞进来| └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 触发 PostConstruct / InitializingBean │ │ 如果有自定义初始化逻辑| └───────│─────────────┘ ▼ ┌─────────────────────┐ │ Web 服务器启动Tomcat/Jetty/Undertow│ └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 现在可以接受 HTTP 请求了 │ │ (进入 Controller 层) │ └─────────────────────┘┌─────────────────────┐ │ main() 方法 (入口) │ │ SpringApplication.run│ └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 创建 SpringApplication │ (读取 application.properties/yml) └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 初始化 ApplicationContext │ ← IoC 容器Bean 工厂 │ ├─ 扫描 Component、Service、Repository、Controller │ ├─ 读取并应用自动配置Spring‑Boot‑Starter │ └─ 注册所有 Bean对象到容器中 └───────│─────────────┘ ▼ ┌─────────────────────┐ │ Bean 的依赖注入DI │ ← Autowired/Inject/Resource │ 把需要的对象自动塞进来| └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 触发 PostConstruct / InitializingBean │ │ 如果有自定义初始化逻辑| └───────│─────────────┘ ▼ ┌─────────────────────┐ │ Web 服务器启动Tomcat/Jetty/Undertow│ └───────│─────────────┘ ▼ ┌─────────────────────┐ │ 现在可以接受 HTTP 请求了 │ │ (进入 Controller 层) │ └─────────────────────┘关键点拆解步骤解释最通俗①SpringBootApplication其实是 三个注解的组合SpringBootConfiguration等同ConfigurationEnableAutoConfiguration打开“一键配置”ComponentScan自动找我们写的类。把它挂在 主类 上Spring Boot 就会把整个包默认是主类所在的包及子包都扫描一遍。②SpringApplication.run这行代码相当于 “打开开关”把上面说的容器、自动配置、服务器全都装好让应用开始“活”起来。③ ApplicationContextIoC 容器IoC → 控制反转我们不再自己new对象容器会帮我们创建并维护所有 Bean对象我们只要说 “我要这个 Bean”容器就把它送给我们。④ 自动装配DIAutowired、Inject、Resource把 依赖比如 Service 需要 Repository注入进去省去手动new、set。⑤ 启动嵌入式 TomcatSpring Boot 自带 嵌入式容器不需要另装独立的 Tomcat直接java -jar app.jar就能跑。2️⃣ 常见的 分层结构解耦、职责单一┌─────────────────┐ │ Controller 层 │ ← 接收 HTTP 请求返回 JSON/HTML │ (Spring MVC) │ - 只写 “请求来了调用 Service” └───────│─────────┘ ▼ ┌─────────────────┐ │ Service 层 │ ← 业务逻辑事务、校验、组合调用 │ (业务层) │ - 只管 “这件事怎么做”不管数据怎么取 └───────│─────────┘ ▼ ┌─────────────────┐ │ Repository 层 │ ← 持久化DAO只负责数据库操作 │ (MyBatis/JPA) │ - 只写 “怎么查、怎么增” └───────│─────────┘ ▼ ┌─────────────────┐ │ Domain/Entity │ ← POJO业务对象对应数据库表 │ (模型层) │ - 只存属性尽量不放业务代码 └─────────────────┘┌─────────────────┐ │ Controller 层 │ ← 接收 HTTP 请求返回 JSON/HTML │ (Spring MVC) │ - 只写 “请求来了调用 Service” └───────│─────────┘ ▼ ┌─────────────────┐ │ Service 层 │ ← 业务逻辑事务、校验、组合调用 │ (业务层) │ - 只管 “这件事怎么做”不管数据怎么取 └───────│─────────┘ ▼ ┌─────────────────┐ │ Repository 层 │ ← 持久化DAO只负责数据库操作 │ (MyBatis/JPA) │ - 只写 “怎么查、怎么增” └───────│─────────┘ ▼ ┌─────────────────┐ │ Domain/Entity │ ← POJO业务对象对应数据库表 │ (模型层) │ - 只存属性尽量不放业务代码 └─────────────────┘为什么要这么分层好处解释职责单一每层只做它该做的事代码更易读、易改。低耦合上层只依赖 接口如UserService下面的实现UserServiceImpl可以随时换掉Controller 不需要改。可测试当你写单元测试时只需要 Mock 下层接口Repository上层业务逻辑仍然可以完整验证。易维护/扩展新增功能往往只在 Service/Repository 增加方法Controller 不动或者反过来。团队协作前端/接口、业务、持久层可以并行开发冲突小。小技巧接口 实现UserService/UserServiceImpl是实现“解耦”的最常见方式。Spring 通过Autowired注入接口自动匹配唯一实现类。3️⃣ IoC 与 DI最核心的“自动装配”3.1 什么是 IoCInversion of Control传统方式我们在代码里手动new MyRepository()→ 我们自己掌控对象的创建。IoC把 “创建对象” 的控制权交给 Spring 容器容器在启动时先把所有标记的类实例化成 Bean之后我们只需要 要用就直接注入。这就是 “控制反转”——我们从 “主动创建” 变成 “被动使用”。3.2 DIDependency InjectionIoC 的实现手段注入方式代码示例说明构造器注入推荐java\nService\npublic class OrderService {\n private final OrderRepository repo;\n public OrderService(OrderRepository repo) { this.repo repo; }\n}\n依赖在 构造函数 里传进来final保证不可变。Spring 会自动匹配唯一的OrderRepositoryBean。属性注入java\nAutowired\nprivate OrderRepository repo;\n直接在字段上加Autowired代码最简但不利于 单元测试因为字段是私有的、难以 mock。Setter 注入java\nprivate OrderRepository repo;\nAutowired\npublic void setRepo(OrderRepository repo) { this.repo repo; }\n当依赖可选或需要后置处理时使用。技巧只要类上有Component,Service,Repository,Controller或Configuration等注解它就会被 Spring 当成 Bean 放进容器从而可以被 DI 注入。3.3 Bean 的作用域ScopeScope说明singleton默认整个容器只创建 一个实例适用于 无状态 的服务大多数 Service、Repository。prototype每次getBean都创建 新实例少用一般用于 有状态 的对象如每次请求生成的对象。request / sessionWeb按 HTTP 请求 或 Session 生命周期创建。需要加Scope(request)等。4️⃣ AOPAspect‑Oriented Programming——给代码“加点儿东西”4.1 什么是 AOP横切关注点比如 日志、事务、权限校验、缓存这些功能在很多业务方法里都会出现。**如果每个方法里都写log.info(...)、Transactional代码会很臃肿且难维护。AOP 的作用是 把这些横切关注点抽离出来在不改业务代码的前提下“织入”到方法的前后。4.2 Spring AOP 的关键概念最常用的四个术语术语含义示例Aspect装有 Advice代码的方法集合使用Aspect标记。Aspect public class LogAspect { … }Advice在目标方法的 不同时间点 执行的代码。四种常见•Before→ 方法执行前•AfterReturning→ 正常返回后•AfterThrowing→ 抛异常后•Around→ 包围整个方法最强大Before(execution(* com.demo.service.*.*(..)))Pointcut切点表达式决定哪些 类/方法 会被拦截。使用 AspectJ 切点语法。execution(* com.demo.service.*.*(..))所有 service 包下的所有方法JoinPoint目标方法 本身的运行时信息方法名、参数等可以在 Advice 里获取。joinPoint.getSignature().getName()4.3 AOP 实际例子统一日志javaAspect Component // 加入容器Spring 才能发现 public class LogAspect { // 切点所有标了 RestController 的公开方法 Pointcut(within(org.springframework.web.bind.annotation.RestController)) public void restControllerMethods() {} // 在方法执行前打印请求信息 Before(restControllerMethods()) public void logBefore(JoinPoint jp) { String method jp.getSignature().toShortString(); Object[] args jp.getArgs(); System.out.println([LOG] 开始执行: method 参数: Arrays.toString(args)); } // 在方法正常返回后打印返回值 AfterReturning(pointcut restControllerMethods(), returning result) public void logAfter(JoinPoint jp, Object result) { String method jp.getSignature().toShortString(); System.out.println([LOG] 执行完毕: method 返回: result); } }Aspect Component // 加入容器Spring 才能发现 public class LogAspect { // 切点所有标了 RestController 的公开方法 Pointcut(within(org.springframework.web.bind.annotation.RestController)) public void restControllerMethods() {} // 在方法执行前打印请求信息 Before(restControllerMethods()) public void logBefore(JoinPoint jp) { String method jp.getSignature().toShortString(); Object[] args jp.getArgs(); System.out.println([LOG] 开始执行: method 参数: Arrays.toString(args)); } // 在方法正常返回后打印返回值 AfterReturning(pointcut restControllerMethods(), returning result) public void logAfter(JoinPoint jp, Object result) { String method jp.getSignature().toShortString(); System.out.println([LOG] 执行完毕: method 返回: result); } }运行原理Spring 在启动时会创建 代理对象JDK 动态代理或 CGLIB当你调用userService.getUser(1)时其实是先走代理代理先执行BeforeAdvice随后调用真实的业务方法最后执行AfterReturning。这对业务代码 零侵入不需要改业务代码。4.4 AOP 常见坑坑点原因解决办法只能拦截 Spring 管理的 Bean自己new UserService()创建的对象不在容器里AOP 失效。必须让对象交给 SpringAutowired、Component或使用 Configurable AspectJ 编译时织入不常用。Transactional 与 Around 混用两个 AOP 程序会产生 嵌套代理事务失效。确保 事务Transactional在 最外层或使用proxyTargetClasstrue强制 CGLIB内部方法调用不触发 AOP同一个类内部this. privateMethod()直接调用代理不经过。将跨切面的方法抽到 另一个 Service或使用 自调用代理AopContext.currentProxy()使用Before时忘记joinPoint.proceed()对Around必须手动调用proceed()否则业务方法根本不执行。在Around中Object ret joinPoint.proceed();后返回ret。5️⃣ 把上面所有概念串联起来的 最小示例项目仅 4‑5 个类目录结构约定俗成src/main/java └─ com.example.demo ├─ DemoApplication.java // 主类SpringBootApplication ├─ controller │ └─ UserController.java ├─ service │ ├─ UserService.java // 接口 │ └─ UserServiceImpl.java // 实现Service ├─ repository │ └─ UserRepository.java // 这里用 Spring Data JPA 示例 ├─ domain │ └─ User.java └─ aop └─ LogAspect.javasrc/main/java └─ com.example.demo ├─ DemoApplication.java // 主类SpringBootApplication ├─ controller │ └─ UserController.java ├─ service │ ├─ UserService.java // 接口 │ └─ UserServiceImpl.java // 实现Service ├─ repository │ └─ UserRepository.java // 这里用 Spring Data JPA 示例 ├─ domain │ └─ User.java └─ aop └─ LogAspect.java5.1DemoApplication.javajavaSpringBootApplication // 包含 ComponentScan EnableAutoConfiguration public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); // 启动 } }SpringBootApplication // 包含 ComponentScan EnableAutoConfiguration public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); // 启动 } }5.2User.javaDomainjavaEntity public class User { Id GeneratedValue private Long id; private String name; private Integer age; // getter / setter或 Lombok Data }Entity public class User { Id GeneratedValue private Long id; private String name; private Integer age; // getter / setter或 Lombok Data }5.3UserRepository.java持久层javaRepository public interface UserRepository extends JpaRepositoryUser, Long { // Spring Data JPA 已经帮我们实现常用 CRUD }Repository public interface UserRepository extends JpaRepositoryUser, Long { // Spring Data JPA 已经帮我们实现常用 CRUD }5.4UserService.java业务层接口javapublic interface UserService { User getById(Long id); User create(User user); }public interface UserService { User getById(Long id); User create(User user); }5.5UserServiceImpl.java业务层实现javaService // Spring 会把它注册成 Bean public class UserServiceImpl implements UserService { private final UserRepository repo; // 构造器注入DI public UserServiceImpl(UserRepository repo) { this.repo repo; } Override public User getById(Long id) { return repo.findById(id).orElseThrow(() - new IllegalArgumentException(User not found)); } Override public User create(User user) { // 这里可以写业务校验、事务等 return repo.save(user); } }Service // Spring 会把它注册成 Bean public class UserServiceImpl implements UserService { private final UserRepository repo; // 构造器注入DI public UserServiceImpl(UserRepository repo) { this.repo repo; } Override public User getById(Long id) { return repo.findById(id).orElseThrow(() - new IllegalArgumentException(User not found)); } Override public User create(User user) { // 这里可以写业务校验、事务等 return repo.save(user); } }5.6UserController.java表现层javaRestController RequestMapping(/api/users) public class UserController { private final UserService userService; // 注入 Service public UserController(UserService userService) { this.userService userService; } GetMapping(/{id}) public User get(PathVariable Long id) { return userService.getById(id); } PostMapping public User add(RequestBody User user) { return userService.create(user); } }RestController RequestMapping(/api/users) public class UserController { private final UserService userService; // 注入 Service public UserController(UserService userService) { this.userService userService; } GetMapping(/{id}) public User get(PathVariable Long id) { return userService.getById(id); } PostMapping public User add(RequestBody User user) { return userService.create(user); } }5.7LogAspect.javaAOP 示例javaAspect Component public class LogAspect { // 切点所有 Service 方法 Pointcut(execution(* com.example.demo.service.*.*(..))) public void serviceMethods() {} Before(serviceMethods()) public void before(JoinPoint jp) { System.out.println([AOP] 调用: jp.getSignature()); } AfterReturning(pointcut serviceMethods(), returning ret) public void after(JoinPoint jp, Object ret) { System.out.println([AOP] 返回: ret); } }Aspect Component public class LogAspect { // 切点所有 Service 方法 Pointcut(execution(* com.example.demo.service.*.*(..))) public void serviceMethods() {} Before(serviceMethods()) public void before(JoinPoint jp) { System.out.println([AOP] 调用: jp.getSignature()); } AfterReturning(pointcut serviceMethods(), returning ret) public void after(JoinPoint jp, Object ret) { System.out.println([AOP] 返回: ret); } }运行后浏览器访问GET /api/users/1→ Controller 收到请求 → 调用 UserService → UserRepository 去数据库 → 结果回到 Controller → 返回 JSON。同时 LogAspect 在 Service 方法的前后自动打印日志我们没有在业务代码里写一行日志代码这就是 AOP 的力量。6️⃣ 小结从「启动」到「解耦」的关键点章节关键一句话启动流程SpringBootApplicationSpringApplication.run→ 创建 IoC 容器 → 自动扫描 Bean → 注入依赖 → 启动嵌入式 Tomcat准备接受请求。分层解耦Controller → Service → Repository → Entity每层只负责自己的职责用 接口 实现 保证 低耦合。IoC/DI把 对象创建交给 Spring我们只声明 需要啥AutowiredSpring 负责 给你准备好注入。AOP把 日志、事务、缓存 等跨业务的代码抽出来用 Aspect Advice 自动在目标方法前后执行业务代码保持干净。常用注解Component/Service/Repository/Controller声明 BeanAutowired/Inject/Resource注入Aspect切面SpringBootApplication启动入口。记住Spring Boot 之所以能让我们写 “几行代码就跑通一个 Web 项目”核心就在于 IoC/DI 把对象交给容器管理 AOP 把横切关注点抽离。只要掌握了 “容器 → 注入 → 分层 → 切面” 四步你就已经能够独立搭建、扩展、调试一个中小型 Java Web 项目了。