当前位置: 首页 > news >正文

Spring Boot 3 中Bean的配置和实例化详解

一、引言

在Java企业级开发领域,Spring Boot凭借其简洁、快速、高效的特点,迅速成为了众多开发者的首选框架。Spring Boot通过自动配置、起步依赖等特性,极大地简化了Spring应用的搭建和开发过程。而在Spring Boot的众多核心特性中,Bean的管理与解析无疑是重中之重。本文将从IOC思想出发,深入探讨Spring Boot 3中Bean的解析过程,包括XML和注解两种配置方式、refresh方法的解析以及Bean实例化过程。

说明:本文分析使用的Spring Boot源码版本为3.3.5

二、IOC思想

IOC(Inversion of Control,控制反转)是Spring框架的核心思想之一。它意味着对象之间的关系不再由传统的程序控制,而是由Spring容器来统一管理对象的创建、协调和销毁。通过这种方式,对象的依赖关系被解耦,提高了代码的可维护性和可扩展性。

在Spring Boot 3中,IOC容器负责实例化、配置和组装Bean。Bean是Spring框架中构成应用程序的基本组件,Spring容器通过配置文件或注解等方式来定义和管理这些Bean。

三、XML方式配置Bean介绍

在Spring Boot中,虽然推荐使用基于Java的配置(例如使用@Configuration和@Bean注解),但XML配置仍然是支持的,特别是对于需要与遗留系统集成或偏好XML配置的场景。以下是使用XML配置bean和注入bean的几种方式:

  • 无参构造
  • 有参构造
  • 静态工厂方法
  • 实例工厂方法

1. 无参构造
XML配置:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 通过无参构造方法创建bean --><bean id="myBean" class="com.example.MyBean"/></beans>

Java类:

package com.example;public class MyBean {public void doSomething() {System.out.println("Doing something in MyBean");}
}

2. 有参构造
XML配置:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 通过有参构造方法创建bean,使用<constructor-arg>指定参数 --><bean id="myBeanWithArgs" class="com.example.MyBeanWithArgs"><constructor-arg value="Argument1"/><constructor-arg value="Argument2"/></bean></beans>

Java类:

package com.example;public class MyBeanWithArgs {private String arg1;private String arg2;public MyBeanWithArgs(String arg1, String arg2) {this.arg1 = arg1;this.arg2 = arg2;}public void doSomething() {System.out.println("Doing something with arguments: " + arg1 + ", " + arg2);}
}

3. 静态工厂方法
XML配置:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 通过静态工厂方法创建bean --><bean id="myStaticFactoryBean" class="com.example.MyStaticFactoryBean" factory-method="createMyBean"/></beans>

Java类:

package com.example;public class MyStaticFactoryBean {public static MyBean createMyBean() {return new MyBean();}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean created by static factory");}
}

4. 实例工厂方法
XML配置:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 先创建工厂bean --><bean id="myInstanceFactoryBean" class="com.example.MyInstanceFactoryBean"/><!-- 通过实例工厂方法创建bean,使用factory-bean和factory-method属性 --><bean id="myBeanFromFactory" factory-bean="myInstanceFactoryBean" factory-method="createMyBean"/></beans>

Java类:

package com.example;public class MyInstanceFactoryBean {public MyBean createMyBean() {return new MyBean();}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean created by instance factory");}
}

5. 测试
在测试类上使用@ContextConfiguration注解,并指定要加载的配置文件或配置类的位置。例如,如果你有一个名为applicationContext.xml的XML配置文件,可以这样写:

@SpringBootTest
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class MyBeanTest {@Autowiredprivate MyBean myBean;@Testpublic void testMyBean() {// 调用myBean的方法并进行断言myBean.doSomething();// 根据需要添加更多的断言来验证myBean的行为}
}

在这些示例中,XML配置文件定义了bean的创建方式和依赖注入。Java类则定义了bean的逻辑。请注意,对于现代Spring Boot应用,通常推荐使用基于Java的配置,因为它提供了更强大的类型检查和重构支持。然而,在需要与遗留系统或特定库集成时,XML配置仍然是一个有用的选项。

四、注解方式配置Bean介绍

在Spring Boot中,有多种方式可以配置和注册Bean。下面介绍五种方式:

  • @Component
  • @Configuration+@Bean
  • 实现FactoryBean
  • 实现BeanDefinitionRegistryPostProcessor
  • 实现ImportBeanDefinitionRegistrar

1. 使用 @Component 注解
@Component 注解用于自动检测和注册Bean。通常用于类级别。以下几种注解都是同类型:

@Component:用于标识一个普通的Spring Bean组件。
@Service:用于标识业务逻辑层的Bean。
@Repository:用于标识数据访问层的Bean。
@Controller:用于标识控制层的Bean。

import org.springframework.stereotype.Component;@Component
public class MyComponent {public void doSomething() {System.out.println("Doing something in MyComponent");}
}

使用@Autowired或者@Resource注解在其他类中注入。

2. 使用 @Configuration 和 @Bean 注解
@Configuration 注解用于定义配置类,@Bean 注解用于在配置类中声明Bean。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyConfiguration {@Beanpublic MyBean myBean() {return new MyBean();}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean");}
}

在这个例子中,MyConfiguration是一个配置类,其中@Bean注解的方法myBean用于声明一个Bean。当Spring容器启动时,它会调用这个方法并注册返回的Bean实例。

使用@Autowired或者@Resource注解在其他类中注入。

3. 实现 FactoryBean 接口
FactoryBean 接口允许你自定义Bean的创建过程。

import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;@Component
public class MyFactoryBean implements FactoryBean<MyBean> {@Overridepublic MyBean getObject() throws Exception {return new MyBean();}@Overridepublic Class<?> getObjectType() {return MyBean.class;}@Overridepublic boolean isSingleton() {return true;}
}class MyBean {public void doSomething() {System.out.println("Doing something in MyBean created by MyFactoryBean");}
}

使用@Autowired或者@Resource注解在其他类中注入。

4. 实现 BeanDefinitionRegistryPostProcessor 接口
BeanDefinitionRegistryPostProcessor 接口允许你在Spring容器启动时动态地添加、修改或删除Bean定义。

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionRegistry;
import org.springframework.beans.factory.config.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {BeanDefinition beanDefinition = new RootBeanDefinition(MyDynamicBean.class);registry.registerBeanDefinition("myDynamicBean", beanDefinition);}@Overridepublic void postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory beanFactory) throws BeansException {// No-op}
}class MyDynamicBean {public void doSomething() {System.out.println("Doing something in MyDynamicBean");}
}

使用@Autowired或者@Resource注解在其他类中注入。

5. 实现 ImportBeanDefinitionRegistrar 接口
ImportBeanDefinitionRegistrar 接口允许你在使用 @Import 注解时动态地注册Bean定义。

首先,创建一个实现 ImportBeanDefinitionRegistrar 的类:

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {BeanDefinition beanDefinition = new RootBeanDefinition(MyImportedBean.class);registry.registerBeanDefinition("myImportedBean", beanDefinition);}
}class MyImportedBean {public void doSomething() {System.out.println("Doing something in MyImportedBean");}
}

然后,在配置类中使用 @Import 注解导入这个注册器:

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyImportingConfiguration {// No additional bean definitions needed here
}

五、XML配置方式和注解配置方式对比

使用XML配置Bean和使用注解方式是Spring框架中两种常见的配置方式,它们各有优缺点。

XML配置Bean的优缺点
优点:

  • 集中式管理:XML配置文件是集中式的,可以方便地对所有Bean进行统一管理,不需要和代码绑定,降低了类之间的耦合度。
  • 清晰明了:XML配置方式能够清晰地展示对象之间的关系和业务类之间的调用,使得配置信息一目了然。
  • 易于扩展:XML配置方式具有良好的扩展性,可以通过添加新的XML文件或修改现有文件来扩展配置。
  • 与其他系统交互方便:XML作为一种标准的数据交换格式,使得Spring应用可以方便地与其他系统进行数据交互和共享。

缺点:

  • 解析开销大:XML配置文件需要解析,这会占用一定的系统资源,可能会影响应用程序的性能。
  • 维护成本高:XML配置文件与代码分离,需要同时维护代码和配置文件,增加了维护成本。
  • 类型不安全:XML配置文件中的配置信息在编译期间无法验证其正确性,只能在运行时发现错误,这增加了排查问题的难度。
  • 不支持复杂的依赖注入:XML配置方式在处理复杂的依赖注入关系时可能不够灵活和直观。

注解方式配置Bean的优缺点
优点:

简化配置:注解方式将配置信息直接写在代码中,简化了配置文件,降低了维护成本。
类型安全:注解方式可以在编译期间验证配置信息的正确性,减少了运行时错误的可能性。
提高开发效率:注解方式使得配置信息与代码紧密结合,开发者可以更快地理解和修改配置,提高了开发效率。
支持复杂的依赖注入:注解方式可以更灵活和直观地处理复杂的依赖注入关系。

缺点:

  • 修改麻烦:如果需要对注解进行修改,通常需要重新编译整个项目,这可能会增加开发周期。
  • 可读性较差:注解方式将配置信息嵌入代码中,可能会降低代码的可读性,特别是当注解较多时。
  • 可能引入潜在问题:如果注解使用不当,可能会引入一些潜在的问题,如循环依赖、配置错误等。

六、refresh方法解析

Spring Boot依赖于Spring。Bean的管理和实例化是在Spring的org.springframework.context.support.AbstractApplicationContext#refresh方法完成的。

在Spring Boot 3中,refresh方法是Spring容器初始化的核心步骤之一。当Spring容器创建后,它会调用refresh方法来完成一系列的配置和初始化工作。

Spring Boot应用启动调用栈
通过上图可以看到,Spring Boot启动时会调用Spring的org.springframework.context.support.AbstractApplicationContext#refresh方法加载Bean。

下面是refresh方法的源码说明。

refresh方法说明
核心内容是:

加载或刷新配置的持久化表示(到内存),该配置可能来自基于Java的配置、XML文件、属性文件、关系数据库模式或其他格式。
由于这是一个启动方法,如果失败,它应该销毁已经创建的单例,以避免资源悬空。换句话说,调用此方法后,要么所有单例都被实例化,要么根本没有单例被实例化。

该方法的源码如下:

@Overridepublic void refresh() throws BeansException, IllegalStateException {this.startupShutdownLock.lock();try {this.startupShutdownThread = Thread.currentThread();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);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (RuntimeException | Error ex ) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {contextRefresh.end();}}finally {this.startupShutdownThread = null;this.startupShutdownLock.unlock();}}

refresh方法的主要步骤包括:

1、prepareRefresh:为刷新操作做准备

  • 设置容器的启动时间和状态
  • 撤销关闭状态
  • 初始化属性设置
  • 检查必备属性是否存在等

2、obtainFreshBeanFactory:

  • 设置BeanFactory序列化ID
  • 获取新的BeanFactory实例

3、prepareBeanFactory:

  • 对BeanFactory进行配置,如设置类加载器、忽略的依赖等
  • 添加后置处理器
  • 设置忽略的自动装配接口
  • 注册一些组件

4、postProcessBeanFactory:允许上下文子类对BeanFactory进行后置处理。
5、invokeBeanFactoryPostProcessors:调用已注册的BeanFactoryPostProcessor实现。

  • 调用BeanDefinitionRegistryPostProcessor实现向容器内添加Bean定义
  • 调用BeanFactoryPostProcessor实现向容器内Bean的定义添加属性

6、registerBeanPostProcessors:注册BeanPostProcessor实现,用于拦截Bean的创建过程。

  • 找到BeanPostProcessor的实现
  • 排序后注册进容器

7、initMessageSource:初始化国际化相关的属性。
8、initApplicationEventMulticaster:初始化事件多播器。
9、onRefresh:这是一个空实现,留给子类来创建Web容器等特定操作。
10、registerListeners:将事件监听器注册到事件多播器中。

  • 添加容器内的事件监听器到事件多播器
  • 派发早期事件

11、finishBeanFactoryInitialization:实例化所有剩余的非懒加载单例Bean。
12、finishRefresh:完成刷新操作,如发布刷新事件、清空缓存等。

  • 初始化生命周期处理器
  • 调用生命周期处理器onRefresh方法
  • 发布ContextRefreshedEvent事件

七、refresh方法中的Bean实例化解析

在refresh方法的finishBeanFactoryInitialization步骤中,Spring容器会实例化所有剩余的非懒加载单例Bean。下面是该方法的源码

 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// Initialize conversion service for this context.if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));}// Register a default embedded value resolver if no BeanFactoryPostProcessor// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:// at this point, primarily for resolution in annotation attribute values.if (!beanFactory.hasEmbeddedValueResolver()) {beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));}// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);for (String weaverAwareName : weaverAwareNames) {try {beanFactory.getBean(weaverAwareName, LoadTimeWeaverAware.class);}catch (BeanNotOfRequiredTypeException ex) {if (logger.isDebugEnabled()) {logger.debug("Failed to initialize LoadTimeWeaverAware bean '" + weaverAwareName +"' due to unexpected type mismatch: " + ex.getMessage());}}}// Stop using the temporary ClassLoader for type matching.beanFactory.setTempClassLoader(null);// Allow for caching all bean definition metadata, not expecting further changes.beanFactory.freezeConfiguration();// Instantiate all remaining (non-lazy-init) singletons.beanFactory.preInstantiateSingletons();}

上面代码逻辑如下:

1、初始化转换服务:如果存在 ConversionService 类型的Bean,则将其设置为BeanFactory的转换服务。
2、注册默认的嵌入值解析器:如果没有其他BeanFactoryPostProcessor注册过嵌入值解析器,则注册一个默认的解析器,用于解析注解属性值中的占位符。
3、初始化 LoadTimeWeaverAware 类型的 Bean:提前初始化这些Bean,以便它们可以尽早注册其转换器。
4、停止使用临时类加载器:停止使用临时类加载器进行类型匹配。
5、冻结配置:允许缓存所有Bean定义元数据,不再期望进一步的更改。
6、预实例化单例 Bean:实例化所有剩余的非懒加载单例Bean。

其中第6步对单例的Bean进行实例化。调用方法org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons实例化单例Bean,下面是该方法的源码:

public void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {getBean(beanName);}}else {getBean(beanName);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize").tag("beanName", beanName);smartSingleton.afterSingletonsInstantiated();smartInitialize.end();}}}

preInstantiateSingletons方法的主要功能是在Spring容器启动时预实例化所有非懒加载的单例Bean。代码解释如下:

1、日志记录:如果启用了跟踪日志,记录预实例化的开始信息。
2、获取 Bean 名称列表:创建一个Bean名称列表的副本,以便在初始化过程中可以安全地注册新的Bean定义。
3、初始化非懒加载的单例 Bean:

  • 遍历Bean名称列表,获取每个Bean的定义。
  • 检查Bean是否是非抽象的、单例的且不是懒加载的。
  • 如果是FactoryBean,先获取FactoryBean实例,再根据条件决定是否获取实际的Bean实例。
    否则,直接获取Bean实例。

4、触发后初始化回调:

  • 再次遍历Bean名称列表,获取每个单例Bean实例。
  • 如果Bean实现了SmartInitializingSingleton接口,调用其afterSingletonsInstantiated方法。
  • Bean的实例化是调用org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法实现的,源码如下:
 public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);}
调用doGetBean方法实现Bean的实例化,源码如下:protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {String beanName = transformedBeanName(name);Object beanInstance;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {// Fail if we're already creating this bean instance:// We're assumably within a circular reference.if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// Check if bean definition exists in this factory.BeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// Not found -> check parent.String nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory abf) {return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}else if (args != null) {// Delegation to parent with explicit args.return (T) parentBeanFactory.getBean(nameToLookup, args);}else if (requiredType != null) {// No args -> delegate to standard getBean method.return parentBeanFactory.getBean(nameToLookup, requiredType);}else {return (T) parentBeanFactory.getBean(nameToLookup);}}if (!typeCheckOnly) {markBeanAsCreated(beanName);}StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate").tag("beanName", name);try {if (requiredType != null) {beanCreation.tag("beanType", requiredType::toString);}RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// Guarantee initialization of beans that the current bean depends on.String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}registerDependentBean(dep, beanName);try {getBean(dep);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}catch (BeanCreationException ex) {if (requiredType != null) {// Wrap exception with current bean metadata but only if specifically// requested (indicated by required type), not for depends-on cascades.throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Failed to initialize dependency '" + ex.getBeanName() + "' of " +requiredType.getSimpleName() + " bean '" + beanName + "': " +ex.getMessage(), ex);}throw ex;}}}// Create bean instance.if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}else {String scopeName = mbd.getScope();if (!StringUtils.hasLength(scopeName)) {throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");}Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {Object scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}});beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new ScopeNotActiveException(beanName, scopeName, ex);}}}catch (BeansException ex) {beanCreation.tag("exception", ex.getClass().toString());beanCreation.tag("message", String.valueOf(ex.getMessage()));cleanupAfterBeanCreationFailure(beanName);throw ex;}finally {beanCreation.end();if (!isCacheBeanMetadata()) {clearMergedBeanDefinition(beanName);}}}return adaptBeanInstance(name, beanInstance, requiredType);}

创建Bean流程图

doGetBean方法的主要功能是从BeanFactory中获取指定名称的Bean实例。具体步骤如下:

1、转换 Bean 名称:将传入的Bean名称转换为标准形式。

String beanName = transformedBeanName(name);

2、检查 Singleton 缓存:如果缓存中存在Singleton实例且没有参数,则直接返回。

Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {// 返回缓存实例
}

3、检查原型创建状态:如果当前正在创建原型Bean,则抛出异常。

if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);
}

4、查找父工厂:如果当前工厂没有定义该Bean,则委托给父工厂处理。

BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// 委托给父工厂
}

5、标记 Bean 创建:标记Bean已经被创建。

if (!typeCheckOnly) {markBeanAsCreated(beanName);
}

6、初始化依赖:初始化当前Bean所依赖的其他Bean。

String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {for (String dep : dependsOn) {// 初始化依赖}
}

7、创建 Bean 实例:

if (mbd.isSingleton()) {// 单例
} else if (mbd.isPrototype()) {// 原型
} else {// 其他作用域
}
  • 如果是Singleton,则从缓存中获取或创建并缓存。
  • 如果是Prototype,则每次调用时都创建新的实例。
  • 其他作用域的Bean通过相应的Scope对象创建。

8、处理异常:捕获并处理创建Bean时的异常。

catch (BeansException ex) {// 处理异常
}

9、清理元数据:如果不需要缓存元数据,则清除合并后的Bean定义。

if (!isCacheBeanMetadata()) {clearMergedBeanDefinition(beanName);
}

10、返回 Bean 实例:

return adaptBeanInstance(name, beanInstance, requiredType);

上面代码调用AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])实例化Bean,之后调用SimpleInstantiationStrategy#instantiate(RootBeanDefinition, String, BeanFactory)方法初始化Bean,调用栈如下

Bean实例化调用栈
SimpleInstantiationStrategy#instantiate源码如下:

 public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// Don't override the class with CGLIB if no overrides.if (!bd.hasMethodOverrides()) {Constructor<?> constructorToUse;synchronized (bd.constructorArgumentLock) {constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;if (constructorToUse == null) {Class<?> clazz = bd.getBeanClass();if (clazz.isInterface()) {throw new BeanInstantiationException(clazz, "Specified class is an interface");}try {constructorToUse = clazz.getDeclaredConstructor();bd.resolvedConstructorOrFactoryMethod = constructorToUse;}catch (Throwable ex) {throw new BeanInstantiationException(clazz, "No default constructor found", ex);}}}return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.return instantiateWithMethodInjection(bd, beanName, owner);}}

BeanUtils.instantiateClass(constructorToUse):使用 BeanUtils 类的 instantiateClass 方法,传入已经找到的构造函数 constructorToUse,通过反射机制创建并返回一个新的对象实例。

获得实例化的Bean之后,会在AbstractAutowireCapableBeanFactory#doCreateBean方法接着调用AbstractAutowireCapableBeanFactory#populateBean给Bean填充属性***,源码如下***:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {if (bw == null) {if (mbd.hasPropertyValues()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");}else {// Skip property population phase for null instance.return;}}if (bw.getWrappedClass().isRecord()) {if (mbd.hasPropertyValues()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Cannot apply property values to a record");}else {// Skip property population phase for records since they are immutable.return;}}// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the// state of the bean before properties are set. This can be used, for example,// to support styles of field injection.if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {return;}}}PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}if (hasInstantiationAwareBeanPostProcessors()) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}pvs = pvsToUse;}}boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);if (needsDepCheck) {PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);checkDependencies(beanName, mbd, filteredPds, pvs);}if (pvs != null) {applyPropertyValues(beanName, mbd, bw, pvs);}}

populateBean 方法的主要作用是填充Bean的属性。在Spring中,Bean的属性可以通过多种方式设置,比如通过构造器注入(Constructor Injection)、属性注入(Setter Injection)或者字段注入(Field Injection)。populateBean 方法负责根据Bean的定义和配置,将这些属性正确地设置到Bean实例中。具体步骤如下

1、检查 BeanWrapper 是否为空:如果BeanWrapper为空且有属性值,则抛出异常;否则跳过属性填充阶段。
2、检查是否为记录类型:如果BeanWrapper包装的是记录类型且有属性值,则抛出异常;否则跳过属性填充阶段。
3、调用 InstantiationAwareBeanPostProcessors :在设置属性之前,允许InstantiationAwareBeanPostProcessors 修改bean的状态。
4、自动装配属性:根据配置的自动装配模式(按名称或按类型),添加相应的属性值。
5、再次调用 InstantiationAwareBeanPostProcessors :在设置属性之后,允许InstantiationAwareBeanPostProcessors修改属性值。
6、依赖检查:如果需要进行依赖检查,则执行依赖检查。
7、应用属性值:将最终的属性值应用到BeanWrapper中。

下面举个例子来说明populateBean 方法的作用。

在类Demo中注入了Depd,执行完BeanUtils.instantiateClass(constructorToUse)后Demo的depd属性为null。

初始化Demo后

执行populateBean填充属性后,depd属性就获取了注入的值。

执行populateBean填充属性depd

Demo和Depd源码:

@Component
public class Demo {@Autowiredprivate Depd depd;private String name = "demo";}@Component
public class Depd {private String name="Dependency";
}

虽然 populateBean 方法负责填充Bean的属性,但是Bean的完全初始化(比如执行自定义的初始化方法)通常是在这个方法之后进行的。

我们回到AbstractAutowireCapableBeanFactory#doCreateBean方法,根据源码,在调用完populateBean方法后,接着调用了AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)方法。

initializeBean被调用

该方法的源码如下:

 /*** Initialize the given bean instance, applying factory callbacks* as well as init methods and bean post processors.* <p>Called from {@link #createBean} for traditionally defined beans,* and from {@link #initializeBean} for existing bean instances.* @param beanName the bean name in the factory (for debugging purposes)* @param bean the new bean instance we may need to initialize* @param mbd the bean definition that the bean was created with* (can also be {@code null}, if given an existing bean instance)* @return the initialized bean instance (potentially wrapped)* @see BeanNameAware* @see BeanClassLoaderAware* @see BeanFactoryAware* @see #applyBeanPostProcessorsBeforeInitialization* @see #invokeInitMethods* @see #applyBeanPostProcessorsAfterInitialization*/@SuppressWarnings("deprecation")protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {invokeAwareMethods(beanName, bean);Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);}if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;}

initializeBean 方法的主要作用是完成Bean的初始化工作。这包括执行任何由Bean定义指定的初始化回调(比如 init-method),以及调用任何由用户定义的 BeanPostProcessor 接口实现所指定的初始化方法。步骤如下

1、调用Aware方法:通过 invokeAwareMethods 方法调用bean的Aware方法,例如 BeanNameAware、BeanClassLoaderAware 和 BeanFactoryAware。
2、应用 BeanPostProcessor 的前置初始化回调:在正式初始化 Bean 之前,Spring 会调用所有注册的 BeanPostProcessor 的 3】3、postProcessBeforeInitialization 方法。这允许在Bean初始化之前对其进行一些自定义的处理。
3、执行 Bean 的初始化回调:如果Bean定义中指定了 init-method,Spring会调用这个方法来完成Bean的初始化工作。此外,如果Bean实现了 InitializingBean 接口,Spring也会调用其 afterPropertiesSet 方法。
4、应用 BeanPostProcessor 的后置初始化回调:在Bean初始化完成后,Spring会调用所有注册的 BeanPostProcessor 的 postProcessAfterInitialization 方法。这允许在Bean初始化之后对其进行一些额外的处理。
5、Bean 初始化完成:经过上述步骤后,Bean的初始化工作就完成了。此时,Bean已经准备好被应用程序使用。

最后,我们将上面Bean实例化过程整理为一张流程图,如下:

Bean实例化流程图

八、面试题

介绍一下IOC思想
IOC(Inversion of Control,控制反转)是Spring以及Spring Boot框架的核心理念之一。它极大地改变了传统的开发方式,帮助开发者更高效、更灵活地构建模块化、可测试的应用。IOC是一种设计原则,旨在将程序中对象创建和依赖管理的控制权从手动管理转移到框架或容器(如Spring容器)中。简单来说,程序中的对象不再由开发者手动创建或管理它们之间的依赖,而是交给Spring容器来负责。通过这种方式,IOC实现了模块间的解耦,提高了代码的灵活性和可维护性。

在Spring Boot中,IOC主要通过依赖注入(Dependency Injection,DI)的方式实现。依赖注入是指在运行时由容器动态地将依赖对象注入到组件中。Spring Boot提供了三种常见的依赖注入方式:构造函数注入、setter方法注入和字段注入。其中,构造函数注入是最推荐的方式,因为它可以保证依赖注入的不可变性,并且能让Spring Boot检查依赖是否完整。

Spring Boot有哪些配置Bean的方式?有何区别?
Spring Boot配置Bean的方式主要有两种:使用注解@Bean注入到Spring IOC容器中,以及使用@Component扫描装配Bean到IOC容器中。

  • 使用@Bean注解的方式:这种方式需要在配置类中定义一个方法,并使用@Bean注解来标记该方法。该方法的返回值将是一个Bean实例,Spring容器会将其装配到IOC容器中。这种方式通常用于需要手动创建Bean实例的场景。
  • 使用@Component注解的方式:这种方式是通过在类上使用@Component(或其子注解如@Service、@Repository等)来标记该类为Spring容器的一个Bean。然后,Spring容器会在启动时自动扫描这些注解,并将对应的类实例化后装配到IOC容器中。这种方式通常用于自动装配的场景,无需手动创建Bean实例。

两者的主要区别在于:使用@Bean注解的方式需要手动编写配置方法,更加灵活但相对繁琐;而使用@Component注解的方式则更加简洁,适用于自动装配的场景。

介绍以下refresh方法的流程
refresh方法是Spring容器初始化过程中的核心方法之一。它负责完成容器的初始化工作,包括Bean的实例化、初始化等。以下是refresh方法的主要流程:

  • 准备刷新:进行一些刷新前的准备工作,如设置容器的活动状态、初始化一些属性等。
  • 获取Bean工厂:从容器中获取BeanFactory实例,这是Spring容器的基础设施之一,负责Bean的创建和管理。
  • 准备Bean工厂:对BeanFactory进行一些配置和初始化工作,如设置Bean的类加载器、属性编辑器等。
  • 后处理BeanFactory:允许用户通过实现BeanFactoryPostProcessor接口来自定义BeanFactory的初始化过程。
  • 注册Bean后处理器:注册一些Bean后处理器(如InstantiationAwareBeanPostProcessor等),这些后处理器将在Bean的创建和初始化过程中发挥作用。
  • 初始化消息源:初始化应用程序的国际化资源。
  • 初始化事件多播器:初始化Spring的事件多播器,用于发布和监听事件。
  • 创建Bean:根据配置信息创建Bean实例,并将它们装配到IOC容器中。
  • 完成刷新:进行一些刷新后的工作,如发布刷新完成事件等。

介绍一下Bean的实例化过程
Bean的实例化过程是Spring容器创建Bean实例的过程。以下是Bean实例化过程的主要步骤:

  • 获取Bean定义:从Bean定义仓库中获取目标Bean的定义信息。这些信息包括Bean的类名、作用域、依赖等。
  • 检查Bean定义:对Bean定义进行检查,确保Bean可以被正确创建和初始化。
  • 实例化Bean:根据Bean的定义信息创建Bean实例。Spring容器提供了多种实例化策略,如直接调用构造方法、使用工厂方法等。
  • 设置Bean属性:对Bean实例的属性进行赋值。这包括通过依赖注入将其他Bean注入到当前Bean中,以及通过配置属性为Bean的字段赋值等。
  • 初始化Bean:调用Bean的初始化方法(如@PostConstruct注解的方法),完成Bean的初始化工作。

Bean实例化的扩展点及其作用
在Spring容器中,Bean实例化的扩展点主要包括以下几个:

  • ApplicationContextInitializer :在容器刷新之前调用,允许用户在整个Spring容器还没被初始化之前做一些事情,如激活配置、动态字节码注入等。
  • BeanDefinitionRegistryPostProcessor :在读取项目中的BeanDefinition之后执行,提供一个补充的扩展点,允许用户动态注册自己的BeanDefinition。
  • BeanFactoryPostProcessor :在读取BeanDefinition信息之后、实例化Bean之前调用,允许用户通过实现这个扩展接口来自行处理一些东西,如修改已经注册的BeanDefinition的元信息。
  • InstantiationAwareBeanPostProcessor :在Bean的实例化阶段和属性注入阶段提供扩展点,允许用户通过实现这个接口来自定义Bean的实例化过程和属性注入过程。

这些扩展点的作用在于提供了灵活的机制,允许用户在Spring容器的不同阶段进行自定义操作,从而满足各种复杂的应用场景需求。

相关文章:

Spring Boot 3 中Bean的配置和实例化详解

一、引言 在Java企业级开发领域&#xff0c;Spring Boot凭借其简洁、快速、高效的特点&#xff0c;迅速成为了众多开发者的首选框架。Spring Boot通过自动配置、起步依赖等特性&#xff0c;极大地简化了Spring应用的搭建和开发过程。而在Spring Boot的众多核心特性中&#xff…...

一文理解 “Bootstrap“ 在统计学背景下的含义

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一文理解 “Bootstrap“ 在统计学背景下的含义 类比&#xff1a;重新抽样 假设我参加了班级的考试&#xff0c;每位同学都获得了一个成绩。现在&#xff0c;我想了解整个班级的平均成绩&#xff0c;但…...

多媒体文件解复用(Demuxing)过程

多媒体文件的解复用&#xff08;Demuxing&#xff09;过程指的是从一个多媒体容器文件&#xff08;如 MP4、MKV、AVI 等&#xff09;中提取不同类型的多媒体数据流&#xff08;例如视频流、音频流、字幕流等&#xff09;的过程。 容器文件本身并不包含实际的视频或音频数据&…...

ARINC 标准全解析:航空电子领域多系列标准的核心内容、应用与重要意义

ARINC标准概述 ARINC标准是航空电子领域一系列重要的标准规范&#xff0c;由航空电子工程委员会&#xff08;AEEC&#xff09;编制&#xff0c;众多航空公司等参与支持。这些标准涵盖了从飞机设备安装、数据传输到航空电子设备功能等众多方面&#xff0c;确保航空电子系统的兼…...

开源架构安全深度解析:挑战、措施与未来

开源架构安全深度解析&#xff1a;挑战、措施与未来 一、引言二、开源架构面临的安全挑战&#xff08;一&#xff09;代码漏洞 —— 隐藏的定时炸弹&#xff08;二&#xff09;依赖项安全 —— 牵一发而动全身&#xff08;三&#xff09;社区安全 —— 开放中的潜在危机 三、开…...

Python装饰器设计模式:为函数增添风味

Python装饰器设计模式&#xff1a;为函数增添风味 什么是装饰器&#xff1f;为什么需要装饰器&#xff1f;如何使用装饰器&#xff1f;示例1&#xff1a;简单的装饰器示例2&#xff1a;带参数的装饰器 装饰器的使用场景总结 大家好&#xff0c;今天我们要学习一个非常有趣的Pyt…...

Vue.js的生命周期

Vue.js 是一个构建用户界面的渐进式框架&#xff0c;它提供了一个响应式和组件化的方式来构建前端应用。了解 Vue 的生命周期对于开发者来说至关重要&#xff0c;因为它可以帮助我们更好地控制组件的状态和行为。本文将详细介绍 Vue 的生命周期&#xff0c;并提供相应的代码示例…...

【数据库】关系代数和SQL语句

一 对于教学数据库的三个基本表 学生S(S#,SNAME,AGE,SEX) 学习SC(S#,C#,GRADE) 课程(C#,CNAME,TEACHER) &#xff08;1&#xff09;试用关系代数表达式和SQL语句表示&#xff1a;检索WANG同学不学的课程号 select C# from C where C# not in(select C# from SCwhere S# in…...

Pytest测试用例使用小结

基础使用 Pytest 测试用例实现代码 import pytest from server.service import Servicepytest.fixture def service():return Service(logger)class TestService:classmethoddef setup_class(cls):"""初始化设置一次:return:"""logger.info(&q…...

KV Shifting Attention Enhances Language Modeling

基本信息 &#x1f4dd; 原文链接: https://arxiv.org/abs/2411.19574&#x1f465; 作者: Mingyu Xu, Wei Cheng, Bingning Wang, Weipeng Chen&#x1f3f7;️ 关键词: KV shifting attention, induction heads, language modeling&#x1f4da; 分类: 机器学习, 自然语言处…...

从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级

从 Zuul 迁移到 Spring Cloud Gateway&#xff1a;一步步实现服务网关的升级 迁移前的准备工作迁移步骤详解第一步&#xff1a;查看源码第二步&#xff1a;启动类迁移第三步&#xff1a;引入 Gateway 依赖第四步 编写bootstrap.yaml第五步&#xff1a;替换路由配置第六步&#…...

推荐几款国外AI音频工具

【加拿大】Resemble AI - 提供AI驱动的语音合成 【加拿大】Resemble AI - 提供AI驱动的语音合成和克隆工具 Resemble,AI提供AI驱动的语音合成和克隆工具,帮助用户高效生成和处理语音内容,其语音合成功能可以自动生成自然流畅的语音,提升音频项目的表现力,Resemble,AI的语音克…...

导入excel动态生成海报

需求&#xff1a;给出一份excel表格&#xff08;1000条数据&#xff09;,要将表格中的字段数据渲染到一张背景图片上&#xff0c;然后再下载图片&#xff0c;貌似浏览器做了限制&#xff0c;当连续下载10张图片后就不在下载了&#xff0c;然后用异步操作解决了这个问题。 // e…...

Unity 使用LineRenderer制作模拟2d绳子

效果展示&#xff1a; 实现如下&#xff1a; 首先&#xff0c;直接上代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public class LineFourRender : MonoBehaviour {public Transform StartNode;public Transform MidNod…...

Android启动优化指南

文章目录 前言一、启动分类与优化目标1、冷启动1.1 优化思路1.2 延迟初始化与按需加载1.3 并行加载与异步执行1.4 资源优化与懒加载1.5 内存优化与垃圾回收控制 2. 温启动2.1 优化应用的生命周期管理2.2 数据缓存与懒加载2.3 延迟渲染与视图优化 3. 热启动3.1 保持应用的状态3.…...

每日一练 | 华为 eSight 创建的缺省角色

01 真题题目 下列选项中&#xff0c;不属于华为 eSight 创建的缺省角色的是&#xff1a; A. Administrator B. Monitor C. Operator D. End-User 02 真题答案 D 03 答案解析 华为 eSight 是一款综合性的网络管理平台&#xff0c;提供了多种管理和监控功能。 为了确保不同用…...

ubuntu 手动更换库文件解决nvcc -V和nvidia-smi不一致

NVML 库版本与驱动不匹配 问题现象问题排查限制解决禁止自动更新降低库版本 问题现象 笔主在训练之前想查看gpu占用情况&#xff0c;使用watch -n 1 nvidia-smi发现&#xff1a; 且在推理、训练时无法使用到显卡。 问题排查 cat /proc/driver/nvidia/version查看当前显卡驱…...

DataSophon集成CMAK KafkaManager

本次集成基于DDP1.2.1 集成CMAK-3.0.0.6 设计的json和tar包我放网盘了. 通过网盘分享的文件&#xff1a;DDP集成CMAK 链接: https://pan.baidu.com/s/1BR70Ajj9FxvjBlsOX4Ivhw?pwdcpmc 提取码: cpmc CMAK github上提供了zip压缩包.将压缩包解压之后 在根目录下加入启动脚本…...

2024-2025关于华为ICT大赛考试平台常见问题

一、考生考试流程 第一步&#xff1a;收到正式考试链接后点击考试链接并登录&#xff1b; 第二步&#xff1a;请仔细阅读诚信考试公约&#xff0c;阅读完成后勾选“我已阅读”&#xff0c;并点击确定&#xff1b; 第三步&#xff1a;上传身份证人像面进行考前校验&#xff0…...

Halcon中lines_gauss(Operator)算子原理及应用详解

在Halcon图像处理库中&#xff0c;lines_gauss算子是一个用于检测图像中线条的强大工具&#xff0c;它能够提供亚像素精度的线条轮廓。以下是对lines_gauss (ImageReducedTracks, Lines, 1.5, 1, 8, ‘light’, ‘true’, ‘bar-shaped’, ‘true’)算子的详细解释&#xff1a;…...

Flink集群搭建整合Yarn运行

Flink 集群 1. 服务器规划 服务器h1、h4、h5 2. StandAlone 模式&#xff08;不推荐&#xff09; 2.1 会话模式 在h1操作 #1、解压 tar -zxvf flink-1.19.1-bin-scala_2.12.tgz -C /app/#2、修改配置文件 cd /app/flink-1.19.1/conf vim conf.yaml ##内容&#xff1a;## j…...

FPGA工作原理、架构及底层资源

FPGA工作原理、架构及底层资源 文章目录 FPGA工作原理、架构及底层资源前言一、FPGA工作原理二、FPGA架构及底层资源 1.FPGA架构2.FPGA底层资源 2.1可编程输入/输出单元简称&#xff08;IOB&#xff09;2.2可配置逻辑块2.3丰富的布线资源2.4数字时钟管理模块(DCM)2.5嵌入式块 …...

Postman的使用

&#xff08;一&#xff09;创建Collections&#xff1a;Collections->New Collection->创建界面填入Collection名称&#xff0c;比如某个系统/模块名&#xff0c;描述里可以稍微更详细的介绍集合的信息 Collection创建时&#xff0c;还可以定义Authorization 如下&#…...

【报错】新建springboot项目时缺少resource

1.问题描述 在新建springboot项目时缺少resources,刚刚新建时的目录刚好就是去掉涂鸦的resources后的目录 2.解决方法 步骤如下&#xff1a;【文件】--【项目结构】--【模块】--【源】--在main文件夹右击选择新建文件夹并命名为resources--在test文件夹右击选择新建文件夹并命名…...

phpstudy访问本地localhost无目录解决办法

phpstudy访问本地localhost无目录解决办法 错误&#xff1a; 直接访问本地http://localhost/&#xff0c;出现hello word&#xff0c;或者直接报错&#xff0c;无法出现本地目录 解决办法&#xff1a; 对于Phpstudy-2018版本来说&#xff1a; 找到这里的Phpstudy设置 2. 打…...

架构16-向微服务迈进

零、文章目录 架构16-向微服务迈进 1、向微服务迈进 &#xff08;1&#xff09;软件开发中的“银弹”概念 **背景&#xff1a;**软件开发过程中常常出现工期延误、预算超支、产品质量低劣等问题&#xff0c;这使得管理者、程序员和用户都渴望找到一种能够显著降低成本的“银…...

基于Springboot汽车资讯网站【附源码】

基于Springboot汽车资讯网站 效果如下&#xff1a; 系统主页面 汽车信息页面 系统登陆页面 汽车信息推荐页面 经销商页面 留言反馈页面 用户管理页面 汽车信息页面 研究背景 随着信息技术的快速发展和互联网的普及&#xff0c;互联网已成为人们查找信息的重要场所。汽车资讯…...

Tomcat项目本地部署

今天分享一下如何在本地&#xff0c;不依赖于idea部署聚合项目&#xff0c;以我做过的哈米音乐项目为例&#xff0c;项目结构如下&#xff1a; ham-core模块为公共模块&#xff0c;我们只需将另外三个模块&#xff1a;前台、后台、文件服务器打包&#xff0c;将打好的jar、war包…...

【OpenCV】直方图

理论 可以将直方图视为图形或曲线图&#xff0c;从而使您对图像的强度分布有一个整体的了解。它是在X轴上具有像素值(不总是从0到255的范围)&#xff0c;在Y轴上具有图像中相应像素数的图。 这只是理解图像的另一种方式。通过查看图像的直方图&#xff0c;您可以直观地了解该…...

pika:适用于大数据量持久化的类redis组件|jedis集成pika(二)

文章目录 0. 引言1. pika客户端支持2. jedis集成pika3. pika性能测试 0. 引言 上节我们讲解了pika的搭建&#xff0c;这节我们来看下如何在java项目中利用jedis集成pika 1. pika客户端支持 pika支持的客户端与redis完全一致&#xff0c;所以理论上redis支持的客户端pika也都…...

Linux 进程间通信

Linux进程间通信 进程间通信&#xff08;IPC&#xff0c;Inter-Process Communication&#xff09;在 Linux 下常用的方法包括&#xff1a; 1&#xff09;管道&#xff08;Pipe&#xff09; 2&#xff09;有名管道&#xff08;FIFO&#xff09; 3&#xff09;消息队列&#x…...

【C++】快速排序详解与优化

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;快速排序的核心思想1. 算法原理2. 算法复杂度分析时间复杂度空间复杂度 &#x1f4af;快速排序的代码实现与解析代码实现代码解析1. 递归终止条件2. 动态分配子数组3. 分区…...

【JAVA高级篇教学】第二篇:使用 Redisson 实现高效限流机制

在高并发系统中&#xff0c;限流是一项非常重要的技术手段&#xff0c;用于保护后端服务&#xff0c;防止因流量过大导致系统崩溃。本文将详细介绍如何使用 Redisson 提供的 RRateLimiter 实现分布式限流&#xff0c;以及其原理、使用场景和完整代码示例。 目录 一、什么是限流…...

NanoLog起步笔记-1

nonolog起步笔记-1 背景与上下文写在前面Nanolog与一般的实时log的异同现代log的一般特性Nanolog的选择 背景与上下文 因为工作中用到了NanoLog。有必要研究一下。 前段时间研究了许多内容&#xff0c;以为写了比较详实的笔记&#xff0c;今天找了找&#xff0c;不仅笔记没找到…...

vs打开unity项目 新建文件后无法自动补全

问题 第一次双击c#文件自动打开vs编辑器的时候能自动补全&#xff0c;再一次在unity中新建c#文件后双击打开发现vs不能自动补全了。每次都要重新打开vs编辑器才能自动补全&#xff0c;导致效率很低&#xff0c;后面发现是没有安装扩展&#xff0c;注意扩展和工具的区别。 解决…...

HDFS的Federation机制的实现原理和Erasure Coding节省存储空间的原理

目录 Federation机制的实现原理1.HDFS的分层图解&#xff08;1&#xff09;NameSpace&#xff08;2&#xff09;Block Storage1&#xff09;Block Management2&#xff09;Storage 2.Federation机制的优点3.Federation机制的缺点4.Federation机制的实现&#xff08;1&#xff0…...

经验笔记:使用 PyTorch 计算多分类问题中Dice Loss 的正确方法

经验笔记&#xff1a;使用 PyTorch 计算多分类问题中Dice Loss 的正确方法 概述 Dice Loss 是一种广泛应用于图像分割任务中的损失函数&#xff0c;它基于 Dice 系数&#xff08;也称为 F1-score&#xff09;&#xff0c;用于衡量预测结果与真实标签之间的相似度。在 PyTorch…...

如何在 Ubuntu 22.04 上安装 PostgreSQL

简介 PostgreSQL&#xff08;或简称Postgres&#xff09;是一个关系型数据库管理系统&#xff0c;它提供了SQL查询语言的实现。它符合标准&#xff0c;并且拥有许多高级特性&#xff0c;比如可靠的事务处理和无需读锁的并发控制。 本指南将展示如何在Ubuntu 22.04服务器上快速…...

正则表达式的高级方法

正则表达式的高级方法 正则表达式&#xff08;regex&#xff09;不仅仅是简单的模式匹配工具&#xff0c;它还提供了一系列高级功能&#xff0c;使得处理复杂文本任务变得更加灵活和强大。以下是一些Python中正则表达式的高级用法&#xff1a; 1. 命名捕获组 命名捕获组允许…...

axios的get和post请求,关于携带参数相关的讲解一下

在使用 Axios 发送 HTTP 请求时&#xff0c;GET 和 POST 请求携带参数的方式有所不同。以下是关于这两种请求方法携带参数的详细讲解&#xff1a; GET 请求携带参数 对于 GET 请求&#xff0c;参数通常附加在 URL 之后&#xff0c;以查询字符串的形式传递。 直接在 URL 中拼接…...

中间件--MongoDB部署及初始化js脚本(docker部署,docker-entrypoint-initdb.d,数据迁移,自动化部署)

一、概述 MongoDB是一种常见的Nosql数据库&#xff08;非关系型数据库&#xff09;&#xff0c;以文档&#xff08;Document&#xff09;的形式存储数据。是非关系型数据库中最像关系型数据库的一种。本篇主要介绍下部署和数据迁移。 在 MongoDB 官方镜像部署介绍中&#xff…...

基于SpringBoot框架的民宿连锁店业务系统(计算机毕业设计)+万字说明文档

系统合集跳转 源码获取链接 一、系统环境 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以 tomcat环境&#xff1a; Tomcat 7.x,8.x,9.x版本均可 操作系统…...

PHP8 动态属性被弃用兼容方案

PHP 类中可以动态设置和获取没有声明过的类属性。这些属性不遵循具体的规则&#xff0c;并且需要使用 __get() 和 __set() 魔术方法对动态属性如何读写进行有效控制。 class User {private int $uid; }$user new User(); $user->name Foo; 上述代码中&#xff0c;User 类…...

Spring Boot 3.0 + MySQL 8.0 + kkFileView 实现完整文件服务

Spring Boot 3.0 MySQL 8.0 kkFileView 实现完整文件服务 背景&#xff1a;比较常见的需求&#xff0c;做成公共的服务&#xff0c;后期维护比较简单&#xff0c;可扩展多个存储介质&#xff0c;上传逻辑简单&#xff0c;上传后提供一个文件id&#xff0c;后期可直接通过此i…...

【YashanDB知识库】php查询超过256长度字符串,数据被截断的问题

本文内容来自YashanDB官网&#xff0c;原文内容请见&#xff1a;https://www.yashandb.com/newsinfo/7488290.html?templateId1718516 问题现象 如下图&#xff0c;php使用odbc数据源&#xff0c;查询表数据&#xff0c;mysql可以显示出来&#xff0c;yashan显示数据被截断。…...

为什么ETH 3.0需要Lumoz的ZK算力网络?

1.Lumoz 模块化计算层 Lumoz 协议是一个全球分布式模块化计算协议&#xff0c;致力于提供先进的零知识证明&#xff08;ZKP&#xff09;服务&#xff0c;支持ZK技术的发展&#xff0c;为ZK、AI等前沿技术提供强大的算力支撑。面对当前零知识计算领域计算成本的挑战&#xff0c…...

反向代理-缓存篇

文章目录 强缓存一、Expires(http1.0 规范)二、cache-control(http1.1 出现的 header 信息)Cache-Control 的常用选项Cache-Control 常用选项的选择三、弊端协商缓存一、ETag二、If-None-Match三、Last-modified四、If-Modified-Since浏览器的三种刷新方式静态资源部署策略…...

(重点来啦!)MySql基础增删查改操作(详细)

目录 一、客户端和数据库操作&#xff1a; 二、表操作 1.查看当前数据库中有哪些表 2.创建一张新表 3.查看表结构&#xff1a; 4.删除表 三、CRUD增删查改 1.新增——插入 2.查询操作 a.全列查询&#xff1a; b.指定列查询&#xff1a; c.列名为表达式的查询&#…...

WPF编写工业相机镜头选型程序

该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…...

阿里云轻量应用服务器开放端口,图文教程分享

阿里云轻量应用服务器如何开放端口&#xff1f;在轻量服务器管理控制台的防火墙中添加规则即可开通端口&#xff0c;开通80端口就填80&#xff0c;开通443就填443端口&#xff0c;开通3306端口就填3306。阿里云百科网aliyunbaike.com整理阿里云轻量应用服务器端口号开通图文教程…...