Spring boot相关
1.●问题1为什么扫描的是 com.example.demo 包因为主入口类在这个包下。com.example.demo ├── DemoApplication.java ← 主入口类在这个包├── HelloController.java ← 同包能被扫描到 ✓├── sub/│ ├── SomeService.java ← 子包也能被扫描到ComponentScan 的默认规则从主入口类所在的包开始往下扫描所有子包。不会往上层扫也不会扫其他包。所以如果把 HelloController 移到 com.other 包下Spring Boot 就找不到它了——除非你手动配置扫描范围。问题2带注解的类怎么理解注解可以标在类上也可以标在方法上它们是不同的东西RestController // ← 类注解标注在类定义上方描述这个类是什么角色public class HelloController {GetMapping(/) // ← 方法注解标注在方法上方描述这个方法怎么处理请求public String hello() {return Hello;}}Spring Boot 扫描时是分层理解的1. 先看类上的注解 → 决定这个类要不要管- 看到 RestController → 这是一个控制器我要注册它- 没有类注解 → 普通类不管它2. 再看方法上的注解 → 在已经管的类内部进一步细化- 看到 GetMapping(/) → 把这个方法和路由 / 绑定所以类注解是第一层筛选——如果一个类上没有任何 Spring 注解Spring Boot 直接跳过它里面的方法注解也不会被看。问题3Spring Boot 启动时会扫描所有类吗不会扫描所有类只扫描指定包下的类。具体流程是这样的Spring Boot 启动↓确定扫描起点主入口类所在的包 (com.example.demo)↓遍历这个包及所有子包下的 .class 文件↓对每个类检查类上有没有 Spring 相关注解有 RestController → 注册为控制器有 Service → 注册为服务层有 Repository → 注册为数据层有 Component → 注册为通用组件有 Configuration → 注册为配置类没有任何 Spring 注解 → 跳过不管它↓完成扫描启动 Tomcat准备接收请求所以不是所有类都会被扫描到也不是扫描到的类都会被注册——只有在指定包下 类上有 Spring 注解的类才会被纳入管理。扫描到的类会被创建成单例对象存进 Spring 容器全局共用2.Component — 通用员工当一个类不属于上面各层任何一种角色但又需要被 Spring 管理就用 Component职责辅助工具类不是核心业务角色但大家都会用到它。为什么要有这些区分如果所有类都用同一个注解Spring Boot 就不知道每个类该承担什么职责。区分角色后1. 职责清晰每个类只干自己的事2. 方便管理Spring Boot 可以对不同角色做不同处理比如 Controller 自动注册路由3. 团队协作前端工程师改 Controller数据库工程师改 Repository互不干扰ps:对于后端工程师来说Service 确实是写得最多、改得最多的因为业务逻辑最复杂、变化最频繁。但 Controller API定义和 Repository数据库操作也是后端工程师的职责——后端工程师负责整个后端不只是某一层3.对比项 ComponentScan 扫描 Bean 方法适用场景自己写的类 可以自己加注解第三方库的类、需特殊配置的对象不能对源码加注解创建方式 Spring 自动调用构造方法 你手动 new 或调用工厂方法能否传参 不能只能默认构造 可以自由传参配置进入容器 都一样存进去全局共用 都一样第2条路Bean 方法现在讲的 Configuration public class AppConfig { Bean // ← 标记这个方法的返回值要存入容器 public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // 手动创建对象 } } 这条路适合第三方库的对象——你没法给第三方类加 Service 注解所以只能用 Bean 手动创建并存入容器。 详细过程 ComponentScan 扫描到 AppConfig 类 ↓ 发现类上有 Configuration → 这是一个配置类我要仔细看它 ↓ 遍历里面的方法找所有带 Bean 注解的方法 ↓ 找到 passwordEncoder() 方法 ↓ 调用这个方法拿到返回值new BCryptPasswordEncoder() ↓ 把这个对象存入容器 容器.put(passwordEncoder, BCryptPasswordEncoder实例) ↓ 以后任何地方需要 PasswordEncoder直接从容器取不用自己 new4.依赖注入依赖注入拆开理解 - 依赖UserService 需要 PasswordEncoder 才能干活这个需求就叫依赖 - 注入不需要你自己创建由 Spring 从外部注入塞进来给你 传统方式手动创建依赖 UserService 自己 new PasswordEncoder → 自己解决依赖 依赖注入方式 Spring 把 PasswordEncoder 注入到 UserService 中 → 外部帮你解决依赖 所以**Autowired(自动装配) 帮我注入依赖**它就是依赖注入机制的一个注解标记。 Spring 中的三种注入方式 除了字段上加 Autowired还有另外两种写法 1. 字段注入最常见你一直在看的 Service public class UserService { Autowired private PasswordEncoder encoder; // 直接在字段上标注 } 2. 构造器注入推荐的方式 Service public class UserService { private final PasswordEncoder encoder; Autowired // 标在构造方法上 public UserService(PasswordEncoder encoder) { this.encoder encoder; // 构造时就注入 } } Spring 创建 UserService 时调用构造方法把容器中的 PasswordEncoder 作为参数传进来。 3. Setter 注入 Service public class UserService { private PasswordEncoder encoder; Autowired // 标在 setter 方法上 public void setEncoder(PasswordEncoder encoder) { this.encoder encoder; } } Spring 创建 UserService 后调用 setter 方法把对象传进来。实际开发中推荐构造器注入因为- 可以把字段设为 final注入后不可变更安全-依赖关系在构造时就确定不会出现对象创建了但依赖还没注入的问题- 不加 Autowired 也能工作Spring 4.3 只有一个构造方法时自动注入// 推荐写法省略 Autowired 也能自动注入Servicepublic class UserService {private final PasswordEncoder encoder;public UserService(PasswordEncoder encoder) { // 自动注入this.encoder encoder;}}5. 如何理解继承自parent可以避免依赖版本冲突 假设你的项目依赖了 A 和 B 两个库而它们各自又依赖了不同版本的 C 你的项目 ├── 库A需要 C 1.0 ├── 库B需要 C 2.0 问题来了两个版本的 C 都想被加载但 JVM 只能用一个版本。 如果你用了 C 1.0库B 可能报错用了 C 2.0库A 可能报错——这就是版本冲突。 你自己手动指定版本的话要搞清楚每个库依赖了什么版本的 C极其麻烦 !-- 你得自己研究到底该用哪个版本 -- dependency groupIdcom.example/groupId artifactIdC/artifactId version1.0 还是 2.0/version /dependency 有 parent 后怎么解决 spring-boot-starter-parent 内部预定义了几百个依赖的版本号这些版本都是 Spring 团队测试过、确认互相兼容的 所以你写依赖时不用写版本号 !-- 没有 parent → 你必须指定版本可能选错 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId version??? 我该写啥 ???/version /dependency !-- 有 parent → 版本号由 parent 统一管理不用你写 -- dependency6. Maven仓库管理Maven 的**仓库Repository**就是存放依赖 jar 包的地方分三种1. 本地仓库在你电脑上 - 默认路径C:\Users\你的用户名\.m2\repository- Maven 下载的 jar 包都存在这里项目引用时从这里取 - 只下载一次后续项目复用不用重复下载2. 中央仓库在互联网上- Maven 官方维护地址repo.maven.apache.org- 几乎所有开源库都在这里本地仓库没有时自动从这里下载3. 私有仓库公司内部- 大公司自建存放内部私有 jar 包- 需要在 pom.xml 里额外配置地址工作流程很简单项目需要一个jar → 先查本地仓库 → 没有就从中央仓库下载到本地 → 用本地那份7. 日志管理Spring Boot 项目中日志配置统一放在 src/main/resources 目录下logback-spring.xml 独立配置文件现在用的 放在 src/main/resources/logback-spring.xml功能完整可以自定义 appender、格式、滚动策略等。 Spring Boot 加载日志配置的优先级 1. logback-spring.xml推荐支持 Spring 特有功能 2. logback.xml纯 Logback 配置不支持 Spring profile 等 3. application.properties 中的 logging.* 配置最简单功能最少 如果你的 logback-spring.xml 存在它就是主配置application.properties 中的 logging.* 设置会被忽略。所以现在你项目里日志的完整控制权在 logback-spring.xml所有日志相关配置改这一个文件就行。8. 前端——中间件——后端架构Tomcat 不是再交给 Spring Boot而是内嵌在 Spring Boot 里面的。 它们不是两个独立的东西接力而是同一个进程 前端请求 → Nginx → Spring Boot应用(内嵌Tomcat) ↓ Tomcat接收HTTP请求 ↓ Spring MVC路由分发 ↓ 映射到HelloController方法处理 ↓ ↓ 返回响应 传统部署方式Spring Boot之前 独立Tomcat服务器 → 部署war包到里面 → Tomcat 应用是分离的 Spring Boot方式 Tomcat打包进jar → java -jar demo.jar 一键启动 → Tomcat和应用是一体的 所以简单说Nginx转发给Spring BootSpring Boot自己内部就包含了Tomcat来处理请求。9.yaml配置的优点properties格式 — 平铺每行一个配置 server.port8080 spring.application.namedemo logging.level.rootinfo logging.file.nameapp.log yaml格式 — 层级缩进树状结构 server: port: 8080 spring: application: name: demo logging: level: root: info file: name: app.log- 功能完全相同没有谁更强- Spring Boot都支持优先级一样同时存在时properties优先- 可以互相转换挑一种用就行实际项目中yaml更主流因为配置多了之后层级一目了然不用反复写前缀。你当前项目用的是properties配置少所以没什么差别想换yaml随时可以。10.相同配置按优先级加载不同配置可以同时生效11.松散绑定(去掉分隔符全转为小写字母来进行匹配)只适用于ConfigurationPropertiesValue 不支持。Value(${app.greeting-prefix}) 必须和 yaml 里的 key 完全一致。12.整合 JUnit是因为 Spring 项目里对象之间有依赖注入关系普通测试拿不到完整的对象必须启动 Spring 容器才行。