上一篇文章分析了除 refresh 方法外的流程,并着重分析了 load 方法,这篇文章就主要分析 refresh 方法,可以说 refresh 方法是 springboot 启动流程最重要的一环,没有之一。我们通常在分析源码的过程中,都需要带着一个目标去看,不然看这看那,感觉什么都没有看一样。这篇文章的目标在于弄懂 SpringBoot 自动装配的原理,并且结合我们常用的 Mybatis-Plus 实战分析一下。如果你不感兴趣,可以直接跳过。

  1. try {
  2. // Allows post-processing of the bean factory in context subclasses.
  3. postProcessBeanFactory(beanFactory);
  4. StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
  5. // Invoke factory processors registered as beans in the context.
  6. invokeBeanFactoryPostProcessors(beanFactory);
  7. // Register bean processors that intercept bean creation.
  8. registerBeanPostProcessors(beanFactory);
  9. beanPostProcess.end();
  10. // Initialize message source for this context.
  11. initMessageSource();
  12. // Initialize event multicaster for this context.
  13. initApplicationEventMulticaster();
  14. // Initialize other special beans in specific context subclasses.
  15. onRefresh();
  16. // Check for listener beans and register them.
  17. registerListeners();
  18. // Instantiate all remaining (non-lazy-init) singletons.
  19. finishBeanFactoryInitialization(beanFactory);
  20. // Last step: publish corresponding event.
  21. finishRefresh();
  22. }

该方法是由 AnnotationConfigServletWebServerApplicationContext 类调用的,不过它也是调用的父类的方法,所以你可以直接挪到 AbstractApplicationContext 抽象类即可。

一、invokeBeanFactoryPostProcessors

  1. if (beanFactory instanceof BeanDefinitionRegistry) {
  2. BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
  3. List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
  4. List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
  5. for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
  6. if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
  7. BeanDefinitionRegistryPostProcessor registryProcessor =
  8. (BeanDefinitionRegistryPostProcessor) postProcessor;
  9. registryProcessor.postProcessBeanDefinitionRegistry(registry);
  10. registryProcessors.add(registryProcessor);
  11. }
  12. else {
  13. regularPostProcessors.add(postProcessor);
  14. }
  15. }
  16. ......
  17. }

这里的 beanFactory 是 DefaultListableBeanFactory,它是 BeanDefinitionRegistry 是实例,至于怎么来的我想你应该清楚哈。然后在这片段代码里,我们比较注意的是 registryProcessor.postProcessBeanDefinitionRegistry(registry)。

这时你应该想到 beanFactoryPostProcessors 的值是怎么来的,如果你往前追溯的话,可以知道是在 prepareContext 方法里赋值的。另外需要注意到的是它分成了两个类型的 processor,一个是 regularPostProcessors,另一个是 registryProcessors。

  1. // Do not initialize FactoryBeans here: We need to leave all regular beans
  2. // uninitialized to let the bean factory post-processors apply to them!
  3. // Separate between BeanDefinitionRegistryPostProcessors that implement
  4. // PriorityOrdered, Ordered, and the rest.
  5. List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
  6. // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
  7. String[] postProcessorNames =
  8. beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
  9. for (String ppName : postProcessorNames) {
  10. if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
  11. currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
  12. processedBeans.add(ppName);
  13. }
  14. }
  15. sortPostProcessors(currentRegistryProcessors, beanFactory);
  16. registryProcessors.addAll(currentRegistryProcessors);
  17. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
  18. currentRegistryProcessors.clear();

接着我们看这段代码,这里的注释意思很清楚:不要在这里实例化 FactoryBeans,我们需要把所有的 regular beans 留给 beanFactory 的 post-processors 去处理。然后将 BeanDefinitionRegistryPostProcessors 分离为实现了 PriorityOrdered、Ordered 两类,以及剩下的为一类,然后调用 invokeBeanDefinitionRegistryPostProcessors 方法进行处理。

  1. private static void invokeBeanDefinitionRegistryPostProcessors(
  2. Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
  3. for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
  4. StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
  5. .tag("postProcessor", postProcessor::toString);
  6. postProcessor.postProcessBeanDefinitionRegistry(registry);
  7. postProcessBeanDefRegistry.end();
  8. }

在这里仅有一个实现了 PriorityOrdered 的 postProcessor,那就是 ConfigurationClassPostProcessor,这个是一个非常非常重要的 postProcessor,这里不妨追溯一下它的数据来源,各位可以自己追溯看看,毕竟这是看源码过程中一个必备的能力,答案就是在创建 context 的时候,调用 createApplicationContext 方法时,就设置值进去了,具体代码如下:

  1. context = createApplicationContext();
  2. public AnnotationConfigServletWebServerApplicationContext() {
  3. this.reader = new AnnotatedBeanDefinitionReader(this);
  4. this.scanner = new ClassPathBeanDefinitionScanner(this);
  5. }
  6. public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
  7. Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
  8. Assert.notNull(environment, "Environment must not be null");
  9. this.registry = registry;
  10. this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
  11. AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  12. }
  13. public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
  14. registerAnnotationConfigProcessors(registry, null);

这个 registerAnnotationConfigProcessors 代码过长,避免通篇都是代码,这里各位可以自己进去看看。

本文目的是分析自动装配原理,并且该原理就藏在 ConfigurationClassPostProcessor 里面,但是直接看代码找还是很难的。这里我们直接打断点分析,首先我们来到 spring-boot-autoconfigure 包下,找到 AutoConfigurationImportSelector 类,至于为什么是该类,如果你看过 @EnableAutoConfiguration 注解的话就知道了,并且 @SpringBootApplication 也包含该注解。然后你可以找到如下代码:

  1. protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  2. if (!isEnabled(annotationMetadata)) {
  3. return EMPTY_ENTRY;
  4. }
  5. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  6. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  7. configurations = removeDuplicates(configurations);
  8. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  9. checkExcludedClasses(configurations, exclusions);
  10. configurations.removeAll(exclusions);
  11. configurations = getConfigurationClassFilter().filter(configurations);
  12. fireAutoConfigurationImportEvents(configurations, exclusions);
  13. return new AutoConfigurationEntry(configurations, exclusions);

然后给它打上个断点,开始调试,看看是怎么跳进来的,下面只展示几个流程:

1、获取启动类 Main 方法上的 Import 注解标注的类:AutoConfigurationImportSelector.class



2、获取该类的 importGroup:AutoConfigurationGroup.class:



3、调用该类的 process:



4、获取 @EnableAutoConfiguration 注解标注的类:



5、对 @EnableAutoConfigureation 标注的类进行处理



这里看 processImports 方法,从这里进去后追踪到 doProcessConfigurationClass:

  1. protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
  2. if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
  3. return;
  4. }
  5. ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  6. if (existingClass != null) {
  7. if (configClass.isImported()) {
  8. if (existingClass.isImported()) {
  9. existingClass.mergeImportedBy(configClass);
  10. }
  11. // Otherwise ignore new imported config class; existing non-imported class overrides it.
  12. return;
  13. }
  14. else {
  15. // Explicit bean definition found, probably replacing an import.
  16. // Let's remove the old one and go with the new one.
  17. this.configurationClasses.remove(configClass);
  18. this.knownSuperclasses.values().removeIf(configClass::equals);
  19. }
  20. }
  21. // Recursively process the configuration class and its superclass hierarchy.
  22. SourceClass sourceClass = asSourceClass(configClass, filter);
  23. do {
  24. sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
  25. }
  26. while (sourceClass != null);
  27. this.configurationClasses.put(configClass, configClass);
  28. }
  29. protected final SourceClass doProcessConfigurationClass(
  30. ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
  31. throws IOException {
  32. if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
  33. // Recursively process any member (nested) classes first
  34. processMemberClasses(configClass, sourceClass, filter);
  35. }
  36. // Process any @PropertySource annotations
  37. for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
  38. sourceClass.getMetadata(), PropertySources.class,
  39. org.springframework.context.annotation.PropertySource.class)) {
  40. if (this.environment instanceof ConfigurableEnvironment) {
  41. processPropertySource(propertySource);
  42. }
  43. else {
  44. logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
  45. "]. Reason: Environment must implement ConfigurableEnvironment");
  46. }
  47. }
  48. // Process any @ComponentScan annotations
  49. Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
  50. sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
  51. if (!componentScans.isEmpty() &&
  52. !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
  53. for (AnnotationAttributes componentScan : componentScans) {
  54. // The config class is annotated with @ComponentScan -> perform the scan immediately
  55. Set<BeanDefinitionHolder> scannedBeanDefinitions =
  56. this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
  57. // Check the set of scanned definitions for any further config classes and parse recursively if needed
  58. for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
  59. BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
  60. if (bdCand == null) {
  61. bdCand = holder.getBeanDefinition();
  62. }
  63. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
  64. parse(bdCand.getBeanClassName(), holder.getBeanName());
  65. }
  66. }
  67. }
  68. }
  69. // Process any @Import annotations
  70. processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
  71. // Process any @ImportResource annotations
  72. AnnotationAttributes importResource =
  73. AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
  74. if (importResource != null) {
  75. String[] resources = importResource.getStringArray("locations");
  76. Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
  77. for (String resource : resources) {
  78. String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
  79. configClass.addImportedResource(resolvedResource, readerClass);
  80. }
  81. }
  82. // Process individual @Bean methods
  83. Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  84. for (MethodMetadata methodMetadata : beanMethods) {
  85. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  86. }
  87. // Process default methods on interfaces
  88. processInterfaces(configClass, sourceClass);
  89. // Process superclass, if any
  90. if (sourceClass.getMetadata().hasSuperClass()) {
  91. String superclass = sourceClass.getMetadata().getSuperClassName();
  92. if (superclass != null && !superclass.startsWith("java") &&
  93. !this.knownSuperclasses.containsKey(superclass)) {
  94. this.knownSuperclasses.put(superclass, configClass);
  95. // Superclass found, return its annotation metadata and recurse
  96. return sourceClass.getSuperClass();
  97. }
  98. }
  99. // No superclass -> processing is complete
  100. return null;
  101. }

这里就是怎么处理这些 @EnableAutoConfigureation 标注的类,会先判断类上是否包含 Component 注解,不要仅看表面,比如 @Configuration 注解也是包含 Component 注解的。如果包含的话会扫描其内部类,递归处理。总的来说:

1、扫描内部类判断是否有资格注册为 bean。

2、如果包含 ComponentScan 等注解,会对包路径进行扫描,扫描到的注册为 bean。

3、加载 @Import 注解标注的类,判断其是否有资格注册为 bean。

4、加载 @ImportResource 标注的资源,注册为 bean。

5、加载 @Bean 注解标注的方法,注册为 bean。

6、加载接口方法、父类判断是否有资格注册为 bean。

这里说完还是比较抽象,我们不如以 mybatis-plus-boot-starter 作示例:

  1. # Auto Configure
  2. org.springframework.boot.env.EnvironmentPostProcessor=\
  3. com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
  4. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  5. com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\
  6. com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
  7. com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

以上是 mybatis-plus 的 spring.factories,可以看到 EnableAutoConfiguration 注解的内容有哪些,这里我们比较关注的应该是 MybatisPlusAutoConfiguration:



它拥有两个内部类,按顺序第一个没有 component 注解,所以不会注册为 bean,第二个有 Configuration 注解、Import 注解,并且导入的是第一个类,还有一个 Conditional 注解,也就是是否有资格注册成为 bean。



现在让我们看看执行顺序是怎么样的,让我们重新来到 doProcessConfigurationClass 方法,并且 configClass 是 MybatisPlusAutoConfiguration。

第一步会扫描到两个内部类,至于怎么处理后面再看,因为这是一个递归函数。第二步会判断该类是否包含 PropertySources 注解,这个名字一看就是处理类的属性的。第三步会判断其是否包含 ComponentScan 注解,递归扫描可被注册的类。第四步就是扫描其 Import 的类,比如这里导入的就是 AutoConfiguredMapperScannerRegistrar,但是其无法注册为 bean,因为它不包含任何可被注册为 bean 的注解。后面的就不说了。这几步流程完成后,Spring 已经注册了绝大部分的 bean 了。但是并没有发现 mybatis-plus 的 mapper bean 是什么时候注册的。

我们继续往下看:



上述流程都是在 parser.parse(candidates) 中发生的,configClasses 就是我们解析到注册的 bean,接下来就看看 this.reader.loadBeanDefinitions(configClasses) 方法:

  1. private void loadBeanDefinitionsForConfigurationClass(
  2. ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
  3. if (trackedConditionEvaluator.shouldSkip(configClass)) {
  4. String beanName = configClass.getBeanName();
  5. if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName))
  6. this.registry.removeBeanDefinition(beanName);
  7. }
  8. this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
  9. return;
  10. }
  11. if (configClass.isImported()) {
  12. registerBeanDefinitionForImportedConfigurationClass(configClass);
  13. }
  14. for (BeanMethod beanMethod : configClass.getBeanMethods()) {
  15. loadBeanDefinitionsForBeanMethod(beanMethod);
  16. }
  17. loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
  18. loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
  19. }
  20. private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
  21. registrars.forEach((registrar, metadata) ->
  22. registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
  23. }

这里我们直接看到最后一段代码,我们假设 configClass 是 MapperScannerRegistrarNotFoundConfiguration,那么其 registrars 就是 AutoConfiguredMapperScannerRegistrar,这里就会执行 registerBeanDefinitions 方法,该方法最后注册了一个 processor:MapperScannerConfigurer,他会扫描 @Mapper 注解的 bean。

到这里 invokeBeanDefinitionRegistryPostProcessors 的流程就介绍的差不多了,现在来看看后面的 invokeBeanFactoryPostProcessors 方法,现在主要看 ConfigurationClassPostProcessor 和 MapperScannerConfigurer 的调用。第一个其源码注释就是为 bean 创建代理子类,为 bean 的实例化作准备,而第二个确什么都没有做。那么 @Mapper 注解标注的 bean 什么时候实例化呢?这个目前不清楚什么时候调用的,不过网上搜的是在 AbstractBeanFactory 类中实例化的,关键代码如下:

  1. // Create bean instance.
  2. if (mbd.isSingleton()) {
  3. sharedInstance = getSingleton(beanName, () -> {
  4. try {
  5. return createBean(beanName, mbd, args);
  6. }
  7. catch (BeansException ex) {
  8. // Explicitly remove instance from singleton cache: It might have been put there
  9. // eagerly by the creation process, to allow for circular reference resolution.
  10. // Also remove any beans that received a temporary reference to the bean.
  11. destroySingleton(beanName);
  12. throw ex;
  13. }
  14. });
  15. bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  16. }

二、registerBeanPostProcessors

该方法和上面的步骤也差不多,registerBeanPostProcessors 方法的作用是将所有的 BeanPostProcessor(后置处理器)注册到 BeanFactory 中。

三、onRefresh

该方法会创建 WebServer,没啥好说的。

四、finishBeanFactoryInitialization

finishBeanFactoryInitialization 法的作用是完成 BeanFactory 的初始化过程。在应用程序上下文启动时,会调用该方法来完成 BeanFactory 的初始化工作,包括实例化和初始化所有的非延迟加载的单例 Bean。

具体来说,finishBeanFactoryInitialization 方法会按照以下步骤完成 BeanFactory 的初始化:

  1. 实例化所有非延迟加载的单例 Bean:根据 Bean 定义信息,通过反射或其他方式实例化所有非延迟加载的单例 Bean,并将它们放入容器中。
  2. 依赖注入:对所有实例化的 Bean 进行依赖注入,即将它们所依赖的其他 Bean 注入到相应的属性中。
  3. 初始化:对所有实例化并注入依赖的 Bean 进行初始化。这包括调用 Bean 的初始化方法(如果有定义的话),以及应用任何配置的 Bean 后置处理器对 Bean 进行处理。
  4. 完成 BeanFactory 的初始化:将 BeanFactory 的状态设置为已初始化完成,标记整个初始化过程的结束。

    通过调用 finishBeanFactoryInitialization 方法,Spring 容器能够确保所有非延迟加载的单例 Bean 都被正确实例化、注入依赖和初始化,从而使它们可以在应用程序中被正常使用。

五、finishRefresh

finishRefresh 方法的作用是在应用程序上下文刷新完成后执行一些额外的操作。

在应用程序上下文的刷新过程中,会调用该方法来完成一些与刷新相关的收尾工作。

具体来说,finishRefresh方法会按照以下步骤完成额外的操作:

  1. 初始化生命周期处理器:对于实现了 Lifecycle 接口的 Bean,会调用它们的 start 方法来启动这些 Bean。
  2. 发布应用程序上下文已完成事件:通过 ApplicationEventPublisher,发布一个 ContextRefreshedEvent 事件,通知其他监听器应用程序上下文已完成刷新。
  3. 注册 ApplicationListener 的 Bean:将实现了 ApplicationListener 接口的 Bean 注册到应用程序上下文中,以便监听其他事件。
  4. 初始化其他单例 Bean:对于非延迟加载的单例 Bean,会调用它们的初始化方法(如果有定义的话)。
  5. 发布应用程序上下文已启动事件:通过 ApplicationEventPublisher,发布一个 ContextStartedEvent 事件,通知其他监听器应用程序上下文已启动。

通过调用finishRefresh方法,Spring容器能够在应用程序上下文刷新完成后执行一些额外的操作,如启动生命周期处理器、发布事件等。这些操作可以用于执行一些初始化、通知或其他自定义的逻辑,以满足特定的需求。

6、总结

refresh 方法感觉还是比较难以分析,后面部分文字内容还是借鉴了 ChatGPT,感觉如果你想知道某个函数的作用时,直接问它,它或许会告诉你正确答案,比如你这样问:finishRefresh 方法的作用是啥?

SpringBoot 启动流程追踪(第二篇)的更多相关文章

  1. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  2. springboot启动流程(一)构造SpringApplication实例对象

    所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 启动入口 本文是springboot启动流程的第一篇,涉及的内容是SpringApplicat ...

  3. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  5. SpringBoot启动流程分析(六):IoC容器依赖注入

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  6. SpringBoot启动流程分析(一):SpringApplication类初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  7. SpringBoot启动流程分析(二):SpringApplication的run方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  8. SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  9. SpringBoot启动流程及其原理

    Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...

  10. SpringBoot源码学习3——SpringBoot启动流程

    系列文章目录和关于我 一丶前言 在 <SpringBoot源码学习1--SpringBoot自动装配源码解析+Spring如何处理配置类的>中我们学习了SpringBoot自动装配如何实现 ...

随机推荐

  1. uni-app 打包发行

    1.云端 发行-原生App-云打包 2.离线 运行-原生App本地打包-生成本地打包资源,如果提示安装依赖包,安装即可 注意:项目的AppID不能为空,请在该项目下的manifest.json中重新获 ...

  2. Go开源世界主流成熟ORM框架gorm实践分享

    @ 目录 概述 定义 核心功能 声明模型与约定 gorm.Model 字段级权限 时间惯例 嵌入结构 字段标签 使用 安装 数据库链接 连接池 CRUD 接口 创建 查询 高级查询 修改 删除 原始S ...

  3. PlayWright(二)

      上篇我们已经安装好了playwright和各个浏览器,那么现在我们直接开始吧   1.怎么使用palywright?   我们需要先导入sync_playwright,然后用start启动,sto ...

  4. Request类源码分析、序列化组件介绍、序列化类的基本使用、常用字段类和参数、反序列化之校验、反序列化之保存、APIVIew+序列化类+Response写的五个接口代码、序列化高级用法之source、序列化高级用法之定制字段的两种方式、多表关联反序列化保存、反序列化字段校验其他、ModelSerializer使用

    目录 一.Request类源码分析 二.序列化组件介绍 三.序列化类的基本使用 查询所有和查询单条 四.常用字段类和参数(了解) 常用字段类 字段参数(校验数据来用的) 五.反序列化之校验 六.反序列 ...

  5. PE 结构的三种地址

    三种地址   (一)VA(虚拟地址):PE 文件被 Windows 加载到内存后的地址.   (二) RVA(相对虚拟地址):PE 文件虚拟地址相对于映射基地址(对于 EXE 文件来说,映射基地址是 ...

  6. ISIS 综合实验;BGP 实验

    目录 ISIS 综合实验 实验拓扑 实验需求 实验步骤 1.配置相应接口IP地址及环回口地址 2.配置 IS-IS,要求全网互通,R8的Loop X口暂不宣告 3. R1和R3直连,要求 R3 成为 ...

  7. P3498 [POI2010]KOR-Beads 题解

    前言: 最近在做哈希的题,发现了这道好题,看题解里很多大佬的方法都很巧妙,自己就发一个较为朴素的方法吧. 题意: 题目传送门 给你一个序列,需要求出数 k,使划分的子串长度为 k 时,不同的子串数量最 ...

  8. Vue——登录小案例、scoped、ref属性、props其他、混入mixin、插件、Element-ui

    解析Vue项目 # 1 为什么浏览器中访问某个地址,会显示某个页面组件 根组件:APP.vue 必须是 <template> <div id="app"> ...

  9. Gitlab版本升级

    Gitlab docker部署命令 docker run -d -p 8443:443 -p 30080:80 -p 9444:22 --name gitlab --restart always \ ...

  10. GPT3与机器翻译的结合:探索新的语言翻译技术

    目录 引言 随着全球化的加速和人工智能的快速发展,机器翻译成为了许多企业.机构和个人的痛点.虽然已有多种机器翻译技术,但基于自然语言处理和深度学习的机器翻译一直缺乏有效的解决方案,这导致机器翻译的准确 ...