Spring Security(认证服务器)Spring Boot 3.1.0:为何大量使用final类?
Spring Security 3.1.0中Final类的设计原因与扩展解决方案
一、为什么大量使用Final类?
Spring Security团队选择大量使用final类,主要基于以下设计考量:
- API稳定性:框架核心组件被final修饰后,能避免子类化带来的意外逻辑破坏。如果允许随意继承,子类可能重写核心方法导致框架内部逻辑混乱,Spring无法保证不同版本间的兼容性。
- 不可变对象与线程安全:final类配合private final成员变量,更容易实现不可变对象。这类对象天生线程安全,适合Spring这种多线程环境下的Bean管理。
- 推崇组合而非继承:Spring现在更鼓励用组合模式替代继承来扩展功能,避免继承带来的紧耦合问题。final类的设计也是在引导开发者采用更灵活的组合方式。
二、针对UserBuilder的扩展方案
因为UserBuilder是final类无法继承,你可以通过以下方式实现自定义UserDetails:
- 自定义构建器:完全实现自己的
CustomUserBuilder来构建CustomUser实例,不用依赖原生的UserBuilder:
public class CustomUser implements UserDetails { private final String username; private final String password; private final String fullName; // 自定义字段 private final Collection<? extends GrantedAuthority> authorities; private CustomUser(Builder builder) { this.username = builder.username; this.password = builder.password; this.fullName = builder.fullName; this.authorities = builder.authorities; } // 实现UserDetails的所有方法 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } // 其他getter方法... public static class Builder { private String username; private String password; private String fullName; private Collection<? extends GrantedAuthority> authorities = Collections.emptyList(); public Builder username(String username) { this.username = username; return this; } public Builder password(String password) { this.password = password; return this; } public Builder fullName(String fullName) { this.fullName = fullName; return this; } public Builder authorities(Collection<? extends GrantedAuthority> authorities) { this.authorities = authorities; return this; } public CustomUser build() { return new CustomUser(this); } } }
- 组合原生User对象:如果想复用原生
User的逻辑,可以把User作为CustomUser的成员变量,通过委托方式实现UserDetails接口:
public class CustomUser implements UserDetails { private final User user; private final String fullName; public CustomUser(User user, String fullName) { this.user = user; this.fullName = fullName; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return user.getAuthorities(); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } // 其他UserDetails方法直接委托给user,自定义字段单独处理 public String getFullName() { return fullName; } }
三、针对OAuth2AuthorizationServerPropertiesMapper的解决方案
由于OAuth2AuthorizationServerPropertiesMapper是final且构造方法包私有,无法直接继承或实例化,你可以这样处理:
- 复制扩展核心逻辑:Spring是开源框架,你可以查看该类的源码,把核心的属性映射逻辑复制到自己的自定义映射器中,再添加需要的扩展逻辑:
@Component public class CustomOAuth2AuthorizationServerPropertiesMapper { public AuthorizationServerSettings map(OAuth2AuthorizationServerProperties properties) { // 复制原类中的核心映射代码 AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder(); if (properties.getIssuer() != null) { builder.issuer(properties.getIssuer()); } // ... 其他原有的映射逻辑 // 添加你的自定义映射规则 builder.tokenEndpoint("/custom-token-endpoint"); return builder.build(); } }
- 使用BeanPostProcessor修改结果:如果只是需要在原映射结果基础上做微调,可以通过
BeanPostProcessor拦截AuthorizationServerSettings的创建过程,修改其属性:
@Component public class AuthorizationServerSettingsPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof AuthorizationServerSettings) { AuthorizationServerSettings original = (AuthorizationServerSettings) bean; return AuthorizationServerSettings.from(original) .tokenEndpoint("/custom-token-endpoint") .build(); } return bean; } }
四、是否需要深入Java手册?
Java手册中关于final关键字的基础内容是需要了解的,但更核心的是理解Spring的设计理念:组合优于继承、不可变对象的优势、框架API稳定性的重要性。这些理念才是理解这类设计的关键,Java语法只是实现这些理念的工具。
内容的提问来源于stack exchange,提问作者Buks van der Lingen
相关产品推荐
相关产品推荐

