Rome库解析的RSS内容详解1. 顶级Feed信息Rome库通过SyndFeed对象解析整个RSS源的基本信息：SyndFeed feed = input.build(new XmlReader(feedUrl)); // 基本信息 String title = feed.getTitle(); // 标题 String description = feed.getDescription(); // 描述 String link = feed.getLink(); // 链接 String language = feed.getLanguage(); // 语言 Date publishedDate = feed.getPublishedDate(); // 发布日期 String copyright = feed.getCopyright(); // 版权信息 String author = feed.getAuthor(); // 作者 String feedType = feed.getFeedType(); // 源类型(RSS_0.9, RSS_1.0, RSS_2.0, ATOM_0.3, ATOM_1.0等) // 图片信息 SyndImage image = feed.getImage(); if (image != null) { String imageUrl = image.getUrl(); // 图片URL String imageTitle = image.getTitle(); // 图片标题 String imageLink = image.getLink(); // 图片链接 } // 分类信息 List<SyndCategory> categories = feed.getCategories(); for (SyndCategory category : categories) { String categoryName = category.getName(); // 分类名称 String taxonomyUri = category.getTaxonomyUri(); // 分类URI }2. 条目信息Rome库通过SyndEntry对象解析每个RSS条目的详细信息：List<SyndEntry> entries = feed.getEntries(); for (SyndEntry entry : entries) { // 基本信息 String entryTitle = entry.getTitle(); // 标题 String entryLink = entry.getLink(); // 链接 Date entryPublishedDate = entry.getPublishedDate(); // 发布日期 Date entryUpdatedDate = entry.getUpdatedDate(); // 更新日期 String entryAuthor = entry.getAuthor(); // 作者 String entryUri = entry.getUri(); // 唯一标识符(GUID) // 描述信息 SyndContent description = entry.getDescription(); if (description != null) { String descriptionType = description.getType(); // 内容类型(text/plain, text/html等) String descriptionValue = description.getValue(); // 内容值 } // 完整内容 List<SyndContent> contents = entry.getContents(); for (SyndContent content : contents) { String contentType = content.getType(); // 内容类型 String contentValue = content.getValue(); // 内容值 String contentMode = content.getMode(); // 内容模式 } // 分类信息 List<SyndCategory> entryCategories = entry.getCategories(); for (SyndCategory category : entryCategories) { String categoryName = category.getName(); // 分类名称 String taxonomyUri = category.getTaxonomyUri(); // 分类URI } // 附件信息(图片、音频、视频等) List<SyndEnclosure> enclosures = entry.getEnclosures(); for (SyndEnclosure enclosure : enclosures) { String enclosureUrl = enclosure.getUrl(); // 附件URL String enclosureType = enclosure.getType(); // 附件类型(image/jpeg, audio/mpeg等) long enclosureLength = enclosure.getLength(); // 附件大小(字节) } // 源信息 SyndFeed entrySource = entry.getSource(); if (entrySource != null) { String sourceTitle = entrySource.getTitle(); // 源标题 String sourceLink = entrySource.getLink(); // 源链接 } }3. 扩展模块Rome库还支持解析RSS和Atom的各种扩展模块：// Dublin Core模块(提供标准化的元数据) DCModule dcModule = (DCModule) entry.getModule(DCModule.URI); if (dcModule != null) { String creator = dcModule.getCreator(); // 创建者 List<String> subjects = dcModule.getSubjects(); // 主题 Date date = dcModule.getDate(); // 日期 } // Content模块(提供完整内容) ContentModule contentModule = (ContentModule) entry.getModule(ContentModule.URI); if (contentModule != null) { String encodedContent = contentModule.getEncoded(); // 完整HTML内容 } // iTunes模块(针对播客) ITunes iTunesModule = (ITunes) entry.getModule(ITunes.URI); if (iTunesModule != null) { String author = iTunesModule.getAuthor(); // 作者 String summary = iTunesModule.getSummary(); // 摘要 String duration = iTunesModule.getDuration(); // 时长 String image = iTunesModule.getImage(); // 图片 } // Media RSS模块(针对多媒体内容) MediaEntryModule mediaModule = (MediaEntryModule) entry.getModule(MediaModule.URI); if (mediaModule != null) { MediaContent[] mediaContents = mediaModule.getMediaContents(); for (MediaContent mediaContent : mediaContents) { String url = mediaContent.getReference().toString(); // 媒体URL String type = mediaContent.getType(); // 媒体类型 int width = mediaContent.getWidth(); // 宽度 int height = mediaContent.getHeight(); // 高度 } MediaThumbnail[] thumbnails = mediaModule.getMetadata().getThumbnail(); for (MediaThumbnail thumbnail : thumbnails) { String thumbUrl = thumbnail.getUrl().toString(); // 缩略图URL int thumbWidth = thumbnail.getWidth(); // 缩略图宽度 int thumbHeight = thumbnail.getHeight(); // 缩略图高度 } }Rome库API详细介绍1. SyndFeedInputSyndFeedInput是Rome库中用于解析RSS/Atom源的主要类。// 创建SyndFeedInput实例 SyndFeedInput input = new SyndFeedInput(); // 配置选项 input.setAllowDoctypes(false); // 是否允许DOCTYPE声明 input.setPreserveWireFeed(true); // 是否保留原始格式信息 input.setXmlHealerOn(true); // 是否修复损坏的XML // 从URL解析Feed SyndFeed feed = input.build(new XmlReader(new URL("http://example.com/rss"))); // 从文件解析Feed SyndFeed feed = input.build(new XmlReader(new File("feed.xml"))); // 从字符串解析Feed SyndFeed feed = input.build(new StringReader(xmlString));2. SyndFeedSyndFeed表示整个RSS/Atom源的信息。// 获取基本信息 String title = feed.getTitle(); // 获取标题 feed.setTitle("New Title"); // 设置标题 String description = feed.getDescription(); // 获取描述 feed.setDescription("New Description"); // 设置描述 String link = feed.getLink(); // 获取链接 feed.setLink("http://example.com"); // 设置链接 Date pubDate = feed.getPublishedDate(); // 获取发布日期 feed.setPublishedDate(new Date()); // 设置发布日期 // 获取和设置条目 List<SyndEntry> entries = feed.getEntries(); // 获取所有条目 feed.setEntries(newEntries); // 设置条目列表 // 获取和设置图片 SyndImage image = feed.getImage(); // 获取图片 feed.setImage(newImage); // 设置图片 // 获取和设置分类 List<SyndCategory> categories = feed.getCategories(); // 获取分类 feed.setCategories(newCategories); // 设置分类 // 获取和设置作者 String author = feed.getAuthor(); // 获取作者 feed.setAuthor("New Author"); // 设置作者 // 获取和设置版权信息 String copyright = feed.getCopyright(); // 获取版权信息 feed.setCopyright("Copyright 2023"); // 设置版权信息3. SyndEntrySyndEntry表示RSS/Atom源中的单个条目。// 创建新条目 SyndEntry entry = new SyndEntryImpl(); // 设置基本信息 entry.setTitle("Entry Title"); // 设置标题 entry.setLink("http://example.com/article"); // 设置链接 entry.setPublishedDate(new Date()); // 设置发布日期 entry.setUpdatedDate(new Date()); // 设置更新日期 entry.setAuthor("Author Name"); // 设置作者 entry.setUri("http://example.com/unique-id"); // 设置唯一标识符 // 设置描述 SyndContent description = new SyndContentImpl(); description.setType("text/html"); description.setValue(" Article description "); entry.setDescription(description); // 添加完整内容 SyndContent content = new SyndContentImpl(); content.setType("text/html"); content.setValue(" Full article content "); List<SyndContent> contents = new ArrayList<>(); contents.add(content); entry.setContents(contents); // 添加分类 SyndCategory category = new SyndCategoryImpl(); category.setName("Technology"); List<SyndCategory> categories = new ArrayList<>(); categories.add(category); entry.setCategories(categories); // 添加附件 SyndEnclosure enclosure = new SyndEnclosureImpl(); enclosure.setUrl("http://example.com/image.jpg"); enclosure.setType("image/jpeg"); enclosure.setLength(12345); List<SyndEnclosure> enclosures = new ArrayList<>(); enclosures.add(enclosure); entry.setEnclosures(enclosures);4. SyndContentSyndContent表示内容块，如描述或完整内容。// 创建内容 SyndContent content = new SyndContentImpl(); // 设置内容类型 content.setType("text/html"); // HTML内容 // 或 content.setType("text/plain"); // 纯文本内容 // 设置内容值 content.setValue(" This is HTML content "); // 设置内容模式 content.setMode("escaped"); // 内容已转义5. SyndEnclosureSyndEnclosure表示附件，如图片、音频或视频。// 创建附件 SyndEnclosure enclosure = new SyndEnclosureImpl(); // 设置URL enclosure.setUrl("http://example.com/image.jpg"); // 设置MIME类型 enclosure.setType("image/jpeg"); // 图片 // 或 enclosure.setType("audio/mpeg"); // 音频 // 或 enclosure.setType("video/mp4"); // 视频 // 设置大小(字节) enclosure.setLength(12345);
Redis学习指南1. NoSQL概述什么是 NoSQLNoSQL（Not Only SQL）是一类非关系型数据库，设计初衷是解决传统关系型数据库在处理大规模数据、高并发和灵活性需求时的局限性。它通常不依赖固定的表结构，适合分布式系统和高性能场景。NoSQL 与传统关系型数据库的区别数据模型：NoSQL 使用键值对、文档、列族、图等模型，而传统数据库使用二维表结构。扩展性：NoSQL 支持水平扩展，通过增加节点提升性能；传统数据库多为垂直扩展。一致性：NoSQL 通常提供最终一致性，而传统数据库强调强一致性（ACID）。NoSQL 的分类键值存储：如 Redis、DynamoDB文档存储：如 MongoDB、CouchDB列族存储：如 Cassandra、HBase图数据库：如 Neo4j、ArangoDB2. Redis介绍什么是 RedisRedis（Remote Dictionary Server）是一个开源的、高性能的键值对数据库。它将数据存储在内存中，支持多种数据结构，常用于缓存、消息队列等场景。Redis 的特点和优势高性能：数据存储在内存，读写速度极快。丰富的数据结构：支持字符串、哈希、列表、集合、有序集合等。持久化：提供 RDB 和 AOF 两种方式将数据保存到磁盘。多功能：支持事务、发布/订阅、Lua 脚本等。Redis 的应用场景缓存系统：加速数据访问排行榜：如游戏积分榜消息队列：实现异步任务处理会话管理：存储用户登录状态3. Redis-CLI常用命令3.1 基础操作# 连接Redis redis-cli # 测试连接 ping # 切换数据库（默认16个） select 0 # 查看所有key keys * # 删除key del key_name # 设置key过期时间 expire key_name seconds #检查键是否存在 exists key3.2 数据类型操作字符串操作# 设置值 set key value # 获取值 get key # 批量设置 mset key1 value1 key2 value2 # 数值增减 incr key decr key列表操作# 从左边插入 lpush list_name value # 从右边插入 rpush list_name value # 获取列表范围 lrange list_name 0 -1哈希操作# 设置哈希值 hset user:1 name zhangsan hset user:1 age 20 # 获取哈希值 hget user:1 name集合操作# 添加集合元素 sadd myset value1 value2 # 查看集合成员 smembers myset有序集合操作# 添加元素和分数 zadd scoreboard 100 user1 zadd scoreboard 200 user24. Redis数据结构详解4.1 String（字符串）最基本的数据类型存储单个字符串、整数、浮点数最大存储512MB使用场景：缓存用户信息计数器分布式锁代码示例：// 设置字符串 redisTemplate.opsForValue().set("user:1:name", "张三"); // 获取字符串 String name = (String) redisTemplate.opsForValue().get("user:1:name");4.2 List（列表）有序集合支持从两端添加和弹出元素底层是双向链表使用场景：消息队列最新消息排行异步任务处理代码示例：// 左侧推入 redisTemplate.opsForList().leftPush("tasks", "task1"); // 右侧弹出 Object task = redisTemplate.opsForList().rightPop("tasks");4.3 Hash（哈希）存储对象类似Map结构字段和值都是字符串使用场景：存储对象用户信息存储购物车代码示例：// 存储用户信息 redisTemplate.opsForHash().put("user:1", "name", "张三"); redisTemplate.opsForHash().put("user:1", "age", "25"); // 获取用户信息 String name = (String) redisTemplate.opsForHash().get("user:1", "name");4.4 Set（集合）无序集合元素唯一支持集合运算使用场景：去重标签系统社交关系代码示例：// 添加元素 redisTemplate.opsForSet().add("tags", "Java", "Redis", "分布式"); // 查看成员 Set<String> tags = redisTemplate.opsForSet().members("tags");4.5 Sorted Set（有序集合）每个元素关联一个分数可按分数排序元素唯一使用场景：排行榜实时数据统计代码示例：// 添加元素和分数 redisTemplate.opsForZSet().add("leaderboard", "user1", 100); redisTemplate.opsForZSet().add("leaderboard", "user2", 200);Redis Java客户端5.1 Jedis传统的Redis Java客户端Maven依赖：<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> </dependency>使用示例：import redis.clients.jedis.Jedis; public class JedisDemo { public static void main(String[] args) { Jedis jedis = new Jedis("localhost", 6379); jedis.set("key", "Hello Redis"); System.out.println(jedis.get("key")); // 输出：Hello Redis jedis.close(); } }SpringRedisTemplateSpringBoot推荐的Redis客户端依赖： <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>配置： spring: data: redis: client-name: springboot_mybatis #redis客户端名称 host: 127.0.0.1 #redis地址 port: 6379 #redis端口 password: #redis密码 lettuce: #lettuce连接池配置 pool: #连接池配置 max-active: 8 #最大连接数 max-idle: 8 #最大空闲连接数 min-idle: 0 #最小空闲连接数 max-wait: 1000ms #最大等待时间配置类：@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 配置序列化方式 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); template.setDefaultSerializer(serializer); return template; } }springCache整合Redis@Cacheable(value = "users", key = "#id") public User getUserById(Long id) { return userMapper.selectById(id); }常见问题缓存穿透：使用布隆过滤器缓存雪崩：设置随机过期时间缓存一致性：使用Canal、消息队列同步
HTTP协议及Spring Boot RESTful接口设计1. HTTP协议概述HTTP（HyperText Transfer Protocol）是一个无状态的应用层协议，主要用于客户端与服务器之间的数据传输。2. 常见HTTP请求方法GET用于获取资源。请求的数据附加在URL中。POST用于提交数据，数据包含在请求体中。适用于表单提交、文件上传等。PUT用于更新资源。将请求体中的数据替换服务器上的资源。DELETE用于删除资源。3. HTTP状态码2xx - 请求成功200 OK201 Created3xx - 重定向301 Moved Permanently302 Found4xx - 客户端错误400 Bad Request404 Not Found5xx - 服务器错误500 Internal Server ErrorHTTP 方法HTTP 协议中常见的请求方法有：GET: 请求获取资源，通常用于查询操作。POST: 请求提交数据，通常用于创建资源。PUT: 请求更新资源，通常用于替换指定的资源。DELETE: 请求删除资源。HTTP 状态码200 OK: 请求成功，响应内容在消息体中。201 Created: 请求成功，资源已被创建。400 Bad Request: 请求格式错误。404 Not Found: 请求的资源未找到。500 Internal Server Error: 服务器内部错误。请求与响应的结构请求：包含方法、URL、头部、请求体。响应：包含状态码、头部、响应体。4. RESTful架构原则资源通过URI唯一标识请求无状态使用标准HTTP方法（GET、POST、PUT、DELETE等）1. RESTful API 的设计原则资源（Resource）: 在REST中，资源是系统中的对象，如用户、商品、订单等。每个资源通过URL唯一标识。无状态（Stateless）: 客户端与服务器之间的通信是无状态的，每次请求都必须包含处理请求所需的所有信息。统一接口（Uniform Interface）: RESTful API使用统一的URL和HTTP方法操作资源。2. 常见RESTful设计模式GET /users: 获取所有用户。GET /users/{id}: 根据ID获取单个用户。POST /users: 创建新用户。PUT /users/{id}: 更新用户信息。DELETE /users/{id}: 删除指定用户。5. Spring Boot中HTTP方法示例@RestController @RequestMapping("/api/products") public class ProductController { @GetMapping public List<Product> getAllProducts() { return productService.getAllProducts(); } @GetMapping("/{id}") public Product getProductById(@PathVariable Long id) { return productService.getProductById(id); } @PostMapping public Product createProduct(@RequestBody Product product) { return productService.createProduct(product); } @PutMapping("/{id}") public Product updateProduct(@PathVariable Long id, @RequestBody Product product) { return productService.updateProduct(id, product); } @DeleteMapping("/{id}") public void deleteProduct(@PathVariable Long id) { productService.deleteProduct(id); } }6.Spring Boot 实现 RESTful API实体类定义：User定义用户实体类，用来接收和返回用户数据。java复制编辑package com.example.usermanagement.model; public class User { private Long id; private String name; private String email; // 构造方法、Getter 和 Setter public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }服务层：UserService服务层负责业务逻辑处理，模拟用户数据存储。java复制编辑package com.example.usermanagement.service; import com.example.usermanagement.model.User; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Optional; @Service public class UserService { private List<User> users = new ArrayList<>(); public UserService() { users.add(new User(1L, "John Doe", "john@example.com")); users.add(new User(2L, "Jane Smith", "jane@example.com")); } public List<User> getAllUsers() { return users; } public Optional<User> getUserById(Long id) { return users.stream().filter(user -> user.getId().equals(id)).findFirst(); } public User createUser(User user) { users.add(user); return user; } public Optional<User> updateUser(Long id, User user) { Optional<User> existingUser = getUserById(id); existingUser.ifPresent(u -> { u.setName(user.getName()); u.setEmail(user.getEmail()); }); return existingUser; } public boolean deleteUser(Long id) { return users.removeIf(user -> user.getId().equals(id)); } }控制器层：UserController控制器层用于处理HTTP请求，并调用服务层进行数据处理。java复制编辑package com.example.usermanagement.controller; import com.example.usermanagement.model.User; import com.example.usermanagement.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @GetMapping public List<User> getAllUsers() { return userService.getAllUsers(); } @GetMapping("/{id}") public User getUserById(@PathVariable Long id) { return userService.getUserById(id).orElse(null); } @PostMapping public User createUser(@RequestBody User user) { return userService.createUser(user); } @PutMapping("/{id}") public User updateUser(@PathVariable Long id, @RequestBody User user) { return userService.updateUser(id, user).orElse(null); } @DeleteMapping("/{id}") public boolean deleteUser(@PathVariable Long id) { return userService.deleteUser(id); } }使用APIPost进行测试1. 获取所有用户（GET请求）URL：http://localhost:8080/users方法：GET预期响应：json复制编辑[ { "id": 1, "name": "John Doe", "email": "john@example.com" }, { "id": 2, "name": "Jane Smith", "email": "jane@example.com" } ]2. 获取单个用户（GET请求）URL：http://localhost:8080/users/1方法：GET预期响应：json复制编辑{ "id": 1, "name": "John Doe", "email": "john@example.com" }3. 创建用户（POST请求）URL：http://localhost:8080/users方法：POST请求体：{ "id": 3, "name": "Alice Johnson", "email": "alice@example.com" }预期响应：{ "id": 3, "name": "Alice Johnson", "email": "alice@example.com" }4. 更新用户（PUT请求）URL：http://localhost:8080/users/3方法：PUT请求体：{ "name": "Alice Smith", "email": "alice.smith@example.com" }预期响应：{ "id": 3, "name": "Alice Smith", "email": "alice.smith@example.com" }5. 删除用户（DELETE请求）URL：http://localhost:8080/users/3方法：DELETE预期响应：true
SpringBoot JWT安全认证指南JWT概述什么是JWT？JSON Web Token (JWT) 是一种紧凑、自包含的方式，用于在各方之间安全地传输信息主要用于身份验证和信息交换由三部分组成：Header、Payload、SignatureJWT的优势无状态认证轻量级跨语言支持可扩展性强项目依赖配置Maven依赖<dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> </dependencies>JWT配置属性类import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "jwt") public class JwtProperties { private String secret; private Long expiration; // Getter and Setter public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } public Long getExpiration() { return expiration; } public void setExpiration(Long expiration) { this.expiration = expiration; } }application.ymljwt: secret: your_secure_secret_key_here expiration: 3600000 # 1小时过期时间配置启动类import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(JwtProperties.class) public class AppConfig { }JwtUtil工具类package com.cq.rssdemo.utils; import com.cq.rssdemo.config.JwtProperties; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtUtil { @Autowired private JwtProperties jwtProperties; // 生成签名密钥 private Key getSigningKey() { // 确保秘钥长度至少为256位（32字节） byte[] keyBytes = jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8); // 如果秘钥太短，可以使用以下方法生成安全的秘钥 return Keys.hmacShaKeyFor(ensureKeyLength(keyBytes)); } // 确保密钥长度至少为32字节的辅助方法 private byte[] ensureKeyLength(byte[] originalKey) { if (originalKey.length >= 32) { return originalKey; } // 如果原始密钥太短，使用填充或重复方法扩展 byte[] extendedKey = new byte[32]; for (int i = 0; i < 32; i++) { extendedKey[i] = originalKey[i % originalKey.length]; } return extendedKey; } // 从token中获取用户名 public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } // 获取token过期时间 public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } // 通用的获取Claim的方法 public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } // 解析token的所有声明 private Claims getAllClaimsFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody(); } // 检查token是否已过期 private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } // 生成token public String generateToken(String username) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, username); } // 生成token的具体实现 private String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration())) .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } // 验证token是否有效 public Boolean validateToken(String token, String username) { final String tokenUsername = getUsernameFromToken(token); return (tokenUsername.equals(username) && !isTokenExpired(token)); } // 刷新token public String refreshToken(String token) { final Claims claims = getAllClaimsFromToken(token); claims.setIssuedAt(new Date()); claims.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration())); return Jwts.builder() .setClaims(claims) .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } }常见问题与解决方案秘钥长度问题确保秘钥长度至少为256位（32字节）使用Keys.hmacShaKeyFor()生成安全秘钥避免使用过短或可预测的秘钥秘钥管理建议使用环境变量存储秘钥定期轮换秘钥避免将秘钥硬编码使用安全的秘钥管理系统测试import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest public class JwtUtilTest { @Autowired private JwtUtil jwtUtil; private String username; private String token; @BeforeEach public void setup() { // 设置测试用户名 username = "testuser@example.com"; // 生成测试Token token = jwtUtil.generateToken(username); } @Test public void testTokenGeneration() { // 测试令牌生成 assertNotNull(token, "Token should not be null"); assertTrue(token.length() > 0, "Token should have length"); } @Test public void testUsernameExtraction() { // 测试从令牌中提取用户名 String extractedUsername = jwtUtil.getUsernameFromToken(token); assertEquals(username, extractedUsername, "Extracted username should match original"); } @Test public void testTokenValidation() { // 测试令牌验证 boolean isValid = jwtUtil.validateToken(token, username); assertTrue(isValid, "Token should be valid"); } @Test public void testTokenValidationWithWrongUsername() { // 测试使用错误用户名验证 boolean isValid = jwtUtil.validateToken(token, "wronguser"); assertFalse(isValid, "Token should be invalid with wrong username"); } @Test public void testTokenExpiration() throws InterruptedException { // 模拟令牌过期场景 // 注意：这里需要在application.yml中设置很短的过期时间，比如1秒 Thread.sleep(2000); // 等待超过令牌过期时间 boolean isValid = jwtUtil.validateToken(token, username); assertFalse(isValid, "Expired token should be invalid"); } @Test public void testTokenRefresh() { // 测试令牌刷新 String originalToken = token; String refreshedToken = jwtUtil.refreshToken(originalToken); assertNotNull(refreshedToken, "Refreshed token should not be null"); assertNotEquals(originalToken, refreshedToken, "Refreshed token should be different from original"); } @Test public void testMultipleTokenGeneration() { // 测试为不同用户生成令牌 String user1Token = jwtUtil.generateToken("user1"); String user2Token = jwtUtil.generateToken("user2"); assertNotEquals(user1Token, user2Token, "Tokens for different users should be unique"); } @Test public void testTokenClaims() { // 测试提取令牌声明 String extractedUsername = jwtUtil.getUsernameFromToken(token); assertNotNull(extractedUsername, "Username should be extractable from token"); assertEquals(username, extractedUsername, "Extracted username should match original"); } }静态使用Jwtutilimport io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import java.security.Key; import java.util.Date; public class JwtUtil { // 秘钥，可以自行修改 private static final String SECRET = "YourSecretKeyForJwtTokenGeneration2024"; // 过期时间，1小时 private static final long EXPIRATION_TIME = 3600000; // 生成token public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } // 获取签名密钥 private static Key getSigningKey() { return Keys.hmacShaKeyFor(SECRET.getBytes()); } // 验证token public static boolean validateToken(String token, String username) { try { String tokenUsername = Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody() .getSubject(); return tokenUsername.equals(username) && !isTokenExpired(token); } catch (Exception e) { return false; } } // 检查token是否过期 private static boolean isTokenExpired(String token) { Date expiration = Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody() .getExpiration(); return expiration.before(new Date()); } // 从token获取用户名 public static String getUsernameFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody() .getSubject(); } }使用示例：javaCopy// 生成token String token = JwtUtil.generateToken("username"); // 验证token boolean isValid = JwtUtil.validateToken(token, "username"); // 获取用户名 String username = JwtUtil.getUsernameFromToken(token);主要特点：静态方法，方便直接调用简单的token生成和验证硬编码秘钥（小项目可以接受）默认1小时过期时间异常处理简单直接需要在pom.xml中添加：<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
Springboot快速入门第一章.Springboot基本介绍Spring Boot倡导约定优于配置，将简化开发发挥到极致。使用Spring Boot框架可以快速构建Spring应用，再也不需要大量的繁琐的的各种配置。Spring Boot框架设计的目标是：程序员关注业务逻辑就行了，环境方面的事儿交给Spring Boot就行。Spring Boot特性：快速创建独立的Spring应用程序。（Spring支持的SpringBoot都支持，也就是说SpringBoot全方位支持IoC，AOP等）嵌入式的Tomcat、Jetty、Undertow容器。（web服务器本身就是几个jar包，Spring Boot框架自动嵌入了。）需要什么功能时只需要引入对应的starter启动器即可。（启动器可以自动管理这个功能相关的依赖，自动管理依赖版本的控制）尽最大努力，最大可能的自动配置Spring应用和第三方库。（例如：如果要进行事务的控制，不用做任何事务相关的配置，只需要在service类上添加@Transactional注解即可。）没有代码生成，没有XML配置。（Spring Boot的应用程序在启动后不会动态地创建新的Java类，所有逻辑都是在编译期就已经确定好的）提供了生产监控的支持，例如健康检查，度量信息，跟踪信息，审计信息等。也支持集成外部监控系统。Spring Boot的开箱即用和约定优于配置：开箱即用：Spring Boot框架设计得非常便捷，开发者能够在几乎不需要任何复杂的配置的情况下，快速搭建并运行一个功能完备的Spring应用。约定优于配置：“约定优于配置”（Convention Over Configuration, CoC）是一种软件设计哲学，核心思想是通过提供一组合理的默认行为来减少配置的数量，从而简化开发流程。例如：Spring Boot默认约定了使用某个事务管理器，在事务方面不需要做任何配置，只需要在对应代码的位置上使用@Transactional注解即可。Starter-启动器在 Spring Boot 中，启动器（Starter）本质上是一个简化依赖管理的概念。Spring Boot 的启动器本质上就是一组预定义的依赖集合，它们被组织成一个个 Maven的依赖，以方便开发者快速集成特定的功能模块。如果你想做web开发，只需要引入web启动器。web启动器会自动引入web开发所需要的子依赖。依赖聚合： 每个启动器通常对应一个特定的功能集或者一个完整的应用模块，如 spring-boot-starter-web 就包含了构建 Web 应用所需的所有基本依赖项，如 Spring MVC, Tomcat 嵌入式容器等。依赖传递： 当你在项目中引入一个启动器时，它不仅会把自身作为依赖加入到你的项目中，还会把它的所有直接依赖项（transitive dependencies）也加入进来。这意味着你不需要单独声明这些依赖项，它们会自动成为项目的一部分。版本管理： 启动器内部已经指定了所有依赖项的具体版本，这些版本信息存储在一个公共的 BOM（Bill of Materials，物料清单）文件中，通常是 spring-boot-dependencies。当引入启动器时，实际上也间接引用了这个 BOM，从而确保了所有依赖项版本的一致性。自动配置： 许多启动器还提供了自动配置（Auto-configuration），这是一种机制，允许 Spring Boot 根据类路径上的可用组件自动设置你的应用程序。例如，如果类路径上有 Spring MVC 和嵌入式 Tomcat，则 Spring Boot 会自动配置它们，并准备好一个 web 应用程序。官方启动器命名特点：spring-boot-starter-*非官方启动器命名特点：*-spring-boot-starterSpring Boot核心注解@SpringBootApplication注解Spring Boot的主入口程序被@SpringBootApplication注解标注，可见这个注解的重要性，查看它的源码：可以看出这个注解属于组合注解。拥有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan的功能。@SpringBootConfiguration注解@SpringBootConfiguration注解的源码如下：可以看到这个注解的被@Configuration标注，说明主入口程序是一个配置类。也就是说主入口中的方法可以被@Bean注解标注，被@Bean注解的标注的方法会被Spring容器自动调用，并且将该方法的返回对象纳入IoC容器的管理。测试一下：@SpringBootApplication public class Sb305CoreApplication { @Bean public Date getNowDate(){ // 方法名作为bean的id return new Date(); } public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Sb305CoreApplication.class, args); Date dateBean1 = applicationContext.getBean(Date.class); System.out.println(dateBean1); Date dateBean2 = applicationContext.getBean("getNowDate", Date.class); System.out.println(dateBean2); } }执行结果：通过测试我们也认证了这一点：SpringBoot主入口类实际上就是一个配置类。这个配置类也可以称为源，起源的意思，SpringBoot从这个配置类开始加载项目中所有的bean。@EnableAutoConfiguration注解该注解表示启用自动配置。Spring Boot 会根据你引入的依赖自动为你配置好一系列的 Bean，无需手动编写复杂的配置代码。例如：如果你在SpringBoot项目中进行了如下配置：spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/springboot spring.datasource.username=root spring.datasource.password=123456并且在依赖中引入了mybatis依赖/mybatis启动器，那么SpringBoot框架将为你自动化配置以下bean：SqlSessionFactory: MyBatis的核心工厂SqlSessionFactory会被自动配置。这个工厂负责创建SqlSession实例，后者用来执行映射文件中的SQL语句。TransactionManager: DataSourceTransactionManager会被自动配置来管理与数据源相关的事务。@ComponentScan注解这个注解的作用是：启动组件扫描功能，代替spring框架xml文件中这个配置：<context:component-scan base-package="com.powernode.sb305core"/>因此被@SpringBootApplication注解标注之后，会启动组件扫描功能，扫描的包是主入口程序所在包及子包，因此如果一个bean要纳入IoC容器的管理则必须放到主入口程序所在包及子包下。放到主入口程序所在包之外的话，扫描不到。测试一下：HelloController代码如下：@RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "hello world!"; } }启动服务器测试：要让bean纳入IoC容器的管理，必须将类放到主入口程序同级目录下，或者子目录下。Spring Boot的单元测试test-starter引入以及测试类编写使用单元测试应该如何调用service对象上的方法呢？在使用脚手架创建Spring Boot项目时，为我们生成了单元测试类，如下：当然，如果要使用单元测试，需要引入单元测试启动器，如果使用脚手架创建SpringBoot项目，这个test启动器会自动引入：<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>@SpringBootTest注解@SpringBootTest 会创建一个完整的 Spring 应用程序上下文（Application Context），这个上下文包含了应用程序的所有组件和服务。以下是 @SpringBootTest 做的一些主要工作：创建 ApplicationContext：@SpringBootTest 使用 SpringApplication 的 run() 方法来启动一个 Spring Boot 应用程序上下文。这意味着它会加载应用程序的主配置类和其他相关的配置类。加载配置文件：它会查找并加载默认的配置文件，如 application.properties自动配置：如果应用程序依赖于 Spring Boot 的自动配置特性，@SpringBootTest 会确保这些自动配置生效。这意味着它会根据可用的类和bean来自动配置一些组件，如数据库连接、消息队列等。注入依赖：使用 @SpringBootTest 创建的应用程序上下文允许你在测试类中使用 @Autowired 注入需要的 bean，就像在一个真实的 Spring Boot 应用程序中一样。总的来说，@SpringBootTest 为你的测试提供了尽可能接近实际运行时环境的条件，这对于验证应用程序的行为非常有用。注入service并调用@SpringBootTest class Sb306TestApplicationTests { @Autowired private UserService userService; @Test void contextLoads() { userService.save(); } }第二章 外部化配置什么是外部化配置外部化配置是指：将配置信息存储在应用程序代码之外的地方。这样配置信息可以独立于代码进行管理。这样方便了配置的修改，并且修改后不需要重新编译代码，也不需要重新部署项目。外部化配置的方式SpringBoot支持多种外部化配置方式，包括但不限于：properties文件YAML文件外部化配置的优势灵活性：配置文件可以独立于应用程序部署，这使得可以根据运行环境的不同来调整配置，而无需修改代码。易于维护：配置变更不需要重新构建和部署应用程序，降低了维护成本。安全性：敏感信息如数据库密码、API密钥等可以存储在外部，并且可以限制谁有权限访问这些配置信息。共享性：多实例或多服务可以共享相同的配置信息，减少重复配置的工作量。版本控制：配置文件可以存放在版本控制系统中，便于跟踪历史版本和回滚配置。总之，外部化配置使得配置更加灵活、安全、易于管理和共享，是现代云原生应用中非常推荐的做法application.propertiesapplication.properties配置文件是SpringBoot框架默认的配置文件。application.properties不是必须的，SpringBoot对于应用程序来说，都提供了一套默认配置（就是我们所说的自动配置）。如果你要改变这些默认的行为，可以在application.properties文件中进行配置。application.properties可以放在类路径当中，也可以放在项目之外。因此称为外部化配置。Spring Boot 框架在启动时会尝试从以下位置加载 application.properties 配置文件：file:./config/：首先在Spring Boot 当前工作目录下的 config 文件夹中查找。注意：如果没有找到**application.properties**会继续找**application.yml**，如果这两个都没有找到，才会进入以下位置查找，以此类推。file:./：如果在当前工作目录下config目录中找不到时，再从当前工作目录中查找。classpath:/config/：如果从工作目录中找不到，会从类路径中找，先从类路径的 /config/ 目录下寻找配置文件。classpath:/：如果在 /config/ 下没有找到，它会在类路径的根目录下查找。Spring Boot 会按照这个顺序来加载配置文件，如果在多个位置有相同的属性定义，那么最先检查的位置中的属性值将优先使用。如果你想要指定其他的配置文件位置或者改变默认的行为，可以通过 --spring.config.location= 后跟路径的方式来指定配置文件的具体位置。例如 ：java -jar sb3-01-first-web-1.0-SNAPSHOT.jar --spring.config.location=file:///E:\a\b\application.properties这样，Spring Boot 将会首先从 E:\a\b\ 这个路径加载配置文件。注意，这种方式可以用来覆盖默认的配置文件位置，并且可以结合以上提到的位置一起使用。注意：以上的--spring.config.location=file:///E:\a\b\application.properties就属于命令行参数，它将来会被传递到main方法的(String[] args)参数上。使用@Value注解@Value注解可以将application.properties/application.yml文件中的配置信息注入/绑定到java对象的属性上。语法格式：@Value("${key}")使用脚手架创建SpringBoot项目，不添加任何启动器：在resources/application.properties文件中进行如下配置：myapp.username=jack myapp.email=jack@123.com myapp.age=30编写service类：@Service("userService") public class UserService { @Value("${myapp.username}") private String username; @Value("${myapp.email}") private String email; @Value("${myapp.age}") private Integer age; public void printInfo(){ String str = String.join(",", username, email, String.valueOf(age)); System.out.println(str); } }编写单元测试：@SpringBootTest class Sb307ExternalConfigApplicationTests { @Autowired private UserService userService; @Test void test01() { userService.printInfo(); } }运行结果：使用@Value注解时也可以指定默认值，当指定默认值时，如果配置文件中没有指定配置值，则采用默认值。语法格式：@Value("${key:defalut}")@Service("userService") public class UserService { @Value("${myapp.username}") private String username; @Value("${myapp.email}") private String email; @Value("${myapp.age}") private Integer age; @Value("${myapp.password:123456}") private String password; public void printInfo(){ String str = String.join(",", username, email, String.valueOf(age), password); System.out.println(str); } }执行结果：YAMLYAML概述SpringBoot采用集中式配置管理，所有的配置都编写到一个配置文件中：application.properties如果配置非常多，层级不够分明，因此SpringBoot为了提高配置文件可读性，也支持YAML格式的配置文件：application.ymlYAML（YAML Ain't Markup Language）是一种人类可读的数据序列化格式，它通常用于配置文件，在各种编程语言中作为一种存储或传输数据的方式。YAML的设计目标是易于阅读和编写，同时保持足够的表达能力来表示复杂的数据结构。YAML文件的扩展名可以是**.yaml**或**.yml**。常见的数据存储和交换格式properties、XML、JSON、YAML这几种格式确实是用来存储和交换数据的常见方式，但它们各有特点和适用场景：Properties这种格式主要用于Java应用程序中的配置文件。它是键值对的形式，每一行是一个键值对，使用等号或冒号分隔键和值。特点是简单易懂，但在处理复杂结构的数据时显得力不从心。XML (eXtensible Markup Language)XML是一种标记语言，用来描述数据的格式。它支持复杂的数据结构，包括嵌套和属性。XML文档具有良好的结构化特性，适合传输和存储结构化的数据。但是，XML文档通常体积较大，解析起来也比较耗资源。JSON (JavaScript Object Notation)JSON是一种轻量级的数据交换格式，易于人阅读和编写，同时也易于机器解析和生成。它基于JavaScript的一个子集，支持多种数据类型，如数字、字符串、布尔值、数组和对象。JSON因为简洁和高效而广泛应用于Web应用程序之间进行数据交换。YAML (YAML Ain't Markup Language)YAML设计的目标之一就是让人类更容易阅读。它支持类似JSON的数据序列化，但提供了更多的灵活性，例如缩进来表示数据结构。YAML非常适合用来编写配置文件，因为它允许以一种自然的方式组织数据，并且可以包含注释和其他人类可读的元素。总结来说，这四种格式都可以用来存储和交换数据，但它们的设计初衷和最佳使用场景有所不同。选择哪种格式取决于具体的应用需求、数据复杂度、性能要求等因素。YAML的语法规则YAML的语法规则如下：数据结构：YAML支持多种数据类型，包括：字符串、数字、布尔值数组、list集合map键值对 等。YAML使用一个空格来分隔属性名和属性值，例如：properties文件中这样的配置：name=jackyaml文件中需要这样配置：name: jackYAML用换行+空格来表示层级关系。注意不能使用tab，必须是空格，空格数量无要求，大部分建议2个或4个空格。例如：properties文件中这样的配置：myapp.name=mallyaml文件中就需要这样配置：myapp: name: mall同级元素左对齐。例如：properties文件中有这样的配置：myapp.name=mall myapp.count=102. `yaml`文件中就应该这样配置： myapp: name: mall count: 10键必须是唯一的：在一个映射中，键必须是唯一的。注释：使用#进行注释。大小写敏感YAML的使用小细节第一：普通文本也可以使用单引号或双引号括起来：（当然普通文本也可以不使用单引号和双引号括起来。）单引号括起来：单引号内所有的内容都被当做普通文本，不转义（例如字符串中有\n，则\n被当做普通的字符串）双引号括起来：双引号中有 \n 则会被转义为换行符第二：保留文本格式| 将文本写到这个符号的下层，会自动保留格式。第三：文档切割--- 这个符号下面的配置可以认为是一个独立的yaml文件。便于庞大文件的阅读。Spring Boot框架同时支持properties和yaml。强调：在同一个目录下同时存在application.properties和application.yml时，SpringBoot优先解析application.properties<font 文件。配置文件合并一个项目中所有的配置全部编写到application.properties文件中，会导致配置臃肿，不易维护，有时我们会将配置编写到不同的文件中，例如：application-mysql.properties专门配置mysql的信息，application-redis.properties专门配置redis的信息，最终将两个配置文件合并到一个配置文件中。properties文件application-mysql.propertiesspring.datasource.username=root spring.datasource.password=123456application-redis.propertiesspring.data.redis.host=localhost spring.data.redis.port=6379application.propertiesspring.config.import=classpath:application-mysql.properties,classpath:application-redis.propertiesyaml文件application-mysql.ymlspring: datasource: username: root password: 789789application-redis.ymlspring: data: redis: host: localhost port: 6379application.ymlspring: config: import: - classpath:application-mysql.yml - classpath:application-redis.yml多环境切换在Spring Boot中，多环境切换是指在一个应用程序中支持多种运行环境配置的能力。这通常用于区分开发（development）、测试（testing）、预生产（staging）和生产（production）等不同阶段的环境。这种功能使得开发者能够在不同的环境中使用不同的配置，比如数据库连接信息、服务器端口、环境变量等，而不需要更改代码。这对于维护一个可移植且易于管理的应用程序非常重要。开发环境的配置文件名一般叫做：application-dev.propertiesspring.datasource.username=dev spring.datasource.password=dev123 spring.datasource.url=jdbc:mysql://localhost:3306/dev测试环境的配置文件名一般叫做：application-test.propertiesspring.datasource.username=test spring.datasource.password=test123 spring.datasource.url=jdbc:mysql://localhost:3306/test预生产环境的配置文件名一般叫做：application-preprod.propertiesspring.datasource.username=preprod spring.datasource.password=preprod123 spring.datasource.url=jdbc:mysql://localhost:3306/preprod生产环境的配置文件名一般叫做：application-prod.propertiesspring.datasource.username=prod spring.datasource.password=prod123 spring.datasource.url=jdbc:mysql://localhost:3306/prod如果你希望该项目使用生产环境的配置，你可以这样做：第一种方式：在application.properties文件中添加这个配置：spring.profiles.active=prod第二种方式：在命令行参数上添加：--spring.profiles.active=prod将配置绑定到bean绑定简单beanSpringBoot配置文件中的信息除了可以使用@Value注解读取之外，也可以将配置信息一次性赋值给Bean对象的属性。例如有这样的配置：application.ymlapp: name: jack age: 30 email: jack@123.comBean需要这样定义：package com.powernode.sb307externalconfig.bean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "app") public class AppBean { private String name; private Integer age; private String email; @Override public String toString() { return "AppBean{" + "name='" + name + '\'' + ", age=" + age + ", email='" + email + '\'' + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }说明：被绑定的bean，需要使用@ConfigurationProperties(prefix = "app")注解进行标注，prefix用来指定前缀，哪个是前缀，如下图所示：配置文件中的name、age、email要和bean对象的属性名name、age、email对应上。（属性名相同）并且bean中的所有属性都提供了setter方法。因为底层是通过setter方法给bean属性赋值的。这样的bean需要使用@Component注解进行标注，纳入IoC容器的管理。@Component注解负责创建Bean对象，@ConfigurationProperties(prefix = "app")注解负责给bean对象的属性赋值。bean的属性需要是非static的属性。编写测试程序，将bean对象输出，结果如下：@Configuration注解以上操作中使用了@Component注解进行了标注，来纳入IoC容器的管理。也可以使用另外一个注解@Configuration，用这个注解将Bean标注为配置类。多数情况下我们会选择使用这个注解，因为该Bean对象的属性对应的就是配置文件中的配置信息，因此这个Bean我们也可以将其看做是一个配置类。@Configuration @ConfigurationProperties(prefix = "app") public class AppBean { private String name; private Integer age; private String email; //setter and getter }运行测试程序：我们把这个Bean对象的类名打印一下看看：可以发现底层实际上创建了AppBean的代理对象AppBean$$SpringCGLIB。生成代理对象会影响效率，这里我们不需要使用代理功能，可以通过以下配置来取消代理机制：@Configuration(proxyBeanMethods = false) @ConfigurationProperties(prefix = "app") public class AppBean { private String name; private Integer age; private String email; //setter and getter }执行结果如下：绑定嵌套bean当一个Bean中嵌套了一个Bean，这种情况下可以将配置信息绑定到该Bean上吗？当然可以。有这样的一个配置：app: name: jack age: 30 email: jack@123.com address: city: BJ street: ChaoYang zipcode: 123456需要编写这样的两个Bean：package com.powernode.sb307externalconfig.bean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @ConfigurationProperties(prefix = "app") public class AppBean { private String name; private Integer age; private String email; private Address address; @Override public String toString() { return "AppBean{" + "name='" + name + '\'' + ", age=" + age + ", email='" + email + '\'' + ", address=" + address + '}'; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } package com.powernode.sb307externalconfig.bean; public class Address { private String city; private String street; private String zipcode; public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getZipcode() { return zipcode; } public void setZipcode(String zipcode) { this.zipcode = zipcode; } @Override public String toString() { return "Address{" + "city='" + city + '\'' + ", street='" + street + '\'' + ", zipcode='" + zipcode + '\'' + '}'; } } 执行测试程序，结果如下：@EnableConfigurationProperties与@ConfigurationPropertiesScan将AppBean纳入IoC容器的管理，之前我们说了两种方式：第一种是使用@Component，第二种是使用@Configuration。SpringBoot其实还提供了另外两种方式：第一种：@EnableConfigurationProperties第二种：@ConfigurationPropertiesScan这两个注解都是标注在SpringBoot主入口程序上的：@EnableConfigurationProperties(AppBean.class) @SpringBootApplication public class Sb307ExternalConfigApplication { public static void main(String[] args) { SpringApplication.run(Sb307ExternalConfigApplication.class, args); } }或者@ConfigurationPropertiesScan(basePackages = "com.powernode.sb307externalconfig.bean") @SpringBootApplication public class Sb307ExternalConfigApplication { public static void main(String[] args) { SpringApplication.run(Sb307ExternalConfigApplication.class, args); } }运行测试程序，执行结果如下：将配置赋值到Bean的Map/List/Array属性上代码如下：package com.powernode.sb307externalconfig.bean; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Arrays; import java.util.List; import java.util.Map; @ConfigurationProperties public class CollectionConfig { private String[] names; private List<Product> products; private Map<String, Vip> vips; @Override public String toString() { return "CollectionConfig{" + "names=" + Arrays.toString(names) + ", products=" + products + ", vips=" + vips + '}'; } public String[] getNames() { return names; } public void setNames(String[] names) { this.names = names; } public List<Product> getProducts() { return products; } public void setProducts(List<Product> products) { this.products = products; } public Map<String, Vip> getVips() { return vips; } public void setVips(Map<String, Vip> vips) { this.vips = vips; } } class Product { private String name; private Double price; @Override public String toString() { return "Product{" + "name='" + name + '\'' + ", price=" + price + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } } class Vip { private String name; private Integer age; @Override public String toString() { return "Vip{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }配置信息如下：application.yml#数组 names: - jackson - lucy - lili #List集合 products: - name: 西瓜 price: 3.0 - name: 苹果 price: 2.0 #Map集合 vips: vip1: name: 张三 age: 20 vip2: name: 李四 age: 22提醒：记得入口程序使用@ConfigurationPropertiesScan(basePackages = "com.powernode.sb307externalconfig.bean")进行标注。编写测试程序，执行结果如下：将配置绑定到第三方对象将配置文件中的信息绑定到某个Bean对象上，如果这个Bean对象没有源码，是第三方库提供的，怎么办？此时可以单独编写一个方法，在方法上使用以下两个注解进行标注：@Bean@ConfigurationProperties假设我们有这样一个类Address，代码如下：package com.powernode.sb307externalconfig.bean; public class Address { private String city; private String street; private String zipcode; public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getZipcode() { return zipcode; } public void setZipcode(String zipcode) { this.zipcode = zipcode; } @Override public String toString() { return "Address{" + "city='" + city + '\'' + ", street='" + street + '\'' + ", zipcode='" + zipcode + '\'' + '}'; } } 当然，我们是看不到这个源码的，只知道有这样一个字节码Address.class。大家也可以看到这个Address类上没有添加任何注解。假设我们要将以下配置绑定到这个Bean上应该怎么做？address: city: TJ street: XiangYangLu zipcode: 11111111实现代码如下：@Configuration public class ApplicationConfig { @Bean @ConfigurationProperties(prefix = "address") public Address getAddress(){ return new Address(); } }运行结果如下：指定数据来源之前所讲的内容是将Spring Boot框架默认的配置文件application.properties或application.yml作为数据的来源绑定到Bean上。如果配置信息没有在默认的配置文件中呢？可以使用@PropertySource注解指定配置文件的位置，这个配置文件可以是.properties，也可以是.xml。这里重点掌握.properties即可。在resources目录下新建a目录，在a目录下新建b目录，b目录中新建group-info.properties文件，进行如下的配置：group.name=IT group.leader=LaoDu group.count=20定义Java类Group，然后进行注解标注：package com.powernode.sb307externalconfig.bean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration @ConfigurationProperties(prefix = "group") @PropertySource("classpath:a/b/group-info.properties") public class Group { private String name; private String leader; private Integer count; @Override public String toString() { return "Group{" + "name='" + name + '\'' + ", leader='" + leader + '\'' + ", count=" + count + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLeader() { return leader; } public void setLeader(String leader) { this.leader = leader; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } } 以下三个注解分别起到什么作用：@Configuration：指定该类为配置类，纳入Spring容器的管理@ConfigurationProperties(prefix = "group")：将配置文件中的值赋值给Bean对象的属性@PropertySource("classpath:a/b/group-info.properties")：指定额外的配置文件编写测试程序，测试结果如下：@ImportResource注解创建Bean的三种方式总结：第一种方式：编写applicationContext.xml文件，在该文件中注册Bean，Spring容器启动时实例化配置文件中的Bean对象。第二种方式：@Configuration注解结合@Bean注解。第三种方式：@Component、@Service、@Controller、@Repository等注解。第二种和第三种我们都已经知道了。针对第一种方式，如果在SpringBoot框架中应该怎么实现呢？使用@ImportResource注解实现定义一个普通的Java类：Personpackage com.powernode.sb307externalconfig.bean; public class Person { private String name; private String age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } } 在resources目录下新建applicationContext.xml配置文件：<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="person" class="com.powernode.sb307externalconfig.bean.Person"> <property name="name" value="jackson"/> <property name="age" value="20"/> </bean> </beans>在SpringBoot主入口类上添加@ImportResource进行资源导入，这样applicationContext.xml文件中的Bean将会纳入IoC容器的管理：@ImportResource("classpath:applicationContext.xml") public class Sb307ExternalConfigApplication {}编写测试程序，看看是否可以获取到person这个bean对象：@SpringBootTest class Sb307ExternalConfigApplicationTests { @Autowired private Person person; @Test void test09(){ System.out.println(person); } }执行结果如下：因此，项目中如果有类似于Spring的这种xml配置文件，要想纳入IoC容器管理，需要在入口类上使用@ImportResource("classpath:applicationContext.xml")注解即可。EnvironmentSpringBoot框架在启动的时候会将系统配置，环境信息全部封装到**Environment**对象中，如果要获取这些环境信息，可以调用**Environment**接口的方法。在Spring Boot中，Environment接口提供了访问应用程序环境信息的方法，比如活动配置文件、系统环境变量、命令行参数等。Environment接口由Spring框架提供，Spring Boot应用程序通常会使用Spring提供的实现类AbstractEnvironment及其子类来实现具体的环境功能。Environment对象封装的主要数据包括：Active Profiles: 当前激活的配置文件列表。Spring Boot允许应用程序定义不同的环境配置文件（如开发环境、测试环境和生产环境），通过激活不同的配置文件来改变应用程序的行为。System Properties: 系统属性，通常是操作系统级别的属性，比如操作系统名称、Java版本等。System Environment Variables: 系统环境变量，这些变量通常是由操作系统提供的，可以在启动应用程序时设置特定的值。Command Line Arguments: 应用程序启动时传递给主方法的命令行参数。Property Sources: Environment还包含了一个PropertySource列表，这个列表包含了从不同来源加载的所有属性。PropertySource可以来自多种地方，比如配置文件、系统属性、环境变量等。在Spring Boot中，可以通过注入Environment来获取上述信息。例如：package com.powernode.springboot.bean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @Component public class SomeBean { @Autowired private Environment environment; public void doSome(){ // 直接使用这个环境对象，来获取环境信息，配置信息等。 String[] activeProfiles = environment.getActiveProfiles(); for (String activeProfile : activeProfiles) { System.out.println(activeProfile); } // 获取配置信息 String street = environment.getProperty("app.xyz.addr.street"); System.out.println(street); } } 通过这种方式，你可以根据环境的不同灵活地配置你的应用程序。Environment是一个非常有用的工具，它可以帮助你管理各种类型的配置信息，并根据不同的运行时条件做出相应的调整。Spring Boot中如何进行AOP的开发Spring Boot AOP概述面向切面编程AOP在Spring教程中已经进行了详细讲解，这里不再赘述，如果忘记的同学，可以重新听一下Spring教程中AOP相关的内容。这里仅带着大家在Spring Boot中实现AOP编程。Spring Boot的AOP编程和Spring框架中AOP编程的唯一区别是：引入依赖的方式不同。其他内容完全一样。Spring Boot中AOP编程需要引入aop启动器： <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>可以看到，当引入aop启动器之后，会引入aop依赖和aspectj依赖。aop依赖：如果只有这一个依赖，也可以实现AOP编程，这种方式表示使用了纯Spring AOP实现aop编程。aspectj依赖：一个独立的可以完成AOP编程的AOP框架，属于第三方的，不属于Spring框架。（我们通常用它，因为它的功能更加强大）Spring Boot AOP实现实现功能：项目中很多service，要求执行任何service中的任何方法之前记录日志。创建Spring Boot项目引入aop启动器项目名：sb3-08-aop <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>编写service并提供方法package com.powernode.aop.service; public interface OrderService { /** * 生成订单 */ void generate(); /** * 订单详情 */ void detail(); }package com.powernode.aop.service.impl; import com.powernode.aop.service.OrderService; import org.springframework.stereotype.Service; @Service("orderService") public class OrderServiceImpl implements OrderService { @Override public void generate(Integer id, String name) { System.out.println("生成订单"); } @Override public void detail(Integer id) { System.out.println("订单详情"); } } 编写切面package com.powernode.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Component // 纳入IoC容器 @Aspect // 指定该类为切面类 public class LogAspect { // 日期格式化器 private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS"); // 前置通知 // 切入点表达式：service包下任意类的任意方法 @Before("execution(* com.powernode.aop.service..*.*(..))") public void sysLog(JoinPoint joinPoint) throws Throwable { StringBuilder log = new StringBuilder(); LocalDateTime now = LocalDateTime.now(); String strNow = formatter.format(now); // 追加日期 log.append(strNow); // 追加冒号 log.append(":"); // 追加方法签名 log.append(joinPoint.getSignature().getName()); // 追加方法参数 log.append("("); Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { log.append(args[i]); if(i < args.length - 1) { log.append(","); } } log.append(")"); System.out.println(log); } } 测试package com.powernode.aop; import com.powernode.aop.service.OrderService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class Sb308AopApplicationTests { @Autowired private OrderService orderService; @Test void contextLoads() { orderService.generate(10, "name"); orderService.detail(10); } } 执行结果如下：第三章.整合持久层框架MyBatis引入mysql驱动以及mybatis的启动器依赖如下： <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>编写数据源配置前面提到过，Spring Boot配置统一可以编写到application.properties中，配置如下：# Spring Boot脚手架自动生成的 spring.application.name=sb3-05-springboot-mybatis # mybatis连接数据库的数据源 spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/springboot spring.datasource.username=root spring.datasource.password=123456以上的配置属于连接池的配置，连接池使用的是Spring Boot默认的连接池：HikariCP编写Mapper/dao/repository接口创建repositoryMapper/dao包，在该包下新建UserMapper接口，代码如下：@RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; // 获取所有用户 @GetMapping public List<User> getAllUsers() { return userService.GetAllUser(); } // 根据ID获取用户 @GetMapping("/{id}") public User getUserById(@PathVariable Long id) { return userService.getUserById(id).orElse(null); } // 创建用户 @PostMapping public User createUser(@RequestBody User user) { return userService.createUser(user); } // 更新用户 @PutMapping("/{id}") public User updateUser(@PathVariable Long id, @RequestBody User user) { return userService.updateUser(id, user).orElse(null); } // 删除用户 @DeleteMapping("/{id}") public boolean deleteUser(@PathVariable Long id) { return userService.deleteUser(id); } }编写Mapper接口的XML配置文件在resources目录下新建mapper目录，将来的mapper.xml配置文件放在这个目录下。生成mapper.xml：接下来就是编写SQL语句了，最终UserMapper.xml文件的配置如下：<?xml version="1.0" encoding="UTF-8" ?> <mapper namespace="com.cq.springboot_mybatis.mapper.UserMapper"> <insert id="insert" parameterType="User"> insert into user(name,email) values(#{name},#{email}) </insert> <delete id="delete" parameterType="long"> delete from user where id=#{id} </delete> select * from user </mapper>添加Mapper的扫描在Spring Boot的入口程序上添加如下的注解，来完成VipMapper接口的扫描：告诉MyBatis框架MapperXML文件的位置在application.properties配置文件中进行如下配置：mybatis.mapper-locations=classpath:mapper/*.xml测试整合MyBatis是否成功在Spring Boot主入口程序中获取Spring上下文对象ApplicationContext，从Spring容器中获取VipMapper对象，然后调用相关方法进行测试：package com.powernode.sb305springbootmybatis; import com.powernode.sb305springbootmybatis.model.Vip; import com.powernode.sb305springbootmybatis.repository.VipMapper; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @MapperScan(basePackages = {"com.powernode.sb305springbootmybatis.repository"}) @SpringBootApplication public class Sb305SpringbootMybatisApplication { public static void main(String[] args) { // 获取Spring上下文 ConfigurableApplicationContext applicationContext = SpringApplication.run(Sb305SpringbootMybatisApplication.class, args); // 根据id获取容器中的对象 VipMapper vipMapper = applicationContext.getBean("vipMapper", VipMapper.class); Vip vip = vipMapper.selectById(1L); System.out.println(vip); // 关闭Spring上下文 applicationContext.close(); } } 测试结果：测试结果中可以看到cardNumber属性没有赋值成功，原因是：表中的字段名叫做card_number，和实体类Vip的属性名cardNumber对应不上。解决办法两个：第一种方式：查询语句使用as关键字起别名，让查询结果列名和实体类的属性名对应上。再次测试：第二种方式：通过配置自动映射在application.properties配置文件中进行如下配置：mybatis.configuration.map-underscore-to-camel-case=truemap-underscore-to-camel-case 是一个配置项，主要用于处理数据库字段名与Java对象属性名之间的命名差异。在许多数据库中，字段名通常使用下划线（_）分隔单词，例如 first_name 或 last_name。而在Java代码中，变量名通常使用驼峰式命名法（camel case），如 firstName 和 lastName。当使用MyBatis作为ORM框架时，默认情况下它会将SQL查询结果映射到Java对象的属性上。如果数据库中的字段名与Java对象的属性名不一致，那么就需要手动为每个字段指定相应的属性名，或者使用某种方式来自动转换这些名称。map-underscore-to-camel-case 这个配置项的作用就是在查询结果映射到Java对象时，自动将下划线分隔的字段名转换成驼峰式命名法。这样可以减少手动映射的工作量，并提高代码的可读性和可维护性。mapper的xml文件中的sql语句仍然使用*的方式：测试结果如下：Lombok库Lombok 是一个 Java 库，它可以通过注解的方式减少 Java 代码中的样板代码。Lombok 自动为你生成构造函数、getter、setter、equals、hashCode、toString 方法等，从而避免了手动编写这些重复性的代码。这不仅减少了出错的机会，还让代码看起来更加简洁。Lombok只是一个编译阶段的库，能够帮我们自动补充代码，在Java程序运行阶段并不起作用。（因此Lombok库并不会影响Java程序的执行效率）Lombok 的主要注解@Data：等价于 @ToString, @EqualsAndHashCode, @Getter，@Setter, @RequiredArgsConstructor.用于生成：必要参数的构造方法、getter、setter、toString、equals 和 hashcode 方法。@Getter / @Setter：分别用于生成所有的 getter 和 setter 方法。可以作用于整个类，也可以作用于特定的字段。@NoArgsConstructor：生成一个无参构造方法。@AllArgsConstructor：生成一个包含所有实例变量的构造器。@RequiredArgsConstructor：生成包含所有被 final 修饰符修饰的实例变量的构造方法。如果没有**final**的实例变量，则自动生成无参数构造方法。@ToString / @EqualsAndHashCode：用于生成 toString 和 equals/hashCode 方法。这两个注解都有**exclude**属性，通过这个属性可以定制toString、hashCode、equals方法。在 Maven 的 pom.xml 文件中添加 Lombok 依赖：<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.34</version> <scope>provided</scope> </dependency>Lombok的其他常用注解@Value该注解会给所有属性添加final，给所有属性提供getter方法，自动生成toString、hashCode、equals通过这个注解可以创建不可变对象。package com.powernode.lomboktest.model; import lombok.Value; @Value public class Customer { Long id; String name; String password; }@BuilderGoF23种设计模式之一：建造模式建造模式（Builder Pattern）属于创建型设计模式。GoF23种设计模式之一。用于解决对象创建时参数过多的问题。它通过将对象的构造过程与其表示分离，使得构造过程可以逐步完成，而不是一次性提供所有参数。建造模式的主要目的是让对象的创建过程更加清晰、灵活和可控。简而言之，建造模式用于：简化构造过程：通过逐步构造对象，避免构造函数参数过多。提高可读性和可维护性：让构造过程更加清晰和有序。增强灵活性：允许按需配置对象的不同部分。这样可以更方便地创建复杂对象，并且使得代码更加易于理解和维护。建造模式的代码建造模式代码如下：package com.powernode.lomboktest.model; // 建造模式 public class Person { // 属性 private final String name; private final int age; private final String email; // 私有的全参数构造方法 private Person(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } public String getName() { return name; } public int getAge() { return age; } public String getEmail() { return email; } public static PersonBuilder builder() { return new PersonBuilder(); } // 静态内部类 public static class PersonBuilder { private String name; private int age; private String email; public PersonBuilder name(String name) { this.name = name; return this; } public PersonBuilder age(int age) { this.age = age; return this; } public PersonBuilder email(String email) { this.email = email; return this; } // 建造对象的核心方法 public Person build() { return new Person(name, age, email); } } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", email='" + email + '\'' + '}'; } public static void main(String[] args) { Person person = Person.builder() .name("jackson") .age(20) .email("jackson@123.com") .build(); System.out.println(person); } } 使用@Builder注解自动生成建造模式的代码该注解可以直接帮助我们生成以上的代码。使用@Builder注解改造以上代码。@Singular当被建造的对象的属性是一个集合，这个集合属性使用@Singular注解进行标注的话，可以连续调用集合属性对应的方法完成多个元素的添加。如果没有这个注解，则无法连续调用方法完成多个元素的添加。@Data @Builder // 建造模式 public class Person { // 属性 private final String name; private final int age; private final String email; // Singular翻译为：单数。表示一条一条添加 @Singular("addPhone") private final List<String> phones; public static void main(String[] args) { Person person = Person.builder() .name("jackson") .age(20) .email("jackson@123.com") .addPhone("15222020214") .addPhone("14875421424") .addPhone("16855241424") .build(); System.out.println(person); } }@Slf4jLombok 支持多种日志框架的注解，可以根据你使用的日志框架选择合适的注解。以下是 Lombok 提供的部分日志注解及其对应的日志框架：@Log4j：自动生成一个 org.apache.log4j.Logger 对象。适用于 Apache Log4j 1.x 版本。@Slf4j：自动生成一个 org.slf4j.Logger 对象。适用于 SLF4J（Simple Logging Facade for Java），这是一种日志门面，可以与多种实际的日志框架（如 Logback、Log4j 等）集成。@Log4j2：自动生成一个 org.apache.logging.log4j.Logger 对象。适用于 Apache Log4j 2.x 版本。第四章.自动配置概述（核心）SpringBoot的两大核心Spring Boot 框架的两大核心特性可以概括为“启动器”（Starter）和“自动配置”（Auto-configuration）。启动器（Starter）： Spring Boot 提供了一系列的 Starter POMs，它们是一组预定义的依赖关系。当你在项目中引入一个 Starter POM 时，它会自动包含所有必要的 Spring 组件以及合理的默认设置。开发者不需要手动管理复杂的依赖关系，也不需要担心版本冲突的问题，减少了配置上的出错可能。自动配置（Auto-Configuration）： 当添加了特定的 Starter POM 后，Spring Boot 会根据类路径上存在的 jar 包来自动配置 Bean（自动配置相关组件）（比如：SpringBoot发现类路径上存在mybatis相关的类，例如SqlSessionFactory.class，那么SpringBoot将自动配置mybatis相关的所有Bean。）。如果开发者没有显式地提供任何与特定功能相关的配置，Spring Boot 将使用其默认配置来自动设置这些功能。当然，如果需要的话，用户也可以覆盖这些默认设置。这两个特性结合在一起，使得使用 Spring Boot 开发应用程序变得更加简单快速，减少了大量的样板代码和重复配置的工作。让程序员专注业务逻辑的开发，在环境方面耗费最少的时间。不加任何启动器，spring boot默认会启动spring-boot-starter里面默认提供以下的beanorg.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory springbootApplication org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory org.springframework.boot.autoconfigure.AutoConfigurationPackages org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration propertySourcesPlaceholderConfigurer org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration mbeanExporter objectNamingStrategy mbeanServer org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor org.springframework.boot.context.internalConfigurationPropertiesBinder org.springframework.boot.context.properties.BoundConfigurationProperties org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter spring.jmx-org.springframework.boot.autoconfigure.jmx.JmxProperties org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration springApplicationAdminRegistrar org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration forceAutoProxyCreatorToUseClassProxying org.springframework.boot.autoconfigure.aop.AopAutoConfiguration org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration applicationAvailability org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration lifecycleProcessor spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration fileWatcher sslPropertiesSslBundleRegistrar sslBundleRegistry spring.ssl-org.springframework.boot.autoconfigure.ssl.SslProperties org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration threadPoolTaskExecutorBuilder org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration simpleAsyncTaskExecutorBuilder org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorConfiguration applicationTaskExecutor org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration threadPoolTaskSchedulerBuilder org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration simpleAsyncTaskSchedulerBuilder org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties org.springframework.aop.config.internalAutoProxyCreator引入web启动器：<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>可以发现，ioc容器中注册的bean总数量为160个：org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory testApplication org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory org.springframework.boot.autoconfigure.AutoConfigurationPackages org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration propertySourcesPlaceholderConfigurer org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration fileWatcher sslPropertiesSslBundleRegistrar sslBundleRegistry org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor org.springframework.boot.context.internalConfigurationPropertiesBinder org.springframework.boot.context.properties.BoundConfigurationProperties org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter spring.ssl-org.springframework.boot.autoconfigure.ssl.SslProperties org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration websocketServletWebServerCustomizer org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat tomcatServletWebServerFactory org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration servletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer server-org.springframework.boot.autoconfigure.web.ServerProperties webServerFactoryCustomizerBeanPostProcessor errorPageRegistrarBeanPostProcessor org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration dispatcherServlet spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration dispatcherServletRegistration org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration threadPoolTaskExecutorBuilder org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorBuilderConfiguration taskExecutorBuilder org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration simpleAsyncTaskExecutorBuilder org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorConfiguration applicationTaskExecutor org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration error beanNameViewResolver org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration conventionErrorViewResolver spring.web-org.springframework.boot.autoconfigure.web.WebProperties org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration errorAttributes basicErrorController errorPageCustomizer preserveErrorControllerTargetClassPostProcessor org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration welcomePageHandlerMapping welcomePageNotAcceptableHandlerMapping localeResolver themeResolver flashMapManager mvcConversionService mvcValidator mvcContentNegotiationManager requestMappingHandlerMapping mvcPatternParser mvcUrlPathHelper mvcPathMatcher viewControllerHandlerMapping beanNameHandlerMapping routerFunctionMapping resourceHandlerMapping mvcResourceUrlProvider defaultServletHandlerMapping requestMappingHandlerAdapter handlerFunctionAdapter mvcUriComponentsContributor httpRequestHandlerAdapter simpleControllerHandlerAdapter handlerExceptionResolver mvcViewResolver mvcHandlerMappingIntrospector viewNameTranslator org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter defaultViewResolver viewResolver requestContextFilter org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration formContentFilter org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration mbeanExporter objectNamingStrategy mbeanServer spring.jmx-org.springframework.boot.autoconfigure.jmx.JmxProperties org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration springApplicationAdminRegistrar org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration forceAutoProxyCreatorToUseClassProxying org.springframework.boot.autoconfigure.aop.AopAutoConfiguration org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration applicationAvailability org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration standardJacksonObjectMapperBuilderCustomizer spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration jacksonObjectMapperBuilder org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration parameterNamesModule org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration jacksonObjectMapper org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonMixinConfiguration jsonMixinModuleEntries jsonMixinModule org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration jsonComponentModule org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration lifecycleProcessor spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration stringHttpMessageConverter org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration mappingJackson2HttpMessageConverter org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration messageConverters org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration threadPoolTaskSchedulerBuilder org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerBuilderConfiguration taskSchedulerBuilder org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration simpleAsyncTaskSchedulerBuilder org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration httpMessageConvertersRestClientCustomizer restClientSsl restClientBuilderConfigurer restClientBuilder org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration restTemplateBuilderConfigurer restTemplateBuilder org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration tomcatWebServerFactoryCustomizer org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration characterEncodingFilter localeCharsetMappingsCustomizer org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration multipartConfigElement multipartResolver spring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartProperties org.springframework.aop.config.internalAutoProxyCreator也就是说，引入了web启动器后，ioc容器中增加了101个bean对象（加入了101个组件）。这101个bean对象都是为web开发而准备的，例如我们常见的：dispatcherServlet：DispatcherServlet 是 Spring MVC 的前端控制器，负责接收所有的 HTTP 请求，并将请求分发给适当的处理器（Controller）viewResolver：ViewResolver 是 Spring MVC 中用于将逻辑视图名称解析为实际视图对象的组件。它的主要作用是根据控制器返回的视图名称，找到对应的视图实现（如 JSP、Thymeleaf、Freemarker 等），并返回给 DispatcherServlet 用于渲染视图。characterEncodingFilter：字符集过滤器组件，解决请求和响应的乱码问题。mappingJackson2HttpMessageConverter：负责处理消息转换的组件。它可以将json字符串转换成java对象，也可以将java对象转换为json字符串。......每一个组件都有它特定的功能。没有使用SpringBoot之前，以上的很多组件都是需要手动配置的。默认配置springboot为功能的实现提供了非常多的默认配置.例如：tomcat服务器端口号在没有配置的情况下，默认是8080当然，也可以在application.properties文件中进行重新配置：server.port=8081这些配置最终都会通过@ConfigurationProperties(prefix="")注解绑定到对应的bean的属性上。这个Bean我们一般称为属性类。例如：ServerProperties：服务器属性类，专门负责配置服务器相关信息。@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties {}SpringBoot官方文档当中也有指导，告诉你都有哪些属性类，告诉你在application.properties中都可以配置哪些东西。默认值都是什么：条件注解如何做到按需加载的，依靠SpringBoot框架中的条件注解来实现的。Spring Boot框架中的@ConditionalOnXxx系列注解属于条件注解（Conditional Annotations），它们用于基于某些条件来决定是否应该创建一个或一组Bean。这些注解通常用在自动配置类上，以确保只有在特定条件满足时才会应用相应的配置。这里是一些常见的@ConditionalOnXxx注解及其作用：@ConditionalOnClass：当指定的类存在时，才创建Bean。@ConditionalOnMissingClass：当指定的类不存在时，才创建Bean。@ConditionalOnBean：当容器中存在指定的Bean时，才创建Bean。@ConditionalOnMissingBean：当容器中不存在指定的Bean时，才创建Bean。@ConditionalOnProperty：当配置文件中存在指定的属性时，才创建Bean。也可以设置属性值需要匹配的值。@ConditionalOnResource：当指定的资源存在时，才创建Bean。@ConditionalOnWebApplication：当应用程序是Web应用时，才创建Bean。@ConditionalOnNotWebApplication：当应用程序不是Web应用时，才创建Bean。使用这些注解可以帮助开发者根据不同的运行环境或配置来灵活地控制Bean的创建，从而实现更智能、更自动化的配置过程。这对于构建可插拔的模块化系统特别有用，因为可以根据实际需求选择性地启用或禁用某些功能。自动配置按需加载SpringBoot提供了非常多的自动配置类，有的是web相关的自动配置，有的是mail相关的自动配置。但是这些自动配置并不是全部生效，它是按需加载的。导入了哪个启动器，则该启动器对应的自动配置类才会被加载。这些自动配置类在哪里？任何启动器都会关联引入这样一个启动器：spring-boot-starter，它是springboot框架最核心的启动器。spring-boot-starter又关联引入了spring-boot-autoconfigure。所有的自动配置类都在这里。自动配置实现原理在程序没有开始执行之前都导入了哪些依赖在程序没有开始运行之前，我们先来分析一下，当导入web启动器之后，底层都一连串的导入了哪些依赖！从这里开始：导入了spring-boot-starter-web【web启动器】然后关联导入了spring-boot-starter、spring-boot-starter-json、spring-boot-starter-tomcat、spring-web、spring-webmvc注意：spring-boot-starter是springboot核心启动器，任何启动器在导入时，都会关联导入springboot核心启动器。核心启动器导入之后，关联导入了一个jar包：spring-boot-autoconfigure。注意：这个jar包中存放的是springboot框架官方支持的自动配置类。如下图：2. 官方支持的自动配置类有多少个呢，可以通过下图位置查看： 得知springboot3.3.5这个版本共152个自动配置类。自动配置类的命名规则是XxxxAutoConfiguration。提示：哪个自动配置类生效，就代表哪个配置文件生效，那么对应的技术就完成了整合，就可以进行对应技术的开发。从main方法开始执行之后都发生了什么以上分析的是在项目结构上已经完成了相关依赖的导入，这些自动配置了导入到了项目当中，那么在运行时哪些自动配置类会被加载？哪些自动配置类会生效呢？我们接下来进行程序运行阶段的分析：程序从main方法进入执行，主入口类上使用@SpringBootApplication进行了标注。@SpringBootApplication注解是复合注解，代表以下三个注解的功能：@SpringBootConfiguration：它被@Configuration标注，说明主入口类就是一个配置类，此时该配置开始加载。@ComponentScan：默认扫描的是主入口所在包以及子包。因此spring-boot-autoconfigure包是扫描不到的，按说XxxAutoConfiguration自动配置类是无法加载的！！！那么这些自动配置类又是如何加载和生效的呢？@EnableAutoConfiguration：自动配置类的加载和生效全靠它了。该注解被翻译为：启用自动配置。@EnableAutoConfiguration被@Import(AutoConfigurationImportSelector.class)标注@Import(AutoConfigurationImportSelector.class)的作用是：将AutoConfigurationImportSelector作为一个Bean加载到IoC容器中。这个Bean的作用是：负责收集和选择所有符合条件的自动配置类。添加断点，跟踪AutoConfigurationImportSelector源码：通过跟踪得知，这152个自动配置类的类名都会被加载到IoC容器中。注意：加载了152，并不是152个全部生效。这152个自动配置类底层是怎么查找的？通过以上源码跟踪，得知，是从下图位置加载的：最终哪些自动配置类生效了？最先获取到152个，经过上图的一层一层的过滤（条件注解），最终筛选了**26**个自动配置类，为什么这么少，因为你只引入了**web starter**。这26个配置就是做web开发需要的最少配置。具体怎么排除的，请看以下解释：configurations = removeDuplicates(configurations);去重：移除 configurations 列表中的重复项，确保每个配置类只出现一次。Set exclusions = getExclusions(annotationMetadata, attributes);获取排除列表：从注解元数据和属性中获取需要排除的配置类名称集合。因为@EnableAutoConfiguration注解还能这么用：@EnableAutoConfiguration(exclude = {排除列表}, excludeName = {排除列表})checkExcludedClasses(configurations, exclusions);检查排除：验证 configurations 中是否有被排除的类，如果有，可能会抛出异常或记录警告。configurations.removeAll(exclusions);移除排除项：从 configurations 列表中移除所有在 exclusions 集合中的配置类。configurations = getConfigurationClassFilter().filter(configurations);过滤配置类：使用 ConfigurationClassFilter 对 configurations 进行进一步过滤。这一行通过条件注解进行判断，例如 @ConditionalOnClass、@ConditionalOnMissingBean 等。fireAutoConfigurationImportEvents(configurations, exclusions);触发事件：触发自动配置导入事件，通知其他组件或监听器关于最终确定的配置类和排除的类。总结自动配置原理运行环境准备阶段引入web启动器最终传递引入了自动配置的jar包自动配置的jar包中有152个自动配置类，到此运行环境准备完毕。运行阶段@EnableAutoConfiguration 启用自动配置，将152个自动配置类全部加载到IoC容器中，然后根据开发场景筛选出必须的自动配置类。自动配置类加载了一堆组件。每个组件需要的数据来自属性类。属性类又和配置文件绑定在一起。因此，最终一句话：导入启动器，修改配置文件，就可以完成对应功能的开发。