首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >彻底讲清Spring Bean

彻底讲清Spring Bean

作者头像
JavaEdge
修改2025-04-20 21:43:36
修改2025-04-20 21:43:36
8750
举报
文章被收录于专栏:JavaEdgeJavaEdge

本文已收录在Github关注我,紧跟本系列专栏文章,咱们下篇再续!

  • 🚀 魔都架构师 | 全网30W技术追随者
  • 🔧 大厂分布式系统/数据中台实战专家
  • 🏆 主导交易系统百万级流量调优 & 车联网平台架构
  • 🧠 AIGC应用开发先行者 | 区块链落地实践者
  • 🌍 以技术驱动创新,我们的征途是改变世界!
  • 👉 实战干货:编程严选网

Spring管理的这些bean由配置元数据创建,如被@Bean注解。Spring 内部又是如何存储这些信息的?

1 BeanDefinition

1.1 域

在容器内,这些bean定义被表示为BeanDefinition对象,它包含但不限于如下元数据:

这些元数据会转换为构成每个bean定义内的一组属性。

1.1.1 包限定类名
代码语言:java
复制
@SuppressWarnings("serial")
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {
  
  @Nullable
	private volatile Object beanClass;

被定义bean的实际实现类:

代码语言:java
复制
/**
 * Specify the bean class name of this bean definition.
 */
@Override
public void setBeanClassName(@Nullable String beanClassName) {
  this.beanClass = beanClassName;
}
1.1.2 bean行为

这些状态指示bean在容器中的行为(作用域、生命周期回调等)。如下即为作用域:

代码语言:java
复制
public abstract class AbstractBeanDefinition
  
  @Nullable
	private String scope = SCOPE_DEFAULT;

默认的作用域singleton:

代码语言:java
复制
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
1.1.3 需要的其它bean引用

这些引用即常见的协作或依赖对象:

代码语言:java
复制
public abstract class AbstractBeanDefinition
  
  @Nullable
	private String[] dependsOn;

如对于如下类:

代码语言:java
复制
@Configuration class Config {
    @Bean() @DependsOn({"bar", "baz"}) Object foo() { return null; }
}

除了包含有关如何创建特定bean信息的bean定义外,ApplicationContext实现还允许注册在容器外部(用户自定义的)创建的现有对象。

这是通过getBeanFactory()访问ApplicationContext#BeanFactory完成,该方法返回其DefaultListableBeanFactory实现。

代码语言:java
复制
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
    DefaultListableBeanFactory beanFactory = this.beanFactory;
    if (beanFactory == null) {
        throw new IllegalStateException("BeanFactory not initialized or already closed - " +
            "call 'refresh' before accessing beans via the ApplicationContext");
    }
    return beanFactory;
}

DefaultListableBeanFactory通过registerSingleton(..)registerBeanDefinition(..)支持此注册。我们开发的应用程序一般只使用通过常规的bean定义内的元数据定义的bean。

DefaultListableBeanFactory支持两种方式注册:

  • registerSingleton:bean实例就是传递给registerSingleton方法的singletonObject对象
代码语言:java
复制
@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
  super.registerSingleton(beanName, singletonObject);
  // 更新工厂内部的手动的单例名称集
  updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
  // 删除类型映射的缓存
  clearByTypeCache();
}
  • registerBeanDefinition(String beanName, BeanDefinition beanDefinition):容器根据BeanDefinition实例化bean

一般应用程序还是仅通过元数据定义的bean来定义bean。

Bean元数据和显式编码提供的单例实例需尽早地注册,方便容器在自动装配和其他自省(指在运行时来判断一个对象的类型的能力)过程能正确推理它们。虽然在某种程度上支持覆盖现有的元数据或单例实例,但在运行时(与对工厂的实时访问并发)对新bean的注册并不被正式支持,并且可能导致并发访问异常,比如bean容器中的状态不一致。

2 如何给 bean 命名?

每个bean都有一或多个标识符,这些标识符在其所在容器中必须唯一。一个bean通常只有一个标识符。但若它就是需要有一个以上的,那么多余标识符被视为别名。

在bean定义中,可组合使用id、name 属性指定bean的标识符。

  • 最多指定一个名称的id属性。一般来说,这些名字由字母数字组成(如myBean,fooService),但也可能包含特殊字符。
  • 如果还想为bean引入其他别名,可在name属性指定任意数量的其他名称。用逗号,、分号;或空格分隔。

在Spring 3.1前,id属性定义为xsd:ID类型,该类型限制了可能的字符。从3.1开始,它被定义为xsd:string类型。注意,Bean的id唯一性仍由容器强制执行,而不再是XML解析器。

开发者无需提供bean的nameid。如果未明确提供,容器将为该bean生成一个唯一name。但如果想通过使用ref元素或服务定位器模式查找来按名称引用该bean,则必须提供一个name。不提供名称的原因和内部beans和自动装配有关。

可以为bean提供多个名称。这些名称视作同一bean的别名,例如允许应用中的每个组件通过使用特定于组件本身的bean名称来引用公共依赖。

2.1 Bean命名规范

与对实例字段名称的命名规范相同。即小写字母开头,后跟驼峰式大小写。

示例:userServiceroleController

扫描类路径下的组件,Spring就会按照该习惯为未命名的组件生成bean名称:将类名初始字符转换为小写。其实这个规范即是JDK 里的Introspector#decapitalize方法,Spring正使用了它:

代码语言:java
复制
protected String buildDefaultBeanName(BeanDefinition definition) {
    String beanClassName = definition.getBeanClassName();
    Assert.state(beanClassName != null, "No bean class name set");
    String shortClassName = ClassUtils.getShortName(beanClassName);
    return Introspector.decapitalize(shortClassName);
}
decapitalize

java.beans.Introspector.decapitalize

代码语言:java
复制
public static String decapitalize(String name) {
    if (name == null || name.length() == 0) {
        return name;
    }
    // 如果有多个字符且第一和第二个字符均为大写字母
    // 则会保留原始大小写
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                    Character.isUpperCase(name.charAt(0))){
        return name;
    }
    // 使用简单的类名,并将其初始字符转换为小写
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}

2.2 如何为单个bean指定多个别名?

有时希望为单个Bean提供多个名称,尤其是在多系统环境。

XML配置

可使用<alias/>标签:

代码语言:bash
复制
<alias name="srcName" alias="extName"/>

定义别名后,可将同一容器中名为srcName的bean称为extName

环境示例:

  • 子系统A的配置元数据可通过名称subA-ds引用数据源
  • 子系统B可通过名称subB-ds引用数据源
  • 使用这俩子系统的主系统通过名称main-ds引用数据源。

要使所有三个名称都引用相同的对象,可将以下别名定义添加到配置元数据:

代码语言:xml
复制
<alias name="subA-ds" alias="subB-ds"/>
<alias name="subA-ds" alias="main-ds" />

现在,每个组件和主应用程序都可以通过唯一名称引用数据源,并且可保证不与任何其它定义冲突(等于高效创建了名称空间),而且引用的是同一bean。

Java代码配置

使用@Bean注解的name属性接收一个String数组。示例如下:

代码语言:java
复制
@Configuration
public class AppConfig {

    @Bean({"dataSource", "subA-ds", "subB-ds"})
    public DataSource dataSource() {
        // ...
    }
}

3 如何实例化 bean?

BeanDefinition可看做是创建对象的配方。容器在被询问时,会查看被命名过的bean的BeanDefinition,并使用该BeanDefinition中的配置元数据创建(或直接从缓存池获取)对应的对象实例。

比如在XML方式下,在<bean/>标签的class属性指定要实例化的对象的类型。这个class属性,其实就是BeanDefinition实例的Class属性,因此该属性一般强制必须指定。

可通过如下方式使用Class属性来实例化 bean:

3.1 构造器

在容器自身通过反射调用其构造器直接创建bean时,指定要构造的bean类,类似new运算符。该方式下,类基本上都能被Spring兼容。即bean类无需实现任何特定接口或以特定方式编码。 指定bean类即可。注意,根据所用的IoC类型,有时需要一个默认的无参构造器。

3.2 静态工厂方法

指定包含将要创建对象的静态工厂方法的实际类,容器将在类上调用静态工厂方法以创建bean。

定义使用静态工厂方法创建的bean时,可使用class属性来指定包含静态工厂方法的类,并使用factory-method属性指定工厂方法本身的名称。开发者应该能够调用此方法并返回一个存活对象,该对象随后将被视为通过构造器创建的。

这种BeanDefinition的一种用法是在老代码中调用static工厂。

看个例子,如下BeanDefinition指定将通过调用工厂方法来创建bean。该定义不指定返回对象的类型,而仅指定包含工厂方法的类。该示例中的initInstance()方法须是静态方法。

代码语言:xml
复制
<bean id="serverService"
    class="examples.ServerService"
    factory-method="initInstance"/>

可与上面的BeanDefinition协同的类:

代码语言:java
复制
public class ServerService {
    private static ServerService serverService = new ServerService();
    private ServerService() {}

    public static ServerService createInstance() {
        return serverService;
    }
}

3.3 实例工厂方法

使用该方式实例化会从容器中调用现有bean的非静态方法来创建新bean。要使用此机制,需将class属性置空,并在factory-bean属性中,在当前(或父/祖先)容器中指定包含要创建该对象的实例方法的bean的名称。factory-method设置工厂方法本身的名称。

示例如下,来看看如何配置这样的bean:

代码语言:xml
复制
<!-- factory bean, 包含 createInstance()方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
</bean>

<!-- 通过factory-bean创建的bean -->
<bean id="clientService"
      factory-bean="serviceLocator"
      factory-method="createClientServiceInstance"/>

相应的类:

代码语言:java
复制
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

一个工厂类也可容纳一个以上的工厂方法,如下:

代码语言:java
复制
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

这种方式还表明,即使是工厂bean,也可通过依赖注入进行管理和配置。

“factory bean”指在Spring容器中配置并通过实例或静态工厂方法创建对象的bean。相比下,FactoryBean是特定于Spring的FactoryBean实现类。

4 如何确定Bean的运行时类型?

bean元数据定义中的指定类只是初始类引用,可能结合使用的如下方式之一:

  • 声明的工厂方法
  • FactoryBean类,该情况可能导致bean的运行时类型不同
  • 实例级工厂方法(通过指定的factory-bean名称解析),该情况下直接就不设置了

因此,看起来确定bean运行时类型绝非易事,该如何准确获取呢?

BeanFactory.getType

推荐调用,确定bean运行时类型。

代码语言:java
复制
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;

该方法可确定给定名称bean的类型。 返回针对相同bean名称的BeanFactory.getBean调用将返回的对象的类型。

且该方法的实现考虑了前面穷举的所有情况,并针对于FactoryBean ,返回FactoryBean所创建的对象类型,和FactoryBean.getObjectType()返回一致。

代码语言:java
复制
public class StaticListableBeanFactory implements ListableBeanFactory {
  
  @Override
	public Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException {
		String beanName = BeanFactoryUtils.transformedBeanName(name);

		Object bean = this.beans.get(beanName);
		if (bean == null) {
			throw new NoSuchBeanDefinitionException(beanName,
					"Defined beans are [" + StringUtils.collectionToCommaDelimitedString(this.beans.keySet()) + "]");
		}

		if (bean instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
			// If it's a FactoryBean, we want to look at what it creates, not the factory class.
			return ((FactoryBean<?>) bean).getObjectType();
		}
		return bean.getClass();
	}

@Bean 和 @Controller 注解的区别

@Bean
  • 这是一个方法级别的注解,用于告诉 Spring 这个方法将返回一个要注册为 Spring 应用上下文中的 Bean 的对象。
  • 通常用于配置类(即标有 @Configuration 注解的类)的方法。
代码语言:java
复制
@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
@Controller:
  • 这是一个类级别的注解,用于标记一个类是一个 Spring MVC 控制器,负责处理 Web 请求。
  • 通过这个注解,Spring 会自动检测并将该类注册为一个处理 HTTP 请求的控制器。
  • 通常与 @RequestMapping 等注解一起使用来映射请求 URL。
代码语言:java
复制
@Controller
public class MyController {

    @RequestMapping("/hello")
    public String sayHello() {
        return "hello";
    }
}
总结
  • @Bean 用于方法级别,定义 Spring 上下文中的 Bean。
  • @Controller 用于类级别,定义一个控制器以处理 Web 请求。

两者的作用不同,应用场景也不同。@Bean 主要用于配置和定义 Bean,而 @Controller 主要用于 MVC 模式中定义控制器。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/09/19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 BeanDefinition
    • 1.1 域
      • 1.1.1 包限定类名
      • 1.1.2 bean行为
      • 1.1.3 需要的其它bean引用
  • 2 如何给 bean 命名?
    • 2.1 Bean命名规范
      • decapitalize
    • 2.2 如何为单个bean指定多个别名?
      • XML配置
      • Java代码配置
  • 3 如何实例化 bean?
    • 3.1 构造器
    • 3.2 静态工厂方法
    • 3.3 实例工厂方法
  • 4 如何确定Bean的运行时类型?
    • BeanFactory.getType
    • @Bean 和 @Controller 注解的区别
      • @Bean
      • @Controller:
      • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档