首页 文章 精选 留言 我的

精选列表

搜索[SpringBoot],共4560篇文章
优秀的个人博客,低调大师

SpringBoot 整合 oauth2(五)实现 jwt 及 扩展

什么是jwt,即 json web token。JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。也是一种token,但是和token有一些不同。 jwt优点: 自包含 防篡改 可自定义扩展 JWT的结构 JWT包含了使用.分割的三部分: Header 头部 Payload 负载 Signature 签名 比如:eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9 三个新东西 什么是自包含? 字符串里包含用户信息。 什么是防篡改? 签名用于验证消息的发送者以及消息是没有经过篡改的。 可扩展是什么意思? 你可以在Payload部分加入自己想加入的json字符串 那我们既然token的实现方式那么优秀,为什么还要有jwt呢,这就需要了解他们的生成机制。 token生成的其实就是一个UUID,和业务没有丝毫的关系,这样带来最大的问题,就是需要人工持久化处理token(像处理分布式下的sessionId一样)。但是jwt就不需要,因为自包含,所以token里有身份验证信息,不需要做后台持久化处理,前端每次请求被保护的资源时请求头里带上该token就可以实现。 好了开始正文。 本文建立在第三节的基础上。这里是顺风车 1. 新增JwtTokenConfig @Configuration public class JwtTokenConfig{ @Bean public TokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } /** * token生成处理:指定签名 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); accessTokenConverter.setSigningKey("internet_plus"); return accessTokenConverter; } } jwtTokenStore方法返回一个TokenStore对象的子对象JwtTokenStore,供给认证服务器取来给授权服务器端点配置器,通俗点就是让MyAuthorizationServerConfig能注入到值。 jwtAccessTokenConverter方法是根据签名生成JwtToken,同样也需要在MyAuthorizationServerConfig类里注入。 修改MyAuthorizationServerConfig 的 configure方法 /** * 认证服务器 * Created by Fant.J. */ @Configuration @EnableAuthorizationServer public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { super.configure(security); } /** * 客户端配置(给谁发令牌) * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient(ConsParams.Auth.GET_CLIENT_ID) .secret(ConsParams.Auth.GET_SECRET) //有效时间 2小时 .accessTokenValiditySeconds(ConsParams.Auth.GET_TOKEN_VALIDITY_SECONDS) //密码授权模式和刷新令牌 .authorizedGrantTypes(ConsParams.Auth.GE_TAUTHORIZED_GRANT_TYPES) .scopes(ConsParams.Auth.GE_TSCOPES); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(tokenStore) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .accessTokenConverter(jwtAccessTokenConverter); } } } 和第三节不同的是 endpoints添加了accessTokenConverter属性,它规定了token生成器是jwtAccessTokenConverter,并按照我们设置的签名来生成。 启动项目 给/oauth/token 发送post请求获取token 请求头:Authorization:Basic +clientid:secret 的base64加密字符串 (认证服务器中设置的client信息) 请求参数:username password (用户登陆账号密码) 2. JWT扩展 在前文我们介绍过jwt的可扩展性,一般情况下实现jwt就可以了,但是有一些特殊需求,比如你想在返回的token中附带一些别的信息,这就需要我们对jwt进行扩展。 2.1 修改JwtTokenConfig 新增jwtTokenEnhancer方法 @Configuration public class JwtTokenConfig{ @Bean public TokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } /** * token生成处理:指定签名 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); accessTokenConverter.setSigningKey("internet_plus"); return accessTokenConverter; } @Bean public TokenEnhancer jwtTokenEnhancer(){ return new JwtTokenEnhancer(); } } jwtTokenEnhancer方法 返回一个JwtTokenEnhancer并交给bean工厂。 2.2 增加JwtTokenEnhancer类 /** * Jwt token 扩展 * Created by Fant.J. */ public class JwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) { Map<String,Object> info = new HashMap<>(); info.put("provider","Fant.J"); //设置附加信息 ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info); return oAuth2AccessToken; } } 重写TokenEnhancer的enhance方法,根据需求扩展jwt。 2.3 修改MyAuthorizationServerConfig类 /** * 认证服务器 * Created by Fant.J. */ @Configuration @EnableAuthorizationServer public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private TokenEnhancer jwtTokenEnhancer; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { super.configure(security); } /** * 客户端配置(给谁发令牌) * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient(ConsParams.Auth.GET_CLIENT_ID) .secret(ConsParams.Auth.GET_SECRET) //有效时间 2小时 .accessTokenValiditySeconds(ConsParams.Auth.GET_TOKEN_VALIDITY_SECONDS) //密码授权模式和刷新令牌 .authorizedGrantTypes(ConsParams.Auth.GE_TAUTHORIZED_GRANT_TYPES) .scopes(ConsParams.Auth.GE_TSCOPES); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(tokenStore) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> enhancerList = new ArrayList<>(); enhancerList.add(jwtTokenEnhancer); enhancerList.add(jwtAccessTokenConverter); enhancerChain.setTokenEnhancers(enhancerList); endpoints .tokenEnhancer(enhancerChain) .accessTokenConverter(jwtAccessTokenConverter); } } endpoints的tokenEnhancer方法需要我们提供一个token增强器链对象TokenEnhancerChain,所以我们需要在链中加入我们重写的TokenEnhancer和jwtAccessTokenConverter,然后放入endpoints。 启动项目 和上文获取token方式相同。 下图红框便是我添加的扩展。 解析jwt,获取扩展信息 如果我们需要获取jwt本来就有的信息,我们直接请求方法中吧Authentication当做参数,就可以获取到jwt原始信息。 如果我们需要获取jwt扩展的信息,即我们自定义添加的信息,我们需要做这几个操作: pom导入依赖 <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> UserController.java @GetMapping("/me") public ServerResponse getCurrentUser(Authentication user, HttpServletRequest request) throws UnsupportedEncodingException { String s = user.getPrincipal().toString(); String name = user.getName(); String header = request.getHeader("Authorization"); String token = StringUtils.substringAfter(header,"bearer "); Claims body = Jwts.parser().setSigningKey(ConsParams.Auth.GET_SIGNING_KEY.getBytes("UTF-8")) .parseClaimsJws(token).getBody(); String username = (String) body.get("username"); log.info("解析token获取到的username为{}",username); log.info("从Authentication里获取到的username为{}",s); log.info("从Authentication里获取到的username为{}",name); return ServerResponse.createBySuccess(user); } 介绍下我的所有文集: 流行框架 SpringCloudspringbootnginxredis 底层实现原理: Java NIO教程Java reflection 反射详解Java并发学习笔录Java Servlet教程jdbc组件详解Java NIO教程Java语言/版本 研究

优秀的个人博客,低调大师

2026版基于springboot的儿童医院挂号管理系统

# 1、研究技术 Java Java 是一种面向对象的跨平台编程语言,凭借其“一次编写,到处运行”的特性,成为企业级应用开发的基石。其强类型、垃圾回收机制和丰富的类库(如 Java Collections Framework)提升了开发效率与代码健壮性。在儿童医院挂号系统中,Java 用于构建后端业务逻辑,处理挂号、分诊、号源管理等核心功能。通过多线程与并发控制,系统可应对高并发场景(如流感季挂号高峰);结合 Java 的安全机制(如加密、权限管理),可保障患者数据隐私,防止号源泄露或恶意攻击,为系统稳定运行提供底层支撑。 Vue Vue 是一个渐进式 JavaScript 框架,以组件化开发和响应式数据绑定为核心,显著提升前端开发效率。在儿童医院挂号系统中,Vue 用于构建用户界面,通过动态渲染挂号表单、科室列表、预约时段选择等交互元素,实现流畅的用户体验。其虚拟 DOM 技术优化了页面性能,减少渲染开销;配合 Vue Router 实现单页应用(SPA)的路由管理,避免页面跳转卡顿。此外,Vue 的生态插件(如 Element UI)提供了丰富的组件库,可快速开发符合儿童医院特色的界面(如卡通化按钮、颜色友好的表单),降低开发成本。 MySQL MySQL 是一款开源的关系型数据库,以高性能、易用性和低成本成为医疗系统的首选数据存储方案。在儿童医院挂号系统中,MySQL 用于存储患者信息、医生排班、号源状态、就诊记录等核心数据。其事务支持(ACID 特性)确保挂号操作的原子性(如扣号与生成预约记录同步完成),避免数据不一致;通过索引优化(如对科室、医生ID建索引)提升查询效率,支持快速检索可用号源。此外,MySQL 的主从复制与备份机制保障数据安全,即使硬件故障也能快速恢复,满足医疗行业对数据可靠性和持久性的严苛要求。 Spring Boot Spring Boot 是基于 Spring 框架的快速开发框架,通过“约定优于配置”原则简化了企业级应用搭建流程。在儿童医院挂号系统中,Spring Boot 用于构建后端服务,集成 MyBatis 或 JPA 实现与 MySQL 的数据交互,通过 RESTful API 为前端(Vue)提供数据接口。其内置的依赖管理(如自动配置 Tomcat、安全模块)大幅减少开发配置工作量;通过 Spring Security 实现用户认证与权限控制(如区分家长、医生、管理员角色);结合 Actuator 监控系统运行状态,便于运维。此外,Spring Boot 的微服务兼容性为未来系统扩展(如接入区域医疗平台)提供技术储备。 # 2、系统实现 ![](https://developer.qcloudimg.com/http-save/yehe-2014964/c914b0717aae07bdbdef04eb9bf05d6c.png) ![](https://developer.qcloudimg.com/http-save/yehe-2014964/29ed03089cb58483e20c8f6aaf1b49d6.png) ![](https://developer.qcloudimg.com/http-save/yehe-2014964/e8e752792a35797108402efa9f19f3e5.png) ![](https://developer.qcloudimg.com/http-save/yehe-2014964/10414521f7a85232daf823c7eccbc979.png) ![](https://developer.qcloudimg.com/http-save/yehe-2014964/7c5fef422daebdde16a7ad412c05d71e.png) ![](https://developer.qcloudimg.com/http-save/yehe-2014964/714e12290844dc7a2f72d524fb93e7f9.png) ![](https://developer.qcloudimg.com/http-save/yehe-2014964/7380530abb02a30d5abd451a9cff6712.png)

优秀的个人博客,低调大师

《SpringBoot4进阶实战》第22招 自己写AutoConfiguration

# 从零手写 Petclinic 审计 Starter:把默认配置变成可替换自动装配 ```text 旧方案:业务代码里到处写审计切面、指标统计、配置项 冲突:每个服务复制粘贴,默认实现无法统一升级,替换成本高 后果:配置漂移、行为不一致、升级困难、新人不敢改 ``` Petclinic 已经具备 Web、数据、缓存、可观测等能力,但请求审计和业务指标仍然散落在控制器、服务层甚至过滤器里。 我是老李,今天要解决的不是“能不能记录”,而是“怎么让所有业务应用只加一个依赖就自动拥有审计能力,同时还能按需替换默认实现”。 企业里大量 xxx-spring-boot-starter 到底是怎么做出来的? 是继续在业务工程里维护一套 @Configuration,还是把它抽成独立 Starter 并用 AutoConfiguration 自动装配? 如果选错,会不会导致类加载冲突、配置不生效、用户无法覆盖默认 Bean,最终 Starter 没人敢用? # 01、故事 ```text 周五 18:40 → 研发区 → 任务是给 Petclinic 加统一请求审计 → 冲突是审计逻辑散落、指标口径不一致、联调催得紧 ``` 周五傍晚,运维在群里贴出告警:Petclinic 几个核心接口 P99 突然升高,但没人能说清是哪个环节变慢。 任务是给所有 HTTP 请求加统一审计和业务指标,方便定位慢请求和热点接口。 我打开代码一看,有的控制器自己记日志,有的服务层手动埋点,还有人在过滤器里写临时统计。 联调同事在旁边催:“能不能先给一版,别让我每个服务都改一遍。” 我意识到,如果继续在业务工程里堆配置,今晚肯定改不完,而且下个服务还得重来。 > 暮色压工位,告警一声催。 > 审计四处散,指标各自飞。 > 若问根何在,配置未成规。 > 欲解千般乱,先立一模块。 # 02、问题 ```text 需求变化:从“能记录”升级为“所有服务统一自动拥有审计能力” 冲突:普通 @Configuration 需要业务方手动 import,且默认实现无法被优雅替换 后果:接入成本高、行为漂移、升级困难 ``` Petclinic 当前审计逻辑分散在多个模块,每个模块对“请求耗时”“用户标识”“业务动作”的定义都不一样。 这导致指标看板上的数据无法横向对比,排障时只能逐个服务翻日志。 更麻烦的是,如果我把审计逻辑写成一个普通 @Configuration,业务应用必须手动 @Import 或包扫描才能生效。 一旦默认实现需要升级,所有业务方都要改代码;如果业务方想替换默认审计器,又容易因为 Bean 冲突而失败。 可验证的验收标准是:业务应用只增加一个 Maven 依赖,不改任何代码,启动后自动具备请求审计能力;同时业务方可以自定义一个 Bean 替换默认实现,且启动不冲突。 > 旧时配置散,各自埋点忙。 > 一朝口径变,全局费周章。 > 若只加依赖,自动把活扛。 > 替换亦有道,方称好组件。 # 03、原理 ```text 核心机制:AutoConfiguration 条件装配 + 配置属性绑定 + Bean 覆盖策略 ``` Spring Boot 的自动配置不是魔法,它依赖 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.i mports 文件声明候选配置类。 启动时,Spring Boot 会加载这些候选类,并根据 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等条 件决定是否生效。 @EnableConfigurationProperties 可以把外部配置绑定到强类型 POJO,再注入到自动配置类中。 @ConditionalOnMissingBean 是实现“用户可替换默认实现”的关键:只有当容器中不存在用户自定义 Bean 时,才创建默认 Bean。 一个反直觉判断是:自动配置类本身不应该被业务包扫描到,否则会绕过条件装配,导致用户无法覆盖默认实现。 另一个反直觉判断是:Starter 的依赖边界要分“必需依赖”和“可选依赖”,如果把所有依赖都写成 compile,会污染业务应用的类路径。 > 自动非魔法,条件定去留。 > 缺豆才补位,有主便低头。 > 配置绑强型,元数据可求。 > 边界若不清,类路径成愁。 # 04、架构 ```text 输入:HTTP 请求、业务方法调用、用户自定义配置 本模块:audit-spring-boot-starter(自动配置、审计器、指标收集器、配置属性) 输出:统一审计日志、业务指标、可替换 Bean 扩展点 边界:不依赖 Petclinic 具体业务,不强制数据库存储 ``` 本模块分为四层:配置属性层、自动配置层、核心能力层、扩展点层。 配置属性层用 @ConfigurationProperties 绑定前缀,例如 petclinic.audit。 自动配置层用 @AutoConfiguration 声明,配合条件注解控制装配时机。 核心能力层提供默认审计器、指标收集器和请求过滤器。 扩展点层通过 @ConditionalOnMissingBean 暴露可替换接口,业务方只需提供一个同类型 Bean 即可覆盖。 收益是业务应用零代码接入,默认行为统一,升级只需升 Starter 版本。 代价是 Starter 内部必须保持向后兼容,配置项变更要有迁移说明。 适用条件是业务应用基于 Spring Boot 3 或 4,且类路径中存在 Spring MVC 或 WebFlux。 > 四层分职责,边界画得清。 > 属性先绑定,条件再点兵。 > 核心给默认,扩展留空名。 > 若问何所益,接入一身轻。 # 05、第一版实战 ```text 环境:JDK 17 + Spring Boot 3.3.x + Maven 3.9.x 配置:新建 audit-spring-boot-starter 模块,先写普通 @Configuration 代码:AuditProperties、AuditService、AuditFilter、AuditAutoConfiguration 启动:Petclinic 手动 @Import 该配置类 验证:请求 /owners 观察审计日志 V1:能记录,但需要手动 import,且替换默认实现容易冲突 ``` 先创建独立 Maven 模块 audit-spring-boot-starter,pom.xml 如下: ```xml org.springframework.boot spring-boot-autoconfigure org.springframework.boot spring-boot-configuration-processor true ``` 写配置属性类: ```java @ConfigurationProperties(prefix = "petclinic.audit") public class AuditProperties { private boolean enabled = true; private String logPrefix = "[AUDIT]"; private List ignorePaths = List.of("/actuator/**"); // getter/setter 省略 } ``` 写默认审计服务: ```java public interface AuditService { void record(String method, String path, long costMs, String user); } public class DefaultAuditService implements AuditService { private static final Logger log = LoggerFactory.getLogger(DefaultAuditService.class); private final AuditProperties properties; public DefaultAuditService(AuditProperties properties) { this.properties = properties; } @Override public void record(String method, String path, long costMs, String user) { log.info("{} {} {} {}ms user={}", properties.getLogPrefix(), method, path, costMs, user); } } ``` 写请求过滤器: ```java public class AuditFilter extends OncePerRequestFilter { private final AuditService auditService; private final AuditProperties properties; public AuditFilter(AuditService auditService, AuditProperties properties) { this.auditService = auditService; this.properties = properties; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { long start = System.currentTimeMillis(); try { chain.doFilter(request, response); } finally { if (properties.isEnabled() && !shouldIgnore(request.getRequestURI())) { auditService.record(request.getMethod(), request.getRequestURI(), System.currentTimeMillis() - start, request.getRemoteUser()); } } } private boolean shouldIgnore(String uri) { return properties.getIgnorePaths().stream().anyMatch(uri::startsWith); } } ``` 写普通配置类: ```java @Configuration @EnableConfigurationProperties(AuditProperties.class) public class AuditAutoConfiguration { @Bean @ConditionalOnMissingBean public AuditService auditService(AuditProperties properties) { return new DefaultAuditService(properties); } @Bean @ConditionalOnMissingBean public AuditFilter auditFilter(AuditService auditService, AuditProperties properties) { return new AuditFilter(auditService, properties); } } ``` 在 Petclinic 主类上手动导入: ```java @SpringBootApplication @Import(AuditAutoConfiguration.class) public class PetClinicApplication { public static void main(String[] args) { SpringApplication.run(PetClinicApplication.class, args); } } ``` 启动并请求: ```bash mvn spring-boot:run curl -s http://localhost:8080/owners | head -n 5 ``` 示例输出: ```text [AUDIT] GET /owners 23ms user=null ``` V1 能记录请求,但业务方必须手动 @Import,且如果业务方自己定义了 AuditService,可能因为包扫描顺序或配置类加载顺序导致 Bean 冲突。 > 初版虽能记,手动导入烦。 > 替换若不当,冲突起波澜。 > 条件虽已写,装配未成环。 > 欲求真自动,还需再深钻。 # 06、排查 ```text 诊断树: 现象:业务方自定义 AuditService 后启动报 BeanDefinitionOverrideException 怀疑:@Import 的配置类与业务包扫描重复注册 检查:查看 Bean 定义来源和注册顺序 证据:业务方 @Service 注解的 AuditServiceImpl 与 @Bean 方法同时存在 根因:@Import 的配置类被业务包扫描到,导致条件装配失效 修复:把自动配置类移出业务包扫描路径,改用 AutoConfiguration.imports 声明 ``` 第一个问题是业务方自定义 AuditService 后启动报错。 怀疑是 @Import 的配置类被业务包扫描重复加载。 检查 Bean 定义来源: ```bash mvn spring-boot:run -Dspring-boot.run.arguments=--debug ``` 示例输出: ```text AuditService defined in com.example.audit.AuditAutoConfiguration AuditService defined in com.example.petclinic.service.AuditServiceImpl ``` 证据显示同一个类型有两个 Bean 定义,且 @ConditionalOnMissingBean 没有生效。 根因是业务方把自定义 AuditServiceImpl 放在 com.example.petclinic 包下,而 AuditAutoConfiguration 也在该包下,业务包扫描把自动配置类也扫 了进去。 修复方法是把自动配置类移到独立包,例如 com.example.audit.autoconfigure,并通过 AutoConfiguration.imports 声明。 第二个问题是配置属性没有 IDE 提示。 怀疑是缺少配置元数据。 检查 target/classes/META-INF 目录: ```bash ls target/classes/META-INF/ ``` 示例输出: ```text spring-configuration-metadata.json 不存在 ``` 根因是 configuration-processor 没有正确生成元数据,需要确认注解处理器是否启用。 **错误尝试:** 我一开始把 @ConditionalOnMissingBean 放在 @Bean 方法上,但配置类本身被业务包扫描到,导致条件判断在错误时机执行,仍然出现 Bean 冲突。 > 现象是冲突,怀疑在扫描。 > 证据看来源,根因在包号。 > 修复移路径,声明换新招。 > 错误曾尝试,条件未生效。 # 07、优化 ```text V1:普通 @Configuration + 手动 @Import 证据:业务包扫描导致条件装配失效,配置元数据缺失 V2:真正 AutoConfiguration + AutoConfiguration.imports + 配置元数据 ``` 把自动配置类移到独立包 com.example.audit.autoconfigure,并加上 @AutoConfiguration 注解: ```java @AutoConfiguration @EnableConfigurationProperties(AuditProperties.class) public class AuditAutoConfiguration { @Bean @ConditionalOnMissingBean public AuditService auditService(AuditProperties properties) { return new DefaultAuditService(properties); } @Bean @ConditionalOnMissingBean public AuditFilter auditFilter(AuditService auditService, AuditProperties properties) { return new AuditFilter(auditService, properties); } } ``` 在 resources/META-INF/spring/ 下创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports: ```text com.example.audit.autoconfigure.AuditAutoConfiguration ``` 在 pom.xml 中确保 configuration-processor 生效: ```xml org.apache.maven.plugins maven-compiler-plugin org.springframework.boot spring-boot-configuration-processor ``` 在 Petclinic 中移除 @Import,只保留 Maven 依赖: ```xml com.example audit-spring-boot-starter 1.0.0 ``` 重新构建并启动,确认自动装配生效。 > 移包避扫描,注解换新装。 > 声明入文件,元数据成双。 > 业务去导入,依赖自担当。 > 替换有缺豆,冲突不再狂。 # 08、演进 ```text V2:真正 AutoConfiguration + 条件装配 + 配置元数据 能力:业务应用零代码接入,用户可自定义 Bean 替换默认实现 遗留:配置项较少,未覆盖异步请求和响应体审计 ``` 用 V1 同一组请求验证: ```bash curl -s http://localhost:8080/owners | head -n 5 ``` 示例输出: ```text [AUDIT] GET /owners 19ms user=null ``` 再验证用户自定义替换: ```java @Service public class CustomAuditService implements AuditService { @Override public void record(String method, String path, long costMs, String user) { System.out.println("CUSTOM: " + method + " " + path + " " + costMs + "ms"); } } ``` 启动后请求,示例输出: ```text CUSTOM: GET /owners 21ms ``` 收益是业务应用只加一个依赖就自动拥有审计能力,默认实现统一,升级只需升 Starter 版本。 代价是 Starter 内部必须保持向后兼容,配置项变更要有迁移说明。 适用条件是业务应用基于 Spring Boot 3 或 4,且类路径中存在 Spring MVC。 遗留问题是当前只覆盖同步 HTTP 请求,异步请求和响应体审计尚未实现。 > 同组请求验,自动已生效。 > 自定义替换,冲突不再生。 > 收益在零码,代价在兼容。 > 遗留异步事,留待后续攻。 # 09、洞见 ```text 从现象到判断:自动装配的本质是“约定优于配置”的工程化落地 ``` ## 自动配置类必须躲开业务包扫描 ```text 业务包扫描 → 自动配置类被提前加载 → 条件注解失效 → Bean 冲突 ``` 自动配置类如果放在业务包扫描路径下,会被当成普通配置类提前加载,@ConditionalOnMissingBean 的判断时机就会错乱。 正确做法是把自动配置类放在独立包,并只通过 AutoConfiguration.imports 声明。 ## 反直觉判断:@ConditionalOnMissingBean 不是万能保险 ```text 条件注解只在自动配置阶段生效 → 业务包扫描会绕过条件 → 冲突仍会发生 ``` 很多人以为加了 @ConditionalOnMissingBean 就一定能避免冲突,但如果配置类被业务包扫描到,条件判断就失效了。 条件装配的前提是配置类必须由自动配置机制加载,而不是业务扫描加载。 ## 反直觉判断:Starter 的依赖边界比功能更重要 ```text compile 依赖 → 污染业务类路径 → 版本冲突 → Starter 被弃用 ``` Starter 如果把所有依赖都写成 compile,会把不必要的库强加给业务应用,导致类路径冲突。 正确做法是把核心依赖写成 compile,把可选依赖写成 optional 或 provided。 ## 配置元数据是 Starter 的“用户界面” ```text 无元数据 → IDE 不提示 → 用户乱猜配置 → 接入成本上升 ``` 配置元数据不仅让 IDE 有提示,还能在编译期发现拼写错误。 这是 Starter 易用性的关键,很多团队却忽略这一步。 > 现象看冲突,判断在时机。 > 条件非万能,扫描破玄机。 > 边界定生死,元数据可依。 > 洞见藏深处,实战方可知。 # 10、系统落地 ```text 入口:HTTP 请求 → 服务:AuditFilter → 本模块:AuditService/DefaultAuditService → 存储:日志/指标收集器 → 未实现:异步审计、响应体审计 ``` 原来 Petclinic 的审计逻辑散落在控制器、服务层和过滤器中,没有统一入口和口径。 本篇增加了 audit-spring-boot-starter 模块,业务应用只加一个依赖就自动拥有请求审计能力。 现在能做什么:统一记录请求方法、路径、耗时、用户,支持配置忽略路径,支持自定义替换默认审计器。 下一篇缺什么:异步请求审计、响应体脱敏审计、审计数据持久化到数据库或消息队列。 官方文档可参考 Spring Boot 官方文档首页:https://docs.spring.io/spring-boot/ 。 > 入口在请求,服务在过滤。 > 模块补能力,存储待扩充。 > 本篇成闭环,异步尚留空。 > 落地有章法,演进自从容。 # 11、小结 ```text 三问三答: Starter 怎么做?→ 独立模块 + AutoConfiguration.imports + 条件装配 怎么自动拥有?→ 业务应用只加 Maven 依赖,零代码接入 怎么替换默认?→ @ConditionalOnMissingBean + 用户自定义 Bean ``` 企业里大量 xxx-spring-boot-starter 的核心做法是:独立模块、自动配置声明、条件装配、配置属性绑定、配置元数据。 业务应用只增加一个 Maven 依赖就自动拥有能力,是因为 Spring Boot 启动时会加载 AutoConfiguration.imports 中声明的配置类。 用户仍可通过自定义 Bean 替换默认实现,是因为 @ConditionalOnMissingBean 在容器中不存在用户 Bean 时才创建默认 Bean。 当前状态:Petclinic 已具备统一请求审计能力,且该能力以 Starter 形式独立存在,可复用到其他业务应用。 > 一问怎么做,独立加声明。 > 二问何自动,依赖即启程。 > 三问怎替换,缺豆才补成。 > 三答皆落地,闭环已打通。 # 12、作业 ```text 理解 → 实战 → 排障 → 架构判断 ``` ## 理解题:@ConditionalOnMissingBean 在什么情况下会失效? 当自动配置类被业务包扫描提前加载时,条件注解的判断时机错乱,@ConditionalOnMissingBean 会失效,导致 Bean 冲突。 ## 实战题:给 audit-spring-boot-starter 增加一个配置项,支持自定义日志级别。 在 AuditProperties 中增加 logLevel 字段,默认 INFO; 在 DefaultAuditService 中根据 logLevel 选择日志输出级别; 重新构建后,在业务应用配置文件中设置 petclinic.audit.log-level=DEBUG 验证生效。 ## 排障题:业务应用加了 Starter 依赖后,启动时提示找不到 AuditFilter 类。 检查 Starter 的 pom.xml 是否把 spring-web 依赖写成了 optional 或 provided; 如果是,业务应用需要显式引入 spring-web 依赖,或者把 Starter 中的 spring-web 改为 compile 依赖。 ## 架构判断题:如果多个 Starter 都提供了同一个类型的默认 Bean,应该怎么避免冲突? 应该使用 @ConditionalOnMissingBean 并配合 @ConditionalOnClass 限定生效条件; 如果确实存在多个 Starter 竞争同一个 Bean,需要明确优先级或让业务方显式定义 Bean 来覆盖所有默认实现。 > 理解问条件,失效在扫描。 > 实战增配置,日志可调参。 > 排障查依赖,边界要看清。 > 架构判冲突,缺豆定去留。 # 13、升华 ```text 工程结论:自定义 Starter 的本质是把“默认配置”变成“可替换自动装配” ``` 从普通 @Configuration 到真正 AutoConfiguration,不是简单加个注解,而是把配置类的加载时机从业务扫描切换到自动配置机制。 这一切换带来了三个能力:业务应用零代码接入、默认实现统一升级、用户自定义 Bean 优雅替换。 有依据的结论是:Spring Boot 的自动配置体系是一套完整的扩展协议,掌握它就能把团队内部的最佳实践沉淀为可复用的 Starter。 下一篇将把审计数据从日志升级为结构化指标,并接入 Actuator 和 Micrometer,让审计能力从“可记录”走向“可观测”。 > 配置非终点,装配是桥梁。 > 默认可替换,升级不慌张。 > 协议掌握后,实践自沉淀。 > 审计入指标,观测路更长。 **系列项目:** Spring 官方 Petclinic 演进为生产级 Spring Boot 4 Java 应用 **已完成:** 环境搭建、启动机制、依赖与自动配置、配置体系、Web/安全/数据/事务、远程调用/数据库迁移/缓存/并发、日志/测试/Testcontainers/Docker Compose、Actuator/可观测 **本篇:** 从零手写 Petclinic 审计 Starter,完成自定义自动配置闭环 **下一篇:** 审计指标接入 Micrometer 与 Actuator,实现可观测增强

优秀的个人博客,低调大师

SOFABoot 4.5.0 发布,蚂蚁开源的基于 Springboot 的服务框架

SOFABoot 是蚂蚁金服开源的基于 Spring Boot 的研发框架,它在 Spring Boot 的基础上,提供了诸如 Readiness Check,类隔离,日志空间隔离等等能力。在增强了 Spring Boot 的同时,SOFABoot 提供了让用户可以在 Spring Boot 中非常方便地使用 SOFA 中间件的能力。 SOFABoot 4.5.0现已发布,具体更新内容包括: 新功能 当组件注册失败时Fast Fail#1374 支持 spring boot 3.5.3#1379 将 OSSRH 更改为Central Portal#1382 错误修复 修复 StandardSofaRuntimeManager 内存泄漏#1373 更改 Maven 用户名和密码的操作#1386 Replenish module name#1388 依赖项升级 将 rpc 版本升级到 5.13.4,将 tracer 版本升级到 4.0.2#1367 将 staging plugin 版本升级到 1.7.0#1381 更新说明:https://github.com/sofastack/sofa-boot/releases/tag/v4.5.0

优秀的个人博客,低调大师

SOFABoot 4.4.1 发布,蚂蚁开源的基于 Springboot 的服务框架

SOFABoot 是蚂蚁金服开源的基于 Spring Boot 的研发框架,它在 Spring Boot 的基础上,提供了诸如 Readiness Check,类隔离,日志空间隔离等等能力。在增强了 Spring Boot 的同时,SOFABoot 提供了让用户可以在 Spring Boot 中非常方便地使用 SOFA 中间件的能力。 SOFABoot 4.4.1现已发布,具体更新内容包括: 新功能 支持 sbom endpoint#1352 错误修复 修复了并行调用多个实例 Bean 时 annotationWrapper 导致的线程安全问题#1354 修复导出模块名称#1357 依赖项升级 将 rpc 所有版本升级到 5.13.2#1356 升级 spring boot 版本到 3.3.7#1358 更新说明:https://github.com/sofastack/sofa-boot/releases/tag/v4.4.1

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册