Spring源码学习:SpringMVC(2)DispatcherServlet初始化【子容器9大组件】

目录

  • DispatcherServlet类图
  • HttpServletBean#init
    • new ServletConfigPropertyValues()
  • FrameworkServlet#initServletBean
    • initWebApplicationContext
    • createWebApplicationContext
    • configureAndRefreshWebApplicationContext
  • DispatcherServlet内部9大组件初始化
    • 初识9大组件
    • DispatcherServlet#onRefresh();
      • DispatcherServlet.properties文件 (默认策略)
      • 获取默认策略解析器
      • initMultipartResolver
      • initLocaleResolver
      • initThemeResolver
      • initHandlerMappings
      • initHandlerAdapters
      • initHandlerExceptionResolvers
      • initRequestToViewNameTranslator
      • initViewResolvers
      • initFlashMapManager

DispatcherServlet类图

Web应用启动的最后一个步骤就是创建和初始化相关Servlet,我们配置了DispatcherServlet类前端控制器,前端控制器作为中央控制器是整个Web应用的核心,用于获取分发用户请求并返回响应。

在这里插入图片描述

通过类图可以看出DispatcherServlet类的间接父类实现了Servlet接口,因此其本质上依旧是一个Servlet

HttpServletBean#init

DispatcherServelt类的本质是Servlet,所以在Web应用部署到容器后进行Servlet初始化时会调用相关的init(ServletConfig)方法,因此,DispatchServlet类的初始化过程也由该方法开始:

(注意:DispatcherServelt 没有init方法,会走到父类HttpServletBean的init方法)

	/*** DispatcherServlet 初始化入口* Map config parameters onto bean properties of this servlet, and* invoke subclass initialization.* @throws ServletException if bean properties are invalid (or required* properties are missing), or if subclass initialization fails.*/@Overridepublic final void init() throws ServletException {// Set bean properties from init parameters./** 1.加载初始化参数,如:* <servlet>* 		<servlet-name>DispatcherServlet</servlet-name>* 		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>* 		<init-param>* 			<param-name>name</param-name>* 			<param-value>zimu</param-value>* 		</init-param>* 		<load-on-startup>1</load-on-startup>* 	</servlet>* 	这里会解析init-param列表。*/PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {// 2.将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());// 3.注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));// 4.空实现,留给子类覆盖initBeanWrapper(bw);// 5.属性注入bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}// Let subclasses do whatever initialization they like.// 【重点】6.子类负责实现,主要来初始化ApplicationContext及一些ResolveinitServletBean();}

new ServletConfigPropertyValues()

		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)throws ServletException {Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?new HashSet<>(requiredProperties) : null);// 获取 web.xml 中 DispatcherServlet 配置的 init-params 属性内容Enumeration<String> paramNames = config.getInitParameterNames();while (paramNames.hasMoreElements()) {String property = paramNames.nextElement();Object value = config.getInitParameter(property); 保存 init-params 属性 addPropertyValue(new PropertyValue(property, value));if (missingProps != null) {missingProps.remove(property);}}// Fail if we are still missing properties.if (!CollectionUtils.isEmpty(missingProps)) {throw new ServletException("Initialization from ServletConfig for servlet '" + config.getServletName() +"' failed; the following required properties were missing: " +StringUtils.collectionToDelimitedString(missingProps, ", "));}}

​ 该方法最主要的作用就是初始化init-param,如果我们没有配置任何init-param,那么该方法不会执行任何操作。从这里我们没有拿到有用的信息,但是在该方法结尾有initServletBean(),这是一个模板方法,可以由子类来实现,那么接下来我们就去看其子类FrameworkServlet中initServletBean

FrameworkServlet#initServletBean

继续查看 initServletBean()。父类 FrameworkServlet 覆盖了 HttpServletBean 中的 initServletBean 函数,如下:

protected void initServletBean() throws ServletException {
}
@Overrideprotected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");if (logger.isInfoEnabled()) {logger.info("Initializing Servlet '" + getServletName() + "'");}long startTime = System.currentTimeMillis();try {//【重点】,用于初始化子ApplicationContext对象,// 主要是用来加载<servlet/>对应的servletName-servlet.xml文件如:springMVC.xmlthis.webApplicationContext = initWebApplicationContext();// 空的模板方法initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}if (logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :"masked to prevent unsafe logging of potentially sensitive data";logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +"': request parameters and headers will be " + value);}//当我们看到这句日志,就能知道dispatcherServlet已经初始化完成,web子容器也就初始化完成了if (logger.isInfoEnabled()) {logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}}

​ 其中我们可以只有一个核心方法:initWebApplicationContext(),主要用来初始化springmvc子容器,其余都是一些日志相关的,关于 initWebApplicationContext 的实现是在 FrameworkServlet#initWebApplicationContext 中完成,下面我们就来看看其实现过程。

initWebApplicationContext

protected WebApplicationContext initWebApplicationContext() {/*获取由ContextLoaderListener创建的根IoC容器获取根IoC容器有两种方法,还可通过key直接获取*/WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;// 如果当前webApplicationContext不为null(context 实例在构造函数中被注入),则为其设置父容器if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parent/*如果当前Servelt存在一个WebApplicationContext即子IoC容器并且上文获取的根IoC容器存在,则将根IoC容器作为子IoC容器的父容器*/cwac.setParent(rootContext);}//配置并刷新当前的子IoC容器,用于构建相关BeanconfigureAndRefreshWebApplicationContext(cwac);}}}// 未能通过构造函数注入,则尝试去ServletContext容器中查找有无WebApplicationContextif (wac == null) {// No context instance was injected at construction time -> see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context id//如果当前Servlet不存在一个子IoC容器则去查找一下wac = findWebApplicationContext();}// 以上均无WebApplicationContext,则创建一个新的WebApplicationContextif (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);}// 刷新上下文容器,空的模板方法,留给子类实现if (!this.refreshEventReceived) {// Either the context is not a ConfigurableApplicationContext with refresh// support or the context injected at construction time had already been// refreshed -> trigger initial onRefresh manually here.synchronized (this.onRefreshMonitor) {onRefresh(wac);}}//我们是否需要吧我们的容器发布出去,作为ServletContext的一个属性值呢?默认值为true哦if (this.publishContext) {// Publish the context as a servlet context attribute.// 这个attr的key的默认值,就是FrameworkServlet.SERVLET_CONTEXT_PREFIX,保证了全局唯一性// 这么一来,我们的根容器、web子容器其实就都放进ServletContext上下文里了,拿取都非常的方便了。   只是我们一般拿这个容器的情况较少,一般都是拿跟容器,比如那个工具类就是获取根容器的~~~~~~String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;}

​ 通过函数名不难发现,该方法的主要作用同样是创建一个WebApplicationContext对象,即Ioc容器,不过前面讲过每个Web应用最多只能存在一个根IoC容器,这里创建的则是特定Servlet拥有的子IoC容器

上述逻辑如下:

  • 如果当前存在一个IOC容器(通过构造注入的),则为其绑定父子关系,并配置刷新当前子容器
  • 如果当前没有则先去ServletContext找一下(目前没啥用)
  • 之后如果当前不存在并且也没找到则去通过反射创建一个新的WebApplicationContext并配置刷新

为什么需要多个IOC容器呢?

答:父子容器类似于类的继承关系,子类可以访问父类中的成员变量,而父类不可访问子类的成员变量,同样的,子容器可以访问父容器中定义的Bean,但父容器无法访问子容器定义的Bean。

根IoC容器做为全局共享的IoC容器放入Web应用需要共享的Bean,而子IoC容器根据需求的不同,放入不同的Bean,这样能够做到隔离,保证系统的安全性。

createWebApplicationContext

	protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {return createWebApplicationContext((ApplicationContext) parent);}protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {// 获取 servlet 的初始化参数 contextClass,如果没有配置默认为 XMLWebApplicationContext.ClassClass<?> contextClass = getContextClass();// 校验必须是ConfigurableWebApplicationContext的子类if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + getServletName() +"': custom WebApplicationContext class [" + contextClass.getName() +"] is not of type ConfigurableWebApplicationContext");}// 通过反射方式实例化 contextClassConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);// 设置好父容器、Enviroment等等wac.setEnvironment(getEnvironment());wac.setParent(parent);// 获取 contextConfigLocation 属性,配置在 servlet 初始化参数中String configLocation = getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}// 这个是重点,如完善、初始化、刷新容器configureAndRefreshWebApplicationContext(wac);return wac;}

configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationif (this.contextId != null) {wac.setId(this.contextId);}else {// Generate default id...// 默认的id  这里面和contextpath有关了wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}// 关联到了Namespace/servlet等等wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());//添加了一个容器监听器  此监听器SourceFilteringListener在后面还会碰到wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}//留给子类,可以复写此方法,做一些初始化时候自己的实行postProcessWebApplicationContext(wac);//同样的 执行一些初始化类们  一般也是用不着applyInitializers(wac);// 加载配置文件及整合 parent 到 wacwac.refresh();}

​ 该方法用于创建一个子IoC容器并将根IoC容器做为其父容器,接着进行配置和刷新操作用于构造相关的Bean。至此,根IoC容器以及相关Servlet的子IoC容器已经配置完成,子容器中管理的Bean一般只被该Servlet使用,因此,其中管理的Bean一般是“局部”的,如SpringMVC中需要的各种重要组件,包括Controller、Interceptor、Converter、ExceptionResolver等。相关关系如下图所示:

在这里插入图片描述

DispatcherServlet内部9大组件初始化

了解DispatcherServlet之前,先回顾一下DispatcherServlet的内置组件及其作用。

初识9大组件

组件名作用
HandlerMapping是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。
HandlerAdapter从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情. (Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。)
HandlerExceptionResolver其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
ViewResolverViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
RequestToViewNameTranslatorViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
LocaleResolver解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
ThemeResolver用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。
MultipartResolver用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
FlashMapManagerFlashMap管理器,它能够存储并取回两次请求之间的FlashMap对象。FlashMap用于在请求重定向的情况下传递数据**

DispatcherServlet#onRefresh();

​ 当IoC子容器构造完成后调用了onRefresh()方法,该方法的调用与initServletBean()方法的调用相同,由父类调用但具体实现由子类覆盖,调用onRefresh()方法时将前文创建的IoC子容器作为参数传入,查看DispatcherServletBean类的onRefresh()方法源码如下:

		private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";// 默认策略配置文件private static final Properties defaultStrategies;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 '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());}}@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}protected void initStrategies(ApplicationContext context) {// 1.初始化 MultipartResolver(文件上传解析器)initMultipartResolver(context);// 2.初始化 LocaleResolver(区域信息解析器,与国际化相关)initLocaleResolver(context);// 3.初始化 ThemeResolver(主题解析器)initThemeResolver(context);// 4.初始化 HandlerMappings(处理器映射器)initHandlerMappings(context);// 5.初始化 HandlerAdapters(处理器适配器)initHandlerAdapters(context);// 6.初始化 HandlerExceptionResolver(异常解析器)initHandlerExceptionResolvers(context);// 7.初始化 RequestToViewNameTranslator(视图名转换器)initRequestToViewNameTranslator(context);// 8.初始化 ViewResolvers(视图解析器	)initViewResolvers(context);// 9.初始化 FlashMapManager(flashMap管理器)initFlashMapManager(context);}

DispatcherServlet.properties文件 (默认策略)

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

DispatcherServlet.properties里面记录了各种默认的加载策略。在下面的代码讲解中会涉及。

获取默认策略解析器

​ 这里先介绍下默认策略解析器的解析创建流程,下面9大组件基本都会用到

	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);}protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {// 策略接口长名称作为 keyString key = strategyInterface.getName();// 这里 defaultStrategies 是一个类静态属性,指向classpath resource 文件 DispatcherServlet.properties// 该行获取策略接口对应的实现类,是','分割的实现类的长名称String value = defaultStrategies.getProperty(key);if (value != null) {String[] classNames = StringUtils.commaDelimitedListToStringArray(value);List<T> strategies = new ArrayList<>(classNames.length);for (String className : classNames) {try {// 获取策略接口实现类Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());// 创建该策略接口实现类的对象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("Unresolvable class definition for DispatcherServlet's default strategy class [" +className + "] for interface [" + key + "]", err);}}return strategies;}else {return new LinkedList<>();}}

​ 逻辑也比较简单,传入了相应解析器接口,根据接口长路径作为key去文件中寻找,找到就反射创建即可

initMultipartResolver

​ 这一步顾名思义,就是配置多文件解析器。在 Spring 中,MultipartResolver 主要用来处理文件上传。默认情况下,Spring是没有 Multipart 处理的。如果想使用 Spring 的 Multipart ,则需要手动注入 Multipart 解析器,即MultipartResolver。这样Spring 会检查每个请求是否包含 Multipart,如果包含,那么 MultipartResolver 就会解析它。一般我们可以注入 CommonsMultipartResolver

​ 代码比较简单,从容器中获取,有就赋下值,没有就算了,没有就说明没有上传文件场景

	public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";private void initMultipartResolver(ApplicationContext context) {try {//从容器中获取beanName为multipartResolver的解析器,并保存到当前的成员变量multipartResolver上this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.multipartResolver);}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());}}//如果没有就没有吧catch (NoSuchBeanDefinitionException ex) {// Default is no multipart resolver.// 默认是没有配置multipartResolver的.this.multipartResolver = null;if (logger.isTraceEnabled()) {logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");}}}

配置CommonsMultipartResolver

<!-- 配置CommonsMultipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 设置上传文件大小上限 --><property name="maxUploadSize" value="10485760" />
</bean>

initLocaleResolver

​ 这里是初始化国际化解析器。逻辑和上面基本相同,不过如果容器中没有会根据默认配置文件创建默认解析器

	public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";private void initLocaleResolver(ApplicationContext context) {try {this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.localeResolver);}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.// 使用默认策略,利用反射创建对象this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);if (logger.isTraceEnabled()) {logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");}}}

一般情况下, localeResolver 有三种注入实例

  • AcceptHeaderLocaleResolver : 基于URL 参数的配置 。他会根据请求的URL后缀来判断国际化场景。比如 : “http://xxx?local=zh_CN”。local参数也可以是en_US。
  • CookieLocaleResolver : 基于Cookie的国际化配置。他是通过浏览器的 Cookies 设置取得Local对象。
  • SessionLocaleResolver :基于 Session 的配置,他通过验证用户会话中预置的属性来解析区域。常用的是根据用户本次会话过程中语言来决定语言种类。如果该会话属性不存在,则会根据 http的 accept-language 请求头确认国际化场景。

initThemeResolver

​ 在Web开发中经常会遇到通过主题Theme 来控制网页风格,这将进一步改善用户体验。简单的说,一个主题就是一组静态资源(比如样式表和图片),他们可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常类似。这里就不再过多赘述。

简单说一下三个常用的主题解析器

  • FixedThemeResolver :用于选择一个固定的主题
  • SessionThemeResolver :用于主题保存在用户的http session中
  • CookieThemeResolver :实现用户所选的主题,以cookie的形式存放在客户端的机器上
	public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";private void initThemeResolver(ApplicationContext context) {try {this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.themeResolver);}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);if (logger.isTraceEnabled()) {logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +"': using default [" + this.themeResolver.getClass().getSimpleName() + "]");}}}

initHandlerMappings

当客户端发出 Request 时, DispatcherServlet 会根据请求地址URL来获取指定的处理器方法来处理请求,URL->处理器方法的映射就维护在HandlerMapping中

在基于Spring mvc 的web应用程序中,我们可以为DispatcherServlet 提供多个 HandlerMapping 供其使用。DispatcherServlet 在选用 HandlerMapping 的过程中,将会根据我们所指定的一系列Handler 的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前HandlerMapping能够返回可用的Handler,DispatcherServlet 则是使用当前返回的Handler 进行Web请求的处理,而不再询问其他HandlerMapping,否则DispatcherServlet将按照各个HandlerMapping 的优先级进行询问,知道获取到一个可用的Handler 为止。

	public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";private void initHandlerMappings(ApplicationContext context) {// 初始化记录 HandlerMapping 对象的属性变量为nullthis.handlerMappings = null;// 1.根据属性detectAllHandlerMappings(默认为true)决定是启用所有的 HandlerMapping 对象,还是// 使用指定名称的 HandlerMapping 对象if (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.// 从容器及其祖先容器查找所有类型为 HandlerMapping 的 HandlerMapping 对象,记录到   handlerMappings 并排序Map<String, HandlerMapping> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());// We keep HandlerMappings in sorted order.// We keep HandlerMappings in sorted order.// 排序,关于这里的排序,可以参考   WebMvcConfigurationSupport 类中对各种 HandlerMapping bean进行定义时所使用的 order 属性AnnotationAwareOrderComparator.sort(this.handlerMappings);}}// 2.否则只获取当前上下文自定义配置的handlerMappingelse {try {// 获取名称为  handlerMapping 的 HandlerMapping bean 并记录到 handlerMappingsHandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);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.// 3.上述两步都未能获取到handlerMapping,则使用默认的handlerMapping,if (this.handlerMappings == null) {// 如果上面步骤从容器获取 HandlerMapping 失败,则使用缺省策略创建 HandlerMapping 对象记录到// handlerMappingsthis.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

detectAllHandlerMappings 参数用来 判断是否启用所有的HandlerMapping。可以通过这个参数来控制是使用指定的HandlerMapping,还是检索所有的HandlerMapping。如果检索所有的话,那么会对获取到的HandlerMapping进行优先级排序,不允许检索所有就获取上下文定义的,不允许检索所有以及上下文没有定义的话,那就走默认的

​ 关于几种HandlerMapping,我们这里来简单看看。

  • RequestMappingHandlerMapping :完成@Controller和@RequestMapping 的解析,并将解析保存。请求发送时与请求路径进行匹配对应找到合适的Handler。RequestMappingHandlerMapping 实现了 InitializingBean 接口,会在afterPropertiesSet 方法中调用解析。匹配调用则是在 DispatcherServlet doDispatch方法中的getHandler中调用了HandlerMapper中的getHandler中的getHandlerInternal方法。

  • BeanNameUrlHandlerMapping :以beanName 作为key值

    @Component("/mappingTest3")
    public class MappingTest3 implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().write("BeanNameUrlHandlerMapping test!");return null;}
    }
    
  • SimpleUrlHandlerMapping :基本逻辑是通过注入SimpleurlHandlerMapping 的mapping属性,mapping key为url, value为handler(beanName)。这里需要注意Controller必须要实现Controller接口。

    @Configuration
    public class MyConfig extends SimpleUrlHandlerMapping{@Beanpublic SimpleUrlHandlerMapping simpleUrlHandlerMapping(){SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();Properties properties = new Properties();properties.setProperty("simpleUrl","mappingTest2");simpleUrlHandlerMapping.setMappings(properties);//设置该handlermapping的优先级为1,否则会被默认的覆盖,导致访问无效simpleUrlHandlerMapping.setOrder(1);return simpleUrlHandlerMapping;}
    }
    
    @Component("mappingTest2")
    public class MappingTest2 implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {response.getWriter().write("SimpleUrlHandlerMapping test!");return null;}
    }
    

​ 在这个例子中,我们访问localhost/simpleUrl就会直接进入容器中名称为mappingTest2beanhandleRequest方法。

initHandlerAdapters

​ 初始化处理器适配器。这里使用了适配器模式来设计。

	private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;// 如果启用所有的HandlerAdapter。可以通过这个参数来控制是使用指定的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<>(matchingBeans.values());// We keep HandlerAdapters in sorted order.AnnotationAwareOrderComparator.sort(this.handlerAdapters);}}else {try {//没有启用则从Spring 容器中获取 beanName = handlerAdapter 并且类型是 HandlerAdapter 类型的beanHandlerAdapter 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.}}// Ensure we have at least some HandlerAdapters, by registering// default HandlerAdapters if no other adapters are found.// 如果还没有获取到适配器,则使用默认策略的适配器。从 DispatcherServlet.properties 中获取 org.springframework.web.servlet.HandlerAdapter 为key值的value加载到容器中。if (this.handlerAdapters == null) {this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

可以看到逻辑和上面的HandlerMapping 基本相同。

这里我们简单介绍一下三个 HandlerAdapter

  • HttpRequestHandlerAdapter : Http请求处理器适配器。
    HTTP请求处理适配器仅仅支持 HTTP 请求处理器的适配。他简单的将HTTP请求对象和响应对象传递给HTTP请求处理器的实现,他并不需要返回值。主要应用在基于 HTTP的远程调用实现上。
  • SimpleControllerHandlerAdapter : 简单控制器处理器适配器
    这个实现类将HTTP请求适配到了一个控制器的实现进行处理。这里的控制器的实现是一个简单的控制器接口的 实现。简单控制器处理器适配器被设计成一个框架类的实现,不需要被改写,客户化的业务逻辑通常在控制器接口的实现类中实现的。
  • RequestMappingHandlerAdapter : 请求映射处理器适配器
    这个实现类需要通过注解方法映射和注解方法处理器协同工作。它通过解析声明在注解控制器的请求映射信息来解析相应的处理器方法来处理当前的http请求,在处理的过程中,他通过反射来发现探测处理器方法的参数,调用处理器方法,并映射返回值到模型和控制器对象。最后返回模型和控制器对象给作为主控制器的派遣器Servlet。

initHandlerExceptionResolvers

​ 基于 HandlerExceptionResolver接口的异常处理,使用这种方式只需要实现 org.springframework.web.servlet.HandlerExceptionResolver中的resolveException` 方法,该方法返回一个 ModelAndView 对象,在方法内部对异常的类型进行判断,然后尝试生成对应的 ModelAndView对象,如果该方法返回了null。则Spring会继续寻找其他的实现了HandlerExceptionResolver 接口的bean,直至找到一个可以返回ModelAndView 的 HandlerExceptionResolver 。

代码逻辑相同,不再赘述。

private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// We keep HandlerExceptionResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}else {try {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.}}// 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.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

initRequestToViewNameTranslator

​ 当Controller处理器方法没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接放Response 的输出流中写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过 Spring 定义的 org.springframework.web.servlet.RequestToViewNameTranslator 接口的 getViewName 方法实现的。Spring默认提供了一个实现 org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator 可以看一下其支持的属性

public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {private static final String SLASH = "/";private String prefix = "";private String suffix = "";private String separator = SLASH;private boolean stripLeadingSlash = true;private boolean stripTrailingSlash = true;private boolean stripExtension = true;private UrlPathHelper urlPathHelper = new UrlPathHelper();...@Overridepublic String getViewName(HttpServletRequest request) {String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);return (this.prefix + transformPath(lookupPath) + this.suffix);}@Nullableprotected String transformPath(String lookupPath) {String path = lookupPath;if (this.stripLeadingSlash && path.startsWith(SLASH)) {path = path.substring(1);}if (this.stripTrailingSlash && path.endsWith(SLASH)) {path = path.substring(0, path.length() - 1);}if (this.stripExtension) {path = StringUtils.stripFilenameExtension(path);}if (!SLASH.equals(this.separator)) {path = StringUtils.replace(path, SLASH, this.separator);}return path;}...
}
private void initRequestToViewNameTranslator(ApplicationContext context) {try {this.viewNameTranslator =context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.viewNameTranslator);}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +"': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");}}}

initViewResolvers

​ 在 Spring mvc 中。当 Controller 将请求处理结果放入到 ModelAndView 中以后,DispatcherServlet 会根据 ModelAndView 选择合适的视图进行渲染。在org.springframework.web.servlet.ViewResolver#resolveViewName 方法中,通过 viewName 创建合适类型的View。

代码逻辑相同,不再赘述。

private void initViewResolvers(ApplicationContext context) {this.viewResolvers = null;if (this.detectAllViewResolvers) {// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.Map<String, ViewResolver> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.viewResolvers = new ArrayList<>(matchingBeans.values());// We keep ViewResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.viewResolvers);}}else {try {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.isTraceEnabled()) {logger.trace("No ViewResolvers declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

initFlashMapManager

​ FlashMapManager是用于检索和保存FlashMap实例的策略接口。

Spring3.1之后引入了一个叫做Flash Attribute的功能,主要就是为了解决表单重复提交数据的问题,应用POST/Redirect/GET(PRG)模式来防止重复提交数据(表单通过HTTP POST请求提交之后,用户在服务器端返回之前刷新了响应的页面,会导致原始的表单内容重复提交,可能会导致一些难以预料的结果)。所以采用重定向请求到成功页面,这样用户进行刷新不会进行提交表单,而是加载新的GET请求。但是重定向会引入无法传递请求参数和属性的问题,所以Spring的Flash Attribute就是为了请求重定向之前,解决临时存储的问题。

FlashMap为一个请求提供方法用于存储在另一个请求中使用的数据属性。

private void initFlashMapManager(ApplicationContext context) {try {this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug("Detected " + this.flashMapManager);}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");}}}

以上:内容部分参考:《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎各位大佬指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/433590.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新手如何学习OpenStack?

引言 在当今云计算的浪潮中&#xff0c;OpenStack作为开源云计算平台的佼佼者&#xff0c;备受关注。它不仅为数据中心提供了灵活的资源管理方案&#xff0c;还极大地推动了云计算技术的普及和发展。然而&#xff0c;对于初学者而言&#xff0c;OpenStack的复杂性和庞大的组件体…

Apache Log4j2 远程代码执行漏洞(CVE-2021-44228)

漏洞描述&#xff1a; 当用户输入信息时&#xff0c;应用程序中的log4j 2组件会将信息记录到日志中 假如日志中包含有语句${jndi:ldap:attacker:1099/exp}&#xff0c;log4j就会去解析该信息&#xff0c;通过jndi的lookup() 方法去解析该url&#xff1a;ldap:attacker:1099/e…

1panel申请https/ssl证书自动续期

参考教程 https://hin.cool/posts/sslfor1panel.html #Acme 账户 #1panel.腾讯云dns账号 这里有一步不需要参考,腾讯云dns账号,就是子帐号授权 直接控制台搜索 访问管理 创建用户 授权搜索dns,选择第一个 点击用户名,去掉AdministratorAccess权限 5.点击api密钥生成即可…

VMware搭建DVWA靶场

目录 1.安装phpstudy 2.搭建DVWA 本次搭建基于VMware16的win7系统 1.安装phpstudy 下载windows版本&#xff1a;小皮面板-好用、安全、稳定的Linux服务器面板&#xff01; 安装后先开启mysql再开启apache&#xff0c;遇到mysql启动不了的情况&#xff0c;最后重装了phpstud…

自动驾驶电车难题的康德式道德决策

摘 要 自动驾驶电车难题是检验人工智能伦理可行性的一块试金石 , 面对不同情境 , 其计算程序既要作出可决定的、 内在一致的判断决策 , 又要与人类的普遍道德常识相兼容 。 康德义务论给出了具有普遍性与一致性的理论框架。 自动驾驶电车的道德决策可视为由计算程序执行的第…

Redis篇(数据类型)

目录 讲解一&#xff1a;简介 讲解二&#xff1a;常用 一、String类型 1. 简介 2. 常见命令 3. Key结构 4. 操作String 5. 实例 二、Hash类型 1. 简介 2. 常见命令 3. 3操作hash 4. 实例 三、List类型 1. 简介 2. 特征 3. 应用场景 4. 常见命令 5. 操作list …

嘻哈纸片人仿手绘插画!FLUX一键生成方法!

​ ​ ​ 如何生成这种嘻哈纸片人的仿手绘插画&#xff1f; 只需1个lora&#xff0c;3个步骤&#xff01; 接下来我们来具体的说一下操作方法以及lora使用注意 嘻哈纸片人lora 基于FLUX模型训练 在线使用&下载地址&#xff1a; https://www.liblib.art/modelinfo/53ee…

计算机毕业设计之:微信小程序的校园闲置物品交易平台(源码+文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

使用 Paramiko 实现 SSH 远程连接和命令执行

使用 Paramiko 实现 SSH 远程连接和命令执行 每当灶火燃起&#xff0c;香气弥漫&#xff0c;熟悉的味道植入记忆深处&#xff0c;家&#xff0c;才获得完整的意义。万户千家&#xff0c;味道迥异&#xff0c;但幸福的滋味&#xff0c;却何其相同。 ——《风味人间》 在现代网络…

Kubernetes从零到精通(15-安全)

目录 一、Kubernetes API访问控制 1.传输安全(Transport Security) 2.认证(Authentication) 2.1 认证方式 2.2 ServiceAccount和普通用户的区别 2.3 ServiceAccount管理方式 自动ServiceAccount示例 手动ServiceAccount示例 3.鉴权 (Authorization) 3.1鉴权方式 3.2 …

TOF系列—深度图滤波

本篇文章主要介绍TOF深度图的后处理&#xff0c;鉴于自身水平所限&#xff0c;如有错误&#xff0c;欢迎批评指正。&#xff08;欢迎进Q群交流&#xff1a;874653199&#xff09; TOF由于其本身的特性&#xff0c;导致其所获得的深度图存在以下问题&#xff1a; 1.对空的地方存…

Kafka学习笔记(一)Kafka基准测试、幂等性和事务、Java编程操作Kafka

文章目录 前言4 Kafka基准测试4.1 基于1个分区1个副本的基准测试4.2 基于3个分区1个副本的基准测试4.3 基于1个分区3个副本的基准测试5 Java编程操作Kafka5.1 引入依赖5.2 向Kafka发送消息5.3 从Kafka消费消息5.4 异步使用带有回调函数的生产消息6 幂等性6.1 幂等性介绍6.2 Kaf…

搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(一)-概述

简介 此前的专栏介绍onesearch1.0和2.0&#xff0c;详情参看4 参考资料&#xff0c;本文解释onesearch 3.0&#xff0c;从Elasticsearch6升级到Elasticsearch8代码实现 &#xff0c;Elasticsearch8 废弃了high rest client&#xff0c;使用新的ElasticsearchClient&#xff0c;…

AI驱动的智能运维:行业案例与挑战解析

华为、蚂蚁、字节跳动如何引领智能运维&#xff1f; ©作者|潇潇 来源|神州问学 引言 OpenAI 发布的 ChatGPT 就像是打开了潘多拉的魔盒&#xff0c;释放出了生产环境中的大语言模型&#xff08;LLMs&#xff09;。一些新的概念&#xff1a;“大语言模型运维 (LLMOps)”…

统信服务器操作系统进入【单用户模式】

统信服务器操作系统D版、E版、A版进入单用户模式的方式。 文章目录 前言一、问题现象二、问题原因三、解决方案1. D版问题解决方案2. E版及A版问题解决方案前言 D版又称企业版、E版又称欧拉版、A版又称龙蜥版。 单用户模式主要是在 grub2 引导时编辑内核引导,一般用于修改用…

mysql索引结构操作(主键/唯一键/普通索引的创建/查询/删除),复合索引介绍(索引覆盖,索引最左匹配原则)

目录 索引操作 创建索引 主键索引 介绍 在创建表时设置主键 创建表后添加主键 唯一键索引 介绍 在创建表时设置唯一键 创建表后添加唯一键 普通索引 在创建表时指定某列为索引 创建表后添加普通索引 自主命名索引 索引创建原则 哪些列适合创建索引 不适合作为…

【Linux:共享内存】

共享内存的概念&#xff1a; 操作系统通过页表将共享内存的起始虚拟地址映射到当前进程的地址空间中共享内存是由需要通信的双方进程之一来创建但该资源并不属于创建它的进程&#xff0c;而属于操作系统 共享内存可以在系统中存在多份&#xff0c;供不同个数&#xff0c;不同进…

14 vue3之内置组件trastion全系列

前置知识 Vue 提供了 transition 的封装组件&#xff0c;在下列情形中&#xff0c;可以给任何元素和组件添加进入/离开过渡: 条件渲染 (使用 v-if)条件展示 (使用 v-show)动态组件组件根节点 自定义 transition 过度效果&#xff0c;你需要对transition组件的name属性自定义。…

基于BeagleBone Black的网页LED控制功能(flask+gpiod)

目录 项目介绍硬件介绍项目设计开发环境功能实现控制LED外设构建Webserver 功能展示项目总结 &#x1f449; 【Funpack3-5】基于BeagleBone Black的网页LED控制功能 &#x1f449; Github: EmbeddedCamerata/BBB_led_flask_web_control 项目介绍 基于 BeagleBoard Black 开发板…

ChatGPT 推出“Auto”自动模式:智能匹配你的需求

OpenAI 最近为 ChatGPT 带来了一项新功能——“Auto”自动模式&#xff0c;这一更新让所有用户无论使用哪种设备都能享受到更加个性化的体验。简单来说&#xff0c;当你选择 Auto 模式后&#xff0c;ChatGPT 会根据你输入的提示词复杂程度&#xff0c;自动为你挑选最适合的AI模…