SpringMVC源码:getHandler、getHandlerAdapter过程

news2025/1/15 6:43:48

参考资料:

《SpringMVC源码解析系列》

《SpringMVC源码分析》

《Spring MVC源码》

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

前文:

《SpringMVC源码:DispatcherServlet初始化流程》

《SpringMVC源码:HandlerMapping加载1》

《SpringMVC源码:HandlerMapping加载2》

        在前文中我们介绍了DispatcherServlet的初始化以及基础组件HandlerMapping的初始化工作,在这之后DispatcherServlet的doService方法就可以开始提供服务了。

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //其余代码
        //为request设置额外的属性信息
        /WebApplicationContext、localeResolver、themeResolver、ThemeSource
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
 
        //设置重定向传参相关
        //INPUT_FLASH_MAP 用于保存上一个请求重定向过来的参数
        //OUTPUT_FLASH_MAP 用于保存重定向到别的请求需要传递的参数
        //flashMapManager 用于支持重定向传参的
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE,Collections.unmodifiableMap(inputFlashMap)    );
    }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
 
        try {
            //具体执行针对不同请求进行处理的核心方法
            doDispatch(request, response);
        }

	}

        doService内部首先进行了部分属性设置,然后就是调用doDispatch实现对不同请求的分发处理,doDispatch的过程可以大致概括为handler查找、适配器查找、处理器调用、视图渲染。

        本文我们将介绍handler处理与其适配器的查找,以及方法调用前的拦截器处理。(本文是基于之前文章的基础上开始讲解的,如果对SpringMVC不熟悉的朋友建议先阅读前文。) 

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            //检查是否为Multipart请求,如果是则HttpServletRequest转换为MultipartHttpServletRequest
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            //1.根据request信息寻找对应的Handler
            //并返回mappedHeadler,它是一个执行链,包含拦截器和自定义controller
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                //没有找到Handler则通过response反馈错误信息
                noHandlerFound(processedRequest, response);
                return;
            }
            // 2.根据当前的handler寻找对应的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // 当前handler处理last-modified
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            //3.mappedHandler调用拦截器的preHandler方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 4.HandlerAdapter调用自定义Controller中的方法并返回ModelAndView
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            //5.视图解析,为request请求找到视图
            applyDefaultViewName(processedRequest, mv);
            //6.调用拦截器的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
}

在这里插入图片描述

目录

一、getHandler

        1、   DispatcherServlet#getHandler     

        2、AbstractHandlerMapping#getHandler

        2.1、AbstractHandlerMethodMapping#getHandlerInternal

        2.2、AbstractHandlerMethodMapping#lookupHandlerMethod

        2.3、RequestMappingInfoHandlerMapping#handleMatch

        3、AbstractHandlerMapping#getHandlerExecutionChain

二、getHandlerAdapter

        为什么需要HandlerAdapter适配器?

        1、DispatcherServlet#getHandlerAdapter

        2、supports

        2.1、AbstractHandlerMethodAdapter#supports

        2.2、SimpleControllerHandlerAdapter#supports

三、applyPreHandle

        preHandle

        postHandle

        afterCompletion

        HandlerExecutionChain#applyPreHandle


一、getHandler

        1、   DispatcherServlet#getHandler     

        getHandler方法就是从HandlerMapping中查询匹配当前request的Handler。只要一匹配上 handler 就不再循环,直接返回。

        这里实际上是调用的HandlerMapping接口的抽象基类AbstractHandlerMapping中的getHandler方法,然后利用模板模式调用每个实现类判断是否匹配该Handler。

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //成员变量handlerMappings,前文中已初始化
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

        2、AbstractHandlerMapping#getHandler

        这里的主要作用是根据请求获取包装了Handler对象和拦截器的HandlerExecutionChain对象。该方法采用模板方法获取拦截器处理器链。

	@Override
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //交由子类实现的根据请求获取处理器的具体逻辑 不同的子类获取Handler的逻辑不同
		Object handler = getHandlerInternal(request);
        //如果没有获取到Handler 则获取默认的Handler
        //默认的处理器可以通过配置映射器实例的时候通过属性设置
		if (handler == null) {
			handler = getDefaultHandler();
		}
        //如果默认的Handler还是没有则返回空
		if (handler == null) {
			return null;
		}
		//Handler处理器是字符串类型 说明获取的Handler是beanName
        //则从spring 容器中获取bean实例对象
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
 
        //为Handler添加拦截器,并最终返回HandlerExecutionChain对象
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}

        2.1、AbstractHandlerMethodMapping#getHandlerInternal

        getHandlerInternal由AbstractHandlerMapping的各个子类实现,这里以AbstractHandlerMethodMapping为例进行介绍(AbstractUrlHandlerMapping中的实现可以看我之前这篇文章《SpringMVC源码:HandlerMapping加载1》)。

        我们可以看到第一步AbstractHandlerMethodMapping与AbstractUrlHandlerMapping中的处理都是先解析出请求的url,然后再根据这个请求url查找对应的handler/HandlerMethod。

	@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //通过UrlPathHelper对象从request解析出urlPath
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		if (logger.isDebugEnabled()) {
			logger.debug("Looking up handler method for path " + lookupPath);
		}
		this.mappingRegistry.acquireReadLock();
		try {
            //查询匹配的HandlerMethod
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			if (logger.isDebugEnabled()) {
				if (handlerMethod != null) {
					logger.debug("Returning handler method [" + handlerMethod + "]");
				}
				else {
					logger.debug("Did not find handler method for [" + lookupPath + "]");
				}
			}
            // 对HandlerMethod进行实例赋值(因为HanlerMethod中的bean属性是该方法对应对应的类对象
            // 这里通过createWithResolvedBean会将spring容器中的HandlerMethod所属的bean实例赋值
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

        2.2、AbstractHandlerMethodMapping#lookupHandlerMethod

        该方法负责根据请求request和对应的请求路径lookupPath获取对应的HandlerMethod。

         如果没有精确匹配,则对查询结果进行排序后选出最佳匹配。如果无法匹配到对应的映射俄关系,则spring提供handleNoMatch 进行处理,获取到不完全匹配映射则根据该映射,.提示改客户请求与该映射接近的请求出现不完全匹配的原因。

	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<Match>();
        // 从mappingRegistry获取匹配到的RequestMappingInfo
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        //找到完全匹配的则将mappings、handlerMethod 添加到matches中
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
        //如果没有精确匹配 则这里将所有映射关系和HandlerMethod封装成Match添到matchs中
		if (matches.isEmpty()) {
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}
        // 对匹配项进行排序
		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			Collections.sort(matches, comparator);
			if (logger.isTraceEnabled()) {
				logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
						lookupPath + "] : " + matches);
			}
			Match bestMatch = matches.get(0);

             //如有多个Match符合要求 这里需要获取最佳匹配的Match
             //获取映射关系的比较器 主要是比较RequestMappingInfo的匹配度 因为包含RequestMappingInfo
             // 包含不同类型的RequestCondtion 比较逻辑是这些RequestCondtion的compareTo()依次比较
			if (matches.size() > 1) {
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
                // 比较最佳匹配有多条相同则抛出异常
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
							request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
				}
			}
            //将匹配的HandlerMethod 存放到request中 方便后期使用
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            //获取的请求mapping进行处理(主要是针对pattern类型进行 请求路径url和请求参数的解析存放request)
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
            // 无匹配项处理
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}
	private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
		for (T mapping : mappings) {
			T match = getMatchingMapping(mapping, request);
			if (match != null) {
				matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
			}
		}
	}

        2.3、RequestMappingInfoHandlerMapping#handleMatch

        在获取到了最佳的Match(包含RequestMappingInfo,handlerMethod)对象后,对其中pattern类型的@RequestMapping修饰的方法进行参数和请求pattern、真实路径path的解析,为后续的便于使用将这些参数作为属性存放到request对象。

	@Override
	protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
        //调用父类的方法 request存储lookupPath 请求路径
		super.handleMatch(info, lookupPath, request);

		String bestPattern;
		Map<String, String> uriVariables;
		Map<String, String> decodedUriVariables;

        //如果不是pattern模式 则直接将请求路径作为pattern存储 参数为null
        //比如请求  localhost:port/context/book/bookListPage
		Set<String> patterns = info.getPatternsCondition().getPatterns();
		if (patterns.isEmpty()) {
			bestPattern = lookupPath;
			uriVariables = Collections.emptyMap();
			decodedUriVariables = Collections.emptyMap();
		}       
        //对于pattern模式  比如请求localhost:port/context/book/book/{id} 则为pattern模式请求
        //对于真实请求路径可能是这样 localhost:port/context/book/book/2243
        //该请求查询最好最低以及品牌类型
		else {
            //获取其中的pattern
			bestPattern = patterns.iterator().next();
            //根据pattern从lookupPath获取参数 上面的例子就获取到了参数2243
			uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
            //针对路径请求进行编码后存储到request
			decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
		}
        //存储pattern到request
		request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
		request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);

		if (isMatrixVariableContentAvailable()) {
			Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
			request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
		}
        //如果方法的@RequestMapping修饰的方法包含produces属性 其中的context-type 中的mediaTypes存储到request
		if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
			Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
			request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
		}
	}

        3、AbstractHandlerMapping#getHandlerExecutionChain

        查询匹配的拦截器,组装Handler生成HandlerExecutionChain。这里的adaptedInterceptors在我们的前文中已经介绍过了,就不重复了。

        该方法内将所有的拦截器通过责任链的模式组装到了一起,当调用该handler时都会通过这个责任链执行拦截器内的处理方法。

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        //判断当前handler是否为拦截器,不是的话就创建一个
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
        //获取到请求的url
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    //将拦截器加入到拦截器链中
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

        到这里为止整个getHandler的调用就结束了,可以看到整个方法过程就是先去HandlerMapping中找到对应请求的处理器handler,并组装拦截器链后返回。

二、getHandlerAdapter

        上一节我们通过HandlerMapping的映射关系找到了对应请求的handler处理器,本节我们将对完成对其的适配器查找。

        为什么需要HandlerAdapter适配器?

        在前文中我们了解到了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统一调用。

        1、DispatcherServlet#getHandlerAdapter

        该方法遍历所有的HandlerAdapter,找出支持解析传入的handler对象的实现类。

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
            //调用HandlerAdapter.support()方法 判断是否支持该handler对象的解析
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

        2、supports

        2.1、AbstractHandlerMethodAdapter#supports

        以抽象类AbstractHandlerMethodAdapter为例,该类中的supports实际上就是判断handler对象是否为HandlerMethod类型,换句话说只要是HandlerMethod类型的处理器该适配器都支持。

	@Override
	public final boolean supports(Object handler) {
		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}

    //RequestMappingHandlerAdapter.java
	@Override
	protected boolean supportsInternal(HandlerMethod handlerMethod) {
		return true;
	}

        2.2、SimpleControllerHandlerAdapter#supports

        再看另一种类型SimpleControllerHandlerAdapter,就变成了支持Controller类型的handler。

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

三、applyPreHandle

        回到doDispatch方法,在handler处理器执行前,会先调用applyPreHandle,执行HandlerExecutionChain 中所有拦截器的preHandle()方法。

    // 拦截器的前置处理
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }

        拦截器是将很多service或者Controller中共有的行为提炼出来,在某些方法执行的前后执行,提炼为通用的处理方式,让被拦截的方法都能享受这一共有的功能,让代码更加简洁,同时,当共有的功能需要发生调整、变动的时候,不必修改很多的类或者方法,只要修改这个拦截器就可以了,可复用性很强。 

         拦截器主要有三个方法:

        preHandle

        该方法将在请求处理之前进行调用。SpringMVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。

        该方法的返回值是布尔值Boolean类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。

        postHandle

        在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。

        postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行。

        afterCompletion

        该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。

        HandlerExecutionChain#applyPreHandle

        这里获取到我们本文第一步中添加到责任链里的拦截器,然后依次执行他们的preHandle方法。

	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

	public HandlerInterceptor[] getInterceptors() {
		if (this.interceptors == null && this.interceptorList != null) {
			this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
		}
		return this.interceptors;
	}

        本文我们介绍了请求分发核心逻辑doDispatch方法过程中的handler查找、适配器查找以及拦截器preHandle方法的执行,下篇文章我们将会继续介绍方法的调用。

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

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

相关文章

机器学习|多变量线性回归 | 吴恩达学习笔记

前文回顾&#xff1a;机器学习 | 线性回归&#xff08;单变量&#xff09; 目录 &#x1f4da;多维特征 &#x1f4da;多变量梯度下降 &#x1f4da;梯度下降法实践 &#x1f407;特征缩放 &#x1f407;学习率 &#x1f4da;特征和多项式回归 &#x1f4da;正规方程 &…

开发手册——一、编程规约_6.并发处理

这篇文章主要梳理了在java的实际开发过程中的编程规范问题。本篇文章主要借鉴于《阿里巴巴java开发手册终极版》 下面我们一起来看一下吧。 1. 【强制】获取单例对象需要保证线程安全&#xff0c;其中的方法也要保证线程安全。 说明&#xff1a;资源驱动类、工具类、单例工厂…

Microsoft designer 使用教程

继各种ai绘图软件诞生之后 dell 2 playground.... 微软自己研发的重量级产品 Microsoft designer 上线了 Microsoft Designer 是微软公司推出的一款设计工具&#xff0c;主要用于快速创建Web和移动应用程序的原型设计。它提供了一系列的工具和模板&#xff0c;可以帮助用户…

Python3.8.8-Django3.2-Redis-连接池-数据类型-字符串-list-hashmap

文章目录1.认识Redis1.1.优点1.2.缺点2.在Django中Redis的连接3.Redis的基础用法3.1.hashmap结构3.2.list结构4.命令行查看数据库5.作者答疑1.认识Redis Remote DIctionary Server(Redis) 是一个key-value 存储系统&#xff0c;是跨平台的非关系型数据库。是一个开源的使用 AN…

【分布式】什么是分布式,分布式和集群的区别又是什么?答案在正文。

文章目录1. 什么是分布式 ?2. 分布式与集群的区别 ?3.用一个请求串起来4.一个简化的架构图5.分布式环境的特点6.分布式环境下面临的问题7.总结1. 什么是分布式 ? 分布式系统一定是由多个节点组成的系统。 其中&#xff0c;节点指的是计算机服务器&#xff0c;而且这些节点一…

Stochastic Approximation —Stochastic gradient descent 随机近似方法的详解之(四)随机梯度下降

Stochastic Approximation —Stochastic gradient descent 随机近似方法的详解之&#xff08;四&#xff09;随机梯度下降 郑重声明&#xff1a;本系列内容来源 赵世钰(Shiyu Zhao)教授的强化学习数学原理系列&#xff0c;本推文出于非商业目的分享个人学习笔记和心得。如有侵权…

【微信小程序】-- 案例 - 本地生活(二十)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

【数据结构初阶】堆排序

目录 前言 概念 堆排序的实现 1.建堆 &#xff08;1&#xff09;堆向上调整算法 &#xff08;2&#xff09;堆的向下调整算法 2. 利用堆删除思想来进行排序 3.堆排序的时间复杂度 4.源码 总结 前言 前边我们学习了堆的实现&#xff0c;对堆的每个接口都进行了详细的讲…

js中getBoundingClientRect()方法

getBoundingClientRect()返回值是一个 DOMRect 对象&#xff0c;是包含整个元素的最小矩形&#xff08;包括 padding 和 border-width&#xff09;。该对象使用 left、top、right、bottom、x、y、width 和 height 这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了 …

高性能网络I/O框架-netmap源码分析

前几天听一个朋友提到这个netmap&#xff0c;看了它的介绍和设计&#xff0c;确实是个好东西。其设计思想与业界不谋而合——因为为了提高性能&#xff0c;几个性能瓶颈放在那里&#xff0c;解决方法自然也是类似的。 netmap的出现&#xff0c;它既实现了一个高性能的网络I/O框…

【Storm】【五】Storm集成Kafka

Storm集成Kafka 一、整合说明二、写入数据到Kafka三、从Kafka中读取数据一、整合说明 Storm 官方对 Kafka 的整合分为两个版本&#xff0c;官方说明文档分别如下&#xff1a; Storm Kafka Integration : 主要是针对 0.8.x 版本的 Kafka 提供整合支持&#xff1b;Storm Kafka …

English Learning - L2-3 英音地道语音语调 小元音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.02.27 周一

English Learning - L2-3 英音地道语音语调 小元音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.02.27 周一课前活动练习方法大小元音总结小元音准备工作[ʌ] 中元音发音技巧对应单词的发音对应句子的发音常见的字母组合[ɒ] 后元音发音技巧对应单词的发音对应句子的发音常见的字母组合…

Spring——什么是事务?传播行为?事务隔离级别有哪些?

思维导图一、什么是事务&#xff1f;多条DML要么同时成功&#xff0c;要么同时失败Transaction&#xff08;tx&#xff09;二、事务的四个过程&#xff1a;开启事务&#xff08;start transaction&#xff09;执行核心业务代码提交事务&#xff08;如果核心业务处理过程中没有出…

真香,Grafana开源Loki日志系统取代ELK?

一、Loki是什么&#xff1f; Loki是由Grafana Labs开源的一个水平可扩展、高可用性&#xff0c;多租户的日志聚合系统的日志聚合系统。它的设计初衷是为了解决在大规模分布式系统中&#xff0c;处理海量日志的问题。Loki采用了分布式的架构&#xff0c;并且与Prometheus、Graf…

【前端】一个更底层库-React基础知识点

目录Reat是什么&#xff1f;为什么要使用React类库比较容易学习&#xff0c;API非常少。组件内聚&#xff0c;容易组合原生组件和自定义组件融合渲染状态/属性驱动全局更新commonjs生态圈/工具栏完善React基础知识JSX概述JSX嵌入变量Event事件组合组合CHILDREN总结大家好&#…

02-问题思考维度:抓住核心用户、场景化分析、需求收集与辨别、用户故事

文章目录2.1 抓住核心用户2.1.1 为什么要抓住核心用户2.1.2 核心用户的特征根据不同维度&#xff0c;描述核心用户2.1.3 如何抓住核心用户2.2 场景化分析2.2.1 场景五要素2.2.2 场景化分析方法2.2.3 场景化分析方法的应用2.3 需求收集与辨别2.3.1 需求的定义及层次2.3.2 需求收…

汇编相关问题

汇编语言期末复习题DX&#xff1a;单项选择题 DU&#xff1a;多项选择题 TK&#xff1a;填空题 MC&#xff1a;名词解释 v JD&#xff1a;简答题 CXFX&#xff1a;程序分析题 CXTK&#xff1a;程序填空题 BC&#xff1a;编程题第1章&#xff1a;基础知识1、在汇编语言程序的开发…

Vue2.0开发之——购物车案例-axios请求列表数据(45)

一 概述 项目导入axios HTTP 库axios请求数据列表将请求到的数据转存到data中 二 项目导入axios HTTP 库 2.1 axios介绍 Axios是一个基于promise 的 HTTP 库&#xff0c;可以用在浏览器和 node.js中 2.2 axios项目地址 https://www.npmjs.com/package/axios 2.3 axios安装…

excel的Countif函数使用详细教程

excel的Countif函数使用详细教程本教程通过七个示例讲解Countif函数使用教程&#xff0c;其中条件如何设置模糊值&#xff0c;统计固定长度的文本&#xff0c;统计大于某个值&#xff0c;统计等于某个值的&#xff0c;统计日期等。Countif函数作用&#xff1a;对指定单元格区域…

pycharm关联github、新建以及更新仓

此处已经默认你安装了git以及pycharm,这篇文章将会教给大家如何利用pycharm管理自己的github. 目录 pycharm关联github设置 Github创建新的仓 仓库的更新 pycharm:2022。不同版本界面略有不同。 pycharm关联github设置 设置PyCharm&#xff0c;打开File --> Settings -…