SpringBoot源码分析(8)--内置ApplicationContextInitializer


本文基于spring-boot-2.2.14.BUILD-SNAPSHOT源码分析。

本篇文章是对上篇prepareContext的补充,在该方法的执行过程中,遍历了最初从META-INF/spring.factories文件中加载到的ApplicationContextInitializer,依次调用了其initialize方法。
在这里插入图片描述

protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}

SpringApplication初始化的时候,一共加载到了7个内置的ApplicationContextInitializer,本篇文章就逐一分析每个内置的初始化器做了哪些事情

spring-boot/src/main/resources/META-INF/spring.factories

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

1、DelegatingApplicationContextInitializer

获取环境中属性context.initializer.classesInitializer配置的ApplicationContextInitializer列表, 执行其initialize()方法。

SpringBoot允许我们通过各种属性配置方式自定义一些ApplicationContextInitializer,它们的调用时机就是该类的initialize方法

//代理初始化器
public class DelegatingApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
        
    private static final String PROPERTY_NAME = "context.initializer.classes";
    
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        //获取environment中配置的context.initializer.classes属性
        //其值为各个initializer,用逗号分隔开
        List<Class<?>> initializerClasses = getInitializerClasses(environment);
        if (!initializerClasses.isEmpty()) {
            //获取到各个initializer,并执行其initialize()方法
            applyInitializerClasses(context, initializerClasses);
        }
    }
            
}

进入getInitializerClasses方法

private static final String PROPERTY_NAME = "context.initializer.classes";

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
	String classNames = env.getProperty(PROPERTY_NAME);
	List<Class<?>> classes = new ArrayList<>();
	if (StringUtils.hasLength(classNames)) {
		for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
			classes.add(getInitializerClass(className));
		}
	}
	return classes;
}

它从environment中找到context.initializer.classes属性,以逗号为分隔符解析成Class列表并返回

由于这里只通过getProperty方法取了一次,我们之前分析过,这个方法会从前往后遍历所有的PropertySource,取到了就立即返回,所以通过不同方式,比如启动参数、系统配置等不同途径设置的context.initializer.classes,只有优先级最高的那个会生效

然后对于找到的自定义初始化器,调用applyInitializerClasses方法

private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
	Class<?> contextClass = context.getClass();
	List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
	for (Class<?> initializerClass : initializerClasses) {
		initializers.add(instantiateInitializer(contextClass, initializerClass));
	}
	applyInitializers(context, initializers);
}

先实例化,然后调用applyInitializers方法

private void applyInitializers(ConfigurableApplicationContext context,
			List<ApplicationContextInitializer<?>> initializers) {
	initializers.sort(new AnnotationAwareOrderComparator());
	for (ApplicationContextInitializer initializer : initializers) {
		initializer.initialize(context);
	}
}

最终调用了它们的initialize方法

2、SharedMetadataReaderFactoryContextInitializer

添加了一个类型为CachingMetadataReaderFactoryPostProcessor的BeanFactoryPostProcessor, CachingMetadataReaderFactoryPostProcessor会做两件事情

  • 注册一个名称为internalCachingMetadataReaderFactory, 类型为SharedMetadataReaderFactoryBean的bean, 用于读取bean的元数据Metadata
  • 获取名称为internalConfigurationAnnotationProcessor, 类型为ConfigurationClassPostProcessor的bean定义, 为其添加name为metadataReaderFactory, value为internalCachingMetadataReaderFactory的internalCachingMetadataReaderFactory
class SharedMetadataReaderFactoryContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    public static final String BEAN_NAME = "org.springframework.boot.autoconfigure."
            + "internalCachingMetadataReaderFactory";

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        //spring上下文容器添加一个CachingMetadataReaderFactoryPostProcessor
        applicationContext.addBeanFactoryPostProcessor(
                new CachingMetadataReaderFactoryPostProcessor());
    }
}

具体类型为CachingMetadataReaderFactoryPostProcessor,跟第一个初始化器的逻辑一样,它也是内部类,并且实现的也是BeanDefinitionRegistryPostProcessor接口,其中postProcessBeanFactory方法为空,所以只需要看其postProcessBeanDefinitionRegistry方法

private static class CachingMetadataReaderFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
   private CachingMetadataReaderFactoryPostProcessor() {
    }

	......
	......

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.register(registry);
        this.configureConfigurationClassPostProcessor(registry);
    }
    ......
}

首先register方法从内部类SharedMetadataReaderFactoryBean获取了一个BeanDefinition,并注册到了容器的BeanDefinitionMap中

private void register(BeanDefinitionRegistry registry) {
    BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean.class, SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean::new).getBeanDefinition();
    registry.registerBeanDefinition("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory", definition);
}

这个SharedMetadataReaderFactoryBean顾名思义,是一个FactoryBean,同时它实现了BeanClassLoaderAware接口,在这个接口的回调方法setBeanClassLoader中初始化了内部的ConcurrentReferenceCachingMetadataReaderFactory,并在getObject方法返回

static class SharedMetadataReaderFactoryBean implements FactoryBean<ConcurrentReferenceCachingMetadataReaderFactory>, BeanClassLoaderAware, ApplicationListener<ContextRefreshedEvent> {
    private ConcurrentReferenceCachingMetadataReaderFactory metadataReaderFactory;

    SharedMetadataReaderFactoryBean() {
    }

    public void setBeanClassLoader(ClassLoader classLoader) {
        this.metadataReaderFactory = new ConcurrentReferenceCachingMetadataReaderFactory(classLoader);
    }

    public ConcurrentReferenceCachingMetadataReaderFactory getObject() throws Exception {
        return this.metadataReaderFactory;
    }

返回的这个ConcurrentReferenceCachingMetadataReaderFactory,它可以生产一个MetadataReader,这个Reader的作用就是读取类的元数据,包括Class相关的信息,比如是否接口、是否抽象类、是否有注解等等,以及获得注解相关的元数据,比如加了哪些注解等等,在整个Bean的生命周期中起到了非常重要的作用

register方法执行完毕,调用configureConfigurationClassPostProcessor方法

private void configureConfigurationClassPostProcessor(BeanDefinitionRegistry registry) {
        try {
        BeanDefinition definition = registry.getBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor");
        definition.getPropertyValues().add("metadataReaderFactory", new RuntimeBeanReference("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"));
    } catch (NoSuchBeanDefinitionException var3) {
        ;
    }
}

先从容器获取了名为internalConfigurationAnnotationProcessor的BeanDefinition,然后把上面生成的metadataReaderFactory设置到它的属性中

在新建Spring容器的时候,会初始化一个BeanDefinitionReader,而这个Reader的初始化过程中,会往容器中注册一个ConfigurationClassPostProcessor,名字就是上面的internalConfigurationAnnotationProcessor,它是Spring容器完成扫描的起点,包括@Component、@Configuration的处理都是在这个类中进行的,而完成这些工作,自然需要解析每个类的元数据,所以它把metadataReaderFactory赋给了ConfigurationClassPostProcessor的属性,后续就会用它来完成一些Bean的元数据解析

3、ContextIdApplicationContextInitializer

初始化容器ID,这个类的作用是给容器设置一个ID,其实就是我们的项目名, 获取属性spring.application.name配置的应用名称, 如果不存在的话, 默认使用application

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    //获取并设置容器ID
    ContextId contextId = getContextId(applicationContext);
    applicationContext.setId(contextId.getId());
    //容器beanFactory中注册一个名称为ContextId类名
    //值为contextId的bean
    applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(),
            contextId);
}

第一行先获取了一个容器ID对象,然后把ID属性设置到容器中,并把这个ID对象作为一个单例Bean注册到容器的单例池
我们看下这个ContextId怎么来的,进入getContextId方法

//获取ContextID
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
    //父容器获取spring.application.name对应的bean
    ApplicationContext parent = applicationContext.getParent();
    if (parent != null && parent.containsBean(ContextId.class.getName())) {
        return parent.getBean(ContextId.class).createChildId();
    }
    //父容器获取不到,则生成一个contextId
    return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}

如果有父容器,就根据父容器的ID创建一个子容器ID,格式为 父容器ID - 子容器序号

ContextIdApplicationContextInitializer.ContextId createChildId() {
    return ContextIdApplicationContextInitializer.this.new ContextId(this.id + "-" + this.children.incrementAndGet());
}

如果没有父容器,就到environment中取spring.application.name属性,没有配置的话默认为application

//获取应用ID
private String getApplicationId(ConfigurableEnvironment environment) {
    //获取属性:spring.application.name
    String name = environment.getProperty("spring.application.name");
    //如果为空,默认名application
    return StringUtils.hasText(name) ? name : "application";
}

将取到的结果作为参数传给ContextId的构造函数

 //ContextId类
class ContextId {
    //原子Long类
    private final AtomicLong children = new AtomicLong(0);

    private final String id;

    ContextId(String id) {
        this.id = id;
    }

    ContextId createChildId() {
        //线程安全递增
        return new ContextId(this.id + "-" + this.children.incrementAndGet());
    }

    String getId() {
        return this.id;
    }
}

也就是说默认情况下,这个ContextId存了我们的项目名,然后把它设置到了容器中

4、ConfigurationWarningsApplicationContextInitializer

配置告警初始化器(通过分析源码实际情况,默认扫描启动类所在的路径(或者@ComponentScan注解指定的路径)如果系统配置包扫描到了org或者org.springframework包就会发出警告打印warn日志并停止系统启动

在这里插入图片描述
ConfigurationWarningsApplicationContextInitializer初始化器源码如下:

public class ConfigurationWarningsApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	private static final Log logger = LogFactory.getLog(ConfigurationWarningsApplicationContextInitializer.class);
	//初始化方法会在容器启动时调用,并将ConfigurationWarningsPostProcessor后置处理器注入到应用上下文中
	@Override
	public void initialize(ConfigurableApplicationContext context) {
		context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
	}

	/**
	 * 返回有问题的扫描包(@ComponentScan)Check对象
	 * @return the checks to apply
	 */
	protected Check[] getChecks() {
		return new Check[] { new ComponentScanPackageCheck() };
	}
}

ComponentScanPackageCheck是ConfigurationWarningsApplicationContextInitializer的一个内部类,源码分析

/**
* 可以应用的单一检查
 */
@FunctionalInterface
protected interface Check {

	/**
	 * 返回检查结果,如果检查失败,则返回警告,如果没有问题,则返回null
	 * @param registry the {@link BeanDefinitionRegistry}
	 * @return a warning message or {@code null}
	 */
	String getWarning(BeanDefinitionRegistry registry);

}

/**
 * 检查@ComponentScan注解扫描有问题的包
 */
protected static class ComponentScanPackageCheck implements Check {

	private static final Set<String> PROBLEM_PACKAGES;
	//定义扫描有问题的包
	static {
		Set<String> packages = new HashSet<>();
		packages.add("org.springframework");
		packages.add("org");
		PROBLEM_PACKAGES = Collections.unmodifiableSet(packages);
	}
	
	//检查@ComponentScan注解扫描的包是否有问题,如果有,则返回警告,否则返回null
	@Override
	public String getWarning(BeanDefinitionRegistry registry) {
     //获取@ComponentScan注解扫描的包集合
		Set<String> scannedPackages = getComponentScanningPackages(registry);
     //获取有问题的扫描包集合
		List<String> problematicPackages = getProblematicPackages(scannedPackages);
		if (problematicPackages.isEmpty()) {
			return null;
		}
		return "Your ApplicationContext is unlikely to start due to a @ComponentScan of "
				+ StringUtils.collectionToDelimitedString(problematicPackages, ", ") + ".";
	}
	
	//获取@ComponentScan注解扫描的包
	protected Set<String> getComponentScanningPackages(BeanDefinitionRegistry registry) {
		Set<String> packages = new LinkedHashSet<>();
     //获取容器中所有bean定义名称
		String[] names = registry.getBeanDefinitionNames();
		for (String name : names) {
       //获取name对应的bean定义对象
			BeanDefinition definition = registry.getBeanDefinition(name);
			if (definition instanceof AnnotatedBeanDefinition) {
				AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;
				addComponentScanningPackages(packages, annotatedDefinition.getMetadata());
			}
		}
		return packages;
	}
	
	//将bean实例上注解@ComponentScan扫描包
	private void addComponentScanningPackages(Set<String> packages, AnnotationMetadata metadata) {
		AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(ComponentScan.class.getName(), true));
		if (attributes != null) {
			addPackages(packages, attributes.getStringArray("value"));
			addPackages(packages, attributes.getStringArray("basePackages"));
			addClasses(packages, attributes.getStringArray("basePackageClasses"));
			if (packages.isEmpty()) {
				packages.add(ClassUtils.getPackageName(metadata.getClassName()));
			}
		}
	}

	private void addPackages(Set<String> packages, String[] values) {
		if (values != null) {
			Collections.addAll(packages, values);
		}
	}

	private void addClasses(Set<String> packages, String[] values) {
		if (values != null) {
			for (String value : values) {
				packages.add(ClassUtils.getPackageName(value));
			}
		}
	}
	
	//获取有问题的扫描包集合,即包名是:org或org.springframework
	private List<String> getProblematicPackages(Set<String> scannedPackages) {
		List<String> problematicPackages = new ArrayList<>();
		for (String scannedPackage : scannedPackages) {
       //判定包名是否有问题,即包名是:org或org.springframework
			if (isProblematicPackage(scannedPackage)) {
				problematicPackages.add(getDisplayName(scannedPackage));
			}
		}
		return problematicPackages;
	}
	//判定包名是否有问题,即包名是:org或org.springframework
	private boolean isProblematicPackage(String scannedPackage) {
		if (scannedPackage == null || scannedPackage.isEmpty()) {
			return true;
		}
		return PROBLEM_PACKAGES.contains(scannedPackage);
	}

	private String getDisplayName(String scannedPackage) {
		if (scannedPackage == null || scannedPackage.isEmpty()) {
			return "the default package";
		}
		return "'" + scannedPackage + "'";
	}
}

最终在控制台打印如下日志

** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of 'org'.

这里只是打印一行日志,并不会停止程序,不过实际测试下来程序是没办法正常启动的,这个路径是Spring框架本身的包路径,扫描这个包会干扰Spring正常执行流程,陷入循环,当然正常情况下我们项目的路径也不会这样定义。

5、ServerPortInfoApplicationContextInitializer

服务端口初始化器, 分别实现了ApplicationContextInitializer和ApplicationListener接口, 在applicationContext中添加了事件监听器this, 监听了WebServerInitializedEvent事件, 配置服务的端口号

public class ServerPortInfoApplicationContextInitializer
        implements ApplicationContextInitializer<ConfigurableApplicationContext>,
        ApplicationListener<WebServerInitializedEvent> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        //把this添加到Application的listener中
        applicationContext.addApplicationListener(this);
    }
    
    //监听处理WebServerInitializedEvent事件
    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        //获取environment中配置的server.ports
        String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
        setPortProperty(event.getApplicationContext(), propertyName,
                event.getWebServer().getPort());
    }
}

6、ConditionEvaluationReportLoggingListener

条件评估日志监听器, 主要作用是给applicationContext添加了一个ConditionEvaluationReportListener监听器, ConditionEvaluationReportListener监听了ContextRefreshedEvent和ApplicationFailedEvent事件, 打印相应日志

public class ConditionEvaluationReportLoggingListener
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        //applicationContext中添加一个ConditionEvaluationReportListener
        applicationContext.addApplicationListener(new ConditionEvaluationReportListener());
        if (applicationContext instanceof GenericApplicationContext) {
            //注册一个ConditionEvaluationReport bean
            this.report = ConditionEvaluationReport
                    .get(this.applicationContext.getBeanFactory());
        }
    }
}

先往容器的监听器列表添加一个监听器ConditionEvaluationReportListener,这个类是其内部类,通过supportsEventType方法指定了感兴趣的事件类型为ContextRefreshedEvent和ApplicationFailedEvent

private class ConditionEvaluationReportListener implements GenericApplicationListener {
    private ConditionEvaluationReportListener() {
    }
......
......
    public boolean supportsEventType(ResolvableType resolvableType) {
        Class<?> type = resolvableType.getRawClass();
        if (type == null) {
            return false;
        } else {
            return ContextRefreshedEvent.class.isAssignableFrom(type) || ApplicationFailedEvent.class.isAssignableFrom(type);
        }
    }
    ......
}

具体在这两个事件做了什么处理,我们后面说到具体事件的时候再来分析

然后下面的if分支,当前Spring容器的类型是AnnotationConfigServletWebServerApplicationContext,它是GenericApplicationContext的子类,所以会进if分支,调用ConditionEvaluationReport的get方法

//用于记录Condition注解的评估情况
public final class ConditionEvaluationReport {

    //bean名称为autoConfigurationReport
    //类型为ConditionEvaluationReport
    private static final String BEAN_NAME = "autoConfigurationReport";
    
    //从beanFactory中获取ConditionEvaluationReport实例
    public static ConditionEvaluationReport get(
            ConfigurableListableBeanFactory beanFactory) {
        synchronized (beanFactory) {
            ConditionEvaluationReport report;
            if (beanFactory.containsSingleton(BEAN_NAME)) {
                //如果bean已经被注册,立即返回
                report = beanFactory.getBean(BEAN_NAME, ConditionEvaluationReport.class);
            }
            else {
                //否则注册bean
                report = new ConditionEvaluationReport();
                beanFactory.registerSingleton(BEAN_NAME, report);
            }
            locateParent(beanFactory.getParentBeanFactory(), report);
            return report;
        }
    }
}

先到容器中找名为autoConfigurationReport的单例Bean,如果没有的话就新建一个,并存储到容器的单例池,然后调用locateParent方法,如果存在父容器,检查父容器中有没有名为autoConfigurationReport的单例Bean,有的话,将父容器中的Report设置到当前Report的parent属性中

 private static void locateParent(BeanFactory beanFactory, ConditionEvaluationReport report) {
        if (beanFactory != null && report.parent == null && beanFactory.containsBean("autoConfigurationReport")) {
            report.parent = (ConditionEvaluationReport)beanFactory.getBean("autoConfigurationReport", ConditionEvaluationReport.class);
        }
    }

ConditionEvaluationReport的作用是在SpringBoot自动配置的过程中,打印一些匹配结果的DEBUG日志,包括哪些类完成了自动配置,哪些类的哪些条件没有满足而装载失败等等,比如下图中

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

   CodecsAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)

   CodecsAutoConfiguration.JacksonCodecConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
      ......
      ......
      ......

Negative matches:
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

   AopAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)
       ......
       ......
       ......

7、RSocketPortInfoApplicationContextInitializer

添加了一个 RSocketServerInitializedEvent事件的 监听器到 ApplicationContext中。

public class RSocketPortInfoApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		/**
		  * 注入一个端口检查和设置的监听器,对应的事件RSocketServerInitializedEvent
		  **/
		applicationContext.addApplicationListener(new Listener(applicationContext));
	}
    
    //这里直接写了个内部类实现RSocketServerInitializedEvent事件的监听
	private static class Listener implements ApplicationListener<RSocketServerInitializedEvent> {

		private static final String PROPERTY_NAME = "local.rsocket.server.port";

		private final ConfigurableApplicationContext applicationContext;

		Listener(ConfigurableApplicationContext applicationContext) {
			this.applicationContext = applicationContext;
		}

		@Override
		public void onApplicationEvent(RSocketServerInitializedEvent event) {
			if (event.getServer().address() != null) {
				setPortProperty(this.applicationContext, event.getServer().address().getPort());
			}
		}

		private void setPortProperty(ApplicationContext context, int port) {
			if (context instanceof ConfigurableApplicationContext) {
				setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), port);
			}
			if (context.getParent() != null) {
				setPortProperty(context.getParent(), port);
			}
		}

		private void setPortProperty(ConfigurableEnvironment environment, int port) {
			MutablePropertySources sources = environment.getPropertySources();
			PropertySource<?> source = sources.get("server.ports");
			if (source == null) {
				source = new MapPropertySource("server.ports", new HashMap<>());
				sources.addFirst(source);
			}
			setPortProperty(port, source);
		}

		@SuppressWarnings("unchecked")
		private void setPortProperty(int port, PropertySource<?> source) {
			((Map<String, Object>) source.getSource()).put(PROPERTY_NAME, port);
		}

	}

}

所有的这些初始化类都没有进行启动服务的实质性操作,都是通过注册对象,埋点,后面invokeBeanFactoryPostProcessors才真正调用初始化方法,而且在项目启动之前

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
springboot vue-simple-uploader 是一个前后端分离的文件上传插件,在使用前需要进行相关配置和代码的编写。 首先,在后端部分,我们使用的是Spring Boot框架。需要导入spring-boot-starter-web依赖,并在配置文件中配置相关参数,例如设置文件上传临时目录、文件上传大小限制等。然后,我们需要编写一个处理文件上传请求的Controller类,使用@RequestBody注解接收前端传递的文件信息,并使用multipartFile.transferTo()方法保存文件到指定目录中。 在前端部分,我们使用的是Vue.js框架,并引入vue-simple-uploader插件。首先,我们需要安装该插件,可以使用npm安装或者直接引入插件的CDN地址。然后,在Vue实例中,我们可以通过配置uploaderOptions对象来进行插件的相关配置,例如设置上传的url、自定义headers、文件的最大数量和大小限制等。然后,在需要上传文件的组件中,我们可以通过引入uploader组件,并使用v-model绑定上传的文件列表。 通过上述配置和代码编写,我们就可以实现前后端分离的文件上传功能了。当用户选择上传的文件后,前端会将文件信息发送给后端,后端接收到请求后进行文件保存操作,并返回相应的结果给前端,例如文件的保存路径或者上传成功的提示信息。 总结一下,springboot vue-simple-uploader是一个支持前后端分离的文件上传插件,通过在后端配置文件上传参数和编写Controller类,在前端通过配置uploaderOptions对象和使用uploader组件,我们可以实现文件的上传和保存功能。这样,我们就可以方便地在Spring Boot和Vue.js项目中实现文件上传的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值