主頁 > 後端開發 > 精盡Spring MVC原始碼分析 - HandlerAdapter 組件(一)之 HandlerAdapter

精盡Spring MVC原始碼分析 - HandlerAdapter 組件(一)之 HandlerAdapter

2020-12-18 06:56:00 後端開發

該系列檔案是本人在學習 Spring MVC 的原始碼程序中總結下來的,可能對讀者不太友好,請結合我的原始碼注釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀

Spring 版本:5.2.4.RELEASE

該系列其他檔案請查看:《精盡 Spring MVC 原始碼分析 - 文章導讀》

HandlerAdapter 組件

HandlerAdapter 組件,處理器的配接器,因為處理器 handler 的型別是 Object 型別,需要有一個呼叫者來實作 handler 是怎么被執行,Spring 中的處理器的實作多變,比如用戶的處理器可以實作 Controller 介面或者 HttpRequestHandler 介面,也可以用 @RequestMapping 注解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器,所以這里需要一個處理器配接器,由它去執行處理器

由于 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,所以將這部分內容拆分成了五個模塊,依次進行分析:

  • 《HandlerAdapter 組件(一)之 HandlerAdapter》
  • 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》
  • 《HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver》
  • 《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》
  • 《HandlerAdapter 組件(五)之 HttpMessageConverter》

HandlerAdapter 組件(一)之 HandlerAdapter

先來回顧一下在 DispatcherServlet 中處理請求的程序中哪里使用到 HandlerMapping 組件,可以回到《一個請求的旅行程序》中的 DispatcherServletdoDispatch 方法中看看,如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   ModelAndView mv = null;
   // ... 省略相關代碼
   // <3> 獲得請求對應的 HandlerExecutionChain 物件(HandlerMethod 和 HandlerInterceptor 攔截器們)
   mappedHandler = getHandler(processedRequest);
   // ... 省略相關代碼
   // <4> 獲得當前 handler 對應的 HandlerAdapter 物件
   HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
   // ... 省略相關代碼
   // <6> 真正的呼叫 handler 方法,也就是執行對應的方法,并回傳視圖
   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
   // ... 省略相關代碼
}
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 [...");
}

通過遍歷 HandlerAdapter 組件們,判斷是否支持處理該 handler 處理器,支持則回傳該 HandlerAdapter 組件,注意,這里是通過一個一個的 HandlerAdapter 組件去判斷是否支持該處理器,如果支持則直接回傳這個 HandlerAdapter 組件,不會繼續下去,所以獲取處理器對應 HandlerAdapter 組件是有一定的先后順序的,默認是HttpRequestHandlerAdapter -> SimpleControllerHandlerAdapter -> RequestMappingHandlerAdapter

本文涉及到的內容適中,可以先查看我的總結

HandlerAdapter 介面

org.springframework.web.servlet.HandlerAdapter介面,處理器的配接器,去執行處理器,代碼如下:

public interface HandlerAdapter {
	/**
	 * 是否支持該處理器
	 */
	boolean supports(Object handler);

	/**
	 * 執行處理器,回傳 ModelAndView 結果
	 */
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

	/**
	 * 回傳請求的最新更新時間,如果不支持該操作,則回傳 -1 即可
	 */
	long getLastModified(HttpServletRequest request, Object handler);
}

HandlerAdapter 介面的體系結構如下:

沒有特別多?? 心里有點點欣慰,其中 RequestMappingHandlerAdapter 就是基于@RequestMapping 等注解的 HandlerMethod 的 HandlerMethodAdapter 實作類,名字都差不多

初始化程序

DispatcherServletinitHandlerAdapters(ApplicationContext context) 方法,會在 onRefresh 方法被呼叫,初始化 HandlerAdapter 組件,方法如下:

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;

    if (this.detectAllHandlerAdapters) {
        // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }

    // Ensure we have at least some HandlerAdapters, by registering
    // default HandlerAdapters if no other adapters are found.
    /**
     * 如果未獲得到,則獲得默認配置的 HandlerAdapter 類
     * {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter}
     * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter}
     * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter}
     */
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}
  1. 如果“開啟”探測功能,則掃描已注冊的 HandlerAdapter 的 Bean 們,添加到 handlerAdapters 中,默認開啟,這里會進行排序,可以通過實作 Order 介面設定排序值

  2. 如果“關閉”探測功能,則獲得 Bean 名稱為 "handlerAdapter" 對應的 Bean ,將其添加至 handlerAdapters

  3. 如果未獲得到,則獲得默認配置的 HandlerAdapter 類,呼叫 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是從 DispatcherServlet.properties 檔案中讀取 HandlerAdapter 的默認實作類,如下:

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

    可以看到對應的是 HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter 三個實作類,接下來就一個一個分析

HttpRequestHandlerAdapter

org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,實作 HandlerAdapter 介面,基于 HttpRequestHandler 介面的 HandlerAdapter 實作類,代碼如下:

public class HttpRequestHandlerAdapter implements HandlerAdapter {
	@Override
	public boolean supports(Object handler) {
		// 判斷是 HttpRequestHandler 型別
		return (handler instanceof HttpRequestHandler);
	}
    
	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// HttpRequestHandler 型別的呼叫
		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}
    
	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		// 處理器實作了 LastModified 介面的情況下
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}
}

//  org.springframework.web.HttpRequestHandler.java
@FunctionalInterface
public interface HttpRequestHandler {
	/**
	 * 處理請求
	 */
	void handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException;
}

邏輯比較簡單,如果這個處理器實作了 HttpRequestHandler 介面,則使用 HttpRequestHandlerAdapter 呼叫該處理器的 handleRequest(HttpServletRequest request, HttpServletResponse response) 方法去處理器請求,回傳 null

這種處理器如何配置呢?

可以回到《HandlerMapping 組件(四)之 AbstractUrlHandlerMapping》SimpleUrlHandlerMapping 或者 BeanNameUrlHandlerMapping 小節中的使用示例看看

SimpleControllerHandlerAdapter

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,實作 HandlerAdapter 介面,基于 Controller 介面的 HandlerAdapter 實作類,代碼如下:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {
	@Override
	public boolean supports(Object handler) {
		// <1> 判斷是 Controller 型別
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// <2> Controller 型別的呼叫
		return ((Controller) handler).handleRequest(request, response);
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		// 處理器實作了 LastModified 介面的情況下
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}
}

@FunctionalInterface
public interface Controller {
	/**
	 * 處理請求
	 */
	@Nullable
	ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

邏輯比較簡單,和 HttpRequestHandlerAdapter 差不多,如果這個處理器實作了 Controoler 介面,則使用 HttpRequestHandlerAdapter 呼叫該處理器的 handleRequest(HttpServletRequest request, HttpServletResponse response) 方法去處理器請求,直接回傳處理器執行后回傳 ModelAndView

這種處理器如何配置和 HttpRequestHandlerAdapter 相同,見上文描述

SimpleServletHandlerAdapter 實作類就不講述了,因為默認的 HandlerAdapter 實作類中沒有它

邏輯實作和 SimpleControllerHandlerAdapter 差不多,區別在于它判斷是否為 javax.servlet.Servlet 物件,是的話則呼叫其 service方法,回傳該方法執行后回傳的 ModelAndView 物件

AbstractHandlerMethodAdapter

org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter,實作 HandlerAdapter、Ordered 介面,繼承 WebContentGenerator 抽象類,基于 HandlerMethod 的 HandlerMethodAdapter 抽象類

構造方法

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
    /** 最低優先級 */
	private int order = Ordered.LOWEST_PRECEDENCE;

	public AbstractHandlerMethodAdapter() {
		// no restriction of HTTP methods by default
		// 呼叫 WebContentGenerator 類的構造方法
	    // 引數 restrictDefaultSupportedMethods 引數為 false ,表示不需要嚴格校驗 HttpMethod
		super(false);
	}
}

supports方法

實作 supports(Object handler) 方法,判斷是否支持該處理器,代碼如下:

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

protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
  • 處理器必須是 HandlerMethod 型別,也就是在《HandlerMapping 組件(三)之 AbstractHandlerMethodMapping》講到的通過 @RequestMapping 等注解方法所生成對應 HandlerMethod 物件
  • 還需要呼叫抽象方法 supportsInternal(HandlerMethod handlerMethod)判斷是否支持, 交由子類去實作,詳情見下文

handle方法

實作 handle(HttpServletRequest request, HttpServletResponse response, Object handler) 方法,用于處理請求,執行該處理器,代碼如下:

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

@Nullable
protected abstract ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
  • 如果該 HandlerAdapter 支持這個處理器,那么則會呼叫該方法去處理請求,執行這個處理器
  • 直接呼叫 handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 抽象方法,交由子類去實作,詳情見下文

getLastModified方法

實作 getLastModified(HttpServletRequest request, Object handler) 方法,獲得最后更新時間,代碼如下

@Override
public final long getLastModified(HttpServletRequest request, Object handler) {
    return getLastModifiedInternal(request, (HandlerMethod) handler);
}

protected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);
  • 直接呼叫getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod)抽象方法,交由子類去實作,詳情見下文

RequestMappingHandlerAdapter

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,實作 BeanFactoryAware、InitializingBean 介面,繼承 AbstractHandlerMethodAdapter 抽象類,基于 @RequestMapping 注解的 HandlerMethod 處理器的 HandlerMethodAdapter 實作類

構造方法

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {

	/**
	 * MethodFilter that matches {@link InitBinder @InitBinder} methods.
	 */
	public static final MethodFilter INIT_BINDER_METHODS = method ->
			AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
	/**
	 * MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods.
	 */
	public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
			(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
					AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));

	@Nullable
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;

	@Nullable
	private HandlerMethodArgumentResolverComposite argumentResolvers;

	@Nullable
	private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;

	@Nullable
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

	@Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

	@Nullable
	private List<ModelAndViewResolver> modelAndViewResolvers;

	private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

	private List<HttpMessageConverter<?>> messageConverters;

	private List<Object> requestResponseBodyAdvice = new ArrayList<>();

	@Nullable
	private WebBindingInitializer webBindingInitializer;

	private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("MvcAsync");

	@Nullable
	private Long asyncRequestTimeout;

	private CallableProcessingInterceptor[] callableInterceptors = new CallableProcessingInterceptor[0];

	private DeferredResultProcessingInterceptor[] deferredResultInterceptors = new DeferredResultProcessingInterceptor[0];

	private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();

	private boolean ignoreDefaultModelOnRedirect = false;

	private int cacheSecondsForSessionAttributeHandlers = 0;

	/**
	 * 是否對相同 Session 加鎖
	 */
	private boolean synchronizeOnSession = false;

	private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

	private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

	@Nullable
	private ConfigurableBeanFactory beanFactory;

	// ========== 快取 ==========
	private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<>(64);
	private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);
	private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>();
	private final Map<Class<?>, Set<Method>> modelAttributeCache = new ConcurrentHashMap<>(64);
	private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache = new LinkedHashMap<>();
    
    // ... 省略 getter、setter 方法
	public RequestMappingHandlerAdapter() {
		// 初始化 messageConverters
		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
		stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

		this.messageConverters = new ArrayList<>(4);
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(stringHttpMessageConverter);
		try {
			this.messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Error err) {
			// Ignore when no TransformerFactory implementation is available
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}
}

有許多的屬性,不著急理解,先列幾個主要的屬性物件:

  • HandlerMethodArgumentResolverComposite argumentResolvers:引數處理器組合物件
  • HandlerMethodReturnValueHandlerComposite returnValueHandlers:回傳值處理器組合物件
  • List<HttpMessageConverter<?>> messageConverters:HTTP 訊息轉換器集合物件
  • List<Object> requestResponseBodyAdvice: RequestResponseAdvice 集合物件

在構造方法中默認會添加了四個 HttpMessageConverter 物件,當然,默認還會添加其他的,例如 MappingJackson2HttpMessageConverter 為 JSON 訊息格式的轉換器

1.afterPropertiesSet 初始化方法

因為 RequestMappingHandlerAdapter 實作了 InitializingBean 介面,在 Sping 初始化該 Bean 的時候,會呼叫該方法,完成一些初始化作業,方法如下:

@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    // <1> 初始化 ControllerAdvice 相關
    initControllerAdviceCache();

    // <2> 初始化 argumentResolvers 屬性
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // <3> 初始化 initBinderArgumentResolvers 屬性
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // <4> 初始化 returnValueHandlers 屬性
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}
  1. 呼叫 initControllerAdviceCache() 方法,初始化 ControllerAdvice 相關,詳情見下文

  2. 初始化 argumentResolvers 屬性,呼叫 getDefaultArgumentResolvers() 方法,獲得默認的 HandlerMethodArgumentResolver 陣列,詳情見下文

  3. 初始化 initBinderArgumentResolvers 屬性,呼叫 getDefaultInitBinderArgumentResolvers() 方法,獲得默認的 HandlerMethodArgumentResolver 陣列,詳情見下文

  4. 初始化 returnValueHandlers 屬性,呼叫 getDefaultReturnValueHandlers() 方法,獲得默認的 HandlerMethodReturnValueHandler 陣列,詳情見下文

1.1 initControllerAdviceCache

initControllerAdviceCache() 方法,初始化 ControllerAdvice 相關,方法如下:

private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }

    // <1> 掃描 @ControllerAdvice 注解的 Bean 們,生成對應的 ControllerAdviceBean 物件,并將進行排序
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);

    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

    // <2> 遍歷 ControllerAdviceBean 陣列
    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        // <2.1> 掃描有 `@ModelAttribute` ,無 `@RequestMapping` 注解的方法,添加到 `modelAttributeAdviceCache` 屬性中
		// 該類方法用于在執行方法前修改 Model 物件
        Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
        if (!attrMethods.isEmpty()) {
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
        }
        // <2.2> 掃描有 `@InitBinder` 注解的方法,添加到 `initBinderAdviceCache` 屬性中
		// 該類方法用于在執行方法前初始化資料系結器
        Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
        if (!binderMethods.isEmpty()) {
            this.initBinderAdviceCache.put(adviceBean, binderMethods);
        }
        // <2.3> 如果是 RequestBodyAdvice 或 ResponseBodyAdvice 的子類,添加到 requestResponseBodyAdviceBeans 中
        if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
    }

    // <2.3> 將 requestResponseBodyAdviceBeans 添加到 this.requestResponseBodyAdvice 屬性種
    if (!requestResponseBodyAdviceBeans.isEmpty()) {
        this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }

    // 列印日志
    if (logger.isDebugEnabled()) {
        int modelSize = this.modelAttributeAdviceCache.size();
        int binderSize = this.initBinderAdviceCache.size();
        int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
        int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
        if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
            logger.debug("ControllerAdvice beans: none");
        }
        else {
            logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
                    " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
        }
    }
}
  1. 從 Spring 背景關系掃描 @ControllerAdvice 注解的 Bean 們,生成對應的 ControllerAdviceBean 物件,并將進行排序,方法如下:

    public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
        return Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class))
                // 排除代理目標類,AOP 相關
                .filter(name -> !ScopedProxyUtils.isScopedTarget(name))
                // 包含 @ControllerAdvice 注解
                .filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null)
                // 生成對應的 ControllerAdviceBean 物件
                .map(name -> new ControllerAdviceBean(name, context))
                .collect(Collectors.toList());
    }
    

    @ControllerAdvice 注解:用于 Controller 類的增強類,其中可定義多種增強的方法,例如 @ExceptionHandler 注解的方法用于處理器 Controller 拋出的例外

  2. 遍歷 1 中生成 ControllerAdviceBean 陣列

    1. 掃描 @ModelAttribute @RequestMapping 注解的方法,添加到 modelAttributeAdviceCache 屬性中,該類方法用于在執行方法前修改 Model 物件
    2. 掃描 @InitBinder 注解的方法,添加到 initBinderAdviceCache 屬性中,該類方法用于在執行方法前初始化資料系結器
    3. 如果是 RequestBodyAdvice 或 ResponseBodyAdvice 的子類,保存至 requestResponseBodyAdviceBeans 臨時變數中
  3. 2.3 的 requestResponseBodyAdviceBeans 保存至 requestResponseBodyAdvice 屬性中

1.2 getDefaultArgumentResolvers

getDefaultArgumentResolvers(),初始化默認的引數決議器,方法如下:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

    // Annotation-based argument resolution
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    resolvers.add(new ModelMethodProcessor());
    resolvers.add(new MapMethodProcessor());
    resolvers.add(new ErrorsMethodArgumentResolver());
    resolvers.add(new SessionStatusMethodArgumentResolver());
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

    // Custom arguments
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    // Catch-all
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}
  • 按順序添加了非常多的引數決議器物件

1.3 getDefaultInitBinderArgumentResolvers

getDefaultInitBinderArgumentResolvers(),初始化默認的引數系結器,方法如下:

private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

    // Annotation-based argument resolution
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());

    // Custom arguments
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    // Catch-all
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

    return resolvers;
}

1.4 getDefaultReturnValueHandlers

getDefaultReturnValueHandlers(),初始化默認的回傳值處理器,方法如下:

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

    // Single-purpose return value types
    handlers.add(new ModelAndViewMethodReturnValueHandler());
    handlers.add(new ModelMethodProcessor());
    handlers.add(new ViewMethodReturnValueHandler());
    handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
            this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
    handlers.add(new StreamingResponseBodyReturnValueHandler());
    handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));
    handlers.add(new HttpHeadersReturnValueHandler());
    handlers.add(new CallableMethodReturnValueHandler());
    handlers.add(new DeferredResultMethodReturnValueHandler());
    handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

    // Annotation-based return value types
    handlers.add(new ModelAttributeMethodProcessor(false));
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));

    // Multi-purpose return value types
    handlers.add(new ViewNameMethodReturnValueHandler());
    handlers.add(new MapMethodProcessor());

    // Custom return value types
    if (getCustomReturnValueHandlers() != null) {
        handlers.addAll(getCustomReturnValueHandlers());
    }

    // Catch-all
    if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
        handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
    }
    else {
        handlers.add(new ModelAttributeMethodProcessor(true));
    }

    return handlers;
}
  • 按順序添加了非常多的回傳值處理器物件

supportsInternal 方法

實作 supportsInternal() 介面,方法如下:

@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    return true;
}

直接回傳 true,也就是說處理器只要是 HandlerMethod 物件就可以

getLastModifiedInternal 方法

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

直接回傳 -1

handleInternal 方法

實作 handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法,處理請求,執行處理器,方法如下:

@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, 
                                      HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
    // <1> 校驗請求(HttpMethod 和 Session 的校驗)
    checkRequest(request);
    // <2> 呼叫 HandlerMethod 方法
    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) { // 同步相同 Session 的邏輯,默認情況false
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 獲取Session的鎖物件
            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)) { // 回應不包含'Cache-Control'頭
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }
    return mav;
}
  1. 呼叫父類 WebContentGenerator 的 checkRequest(ttpServletRequest request) 方法,校驗請求(HttpMethod 和 Session)是否合法

    protected final void checkRequest(HttpServletRequest request) throws ServletException {
        // Check whether we should support the request method.
        String method = request.getMethod();
        if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
            throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
        }
        // Check whether a session is required.
        if (this.requireSession && request.getSession(false) == null) {
            throw new HttpSessionRequiredException("Pre-existing session required but none found");
        }
    }
    

    在 AbstractHandlerMethodAdapter 的構造方法中,傳入 restrictDefaultSupportedMethods 引數為 false,表示不需要嚴格校驗 HttpMethod,這里正常情況都會校驗通過

  2. 呼叫 invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法,執行 HandlerMethod 處理器

    這里會判斷 synchronizeOnSession 屬性,控制是否同步相同 Session 的邏輯,其中 WebUtils#getSessionMutex(session) 方法,獲得用來鎖的物件,方法如下:

    public static Object getSessionMutex(HttpSession session) {
        Assert.notNull(session, "Session must not be null");
        Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE);
        if (mutex == null) {
            mutex = session;
        }
        return mutex;
    }
    

    當然,因為鎖是通過 synchronized 是 JVM 行程級,所以在分布式環境下,無法達到同步相同 Session 的功能

    默認情況下,synchronizeOnSessionfalse

【重點】invokeHandlerMethod方法

invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法,執行 HandlerMethod 處理器,方法如下:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, 
                                           HandlerMethod handlerMethod) throws Exception {
    // <1> 創建 ServletWebRequest 物件
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // <2> 創建 WebDataBinderFactory 物件
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // <3> 創建 ModelFactory 物件
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // <4> 創建 ServletInvocableHandlerMethod 物件,并設定其相關屬性
        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);

        // <5> 創建 ModelAndViewContainer 物件,并初始其相關屬性
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // <6> 創建 AsyncWebRequest 異步請求物件
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // <7> 創建 WebAsyncManager 異步請求管理器物件
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        // <8>
        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);
        }

        // <9> 執行呼叫
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        // <10>
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        // <11> 獲得 ModelAndView 物件
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        // <12> 標記請求完成
        webRequest.requestCompleted();
    }
}

因為,Spring MVC 提供了大量的特性,所以 HandlerAdapter 又涉及許多組件,?? 我們主要先梳理好主流程,所以涉及的組件,還是先不詳細決議,我們的目的是,看到怎么呼叫 HandlerMethod 方法的,即呼叫 Controller 的 @RequestMapping 注解的方法,

  1. 創建 ServletWebRequest 物件,包含了 request 請求和 response回應

  2. 呼叫 getDataBinderFactory(HandlerMethod handlerMethod) 方法,創建 WebDataBinderFactory 物件,有關于資料系結,暫時忽略

  3. 呼叫 getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) 方法,創建 ModelFactory 物件,有關于往 Model 物件設定資料,暫時忽略

  4. 【核心】呼叫 createInvocableHandlerMethod(HandlerMethod handlerMethod) 方法,創建 ServletInvocableHandlerMethod 物件,然后設定其屬性,本文會對 ServletInvocableHandlerMethod 做簡單的決議, 詳細的決議在《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》

  5. 創建 ModelAndViewContainer 物件,并初始其相關屬性

  6. 創建 AsyncWebRequest 異步請求物件,暫時忽略

  7. 創建 WebAsyncManager 異步請求管理器物件,暫時忽略

  8. 異步處理,并發結果相關,暫時忽略

  9. 【核心】呼叫 ServletInvocableHandlerMethodinvokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) 方法,執行處理器,方法如下:

    // ServletInvocableHandlerMethod.java
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, 
                                Object... providedArgs) throws Exception {
        // <1> 執行呼叫
        Object returnValue = https://www.cnblogs.com/lifullmoon/archive/2020/12/17/invokeForRequest(webRequest, mavContainer, providedArgs);
        // <2> 設定回應狀態碼
        setResponseStatus(webRequest);
    
        // <3> 設定 ModelAndViewContainer 為請求已處理,回傳,和 @ResponseStatus 注解相關
        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;
        }
        // <4> 設定 ModelAndViewContainer 為請求未處理
        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null,"No return value handlers");
        try {
            // <5> 處理回傳值
            this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(formatErrorForReturnValue(returnValue), ex);
            }
            throw ex;
        }
    }
    
    // InvocableHandlerMethod.java
    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
    
        // <y> 決議引數
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }
        // 執行呼叫
        return doInvoke(args);
    }
    
    // InvocableHandlerMethod.java
    @Nullable
    protected Object doInvoke(Object... args) throws Exception {
        // <z1> 設定方法為可訪問
        ReflectionUtils.makeAccessible(getBridgedMethod());
        try {
            // <z2> 執行呼叫
            return getBridgedMethod().invoke(getBean(), args);
        } catch (IllegalArgumentException ex) {
            assertTargetBean(getBridgedMethod(), getBean(), args);
            String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
            throw new IllegalStateException(formatInvokeError(text, args), ex);
        } catch (InvocationTargetException ex) {
            // Unwrap for HandlerExceptionResolvers ...
            Throwable targetException = ex.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException) targetException;
            }
            else if (targetException instanceof Error) {
                throw (Error) targetException;
            }
            else if (targetException instanceof Exception) {
                throw (Exception) targetException;
            }
            else {
                throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
            }
        }
    }
    

    可以大致過一下上面的執行邏輯,決議引數,通過反射執行方法,決議執行結果,詳細決議在后續的《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》檔案中

  10. 異步處理,并發結果相關,暫時忽略

  11. 呼叫 getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) 方法,獲得 ModelAndView 物件,方法如下:

    @Nullable
    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    
        modelFactory.updateModel(webRequest, mavContainer);
        // 情況一,如果 mavContainer 已處理,則回傳“空”的 ModelAndView 物件,
        if (mavContainer.isRequestHandled()) {
            return null;
        }
        // 情況二,如果 mavContainer 未處理,則基于 `mavContainer` 生成 ModelAndView 物件
        ModelMap model = mavContainer.getModel();
        // 創建 ModelAndView 物件,并設定相關屬性
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            if (request != null) {
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
        }
        return mav;
    }
    
    • 情況一,如果 mavContainer 已處理,則回傳“空”的 ModelAndView 物件,@ResponseBody 注解的結果處理則直接回傳 null
    • 情況二,如果 mavContainer 未處理,則基于 mavContainer 生成 ModelAndView 物件

    在后續的檔案分析中會講到,注意這里的 requestHandled 屬性,到時候再回過頭來理解??

  12. 標記請求完成,暫時忽略

總結

Spring MVC 通過 HandlerMapping 組件會為請求找到合適的 HandlerExecutionChain 處理器執行鏈,包含處理器(handler)和攔截器們(interceptors),其中處理器的實作有多種,例如通過實作 Controller 介面、HttpRequestHandler 介面,或者使用 @RequestMapping 注解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器,所以這里需要一個處理器配接器,由它去執行處理器,

HandlerAdapter 處理器配接器對應的也有多種,那種配接器支持處理這種型別的處理器,則由該配接器去執行,如下:

  • HttpRequestHandlerAdapter:執行實作了 HttpRequestHandler 介面的處理器
  • SimpleControllerHandlerAdapter:執行實作了 Controller 介面的處理器
  • SimpleServletHandlerAdapter:執行實作了 Servlet 介面的處理器
  • RequestMappingHandlerAdapter:執行 HandlerMethod 型別的處理器,也就是通過 @RequestMapping 等注解標注的方法

這里我們重點看 RequestMappingHandlerAdapter 物件,因為這種方式是目前使用最普遍的,其他型別的 HandlerAdapter 處理器配接器做了解即可

本文講述了 RequestMappingHandlerAdapter 處理執行器的整個流程,大致邏輯如下:

  1. 通過 ServletInvocableHandlerMethodHandlerMethod 處理器的封裝)物件去執行
  2. 需要通過 HandlerMethodArgumentResolver 物件進行引數決議
  3. 通過反射執行對應的 Method 方法物件
  4. 需要通過 HandlerMethodReturnValueHandler 物件對執行結果進行處理,設定到 response 回應中,生成對應的 ModelAndView 物件

上面涉及到的三個組件分別在后續的檔案中進行決議,先整體,后區域??

參考文章:芋道原始碼《精盡 Spring MVC 原始碼分析》

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/236379.html

標籤:其他

上一篇:演算法相關問題

下一篇:一次突發流量引起的 Dubbo 服務擁堵!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more