SpringMVC源码:DispatcherServlet初始化流程

news2024/11/26 18:50:29

参考资料:

《SpringMVC源码解析系列》

《SpringMVC源码分析》

《Spring MVC源码》​​​​​​​

写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。

目录

前文

        1、简介

        2、SpringMVC的配置

源码分析

一、Servlet

        1、FrameworkServlet.initWebApplicationContext

         2、FrameworkServlet.createWebApplicationContext

二、DispatcherServlet

         1、DispatcherServlet.onRefresh

        2、DispatcherServlet.initHandlerMappings

        3、DispatcherServlet.initHandlerAdapters

补充

        Spring与SpringMVC的父子容器

        默认HandlerMapping的初始化


前文

        1、简介

        MVC结构我们都知道,将模型、视图与控制器拆分实现分层。SrpingMVC采用类似的结构。

        不同的地方在于,SpringMVC的控制器多了一个,即前端控制器。前端控制器的作用在于将不同的请求根据地址转给不同的控制器进行处理,并对返回的模型选择相应的视图进行渲染。

        2、SpringMVC的配置

        SpringMVC通过servlet实现注册,通过配置load-on-startup为1使其在tomcat启动时执行。

  <servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <init-param>
             <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springContext.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
      <servlet-name>dispatcher</servlet-name>
      <url-pattern>/</url-pattern>
  </servlet-mapping>
  <context-param>

源码分析

        SpringMVC的分发是由DispatcherServlet这个类实现的,根据类名我们可以确定这是个Servlet的实现类,下面是它的继承关系图。

         对于servlet我们知道会在首次调用时调用init方法初始化(这里我们配置了启动时初始化),并使用service方法为request请求服务。

        我们将DispatcherServlet的初始化过程整理如下:

         initServletBean没什么具体操作,因此这里从initServletBean开始看起。

一、Servlet

        1、FrameworkServlet.initWebApplicationContext

          这个方法主要做了以下几步:

  1. 从ServletContext中获取第一步中创建的SpringMVC根上下文,为下面做准备
  2. 根据init-param中的contextAttribute属性值从ServletContext查找是否存在上下文对象
  3. 以XmlWebApplicationContext作为Class类型创建上下文对象,设置父类上下文,并完成刷新
  4. 执行子类扩展方法onRefresh,在DispatcherServlet内初始化所有web相关组件
  5. 将servlet子上下文对象发布到ServletContext
	protected WebApplicationContext initWebApplicationContext() {
        // 获取ServletContext(全局唯一)获取与之关联的WebApplicationContext
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
        // 如果SpringMVC的servlet子上下文对象不为空,则设置根上下文为其父类上下文,然后刷新
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
            // 根据init-param配置的属性名称从ServletContext查找SpringMVC的servlet子上下文
			wac = findWebApplicationContext();
		}
		if (wac == null) {
            // 若还为空,则创建一个新的上下文对象并刷新
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
            // 子类自定义对servlet子上下文后续操作,在DispatcherServlet中实现
			onRefresh(wac);
		}
        // 将新创建的上下文注册到ServletContext
		if (this.publishContext) {
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

         在findWebApplicationContext方法中会根据web.xml中的配置判断是否从ServletContext中查找上下文对象,配置了的话就直接获取。

	protected WebApplicationContext findWebApplicationContext() {
        // 根据init-param中的contextAttribute属性值从ServletContext查找是否存在上下文对象
		String attrName = getContextAttribute();
		if (attrName == null) {
			return null;
		}
        // 通过现有的WebApplicationContext获取已存在的上下文对象
		WebApplicationContext wac =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
		if (wac == null) {
			throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
		}
		return wac;
	}

         2、FrameworkServlet.createWebApplicationContext

        createWebApplicationContext方法创建SpringMVC的应用上下文,并调用configureAndRefreshWebApplicationContext方法进行上下文的刷新。

	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        // 默认XmlWebApplicationContext
		Class<?> contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		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");
		}
        // 反射创建applicationContext
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
        // 设置parent(ContextLoadListener中创建的applicationContext)
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());
        //refresh()
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

        configureAndRefreshWebApplicationContext会先将新创建的这个上下文与servletcontext绑定,然后进行刷新操作,这个刷新操作就和我们之前介绍的IOC的执行过程一样,这部分内容可以看这里《Spring IOC:从ContextLoaderListener到AbstractApplicationContext》

	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {

				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}
        //将ServletContext和ServletConfig都绑定到servlet子上下文对象中
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));


		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
        //最后初始化子容器,和上面根容器初始化一样
		wac.refresh();
	}

        到这一步为止,我们一共创建了3个context,分别为:

  • ServletContext:全局唯一,tomcat启动该web项目了创建
  • Spring的context:由servletcontext的监听器contextloaderlistener创建,默认读取servletcontext.xml配置,具体内容参考我们之前介绍的IOC的源码实现。同时该cntext与servletcontext互相关联,该context被注册到servletcontext中以webapplicationcontext属性存在。
  • SpringMVC的context:SpringMVC初始化DispacherServlet时创建的上下文,该context为Spring的context的子容器,可以访问Spring容器中的内容(子容器可以访问父容器中的内容,但是反过来不行)。

在这里插入图片描述

        经过上面两个步骤,DispatcherServlet父类中的流程已经全部走完,这几个步骤主要的功能就是生成了SpringMVC容器,并将其与ServletContext、Spring容器相关联。

        这个SpringMVC容器会和Spring容器一样扫描web.xml中配置文件,例如如下配置就会扫描spring-mvc.xml并生成该容器的beanFactory,生成注册在其中的bean。

	<servlet>
		<servlet-name>SpringMvcDispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.org.springframework.web.servlet</servlet-class>
		<async-supported>true</async-supported>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath*:spring-mvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>SpringMvcDispatcher</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

二、DispatcherServlet

         1、DispatcherServlet.onRefresh

        onRefresh将会执行initStrategies方法初始化九大组件。这些组件都是整个SpringMVC运行的基础。其中最重要的就是initHandlerMappings、initHandlerAdapters,下面我们会着重进行介绍。

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

        // 初始化九大组件
    protected void initStrategies(ApplicationContext context) {
        //初始化文件上传解析对象组件MultipartResolver
        //该组件主要用于支持springMVC的文件上传
        initMultipartResolver(context);
        //初始化国际化资源解析器LocaleResolver
        initLocaleResolver(context);
        //初始化主题资源解析器 一个主题就是一组静态资源  其包含主题资源和主题解析器
        initThemeResolver(context);
        //初始化处理器映射器 该组件主要是根据对应的请求 获取对应的处理逻辑组件Handler
        //(说白了就是我们编写的Controller的方法)
        initHandlerMappings(context);
        //初始化处理器适配器,因为我们编写的Handler 可能是不同的了类型,比如简单类型,http类型等
        //为了使请求可能被不同的handler处理统一起来 这里会使用适配模式提供统一的处理请求的返回结果的接口
        //所有业务逻辑执行是在该处理器适配器中执行的
        //比如:我们国家的电压220V 但是和手机,洗衣机需要的电压,这里充电的时候会有不同的适配器将其电压转换为
        //合适终端的电压,此即为处理器适配器
        initHandlerAdapters(context);
        //初始化 处理异常的组件,该组件有一个方法resolveException() 该方法会对请求处理过程中出现异常的情况
        //进行处理 根据不同的异常返回不同的异常页面
        initHandlerExceptionResolvers(context);
        //当Controller处理方法并没有返回视图的时候,且没有在reponse存放数据(往reponse中存放数据大多数是下载功能)
        //该组件按照其getViewName()设置视图 从而返回
        initRequestToViewNameTranslator(context);
        //初始化视图解析器,当请求被处理放入ModelAndView.该组件会选择合适的视图去进行渲染
        initViewResolvers(context);
        //初始化FlashMapManager 用于在重定向的时候 还能继续使用数据(一般情况重定向请求后请求参数会丢失)
        initFlashMapManager(context);
    }

        2、DispatcherServlet.initHandlerMappings

        该方法负责进行HandlerMapping接口实现类的加载。HandlerMapping接口主要用来提供request 请求对象和​​​​​​​Handler对象 映射关系的接口。所谓request对象比如我们web应用中的http 请求,Handler对象则指的是对应rquest请求的相关处理逻辑。

        首先判断是否查找所有HandlerMapping(默认为true)。如果为是,则从上下文(包括所有父上下文)中查询类型为HandlerMapping的Bean,并进行排序。如果为否,则从上下文中按指定名称去寻找。如果都没有找到,提供一个默认的实现。这个默认实现从DispatcherServlet同级目录的DispatcherServlet.properties中加载得。

	/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
	private boolean detectAllHandlerMappings = true;

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
        // 是否查找所有HandlerMapping标识
		if (this.detectAllHandlerMappings) {
            // 从上下文(包含所有父上下文)中查找HandlerMapping类型的Bean,是下文中的RequestMappingHandlerMapping,其中包含URL和Mapping的映射Map
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
                //将从容器中查找出来的HandlerMapping加入到DispatcherServlet的handlerMappings属性中
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
                // 根据指定名称获取HandlerMapping对象
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
			}
		}
        // 如果容器中没有实现了handlerMapping接口的类,则创建默认的handlerMapping实现类
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

        3、DispatcherServlet.initHandlerAdapters

        上一节中通过initHandlerMappings已经将request通过HandlerMapping(处理器映射器)将请求映射到了对应的Handler上,这一步就需要考虑如何解析并执行该handler对象。

        这里Spring使用了适配器模式,主要是因为handler对象有两种不同的类型。

        (1)以实现了Controller接口的Handler类

public class DemoController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("进入DemoController方法执行处理逻辑");
        return new ModelAndView("demo");
    }
}

        (2)以@RequestMapping注解修饰的HandlerMethod对象

@RequestMapping(value = "/book/ListPage",method = RequestMethod.POST)
@ResponseBody
public String getBookPage(@RequestBody BookQuery bookQuery){
       return success(pageTotal(pageInfo));
}

        其他还有实现Servlet的实例,HandlerFunction实例、HttpRequestHandler实例等,不同的实例对象调用时走不同的方法,为了能将不同的方法转换成统一的调用形式,这里使用了适配器模式,将各个实例的方法调用包装到HandlerAdapter统一调用。

        initHandlerAdapters的过程和initHandlerMappings类似,也是先从上下文(包括所有父上下文)中查询类型为HandlerAdapter的Bean,并进行排序。如果为否,则从上下文中按指定名称去寻找。如果都没有找到,提供一个默认的实现。

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;
    if (this.detectAllHandlerAdapters) {
        Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    } else {
        try {
            HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        } catch (NoSuchBeanDefinitionException var3) {
            ;
        }
    }

    if (this.handlerAdapters == null) {
        this.handlerAdapters = this.getDefaultStrategies(context, HandlerAdapter.class);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("No HandlerAdapters found in servlet '" + this.getServletName() + "': using default");
        }
    }

}

               

        整个SpringMVC的初始化流程就大致介绍了一遍,主要完成了SpringMVC容器的构建并加载了初始化相关的组件如HandlerMapping、HandlerAdapter等,下篇文章我们会介绍下HandlerMapping与HandlerAdapter的实现类。

补充

        Spring与SpringMVC的父子容器

        1、方便划分框架边界

        在service层我们一般使用spring框架来管理, 而在web层则有多种选择,如spring mvc、struts等。因此,通常对于web层我们会使用单独的配置文件。

        如果现在我们想把web层从SpringMVC替换成Struts,那么只需要将SpringMVC替换成Struts的配置文件Struts.xml即可,而Spring的配置文件不需要改变。

        2、是否可以把所有类都通过Spring父容器来管理?

        这里以spring-context-4.3.29为例,initHandlerMethods的源码如下

    private boolean detectHandlerMethodsInAncestorContexts = false;
	protected void initHandlerMethods() { 
        // 获取所有的beanName
		String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));
        // 其余省略
	}

        这里获取所有的beanName有2种方法,我们分别来看下:

    //BeanFactoryUtils.java
	public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class<?> type) {
		Assert.notNull(lbf, "ListableBeanFactory must not be null");
		String[] result = lbf.getBeanNamesForType(type);
		if (lbf instanceof HierarchicalBeanFactory) {
			HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
			if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
                // 获取父BeanFactory中结果
				String[] parentResult = beanNamesForTypeIncludingAncestors(
						(ListableBeanFactory) hbf.getParentBeanFactory(), type);
				result = mergeNamesWithParent(result, parentResult, hbf);
			}
		}
		return result;
	}


    //DefaultListableBeanFactory.java
	@Override
	public String[] getBeanNamesForType(Class<?> type) {
		return getBeanNamesForType(type, true, true);
	}

	@Override
	public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
		if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
			return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
		}
        // 从当前容器中获取结果
		Map<Class<?>, String[]> cache =
				(includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
		String[] resolvedBeanNames = cache.get(type);
		if (resolvedBeanNames != null) {
			return resolvedBeanNames;
		}
		resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
		if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
			cache.put(type, resolvedBeanNames);
		}
		return resolvedBeanNames;
	}

        从上面的结果可以看出,SpringMVC中的initHandlerMethods默认是从当前容器中查询beanName的,如果将所有的bean都交给Spring容器来管理,可能导致无法处理相应的请求。

        3、是否可以把我们所需的类都放入SpringMVC子容器里面来管理

        可以,但不推荐。如果项目里有用到事务或AOP则需要把这部分配置需要放到SpringMVC子容器的配置文件来,不然一部分内容在子容器和一部分内容在父容器,可能就会导致你的事物或者AOP不生效。

        4、是否可以同时通过两个容器同时来管理所有的类?

        首先两个容器里面都放一份一样的对象,造成了内存浪费。另外子容器会覆盖父容器加载,本来可能父容器配置了事物生成的是代理对象,但是被子容器一覆盖,又成了原生对象。这就导致了你的事物不起作用了。

        默认HandlerMapping的初始化

        在DispatcherServlet.initHandlerMappings中,如果容器中没有配置别的HandlerMapping的话则创建默认的实现类。方法是读取DispatcherServlet.properties中的配置,并调用createBean创建。

	private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
	private static final Properties defaultStrategies;

	static {

        // 获取默认配置DispatcherServlet.properties中的属性
		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());
		}
	}

	@SuppressWarnings("unchecked")
	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
        // 获取默认配置DispatcherServlet.properties中的属性
		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());
					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>();
		}
	}

    	protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
        // 对传入进来的类调用createBean方法进行创建
		return context.getAutowireCapableBeanFactory().createBean(clazz);
	}

         DispatcherServlet.properties的内容如下,我们可以看到HandlerMapping有2个实现类,BeanNameUrlHandlerMapping与DefaultAnnotationHandlerMapping,分别对应xml注册方式与注解方式的分发模式。

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.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

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

         再补充下DefaultAnnotationHandlerMapping的初始化。

​​

       

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

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

相关文章

Java实现简单KV数据库

用Java实现一个简单的KV数据库 开发思路&#xff1a; 用map存储数据&#xff0c;再用一个List记录操作日志&#xff0c;开一个新线程将List中的操作写入日志文件中&#xff0c;再开一个线程用于网络IO服务接收客户端的命令&#xff0c;再启动时检查日志&#xff0c;如果有数据就…

3.1 网站树的爬起路径

一个网站往往由很多相互关联的网页组成&#xff0c;每个网页上都可能包含我们所要关心的数据&#xff0c;那么我们怎么样获取这些数据呢&#xff1f;显然我们必须穿梭于各个网页之间&#xff0c;那么按什么样的规则穿梭呢&#xff1f;常用的有深度优先与广 度优先方法。为了说明…

0402换元积分法-不定积分

文章目录1 第一类换元法1.1 定理11.2 例题1.2 常见凑微分形式1.2.1常见基本的导数公式的逆运算1.2.2被积函数含有三角函数2 第二类换元法2.1 定理22.2 常见第二换元代换方法2.2.1 三角代换-弦代换2.2.2 三角代换-切代换2.2.3 三角代换-割代换2.2.4 三角代换汇总2.2.5 倒代换2.2…

java基础系列(六) sleep()和wait() 区别

一.前言 关于并发编程这块, 线程的一些基础知识我们得搞明白, 本篇文章来说一下这两个方法的区别,对Android中的HandlerThread机制原理可以有更深的理解, HandlerThread源码理解,请查看笔者的这篇博客: HandlerThread源码理解_handlerthread 源码_broadview_java的博客-CSDN博…

requests库---(1)requests简介

目录&#xff1a;导读 request简介 requests安装 requests发送get请求 requests请求post 返回值其他内容 写在最后 在做接口测试&#xff0c;接口自动化测试的时候都会用到很多工具&#xff0c;如postman、jmeter、pytest等工具&#xff0c;除了这些工具外&#xff0c;我…

ESP32设备驱动-MAX30100心率监测传感器驱动

MAX30100心率监测传感器驱动 1、MAX30100介绍 MAX30100 是一款集成脉搏血氧饱和度和心率监测传感器解决方案。 它结合了两个 LED、一个光电探测器、优化的光学器件和低噪声模拟信号处理,以检测脉搏血氧饱和度和心率信号。 MAX30100 采用 1.8V 和 3.3V 电源供电,可通过软件…

单机模拟kafka分布式集群(演示生产、消费数据过程)

用单机搭建kafka伪分布式集群&#xff0c;其实集群的概念并不复杂 先说明一下&#xff0c;以下的每个服务启动后都需要新开一个终端来启动另外的服务(因为是集群&#xff0c;自然会用多个终端) 首先下载kafka 提取码&#xff1a;dvz4 或者直接去官网下载kafka_2.11-1.0.0.tgz t…

DevOps实战50讲-(1)彻底理解DevOps

持续坚持原创输出&#xff0c;点击蓝字关注我吧软件质量保障:所寫即所思&#xff5c;一个阿里质量人对测试的所感所悟。浅谈软件开发流程软件开发流程是从需求分析、设计、编码、测试到上线等一系列环节的步骤和活动。通常来说&#xff0c;软件开发流程可以分为以下几个阶段&am…

Vue3电商项目实战-商品详情模块7【21-商品详情-评价组件-头部渲染、22-商品详情-评价组件-实现列表】

文章目录21-商品详情-评价组件-头部渲染22-商品详情-评价组件-实现列表21-商品详情-评价组件-头部渲染 目的&#xff1a;根据后台返回的评价信息渲染评价头部内容。 yapi 平台可提供模拟接口&#xff0c;当后台接口未开发完毕或者没有数据的情况下&#xff0c;可以支持前端的开…

CentOS 7安装Docker并使用tomcat测试

文章目录环境准备Docker安装安装tomcat环境准备 CentOS 7以上版本linux内核版本需要在3.10以上&#xff0c;可通过uname -r 查看系统内核。 Docker安装 检查docker安装源 yum list docker yum安装docker &#xff1a; yum install docker.x86_64 启动 docker &#xff1a; s…

操作系统权限提升(十六)之绕过UAC提权-CVE-2019-1388 UAC提权

系列文章 操作系统权限提升(十二)之绕过UAC提权-Windows UAC概述 操作系统权限提升(十三)之绕过UAC提权-MSF和CS绕过UAC提权 操作系统权限提升(十四)之绕过UAC提权-基于白名单AutoElevate绕过UAC提权 操作系统权限提升(十五)之绕过UAC提权-基于白名单DLL劫持绕过UAC提权 注&a…

QML Item和Rectangle详解

1.Item和Rectangle Item类型是Qt Quick中所有可视项的基本类型。 Qt Quick中的所有可视项都继承Item。尽管Item对象没有视觉外观&#xff0c;但它定义了视觉项中常见的所有属性&#xff0c;例如x和y位置、宽度和高度、锚定和键处理支持。 Rectangle继承自Item&#xff0c;多…

数组初始化方式与decimal.InvalidOperation

数组初始化方式与decimal.InvalidOperation调用函数主函数: 数组声明不同带来的报错与否1. 报错decimal.InvalidOperation的数组初始化版本2. 可行的初始化版本输出结果1. 报错时的内容2. 正常的输出计算结果原因&#xff08;是否是数组与列表不同引起&#xff08;&#xff1f;…

因果推断10--一种大规模预算约束因果森林算法(LBCF)

论文&#xff1a;A large Budget-Constrained Causal Forest Algorithm 论文&#xff1a;http://export.arxiv.org/pdf/2201.12585v2.pdf 目录 0 摘要 1 介绍 2 问题的制定 3策略评价 4 方法 4.1现有方法的局限性。 4.2提出的LBCF算法 5验证 5.1合成数据 5.2离线生…

gitlab部署使用,jenkins部署使用

gitlab部署使用&#xff0c;jenkins部署使用gitlab下载gitlab安装gitlab使用gitlab设置中文修改管理员密码创建组,创建项目,创建用户jenkins下载jenkins安装jenkin使用jenkins更改管理员密码配置拉取代码配置登录gitlab拉取代码的账号密码配置项目配置gitlab仓库配置构建构建构…

动态分区分配计算

动态分区分配 内存连续分配管理分为&#xff1a; 单一连续分配固定分区分配动态分区分配&#xff08;本篇所讲&#xff09; 首次适应算法&#xff08;First Fit&#xff0c;FF&#xff09; 该算法又称最先适应算法&#xff0c;要求空闲分区按照首地址递增的顺序排列。 优点…

数据结构——树

深度优先/广度优先遍历深度优先&#xff1a;访问根节点对根节点的 children 挨个进行深度优先遍历const tree {val: "a",children: [{val: "b",children: [{val: "d",children: [],},{val: "e",children: [],},],},{val: "c&quo…

可靠性设计

目录 一、可靠性设计概述 二、冗余的类型 三、冗余系统的设计 1.N版本程序设计 2.恢复块设计 3.防卫式程序设计 4.双机容错 一、可靠性设计概述 可靠性指系统能够正常运行的概率。如何设计出一个具有高可靠性的系统呢&#xff1f;可以利用避错技术&#xff0c;容错技术…

【LeetCode】剑指 Offer 16. 数值的整数次方 p110 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/ 1. 题目介绍&#xff08;16. 数值的整数次方&#xff09; 实现 pow(x, n) &#xff0c;即计算 x 的 n 次幂函数&#xff08;即&#xff0c;xn&#xff09;。不得使用库函数&#xff0c;…

文献计量分析方法:Citespace安装教程

Citespace是一款由陈超美教授开发的可用于海量文献可视化分析的软件&#xff0c;可对Web of Science&#xff0c;Scopus&#xff0c;Pubmed&#xff0c;CNKI等数据库的海量文献进行主题、关键词&#xff0c;作者单位、合作网络&#xff0c;期刊、发表时间&#xff0c;文献被引等…