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

news2024/11/18 5:29:48

目录

  • 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.
 */
	@Override
	public 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及一些Resolve
		initServletBean();
	}

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 {
}
@Override
	protected 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.xml
			this.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 it
			wac = 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, etc
					if (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容器,用于构建相关Bean
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}

		// 未能通过构造函数注入,则尝试去ServletContext容器中查找有无WebApplicationContext
		if (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,则创建一个新的WebApplicationContext
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = 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.Class
		Class<?> 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");
		}

		// 通过反射方式实例化 contextClass
		ConfigurableWebApplicationContext 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 information
			if (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 #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		//留给子类,可以复写此方法,做一些初始化时候自己的实行
		postProcessWebApplicationContext(wac);
		//同样的 执行一些初始化类们  一般也是用不着
		applyInitializers(wac);
		// 加载配置文件及整合 parent 到 wac
		wac.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());
		}
	}

	@Override
	protected 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.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.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.HandlerFunctionAdapter


org.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.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.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) {
		// 策略接口长名称作为 key
		String 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 对象的属性变量为null
		this.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.否则只获取当前上下文自定义配置的handlerMapping
		else {
			try {
				// 获取名称为  handlerMapping 的 HandlerMapping bean 并记录到 handlerMappings
				HandlerMapping 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 对象记录到
			// handlerMappings
			this.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 {
        @Override
        public 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{
    
        @Bean
        public 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 {
    
        @Override
        public 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 类型的bean
				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.
			}
		}

		// 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();
	
    ...
        
	@Override
	public String getViewName(HttpServletRequest request) {
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
		return (this.prefix + transformPath(lookupPath) + this.suffix);
	}

	@Nullable
	protected 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.coloradmin.cn/o/2170356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型还能产生幻觉?一文讲清楚原理和解决方法

“像我们这样相信物理学的人都知道&#xff0c;过去、现在和未来之间的区别只是一种顽固执着的幻觉。换句话说&#xff0c;时间是一种幻觉。” ——波尔 幻觉&#xff0c;即一种看似真&#xff0c;实为假的感受。最近接触到了一个概念&#xff1a;大模型幻觉。有点好奇&#…

【Qt | QList 】QList<T> 容器详细介绍和例子代码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-09-26 …

python面向对象三大特性

面向对象 面向对象编程&#xff0c;是许多编程语言都支持的一种编程思想。 基于模板(类)去创建实体(对象)&#xff0c;使用对象去完成功能开发 面向对象的三大特性 封装继承多态 封装 封装表示&#xff1a;将现实世界事物的属性和行为&#xff0c;封装到类中&#xff0c;描…

打造高质量软件架构 - 9大质量属性

关注TechLead&#xff0c;复旦博士&#xff0c;分享云服务领域全维度开发技术。拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;复旦机器人智能实验室成员&#xff0c;国家级大学生赛事评审专家&#xff0c;发表多篇SCI核心期刊学术论文&#xff0c;阿里云认…

球体RCS计算 - 金属球的单站RCS【CST软件分析】

用金属球算RCS雷达散射截面可谓RCS的入门案例&#xff0c;本期用T和I两个求解器算单站RCS进行比较。 Step 1. RCS模板&#xff0c;T-solver&#xff0c; 频率0-5GHz&#xff0c;然后建模&#xff0c;半径10.16cm&#xff0c;可以算出来电尺寸在5GHz大概为三个波长&#xff0c;…

ROS理论与实践学习笔记——2 ROS通信机制之常用的命令

4.1 rosnode操作节点 rosnode&#xff1a;是用于获取节点信息的命令。 rosnode ping 测试到节点的连接状态 rosnode list 列出活动节点 rosnode info 打印节点信息 rosnode machine 列出指定设备上节点 rosnode kill 杀死某个节点 rosnode cleanup 清除不…

Python中的数据处理与分析:从基础到高级

在数据科学和数据分析领域&#xff0c;Python凭借其丰富的库和强大的生态系统&#xff0c;成为了最受欢迎的语言之一。本文将从基础到高级&#xff0c;详细介绍如何使用Python进行数据处理和分析&#xff0c;涵盖数据清洗、数据转换、数据可视化等多个方面。 1. 数据导入与导出…

华为 HCIP-Datacom H12-821 题库 (27)

&#x1f423;博客最下方微信公众号回复题库,领取题库和教学资源 &#x1f424;诚挚欢迎IT交流有兴趣的公众号回复交流群 &#x1f998;公众号会持续更新网络小知识&#x1f63c; 1. 如图&#xff0c;BGP 下有如下配置&#xff0c;下面哪些描述是错误的&#xff1f; A、Time…

Minderbinder:一款基于eBPF的进程安全测试工具

关于Minderbinder Minderbinder是一款基于eBPF的进程安全测试工具&#xff0c;在该工具的帮助下&#xff0c;广大研究人员可以通过注入噪声来测试目标进程的安全性。 Minderbinder 是一款使用 eBPF 将故障注入正在运行的进程的工具。当前版本的Minderbinder 可以通过将 kprobe…

动手学LLM(ch2)

2.1 理解词嵌入 深度神经网络模型&#xff0c;包括大型语言模型&#xff08;LLMs&#xff09;&#xff0c;无法直接处理原始文本&#xff0c;因为文本是分类数据&#xff0c;与神经网络的数学运算不兼容。为了达到这个目的&#xff0c;需要将单词转换为连续值向量。记住一句话…

“Y模型”—我在3年实操后的个人总结

一直想写一篇关于【需求分析】及【产品设计】方面个人最常用的一些方式方法&#xff0c;对于一些刚入行以及埋头苦干的同学来说&#xff0c;大多数情况都是粗放式凭感觉的分析产品。 因为自己也有过这样的阶段&#xff0c;深知这种思考方式的弊端。从用户场景/反馈到具象化的产…

Linux标准IO(四)-格式化I/O输入

C 库函数提供了 3 个格式化输入函数&#xff0c;包括&#xff1a;scanf()、fscanf()、sscanf()&#xff0c;其函数定义如下所示&#xff1a; #include <stdio.h> int scanf(const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int sscanf(c…

2023年金融科技建模大赛(初赛)开箱点评,多分类模型实战

原创作者Toby&#xff0c;文章来源公众号&#xff1a;python风控模型&#xff0c;2023年金融科技建模大赛&#xff08;初赛&#xff09;开箱点评 各位同学大家好&#xff0c;我是Toby老师。2023年金融科技建模大赛&#xff08;初赛&#xff09;从今年10月14日开始&#xff0c;…

最强反推更新!Joy Caption Alpha One详细测评、在线免费使用

免费教程网站&#xff1a;AI教程_深度学习入门指南 - 站长素材 (chinaz.com) 原文链接&#xff1a;最强反推更新&#xff01;Joy Caption Alpha One详细测评、在线免费使用 (chinaz.com) JoyCaption在一周前悄悄上线了最新版本Joycaption alpha one Joycaption alpha one免费在…

Python进阶:利用NotImplemented优化你的对象交互逻辑,让Python对象间的操作更加智能与灵活

推荐阅读&#xff1a;从混乱到清晰&#xff1a;用NotImplementedError重构你的Python代码&#xff0c;NotImplementedError如何助你打造更健壮的API NotImplemented 在Python中&#xff0c;NotImplemented并不是一个异常类&#xff0c;而是一个特殊的值&#xff0c;用于在二元…

linux桌面软件(wps)内嵌到其他窗口

程序测试环境是&#xff1a;slackware系统&#xff0c;属于linux系统&#xff0c;有桌面&#xff08;Xface Session&#xff09;。系统镜像是&#xff1a;slackware64-15.0-install-dvd.iso。qt、c代码实现。 程序功能&#xff1a;将已经打开的wps&#xff08;word、pdf等都可…

【优选算法】(第五篇)

目录 ⻓度最⼩的⼦数组&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 ⽆重复字符的最⻓⼦串&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 ⻓度最⼩的⼦数组&#xff08;medium&#xff09; 题目解析 1.题目链接&#xff1a;. - …

分割数组的最大值

题目链接 分割数组的最大值 题目描述 注意点 0 < nums[i] < 10^61 < nums.length < 10001 < k < min(50, nums.length) 解答思路 首先需要理解题意&#xff0c;需要将这个数组分成 k 个非空的连续子数组&#xff0c;找到划分组合中子数组和的最大值最小…

el-table+el-form实现表单校验和解决不垂直居中导致的问题

el-tableel-form实现表单校验 1.实现el-table的表单校验 关键点123 2.解决不垂直居中导致的问题 问题效果图 解决方案 .item-align-center {display: inline-flex; }

OJ在线评测系统 原生Java代码沙箱核心实现流程三 整理封装输出结果 拿到程序执行时间(stopwatch类) 和 运行内存

我们在之前的操作中已经拿到程序进行了编译和运行 接下来我们要将我们的结果输出 整理输出 // 4.收集整理输出结果 ExecuteCodeResponse executeCodeResponse new ExecuteCodeResponse(); ArrayList<String> outputList new ArrayList<>();for (ExecuteMessage…