spring-reading./spring-jsr/spring-jsr330-provider/README.md

24 KiB
Raw Permalink Blame History

javax.inject.Provider

一、基本信息

✒️ 作者 - Lex 📝 博客 - 我的CSDN 📚 文章目录 - 所有文章 🔗 源码地址 - Provider源码

二、接口描述

javax.inject.Provider<T> 是一个在 JSR-330 "Dependency Injection for Java" 规范中定义的接口它用于延迟地获取注入的依赖项。Spring 框架支持 JSR-330并因此也支持 javax.inject.Provider 接口。

三、接口源码

Provider<T> 接口的目的主要是为了允许实时、按需的创建和提供对象。

/**
 * 代表一个特定类型T的实例提供者。
 * 它是 JSR-330 "Java的依赖注入" 规范的一部分。
 * 该接口允许实现每次调用 get 方法时产生新的实例(类似于工厂方法),
 * 或者为多次调用返回相同的实例。
 * 
 * 它在以下情况尤为有用:
 * - 懒加载或按需创建对象。
 * - 检索多个原型作用域 bean 的实例。
 * - 在某些情况下解决循环依赖。
 */
public interface Provider<T> {

    /**
     * 提供 T 的一个实例。根据实现情况,
     * 每次调用此方法时都可能创建一个新的实例,
     * 或者为多次调用返回相同的实例。
     *
     * @return T 的一个实例
     * @throws RuntimeException 如果在提供实例时出现错误。
     * 例如,在 T 的注入或构造过程中出现错误。
     * 建议调用者不要捕获这些异常,因为行为可能因不同的注入器实现或配置而异。
     */
    T get();
}

四、主要功能

  1. 按需创建和提供实例
    • 当我们请求一个对象的实例时,Provider<T> 可以为我们提供一个实例,而不需要直接实例化或查找对象。这为按需创建对象提供了一种机制。
  2. 处理原型作用域的 beans
    • 在Spring中如果我们有一个原型作用域的bean使用 Provider<T> 可以确保每次调用 get() 方法时都获得一个新的bean实例。
  3. 解决循环依赖
    • 在某些情况下特别是当涉及到原型作用域的bean时使用 Provider<T> 可以帮助解决因直接注入而产生的循环依赖问题。
  4. 提供更多的灵活性
    • 与直接注入bean相比使用 Provider<T> 可以让我们决定何时以及如何获取bean实例。这可以为那些需要在运行时基于特定条件决定是否创建或获取bean的应用程序提供更大的灵活性。

五、最佳实践

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个MyConfiguration组件类。然后从Spring上下文中获取一个MyController类型的bean并调用了showService方法,

public class ProviderApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        MyController controller = context.getBean(MyController.class);
        controller.showService();
    }
}

MyConfiguration类中,使用了@ComponentScan("com.xcs.spring")注解告诉 Spring 在指定的包(在这里是 "com.xcs.spring")及其子包中搜索带有 @Component@Service@Repository@Controller 等注解的类,并将它们自动注册为 beans。这样spring就不必为每个组件明确写一个 bean 定义。Spring 会自动识别并注册它们。

@Configuration
@ComponentScan("com.xcs.spring")
public class MyConfiguration {

}

MyController,它使用@Autowired注入了一个Provider<MyService>。然后,MyController类的showService方法中使用myServiceProvider来获取MyService的两个实例。

@Controller
public class MyController {

    @Autowired
    private Provider<MyService> myServiceProvider;

    public void showService(){
        System.out.println("myServiceProvider1 = " + myServiceProvider.get());
        System.out.println("myServiceProvider2 = " + myServiceProvider.get());
    }
}

MyService 是一个简单的服务类,但我们没有定义任何方法或功能。

@Service
public class MyService {
    
}

运行结果发现,myServiceProvider1myServiceProvider2 两次获取到的 MyService 实例具有相同的对象引用(@235ecd9f)。这说明 MyService bean 是单例作用域的。

⚠️ 注意!
在 Spring 中默认的作用域是单例singleton这意味着在整个 Spring 容器中,一个特定的 bean 定义只有一个实例。因此,无论我们调用多少次 myServiceProvider.get(),它都会返回相同的 MyService 实例。如果我们想每次都获得一个新的 MyService 实例,我们需要将 MyService 定义为原型作用域prototype
myServiceProvider1 = com.xcs.spring.service.MyService@235ecd9f
myServiceProvider2 = com.xcs.spring.service.MyService@235ecd9f

六、时序图

Provider注册

sequenceDiagram
Title: Provider注册时序图

NamedApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
Note right of AnnotationConfigApplicationContext: 创建一个基于注解的应用上下文

AnnotationConfigApplicationContext->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext()
Note right of AnnotationConfigApplicationContext: AnnotationConfigApplicationContext 构造器

AnnotationConfigApplicationContext->>GenericApplicationContext:GenericApplicationContext()
Note right of GenericApplicationContext: 基于GenericApplicationContext的构造

GenericApplicationContext->>DefaultListableBeanFactory: new DefaultListableBeanFactory()
Note right of DefaultListableBeanFactory: 创建一个默认的可列出的bean工厂

DefaultListableBeanFactory->>DefaultListableBeanFactory: ClassUtils.forName("javax.inject.Provider",classLoader)
Note right of DefaultListableBeanFactory: 执行DefaultListableBeanFactory内的静态代码块

Provider实例化

sequenceDiagram
Title: Provider实例化时序图

AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor: postProcessProperties(pvs,bean,beanName)
Note right of AutowiredAnnotationBeanPostProcessor: 处理bean属性的注入

AutowiredAnnotationBeanPostProcessor->>InjectionMetadata: inject(target,beanName,pvs)
Note right of InjectionMetadata: 对目标bean执行依赖注入

InjectionMetadata->>AutowiredFieldElement: inject(bean,beanName,pvs)
Note right of AutowiredFieldElement: 对具体的字段执行依赖注入

AutowiredFieldElement->>AutowiredFieldElement: resolveFieldValue(field,bean,beanName)
Note right of AutowiredFieldElement: 解析字段值以进行注入

AutowiredFieldElement->>DefaultListableBeanFactory: resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)
Note right of DefaultListableBeanFactory: 解析指定的依赖关系

DefaultListableBeanFactory->>Jsr330Factory: new Jsr330Factory()
Note right of Jsr330Factory: 创建一个新的JSR-330工厂

Jsr330Factory->>DefaultListableBeanFactory: 返回Factory
Note right of DefaultListableBeanFactory: 获取JSR-330工厂实例

DefaultListableBeanFactory->>Jsr330Factory: createDependencyProvider(descriptor,beanName)
Note right of Jsr330Factory: 创建依赖提供者

Jsr330Factory->>Jsr330Provider: new Jsr330Provider(descriptor, beanName)
Note right of Jsr330Provider: 创建一个新的JSR-330提供者

Jsr330Provider->>Jsr330Factory: 返回Provider
Note right of Jsr330Factory: 获取JSR-330提供者实例

Jsr330Factory->>DefaultListableBeanFactory: 返回Provider
Note right of DefaultListableBeanFactory: 获取JSR-330提供者

DefaultListableBeanFactory->>AutowiredFieldElement: 返回Provider
Note right of AutowiredFieldElement: 

AutowiredFieldElement->>Field: field.set(bean, Provider)
Note right of Field: 使用Provider设置字段值

Provider泛型提取

sequenceDiagram
Title: Provider泛型提取时序图

MyController->>Jsr330Provider:get()
Note over Jsr330Provider: MyController尝试获取Bean。

Jsr330Provider->>DependencyObjectProvider:getValue()
Note over DependencyObjectProvider: Jsr330Provider委托给DependencyObjectProvider来实际获取值。

DependencyObjectProvider->>DefaultListableBeanFactory:doResolveDependency(descriptor,beanName,autowiredBeanNames,typeConverter)
Note over DefaultListableBeanFactory: DependencyObjectProvider将实际的依赖解析任务交给DefaultListableBeanFactory。

DefaultListableBeanFactory->>DependencyObjectProvider:返回Bean
Note over DependencyObjectProvider: DefaultListableBeanFactory完成解析并返回相关Bean。

DependencyObjectProvider->>Jsr330Provider:返回Bean
Note over Jsr330Provider: DependencyObjectProvider将Bean返回给Jsr330Provider。

Jsr330Provider->>MyController:返回Bean
Note over MyController: Jsr330Provider将Bean返回给MyController。

七、源码分析

Provider注册

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个MyConfiguration组件类。然后从Spring上下文中获取一个MyController类型的bean并调用了showService方法,

public class ProviderApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        MyController controller = context.getBean(MyController.class);
        controller.showService();
    }
}

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext构造函数中,执行了三个步骤,我们本次重点关注this()

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext()方法中初始化了的两个核心组件一个用于读取注解定义的bean (AnnotatedBeanDefinitionReader)另一个用于扫描类路径并自动检测bean组件 (ClassPathBeanDefinitionScanner),也是本次重点分析的内容。

public AnnotationConfigApplicationContext() {
    StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
    this.reader = new AnnotatedBeanDefinitionReader(this);
    createAnnotatedBeanDefReader.end();
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

org.springframework.context.support.GenericApplicationContext#GenericApplicationContext()方法中,创建了一个新的DefaultListableBeanFactory实例并赋值给beanFactory属性

public GenericApplicationContext() {
    this.beanFactory = new DefaultListableBeanFactory();
}

org.springframework.beans.factory.support.DefaultListableBeanFactory静态代码块中尝试加载JSR-330javax.inject API中定义的Provider接口。这个接口是Java的依赖注入规范中的一部分。如果该接口可用它将被赋给javaxInjectProviderClass变量;否则,如果接口不可用(例如,运行环境中没有提供相关的库),javaxInjectProviderClass将被设置为null

static {
    try {
        javaxInjectProviderClass =
            ClassUtils.forName("javax.inject.Provider", DefaultListableBeanFactory.class.getClassLoader());
    }
    catch (ClassNotFoundException ex) {
        // JSR-330 API not available - Provider interface simply not supported then.
        javaxInjectProviderClass = null;
    }
}

Provider实例化

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties方法中用于处理bean属性的后处理特别是通过@Autowired等注解进行的属性注入。

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 获取与bean名称和类相关的InjectionMetadata。
    // 这包括该bean需要进行注入的所有字段和方法。
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    
    try {
        // 使用获取到的InjectionMetadata实际进行属性的注入。
        metadata.inject(bean, beanName, pvs);
    }
    // 如果在注入过程中出现BeanCreationException直接抛出。
    catch (BeanCreationException ex) {
        throw ex;
    }
    // 捕获其他异常并以BeanCreationException的形式抛出提供详细的错误信息。
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    // 返回原始的PropertyValues因为这个方法主要关注依赖注入而不是修改属性。
    return pvs;
}

org.springframework.beans.factory.annotation.InjectionMetadata#inject方法中,主要目的是将所有需要注入的元素(例如带有@Autowired等注解的字段或方法注入到目标bean中。

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 获取已经检查的元素。通常,在初始化阶段,所有的元素都会被检查一次。
    Collection<InjectedElement> checkedElements = this.checkedElements;

    // 如果已经有检查过的元素,则使用它们,否则使用所有注入的元素。
    Collection<InjectedElement> elementsToIterate =
        (checkedElements != null ? checkedElements : this.injectedElements);

    // 如果有需要注入的元素...
    if (!elementsToIterate.isEmpty()) {
        // 遍历每个元素并注入到目标bean中。
        for (InjectedElement element : elementsToIterate) {
            // 对每个元素(字段或方法)执行注入操作。
            element.inject(target, beanName, pvs);
        }
    }
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject方法中首先检查字段的值是否已经被缓存。如果已缓存则从缓存中获取否则重新解析。然后它确保字段是可访问的特别是对于私有字段并将解析的值设置到目标bean的相应字段中。

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 获取代表带有@Autowired注解的字段的Field对象。
    Field field = (Field) this.member;

    Object value;
    // 如果字段的值已经被缓存(即先前已解析过),则尝试从缓存中获取。
    if (this.cached) {
        try {
            // 从缓存中获取已解析的字段值。
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 如果缓存中的bean已被意外删除 -> 重新解析。
            value = resolveFieldValue(field, bean, beanName);
        }
    }
    else {
        // 如果字段值未被缓存,直接解析。
        value = resolveFieldValue(field, bean, beanName);
    }

    // 如果解析到的值不为null...
    if (value != null) {
        // 使字段可访问这是必要的特别是当字段是private时。
        ReflectionUtils.makeAccessible(field);
        // 实际将解析的值注入到目标bean的字段中。
        field.set(bean, value);
    }
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue方法中,通过beanFactory.resolveDependency方法从Spring的bean工厂中解析字段的值。

@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
    // ... [代码部分省略以简化]
    Object value;
    try {
        // 通过`beanFactory.resolveDependency`方法从Spring的bean工厂中解析字段的值
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    }
    catch (BeansException ex) {
        throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
    }
    // ... [代码部分省略以简化]
    return value;
}

org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency方法中,如果依赖类型确实是javax.inject.Provider,则创建一个Jsr330Factory实例并调用其createDependencyProvider方法。这将创建并返回一个满足JSR-330规范的Provider实例。

@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    // ... [代码部分省略以简化]
    if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    }
    // ... [代码部分省略以简化]
}

org.springframework.beans.factory.support.DefaultListableBeanFactory.Jsr330Factory#createDependencyProvider方法中,创建并返回了一个 Jsr330Provider 的新实例。这个 Jsr330Provider 类是一个适配器或代理,用于实现 JSR-330 的 Provider 接口,从而允许 Spring 框架与其他遵循 JSR-330 规范的依赖注入框架互操作。

public Object createDependencyProvider(DependencyDescriptor descriptor, @Nullable String beanName) {
    return new Jsr330Provider(descriptor, beanName);
}

Provider泛型提取

MyController,它使用@Autowired注入了一个Provider<MyService>。然后,MyController类的showService方法中使用myServiceProvider来获取MyService的两个实例。

@Controller
public class MyController {

    @Autowired
    private Provider<MyService> myServiceProvider;

    public void showService(){
        System.out.println("myServiceProvider1 = " + myServiceProvider.get());
        System.out.println("myServiceProvider2 = " + myServiceProvider.get());
    }
}

org.springframework.beans.factory.support.DefaultListableBeanFactory.Jsr330Factory.Jsr330Provider#get方法中,简单地调用了 getValue() 方法来获取并返回一个对象。

@Override
@Nullable
public Object get() throws BeansException {
    return getValue();
}

org.springframework.beans.factory.support.DefaultListableBeanFactory.DependencyObjectProvider#getValue方法中,直接解析和返回该依赖。

@Nullable
protected Object getValue() throws BeansException {
    if (this.optional) {
        return createOptionalDependency(this.descriptor, this.beanName);
    }
    else {
        return doResolveDependency(this.descriptor, this.beanName, null, null);
    }
}

八、注意事项

  1. 依赖检查
    • 与直接注入Bean不同使用 Provider 注入的Bean在启动时不会进行立即检查。如果我们希望容器启动时进行依赖检查我们应该避免使用 Provider
  2. 性能
    • 每次调用 Provider.get()都可能会触发一个新的Bean的创建如果该Bean的scope是prototype的话。所以频繁地调用 Provider.get() 可能会有性能问题。
  3. 原型作用域
    • 如果我们使用 Provider 来注入原型作用域的Bean那么每次调用 Provider.get() 都会返回一个新的实例。需要确保这是我们所期望的行为。
  4. 泛型类型
    • 当使用 Provider必须为其提供泛型类型以指示所期望注入的Bean的类型。
  5. 与其他注解的组合
    • 虽然 Provider 可以与其他Spring注解@Qualifier)组合使用,但必须确保这些组合在语义上是有意义的。
  6. 错误处理
    • Provider.get() 方法可能会抛出 BeansException。在使用它时,我们应该准备处理这些异常。
  7. 与JSR-330的兼容性
    • 尽管 Spring 提供了对 Provider 的支持但如果我们正在使用其他支持JSR-330的容器确保 Provider 的行为在这些容器中是一致的。
  8. 循环依赖
    • 与直接Bean注入相比使用 Provider 可能会更容易解决某些循环依赖的问题,但仍然要注意避免引入不必要的复杂性。

九、总结

最佳实践总结

  1. 启动类与上下文配置
    • 利用 AnnotationConfigApplicationContext 为 Spring 提供了基于 Java 注解的配置方式。在这里,我们使用 MyConfiguration 类作为配置类来初始化 Spring 上下文。
  2. 组件扫描
    • 通过在 MyConfiguration 配置类上使用 @ComponentScan 注解,我们指示 Spring 自动扫描指定包及其子包中的组件(如 @Component@Service@Repository@Controller 注解的类),并将它们注册为 beans从而减少了明确为每个组件定义 bean 的需要。
  3. 服务注入
    • MyController 类中,我们使用 @Autowired 注解来自动注入 Provider<MyService> 类型的 bean。通过这种方式我们可以轻松地从 Provider 中获取 MyService 实例。
  4. 服务使用
    • MyControllershowService 方法中,我们两次调用 myServiceProvider.get() 以演示如何从 Provider 获取 MyService 的实例。
  5. 单例作用域
    • 通过运行结果,我们可以观察到两次获取的 MyService 实例是相同的,这说明 MyService bean 是单例作用域的。在 Spring 中,默认的 bean 作用域是单例,这意味着对于一个特定的 bean 定义,在整个 Spring 容器中只有一个实例。
  6. 注意点
    • 尽管 Spring 的默认作用域是单例,但在某些场景下,我们可能希望每次请求都返回一个新的 bean 实例。在这种情况下,我们可以考虑将 bean 定义为原型作用域。

源码分析总结

  1. 初始化与上下文配置
    • 启动时,我们使用AnnotationConfigApplicationContext为Spring创建了基于Java注解的配置环境并使用MyConfiguration组件类作为构造参数。
    • 在其构造函数中,通过this()初始化了两个核心组件一个用于读取注解定义的bean (AnnotatedBeanDefinitionReader) 和一个用于扫描类路径并自动检测bean组件 (ClassPathBeanDefinitionScanner)。
  2. JSR-330 Provider的加载
    • DefaultListableBeanFactory的静态代码块中尝试加载了JSR-330规范的Provider接口。
    • 如果Provider接口可用,它就会被赋给一个特定的类变量。
  3. 处理@Autowired注解
    • 通过AutowiredAnnotationBeanPostProcessor处理bean属性尤其是通过@Autowired进行的属性注入。
    • 在处理过程中,会解析字段的值并进行注入,其中涉及到Provider的使用和处理。
  4. 解析Provider的依赖
    • 当遇到一个字段或属性的类型为Provider<T>Spring会在DefaultListableBeanFactory中特殊处理它。
    • 对于JSR-330的Provider类型的依赖Spring会创建一个特定的Provider实例来满足这个依赖,这是通过Jsr330Factory和其内部类Jsr330Provider实现的。
  5. 获取Provider的值
    • Jsr330Provider中的get方法被调用时,会进一步调用getValue方法。
    • getValue方法负责实际的依赖解析返回所需的bean实例。