前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring上下文无法通过getBean获取某些bean?

Spring上下文无法通过getBean获取某些bean?

作者头像
叔牙
修改2024-01-26 08:26:29
2360
修改2024-01-26 08:26:29
举报

一、问题与现象

1.问题

在springboot项目中发送应用内事件,发现业务并没有如预期方式处理,查看日志发现有报错:

发现通过容器上下文工具类getBean的方式获取ApplicationEventPublisher失败;代码如下:

上下文工具类:

代码语言:javascript
复制
@Component
public class AppSpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;


    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        AppSpringContextUtil.applicationContext = applicationContext;
    }
    /**
    * 根据类型获取bean
    */
    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }
}

发送事件:

代码语言:javascript
复制
/**
     * 发送自定义事件
     * @param type
     * @param buzzId
     */
default void sendCustomEvent(Integer type,String buzzId) {
    try {
        CustomEventBO eventBO = new CustomEventBO();
        eventBO.setType(type);
        eventBO.setBuzzId(buzzId);
        AppSpringContextUtil.getBean(ApplicationEventPublisher.class).publishEvent(eventBO);
    } catch (Exception e) {
        log.warn("sendCustomEvent occur error;type={},buzzId={}",type,buzzId,e);
    }
}

2.现象

在业务bean中通过@Autowired的方式,然后调用发送事件能力,就能获取到ApplicationEventPublisher对应的bean:

业务bean:

代码语言:javascript
复制
@Component
public class TestCustomEvent implements ISendCustomEvent {
    @Autowired
    ApplicationEventPublisher applicationEventPublisher;


    public void testSendEvent(int type,String buzzId) {
        this.sendCustomEvent(applicationEventPublisher,type,buzzId);
    }
}

把发送事件上下文获取ApplicationEventPublisher的方式改为传参:

代码语言:javascript
复制
default void sendCustomEvent(ApplicationEventPublisher eventPublisher,Integer type,String buzzId) {
    try {
        CustomEventBO eventBO = new CustomEventBO();
        eventBO.setType(type);
        eventBO.setBuzzId(buzzId);
        eventPublisher.publishEvent(eventBO);
    } catch (Exception e) {
        log.warn("sendCustomEvent occur error;type={},buzzId={}",type,buzzId,e);
    }
}

这样就可以正常发送事件了。那么问题来了,为什么对于ApplicationEventPublisher,为什么@Autowired方式可以获取,上下文getBean方式获取不到?

二、@Autowired与上下文getBean有什么不同?

1.bean的注册

springboot项目启动的时候,会调用AbstractApplicationContext的refresh方法:

代码语言:javascript
复制
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");


// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
//...省略...
}

然后会调用做一些初始化准备:

代码语言:javascript
复制
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  // Tell the internal bean factory to use the context's class loader etc.
  beanFactory.setBeanClassLoader(getClassLoader());
  //...省略 ...
  // BeanFactory interface not registered as resolvable type in a plain factory.
  // MessageSource registered (and found for autowiring) as a bean.
  beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
  beanFactory.registerResolvableDependency(ResourceLoader.class, this);
  beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
  beanFactory.registerResolvableDependency(ApplicationContext.class, this);
  //...省略 ...
}

中间几个registerResolvableDependency大概意思是使用相应的定义类注册一个特殊的依赖项类型。这适用于工厂上下文引用,这些引用应该是可自动注册的,但在工厂中没有定义为bean。也即是你可以使用对应的bean,但是beanFactory中并没有对应定义的bean。

代码语言:javascript
复制
public void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue) {
  Assert.notNull(dependencyType, "Dependency type must not be null");
  if (autowiredValue != null) {
    if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) {
      throw new IllegalArgumentException("Value [" + autowiredValue +
          "] does not implement specified dependency type [" + dependencyType.getName() + "]");
    }
    this.resolvableDependencies.put(dependencyType, autowiredValue);
  }
}

从registerResolvableDependency方法定义中可以看到,这里实际把特殊类型的依赖添加到resolvableDependencies(本身是个map)本地缓存中。

而对于项目中自己定义的bean会注入到上下文和beanFactory中,这也是区别的地方。

2.@Autowired方式获取

对于@Autowired如何生效和bean如何注入的,我们之前的文章有介绍《@Autowired注解原理分析》,此处不展开详细分析。

@Autowired注入获取是由AutowiredAnnotationBeanPostProcessor驱动,会调用AutowiredFieldElement的inject方法注入,然后会调用到其resolveFieldValue方法:

代码语言:javascript
复制
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
  DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
  desc.setContainingClass(bean.getClass());
  Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
  Assert.state(beanFactory != null, "No BeanFactory available");
  TypeConverter typeConverter = beanFactory.getTypeConverter();
  Object value;
  try {
    value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
  }
  catch (BeansException ex) {
    throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
  }
  //省略放入缓存部分
  return value;
}

此处的beanFactory类型是DefaultListableBeanFactory,resolveDependency方法实现如下:

代码语言:javascript
复制
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
    @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
  descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
  if (Optional.class == descriptor.getDependencyType()) {
    return createOptionalDependency(descriptor, requestingBeanName);
  }
  else if (ObjectFactory.class == descriptor.getDependencyType() ||
      ObjectProvider.class == descriptor.getDependencyType()) {
    return new DependencyObjectProvider(descriptor, requestingBeanName);
  }
  else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
    return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
  }
  else {
    Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
        descriptor, requestingBeanName);
    if (result == null) {
      result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    }
    return result;
  }
}

接着会调用doResolveDependency方法查找依赖:

代码语言:javascript
复制
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {


InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
//...省略部分代码...
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
//...省略部分代码...
return result;
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}

这里调用了@Autowired注入依赖和核心方法findAutowireCandidates:

代码语言:javascript
复制
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
    this, requiredType, true, descriptor.isEager());
    Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
    for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
        Class<?> autowiringType = classObjectEntry.getKey();
        if (autowiringType.isAssignableFrom(requiredType)) {
            Object autowiringValue = classObjectEntry.getValue();
            autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
            if (requiredType.isInstance(autowiringValue)) {
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }
    for (String candidate : candidateNames) {
            if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
        addCandidateEntry(result, candidate, descriptor, requiredType);
        }
    }
//...省略部分代码...
    return result;
}

从代码中可以看出,对于@Autowired方式的属性获取和依赖注入,会同时从BeanDefinitionNames和resolvableDependencies两个地方寻找依赖。从BeanDefinitionNames获取的核心代码是DefaultListableBeanFactory的getBeanNamesForType方法(上下文getBean方式也会用到):

代码语言:javascript
复制
public String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
  if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
    return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
  }
  Map<Class<?>, String[]> cache =
      (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
  String[] resolvedBeanNames = cache.get(type);
  if (resolvedBeanNames != null) {
    return resolvedBeanNames;
  }
  resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
  if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
    cache.put(type, resolvedBeanNames);
  }
  return resolvedBeanNames;
}

3.上下文getBean方式获取

我们通过debug看到实现ApplicationContextAware注入的上下文类型是AnnotationConfigServletWebServerApplicationContext:

调用getBean会调用AbstractApplicationContext的getBean方法:

代码语言:javascript
复制
public <T> T getBean(Class<T> requiredType) throws BeansException {
  assertBeanFactoryActive();
  return getBeanFactory().getBean(requiredType);
}

然后会调用DefaultListableBeanFactory的getBean方法:

代码语言:javascript
复制
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
  Assert.notNull(requiredType, "Required type must not be null");
  Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
  if (resolved == null) {
    throw new NoSuchBeanDefinitionException(requiredType);
  }
  return (T) resolved;
}

接着调用私有resolveBean方法:

代码语言:javascript
复制
private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
  NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
  if (namedBean != null) {
    return namedBean.getBeanInstance();
  }
  //...省略部分代码...
  return null;
}

resolveBean接着会调用resolveNamedBean,然后调用getBeanNamesForType方法:

代码语言:javascript
复制
public String[] getBeanNamesForType(ResolvableType type) {
  return getBeanNamesForType(type, true, true);
}

最后,也会进入如前边@Autowired会调用的getBeanNamesForType方法:

代码语言:javascript
复制
public String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
  if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
    return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
  }
  Map<Class<?>, String[]> cache =
      (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
  String[] resolvedBeanNames = cache.get(type);
  //...省略...
  return resolvedBeanNames;
}

4.两种方式获取bean的不同

  • @Autowired:同时从BeanDefinitionNames和resolvableDependencies两个地方寻找
  • 上下文getBean:只会从BeanDefinitionNames中获取

结合前边所述,对于特殊类型的类,比如ApplicationEventPublisher,只会注册到resolvableDependencies,所以getBean的方式无法获取到,也就解释了异常报错的原因。

三、还有哪些bean不能通过上下文获取?

从AbstractApplicationContext的prepareBeanFactory方法可以看出目前有以下几个bean不能通过getBean方式获取:

  • BeanFactory:bean工厂
  • ResourceLoader: 资源加载器
  • ApplicationEventPublisher: 事件发送器
  • ApplicationContext: 应用上下文

当然如果自己也可以定义一些bean通过registerResolvableDependency注入,从而实现只能@Autowired方式注入,不能getBean方式获取。

四、为什么这么设计?

当然框架这样设计,一定有其存在的意义,通过翻阅官方文档和其他资料,总结出以下几点如此设计的原因。

  • 单一职责原则:特殊组件的职责和作用区别于普通bean,而不是作为 Spring 容器中的一个普通 bean。如果通过 getBean 获取,它就可能被当作一个普通的 bean 使用,这会增加它的职责,使其更难以维护和测试。
  • 避免混淆:如果可以通过 getBean获取特殊组件,那么开发者可能会误以为它是一个普通的bean,从而在不需要的地方使用它。这会导致代码的混乱和潜在的运行时错误。
  • 控制其生命周期:在 Spring容器中,bean 的生命周期(例如:单例、原型等)是可配置的。但是spring框架自带的特殊组件通常在应用启动时自动创建,并一直存在到应用关闭。这样的生命周期与常见的bean生命周期不同,因此不将其作为可配置的bean更符合其设计初衷。
  • 减少依赖:如果特殊组件是通过getBean获取的,那么它可能会被许多不需要它的组件或bean间接依赖,这会增加系统的复杂性。

总的来说,将特殊组件类似ApplicationEventPublisher设计为不能通过getBean方式获取有助于保持其职责的单一性,减少混淆和不必要的依赖,并更好地控制其生命周期。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、问题与现象
    • 1.问题
      • 2.现象
      • 二、@Autowired与上下文getBean有什么不同?
        • 1.bean的注册
          • 2.@Autowired方式获取
            • 3.上下文getBean方式获取
              • 4.两种方式获取bean的不同
              • 三、还有哪些bean不能通过上下文获取?
              • 四、为什么这么设计?
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档