SpringBoot启动流程原理+自动装配原理

通过SpringBoot启动流程分析SpringBoot自动装配原理 本文深入分析了Spring Boot自动配置的实现原理,从启动类的run方法开始,逐步跟踪到核心的refreshContext方法。重点剖析了BeanFactoryPostProcessor的处理流程,特别是ConfigurationClassPostProcessor如何解析@Configuration类。详细追踪了@Import注解的处理机制,揭示了AutoConfigurationImportSelector如何通过SpringFactoriesLoader加载META-INF/spring.fact 阅读详情


前言

SpringBoot 设计的目的是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。SpringBoot相对Spring的优点主要有两个:
1.起步依赖-会将很多jar包按照功能合并成stater整体进行版本管理和引用,解决Spring集成其他框架时jar版本管理问题
2.自动装配-引入相关的jar包后SpringBoot会自动注册一些比较关键的bean,并进行默认配置,不用我们进行特殊配置,解决Spring重量级XML配置问题。比如整合Mybatis时的SqlSessionFactory

注:其中起步依赖主要是解决版本控制问题,主要设计在于POM文件,这里主要探究第二优点自动装配。

SpringBoot启动依靠的是带有main方法的启动类,启动类的内容可以分为两个部分一个是启动类上@SpringBootApplication这个注解;第二部分是main方法里的SpringApplication.run(启动类.class,args)方法。下面主要就是分析一下这两部分分别是什么作用?完成了什么功能?怎样实现的自动装配?以及SpringBoot的启动流程分析。


1、@SpringBootApplication注解剖析

@SpringBootApplication是个组合注解包含四个元注解和@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan组成,下面逐个分析。
在这里插入图片描述
在这里插入图片描述

1.1、@SpringBootConfiguration

@SpringBootConfiguration也是一个组合注解由元注解和@Configuration构成,@Configuration是@Component的一个衍生注解主要作用是标记当前类是个配置类。
在这里插入图片描述

1.2、@EnableAutoConfiguration

@EnableAutoConfiguration也是一个组合注解由元注解和@AutoConfigurationPackage、@Import注解构成,Spring中有很多Enable开头的注解,其作用大都是借助@Import来收集并注册特定场景相关的bean。@EnableAutoConfiguration的主要作用就是借助@Import来收集并注册所有符合自动装配条件的bean。
在这里插入图片描述

1.2.1@AutoConfigurationPackage

注:很多人以为@SpringBootApplication可以扫描启动类当前包及其子包下面的类是由此注解完成的,是错误的

@AutoConfigurationPackage由元注解和@Import注解组成
在这里插入图片描述
@Import注解导入了AutoConfigurationPackages.Registrar.class实现了ImportBeanDefinitionRegistrar接口会调用registerBeanDefinitions方法

在这里插入图片描述

进入AutoConfigurationPackages#register,这里主要为Spring容器里注入了BasePackages的BeanDefinition目的是讲启动类的包路径传入容器,官网解释在后面整合jpa时会用到,这里暂不做探究。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	    // 如果已经存在该 BEAN ,则修改其包(package)属性
		// BEAN 就是 AutoConfigurationPackages,用于存储自动配置包以供稍后引用
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			// 将构造函数的第一个参数设置为包名列表
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        // 如果不存在该 BEAN ,则创建一个 Bean ,并进行注册
        } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			// 将beanClass设置为BasePackages
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			// 将构造函数的第一个参数设置为包名列表,也就是BasePackages的构造函数
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			// 注册beanDefinition
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}
1.2.2 @Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector类是SpringBoot实现自动装配的关键。AutoConfigurationImportSelector实现了DeferredImportSelector接口会调用process和selectImports方法(在何处调用会在后面2.2.3讲到),其中selectImports方法会返回一个数组,数组中的类都会注册到Spring容器中

AutoConfigurationImportSelector.AutoConfigurationGroup.class
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			// 断言
		    Assert.state(
					deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
		    // 获得 AutoConfigurationEntry 对象
		    // 核心方法:获取并过滤全部自动装配的类
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			// 添加到 autoConfigurationEntries 中
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			// 添加到 entries 中
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}
AutoConfigurationImportSelector.AutoConfigurationGroup.class
public Iterable<Entry> selectImports() {
		    // 如果为空,则返回空数组
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			// 获得 allExclusions
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions)
					.flatMap(Collection::stream).collect(Collectors.toSet());
			// 获得 processedConfigurations
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations)
					.flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			// 从 processedConfigurations 中,移除排除的
			processedConfigurations.removeAll(allExclusions);
			// 处理,返回结果
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()) // 排序
                        .stream()
                        .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) // 创建 Entry 对象
                        .collect(Collectors.toList()); // 转换成 List
		}

由源码可以看到selectImports只是对process中封装到autoConfigurationEntries的结果进行分组排序等处理后返回,下面主要看到process中的getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);方法

AutoConfigurationImportSelector.class
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	    // 1. 判断是否开启注解。如未开启,返回空串
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 2. 获得注解的属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

		// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
		// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
		// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
		// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);


		// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
		configurations = removeDuplicates(configurations);
		// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
		// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
		//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
		checkExcludedClasses(configurations, exclusions);
		// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
		configurations.removeAll(exclusions);

		// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

		//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
		//@ConditionalOnMissingClass : classpath中不存在该类时起效
		//@ConditionalOnBean : DI容器中存在该类型Bean时起效
		//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
		//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
		//@ConditionalOnExpression : SpEL表达式结果为true时
		//@ConditionalOnProperty : 参数设置或者值一致时起效
		//@ConditionalOnResource : 指定的文件存在时起效
		//@ConditionalOnJndi : 指定的JNDI存在时起效
		//@ConditionalOnJava : 指定的Java版本存在时起效
		//@ConditionalOnWebApplication : Web应用环境下起效
		//@ConditionalOnNotWebApplication : 非Web应用环境下起效
		
		//要判断@Conditional是否满足
		// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
		configurations = filter(configurations, autoConfigurationMetadata);


		// 6. 将自动配置导入事件通知监听器
		//当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
		// 并触发fireAutoConfigurationImportEvents事件。
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// 7. 创建 AutoConfigurationEntry 对象
		return new AutoConfigurationEntry(configurations, exclusions);
	}

1.3@ComponentScan

这个注解才是@SpringBootApplication会默认扫描启动类所在包以及子包路径下全部类的原因

2.SpringApplication.run(启动类.class,args)方法剖析

SpringApplication#run主要完成的事件可以分成两部分1.实例化SpringApplication对象2. run(args):调用run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	   //SpringApplication的启动由两部分组成:
		//1. 实例化SpringApplication对象
		//2. run(args):调用run方法
		return new SpringApplication(primarySources).run(args);
	}

在这里插入图片描述

2.1实例化SpringApplication对象

在实例化SpringApplication中设置的初始化器和监听器都是在/META-INF/spring.factories 中获取的

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

		this.sources = new LinkedHashSet();
		this.bannerMode = Mode.CONSOLE;
		this.logStartupInfo = true;
		this.addCommandLineProperties = true;
		this.addConversionService = true;
		this.headless = true;
		this.registerShutdownHook = true;
		this.additionalProfiles = new HashSet();
		this.isCustomEnvironment = false;
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");

		//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

		//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();

		// 设置初始化器(Initializer),最后会调用这些初始化器
		//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

		// 设置监听器(Listener)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

		// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

2.2 run(args):调用run方法

这里一个分为九步,最核心的是3、4、5下面会逐一介绍:

  1. 获取并启动监听器,监听器也是在spring.factories中获取的。
  2. 项目运行环境Environment的预配置
  3. 创建Spring容器
  4. Spring容器前置处理,这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
  5. 刷新容器
  6. Spring容器后置处理,扩展接口,设计模式中的模板方法,默认为空实现。
  7. 向监听器发出结束执行的事件通知
  8. 执行Runners
  9. 向监听器发布应用上下文就绪事件
public ConfigurableApplicationContext run(String... args) {
	    // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		// 初始化应用上下文和异常报告集合
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 配置 headless 属性
		configureHeadlessProperty();


		//   (1)获取并启动监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
		    // 创建  ApplicationArguments 对象 初始化默认应用参数类
			// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

			//(2)项目运行环境Environment的预配置
			// 创建并配置当前SpringBoot应用将要使用的Environment
			// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

			configureIgnoreBeanInfo(environment);
			// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
			Banner printedBanner = printBanner(environment);

			// (3)创建Spring容器
			context = createApplicationContext();
			// 获得异常报告器 SpringBootExceptionReporter 数组
			//这一步的逻辑和实例化初始化器和监听器的一样,
			// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);


			// (4)Spring容器前置处理
			//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);

			// (5):刷新容器
			refreshContext(context);

			// (6):Spring容器后置处理
			//扩展接口,设计模式中的模板方法,默认为空实现。
			// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
			afterRefresh(context, applicationArguments);
			// 停止 StopWatch 统计时长
			stopWatch.stop();
			// 打印 Spring Boot 启动的时长日志。
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			// (7)发出结束执行的事件通知
			listeners.started(context);

			// (8):执行Runners
			//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
			//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
			//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
			callRunners(context, applicationArguments);
		} catch (Throwable ex) {
		    // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

        //   (9)发布应用上下文就绪事件
		//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
		// 这样整个Spring Boot项目就正式启动完成了。
		try {
			listeners.running(context);
		} catch (Throwable ex) {
            // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
            handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		 //返回容器
		return context;
	}
2.2.1run(args)方法——第三步之创建Spring应用上下文

这里根据实例SpringApplication时获取的应用类型来创建不同的应用上下文对象

SpringApplication.class
protected ConfigurableApplicationContext createApplicationContext() {
	    // 根据 webApplicationType 类型,获得 ApplicationContext 类型
		// 这里创建容器的类型 还是根据webApplicationType进行判断的,
		// 该类型为SERVLET类型,所以会通过反射装载对应的字节码,
		// 也就是AnnotationConfigServletWebServerApplicationContext

		// 先判断有没有指定的实现类
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {

				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			} catch (ClassNotFoundException ex) {
				throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex);
			}
		}
		// 创建 ApplicationContext 对象
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
2.2.2run(args)方法——第四步之Spring应用上下文前置处理

这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等

SpringApplication.class
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//设置容器环境,包括各种变量
	    context.setEnvironment(environment);

		//设置上下文的 bean 生成器和资源加载器
		postProcessApplicationContext(context);

		//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
		applyInitializers(context);

		//触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
		listeners.contextPrepared(context);

		//记录启动日志
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		//注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// Load the sources
		// 加载所有资源
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		//加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础
		load(context, sources.toArray(new Object[0]));

		//触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
        listeners.contextLoaded(context);

		
	}

在前置处理中最核心的一步是加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础load(context, sources.toArray(new Object[0]));

BeanDefinitionLoader.class
private int load(Object source) {
		Assert.notNull(source, "Source must not be null");
		// 如果是 Class 类型,则使用 AnnotatedBeanDefinitionReader 执行加载
		if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
        // 如果是 Resource 类型,则使用 XmlBeanDefinitionReader 执行加载
        if (source instanceof Resource) {
			return load((Resource) source);
		}
        // 如果是 Package 类型,则使用 ClassPathBeanDefinitionScanner 执行加载
        if (source instanceof Package) {
			return load((Package) source);
		}
        // 如果是 CharSequence 类型,则各种尝试去加载
		if (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		// 无法处理的类型,抛出 IllegalArgumentException 异常
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}
2.2.3run(args)方法——第五步之刷新容器

这里刷新容器最终调用的是AbstractApplication#refresh方法

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			// 第一步 刷新前的预处理
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 第二步 1.创建BeanFactory实例,默认实现是DefaultListableBeanFactory
			//       2.解析XML中的<bean>为BeanDefition 并注册到 BeanDefitionRegistry
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 第三步 BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				// 第四步 BeanFactory准备工作完成后的后置处理工作,钩子方法,等子类重写
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 第五步 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
				// 提前初始化工厂后置处理器bean,并调用postProcessBeanFactory方法
				//其中BeanFactoryPostProcessor比较重要的一个ConfigurationClassPostProcessor在这里调用,
				//用来遍历BeanDefinitionRegistry中现有的BeanDefinition解析@Import、@Configuration
				// 、@ComponentScan等注解将注解覆盖到的类也注册到BeanDefinitionRegistry中
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 第六步 注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 第七步 初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
				initMessageSource();

				// Initialize event multicaster for this context.
				// 第八步 初始化事件派发器
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 第九步 ⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑,钩子方法
				onRefresh();

				// Check for listener beans and register them.
				// 第十步 注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 第十一步 初始化所有剩下的⾮懒加载的单例bean
				//1).初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
				//2).填充属性
				//3) .如果bean实现了Aware相关接口,则调用Aware接口的实现方法
				//4) .调用BeanPostProcessor处理器的前置方法
				//5).初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
				//6).调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 第十二步 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 (ContextRefreshedEvent)
				finishRefresh();
			}

			catch (BeansException 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 {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

这也是Spring容器启动的经典方法这里就不每个步骤逐一过了,只重点关注和SpringBoot自动装配相关的步骤——第五步 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean,就是在这一步解析的@SpringBootApplication这个组合注解,BeanFactoryPostProcessor比较重要的一个ConfigurationClassPostProcessor在这里调用,用来遍历BeanDefinitionRegistry中现有的BeanDefinition解析@Import、@Configuration 、@ComponentScan等注解将注解覆盖到的类也注册到BeanDefinitionRegistry中。

a.进入ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

ConfigurationClassPostProcessor.class
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);
		// 核心方法
		processConfigBeanDefinitions(registry);
	}

b.进入ConfigurationClassPostProcessor#processConfigBeanDefinitions,这里遍历BeanDefinitionRegistry现有的全部类不包含@Configuration的类不会进行解析,这也是为什么配置类需要加@Configuration的原因

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		//如果不存在@Configuration直接return
		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 核心解析方法
			parser.parse(candidates);
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}

c.进入ConfigurationClassParser#parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					// 注解解析BeanDefinition核心方法
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}

		this.deferredImportSelectorHandler.process();
	}

d.进入ConfigurationClassParser#processConfigurationClass

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			// 解析核心方法
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}

e.进入ConfigurationClassParser#doProcessConfigurationClass,在这里解析@PropertySource、@ComponentScan、@Import、@Bean、@ImportResource等注解,并将其覆盖的资源或类加载到容器上下文中,每个注解的具体解析细节这里就不深探讨了,主要梳理流程

ConfigurationClassParser.class
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass);
		}
		//解析@PropertySource注解
		// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}
		// 解析@ComponentScan注解
		// Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}
		// 解析@Import注解
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true);
		// 解析@ImportResource注解
		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}
		// 解析@Bean注解
		// Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

f.此处简单过一下@Import注解的解析过程,验证一下1.2.2 @Import(AutoConfigurationImportSelector.class)中process和selectImports方法的调用进入ConfigurationClassParser#processImports

在这里插入图片描述

在进入ConfigurationClassParser#handle

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
			DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
					configClass, importSelector);
			if (this.deferredImportSelectors == null) {
				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
				handler.register(holder);
				// 核心方法
				handler.processGroupImports();
			}
			else {
				this.deferredImportSelectors.add(holder);
			}
			
		}

进入ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports

public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				//getImports()中调用了AutoConfigurationImportSelector.AutoConfigurationGroup.class中的process和selectImports
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

进入ConfigurationClassParser.DeferredImportSelectorGrouping#getImports方法此处调用了AutoConfigurationImportSelector.AutoConfigurationGroup的process和selectImports

在这里插入图片描述

SpringBoot启动流程与自动装配原理深度解析:从main方法到条件注解 在Java企业级开发中,SpringBoot以其"约定大于配置"的理念和"开箱即用"的特性,极大地简化了Spring应用的初始搭建和开发过程。其核心机制在于通过自动化配置减少繁琐的XML配置,这一过程主要依赖于两个关键技术:启动流程和自动装配。启动流程从main方法开始,通过SpringApplication类初始化应用上下文,并执行一系列标准化生命周期事件,如环境准备、上下文刷新等。自动装配则基于classpath下的依赖,利用条件注解智能判断并注册所需的Bean到IoC容器中,实现了"按需配置"。这种机 阅读详情

相关推荐

SpringBoot自动装配与启动流程原理深度解析:从源码到实战踩坑

在Java后端开发中,SpringBoot凭借“约定大于配置”的理念大幅降低了项目搭建成本,其核心机制包括自动装配、启动流程、内嵌容器与配置加载。自动装配通过@EnableAutoConfiguration与spring.factories实现按需注入Bean,而启动流程则围绕SpringApplication.run()完成容器初始化与内嵌Web服务器启动。理解这些原理不仅有助于解决日常开发中的Bean覆盖、配置不生效等疑难问题,更是自定义Starter、优化多环境配置、进行源码级排障的基础。本文结合实际

weixin_30431445的博客 225

Spring-boot自动装配原理

自动装配是指 Spring Boot 通过分析项目的依赖和配置,自动配置 Spring 应用程序所需的组件,而无需手动配置大量的 XML 配置文件或 Java 代码。Spring Boot 根据项目的依赖和约定来推断出应用程序的配置需求,并自动配置相应的组件或者类。到这里,自动装配到底是什么,应该比较清楚了,原来是 SpringBoot 帮我们加载了各种已经写好的Config类文件,实现了这些JavaConfig配置文件的重复利用和组件化。

weixin_56003915的博客 4458

springboot启动流程 + 自动装配原理

简要说明 简单一点就是 : 扫描到spring.factories中的EnableAutoConfiguration,进行注入。比如RedisAutoConfiguration.class中的@bean注解,应该就明白,这里和spring中创建通过注解创建bean一样了。后面详细说明。 @Bean怎么注入ioc不明白可以去看一下spring ioc。 这里随便取一个spring.factories 那么现在问题就是spring是如何找到该类或者该注解,并且注入进容器的。 再简单说下。 就是在Spr

小老虎 766

Spring Boot 自动装配原理

Spring Boot 自动装配原理 每次问到 Spring Boot, 面试官非常喜欢问这个问题:“讲述一下 SpringBoot 自动装配原理?”。 我觉得我们可以从以下几个方面回答: 什么是 SpringBoot 自动装配? SpringBoot 是如何实现自动装配的?如何实现按需加载? 如何实现一个 Starter? 前言 使用过 Spring 的小伙伴,一定有被 XML 配置统治的恐惧。即使 Spring 后面引入了基于注解的配置,我们在开启某些 Spring

林心涯的博客 4006

AB SCIEX质谱数据分析实战:从PeakView入门到精通

质谱分析是现代生命科学和药物研发中不可或缺的核心技术,它通过测量离子的质荷比来鉴定和定量化合物。其基本原理是将样品离子化后,在电场和磁场中按质荷比分离,最终形成质谱图。这项技术的核心价值在于能够从复杂混合物中高灵敏度、高特异性地获取化合物的定性和定量信息,广泛应用于代谢组学、蛋白质组学、环境监测和药物分析等领域。在实际工作中,高效的数据处理平台至关重要。以AB SCIEX质谱仪生成的.wiff文件为例,其原始数据需要专业软件进行解析。PeakView软件正是这样一个综合平台,它集成了高级数据处理、谱图解析和

weixin_29053577的博客 303

相机SDK开发C++篇-常用开发函数封装讲解(迈德威视相机)

/ 软触发失败,返回false } return true;= (nRet = CameraSetSysOption("NumBuffers", QString("%1").arg(getImageBufferNodeNum()).toLocal8Bit().data()))) { setLastErrorMsg("相机#%1设置缓存区数量错误,错误码:%2").arg(getCameraIndex()).arg(nRet);

GodJhin的博客 1541

Springboot自动装配原理

Springboot自动装配原理

qq_45813393的博客 5058

Windows下用RKDevTool解包update.img的完整流程(含常见错误排查)

本文详细介绍了在Windows系统下使用RKDevTool工具解包update.img固件的完整流程。从工具准备、环境搭建到逐步解包操作,重点解析了输出文件结构,并提供了修改分区后重新打包的实战方法。文章还针对解包失败、打包错误等常见问题提供了详细的排查与解决方案,旨在帮助嵌入式开发者和爱好者高效完成固件分析与定制工作。

aa123的博客 715

SpringBoot自动装配原理

Spring Boot 的自动装配(Auto-configuration)是 Spring Boot 框架中一项强大的功能,它可以根据应用程序的依赖和配置自动完成大部分常见的配置工作,减少了开发者在项目中进行繁琐的手动配置的工作。自动装配其实是通过条件化装配、自动配置类、配置属性绑定来实现的条件化装配:Spring Boot 使用条件化注解()来实现自动装配。这些注解基于运行时环境的条件来决定是否需要装配某个组件。自动配置类:Spring Boot 通过在 classpath 下的。

dongdong199033的博客 5869

Python手记-9:Python LXML库XPath的爬取晋江书目

1. lxml库 lxml是功能最丰富且易于使用的库,用于处理Python语言中的XML和HTML,还可实现WEB爬取,官文参考:https://lxml.de/,完整的PDF文档下载链接https://lxml.de/lxmldoc-4.5.0.pdf,但是阅读起来……em……怪自己不够聪明的样子。 Linux下安装lxml库:[root@chengyu ~]# pip3 instal...

成屿的专栏 4万+

PCL 计算点云法向量并显示【2026最新版】

PCL计算法向量并显示,用OMP进行计算加速以及法线定向的深层次理解、操作。博客长期更新,本文最近更新时间为:2026年1月28日。

点云侠的博客 32万+

【springboot】springboot 自动装配原理

springboot 大大降低了我们项目搭建成本,也对常用的框架版本进行了管理。我们引入常用框架也只是在 Maven 中引入一个 starter jar 包,然后配置好关键配置,如数据源地址、用户名、密码等,就可以使用。我们自己在工作中的也可以下一个 starter 供项目使用,极大方便了日常的开发。而上面的一切都指向了 springboot 的自动装配功能,可以说没有自动装配功能就没有 springboot。甚至可以说 springboot 的自动装配是微应用的一个基石。

zzd13546036394的博客 4589

团体程序设计天梯赛竞赛题--登顶题【L3-039 攀岩】

登顶级 3 道题,每道题 30 分,满分为 90 分。

Pretty Boy Fox的博客 653

SpringBoot自动装配原理与启动流程

序言 SpringBoot的自动装备与启动流程在面试中被问了很多次了。这次认真做给总结吧,免得下一次面试一问三不知,毕竟作为一名的Javaer,常用框架基本原理搞不清楚的话,就有点说不过去了o(╥﹏╥)o SpringBoot自动装配 首先找到启动类(SpringBoot主启动类),点击进入@SpringBootApplication注解,可以该注解是一个符合注解,重点看@EnableAutoConfiguration注解(开启自动配置),那具体的自动配置逻辑在哪呢?别急,继续@EnableAutoConf

我的鱼要死了的博客 4691

16、天文时间序列分类:基于人工神经网络的研究

本研究探讨了基于人工神经网络的天文时间序列分类方法,重点比较了MLP、LSTM和CNN等模型在BRITE和开普勒K2数据集上的表现。研究发现LSTM不适用于此类数据,而结合特征提取与PSO超参数优化的多层感知器(MLP)在开普勒K2数据上实现了98.55%的二分类准确率。通过精细的数据预处理和高置信度样本选择,显著提升了分类性能。研究成果为恒星变异性识别提供了高效工具,并对天文学与机器学习交叉领域具有重要参考价值。

pz8901234的博客 111

手把手教你学Simulink——基于经典PID控制的电机系统场景实例:步进电机PID位置控制系统的实现

步进电机(Stepper Motor)是一种将电脉冲信号转化为角位移的执行元件,具有结构简单、控制方便、定位精度高等优点,广泛应用于数控机床、机器人、打印机、3D打印机等需要精确定位的场合。传统的步进电机多采用开环控制,但在高速或负载变化较大的情况下容易出现丢步、振荡等问题。为提高控制精度与响应性能,闭环PID控制被引入步进电机的位置控制中,通过编码器或旋转变压器等反馈装置,实现高精度、高稳定性的位置控制。本教程将带你使用构建一个基于PID控制的步进电机位置控制系统仿真模型步进电机建模。

MHD0815的博客 9686

SpringBoot的 启动流程和自动装配 的核心原理

SpringBoot的 启动流程和自动装配 的核心原理

zhang_qing_yun的博客 325

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

简述SpringBoot启动流程和自动装配原理、核心特性

Spring Boot 是一个用于简化 Spring 应用程序开发的框架,它提供了许多自动化配置和约定优于配置的特性,使得开发者能够更快地搭建和部署应用程序。下面是 Spring Boot 的启动流程和自动装配原理的概述。

豆恭梓的博客 546

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

SpringBoot启动流程,自动装配原理

web项目中引用了utils-jar包,但是现在直接从IOC容器中却没有获取到,这是因为@SpringBootApplication注解他是默认的去当前包及其子包。@Import注解的升级版本采用@EnableXxx注解 这个注解是第三方提供的,因为你需要导入那些类 第三放是最清楚的,所有第三方提供了@EnableXxx。元注解:是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其他的注解上面。他这个注解里面就有@import注解里面导入的就是ImportSelect的接口实现类。

qq_41951891的博客 880

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Springboot启动流程核心知识点(一):Spring自动装配原理

依托Springboot启动流程,结合源码,完整了解Springboot自动装配原理。

bigbearxyz的博客 703

SpringBoot 源码分析 (SpringBoot自动装配原理 + tomcat 启动流程分析) (四)

文章目录1.SpringBoot自动装配原理2.SpringBoot如何启动SpringMvc和tomcat2.1 web服务器工厂的创建和tomcat创建2.2 tomcat服务器的启动2.3 DispatherServlet注册进tomcat 1.SpringBoot自动装配原理 首先, SpringBoot通过@SpringBootApplication 和 SpringApplication.run()方法启动了SpringMvc tomcat 以及其他的配置。 @SpringBootApplica

959 696

SpringBoot启动流程原理+自动装配原理讲解

SpringBoot启动流程原理+自动装配原理讲解

weixin_42113716的博客 357

SpringBoot核心原理:自动装配与启动流程深度解析

Java后端开发中,框架的封装程度直接影响开发效率与问题排查成本。SpringBoot之所以能大幅简化Spring应用搭建,核心在于其“约定大于配置”的设计思想,以及底层的自动装配机制。自动装配通过条件注解与AutoConfiguration.imports文件,在启动时按需加载相应Bean,既保证功能完整又避免资源浪费。与此同时,SpringApplication.run()的启动流程、配置文件的优先级解析、内嵌Web容器、定时任务、日志与敏感信息处理等,都是高频使用的工程场景。理解从配置加载、Bean创

weixin_30702441的博客 216

SpringBoot启动流程(二)——自动装配原理

一文彻底搞懂SpringBoot自动装配原理!

xulongyu_0617的博客 268

Spring Boot原理分析(一):项目启动流程、自动装配

关于自定义注解的相关知识可以先看我这篇博客了解一下。@Target(ElementType . TYPE) //注解的作用目标是类、接口(包括注解类型)或枚举声明。@Retention(RetentionPolicy . RUNTIME) @Documented @Inherited //此注解可被继承。

TracyCoder的博客 884

《SpringBoot启动流程五》:你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)

你真的知道SpringBoot自动装配原理吗? 源码分析SpringBoot自动装配入口? SpringBoot自动装配在SpringBoot启动流程中的体现?

健身变秃,coding变强 3956
上一篇: 探讨整合Spring前提下Jpa的EntityManager和Mybatis的SqlSession在不加@Transactional时会不会占用连接不释放(待完善)
下一篇: 什么是互联网
躺平程序猿
博客等级 码龄8年 4870粉丝 71原创
评论 29
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺平程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值