Spring中扩展 Spring

2025-07-10 约 3921 字 阅读时长8 分钟

Spring中扩展

AutowireCapableBeanFactory

在Spring中,AutowireCapableBeanFactory 是一个非常强大的工具类,它允许在Spring容器之外手动创建对象并为其注入依赖。这对于处理第三方类或动态创建的对象非常有用。

使用场景

  1. 动态创建对象:在运行时动态创建对象,并为其注入Spring管理的Bean。
  2. 非托管对象的依赖注入:对于不在Spring生命周期管理中的对象,可以手动注入依赖。
  3. 后期依赖注入:在对象创建后,根据运行时确定的依赖关系注入依赖。

使用方法

  1. 获取 AutowireCapableBeanFactory 实例;通过Spring的 ApplicationContext 获取 AutowireCapableBeanFactory

    java
    1@Autowired
    2private ApplicationContext context;
    3
    4AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
  2. 手动创建对象并注入依赖;假设有一个第三方类 ThirdPartyClass,它需要注入一个Spring管理的Bean MyDependency

    java
     1public class ThirdPartyClass {
     2    private MyDependency myDependency;
     3
     4    public void setMyDependency(MyDependency myDependency) {
     5        this.myDependency = myDependency;
     6    }
     7
     8    public void doSomething() {
     9        myDependency.doSomething();
    10    }
    11}
    java
    1// 手动注入依赖
    2ThirdPartyClass thirdPartyClass = new ThirdPartyClass();
    3beanFactory.autowireBean(thirdPartyClass); // 自动注入依赖
    4thirdPartyClass.doSomething(); // 调用方法验证依赖是否注入成功

关键方法

  1. autowireBean(Object existingBean)
    • 自动装配给定的现有Bean。此方法会尝试对整个Bean进行自动装配,包括调用@PostConstruct注解的方法。
    • 适用于需要完整初始化流程的场景。
  2. autowireBeanProperties(Object existingBean, int autowireMode, boolean dependent)
    • 自动装配给定Bean的属性,支持指定自动装配模式(如AUTOWIRE_BY_NAMEAUTOWIRE_BY_TYPE)。
    • 适用于只需要部分属性注入的场景。
  3. initializeBean(Object existingBean, String beanName)
    • 初始化给定的Bean,包括调用Aware接口和@PostConstruct注解的方法。

注意事项

  • 线程安全AutowireCapableBeanFactory 的使用需要考虑线程安全问题。
  • 性能开销:手动注入依赖会比Spring自动管理的Bean有额外的性能开销,但通常可以忽略。
  • 适用场景:尽量只在必要时使用,例如处理第三方类或动态创建的对象。

通过 AutowireCapableBeanFactory,可以灵活地为第三方类或动态创建的对象注入Spring管理的Bean,而不必将其纳入Spring容器的管理范围。

SessionFixationProtectionStrategy

SessionFixationProtectionStrategy是 Spring Security 框架里用于防止会话固定攻击的重要组件。

此策略主要运用HttpServletRequest.invalidate()方法来防范会话固定攻击。当用户成功认证后,系统会创建一个新的会话,以此避免攻击者利用旧会话 ID 进行非法操作。

工作流程

  1. 会话无效化处理:当用户认证成功时,原本的会话会被系统无效化。
  2. 新会话创建操作:系统会生成一个全新的会话,从而分配新的会话 ID。
  3. 属性迁移机制:能够把旧会话中的属性迁移到新会话中,不过像 Spring Security 相关的内部属性,无论怎样都会被迁移。
  4. 会话超时时间设置:新会话会沿用旧会话的超时时间配置。

关键属性

  • migrateSessionAttributes:这是一个布尔类型的属性,默认值为true。它主要用于控制是否将旧会话中的属性迁移到新会话。

核心方法解析

  1. extractAttributes(HttpSession session)
    • 该方法的作用是从即将无效化的旧会话中提取需要迁移的属性。
    • migrateSessionAttributes被设置为false时,除了 Spring Security 相关属性外,其他应用属性都会被丢弃。
    • 若有特殊的属性迁移需求,可以通过重写此方法来实现自定义的迁移逻辑。
  2. applySessionFixation(HttpServletRequest request)
    • 这是处理会话固定攻击的核心方法,它会先无效化旧会话,然后创建新会话。
    • 其执行步骤为:获取当前会话并记录原始会话 ID,提取需要迁移的属性,无效化旧会话,创建新会话,将提取的属性迁移到新会话,最后设置新会话的超时时间。
  3. transferAttributes(Map**<**String, Object> attributes, HttpSession newSession)
    • 此方法负责将提取的属性添加到新创建的会话中。
  4. createMigratedAttributeMap(HttpSession session)
    • 这是一个辅助方法,用于创建需要迁移的属性映射。
    • 它会遍历旧会话中的所有属性,根据migrateSessionAttributes的值来决定是否保留某个属性。

自定义需要迁移的属性

继承SessionFixationProtectionStrategy类并重写extractAttributes方法

java
 1public class CustomSessionFixationProtectionStrategy extends SessionFixationProtectionStrategy {
 2
 3    // 定义需要保留的应用属性白名单
 4    private final String[] retainedAttributes;
 5
 6    public CustomSessionFixationProtectionStrategy(String[] retainedAttributes) {
 7        this.retainedAttributes = retainedAttributes;
 8        // 禁用默认的属性迁移,完全由自定义逻辑控制
 9        super.setMigrateSessionAttributes(false);
10    }
11
12    @Override
13    protected Map<String, Object> extractAttributes(HttpSession session) {
14        Map<String, Object> attributesToMigrate = new HashMap<>();
15        
16        // 1. 始终保留Spring Security相关属性
17        Enumeration<String> attributeNames = session.getAttributeNames();
18        while (attributeNames.hasMoreElements()) {
19            String key = attributeNames.nextElement();
20            if (key.startsWith("SPRING_SECURITY_")) {
21                attributesToMigrate.put(key, session.getAttribute(key));
22            }
23        }
24        
25        // 2. 添加应用特定的保留属性
26        if (retainedAttributes != null) {
27            for (String attribute : retainedAttributes) {
28                Object value = session.getAttribute(attribute);
29                if (value != null) {
30                    attributesToMigrate.put(attribute, value);
31                }
32            }
33        }
34        
35        return attributesToMigrate;
36    }
37}

Java 配置类中使用自定义策略

java
 1@Configuration
 2@EnableWebSecurity
 3public class SecurityConfig extends WebSecurityConfigurerAdapter {
 4    @Override
 5    protected void configure(HttpSecurity http) throws Exception {
 6        // 定义需要保留的应用属性
 7        String[] retainedAttributes = {"userLocale", "themePreference"};
 8        
 9        http
10            .sessionManagement()
11                .sessionFixation()
12                    .sessionAuthenticationStrategy(new CustomSessionFixationProtectionStrategy(retainedAttributes));
13                // 其他会话管理配置
14    }
15}

路径匹配

PathMatcher

PathMatcher是个很实用的接口,它主要用于对字符串路径和路径模式进行匹配操作;借助这个接口,能够判断某个路径是否与特定的模式相契合,这在路由匹配、资源处理等场景中经常会用到。

核心接口与实现类

Spring Boot 提供了多种PathMatcher的实现,其中AntPathMatcher是最常用的一个,它支持 Ant 风格的路径模式。Spring Boot 中默认会有一个 AntPathMatcher 实例 Bean。

Ant 风格路径模式的规则

  • ?:用于匹配单个字符。
  • *:能够匹配任意数量的任意字符,但不包括路径分隔符/
  • **:可以递归地匹配任意数量的任意字符,包括路径分隔符/

示例

java
 1PathMatcher matcher = new AntPathMatcher();
 2
 3// pathMatcher.setCaseSensitive(false); // 设置路径匹配不区分大小写
 4// pathMatcher.setPathSeparator("|"); // 设置路径分隔符为|
 5
 6// 精确匹配
 7matcher.match("/users/{id}", "/users/123"); // 返回true
 8matcher.match("/users/{id}", "/users/123/profile"); // 返回false
 9
10// 使用*匹配
11matcher.match("/static/*.js", "/static/main.js"); // 返回true
12matcher.match("/static/*.js", "/static/css/style.css"); // 返回false
13
14// 使用**递归匹配
15matcher.match("/static/**/*.js", "/static/js/main.js"); // 返回true
16matcher.match("/static/**/*.js", "/static/css/style.css"); // 返回false

PathPatternParser

在 Spring Framework 5.3 及之后的版本中,PathPatternParser 作为新的路径匹配器被引入,它逐渐替代了旧的 AntPathMatcherPathPatternParser 提供了更高效、更安全的路径匹配功能,尤其在 WebFlux 和 Spring MVC 中得到了广泛应用。

核心概念与优势

  • 基于路径模式的匹配PathPatternParser 将路径模式编译为 PathPattern 对象,这种编译后的模式在匹配时效率更高。
  • 线程安全:编译后的 PathPattern 对象是线程安全的,可以被多个线程共享使用。
  • 更好的性能:相较于 AntPathMatcherPathPatternParser 在频繁匹配相同模式的场景下,性能有显著提升。
  • 标准化的路径处理:它会自动处理路径中的斜杠、解码等问题,避免了一些潜在的安全风险。

路径模式语法

PathPatternParser 支持的路径模式语法与 AntPathMatcher 类似,但也有一些细微的差别:

  • {variable}:用于匹配并捕获路径中的变量部分。例如,/users/{id} 可以匹配 /users/123,并将 123 捕获为变量 id
  • {variable:regex}:可以使用正则表达式来限制变量的匹配范围。比如,/users/{id:\\d+} 要求 id 必须是数字。
  • **:用于递归匹配任意路径片段。例如,/static/**/*.js 可以匹配 /static/js/main.js 等路径。
  • ?:匹配单个字符。
  • *:匹配零个或多个字符,但不包括路径分隔符 /

示例

java
 1// 创建PathPatternParser实例
 2PathPatternParser parser = new PathPatternParser();
 3// parser.setCaseSensitive(false);  // 设置路径匹配不区分大小写
 4// parser.setMatchOptionalTrailingSeparator(false);  // 严格模式,尾部斜杠也必须匹配
 5//        parser.setPathOptions(PathContainer.Options.create(
 6//                '/',   // 分隔符
 7//                true  // true 完整解码路径段并解析路径参数;false 仅解码分隔符的转义序列(例如:将 %2F 解码为 /,但不解码其他 URL 编码字符)
 8//        ));
 9
10
11// 编译路径模式
12PathPattern pattern1 = parser.parse("/users/{id}/profile");
13pattern1.matches(PathContainer.parsePath("/users/12321/profile")); // true
14pattern1.matchAndExtract(PathContainer.parsePath("/users/12321/profile"));  //  {id=12321}
15
16PathPattern pattern2 = parser.parse("/users/*");
17pattern2.matches(PathContainer.parsePath("/users/add"));  // true
18pattern2.matchAndExtract(PathContainer.parsePath("/users/add/12"));  // false
19
20PathPattern pattern3 = parser.parse("/users/**");
21pattern2.matches(PathContainer.parsePath("/users/add"));  // true
22pattern2.matchAndExtract(PathContainer.parsePath("/users/add/12"));  // true

类型转换ConversionService

ConversionService 是 Spring 框架自 3.0 起引入的“统一类型转换”总入口,它把任意 Java 类型 → 任意 Java 类型的转换逻辑抽象成一套可扩展的服务,从而彻底取代早期仅支持 String↔Object 且线程不安全的 PropertyEditor


  1. 接口定义

    java
     1public interface ConversionService {
     2    // 判断是否可以将 sourceType 转换为 targetType
     3    boolean canConvert(Class<?> sourceType, Class<?> targetType);
     4
     5    // 更细致的类型判断(支持泛型)
     6    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
     7
     8    // 执行转换(无类型描述符)
     9    <T> T convert(Object source, Class<T> targetType);
    10
    11    // 执行转换(带类型描述符,支持泛型和注解信息)
    12    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
    13}
  2. 核心实现体系

    1. GenericConversionService: 最底层、线程安全、无状态,内部维护一张 Converters 并发哈希表,负责“根据源→目标类型”路由具体转换器。
    2. DefaultConversionService: 在 1 的基础上预注册了大量常用转换器(String↔Number、String↔Enum、String↔Date、数组/集合互转等),开箱即用。
    3. FormattingConversionService: 继承2, 额外支持字段格式化@NumberFormat@DateTimeFormat),Spring MVC 默认装配的就是它。
    4. ApplicationConversionService: Spring Boot 中,在 3 的基础上再追加 Boot 自身所需的一套转换器(String→DurationString→DataSize 等),由 ApplicationConversionService.getSharedInstance() 单例提供。
  3. 转换器三种形态及注册方式

    1. Converter<S, T>: 最基础的转换器接口,用于将类型 S 转换为类型 T

      java
      1public class StringToLocalDateConverter implements Converter<String, LocalDate> {
      2    @Override
      3    public LocalDate convert(String source) {
      4        return LocalDate.parse(source, DateTimeFormatter.ISO_LOCAL_DATE);
      5    }
      6}
    2. GenericConverter: 更灵活的转换器,支持多源类型到多目标类型的转换,还能获取类型上下文(如注解、泛型信息)

      java
      1public interface GenericConverter {
      2    // 返回支持的类型对(源类型→目标类型)
      3    Set<ConvertiblePair> getConvertibleTypes();
      4
      5    // 执行转换,可利用类型描述符获取更多信息
      6    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
      7}
    3. ConverterFactory<S, R>: 用于将源类型 S 转换为 R 类型的子类型(如将 String 转换为任意 Enum 类型)

      java
      1public interface ConverterFactory<S, R> {
      2    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
      3}
  4. 使用方式

    1. 创建 ConversionService, 通常直接使用 DefaultConversionService,它已内置常用转换器

      java
      1ConversionService conversionService = new DefaultConversionService();
    2. 注册自定义转换器

      java
      1GenericConversionService conversionService = new GenericConversionService();
      2// 注册 Converter
      3conversionService.addConverter(new StringToLocalDateConverter());
      4// 注册 ConverterFactory
      5conversionService.addConverterFactory(new StringToEnumConverterFactory());
    3. 执行转换

      java
       1// 字符串转整数
       2Integer num = conversionService.convert("123", Integer.class);
       3
       4// 字符串转LocalDate
       5LocalDate date = conversionService.convert("2023-10-01", LocalDate.class);
       6
       7// 集合转换(需借助TypeDescriptor处理泛型)
       8List<String> strList = Arrays.asList("1", "2", "3");
       9TypeDescriptor sourceType = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class));
      10TypeDescriptor targetType = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class));
      11List<Integer> intList = (List<Integer>) conversionService.convert(strList, sourceType, targetType);
使用滚轮缩放
按住拖动