SpringMVC之浅析组件初始化过程

本文涉及的产品
数据管理 DMS,安全协同 3个实例 3个月
推荐场景:
学生管理系统数据库
简介: 在上篇的文章中简单的说了一下SpringMVC请求大致处理的过程(点这里查看),说了一下SpringMVC为我们提供好的一些相关的组件。在这篇文章中我们接着看一下SpringMVC初始化这些组件的过程。

在上篇的文章中简单的说了一下SpringMVC请求大致处理的过程(点这里查看),说了一下SpringMVC为我们提供好的一些相关的组件。在这篇文章中我们接着看一下SpringMVC初始化这些组件的过程。

SpringMVC默认组件

在spring-webmvc.jar的中有一个 org/springframework/web/servlet/DispatcherServlet.properties的文件,在这个文件中指定了一下默认的组件,如下所示:
## 本地化解析器
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
## 主题解析器
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
## 处理器映射(2个)
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
## 处理器适配器(3个)
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
## 异常处理器(3个)
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
## 视图名称翻译器
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
## 视图解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
## 重定向数据管理器
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
有默认的组件也有可能会有自定义的组件,那么怎么初始化这些组件让DispatcherServlet可以使用这些组件呢?

组件的初始化过程

在DispatcherServlet中有这样的一个方法:initStrategies(ApplicationContext context) ,它的作用是通过反射机制查找并装配Spring容器中用户显示自定义的组件Bean,如果没有显示自定义的组件Bean,则装配默认的组件实例。这个方法被调用的时机是在容器启动的时候,我们先来看一下方法的调用链:

对于做web开发的同学们应该不会忘了Servlet的生命周期吧?我们看一下这个方法中有哪些内容:
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context); //初始化上传文件解析器(或者是多部分请求解析器)
		initLocaleResolver(context);//初始化本地化解析器
		initThemeResolver(context);//初始化主题解析器
		initHandlerMappings(context);//初始化处理器映射器
		initHandlerAdapters(context);//初始化处理器适配器
		initHandlerExceptionResolvers(context);//初始化处理器异常解析器
		initRequestToViewNameTranslator(context);//初始化请求到视图名翻译器
		initViewResolvers(context);//初始化视图解析器
		initFlashMapManager(context);//初始化重定向数据管理器
	}
在这里需要注意的是:从ApplicationContext中获取的Bean是已经初始化完毕的Bean。在上一篇文章中我们说了有些组件的Bean的名字是固定的像multipartResolver、localeResolver等等。那么这些Bean的name是在哪里定义的呢?我们在DispatcherServlet这个类中发现这样的一些常量:
	/** Well-known name for the MultipartResolver object in the bean factory for this namespace. */
	public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
	/** Well-known name for the LocaleResolver object in the bean factory for this namespace. */
	public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
	/** Well-known name for the ThemeResolver object in the bean factory for this namespace. */
	public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
	/**
	 * Well-known name for the HandlerMapping object in the bean factory for this namespace.
	 * Only used when "detectAllHandlerMappings" is turned off.
	 * @see #setDetectAllHandlerMappings
	 */
	public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
	/**
	 * Well-known name for the HandlerAdapter object in the bean factory for this namespace.
	 * Only used when "detectAllHandlerAdapters" is turned off.
	 * @see #setDetectAllHandlerAdapters
	 */
	public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
	/**
	 * Well-known name for the HandlerExceptionResolver object in the bean factory for this namespace.
	 * Only used when "detectAllHandlerExceptionResolvers" is turned off.
	 * @see #setDetectAllHandlerExceptionResolvers
	 */
	public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
	/**
	 * Well-known name for the RequestToViewNameTranslator object in the bean factory for this namespace.
	 */
	public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
	/**
	 * Well-known name for the ViewResolver object in the bean factory for this namespace.
	 * Only used when "detectAllViewResolvers" is turned off.
	 * @see #setDetectAllViewResolvers
	 */
	public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
	/**
	 * Well-known name for the FlashMapManager object in the bean factory for this namespace.
	 */
	public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
这些常量的值就是对应的Bean的名字。OK,接着我们就一步一步的分析这些方法。

initMultipartResolver

我们首先来看一下initMultipartResolver这个方法的内容:
	private void initMultipartResolver(ApplicationContext context) {
		try {
			//MULTIPART_RESOLVER_BEAN_NAME的值为multipartResolver
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Default is no multipart resolver.
			this.multipartResolver = null;
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
						"': no multipart request handling provided");
			}
		}
	}
这个方法的内容还是比较简单的,就是从上下文中获取bean name为multipartResolver,类型为MultipartResolver.class的bean,如果没有获取到到Bean的话则multipartResolver 为null。从这段代码中可以看出来MultipartResolver是没有默认的组件类的。

initLocaleResolver

我们继续看一下initLocaleResolver这个方法的内容,代码如下:
	private void initLocaleResolver(ApplicationContext context) {
		try {
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default.
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
		}
	}
从上面的代码中我们可以看到组装视图解析器的时候会先从上下文中获取name为localeResolver,类型为LocaleResolver.class的bean,如果没有获取到,则调用getDefaultStrategy这个方法获取默认的LocaleResolver。我们进入到getDefaultStrategy这个方法中看一下这个方法做了哪些事情:
	protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
		List<T> strategies = getDefaultStrategies(context, strategyInterface);
		if (strategies.size() != 1) {
			throw new BeanInitializationException(
					"DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
		}
		return strategies.get(0);
	}
从上面这段代码中我们可以看到会继续调用getDefaultStrategies这个方法来获取默认的LocaleResolver,并且LocaleResolver的数量只能有一个。我们来看一下getDefaultStrategies这个方法的内容:

	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		//获取全限定类名(包名+类名)
		String key = strategyInterface.getName();
		//根据获取到的类名取相应的值
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			//将获取到的值根据","进行分割
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			//创建指定长度的集合 避免空间浪费啊
			List<T> strategies = new ArrayList<T>(classNames.length);
			for (String className : classNames) {
				try {
					//反射获取相应的类对象
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					//实例化相应的类,并放到上下文中 (实例化的过程比较复杂,以后会放到Spring的系列中进行分析)
					Object strategy = createDefaultStrategy(context, clazz);
					//添加到集合中
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Error loading DispatcherServlet's default strategy class [" + className +
									"] for interface [" + key + "]: problem with class file or dependent class", err);
				}
			}
			return strategies;
		}
		else {
			return new LinkedList<T>();
		}
	}
defaultStrategies是其中比较重要的一个属性,我们看一下它是什么,在DispatcherServlet中的定义是这样的:
private static final Properties defaultStrategies;
从上面的代码中我们可以看出来defaultStrategies就是一个Properties。我们看一下为它赋值的代码:
	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
		}
	}
从上面的代码中我们发现为defaultStrategies赋值的过程是在静态代码块中进行的,我们知道静态代码块会在类初始化的时候执行。这里需要注意的时候加载资源的时候用的ClassPathResource这个类,还记得我们在Spring学习之资源管理器(Resource)中说的内容吗?如果忘记的话可以看一下这篇文件( Spring学习之资源管理器(Resource))。OK,到这里我们对initLocaleResolver的分析也就结束了。

initThemeResolver

主题的解析器的代码内容如下:
	private void initThemeResolver(ApplicationContext context) {
		try {
			this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default.
			this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME +
						"': using default [" + this.themeResolver + "]");
			}
		}
	}
从上面的代码中我们可以看出来它和本地化解析器(initLocaleResolver)的初始化过程是一样的,就不在具体分析了、

initHandlerMappings

接下来我们看一下处理器映射的初始化过程,代码如下:
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		//如果检测所有的处理器映射
		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			//从上下文中查找所有的HandlerMapping实现类
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				//这里只取固定的bean
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				//不可修改的list
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}
		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		//如果上面都没有取到HandlerMapping,则取默认的HandlerMapping
		//这里可能有个bug,如果DispatcherServlet.properties里没有的话,可能会出问题
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}
从上面的代码中我们可以看到如果detectAllHandlerMappings为true的话,则从上下文中查找所有类型为HandlerMapping的的bean,如果detectAllHandlerMappings为false的话,则从上下文中查找bean的名字为handlerMapping,类型为HandlerMapping的bean。如果这两步都取不到bean的话,则从DispatcherServlet.properties中查找默认的HandlerMapping类型的bean。即org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,和org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping。这里需要注意一下detectAllHandlerMappings的默认值为true,那么如果修改detectAllHandlerMappings的默认值呢?我们只需要这样配置一下就行了:
    <servlet>
        <servlet-name>spring-miscellaneous</servlet-name>
        <!-- SpringMVC 分发器 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>detectAllHandlerMappings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

initHandlerAdapters

我们接着看一下处理器适配器的初始化的过程,代码如下:
	private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;
		//检测所有的HandlerAdapter类型的处理器适配器
		if (this.detectAllHandlerAdapters) {
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				//这里为处理器适配器进行排序
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				//这里取bean名字为handlerAdapter,类型为HandlerAdapter的处理器适配器
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}
		//从DispatcherServlet.properties中取默认的处理器适配器
		// Ensure we have at least some HandlerAdapters, by registering
		// default HandlerAdapters if no other adapters are found.
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
			}
		}
	}
处理器适配器的初始化过程和处理器映射器的初始化过程基本上是一致的。detectAllHandlerAdapters值的设置和detectAllHandlerMappings是一样的、

initHandlerExceptionResolvers

异常处理器的初始化过程同上。
	private void initHandlerExceptionResolvers(ApplicationContext context) {
		this.handlerExceptionResolvers = null;
		//如果检测所有的HandlerExceptionResolver类型的bean
		if (this.detectAllHandlerExceptionResolvers) {
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
			//从上下文中查找HandlerExceptionResolver类型的bean
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
				// We keep HandlerExceptionResolvers in sorted order.
				//为取到的上下文中的bean排序
				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
			}
		}
		else {
			try {
				//取名字为handlerExceptionResolver,类型为HandlerExceptionResolver的bean
				HandlerExceptionResolver her =
						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
				//将异常处理器封装为不可修改的集合
				this.handlerExceptionResolvers = Collections.singletonList(her);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, no HandlerExceptionResolver is fine too.
			}
		}
		//从DispatcherServlet.properties中取默认的异常处理器
		// Ensure we have at least some HandlerExceptionResolvers, by registering
		// default HandlerExceptionResolvers if no other resolvers are found.
		if (this.handlerExceptionResolvers == null) {
			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}

initViewResolvers

视图解析的初始化过程和上面异常处理器的初始化过程是一样的,不再详述。
	private void initViewResolvers(ApplicationContext context) {
		this.viewResolvers = null;
		//如果检测所有的ViewResolver类型的bean
		if (this.detectAllViewResolvers) {
			// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
			//从上下文中查找ViewResolver类型的bean
			Map<String, ViewResolver> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
				// We keep ViewResolvers in sorted order.
				//为取到的上下文中的bean排序
				AnnotationAwareOrderComparator.sort(this.viewResolvers);
			}
		}
		else {
			try {
				//取名字为viewResolver,类型为ViewResolver的bean
				ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
				//将视图解析器封装为不可修改的集合
				this.viewResolvers = Collections.singletonList(vr);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default ViewResolver later.
			}
		}
		//取默认的视图解析器
		// Ensure we have at least one ViewResolver, by registering
		// a default ViewResolver if no other resolvers are found.
		if (this.viewResolvers == null) {
			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}

initRequestToViewNameTranslator

视图名称翻译器的初始化过程和本地化解析器的初始化过程是一样的,不再详述。
	private void initRequestToViewNameTranslator(ApplicationContext context) {
		try {
			//从上下文中取名字为viewNameTranslator 类型为RequestToViewNameTranslator的bean
			this.viewNameTranslator =
					context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default.
			//如果上下文中没有则取默认的视图名称翻译器
			this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
						REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default [" + this.viewNameTranslator +
						"]");
			}
		}
	}

initFlashMapManager

重定向数据管理器的初始化过程同上。
	private void initFlashMapManager(ApplicationContext context) {
		try {
			//从上下文中取名字为flashMapManager 类型为FlashMapManager的bean
			this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using FlashMapManager [" + this.flashMapManager + "]");
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default.
			//如果上下文中没有则取默认的重定向数据管理器
			this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate FlashMapManager with name '" +
						FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager + "]");
			}
		}
	}
SpringMVC组件的初始化过程到这里我们已经分析完了。下面我们总结一下这个初始化的过程。如下图所示:












相关实践学习
MySQL基础-学生管理系统数据库设计
本场景介绍如何使用DMS工具连接RDS,并使用DMS图形化工具创建数据库表。
相关文章
|
7月前
|
缓存 Java Spring
Spring循环依赖原理和Bean创建过程
Spring循环依赖原理和Bean创建过程
101 0
|
4月前
|
前端开发 Java 应用服务中间件
SpringMVC几种创建方式以及配置
SpringMVC几种创建方式以及配置
39 0
|
5月前
|
存储 Java Spring
Spring初始化加速的思路和方案问题之替换默认的Spring Bean初始化逻辑中的问题如何解决
Spring初始化加速的思路和方案问题之替换默认的Spring Bean初始化逻辑中的问题如何解决
|
5月前
|
存储 缓存 安全
Spring初始化加速的思路和方案问题之手动指定要异步初始化的bean中的问题如何解决
Spring初始化加速的思路和方案问题之手动指定要异步初始化的bean中的问题如何解决
|
5月前
|
Java Spring
Spring初始化加速的思路和方案问题之在BeanFactory#doGetBean方法中,栈状态的变化影响bean的初始化的问题如何解决
Spring初始化加速的思路和方案问题之在BeanFactory#doGetBean方法中,栈状态的变化影响bean的初始化的问题如何解决
|
5月前
|
XML Java 数据格式
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
|
7月前
|
Java
SpringBoot关闭过程中是如何销毁一个DisposableBean的?
SpringBoot关闭过程中是如何销毁一个DisposableBean的?
66 0
|
消息中间件 缓存 NoSQL
Spring 填充属性和初始化流程源码剖析及扩展实现
Spring 填充属性和初始化流程源码剖析及扩展实现
124 0
|
应用服务中间件 容器
Servlet生命周期、配置、创建时机及默认Servlet
Servlet生命周期、配置、创建时机及默认Servlet
235 0
|
Java
SpringBoot项目启动过程中做数据资源初始化的方式
SpringBoot项目启动过程中做数据资源初始化的方式
503 0