Spring源码学习:SpringMVC(4)DispatcherServlet请求入口分析

news2024/11/22 21:42:53

目录

  • 前言
  • HttpServlet &FrameworkServlet
    • HttpServlet #service
    • FrameworkServlet#processRequest
  • DispatcherServlet#doService
    • doDispatch
    • checkMultipart
    • getHandler
      • AbstractHandlerMapping#getHandler
      • RequestMappingInfoHandlerMapping#getHandlerInternal
      • AbstractHandlerMethodMapping#getHandlerInternal
      • HandlerMethod#createWithResolvedBean
      • AbstractHandlerMapping#getHandlerExecutionChain
    • noHandlerFound
    • getHandlerAdapter
    • Last-Modified 的缓存处理
      • SimpleControllerHandlerAdapter#getLastModified
      • RequestMappingHandlerAdapter#getLastModifiedInternal
    • 拦截器三个方法的调用
      • HandlerExecutionChain#applyPreHandle
      • HandlerExecutionChain#applyPostHandle
      • HandlerExecutionChain#triggerAfterCompletion
    • HandlerAdapter#handle
      • handleInternal
      • invokeHandlerMethod
    • applyDefaultViewName
      • getDefaultViewName
    • processDispatchResult
      • processHandlerException
        • AbstractHandlerExceptionResolver#resolveException
        • getExceptionHandlerMethod
      • render
  • 总结

前言

​ 通过前面的分析,我们知道DispatcherServlet其本质还是Servlet,那么当客户端的请求到达时,根据Servlet生命周期,其应该会调用其或者其父类中的service方法。

在其父类FrameworkServlet中我们找到了service方法

	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		/*
		 *	获取HttpMethod类型,
		 *	HttpMethod为枚举类,支持的Http请求类型有GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
		 */
		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
			// 若方法为 PATCH 方法或为空则单独处理
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}

这里有两个方法,一个是processRequest(request, response)super.service(request, response),这里的super就是HttpServlet,我们分别看一下

HttpServlet &FrameworkServlet

HttpServlet #service

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();
		//如果是get
        if (method.equals(METHOD_GET)) {
            //lastModified 缓存判断
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

​ 上面几个方法最常用的就是 doGet()doPost() 。这两个方法被 FrameworkServlet 重写了。我们来看看在 FrameworkServlet 中的实现。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {
    processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {
    processRequest(request, response);
}

​ 我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse) 来进行一个请求的分发。对于我们常见的 doGet() 和 doPost() 方法都是直接调用 processRequest(request, response);, 而processRequest方法 的具体实现在FrameworkServlet#processRequest 中 。

FrameworkServlet#processRequest

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		// 记录抛出的异常~~~(若有的话)
		Throwable failureCause = null;

		//国际化设置
    	// 1 提取当前线程的 LocaleContext  属性
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    	// 2. 根据当前的request 创建对应的 LocaleContext ,并绑定到当前线程
		LocaleContext localeContext = buildLocaleContext(request);

		//构建ServletRequestAttributes对象
    	//3. 提取当前线程的 RequestAttributes 属性
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    	//4.根据当前的request 创建对应的 RequestAttributes ,并绑定到当前线程
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		// 拿到异步管理器。这里是首次获取,会new WebAsyncManager(),然后放到request的attr里面
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		//这里需要注意:给异步上下文恒定注册了RequestBindingInterceptor这个拦截器(作用:绑定当前的request、response、local等)
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		//这句话很明显,就是吧request和Local上下文、RequestContext绑定
		initContextHolders(request, localeContext, requestAttributes);

		try {
			//5.模版设计模式:由子类DispatcherServlet去实现实际逻辑
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
			//这个时候已经全部处理完成,视图已经渲染了
			//doService()方法完成后,重置上下文,也就是解绑
            // 6. 请求结束,恢复线程原状
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
			//关键:不管执行成功与否,都会发布一个事件,我处理了这个请求(有需要监听的,就可以监听这个事件了,每次请求都会有)
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

​ 由于逻辑都被封装到 doService(request, response); 中,所以这里还是比较简单。而doService又被 DispatcherServlet实现了。因此我们这里来看看 DispatcherServlet#doService

DispatcherServlet#doService

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		 /*
		 * 如果当前请求是一个 include request,如:<jsp:incluede page="xxx.jsp"/>
		 * 则为此请求属性建立快照,以便include request结束后能够将其恢复
		 */
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		// 说得很清楚,把一些常用对象放进请求域  方便Handler里面可以随意获取
		// Spring上下文
		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());

		// 如果是重定向,放置得更多一些
		if (this.flashMapManager != null) {
			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 {
			// 【重要】 真正开始处理http请求
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					// 恢复之前保存的数据快照
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

​ 很明显,这 “祖孙三代” 一层一层传递,终于传递到了DispatcherServlet 手里,这里我们直接开始看doDispatch(request, response);,这里才是核心逻辑的所在!!!

doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// 此处用processedRequest  需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象
		HttpServletRequest processedRequest = request;
		// 链处理器
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				//1.checkMultipart 判断是否是上传需求。且看下面的具体分析:
				//如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求
				processedRequest = checkMultipart(request);
				// 标记一下:是否是文件上传的request了
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 2.查找当前请求对应的handler,包括Handler(控制器)本身和Handler拦截器
				mappedHandler = getHandler(processedRequest);
				// 未能找到对应的handler,抛出NoHandlerFoundException异常并返回404
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 3.查找当前请求对应的HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				// 4.处理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;
					}
				}

				// 5.应用前置拦截器:执行注册拦截器的preHandle方法
				// 如果有拦截器返回false,则表明该拦截器已经处理了返回结果,直接返回;
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 6.调用HandlerAdapter的handler方法,真正开始处理Controller
				// *****真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				// 7.如果当前请求是异步处理,直接返回
				// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				//8. 为返回值设定默认视图名,如果当前返回值中不包含视图名的话
				applyDefaultViewName(processedRequest, mv);

				// 9.应用已注册拦截器的后置方法:执行所有的拦截器的postHandle方法,并且把mv给他
				// 这里有一个小细节:这个时候拦截器是【倒序】执行的
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}

			// 10.处理分发调用结果,如视图模型解析、返回等工作
			// 处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletion
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

checkMultipart

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
				if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
					logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
				}
			}
			else if (hasMultipartException(request)) {
				logger.debug("Multipart resolution previously failed for current request - " +
						"skipping re-resolution for undisturbed error rendering");
			}
			else {
				try {
					return this.multipartResolver.resolveMultipart(request);
				}
				catch (MultipartException ex) {
					if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
						logger.debug("Multipart resolution failed for error dispatch", ex);
						// Keep processing error dispatch with regular request handle below
					}
					else {
						throw ex;
					}
				}
			}
		}
		// If not returned before: return original request.
		return request;
	}

​ 对于请求的处理,Spring首先考虑的是对 Multipart 多文件上传的处理,如果是 MultipartContent 类型的request 则转换request 为 MultipartHttpServletRequest 类型的request。简单来说,就是判断是否是文件请求。

PS : 注意一下这个赋值代码

multipartRequestParsed = (processedRequest != request);

这里如果为false就证明不是文件上传请求,因为如果为true的话就证明request已经变成了MultipartHttpServletRequest,不是一开始的了

getHandler

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

AbstractHandlerMapping#getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		// 1.获取当前请求对应的handler,该方法供子类实现
		Object handler = getHandlerInternal(request);

		// 未能获取到对应的handler,则使用默认的defaultHandler
		if (handler == null) {
			handler = getDefaultHandler();
		}

		// 两者同时未找到,则返回null
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		// 2.如果获取到的handler是String类型,则以handler为beanName,从IOC容器中获取其实例
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		// 3.根据handler和request获取对应的HandlerExecutionChain实例
		// 会将handler封装到HandlerExecutionChain对象中,
		// 并将系统和自定义的拦截器加入到HandlerExecutionChain中
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		//是不是cors请求,cors是跨域请求
		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
			//方法上@CrossOrigin注解信息
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			config = (config != null ? config.combine(handlerConfig) : handlerConfig);
			//往拦截器链里添加new CorsInterceptor(config)
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

getHandlerInternal(request)该方法在 AbstractHandlerMethodMapping#getHandlerInternal 中没有具体实现,是供不同的 HandlerMapping 子类自己实现的。这里我们直接看 RequestMappingInfoHandlerMapping#getHandlerInternal

RequestMappingInfoHandlerMapping#getHandlerInternal

	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
		try {
			return super.getHandlerInternal(request);
		}
		finally {
			ProducesRequestCondition.clearMediaTypesAttribute(request);
		}
	}

super.getHandlerInternal(request)调用的是AbstractHandlerMethodMapping#getHandlerInternal

AbstractHandlerMethodMapping#getHandlerInternal

	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		//从request对象中获取uri,解析请求路径
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		// 加只读锁
		request.setAttribute(LOOKUP_PATH, lookupPath);
		this.mappingRegistry.acquireReadLock();
		try {
			// 根据请求路径和当前请求对象,获取最佳匹配的HandlerMethod
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			// 获取当前Controller的实例,并将获取到的实例封装至HandlerMethod对象中
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			// 释放只读锁
			this.mappingRegistry.releaseReadLock();
		}
	}
	...
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		// 这里的lookupPath就是请求的url,从urlLookup中获取key完全匹配的
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

		// 如果不为空的话,判断其它的信息是否符合,将符合的添加到matches变量中
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
        // 如果上面没有获取到匹配的路径,则只能遍历所有的 mapping。
		// 由于会遍历所有的 RequestMapping。所以性能会随着 RequestMapping数量的增加降低
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			// 如果为空的话,从mappingLookup中查找所有信息是否有符合的
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			// 如果匹配多个的话,会根据相关的算法找到最合适的那个,然后返回它的处理方法
			Match bestMatch = matches.get(0);
			// 如果合适的 Mapping 不止一个,则筛选出最合适的
			if (matches.size() > 1) {
				Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
				matches.sort(comparator);
				bestMatch = matches.get(0);
				if (logger.isTraceEnabled()) {
					logger.trace(matches.size() + " matching mappings: " + matches);
				}
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				//如果两个RequestMappinginfo什么都相同,报错
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(
							"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
				}
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			// 如果没有匹配的话,会直接根据名字找一次
			// 如果找到的话,会对比其他信息,只要有不符合的就会抛出异常
			// 如果没有找到,直接返回null
			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)));
			}
		}
	}

		//  this.mappingRegistry.getMappingsByUrl(lookupPath);  
	// 也就是 AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl
	@Nullable
	public List<T> getMappingsByUrl(String urlPath) {
		return this.urlLookup.get(urlPath);
	}

​ 可以看到逻辑主要是通过url从mappingRegistry中获取指定的requestMappingInfo集合,为什么是集合呢,因为可能会存在restFul风格的接口了,设置的url都相同,但是请求方式不同,这种情况集合中就会存在多个,正常如果不是restFul风格的话其实集合就一个元素,

  • List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); 这个方法是非常直观的根据URL来获取,springMVC会在初始化的时候建立URL和相应RequestMappingInfo的映射。如果不是restful接口,这里就可以直接获取到了。

  • 如果从mappingRegistry中已经获取到,则调用方法addMatchingMappings(directPathMatches, matches, request)进行匹配校验。

  • 如果mappingRegistry中未获取到匹配方法信息,则调用方法addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); 进行全局(all mappings)扫描匹配。且会把所有的RequestMappingInfo都遍历完才会停止,也就是说项目中的@RequestMapping方法越多,这个匹配的效率就越低,性能越差。但是我感觉呀,一般情况下其实都不会走到这里,除非调试阶段输错了url,正常其实上面两步完了以后,基本就能找到了

  • 如果合适的 Mapping 不止一个,则筛选出最合适的,到了最后如果前两个一模一样则报错

HandlerMethod#createWithResolvedBean

public HandlerMethod createWithResolvedBean() {
    Object handler = this.bean;
    if (this.bean instanceof String) {
       Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
       String beanName = (String) this.bean;
       handler = this.beanFactory.getBean(beanName);
    }
    return new HandlerMethod(this, handler);
}

​ 如果提供的实例包含 Bean 名称而不是对象实例,则先创建好相应的bean之后再返回,这个方法主要是将再将HandlerMethod封装一层,之前bean如果是String类型的beanName的话那就替换成对象Bean,以便之后反射调用方法

AbstractHandlerMapping#getHandlerExecutionChain

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {

		//判断handler是不是执行器链,如果不是创建一个执行器链
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		//获取uri
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);

		//包装拦截器
    	//遍历所有的拦截器,如果拦截器匹配符则加入到执行链中。adaptedInterceptors 是在 Mapping 初始化的时候加载的
		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;
	}

​ 主要目的是将配置中对应的拦截器加入到执行链中,以保证这些拦截器可以有效的作用于目标对象

​ 关于 adaptedInterceptors、interceptors 两个拦截器集合:adaptedInterceptors 是 AbstractHandlerMapping 在初始化的时候实现了 ApplicationContextAware 接口,在 ApplicationObjectSupport#setApplicationContext 方法中调用 initApplicationContext 方法,进行了 adaptedInterceptors 的初始化。而 interceptors 则可以通过 set 方法进行注入。

	protected void initApplicationContext() throws BeansException {
		extendInterceptors(this.interceptors);
		detectMappedInterceptors(this.adaptedInterceptors);
		initInterceptors();
	}

	protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
		mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
				obtainApplicationContext(), MappedInterceptor.class, true, false).values());
	}
	
		protected void initInterceptors() {
		if (!this.interceptors.isEmpty()) {
			for (int i = 0; i < this.interceptors.size(); i++) {
				Object interceptor = this.interceptors.get(i);
				if (interceptor == null) {
					throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
				}
				this.adaptedInterceptors.add(adaptInterceptor(interceptor));
			}
		}
	}

​ 到这里就获取到了handler执行器链了,里面即有我们要执行的handler,也有相对应的拦截器,如果没找到就会进去下面的方法报异常

noHandlerFound

​ 正常情况下,每一个请求都应该对应一个 Handler,因为每个请求都应该在后台有对应的处理逻辑。而逻辑的实现就是在Handler 中。但是,如果没有URL匹配的Handler,我们可以通过设置默认的Handler 来解决这一问题,不过如果没有设置默认的Handler。则只能通过Response 向用户返回错误信息。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
       pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
    }
    if (this.throwExceptionIfNoHandlerFound) {
       throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
             new ServletServerHttpRequest(request).getHeaders());
    }
    else {
       response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

getHandlerAdapter

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

​ 这一步的目的是根据 Handler 寻找对应的 HandlerAdapter。这里使用了适配器模式,遍历所有的 Adapter。根据 HandlerAdapter#supports 方法来判断是否支持当前Handler 的解析,如果支持,则返回。

我们这里返回的是RequestMappingHandlerAdapter,其判定条件如下:

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

Last-Modified 的缓存处理

Last-Modified机制流程如下(以下内容来源于百度百科):

  1. 在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时响应体里有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。Last-Modified格式类似这样:Last-Modified : Fri , 12 May 2006 18:53:33 GMT

  2. 客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头,询问该时间之后文件是否有被修改过:If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
    如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

Spring通过实现 LastModified 接口即可完成Last-Modified 机制,如下:

PS : 这里是通过实现Controller接口来实现的,我们在日常开发一般都是使用的@RestController+@RequestMapping注解那种方式,那种方式的话就不能只是通过实现 LastModified 接口来完成了,至于细节可以看这篇大佬的博客:https://blog.csdn.net/qq_36882793/article/details/109515781

@Component("/testLastModifiedController")
public class TestLastModifiedController implements Controller, LastModified {
    private long lastModified;
    
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        return new ModelAndView("/hello.html");
    }
    
    @Override
    public long getLastModified(HttpServletRequest request) {
        if (lastModified == 0L){
            lastModified = System.currentTimeMillis();
        }
        return lastModified;
    }
}

在一次请求成功后,第二次请求返回的状态码 304。

在这里插入图片描述

好了,了解了last-Modified机制我们继续来看doDispatcher中相关逻辑:

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        String method = request.getMethod();
		boolean isGet = "GET".equals(method);
        //主要请求方法是get或者head都会进入
		if (isGet || "HEAD".equals(method)) {
			long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
			if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
		}
        ...
}

mappedHandler就是上面getHandler方法返回的执行器链,可以看到调用了适配器的getLastModified方法,所以主要逻辑在适配器的getLastModified方法中,

SimpleControllerHandlerAdapter#getLastModified

	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}

​ 这个适配器其实就是对应了实现Controller接口的控制器,如果你实现了接口,那就调用你重写的逻辑,反之默认返回-1。

RequestMappingHandlerAdapter#getLastModifiedInternal

ps : RequestMappingHandlerAdapter#getLastModified 方法会调用 RequestMappingHandlerAdapter#getLastModifiedInternal 方法

	protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
		return -1;
	}

我们在getHandlerAdapter方法中可以得知我们通过@RequestMapping注解方式写的接口最后适配器就是获取的RequestMappingHandlerAdapter,它的lastModified直接返回的就是-1,也就是我们这种方式根本没办法修改 lastModified。

接着看下checkNotModified(lastModified)方法

	@Override
	public boolean checkNotModified(long lastModifiedTimestamp) {
		return checkNotModified(null, lastModifiedTimestamp);
	}

	@Override
	public boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) {
		HttpServletResponse response = getResponse();
		//notModified其实表示资源没有被修改的意思,默认是false,没有被修改直接返回true
		//如果 notModified  已经true || 返回状态码已经不是200直接返回 notModified
        if (this.notModified || (response != null && HttpStatus.OK.value() != response.getStatus())) {
			return this.notModified;
		}

		// Evaluate conditions in order of precedence.
		// See https://tools.ietf.org/html/rfc7232#section-6
        //解析校验 If-Unmodified-Since 请求头。这个请求头和If-Modified-Since请求头意义相反
		if (validateIfUnmodifiedSince(lastModifiedTimestamp)) {
			if (this.notModified && response != null) {
				response.setStatus(HttpStatus.PRECONDITION_FAILED.value());
			}
			return this.notModified;
		}
		
        //校验 If-None-Match 请求头。这是针对 Etag 缓存。
		boolean validated = validateIfNoneMatch(etag);
		if (!validated) {
			validateIfModifiedSince(lastModifiedTimestamp);
		}

		// Update response
        // 更新 Response。包括状态码等信息
		if (response != null) {
			boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());
			if (this.notModified) {
				response.setStatus(isHttpGetOrHead ?
						HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());
			}
			if (isHttpGetOrHead) {
				if (lastModifiedTimestamp > 0 && parseDateValue(response.getHeader(HttpHeaders.LAST_MODIFIED)) == -1) {
					response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTimestamp);
				}
				if (StringUtils.hasLength(etag) && response.getHeader(HttpHeaders.ETAG) == null) {
					response.setHeader(HttpHeaders.ETAG, padEtagIfNecessary(etag));
				}
			}
		}

		return this.notModified;
	}

	private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) {
		if (lastModifiedTimestamp < 0) {
			return false;
		}
		long ifUnmodifiedSince = parseDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
		if (ifUnmodifiedSince == -1) {
			return false;
		}
		// We will perform this validation...
        //上一次的修改小于最后修改时间就可以
		this.notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000));
		return true;
	}

	private boolean validateIfModifiedSince(long lastModifiedTimestamp) {
		if (lastModifiedTimestamp < 0) {
			return false;
		}
		long ifModifiedSince = parseDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
		if (ifModifiedSince == -1) {
			return false;
		}
		// We will perform this validation...
        //上一次的修改时间大于等于最后修改时间就可以
		this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000);
		return true;
	}

拦截器三个方法的调用

HandlerExecutionChain#applyPreHandle

	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[0]);
		}
		return this.interceptors;
	}

​ 首先获取所有的拦截器,然后依次遍历执行preHandle方法,根据方法返回值来决定是否继续下一个拦截器执行,记录上一个执行成功preHandle方法拦截器的下标,如果返回false就停止执行拦截器的preHandle方法,将执行过preHandle方法的拦截器继续执行它的afterCompletion方法

HandlerExecutionChain#applyPostHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
       throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
       for (int i = interceptors.length - 1; i >= 0; i--) {
          HandlerInterceptor interceptor = interceptors[i];
          interceptor.postHandle(request, response, this.handler, mv);
       }
    }
}

​ 执行完handle方法之后执行,逻辑和上面执行preHandle差不多,但是需要注意的是倒序执行postHandle方法,从末尾往前执行,类似于之前多个AOP通知方法的执行过程

HandlerExecutionChain#triggerAfterCompletion

	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
			throws Exception {

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = this.interceptorIndex; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				try {
					interceptor.afterCompletion(request, response, this.handler, ex);
				}
				catch (Throwable ex2) {
					logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
				}
			}
		}
	}

​ 调用拦截器的结束方法,视图呈现之后调用的,或者执行前置拦截方法时返回false也会执行到这里

HandlerAdapter#handle

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
}

handleInternal

@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		//定义返回值变量
		ModelAndView mav;
		// 1.检测当前请求,验证请求方法合法性和session合法性
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		// 2.根据synchronizeOnSession值判断,当前请求是否需串行化访问。
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				// 获取最佳互斥锁,即同步当前回话对象;如未能获取到互斥锁,将返回HttpSession对象本身
				Object mutex = WebUtils.getSessionMutex(session);
				//加锁,所有请求串行化
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				// 即无最佳互斥锁,也未能获取到HttpSession,则当前回话无需串行化访问
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			// *** 正常调用处理方法
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		// 3.相应信息不包含Cache-Control
		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}

invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		//对HttpServletRequest进行包装,产生ServletWebRequest处理web的request对象
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			//WebDataBinderFactory --> 工厂类,为目标对象创建一个WebDataBinder实例
			// 1.WebDataBinder继承了DataBinder类,为web请求提供了参数绑定服务
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);

			// 获取ModelFactory:
			// 2.ModelFactory可以协助控制器在调用方法之前初始化模型,并在调用之后更新模型
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			// 创建ServletInvocableHandlerMethod对象
			// 3.ServletInvocableHandlerMethod继承并扩展了InvocableHandlerMethod
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

			// 4.尝试设置 参数解析器、返回值解析器
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			// 5.创建ModelAndViewContainer,并初始化Model对象
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			// 6.异步请求相关
			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);

			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				LogFormatUtils.traceDebug(logger, traceOn -> {
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}

			// 7.反射调用Controller中的具体方法并处理返回值
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			// 8.返回ModelAndView对象
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			// 完成请求后续处理,并将当前请求置为未激活
			webRequest.requestCompleted();
		}
	}

​ invocableMethod.invokeAndHandle(webRequest, mavContainer); 这个方法里面比较复杂,暂时先不做说明,简单说下:

  • 通过参数解析器来解析参数值,最后形成一个object类型的数组
  • 调用目标方法
  • 通过返回值解析器来解析返回值

大致流程就是这样,了解即可,这里因为篇幅原因,不做细致的讲解了,后面有机会再详细说吧。。。

applyDefaultViewName

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    // ModelAndView不为空,但是没有View对象则尝试为其生成一个默认的视图名
    if (mv != null && !mv.hasView()) {
       String defaultViewName = getDefaultViewName(request);
       if (defaultViewName != null) {
          mv.setViewName(defaultViewName);
       }
    }
}

​ 如果没有视图则会会生成一个默认的视图名,其实就是请求URL

getDefaultViewName

	protected String getDefaultViewName(HttpServletRequest request) throws Exception {
		//这里是不为空的,所以会执行到this.viewNameTranslator.getViewName
        return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
	}

	public String getViewName(HttpServletRequest request) {
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
        //this.prefix和this.suffix默认都是空的 transformPath里面主要是对URL做一些截取
		return (this.prefix + transformPath(lookupPath) + this.suffix);
	}

processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
       @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
       @Nullable Exception exception) throws Exception {

    boolean errorView = false;

    // 处理异常信息
    if (exception != null) {
       if (exception instanceof ModelAndViewDefiningException) {
          logger.debug("ModelAndViewDefiningException encountered", exception);
          mv = ((ModelAndViewDefiningException) exception).getModelAndView();
       }
       else {
          Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
          mv = processHandlerException(request, response, handler, exception);
          errorView = (mv != null);
       }
    }

    // Did the handler return a view to render?
    // 尝试解析视图和模型;
    // wasCleared:判断当前模型和视图是否已经被标识为清空,且当前视图和模型是否同时为空
    if (mv != null && !mv.wasCleared()) {
       // 解析并呈现视图和模型
       render(mv, request, response);
       if (errorView) {
          WebUtils.clearErrorRequestAttributes(request);
       }
    }
    else {
       if (logger.isTraceEnabled()) {
          logger.trace("No view rendering, null ModelAndView returned.");
       }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
       // Concurrent handling started during a forward
       return;
    }

    // 处理注册的后置完成拦截器
    if (mappedHandler != null) {
       // Exception (if any) is already handled..
       mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

​ 关于spring的几种异常处理方案可以看这篇博客:
Spring源码学习(拓展篇):SpringMVC中的异常处理,我们这里对 @ExpceptionHander+ @ControllerAdvice这种方式来进行分析下,我们都知道最后肯定会调用在全局异常处理类中和当前发生异常相匹配的异常方法来执行,主要逻辑在processHandlerException

processHandlerException

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

		// Success and error responses may use different content types
		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

		// Check registered HandlerExceptionResolvers...
    	//检查已注册的 HandlerExceptionResolvers
		ModelAndView exMv = null;
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
				request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
				return null;
			}
			// We might still need view name translation for a plain error model...
			if (!exMv.hasView()) {
				String defaultViewName = getDefaultViewName(request);
				if (defaultViewName != null) {
					exMv.setViewName(defaultViewName);
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using resolved error view: " + exMv, ex);
			}
			else if (logger.isDebugEnabled()) {
				logger.debug("Using resolved error view: " + exMv);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

​ 我们看到会遍历已经注册的异常解析器,这里是什么时候注册的,其实就是解析mvc:annotation-driven标签的时候注册的,一般会有三个

  • ExceptionHandlerExceptionResolver : 这个其实就是我们使用全局异常处理类的时候会用到的,也就是@ExpceptionHander+ @ControllerAdvice这种方式会使用该类来进行解析,在项目初始化时,ExceptionHandlerExceptionResolver@ControllerAdvice@ExceptionHandler标注的异常处理方法进行缓存,异常-处理方法的映射。

  • ResponseStatusExceptionResolver :解析有@ResponseStatus注解的异常。

  • DefaultHandlerExceptionResolver : 通过配置的异常类和view的对应关系来解析异常。

我们主要来看ExceptionHandlerExceptionResolver

AbstractHandlerExceptionResolver#resolveException
public ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		if (shouldApplyTo(request, handler)) {
			prepareResponse(ex, response);
			ModelAndView result = doResolveException(request, response, handler, ex);
			if (result != null) {
				// Print debug message when warn logger is not enabled.
				if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
					logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
				}
				// Explicitly configured warn logger in logException method.
				logException(ex, request);
			}
			return result;
		}
		else {
			return null;
		}
	}

​ 可以看到会进入AbstractHandlerExceptionResolver#resolveException方法中,这里面主要逻辑在doResolveException,这是一个模板方法,由子类AbstractHandlerMethodExceptionResolver实现,AbstractHandlerMethodExceptionResolver中的doResolveException会调用ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
			HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

    	//获取异常对应的异常处理方法
		ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
		if (exceptionHandlerMethod == null) {
			return null;
		}

    	//设置参数解析器和返回值解析器
		if (this.argumentResolvers != null) {
			exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
			exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		ModelAndViewContainer mavContainer = new ModelAndViewContainer();

		try {
			if (logger.isDebugEnabled()) {
				logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
			}
			Throwable cause = exception.getCause();
            
			if (cause != null) {
				// Expose cause as provided argument as well
                //执行目标方法
				exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
			}
			else {
                 //执行目标方法
				// Otherwise, just the given exception as-is
				exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
			}
		}
		catch (Throwable invocationEx) {
			// Any other than the original exception (or its cause) is unintended here,
			// probably an accident (e.g. failed assertion or the like).
			if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
				logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
			}
			// Continue with default processing of the original exception...
			return null;
		}

		if (mavContainer.isRequestHandled()) {
			return new ModelAndView();
		}
		else {
			ModelMap model = mavContainer.getModel();
			HttpStatus status = mavContainer.getStatus();
			ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
			mav.setViewName(mavContainer.getViewName());
			if (!mavContainer.isViewReference()) {
				mav.setView((View) mavContainer.getView());
			}
			if (model instanceof RedirectAttributes) {
				Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
			return mav;
		}
	}

这里面第一段就是获取异常对应的异常处理方法,获取到了之后就去执行了

getExceptionHandlerMethod
		private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
			new LinkedHashMap<>();

	protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
			@Nullable HandlerMethod handlerMethod, Exception exception) {

		Class<?> handlerType = null;

		if (handlerMethod != null) {
			// Local exception handler methods on the controller class itself.
			// To be invoked through the proxy, even in case of an interface-based proxy.
			handlerType = handlerMethod.getBeanType();
            //this.exceptionHandlerCache 这个map维护了controller类中加了@ExceptionHandler注解方法的关系
			ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
			if (resolver == null) {
				resolver = new ExceptionHandlerMethodResolver(handlerType);
				this.exceptionHandlerCache.put(handlerType, resolver);
			}
			Method method = resolver.resolveMethod(exception);
			if (method != null) {
				return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
			}
			// For advice applicability check below (involving base packages, assignable types
			// and annotation presence), use target class instead of interface-based proxy.
			if (Proxy.isProxyClass(handlerType)) {
				handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
			}
		}
        //上面其实主要是对controller类内部加了@ExceptionHandler注解方法来进行处理了,不算全局异常处理
        //下面其实就是对全局异常来进行处理了
		
        //this.exceptionHandlerAdviceCache这个map维护了@ControllerAdvice的类中所有修饰了@ExceptionHandler注解方法的关系
        //map的key为修饰了@ControllerAdvice的类,value为ExceptionHandlerMethodResolver对象,内部有一个methods
		for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
			ControllerAdviceBean advice = entry.getKey();
            //检测下当前全局异常处理类能否处理当前handler中的请求,一般都是允许的
			if (advice.isApplicableToBeanType(handlerType)) {
				ExceptionHandlerMethodResolver resolver = entry.getValue();
                //resolver中不是有mathods吗,找到和异常相匹配的方法返回就可以了
				Method method = resolver.resolveMethod(exception);
				if (method != null) {
                    //advice.resolveBean()返回的就是全局异常处理类的bean对象
					return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
				}
			}
		}

		return null;
	}

可以从源码中看出来获取异常方法主要有两个逻辑,第一是先找controller类内部有没有对应方法,如果没有再看下全局异常处理类中有没有对应的方法,哪个找到了就返回,至此,异常处理就结束了

render

​ 在最后的处理中,一定会涉及页面的跳转问题。而在render(mv, request, response); 完成了页面的跳转。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    // 确定请求的区域设置并将其应用于响应
    Locale locale =
          (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    // 获取视图名
    String viewName = mv.getViewName();
    //  如果viewname不为null,则需要通过viewName 解析出来对应的 View
    if (viewName != null) {
       // We need to resolve the view name.
       //解析视图名称
       view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
       if (view == null) {
          throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                "' in servlet with name '" + getServletName() + "'");
       }
    }
    // 如果viewName 为null,则认为 ModelAndView 直接指定了View。不需要解析了。
    else {
       // No need to lookup: the ModelAndView object contains the actual View object.
       view = mv.getView();
       if (view == null) {
          throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                "View object in servlet with name '" + getServletName() + "'");
       }
    }

    // Delegate to the View object for rendering.
    if (logger.isTraceEnabled()) {
       logger.trace("Rendering view [" + view + "] ");
    }
    try {
       // 设置返回状态码
       if (mv.getStatus() != null) {
          response.setStatus(mv.getStatus().value());
       }
       // 调用View对象的render方法完成跳转逻辑
       view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
       if (logger.isDebugEnabled()) {
          logger.debug("Error rendering view [" + view + "]", ex);
       }
       throw ex;
    }
}

resolveViewName 是 通过视图解析器进行视图解析。返回合适视图。具体实现如下。

	// org.springframework.web.servlet.DispatcherServlet#resolveViewName
	@Nullable
	protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {

		if (this.viewResolvers != null) {
			// 遍历视图解析器,直到有解析器能解析出来视图
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
		return null;
	}

我们看一下 viewResolver.resolveViewName方法。这里我们看InternalResourceViewResolver#resolveViewName 方法,其方法是在父类AbstractCachingViewResolver#resolveViewName中实现,如下:

	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		// 如果没有缓存,则直接创建 View
		if (!isCache()) {
			return createView(viewName, locale);
		}
		else {
            //this.viewAccessCache 操作时不需要加锁的
            //this.viewCreationCache 操作时需要加锁的
			// 从不需要加锁的map缓存中获取视图
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
            //如果缓存中没有则加锁去创建,最后放到两个map中来
			if (view == null) {
				synchronized (this.viewCreationCache) {
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) {
						// Ask the subclass to create the View object.
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
						if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
						}
					}
				}
			}
			else {
				if (logger.isTraceEnabled()) {
					logger.trace(formatKey(cacheKey) + "served from cache");
				}
			}
            //返回视图
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}


createView 方法 被 UrlBasedViewResolver 重写了。UrlBasedViewResolver#createView具体如下:

protected View createView(String viewName, Locale locale) throws Exception {
		// If this resolver is not supposed to handle the given view,
		// return null to pass on to the next resolver in the chain.
    	// 如果当前视图解析器无法解析该视图,则返回null
		if (!canHandle(viewName, locale)) {
			return null;
		}

		// Check for special "redirect:" prefix.
    	// 处理前缀为  "redirect:" (重定向)的情况
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			RedirectView view = new RedirectView(redirectUrl,
					isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
				view.setHosts(hosts);
			}
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}

		// Check for special "forward:" prefix.
    	// 处理前缀为  "forward:" 的情况
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			InternalResourceView view = new InternalResourceView(forwardUrl);
			return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
		}

		// Else fall back to superclass implementation: calling loadView.
    	// 调用父类的方法创建视图
		return super.createView(viewName, locale);
	}

super.createView(viewName, locale); 调用 AbstractCachingViewResolver#createView 如下:

	protected View createView(String viewName, Locale locale) throws Exception {
		return loadView(viewName, locale);
	}

	// org.springframework.web.servlet.view.UrlBasedViewResolver#loadView
	@Override
	protected View loadView(String viewName, Locale locale) throws Exception {
		AbstractUrlBasedView view = buildView(viewName);
		View result = applyLifecycleMethods(viewName, view);
		return (view.checkResource(locale) ? result : null);
	}
	
	// org.springframework.web.servlet.view.UrlBasedViewResolver#buildView
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");

		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
		// 设置视图 url 添加前缀和后缀
		view.setUrl(getPrefix() + viewName + getSuffix());
		view.setAttributesMap(getAttributesMap());

		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}

		String requestContextAttribute = getRequestContextAttribute();
		if (requestContextAttribute != null) {
			view.setRequestContextAttribute(requestContextAttribute);
		}

		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
			view.setExposePathVariables(exposePathVariables);
		}
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}

这里设置了视图的url,拼接了前缀和后缀,最后返回。

​ 接下来继续回到DispatcherServlet中的render方法中,拿到了view对象,调用view对象的render方法,在 org.springframework.web.servlet.view.AbstractView#render 中完成了视图跳转。对于ModelView 的使用,我们可以将一些属性放入其中,然后在页面上通过 JSTL 语法或者 request 获取属性,这个功能的实现就是在这里完成的。实现原理很简单,就是将要用到的属性方法request中,以便在其他地方可以获取到。

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isDebugEnabled()) {
			logger.debug("View " + formatViewName() +
					", model " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
		}

		// 合并模型
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		// 如果当前请求为下载的话,预先处理请求头
		prepareResponse(request, response);
		// 处理页面跳转。同时将 mergedModel  保存到request中
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Expose the model object as request attributes.
		// 曝光模型
		exposeModelAsRequestAttributes(model, request);

		// Expose helpers as request attributes, if any.
		// 空的模板方法 //todo
		exposeHelpers(request);

		// Determine the path for the request dispatcher.
		// 获取转发路径
		String dispatcherPath = prepareForRendering(request, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		// 获取可应用于 forward/include 的RequestDispatche
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}

		// If already included or response already committed, perform include, else forward.
		// 处理include
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including [" + getUrl() + "]");
			}
			rd.include(request, response);
		}

		// 处理转发
		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to [" + getUrl() + "]");
			}
			rd.forward(request, response);
		}
	}
	
	protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {

		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

​ 可以看到在view对象中又调用了renderMergedOutputModel方法,这个方法里首先会曝光模型,就是将我们向model中存放的数据给放到request域中,这样jsp页面就可以通过requestScope来获取了,最后转发到相应页面即可

总结

  1. 用户向服务器发送HTTP请求,请求被Spring中的DispatcherServlet给捕获,最后会进入service方法中来进行处理
  2. dispatcherServlet先通过handlerMapping获取当前request对应的HandlerExecutionChain,也就是执行器链,里面包含了请求对应的目标方法和请求相匹配的拦截器
  3. dispatcherServlet根据获取的handler来选择指定的handlerAdapter,获取到以后首先通过handlerAdapter来执行拦截器中的preHandler方法
  4. 再通过handlerAdapter来执行目标handle方法,执行之前需要先解析参数,这一步需要参数解析器,会返回一个object类型的参数数组,接着反射执行目标方法,执行完以后,通过返回值解析器来解析返回值,最后返回ModelAndView对象
  5. 目标方法执行完以后再执行拦截器中的postHandle方法
  6. 如果目标方法执行出现了异常的话,就会去执行用户自定义的异常处理方法,常用的就是全局异常处理类@ControllerAdvice+@ExceptionHandler,主要是通过异常解析器来完成,因为异常解析器中已经维护了异常和指定方法的关系,key为异常,value为对应方法,调用即可
  7. 通过异常解析器来解析视图,包括对视图名的拼接,以及将model中的数据放到request域中,dispathcerServlet将解析好的视图返回给客户端
    在这里插入图片描述
    如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎各位大佬指正

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

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

相关文章

Excel-查找和引用数据-VLOOKUP 和 HLOOKUP 函数

在 Excel 中&#xff0c;VLOOKUP 和 HLOOKUP 是用于查找和引用数据的函数。下面是它们的基本用法&#xff1a; VLOOKUP 用途&#xff1a;在表格的第一列中查找某个值&#xff0c;并返回该值所在行的指定列中的数据。 语法&#xff1a; VLOOKUP(lookup_value, table_array, …

多模态大语言模型(MLLM)-Blip2深度解读

前言 Blip2是一个多模态大语言模型&#xff0c;因其提出时间较早&#xff08;2023年&#xff09;&#xff0c;且效果较好&#xff0c;很快成为一个标杆性工作。Blip2中提出的Q-former也成为衔接多模态和文本的重要桥梁。 Blip2发表时间是2023年&#xff0c;现在引用已经3288了…

产品经理内容分享(一):AI产品经理需必备那些能力

目录 必备的AI技术知识 第一章&#xff1a;AI产品经理是否需要懂技术及其程度 第二章&#xff1a;AI产品经理必备的AI技术基础知识——基础算法与机器学习方法 第三章&#xff1a;AI产品经理必须要懂的AI技术知识——场景应用 第四章&#xff1a;AI算法与模型的关系 第五…

PhotoMaker部署文档

一、介绍 PhotoMaker&#xff1a;一种高效的、个性化的文本转图像生成方法&#xff0c;能通过堆叠 ID 嵌入自定义逼真的人类照片。相当于把一张人的照片特征提取出来&#xff0c;然后可以生成你想要的不同风格照片&#xff0c;如写真等等。 主要特点&#xff1a; 在几秒钟内…

求1000以内的完数

题目&#xff1a;一个数如果恰好等于他的因子之和&#xff08;包括1&#xff0c;但不包括这个数&#xff09;&#xff0c;这个数就是完数。编写算法找出1000之内的所有完数&#xff0c;并按下面格式输出其因子&#xff1a;28 its factors are 1,2,4,7,14 代码如下&#xff1a;…

Dell服务器电源配置

Dell服务器电源配置规则 PowerEdge 电源设置

医院综合服务系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;患者管理&#xff0c;医生管理&#xff0c;就诊信息管理&#xff0c;科室信息管理&#xff0c;挂号信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;订单排队&#…

《PyTorch深度学习快速入门教程》学习笔记(第15周)

目录 摘要 Abstract 1. 安装Anaconda 2. 查看显卡驱动 3. 安装Pytorch 4. Pytorch加载数据 5. 常用数据集两种形式 6. 路径直接加载数据 7. Dataset加载数据 摘要 本周报的目的在于汇报《PyTorch深度学习快速入门教程》课程第一周的学习成果&#xff0c;主要聚焦于py…

微服务Sleuth解析部署使用全流程

目录 1、Sleuth链路追踪 1、添加依赖 2、修改日志配置文件 3、测试 2、zipkin可视化界面 1、docker安装 2、添加依赖 3、修改配置文件 4、查看页面 5、ribbon配置 1、Sleuth链路追踪 sleuth是链路追踪框架&#xff0c;用于在微服务架构下开发&#xff0c;各个微服务之…

轻松部署大模型:Titan Takeoff入门指南

轻松部署大模型&#xff1a;Titan Takeoff入门指南 在人工智能的快速发展中&#xff0c;处理自然语言处理&#xff08;NLP&#xff09;任务的大规模语言模型&#xff08;LLM&#xff09;至关重要。然而&#xff0c;部署这些模型往往具有挑战性&#xff0c;需要高性能的硬件和优…

设计模式之适配器模式(Adapter)

一、适配器模式介绍 适配器模式(adapter pattern )的原始定义是&#xff1a;将类的接口转换为客户期望的另一个接口&#xff0c; 适配器可以让不兼容的两个类一起协同工作。 适配器模式是用来做适配&#xff0c;它将不兼容的接口转换为可兼容的接口&#xff0c;让原本由于接口…

YOLOv10改进,YOLOv10添加CA注意力机制,二次创新C2f结构,助力涨点

改进前训练结果: 二次创新C2f结构训练结果: 摘要 在本文中,提出了一种新的移动网络注意力机制,将位置信息嵌入到信道注意力中称之为“协调注意力”。与渠道关注不同通过 2D 全局池将特征张量转换为单个特征向量,坐标注意力因子将通道注意力转化为两个 1D 特征编码过程…

如何在AI时代成为优秀的AI产品经理?全面解析与全套学习路径分享!!!

前言 在当前人工智能技术飞速发展的时代背景下&#xff0c;AI产品经理无疑成为了职场中的一片蓝海。随着AI技术在各行各业的广泛应用&#xff0c;AI产品经理的角色变得越来越重要&#xff0c;成为了众多求职者眼中的优质赛道。那么&#xff0c;如何在AI的大环境下成为一名优秀…

李宏毅深度学习-自注意力机制

输入是向量序列的情况 在图像识别的时候&#xff0c;假设输入的图像大小都是一样的。但如果问题变得复杂&#xff0c;如图6.2所示&#xff0c;输入是一组向量&#xff0c;并且输入的向量的数量是会改变的&#xff0c;即每次模型输入的序列长度都不一样&#xff0c;这个时候应该…

搬砖 网盘一键转存源码

网盘一键转存源码&#xff0c;免费资源没测试 网盘一键转存源码&#xff0c;可以将您的百度网盘资源一键转存到。并支持后台设置开屏广告 源码截图&#xff1a; 下载地址&#xff1a; https://yuncv.lanzouw.com/i8dZk2btyl4h

六自由度机械重力补偿控制

1.动力学方程 六自由度机械臂动力学方程形式如下&#xff1a; 进行重力补偿&#xff0c;就是在驱动力矩中对重力G进行补偿&#xff0c;从而消除重力的影响&#xff0c;这样就能够在进行闭环控制的时候避免重力影响带来的大超调问题&#xff0c;使得机器人更好的实现轨迹跟踪控…

如何使用BlinkShot.io生成照片

在当今的数字时代&#xff0c;AI生成照片已经成为一项令人惊叹的技术。而BlinkShot.io就是这样一个平台&#xff0c;它可以让你轻松生成各种类型的照片。以下是详细步骤&#xff0c;教你如何使用BlinkShot.io生成照片。 第一步&#xff1a;访问网站 首先&#xff0c;打开Blin…

python调用父类同名成员

语法 print(f"父类的厂商是&#xff1a;{Phone.producer}“) Phone.call_by_5g(self) print(f"父类的厂商是&#xff1a;{super().producer}”) print(f"父类的序列号是&#xff1a;{super().IMEI}") super().call_by_5g() print(“关闭CPU单核模式&…

AIGC下的数据战略,助力还是阻力?

AIGC下的数据战略&#xff0c;助力还是阻力&#xff1f; 前言一、生成式AI的崛起与影响二、企业数据战略的关键要点&#xff08;一&#xff09;找准应用方向&#xff0c;激发创新价值&#xff08;二&#xff09;准备专有数据&#xff0c;确保数据安全&#xff08;三&#xff09…

毕业设计项目(难度高)——文本驱动的可控人体动作生成方法(论文/代码)

完整的论文代码见文章末尾 以下为核心内容 摘要 本文实现了一种基于扩散模型的文本驱动的可控人体动作生成方法。本文利用先进的交叉模态线性变换器及细粒度控制技术&#xff0c;根据自然语言描述生成逼真的人体动作序列。扩散模型在生成高质量图像和视频方面有较大优点&…