SpringMVC之浅析组件初始化过程

简介: 在上篇的文章中简单的说了一下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组件的初始化过程到这里我们已经分析完了。下面我们总结一下这个初始化的过程。如下图所示:












相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
相关文章
|
机器学习/深度学习 运维 算法
梯度&散度&旋度&峰度&偏度你分得清楚吗?驻点&鞍点你分得清楚吗?曲率&斜率你分得清楚吗?
本文介绍了四种常见的物理量:加速度,速度,位移和力学功。详细介绍了它们的定义、计算以及在物理学和工程学领域中的应用。此外,本文还介绍了四种与物理量相关的概念:向量、标量、质量和密度。 数学,物理,机器学习领域常见概念区分
2234 0
|
域名解析 网络协议 算法
阿里云申请免费ssl证书(HTTPS) 保姆级教程
阿里云申请免费ssl证书(HTTPS) 保姆级教程,阿里云SSL免费证书在哪申请?一个阿里云账号一年可以申请20张免费SSL证书,很多同学找不到免费SSL的入口,阿小云来详细说下阿里云SSL证书免费申请入口链接以及免费SSL证书申请流程
875 0
|
分布式计算 Hadoop 测试技术
Hadoop【环境搭建 05】【hadoop-3.1.3 单机版基准测试 TestDFSIO + mrbench + nnbench + Terasort + sort 举例】
【4月更文挑战第1天】Hadoop【环境搭建 05】【hadoop-3.1.3 单机版基准测试 TestDFSIO + mrbench + nnbench + Terasort + sort 举例】
450 3
|
9月前
|
Ubuntu 计算机视觉 C++
Ubuntu系统下编译OpenCV4.8源码
通过上述步骤,你可以在Ubuntu系统上成功编译并安装OpenCV 4.8。这种方法不仅使你能够定制OpenCV的功能,还可以优化性能以满足特定需求。确保按照每一步进行操作,以避免常见的编译问题。
216 30
|
11月前
|
算法 Unix 数据库
Python编程入门:从基础到实战
本篇文章将带你进入Python编程的奇妙世界。我们将从最基础的概念开始,逐步深入,最后通过一个实际的项目案例,让你真正体验到Python编程的乐趣和实用性。无论你是编程新手,还是有一定基础的开发者,这篇文章都将为你提供有价值的信息和知识。让我们一起探索Python的世界吧!
|
算法
数据结构中的KMP算法及其改进算法
KMP算法通过引入部分匹配表,有效避免了重复计算,从而将字符串匹配的时间复杂度降低到O(m+n)。通过进一步优化next数组,KMP算法的效率得到了进一步提升。对于大规模字符串匹配问题,KMP算法及其改进算法提供了高效的解决方案,是计算机科学领域的经典算法之一。
351 3
|
11月前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
存储 缓存 NoSQL
深入解析Memcached:内部机制、存储结构及在大数据中的应用
深入解析Memcached:内部机制、存储结构及在大数据中的应用
|
运维 架构师 微服务
谷粒商城:如何通过笔记复盘实现事半功倍?
该博客文章讨论了如何通过笔记复盘来提高学习和工作效率。
谷粒商城:如何通过笔记复盘实现事半功倍?
|
敏捷开发 人工智能 Devops
开发必备:2024年整理10款超级好用的项目管理工具
整理10款适合企业研发团队使用的项目管理工具,包括(排名不分先后): 1.PingCode 智能化研发管理工具;2.Ones 大型企业研发管理平台;3.YesDev 研发项目协同管理工具;4.Teambition 阿里巴巴旗下团队协作工具;5.Jira Atlassian公司出品的项目与事务跟踪工具;6.Tower 专注50人以下团队的任务协作 ;7.TAPD 由腾讯出品的一站式敏捷研发协作云平台;8.码云Gitee DevOps一站式研发效能平台;9.禅道 国产开源的项目管理软件;10.Momday 由以色列提供的全新工作平台,内含项目管理模块。