SpringMVC源码分析(四)请求流程分析

news2024/11/17 3:42:50

a、http请求是怎么被Controller接受处理,然后返回结果的?

发出HTTP请求后,跳过网络层的东西,当被应用服务器Tomcat接受的时候。在Tomcat中存在一个servlet容器,它负责管理所有的servlet,包括SpringMVC的核心组件DispatcherServlet。
请求进入到HttpServlet的service方法中后。service方法会根据 HTTP 请求是 GET、POST、PUT 还是 DELETE 等,进一步调用 Servlet 的 doGet(),doPost(),doPut(),doDelete() 等方法。
1、HttpServlet# service()
2、FrameworkServlet# doGet() 或 doPost() 或 doPut() 或 doDelete()
3、FrameworkServlet# processRequest()

在这里插入图片描述
在这里插入图片描述

1、HttpServlet#service()

HttpServletRequest 和 HttpServletResponse 对象是由 Servlet 容器(Tomcat)在调用 Servlet 的 service() 方法之前创建和初始化的。
最终所有的请求都会走到FrameworkServlet的processRequest()中

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            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);
        }
    }

1.2 FrameworkServlet#doPost()

1.2 FrameworkServlet#doGet()

1.2 FrameworkServlet#doPut()

1.2 FrameworkServlet#doDelete()

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

		processRequest(request, response);
	}

	/**
	 * Delegate POST requests to {@link #processRequest}.
	 * @see #doService
	 */
	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

	/**
	 * Delegate PUT requests to {@link #processRequest}.
	 * @see #doService
	 */
	@Override
	protected final void doPut(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

	/**
	 * Delegate DELETE requests to {@link #processRequest}.
	 * @see #doService
	 */
	@Override
	protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

1.3 FrameworkServlet# processRequest()

在processRequest(request, response)方法中,主要执行了如下步骤:
【1】获得当前和最新request中的国际化上下文LocaleContext;
【2】获得当前和最新request中的请求参数RequestAttributes;
【3】初始化异步请求管理器WebAsyncManager;
【4】将最新request中的“国际化上下文”和“请求参数”设置到当前线程的上下文中,在整个doService共享,在finally后删除。
【5】doService()处理Http请求;
【6】将当前线程上下文中的“国际化上下文”和“请求参数”还原为之前的值;
【7】无论成功与否,都会发布ServletRequestHandledEvent事件;

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    
    // 针对【国际化上下文】,获得当前的LocaleContext和最新request请求中的LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); 
    LocaleContext localeContext = buildLocaleContext(request);

    // 针对【请求参数】,获得当前的RequestAttributes和最新request请求中的RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    // 初始化【异步请求管理器】
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // 将request中最新的“国际化上下文”和“请求参数”设置到当前线程的上下文中
    initContextHolders(request, localeContext, requestAttributes);
    
    try {
        /** 处理请求 */
        doService(request, response); 
    }
    catch (ServletException | IOException ex) {...}
    catch (Throwable ex) {...}
    finally {
        // 还原以前的国际化上下文和请求参数设置到当前线程的上下文中
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) requestAttributes.requestCompleted();

        // 无论成功与否,都会发布ServletRequestHandledEvent事件
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

2、doService(request, response)

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    Map<String, Object> attributesSnapshot = null;

    /** 在include请求时保存请求属性的快照,以便在include请求后恢复原始属性 */
    if (WebUtils.isIncludeRequest(request)) { 
    // 确定给定的请求是否是一个include请求(判断方法:request中是否存在“javax.servlet.include.request_uri”的属性参数)
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            // DEFAULT_STRATEGIES_PREFIX="org.springframework.web.servlet"
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) 
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
    }

    /** 设置request属性 */
    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);
    }
    RequestPath previousRequestPath = null;
    if (this.parseRequestPath) {
        previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
        ServletRequestPathUtils.parseAndCache(request); // request.setAttribute(PATH_ATTRIBUTE, requestPath);
    }

    /** 处理Http请求 */
    try {
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (attributesSnapshot != null) 
                restoreAttributesAfterInclude(request, attributesSnapshot); // include请求后恢复原始属性
        }
        if (this.parseRequestPath) 
            ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); // 还原请求参数中PATH_ATTRIBUTE的值
    }
}

3、doDispatch(request, response)

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请求,则将request转换为MultipartHttpServletRequest类型 */
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            /**根据request,寻找匹配的Handler */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response); // 如果没找到,则抛出异常或者返回404
                return;
            }
            
            /**根据Handler,寻找匹配的HandlerAdapter */
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) return;
            }

            /**调用已注册的拦截器列表interceptors的preHandle方法 */
            if (!mappedHandler.applyPreHandle(processedRequest, response)) return;
            
            /**处理请求的逻辑并返回ModelAndView */
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            
            if (asyncManager.isConcurrentHandlingStarted()) return;

            //如果没有视图View,则向mv中设置默认的视图名称
            applyDefaultViewName(processedRequest, mv);

            //调用已注册的拦截器列表interceptors的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
        //	省略代码
        }
        catch (Throwable err) {
        //省略代码
        }
        /**调用handler以及处理返回的结果,该结果要么是ModelAndView,要么就是一个需要解析到ModelAndView的异常 */
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
    //省略代码
    }
    catch (Throwable err) {
    //省略代码
    }
    finally {
    //省略代码
    }
}

3.1 getHandler(request)

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    /** 2.1.1 试图从request请求中获取handler实例 **/
    Object handler = getHandlerInternal(request);
    if (handler == null) handler = getDefaultHandler(); // 如果获取不到,则获取默认handler,
    if (handler == null) return null; // 如果仍然获取不到,则直接返回null
    
    // 如果上面获取到的header是字符串类型的beanName,则从IOC中获取到对应的bean
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }
    
    // 确保拦截器等的缓存查找路径(lookupPath)的存在
    if (!ServletRequestPathUtils.hasCachedPath(request)) 
        initLookupPath(request);

    /** 2.1.2 将配置中对应的拦截器加入到执行链中,以确保拦截器生效 */
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    // 针对CORS(跨域)请求,进行特殊处理
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = getCorsConfiguration(handler, request);
        if (getCorsConfigurationSource() != null) {
            CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
            config = (globalConfig != null ? globalConfig.combine(config) : config);
        }
        if (config != null) {
            config.validateAllowCredentials();
        }
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    
    return executionChain;
}

3.1.1 getHandlerInternal(request)

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    try {
        return super.getHandlerInternal(request); // 调用AbstractHandlerMethodMapping的getHandlerInternal方法
    }
    finally {
        ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}
> AbstractHandlerMethodMapping#getHandlerInternal(request)
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    /**【步骤1】从request中寻找请求路径 */
    String lookupPath = initLookupPath(request); // eg: lookupPath="/hello"
    this.mappingRegistry.acquireReadLock();
    try {
        /**【步骤2】通过lookupPath和request,获得请求待流转的方法handlerMethod */
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

3.1.2、initLookupPath()

// eg: lookupPath="/hello"
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    /**【步骤1】根据请求路径lookupPath来获得可以匹配处理的方法列表  */
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);

    /**【步骤2】从directPathMatches获取匹配的Match实例对象,并保存到matches集合中 */
    if (directPathMatches != null) 
         // 根据request对象,解析出http请求method、参数params,请求headers等信息来创建RequestMappingInfo对象,
         // 然后封装到Match对象中,并保存到matches中 
        addMatchingMappings(directPathMatches, matches, request);
    
    if (matches.isEmpty()) 
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);

    /**【步骤3】针对已匹配的Match集合对象进行处理 */
    if (!matches.isEmpty()) {
        Match bestMatch = matches.get(0);
        // 发现匹配上了两个相同的method,则进行特殊处理或者抛出异常
        if (matches.size() > 1) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            bestMatch = matches.get(0);

            if (CorsUtils.isPreFlightRequest(request)) 
                for (Match match : matches) 
                    if (match.hasCorsConfig()) return PREFLIGHT_AMBIGUOUS_MATCH;    
            else {
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.getHandlerMethod().getMethod();
                    Method m2 = secondBestMatch.getHandlerMethod().getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
        }
        // eg: bestMatch.getHandlerMethod()=com.muse.springbootdemo.controller.DemoController#hello()
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
        handleMatch(bestMatch.mapping, lookupPath, request); // 对request进行附加属性赋值
        return bestMatch.getHandlerMethod(); // 返回com.muse.springbootdemo.controller.DemoController#hello()
    }

    /**【步骤4】没有找到已匹配的Match集合对象,则进行异常抛出等操作 */
    else return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
> getMappingsByDirectPath()
// eg:urlPath="/hello"
public List<T> getMappingsByDirectPath(String urlPath) {
    return this.pathLookup.get(urlPath); // eg:return {GET [/hello]}
}

3.1.3、getHandlerExecutionChain(handler, request)

【Object handler】真正处理请求的headler;
【List interceptorList】interceptor拦截器列表;

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    //【步骤1】要么强转HandlerExecutionChain类型,要么新建HandlerExecutionChain实例对象
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    //【步骤2】遍历adaptedInterceptors,将符合条件的拦截器加入到chain中
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(request)) 
                chain.addInterceptor(mappedInterceptor.getInterceptor());
        }
        else chain.addInterceptor(interceptor);
    }
    return chain;
}

实现一个自定义的请求拦截器时,可以通过实现HandlerInterceptor接口的方式,这个接口一共有3个方法,分别是针对处理请求之前进行拦截操作的preHandle方法,以及针对处理请求之后进行拦截操作的postHandle方法,和在所有请求处理完毕之后进行额外操作afterCompletion方法,代码如下所示:

public interface HandlerInterceptor {
    // 在处理请求之前进行额外操作
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        return true;
    }

    // 在处理请求之后进行额外操作
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {}

    // 在所有请求处理完毕之后进行额外操作
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {}

}

3.2 getHandlerAdapter(request)

我们会通过逐一遍历根据handlerAdapters集合,通过调用HandlerAdapter的supports(handler)方法,来获取与入参handler相匹配的HandlerAdapter,只要找到了匹配的HandlerAdapter,直接返回即可,不再继续遍历对比.
handlerAdapters默认包含4个HandlerAdapter,分别是:
RequestMappingHandlerAdapter
HandlerFunctionAdapter
HttpRequestHandlerAdapter
SimpleControllerHandlerAdapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) 
        for (HandlerAdapter adapter : this.handlerAdapters) 
            if (adapter.supports(handler)) // 寻找到匹配的adapter
                return adapter; // eg: RequestMappingHandlerAdapter@7511

    // 如果没有找到HandlerAdapter,则抛出异常
    throw new ServletException(...);
}

HandlerAdapter接口

public interface HandlerAdapter {
    // 是否匹配该HandlerAdapter
    boolean supports(Object handler);

    // 处理请求,返回结果ModelAndView
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    // 用于请求缓存,已作废
    @Deprecated
    long getLastModified(HttpServletRequest request, Object handler);
}

3.3 handle()

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

3.3.1 handleInternal()

在该方法中,主要是执行了两个步骤:
【步骤1】调用invokeHandlerMethod(request, response, handlerMethod)方法真正的执行请求处理;
【步骤2】对于Header不包含 “Cache-Control”的情况进行特殊处理。

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

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}
3.3.1.1 invokeHandlerMethod()

【1】进行实例对象的创建及赋值操作,包含:webRequest、binderFactory、modelFactory、invocableMethod、……
【2】通过invocableMethod.invokeAndHandle(webRequest, mavContainer)方法,进行请求处理;
【3】通过getModelAndView(mavContainer, modelFactory, webRequest)方法,将执行结果封装为ModelAndView实例对象;

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, 
                                           HttpServletResponse response,
                                           HandlerMethod handlerMethod) throws Exception {
    // 创建webRequest
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 创建binderFactory和modelFactory
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        //  创建及设置invocableMethod
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) 
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        if (this.returnValueHandlers != null) 
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        // 创建及设置mavContainer
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 创建及设置asyncWebRequest
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // 创建及设置asyncManager
        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();
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }
        
        /** 处理请求,实际执行逻辑的地方 */
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        
        if (asyncManager.isConcurrentHandlingStarted()) return null;

        // 处理请求返回结果,获得ModelAndView
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}
1 invokeAndHandle()
public void invokeAndHandle(ServletWebRequest webRequest, 
                            ModelAndViewContainer mavContainer,
                            Object... providedArgs) throws Exception {
    /** 通过反射请求到具体的Controller上,并获得返回值 */
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

    // 根据ResponseStatus注释设置响应状态
    setResponseStatus(webRequest);

    // 如果没有返回值
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    // 如果存在返回相关的响应状态原因
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
    mavContainer.setRequestHandled(false);

    /** 针对利用HandlerMethodReturnValueHandler的handleReturnValue方法,对返回值进行处理 */
    try {
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer,  webRequest);
    } catch (Exception ex) {throw ex;}
}

/**
 * 根据ResponseStatus注释设置响应状态
 */
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
    // 获得请求响应状态,如果为null,则直接返回
    HttpStatus status = getResponseStatus();
    if (status == null) return;

    // 获得请求响应response,尝试为response设置失败信息(response.sendError)或者状态码(response.setStatus)
    HttpServletResponse response = webRequest.getResponse();
    if (response != null) {
        String reason = getResponseStatusReason();
        if (StringUtils.hasText(reason)) response.sendError(status.value(), reason);
        else response.setStatus(status.value());
    }
    
    // 被RedirectView获取
    webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
>>>>>> invokeForRequest()

对request请求中的参数进行解析,转换为方法的入参args,然后再采用反射的方式调用Controller类的所对应的处理方法,获得最终处理后的结果:

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    /** 解析出请求的入参 **/
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

    /** 利用反射,调用Controller类的所对应的处理方法 */
    return doInvoke(args);
}

/**
 * 请求参数解析
 */
protected Object[] getMethodArgumentValues(NativeWebRequest request, 
                                           ModelAndViewContainer mavContainer,
                                           Object... providedArgs) throws Exception {
    // 获得http请求中的参数列表,如果没有入参,则直接返回空的入参数组
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) return EMPTY_ARGS;
    
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        
        // 如果参数属于providedArgs类型,则跳过,不进行解析
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) continue;

        /** 如果所有resolver解析器都不能解析的话,则直接抛出异常 */
        if (!this.resolvers.supportsParameter(parameter)) throw new IllegalStateException(...);

        /** 进行参数解析操作 */
        try {
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        } catch (Exception ex) {throw ex;}
    }
    return args;
}

/**
 * 通过反射,执行逻辑调用
 */
protected Object doInvoke(Object... args) throws Exception {
    // 获得被桥接的方法,即:用户自定义的方法
    Method method = getBridgedMethod();
    try {
        if (KotlinDetector.isSuspendingFunction(method)) 
            return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);

        // 利用反射,调用Controller类中相应的method方法
        return method.invoke(getBean(), args);
    }
    catch (IllegalArgumentException ex) {...}
    catch (InvocationTargetException ex) {...}
}
>>>>>>> supportsParameter(parameter)

该方法主要确定这个入参我们的解析器是否支持解析

public boolean supportsParameter(MethodParameter parameter) {
    // 获取方法参数解析器
    return getArgumentResolver(parameter) != null;
}
>>>>>>> resolveArgument()

在这个方法会依次遍历每个解析器的 supportsParameter(parameter) 方法来寻找可以解析入参parameter的具体解析器实现类resolver,如果找到了主要判断parameter参数是否有解析器可以对其进行解析;

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    //【步骤1】获取方法参数解析器
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) 
        throw new IllegalArgumentException("Unsupported parameter type ...");

    //【步骤2】执行解析操作
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
>>>>>>>> getArgumentResolver() 获取入参解析器

RequestParamMethodArgumentResolver:负责处理@RequestParam
RequestHeaderMethodArgumentResolver:负责处理@RequestHeader
SessionAttributeMethodArgumentResolver:负责处理@SessionAttribute
RequestAttributeMethodArgumentResolver:负责处理@RequestAttribute
RequestResponseBodyMethodProcessor:负责处理@RequestBody

在这里插入图片描述

/**
 * 获得可以解析parameter参数的方法参数解析器(HandlerMethodArgumentResolver)
 */
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 如果之前解析过,则直接从缓存中获取
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);

    if (result == null) {
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            if (resolver.supportsParameter(parameter)) { // supportsParameter方法是由子类实现的
                result = resolver;
                this.argumentResolverCache.put(parameter, result); // 保存到缓存中
                break;
            }
        }
    }
    return result; 
}
>>>>>> handleReturnValue()

主要寻找可以对结果进行处理的handler实例对象;然后调用handler的handleReturnValue()方法来进行结果的处理。
HandlerMethodReturnValueHandler来处理返回值:
RequestResponseBodyMethodProcessor:处理加了@ResponseBody注解的情况
ViewNameMethodReturnValueHandler:处理没有加@ResponseBody注解并且返回值类型为String的情况
ModelMethodProcessor:处理返回值是Model类型的情况
…等等其他得默认的一些处理器
在这里插入图片描述

public void handleReturnValue(Object returnValue, 
                              MethodParameter returnType,
                              ModelAndViewContainer mavContainer, 
                              NativeWebRequest webRequest) throws Exception {
    /** 选择可以对结果进行解析的解析器 */
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) throw new IllegalArgumentException("Unknown return value type: " + ...);

    /** 具体解析操作,由子类负责 */
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

/** 
 * 选择可以对结果进行解析的解析器
 */
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) 
            continue;
        if (handler.supportsReturnType(returnType)) // 由子类负责实现supportsReturnType方法
            return handler;
    }
    return null;
}
>>>>>>>>>>RequestResponseBodyMethodProcessor#handleReturnValue()

RequestResponseBodyMethodProcessor会处理加了@ResponseBody注解的情况,也是常用的一种。会直接方法返回的结果直接响应给浏览器,返回的结果可能是字符串 ;可能是对象; 可能是Map或者其他形式的。

@ResponseBody
public String test() {
	return "张三";
}
>------------------>默认的4个MessageConverter

SpringMVC会利用HttpMessageConverter来处理;

  1. ByteArrayHttpMessageConverter:处理返回值为字节数组的情况,把字节数组返回给浏览器
  2. StringHttpMessageConverter:处理返回值为字符串的情况,把字符串按指定的编码序列号后返回给浏览器
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
    HttpHeaders headers = outputMessage.getHeaders();
    if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
        headers.setAcceptCharset(getAcceptedCharsets());
    }
    Charset charset = getContentTypeCharset(headers.getContentType());
    StreamUtils.copy(str, charset, outputMessage.getBody());
}
  1. SourceHttpMessageConverter:处理返回值为XML对象的情况,比如把DOMSource对象返回给浏览器
  2. AllEncompassingFormHttpMessageConverter:处理返回值为MultiValueMap对象的情况
>------------------>特殊的MappingJackson2HttpMessageConverter

MappingJackson2HttpMessageConverter,这个Converter比较强大,能把String、Map、User对象等等都能转化成JSON格式。

@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
		messageConverter.setDefaultCharset(StandardCharsets.UTF_8);
		converters.add(messageConverter);
	}
}
@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

3.4 processDispatchResult()

private void processDispatchResult(HttpServletRequest request,
                                   HttpServletResponse response,
                                   HandlerExecutionChain mappedHandler, 
                                   ModelAndView mv,
                                   Exception exception) throws Exception {
    boolean errorView = false;

    //【步骤1】如果出现了异常,则进行异常处理
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) 
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    //【步骤2】如果存在mv,则对mv进行渲染操作
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response); /** 执行页面渲染操作 */
        if (errorView) WebUtils.clearErrorRequestAttributes(request);
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) return;
    
    if (mappedHandler != null) 
        mappedHandler.triggerAfterCompletion(request, response, null);
}

3.4.1 processHandlerException(request, response, handler, exception)

具体流程见异常处理描述

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...
		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;
	}
@Override
	@Nullable
	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;
		}
	}

3.4.2 render(mv, request, response)

尝试获得View实例对象;然后对view对象页面渲染

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();
		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() + "'");
			}
		}
		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(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}
3.4.2.1 resolveViewName()

通过resolveViewName()来创建view视图对象,并将其加入到IOC中,其具体实现方式还是遍历每一个视图解析器(ViewResolver),调用其resolverViewName(viewName, locale)方法,尝试获得View视图实例对象,如果获得到了,则直接返回,不需要继续遍历其他的视图解析器了

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;
	}

在这里插入图片描述

@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		if (!isCache()) {
			return createView(viewName, locale);
		}
		else {
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			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.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()

最终创建视图是通过loadView()

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

@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);
	}
loadView()->buildView(viewName)
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");

		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
		view.setUrl(getPrefix() + viewName + getSuffix());

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

		view.setRequestContextAttribute(getRequestContextAttribute());
		view.setAttributesMap(getAttributesMap());

		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;
	}
loadView()->applyLifecycleMethods(viewName, view)

initializeBean() ioc的知识了

protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
		ApplicationContext context = getApplicationContext();
		if (context != null) {
			Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
			if (initialized instanceof View) {
				return (View) initialized;
			}
		}
		return view;
	}
3.4.2.2 view.render(mv.getModelInternal(), request, response)
@Override
	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);
		/** 将渲染后的视图合并到输出流中(可由子类自定义实现)*/
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}
1 prepareResponse()
/**
 * 试图View被渲染前的准备操作
 */
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
    setResponseContentType(request, response); // 设置response响应的ContentType
    response.setCharacterEncoding(this.encoding.getJavaName()); // 设置response响应的CharacterEncoding
    if (this.disableCaching) response.addHeader("Cache-Control", "no-store"); // 设置response响应的Cache-Control
}
2 renderMergedOutputModel()
/**
 * 将渲染后的视图合并到输出流中
 */
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
    ByteArrayOutputStream temporaryStream = null;
    OutputStream stream;
    //【步骤1】获得相应的输出流
    if (this.updateContentLength) {
        temporaryStream = createTemporaryOutputStream();
        stream = temporaryStream;
    }
    else stream = response.getOutputStream();

    //【步骤2】试图serializationView和filters包装在MappingJacksonValue实例对象中
    Object value = filterAndWrapModel(model, request);

    //【步骤3】将渲染的视图value保存到输出流stream中
    writeContent(stream, value);
    if (temporaryStream != null) 
        writeToResponse(response, temporaryStream);
}

3、总结流程图

在这里插入图片描述

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

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

相关文章

MySQL绕过WAF实战技巧

一、前言 本人喜欢遇到好的东西&#xff0c;乐于分享&#xff0c;关注freebuf有段时间了&#xff0c;写过两篇文章&#xff0c;每次写写文章&#xff0c;不仅仅是为了挣点稿费。而是通过此平台能够认识一些安全圈的小伙伴&#xff0c;互相学习&#xff0c;共同进步。在安全行业…

为什么不可大张旗鼓地推动“汉字编程”?

为什么不可大张旗鼓地推动“汉字编程”&#xff1f; 没有不可。 我之前看到过一个vscode插件&#xff0c;是给一个不知道叫什么名字的编程语言用的&#xff0c;从代码到注释全是西里尔字母写的&#xff0c;反正就只有东欧那片区域用。最近很多小伙伴找我&#xff0c;说想要一些…

c++实现最大堆

前言 在写leetcode的时候&#xff0c;看到一道优先队列的题目&#xff0c;复习了一下最大堆&#xff0c;用c实现了一下。以前听网课的时候&#xff0c;根本看不懂实现&#xff0c;现在自己也能实现了。 参考文献 这个我觉得讲得挺好的&#xff0c;图很生动形象 代码 #incl…

2023年京东洗发护发行业增长趋势分析:头皮清洁或成小风口

如今&#xff0c;随着消费观念的转变&#xff0c;越来越多的消费者愈加重视头部的洗护&#xff0c;无论是女性还是男性&#xff0c;都开始积极寻找头部洗护用品&#xff0c;以更好地呵护头发及头皮。在用户需求的推动下&#xff0c;洗发护发行业已经逐渐发展成为成熟行业。 根据…

linux性能分析(二)如何从日志分析 PV、UV

一 如何从日志分析 PV、UV 本文是从业务侧来衡量整个应用系统的性能,区别与上篇的网络性能分析备注&#xff1a; 这里的日志不仅指的是业务类型日志,也包括系统日志等各种类型的日志关键&#xff1a; 掌握PV和UV的概念和度量方式 "以下是关于埋点的科普文章" 埋…

不会代码循环断言如何实现?只要6步!

对于使用jmeter工具完成接口测试的测试工程师而言。在工作中&#xff0c;或者在面试中&#xff0c;都会遇到一个问题—— “CSV文档做了一大笔测试数据后&#xff0c;怎么去校验这个结果呢&#xff1f;” 现在大部分测试工程师可能都是通过人工的方法去查看结果&#xff0c;十几…

喜报!迅镭激光荣膺“江苏省智能制造领军服务机构”!

近日&#xff0c;“2023江苏省智能制造领军服务机构”名单揭晓&#xff0c;迅镭激光凭借在智能制造领域的强劲实力和突出的行业影响力位列其中&#xff0c;摘得该项殊荣。 近年来&#xff0c;智能制造正在成为全球传统工业和制造业转型升级的主要方向&#xff0c;越来越多的企业…

关于HBuilder X配置微信小程序开发的整体解决方案

配置小程序ID 配置微信小程序开发工具路径 [微信小程序开发者工具] initialize 问题解决方案 错误原因&#xff1a;微信开发者工具没有开启服务端口&#xff0c;无法使用命令行调试 解决办法&#xff1a;开启微信开发者工具的服务端口( 开发微信开发者工具 --> 设置 -->…

​蔚来自动驾驶,从 2020 年开始讲起的故事

2020 年底&#xff0c;摆脱 2019 年阴霾的李斌先生&#xff0c;热情而兴奋&#xff0c;再一次说&#xff1a;「欢迎来到蔚来日。」 那天蔚来发布了令人咋舌的智能驾驶硬件系统&#xff0c;4 块当时甚至还没有宣布量产日期的 Orin 芯片&#xff0c;11 路高清摄像头。 早在 ET7…

2023年9款好用的在线流程图软件推荐!

随着互联网技术和基础设施的发展&#xff0c;人们能用上比过去更加稳定的网络&#xff0c;因此在使用各类工具软件时&#xff0c;越来越倾向于选择在线工具&#xff0c;或是推出了网页版的应用。 就流程图软件而言&#xff0c;过去想要绘制流程图&#xff0c;我们得在电脑上安…

Git——解决 TortoiseGit 提示 No supported authentication methods available 错误

快速导航 问题描述解决方案步骤1 打开Git 的 Settings窗口步骤2 选择Network步骤3 选择安装目录usr\bin 下的 ssh.exe 文件 问题描述 git 推送&#xff08;push&#xff09;后提示No supported authentication methods available 错误 解决方案 步骤1 打开Git 的 Settings窗…

美国科技消费品公司Society Brands完成2500万美元融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于美国俄亥俄州坎顿的科技消费品公司Society Brands今日宣布已完成2500万美元融资。 本轮融资由Gullane Capital领投&#xff0c;Callais Capital和North Coast Ventures跟投。 该公司打算利…

天猫店铺商品评论数据采集,天猫商品评论数据接口,天猫API接口

天猫店铺商品评论数据接口可以获取到商品ID&#xff0c;商品标题&#xff0c;商品优惠券&#xff0c;商品到手价&#xff0c;商品价格&#xff0c;商品优惠价&#xff0c;商品sku属性&#xff0c;商品图片&#xff0c;商品视频&#xff0c;商品sku属性图片&#xff0c;商品属性…

WebMagic抓取医院科室,医生信息实战及踩坑

简介 WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现&#xff0c;而扩展部分则包括一些便利的、实用性的功能。WebMagic的架构设计参照了Scrapy&#xff0c;目标是尽量的模块化&#xff0c;并体现爬虫的功能特点。 WebMagic概…

支持PC端、手机端、数据大屏端的Spring Cloud智慧工地云平台源码

技术架构&#xff1a;微服务JavaSpring Cloud VueUniApp MySql 智慧建筑工地云平台主要利用大数据、物联网等技术&#xff0c;整合工地信息、材料信息、工程进度等&#xff0c;实现对建筑项目的全程管理。它可以实现实时监测和控制&#xff0c;有效解决施工中的问题&#xff0c…

基于springboot的网上商城设计与实现(包调试+LW)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等&#xff0c;今天给大家介绍一篇基于springbo…

【EI会议征稿】2024年遥感技术与测量测绘国际学术会议(RSTSM 2024)

2024年遥感技术与测量测绘国际学术会议&#xff08;RSTSM 2024&#xff09; 2024 International Conference on Remote Sensing Technology and Survey Mapping 2024年遥感技术与测量测绘国际学术会议&#xff08;RSTSM 2024&#xff09;将在2024年1月12-14日于吉林长春召开。…

耐心使用FPmarkets时间框架交易法,想亏钱都难

很多投资者在交易中极易喜欢使用热门工具&#xff0c;结果不仅没有盈利还把自己的本金亏进去&#xff0c;今天FPmarkets就分享时间框架交易法&#xff0c;想亏钱都难。 FPmarkets积累了处理不同时间框架的经验&#xff0c;并了解在此基础上的工具和指标在较短的时间框架内更为敏…

java--关键字、标识符

1.关键字 1.java语言自己用到的一些词&#xff0c;有特殊作用的&#xff0c;我们称之为关键字&#xff0c;如&#xff1a;public、class、int、double... 2.注意&#xff1a;关键字是java用了的&#xff0c;我们就不能用来做为&#xff1a;类名、变量名&#xff0c;否则会报错…

web前端面试-- 手写原生Javascript方法(new、Object.create)

web面试题 本人是一个web前端开发工程师&#xff0c;主要是vue框架&#xff0c;整理了一些面试题&#xff0c;今后也会一直更新&#xff0c;有好题目的同学欢迎评论区分享 ;-&#xff09; web面试题专栏&#xff1a;点击此处 手动实现Object.create 通过Object.create&#…