天天看點

JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇

SpringMVC學習筆記

  • 源碼篇
    • 處理器擴充卡
      • 注冊流程
      • 處理流程
      • 參數綁定流程
      • 傳回值處理流程
      • ResponseBody注解解析
    • 視圖解析器
      • 注冊流程
      • 處理流程
  • 了解篇
    • 異常處理器
      • 異常了解
      • 異常處理器示範
    • 亂碼解決
      • GET請求亂碼
      • POST請求亂碼
      • 響應亂碼
    • 非注解開發方式
      • 處理器開發
      • 配置處理器映射器
      • 配置處理器擴充卡
      • 注意事項

源碼篇

處理器擴充卡

注冊流程

  • 分析源碼入口:

    RequestMappingHandlerAdapter#afterPropertiesSet

    方法:
@Override
public void afterPropertiesSet() {
	// Do this first, it may add ResponseBody advice beans
	// 初始化Controller類通知緩存
	initControllerAdviceCache();
	// 初始化參數解析器
	if (this.argumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
		this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	// 初始化處理器Binder參數解析器
	if (this.initBinderArgumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
		this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	// 初始化處理器傳回值解析器
	if (this.returnValueHandlers == null) {
		List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
		this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
	}
}
           
  • 以上方法,分别注冊了處理器增強類的相關操作(@ModelAttribute、@InitBinder、@ExceptionHandler)、參數解析器、initBinder參數解析器、傳回值處理器。這些注冊的參數解析器和傳回值處理器會在執行Handler方法時進行調用。
  • 進入

    initControllerAdviceCache

    方法看看:
private void initControllerAdviceCache() {
	if (getApplicationContext() == null) {
		return;
	}
	if (logger.isInfoEnabled()) {
		logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
	}
	
	// 查找到所有被ControllerAdvice注解的類
	List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
	AnnotationAwareOrderComparator.sort(adviceBeans);

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

	for (ControllerAdviceBean adviceBean : adviceBeans) {
		Class<?> beanType = adviceBean.getBeanType();
		if (beanType == null) {
			throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
		}
		// 擷取ControllerAdviceBean中帶有ModelAttribute注解但是不帶用RequestMapping注解的方法
		Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
		if (!attrMethods.isEmpty()) {
			this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
			if (logger.isInfoEnabled()) {
				logger.info("Detected @ModelAttribute methods in " + adviceBean);
			}
		}
		// 擷取ControllerAdviceBean中帶有InitBinder注解的方法
		Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
		if (!binderMethods.isEmpty()) {
			this.initBinderAdviceCache.put(adviceBean, binderMethods);
			if (logger.isInfoEnabled()) {
				logger.info("Detected @InitBinder methods in " + adviceBean);
			}
		}
		// 如果該ControllerAdviceBean是RequestBodyAdvice的子類,則将該類放入集合中
		if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
			requestResponseBodyAdviceBeans.add(adviceBean);
			if (logger.isInfoEnabled()) {
				logger.info("Detected RequestBodyAdvice bean in " + adviceBean);
			}
		}
		// 如果該ControllerAdviceBean是ResponseBodyAdvice的子類,則将該類放入集合中
		if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
			requestResponseBodyAdviceBeans.add(adviceBean);
			if (logger.isInfoEnabled()) {
				logger.info("Detected ResponseBodyAdvice bean in " + adviceBean);
			}
		}
	}

	if (!requestResponseBodyAdviceBeans.isEmpty()) {
		this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
	}
}
           
  • 記筆記:
RequestMappingHandlerAdapter#afterPropertiesSet()
  |--RequestMappingHandlerAdapter#initControllerAdviceCache():初始化控制器增強
  |--RequestMappingHandlerAdapter#getDefaultArgumentResolvers():初始化參數解析器
  |--RequestMappingHandlerAdapter#getDefaultInitBinderArgumentResolvers():初始化處理器Binder參數解析器
  |--RequestMappingHandlerAdapter#getDefaultReturnValueHandlers():初始化處理器傳回值解析器
           
  • 涉及到的類:
* RequestMappingHandlerAdapter
           

處理流程

  • 分析源碼入口:

    DispatcherServlet#doDispatch

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 接着進入

    AbstractHandlerMethodAdapter#handle

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

    RequestMappingHandlerAdapter#handleInternal

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 進入

    invokeHandlerMethod

    方法:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		// 擷取資料綁定工廠,作用是為了擷取WebDataBinder,WebDataBinder給參數進行類型轉換
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		// 設定參數解析器
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		// 設定傳回值解析器
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		invocableMethod.setDataBinderFactory(binderFactory);
		// parameterNameDiscoverer : 參數名稱發現者。通過反射可以很容易擷取參數的類型,但是參數名稱一般都和源碼的不一樣
		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

		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();
			if (logger.isDebugEnabled()) {
				logger.debug("Found concurrent result value [" + result + "]");
			}
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}
		
		// 調用HandlerMethod,執行請求并處理結果
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}
           
  • 進入

    ServletInvocableHandlerMethod#invokeAndHandle

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 到此為止,我們已經看完處理器的處理流程了。
  • 記筆記:
DispatcherServlet#doDispatch():ha.handle(...),以AbstractHandlerMethodAdapter為例
  |--AbstractHandlerMethodAdapter#handle(request, response, handler)
	|--RequestMappingHandlerAdapter#handleInternal(request, response, (HandlerMethod) handler)
	  |--RequestMappingHandlerAdapter#invokeHandlerMethod(request, response, handler)
		|--ServletInvocableHandlerMethod#invokeAndHandle(...):執行請求并處理結果
		  |--InvocableHandlerMethod#invokeForRequest(...):執行請求【參數綁定流程入口】
		  |--HandlerMethodReturnValueHandlerComposite#handleReturnValue(...):處理傳回值【傳回值處理流程入口】
           
  • 涉及到的類:
* DispatcherServlet
* AbstractHandlerMethodAdapter
* RequestMappingHandlerAdapter
* ServletInvocableHandlerMethod
* InvocableHandlerMethod
* HandlerMethodReturnValueHandlerComposite
           

參數綁定流程

  • 分析入口:

    InvocableHandlerMethod#invokeForRequest

    方法:
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	// 擷取方法參數值(這一步也是從request中将參數解析出來的過程)——已經進行類型轉換了
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
				"' with arguments " + Arrays.toString(args));
	}
	// 執行HandlerMethod方法,并擷取傳回值(也就是執行Controller類中的方法)
	Object returnValue = doInvoke(args);
	if (logger.isTraceEnabled()) {
		logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
				"] returned [" + returnValue + "]");
	}
	return returnValue;
}
           
  • 進入

    getMethodArgumentValues

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 重點看看

    HandlerMethodArgumentResolverComposite#resolveArgument

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 此處需要根據具體的參數類型調用相應的解析器,其中簡單類型調用的都是

    AbstractNamedValueMethodArgumentResolver

    類中的方法;POJO類型調用的都是

    ModelAttributeMethodProcessor

    類中的方法。我們就以簡單類型(對應

    AbstractNamedValueMethodArgumentResolver

    )為例講解一下具體的參數綁定過程:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	
	// NamedValueInfo對象封裝了方法參數的[方法名稱、是否必須、預設值]三個資訊
	// 相當于封裝了RequestParam注解(請求參數名稱、是否必須、預設值)的資訊
	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
	MethodParameter nestedParameter = parameter.nestedIfOptional();
	
	// 将方法參數名稱再處理一下
	Object resolvedName = resolveStringValue(namedValueInfo.name);
	if (resolvedName == null) {
		throw new IllegalArgumentException(
				"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
	}

	// 從request請求中解析出來指定key(參數名稱)的值,此時從request中解析出來的參數值都是String類型的。
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
	// 空值處理
	if (arg == null) {
		// 使用預設值去設定參數值
		if (namedValueInfo.defaultValue != null) {
			arg = resolveStringValue(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required && !nestedParameter.isOptional()) {
			handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
		}
		// 處理空值情況
		arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
	}
	else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
		arg = resolveStringValue(namedValueInfo.defaultValue);
	}

	// 使用WebDataBinder進行資料類型轉換
	// WebDataBinder會根據不同的參數,選擇不同的PropertyEditor進行參數類型轉換
	// 而大多數PropertyEditor都已經内置了,那麼如果需要自定義添加PropertyEditor的話,需要使用@InitBinder注解的方法進行處理
	if (binderFactory != null) {
		// 擷取web資料綁定器,主要作用是将request請求參數中的值,轉換成指定類型的Controller方法參數
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
			// 使用web資料綁定器,将String類型的請求資料,轉換成指定資料類型,并綁定到指定方法參數中
			// 此處有兩個arg變量,作為參數是String類型的,作為傳回值,已經變為了其他類型。
			// parameter參數,表示的是Controller方法的參數
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
		catch (ConversionNotSupportedException ex) {
			throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		catch (TypeMismatchException ex) {
			throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());

		}
	}

	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

	return arg;
}
           
  • 我們進入

    RequestParamMethodArgumentResolver#resolveName

    方法看看:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 至于參數綁定器的源碼比較複雜,我們就不深入追蹤了,我們隻需要知道參數轉換方式有兩種處理方式:

    PropertyEditor

    ConversionService

    ,以及它們處理參數轉換的差別就行了。下面代碼是在

    TypeConverterDelegate

    類中的:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 記筆記:
InvocableHandlerMethod#invokeForRequest(...)
  |--InvocableHandlerMethod#getMethodArgumentValues(request, mavContainer, providedArgs):參數解析
	|--HandlerMethod#getMethodParameters():得到方法參數
	|--HandlerMethodArgumentResolverComposite#supportsParameter(MethodParameter):看看參數解析器是否可以解析這個參數
	|--HandlerMethodArgumentResolverComposite#resolveArgument(...):執行參數解析
      |--HandlerMethodArgumentResolverCompositegetArgumentResolver(parameter):根據參數擷取具體的參數解析器
	  |--AbstractNamedValueMethodArgumentResolver#resolveArgument(...):調用具體的參數解析器進行解析,此處以簡單類型的為例
		|--AbstractNamedValueMethodArgumentResolver#getNamedValueInfo(parameter):擷取NamedValueInfo資訊【方法參數名稱、是否必須、預設值】
		|--AbstractNamedValueMethodArgumentResolver#resolveStringValue(value):解析出方法參數名稱
		|--RequestParamMethodArgumentResolver#resolveName(...):解析出請求參數中指定名稱的參數值,該參數此時是字元串類型
		|--DataBinder#convertIfNecessary(...):利用參數綁定器将字元串類型的請求參數值轉換成指定方法參數類型的值
		  |--TypeConverterSupport#convertIfNecessary(...)
			|--TypeConverterSupport#doConvert(...)
			  |--TypeConverterDelegate#convertIfNecessary(...):交給類型轉換器代理處理
				|--TypeConverterDelegate#convertIfNecessary(...)
				  |--PropertyEditorRegistrySupport#findCustomEditor(...):擷取自定義屬性編輯器【PropertyEditor】——處理String→任意類型
				  |--PropertyEditorRegistrySupport#fgetConversionService(...):擷取自定義類型轉換服務【ConversionService】——處理任意類型→任意類型
  |--InvocableHandlerMethod#doInvoke(args):參數綁定之後,真正執行HandlerMethod對象
           
  • 涉及到的類:
* InvocableHandlerMethod
* HandlerMethod
* HandlerMethodArgumentResolverComposite
* AbstractNamedValueMethodArgumentResolver
* RequestParamMethodArgumentResolver
* DataBinder
* TypeConverterSupport
* TypeConverterDelegate
* PropertyEditorRegistrySupport
           

傳回值處理流程

  • 分析入口:

    ServletInvocableHandlerMethod#invokeAndHandle

    方法中的

    handleReturnValue

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 傳回值由

    this.returnValueHandlers

    進行處理,這個成員變量的類型是

    HandlerMethodReturnValueHandlerComposite

    ,它繼承自

    HandlerMethodReturnValueHandler

    類,該類有兩個方法:
public interface HandlerMethodReturnValueHandler {
	// 是否支援指導傳回值類型解析
	boolean supportsReturnType(MethodParameter returnType);
	// 解析傳回值
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
           
  • 我們進入

    HandlerMethodReturnValueHandlerComposite#handleReturnValue

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 記筆記:
HandlerMethodReturnValueHandlerComposite#handleReturnValue(...)
  |--HandlerMethodReturnValueHandlerComposite#selectHandler(...)
  |--調用具體的處理器處理,以`@ResponseBody`注解為例
           
  • 涉及到的類:
* HandlerMethodReturnValueHandlerComposite
           

ResponseBody注解解析

  • 如果傳回值使用

    @ResponseBody

    注解,那麼由它注解的傳回值會使用

    RequestResponseBodyMethodProcessor

    類進行傳回值處理,是以分析入口:

    RequestResponseBodyMethodProcessor#handleReturnValue

    方法
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 進入

    AbstractMessageConverterMethodProcessor#writeWithMessageConverters

    方法,這個方法處理的邏輯比較長,我們挑重點代碼進行閱讀:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 繼續深入

    AbstractGenericHttpMessageConverter#write

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 進入

    AbstractJackson2HttpMessageConverter#writeInternal

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 記筆記:
RequestResponseBodyMethodProcessor#handleReturnValue(...)
	|--AbstractMessageConverterMethodProcessor#writeWithMessageConverters(...)
		|--AbstractGenericHttpMessageConverter#write(...)
			|--AbstractGenericHttpMessageConverter#writeInternal(...)
           
  • 涉及到的類:
* RequestResponseBodyMethodProcessor
* AbstractMessageConverterMethodProcessor
* AbstractGenericHttpMessageConverter
           

視圖解析器

注冊流程

處理流程

  • 分析入口:

    DispatcherServlet#processDispatchResult

    方法:
  • 進入processDispatchResult方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 然後進入

    render

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 進入

    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;
}
           
  • 進入

    ViewResolverComposite#resolveViewName

    方法:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
	for (ViewResolver viewResolver : this.viewResolvers) {
		View view = viewResolver.resolveViewName(viewName, locale);
		if (view != null) {
			return view;
		}
	}
	return null;
}
           
  • 接下來進入

    AbstractCachingViewResolver#resolveViewName

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 接下來進入

    UrlBasedViewResolver#createView

    方法:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 經過幾波周轉,最終請求進入到

    UrlBasedViewResolver#buildView

    方法,獲得基礎視圖對象:
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇

最終由該類以下方法擷取真正的視圖對象

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;
}
           
  • 記筆記:
DispatcherServlet#processDispatchResult(...)
  |--DispatcherServlet#render(...):視圖渲染
	|--DispatcherServlet#resolveViewName(...):解析視圖名稱
	  |--ViewResolverComposite#resolveViewName(...)
		|--AbstractCachingViewResolver#resolveViewName(...)
		  |--UrlBasedViewResolver#createView(...)
			|--AbstractCachingViewResolver#createView(...)
			  |--UrlBasedViewResolver#loadView(...)
				|--UrlBasedViewResolver#buildView(...)
				|--UrlBasedViewResolver#applyLifecycleMethods(...):擷取真正的視圖對象
           
  • 涉及的類:
* DispatcherServlet
* ViewResolverComposite
* AbstractCachingViewResolver
* UrlBasedViewResolver
           

了解篇

異常處理器

  • 異常處理器(HandlerExceptionResolver):SpringMVC在處理請求過程中出現的異常資訊交由異常處理器進行處理。異常處理器可以實作針對一個應用系統中出現的異常進行相應的處理。
JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇

異常了解

JavaEE 企業級分布式進階架構師(四)SpringMVC學習筆記(6)源碼篇了解篇
  • 我們通常說的異常包含編譯時異常(已檢查異常、預期異常)和運作時異常(未檢查異常)
    • 繼承RuntimeException類的異常類,就是運作時異常。
    • 繼承Exception類的異常類,沒有繼承RuntimeException類的異常類,就是編譯時異常。
  • 常見異常舉例:
    • 運作時異常,比如:空指針異常、數組越界異常等。對于這類異常,隻能通過程式員豐富的經驗來解決和測試人員不斷的嚴格測試來解決。
    • 編譯時異常,比如:資料庫異常、檔案讀取異常、自定義異常等。對于這類異常,必須使用

      try..catch

      代碼塊或者throws關鍵字來處理異常。

異常處理器示範

  • 自定義異常類:
public class BusinessException extends Exception {
	private static final long serialVersionUID = 1L;
	// 異常資訊
	private String message;
	public BusinessException(String message) {
		super(message);
		this.message = message;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
}
           
  • 異常處理器實作:
public class BusinessExceptionResolver implements HandlerExceptionResolver {
	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		// 自定義預期異常
		BusinessException businessException = null;
		// 如果抛出的是系統自定義的異常
		if(ex instanceof BusinessException) {
			businessException = (BusinessException) ex;
		}else {
			businessException = new BusinessException("未知錯誤");
		}
		ModelAndView modelAndView = new ModelAndView();
		// 把錯誤資訊傳遞到頁面
		modelAndView.addObject("message", businessException.getMessage());
		// 指向錯誤頁面
		modelAndView.setViewName("error");
		return modelAndView;
	}
}
           
  • 異常處理器配置:在springmvc.xml中加入一下代碼
<!-- 自定義異常處理器(全局) -->
<bean class="com.yw.springmvc_demo.exception.resolver.BusinessExceptionResolver"/>
           
  • 錯誤頁面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>參數綁定示範demo</title>
</head>
<body>
${message}
</body>
</html>
           
  • 異常測試:
@RequestMapping(value = "/showItemEdit")
@ResponseBody
public String showItemEdit(Integer id) throws Exception{
	Item item = itemService.queryItemById(id);
	if(item == null){
		throw new BusinessException("查詢不到商品無法修改");
	}
	return item;
}
           

亂碼解決

GET請求亂碼

  • 原因分析:GET 請求參數是通過請求首行中的URI發送給Web伺服器(Tomcat)的,Tomcat伺服器會對URI進行編碼操作(此時使用的是Tomcat設定的字元集,預設是ISO8859-1),到了我們的應用程式中的請求參數,已經被Tomcat使用ISO8859-1字元集進行編碼了。
  • 解決方式1:修改Tomcat配置檔案,指定UTF-8編碼:
  • 解決方式2:對請求參數進行重新編碼:
String username = request.getParamter("username");
username = new String(username.getBytes("ISO8859-1"), "utf-8");
           
  • 解決方式3:過濾器+請求裝飾器統一解決請求亂碼,自定義 MyRequestWrapper、MyCharacterEncodingFilter。

POST請求亂碼

  • 在 web.xml 檔案中加入如下配置:
<filter>
	<filter-name>CharacterEncodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>utf-8</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
           

響應亂碼

  • 使用@RequestMapping注解中的produces屬性,指定響應體中的編碼格式
// @RequestMapping注解中的consumes和produces分别是為請求頭和響應頭設定contentType
@RequestMapping(value = "responseString", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String responseString() {
	// 如果在使用@ResponseBody注解的前提下,如果傳回值是String類型,則傳回值會由StringHttpMessageConverter進行處理
	return "查詢失敗";
}
           

非注解開發方式

處理器開發

  • 實作HttpRequestHandler接口:
// 第二種Handler處理器的編寫方式:實作httpRequestHandler接口
public class DemoController implements HttpRequestHandler{
	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		// 資料填充
		request.setAttribute("msg", "實作HttpRequestHandler接口的開發方式");
		request.getRequestDispatcher("/WEB-INF/jsp/msg.jsp").forward(request, response);
	}
}
           
  • 配置處理器:

配置處理器映射器

  • 在 DispatcherServlet.properties 檔案中出現的處理器擴充卡和處理器映射器,如果沒有特殊要求(比如在擴充卡中配置轉換器、在映射器中配置攔截器),那麼就不需要再單獨進行配置了。
  • BeanNameUrlHandlerMappig 處理器映射器的映射思路:解析 bean 标簽中的 name 屬性值(請求URL),作為 key;解析 bean 标簽中的 class 屬性值,作為 value。
  • 根據映射器的映射規則,在 springmvc.xml 中配置請求 URL 和處理器之間的映射關系
<!-- 配置處理器 -->
<bean name="/helloHandler" class="com.yw.springmvc_demo.controller.DemoController"/>
           

配置處理器擴充卡

  • HttpRequestHandlerAdapter
public class HttpRequestHandlerAdapter implements HandlerAdapter {
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}
	@Override
	public boolean supports(Object handler) {
		return handler instanceof HttpRequestHandler;
	}
}
           
<!-- 配置http請求處理器擴充卡 -->
<bean name="/helloHandler" class="org.springframeword.web.servlet.mvc.HttpRequestHandlerAdapter"/>
           
  • SimpleControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		return ((Controller) handler).handleRequest(request, response);
	}
	@Override
	public boolean supports(Object handler) {
		return handler instanceof Controller;
	}
}
           
<!-- 配置處理器 -->
<bean name="/helloHandler" class="org.springframeword.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
           

注意事項

  • 處理器映射器和處理器擴充卡可以同時存在多個
  • 非注解方式開發的處理器隻能使用非注解的映射器和擴充卡進行處理

繼續閱讀