Spring의 DispatcherServlet 및 @EnableWebMvc 소스 코드 분석 및 몇 가지 문제점

1. DispatcherServlet 클래스 분석

DispatcherServlet 클래스를 직접 입력해 보세요. 핵심 소스코드는 다음과 같습니다.


	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		// ...
		try {
    
    
			ModelAndView mv = null;
			try {
    
    
				// 获取映射关系
				mappedHandler = getHandler(processedRequest);
				// 获取可以处理的方法
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				// 调用处理方法生成视图
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			}
			// 主要是视图解析、渲染过程
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		} 
	}

다음은 위의 네 가지 메소드인 getHandler (), getHandlerAdapter (), handler (), processDispatchResult () 를 분석하는 매우 긴 분석 프로세스입니다 .

1. getHandler() 메소드 분석

요청이 들어오면 첫 번째는 getHandler () 메소드에 들어가고, 이 메소드를 통해 해당 매핑 관계를 찾는다.핵심 소스코드는 다음과 같다.

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

팁: HandlerMethod 또는 클래스 자체가 HandlerExecutionChain을 다시 래핑해야 하는 이유 SpringMVC에는 인터셉터가 많기 때문에 대상 메서드와 인터셉터를 HandlerExecutionChain 컨텍스트로 래핑해야 합니다.

여기서 handlerMappings 컬렉션은 어디에서 초기화됩니까? 전체 과정을 먼저 분석한 후 되돌아보세요. 계속해서 입력하세요mapping.getHandler(요청)내부 로직, 핵심 소스 코드는 다음과 같습니다.

	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    
    

		Object handler = getHandlerInternal(request);
		if (handler == null) {
    
    
			handler = getDefaultHandler();
		}
}

여기에 반환된 핸들러는 반드시 모든 HandlerMethod 유형일 필요는 없습니다. 클래스 자체 일 수 있습니다 . @RequestMapping 으로 주석이 달린 핸들러만 HandlerMethod 유형을 반환합니다 . Controller , AbstractControllerHttpRequestHandler 인터페이스를 구현하는 클래스와 같은 클래스 자체를 반환합니다. , 메서드가 하나만 있기 때문에 클래스를 찾으면 현재 요청을 어떤 메서드로 처리할지 결정할 수 있으므로 이때 반환되는 핸들러는 클래스 자체입니다. 이것이 바로 반환 핸들러 객체가 Object 유형을 통해 허용되는 이유입니다.

getHandlerInternal() 메소드가 Hook 메소드인지 확인하세요. 소스코드는 다음과 같습니다.

	@Nullable
	protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

여기에는 두 가지 명백한 하위 클래스 구현이 있습니다: AbstractHandlerMethodMappingAbstractUrlHandlerMapping 이름을 보면 첫 번째는 @RequestMapping 주석용이고 두 번째는 Controller , AbstractControllerHttpRequestHandler 인터페이스 구현용임을 알 수 있습니다 .

먼저 간단한 AbstractUrlHandlerMapping 클래스를 분석하고 다음과 같이 핵심 소스 코드를 입력합니다.

	@Override
	@Nullable
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    
    
		String lookupPath = initLookupPath(request);
		Object handler = lookupHandler(lookupPath, request);
		// ...
		return handler;
	}

initLookupPath ()는 요청에 의해 전달된 데이터(키와 동일)를 기반으로 매핑 경로를 생성합니다. 키에 따라 해당 핸들러를 찾으십시오.

예를 들어 아래 HelloHttpRequestHandler 클래스에 액세스할 때 key = /hello, 핸들러 값은 HelloHttpRequestHandler 자체의 클래스 인스턴스입니다.

@Controller("/hello")
public class HelloHttpRequestHandler implements HttpRequestHandler {
    
    

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    

		response.getWriter().write("hello HttpRequestHandler.....");
	}
}

마지막으로 lookupPath = /hello, lookupHandler() 메서드를 호출합니다. 핵심 소스 코드는 다음과 같습니다.

	protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
    
    
		Object handler = getDirectMatch(lookupPath, request);
		if (handler != null) {
    
    
			return handler;
		}
}

getDirectMatch()를 계속 진행합니다. 핵심 소스 코드는 다음과 같습니다.

	@Nullable
	private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception {
    
    
		Object handler = this.handlerMap.get(urlPath);
		if (handler != null) {
    
    
			// Bean name or resolved handler?
			if (handler instanceof String) {
    
    
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(handler, request);
			return buildPathExposingHandler(handler, urlPath, urlPath, null);
		}
		return null;
	}

위의 소스 코드에서 핸들러 값은 최종적으로 handlerMap , urlPath = /hello에서 검색되므로 다음과 같이 요약할 수 있습니다. Controller , AbstractControllerHttpRequestHandler 인터페이스 유형을 구현하는 핸들러는 모두 클래스 자체의 인스턴스입니다. , 매핑 관계는 handlerMap 컬렉션에 저장됩니다. 이 handlerMap에 할당된 값은 언제 초기화되나요? , 전체 과정을 분석하고 되돌아보세요.

그런 다음 AbstractHandlerMethodMapping 클래스를 분석 하고 다음과 같이 핵심 소스 코드를 입력합니다.

	@Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    
    
		List<Match> matches = new ArrayList<>();
		
		List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
		
		if (directPathMatches != null) {
    
    
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
    
    

			addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
		}
		if (!matches.isEmpty()) {
    
    
			/**
			 * 一般情况下一个 url 就只会映射到一个 RequestMappingInfo,但是也不能保证只有一个,当一个 url 匹配到多个 RequestMappingInfo
			 * 时,就要去校验哪个最合适
			 */
			Match bestMatch = matches.get(0);

			return bestMatch.getHandlerMethod();
		}
	}

위 소스코드는 주로 이 문장을 관찰하고 있습니다.this.mappingRegistry.getRegistrations(), 다음과 같이 핵심 소스 코드를 입력합니다.

	class MappingRegistry {
    
    

		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

		public Map<T, MappingRegistration<T>> getRegistrations() {
    
    
			return this.registry;
		}
}

@RequestMapping 주석 의 매핑 관계 도 Map 형태의 레지스트리 컨테이너에 저장되어 있는 것을 확인할 수 있으며, @RequestMapping 주석은 요청 레지스트리 컨테이너의 키로 RequestMappingInfo 객체 에 캡슐화되어 있음을 알 수 있다 .

레지스트리 컨테이너 의 값은 HandlerMethodMapping#MappingRegistration 유형 인스턴스입니다. MappingRegistration은 Handler의 HandlerMethod 유형, 즉 컨트롤러의 해당 특정 메소드 +@를 캡슐화합니다 . RequestMapping 정보는 HandlerMethod 객체 로 캡슐화됩니다 . 따라서 @RequestMapping 형태의 매핑 관계는 최종적으로 Map 컨테이너 에 저장된다는 결론을 내릴 수 있다 .

여기서 초기화된 레지스트리 컨테이너의 값은 무엇입니까? 전체 프로세스를 분석한 다음 다시 돌아가서 분석하세요.

요약

이를 분석한 후 getHandler () 메소드가 결국 두 가지 유형 값을 반환한다는 것을 알 수 있습니다 . 하나는 HandlerMethod (이 유형을 반환하려면 @RequestMapping 주석을 사용함)이고 다른 하나는 클래스 자체(Controller 및 HttpRequestHandler 인터페이스를 구현하면 다음을 반환함)입니다 . 클래스 자체 인스턴스 ).

참고: 요청 요청을 통해 현재 요청을 처리할 수 있는 메소드를 찾았습니다. 다음 단계는 메소드를 호출하는 방법을 보는 것입니다. 그 다음에는 메소드가 다음과 같이 형성되기 때문에 getHandlerAdapter () 메소드가 호출을 어떻게 적응시키느냐에 따라 다릅니다. 각 유형의 컨트롤러는 다르지만 동일하므로 호출을 캡슐화하려면 어댑터 계층이 필요합니다.

2. getHandlerAdapter() 메소드 분석

다음으로 두 번째 큰 단계인 getHandlerAdapter () 메소드를 살펴보겠습니다. 핵심 소스 코드는 다음과 같습니다.

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    
    
		/**
		 * 采用策略模式处理不同类型的 handler
		 */
		if (this.handlerAdapters != null) {
    
    
			for (HandlerAdapter adapter : this.handlerAdapters) {
    
    
				if (adapter.supports(handler)) {
    
    
					return adapter;
				}
			}
		}
	}

HandlerAdapter 어댑터가 많이 제공되는 것을 볼 수 있습니다 . handlerAdapter의 초기화는 어디에 있습니까? 추후 분석을 통해 다양한 형태의 Controller의 메소드 호출을 적용하고 처리합니다. 각 유형의 Controller의 형태가 조금씩 다르기 때문에 처리를 조정하려면 어댑터가 필요합니다. 직설적으로 말하면 중간에 호출 레이어를 래핑합니다. 일반적인 예는 다음과 같습니다.

  • HandlerFunctionAdapter : 아직 사용되지 않은 함수형 프로그래밍 유형을 처리하는 컨트롤러 메서드입니다.

  • HttpRequestHandlerAdapter : HttpRequestHandler 인터페이스를 구현한 Controller 메소드의 호출을 처리하는데, 이 메소드는 값을 반환할 필요가 없으므로 값을 반환할 필요가 없는 경우 HttpRequestHandler 유형의 Controller를 정의할 수 있습니다.

  • SimpleControllerHandlerAdapter : Controller 및 AbstractController 인터페이스를 구현하는 Controller 메소드 호출을 처리합니다. 이 메소드는 값을 반환해야 합니다.

  • AbstractHandlerMethodAdapter : 반환 값이 있을 수도 있고 없을 수도 있는 @RequestMapping 주석 형식의 Controller 메서드 호출을 처리하며 Controller 및 HttpRequestHandler 유형의 조합과 비슷합니다.

위의 작성 방법은 매우 일반적 动态策略模式이며 참조 의미가 큽니다. 더 이상 고민하지 않고 분석을 진행해 보겠습니다.

위의 요청으로 돌아가기http://localhost:9292/hello요청된 Controller 유형은 HttpRequestHandler 유형이므로 이 HttpRequestHandlerAdapter 어댑터에 의해 처리됩니다 . 어떻게 처리하느냐에 따라 다르며, 핵심 소스코드는 다음과 같습니다.

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    
    

	@Override
	public boolean supports(Object handler) {
    
    
		/**
		 * 支持实现了 HttpRequestHandler 接口的 Controller
		 */
		return (handler instanceof HttpRequestHandler);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}
}

위의 소스 코드를 보면 이때 핸들러는 HelloHttpRequestHandler이고, 타입은 HttpRequestHandler이므로 handler() 처리 메소드가 호출되는 것을 알 수 있는데, 이 메소드는 두 가지 일을 한다. HelloHttpRequestHandler 클래스를 호출한 다음 null 값을 반환합니다. 간단한 호출 프로세스만 만들어도 HttpRequestHandlerAdapter 어댑터는 이 두 가지 작업을 수행합니다.

다음 컨트롤러와 같은 예를 들어 보겠습니다. 코드는 다음과 같습니다.

@Controller
public class JspController {
    
    

	@RequestMapping("/toJsp")
	public String toJsp() {
    
    
		System.out.println(">>>>>>toJsp..");
		return "abc";
	}
}

요청 중http://localhost:9292/toJsp, 이는 AbstractHandlerMethodAdapter 어댑터에 의해 처리됩니다. 어댑터는 또한 JspController에서 toJsp() 메소드를 호출하는 방법을 캡슐화해야 합니다. 핵심 소스 코드를 다음과 같이 입력합니다.

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
    
    

	private int order = Ordered.LOWEST_PRECEDENCE;

	@Override
	public final boolean supports(Object handler) {
    
    

		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}


	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    

		return handleInternal(request, response, (HandlerMethod) handler);
	}
}

위의 소스 코드를 보면 JspController가 @RequestMapping 형식으로 구성되어 있으므로 support() 메소드의 핸들러가 HandlerMethod 타입이고 AbstractHandlerMethodAdapter 어댑터로 처리할 수 있으며 이후에 handler() 처리 메소드를 호출한다는 것을 알 수 있다. 다음과 같이 handlerInternal() 핵심 소스 코드를 입력합니다.

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

		ModelAndView mav;
		// ...
		mav = invokeHandlerMethod(request, response, handlerMethod);
		// ...
		return mav;
}

계속해서 invokeHandlerMethod() 메서드로 진행합니다. 핵심 소스 코드는 다음과 같습니다.

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    
		/** mvc_tag: 包装成自己方便使用的 DTO */
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
    
    
			// 1、请求过来的字段和 Person 里面的字段一一绑定,就是这个绑定器要干的事情
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			// 2、 这几个参数都在 pre_params 标记处准备好了 
			if (this.argumentResolvers != null) {
    
    
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
    
    
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);

			// 3、parameterNameDiscoverer 获取一个方法上参数名称,Spring 封装的一个工具类
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			// 4、 mvc_tag: 在一次请求中共享Model 和 View 数据的临时容器 
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

			/**
			 * 5、调用 @ModelAttribute 注解修饰的方法(这是个公共方法),把公共逻辑抽取到这个方法,然后用这个 @ModelAttribute 注解修饰该方法
			 * 这样再其他方法中就不用手动调用,Spring 会反射调用后把返回结果封装到 ModelAndViewContainer 中的 ModelMap 属性,
			 * 具体没啥太多可用价值
			 */
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			

			// 6、 开始去调用目标方法,并且把临时容器穿进去了
			invocableMethod.invokeAndHandle(webRequest, mavContainer);

			// 7、从临时容器中直接抽取出需要的 ModelAndView
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
    
    
			webRequest.requestCompleted();
		}
	}

위의 소스 코드에서 많은 작업이 수행되었음을 확인했습니다.

  • 매개변수 바인딩 팩토리 바인더Factory 가져오기

  • handlerMethod를 ServletInvocableHandlerMethod로 다시 캡슐화합니다.

  • 매개변수 파서 설정

  • 반환 값 파서 설정

  • 데이터 바인딩 팩토리 설정(@RequestBody 주석이 유용함)

  • Context와 유사한 ModelAndViewContainer 컨테이너를 새로 생성했습니다.

위에서 얻은 것들은 어디서 초기화되나요? 모두 다시 살펴보고 계속해서 분석하여 호출AndHandle() 메소드의 핵심 소스코드를 다음과 같이 입력해보자.

	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    
    

		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

		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);
		
		try {
    
    
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
	}

다음 단계로 나눌 수 있습니다.

  • 컨트롤러에서 특정 메소드 호출
  • 뷰 렌더링 스위치 requestHandled를 설정합니다. false는 뷰 렌더링이 필요함을 의미하고, true는 뷰 렌더링이 필요하지 않음을 의미합니다.
  • 컨트롤러에서 메서드 반환 값 처리

먼저 InvokeForRequest() 메소드를 살펴보겠습니다.핵심 소스코드는 다음과 같습니다.

	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    
    

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
    
    
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}
	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
    
    
		Method method = getBridgedMethod();
		ReflectionUtils.makeAccessible(method);
		try {
    
    
			if (KotlinDetector.isSuspendingFunction(method)) {
    
    
				return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
			}
			return method.invoke(getBean(), args);
		}
	}

이 메소드는 주로 매개변수 값을 파싱하고 Reflection을 통해 Controller에서 특정 메소드를 호출하는 것을 알 수 있다. 그가 어떻게 매개변수를 파싱하고 getMethodArgumentValues() 메소드를 입력하는지 살펴보겠습니다. 핵심 소스 코드는 다음과 같습니다.

	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    
    

		MethodParameter[] parameters = getMethodParameters();
		
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
    
    
			MethodParameter parameter = parameters[i];
			
			if (this.resolvers.supportsParameter(parameter)){
    
    
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
		}
		return args;
	}

위의 소스 코드를 보면 각 매개변수가 처리되고, 각 유형 매개변수에는 이를 처리하기 위한 전략이 있다는 것을 알 수 있으며, 처리할 수 없는 경우 직접 예외가 발생하고 요청 프로세스가 종료됩니다. 이것은 또한 전형적인 동적입니다 策略模式. 예를 들어:

@Controller
public class JspController {
    
    

	@RequestMapping("/toJsp")
	public String toJsp(
		@RequestBody Person,
		@RequestParam String name,
		@PathVariable Integer id,
		@RequestHeader String contentType,
		String name,Map<String,String> map) {
    
    
		
		System.out.println(">>>>>>toJsp..");
		return "abc";
	}
}

우선, 각 매개변수 유형이 다르며 이를 처리하기 위한 전략을 사용해야 하므로 현재 최소 17개 이상의 매개변수 파서가 많이 있습니다.
여기서 매개변수 값을 파싱한 후 최종적으로 리플렉션을 통해 특정 메소드를 호출하고 커스텀 로직 처리를 수행할 수 있다.

그런 다음 반환 값에 따라 스위치 requestHandled를 설정합니다. 반환 값이 있으면 false로 설정되어 뷰 렌더링이 필요하다는 의미이고, 반환 값이 없으면 true로 설정되어 있음을 의미합니다. 뷰 렌더링이 없습니다.

그런 다음 마지막 단계는 반환 값을 처리하는 것입니다.handleReturnValue() 메소드를 입력합니다.핵심 소스 코드는 다음과 같습니다.

	@Override
	public void handleReturnValue(@Nullable 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: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

	@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    
    
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
    
    
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
    
    
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
    
    
				return handler;
			}
		}
		return null;
	}

또한 다양한 유형의 반환 값을 처리하려면 여러 가지 전략이 필요합니다. 아래 그림과 같이:

여기에 이미지 설명을 삽입하세요.

반환 값 처리가 완료되면 최상위 호출로 복귀하여 계속 입력합니다.return getModelAndView(mavContainer, modelFactory, webRequest);핵심 소스코드는 다음과 같습니다.

3. ModelAndView 객체 생성


	@Nullable
	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    
    

		modelFactory.updateModel(webRequest, mavContainer);

		/**
		 * 根据 ModelAndViewContainer 容器中的 requestHandled 属性来判断需不需要渲染视图
		 * 如果是 @ResponseBody 明显是不需要的此时 requestHandled = true,立马就 return 结束了
		 */
		if (mavContainer.isRequestHandled()) {
    
    
			return null;
		}
		ModelMap model = mavContainer.getModel();
		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;
	}

위의 소스 코드에서 requestHandled=true일 때 null이 직접 반환되어 뷰 렌더링이 필요하지 않으며 다음 ModeAndView 데이터 준비를 수행할 필요가 없음을 알 수 있습니다. false인 경우 뷰 렌더링이 필요하므로 여기서는 다음 코드를 실행하여 모드 데이터를 준비하고, 논리 뷰를 지정하고, 플래시 메모리가 있는 경우 플래시 메모리 데이터를 준비합니다. 마지막으로 ModelAndView로 돌아갑니다. 그런 다음 뷰 구문 분석 프로세스를 계속 실행합니다.

4. 구문 분석 및 렌더링 보기

processDispatchResult()를 입력하면 핵심 소스 코드는 다음과 같습니다.

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

		if (mv != null && !mv.wasCleared()) {
    
    
			render(mv, request, response);
		}
	}

위의 작은 코드에서 볼 수 있듯이 mv는 ModelAndView 개체이며 null인 경우 뷰 렌더링이 필요하지 않습니다. mv는 언제 null입니까? 위에서 분석한 바와 같이 requestHandled 스위치에 의해 제어되며, requestHandled=true인 경우 null이 직접 반환되어 뷰 렌더링이 필요하지 않으며 다음과 같은 ModeAndView 데이터 준비를 수행할 필요가 없음을 나타냅니다. false인 경우 뷰 렌더링이 필요합니다.

render()의 핵심 소스 코드를 다음과 같이 입력합니다.

	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		// 获取逻辑视图名称
		String viewName = mv.getViewName();
		// 通过视图解析器生成视图 View
		View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
		// 视图再去渲染
		view.render(mv.getModelInternal(), request, response);
	}

다음은 몇 가지 일반적인 뷰 파서입니다.

  • InternalResourceViewResolver : JSP 페이지의 View 뷰를 생성하며 Spring은 JSP 페이지가 존재해야 하는지 여부를 결정하지 않으며 Tomcat이 이를 자체적으로 처리합니다.
  • FreeMarkerViewResolver : .ftl 템플릿 페이지의 보기 보기를 생성합니다. Spring은 .ftl 파일 리소스가 존재하는지 여부를 결정합니다.
  • ThymeleafViewResolver : .html 템플릿 페이지의 뷰 뷰를 생성하며 Spring은 .html 파일 리소스가 존재하는지 여부를 결정합니다.

View View가 성공적으로 생성된 후에는 효과를 표시해야 하며, 그런 다음 View 뷰에서 render() 렌더링 메서드를 호출하여 효과를 육안으로 볼 수 있도록 표시해야 합니다.

마지막으로 전체 SpringMVC 호출 프로세스가 종료됩니다. 나머지는 get()으로 얻은 데이터가 무엇인지, 어디서 초기화되는지 분석하는 것입니다.

요청이 들어오면 첫 번째는 getHandler () 메소드에 들어가고, 이 메소드를 통해 해당 매핑 관계를 찾는다.핵심 소스코드는 다음과 같다.

2. HandlerMapping 초기화

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

아래 코드를 보면 handlerMappings의 초기화 값은 어디에 있습니까? 다음과 같이 소스 코드의 일부를 볼 수 있습니다.

	protected void initStrategies(ApplicationContext context) {
    
    
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

팁: 컨테이너가 시작되면 initStrategies()가 ContextRefreshListener 청취 클래스에 의해 호출되어 webmvc에 필요한 구성 요소를 초기화합니다.

여기서는 HandlerMapping, HandlerAdapter, ViewResolver가 초기화된 것을 확인할 수 있으며, 다음으로 initHandlerMappings() 메소드를 살펴보겠습니다.핵심 소스코드는 다음과 같습니다.

	private void initHandlerMappings(ApplicationContext context) {
    
    
		this.handlerMappings = null;

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

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
    
    
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
    
    
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}

		for (HandlerMapping mapping : this.handlerMappings) {
    
    
			if (mapping.usesPathPatterns()) {
    
    
				this.parseRequestPath = true;
				break;
			}
		}
	}

위의 소스코드를 보면 초기화가 두 곳에서 이루어지는 것을 볼 수 있습니다: @EnableWebMvc또는 DispatcherServlet.properties:

1. @EnableWebMvc 주석 방법

beansOfTypeInclusionAncestors(HandlerMapping.class) 메소드는 컨테이너에 구성된 HandlerMapping 구현 클래스를 얻을 수 있으며 일반적으로 @EnableWebMvc Annotation을 통해 구성 클래스를 소개하고 HandlerMapping 구현 클래스는 구성 클래스에 @Bean을 통해 주입된다.

@EnableWebMvc 주석 소스 코드는 다음과 같습니다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class) 
public @interface EnableWebMvc {
    
    

}

DelegatingWebMvcConfiguration 구성 클래스는 Spring 주석 @Import를 통해 도입되었으며 핵심 소스 코드는 다음과 같습니다.

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    

}

핵심 소스코드는 상위 클래스인 WebMvcConfigurationSupport에 있는데, 먼저 HandlerMapping을 초기화하는 핵심 소스코드를 살펴보면 다음과 같다.

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    

	@Bean
	@SuppressWarnings("deprecation")
	public RequestMappingHandlerMapping requestMappingHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    
		// ...
		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		// ...
		return mapping;
	}
	
	@Bean
	public BeanNameUrlHandlerMapping beanNameHandlerMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    
		// ...
		BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
		// ...
		return mapping;
	}
	
	@Bean
	@Nullable
	public HandlerMapping viewControllerHandlerMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
		addViewControllers(registry);

		AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
		// ...
		return handlerMapping;
	}
	
	@Bean
	public RouterFunctionMapping routerFunctionMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		RouterFunctionMapping mapping = new RouterFunctionMapping();
		// ...
		return mapping;
	}

	@Bean
	@Nullable
	public HandlerMapping resourceHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    
		// ...
		AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
		// ...
		return handlerMapping
	}
	
	@Bean
	@Nullable
	public HandlerMapping defaultServletHandlerMapping() {
    
    
		Assert.state(this.servletContext != null, "No ServletContext set");
		DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(this.servletContext);
		configureDefaultServletHandling(configurer);
		return configurer.buildHandlerMapping();
	}
}

2. DispatcherServlet.properties 구성 방법

위의 구성 클래스를 통해 HandlerMapping 구현 클래스를 찾을 수 없는 경우 은밀한 해결책이 있을 것이며, getDefaultStrategiesDispatcherServlet.properties () 메소드는 구성 파일에 구성된 HandlerMapping 구현 클래스를 찾습니다 . 아래 그림과 같이:

여기에 이미지 설명을 삽입하세요.

DispatcherServlet.properties구성 파일은 다음과 같습니다.

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

BeanNameUrlHandlerMapping : Controller, AbstractController, HttpRequestHandler 인터페이스를 구현하는 Controller 매핑 관계를 처리하며 실제로 최하위 계층에서는 Map을 유지 관리한다.

RequestMappingHandlerMapping : @RequestMapping 형태로 Controller 관계 매핑을 처리하며, 최하위 레이어는 Map 컨테이너를 유지한다.

SimpleUrlHandlerMapping : 단순 URL 관계 매핑으로, URL을 커스터마이징한 후 프로세서를 바인딩할 수 있으며, 정적 리소스 액세스, 컨트롤러 점프 등 확장에 자주 사용됩니다.

3. SimpleUrlHandlerMapping에 대한 간략한 소개

이 클래스의 소스코드는 다음과 같습니다.

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
    
    

	private final Map<String, Object> urlMap = new LinkedHashMap<>();

	public SimpleUrlHandlerMapping() {
    
    
	}

	public SimpleUrlHandlerMapping(Map<String, ?> urlMap) {
    
    
		setUrlMap(urlMap);
	}

	public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) {
    
    
		setUrlMap(urlMap);
		setOrder(order);
	}
	
	public void setMappings(Properties mappings) {
    
    
		CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
	}

}

나는 urlPath와 핸들러 사이의 매핑 관계를 유지하는 데 사용되어야 하는 urlMap 컨테이너가 내부에 유지된다는 것을 발견했습니다. 이 클래스를 사용할 때 urlMap 컨테이너 에 urlPath와 핸들러 사이의 관계만 저장하면 되지만 여기서는 클래스 자체의 컨트롤러인 핸들러만 지원한다는 점에 유의하세요. 관계를 등록할 때 하나만 채울 수 있기 때문입니다. value이며, 이 값은 정확할 수 없으므로 특정 메소드에 @RequestMapping을 바인딩할 수 없습니다. 이는 Controller, AbstractController 및 HttpRequestHandler 유형을 구현하는 컨트롤러만 지원합니다.

사용 사례는 다음과 같습니다.

@Configuration
public class SimpleUrlConfig {
    
    

	// @Bean
	public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
    
    
		/* 
		Map<String, String> urlMap = new HashMap<>();
		urlMap.put("area/index", "helloSimpleController");
		SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping(urlMap);
		或
		*/
		
		SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
		Properties properties = new Properties();
		// key 就是访问路径,你自己随便定义,这里写的是 area/index
		// value 值就是 Controller 类本身在 Spring 容器中的 beanName
		properties.put("area/index","helloSimpleController");
		simpleUrlHandlerMapping.setMappings(properties);
		return simpleUrlHandlerMapping;
	}
}

// HelloSimpleController 在 Spring 中 beanName = helloSimpleController
@Component
public class HelloSimpleController extends AbstractController {
    
    

	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		response.getWriter().write("ControllerController execute..");
		return null;
	}
}

이러한 방식으로 특정 URL 액세스 경로를 특정 컨트롤러에 바인딩하고 브라우저에서 액세스할 수 있습니다.http://localhost:8887/area/index
컨트롤러에 접근할 수 있습니다.

그러면 다음과 같이 이 클래스의 두 가지 다른 사용 사례를 볼 수 있습니다.

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    

		registry.addResourceHandler("/dist/**").addResourceLocations("classpath:/static/dist/");
		registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
		registry.addResourceHandler("/boot/*").addResourceLocations("classpath:/static/");
		registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
	}

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
    
    
		registry.addViewController("/gotoJsp").setViewName("abc2");
	}
}

누구나 커스텀 webmvc 함수를 사용해봤을 텐데요, addResourceHandlers()를 통해 정적 리소스에 대한 액세스를 지정하고, addViewControllers() 메서드를 통해 이동할 페이지를 지정할 수 있습니다. 이 두 함수의 맨 아래 레이어는 SimpleUrlHandlerMapping 클래스로 완성됩니다.

먼저 addResourceHandlers ()에 의해 추가된 정적 리소스 액세스 메서드를 분석해 보겠습니다. 디버그 추적은 최종적으로 다음 핵심 소스 코드에서 호출됩니다.

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    

	@Bean
	@Nullable
	public HandlerMapping resourceHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
				this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
		addResourceHandlers(registry);

		AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
		return handlerMapping;
	}	
	
	@Nullable
	protected AbstractHandlerMapping getHandlerMapping() {
    
    
		if (this.registrations.isEmpty()) {
    
    
			return null;
		}
		Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
		for (ResourceHandlerRegistration registration : this.registrations) {
    
    
			ResourceHttpRequestHandler handler = getRequestHandler(registration);
			for (String pathPattern : registration.getPathPatterns()) {
    
    
				urlMap.put(pathPattern, handler);
			}
		}
		return new SimpleUrlHandlerMapping(urlMap, this.order);
	}
}

위의 소스 코드에서 들어오는 정적 리소스 액세스 경로가 SimpleUrlHandlerMapping 클래스의 urlMap 컨테이너에 캡슐화되어 있음을 확인할 수 있습니다. 그래서 다음과 같은 코드 줄을 작성합니다.Registry.addResourceHandler("/**").addResourceLocations("classpath:/static/")SimpleUrlHandlerMapping 클래스는 액세스 경로와 컨트롤러 매핑 관계를 설정하는 데 도움이 되므로 브라우저에서 정적 리소스에 액세스할 수 있습니다 . 여기서 컨트롤러는 ResourceHttpRequestHandler입니다. 각 경로는 새로운 ResourceHttpRequestHandler 클래스에 해당합니다.

addViewControllers () 메소드를 분석 하고 호출 지점까지 디버그를 추적해 보겠습니다. 핵심 소스 코드는 다음과 같습니다.

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    

	@Bean
	@Nullable
	public HandlerMapping viewControllerHandlerMapping(
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    
    

		ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
		addViewControllers(registry);

		AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
		return handlerMapping;
	}
	
	@Nullable
	protected SimpleUrlHandlerMapping buildHandlerMapping() {
    
    
		if (this.registrations.isEmpty() && this.redirectRegistrations.isEmpty()) {
    
    
			return null;
		}

		Map<String, Object> urlMap = new LinkedHashMap<>();
		for (ViewControllerRegistration registration : this.registrations) {
    
    
			urlMap.put(registration.getUrlPath(), registration.getViewController());
		}
		for (RedirectViewControllerRegistration registration : this.redirectRegistrations) {
    
    
			urlMap.put(registration.getUrlPath(), registration.getViewController());
		}

		return new SimpleUrlHandlerMapping(urlMap, this.order);
	}
}

public class ViewControllerRegistration {
    
    

	private final ParameterizableViewController controller = new ParameterizableViewController();
	
	protected ParameterizableViewController getViewController() {
    
    
		return this.controller;
	}
}

위의 코드를 보면 SimpleUrlHandlerMapping 클래스가 urlPath와 ParameterizedViewController 간의 매핑 관계를 설정한다는 것을 알 수 있으므로 addViewControllers() 메서드에 한 줄을 추가하는 이유를 알 수 있습니다.Registry.addViewController("/gotoJsp").setViewName("abc2");이러한 코드는 브라우저에서 액세스할 수 있습니다. 액세스 경로는 다음과 같습니다.http://localhost:8887/gotoJsp

그 중 abc.jsp는 webapp 하위에 존재해야 하며 , 그렇지 않으면 404 에러가 발생합니다.

여기에 이미지 설명을 삽입하세요.

최종 액세스 효과는 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.

따라서 SimpleUrlHandlerMapping 클래스는 매우 중요하며 이 클래스를 사용하여 동적 매핑 관계 바인딩 등을 수행할 수 있습니다.

3. HandlerAdapter 초기화

HandlerAdapter와 HandlerMapping은 기본적으로 유사하며, 핵심 소스코드는 다음과 같습니다.

	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.
		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");
			}
		}
	}

@EnableWebMvc두 가지 면에서 동일합니다 DispatcherServletl.properties.

1. @EnableWebMvc 주석 방법

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    
    
		
	@Bean
	public RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
    
    
		return new RequestMappingHandlerAdapter();
	}
	
	@Bean
	public HandlerFunctionAdapter handlerFunctionAdapter() {
    
    
		HandlerFunctionAdapter adapter = new HandlerFunctionAdapter();
		return adapter;
	}

	@Bean
	public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
    
    
		return new HttpRequestHandlerAdapter();
	}
	
	@Bean
	public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
    
    
		return new SimpleControllerHandlerAdapter();
	}
}

2. DispatcherServlet.properties 구성 파일

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,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter

4. ViewResolver 초기화

뷰 파서가 초기화되고 핵심 소스 코드가 다음과 같이 입력됩니다.

	private void initViewResolvers(ApplicationContext context) {
    
    
		this.viewResolvers = null;

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

		// Ensure we have at least one ViewResolver, by registering
		// a default ViewResolver if no other resolvers are found.
		if (this.viewResolvers == null) {
    
    
			this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
			if (logger.isTraceEnabled()) {
    
    
				logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

뷰 파싱의 초기화는 기본적으로 HandlerAdapter 및 HandlerMapping과 동일한 루틴이라는 것을 알 수 있습니다. 그러나 여기서 주목해야 할 것은 뷰 파서를 주입할 때 @EnableWebMvc 주석에 몇 가지 세부 정보가 있다는 것입니다. 다음과 같이:

1. @EnableWebMvc는 뷰 확인자를 주입합니다.

소스코드를 다음과 같이 입력하세요.

	@Bean
	public ViewResolver mvcViewResolver(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    
    
		ViewResolverRegistry registry =
				new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
		configureViewResolvers(registry);

		if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
    
    
			String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.applicationContext, ViewResolver.class, true, false);
			// 这里 ==1 指的是上来容器中就有一个,这个是指定此方法 @Bean mvcViewResolver 这个 bean
			// 此时视图解析器就是 InternalResourceViewResolver
			if (names.length == 1) {
    
    
				registry.getViewResolvers().add(new InternalResourceViewResolver());
			}
		}

		ViewResolverComposite composite = new ViewResolverComposite();
		composite.setOrder(registry.getOrder());
		composite.setViewResolvers(registry.getViewResolvers());
		if (this.applicationContext != null) {
    
    
			composite.setApplicationContext(this.applicationContext);
		}
		if (this.servletContext != null) {
    
    
			composite.setServletContext(this.servletContext);
		}
		return composite;
	}

위의 소스 코드를 주의 깊게 읽고 beanNamesForTypeInclusiveAncestors () 메소드 를 통해 Spring 컨테이너에서 ViewResolver 인터페이스의 구현 클래스를 찾으십시오 . ViewResolver 구현 클래스 를 구성하지 않으면 컨테이너에는 현재 하나만 있을 것입니다. @Bean 수정된 mvcViewResolver () 메소드는 하나만 발견될 것이라는 데는 의심의 여지가 없습니다. 이때 SpringMVC는 뷰 리졸버 InternalResourceViewResolver를 구축하는 데 도움을 줍니다. 뷰 리졸버는 기본적으로 JSP 리소스 파일을 구문 분석합니다. suffix 와 prefix 를 구성하는 것을 잊지 마세요 . 일치하지 않으면 액세스 경로에 suffix 와 prefix 를 추가하세요.http://localhost:9292/toJsp?cx=abc2.jsp그게 다입니다. 직접 액세스가 구성된 경우http://localhost:9292/toJsp?cx=abc2, 어쨌든 완전한 도로를 형성하는 것입니다.

@Controller
public class ToJspController {
    
    

    @RequestMapping("/toJsp")
    public ModelAndView toJsp(String cx) {
    
    
        ModelAndView view = new ModelAndView();
        System.out.println(">>>>>>toJsp...");
        System.out.println("hnhds");
        view.addObject("name", "gwm");
        view.setViewName(cx);
        return view;
    }
}

기본 로딩 외에도 사용자 정의할 수도 있지만 WebMvcConfigurer 인터페이스를 구현한 다음 해당 메소드를 다시 작성해야 합니다. 예를 들면 다음과 같습니다.

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    
    
		registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/*");
	}

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
		/**
		 * 这里不加 Jackson 也是有默认值存在的,如果重写了的话,默认的 Converts 都不会加载,所以要非常注意这个细节
		 * 推荐重写 extendMessageConverters() 方法
		 */
		// converters.add(new MappingJackson2HttpMessageConverter());
	}

	@Override
	public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
		MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();

		ObjectMapper objectMapper = new ObjectMapper();
		SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd");
		objectMapper.setDateFormat(smt);

		jackson2HttpMessageConverter.setObjectMapper(objectMapper);
		converters.add(jackson2HttpMessageConverter);
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		// InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		// resolver.setSuffix(".jsp");
		// resolver.setPrefix("/");
		// registry.viewResolver(resolver);

		// registry.enableContentNegotiation(new MappingJackson2JsonView());
		registry.jsp("/",".jsp");
		// registry.freeMarker();
	}

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    

		registry.addResourceHandler("/dist/**").addResourceLocations("classpath:/static/dist/");
		registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
		registry.addResourceHandler("/boot/*").addResourceLocations("classpath:/static/");
		// registry.addResourceHandler("/boot/**").addResourceLocations("classpath:/static/");
		// registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
	}

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
    
    
		registry.addViewController("/gotoJsp").setViewName("abc");
	}
}

다만, 함수를 직접 커스터마이징할 때에는 소스코드가 어떻게 구현되어 있는지 먼저 살펴봐야 한다는 점을 유의하시기 바랍니다. 주의하지 않으면 쉽게 오류가 발생할 수 있는 세부 사항이 실제로 많기 때문입니다. 예를 들어, configureViewResolvers () 메소드에서 JSP와 FTL 파일이 동시에 존재하는 경우 JSP 앞에 FLT 파일을 넣어야 리소스를 정상적으로 구문 분석할 수 있습니다.

이러한 사용자 정의 기능을 로드하는 방법에 대한 디자인 패턴은 다음과 같습니다 委托模式. 특정 기능의 구현을 다른 사람에게 맡기고, 나는 그냥 호출한다는 뜻이다.

다음은 사용자 정의 configureViewResolvers () 메소드가 호출되는 방법에 대한 간략한 분석입니다 . WebMvc의 모든 기능은 기본적으로 WebMvcConfigurationSupport 클래스에 하드코딩되어 있는 것으로 알고 있는데 이는 좋지 않습니다.개발자도 이런 점을 생각하기 때문에 일부 구현을 확장하거나 커스터마이즈할 수 있도록 이 클래스의 각 메소드에 포인트를 묻어두는 편입니다. 핵심은 개발자가 일부 기능을 맞춤 설정할 수 있다는 것입니다 . 예를 들어 WebMvcConfigurationSupport 클래스 에 뷰 파서를 삽입하면 소스 코드는 다음과 같습니다.

	@Bean
	public ViewResolver mvcViewResolver(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    
    
		ViewResolverRegistry registry =
				new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
		configureViewResolvers(registry);

		if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
    
    
			String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.applicationContext, ViewResolver.class, true, false);
			// 这里 ==1 指的是上来容器中就有一个,这个是指定此方法 @Bean mvcViewResolver 这个 bean
			// 此时视图解析器就是 InternalResourceViewResolver
			if (names.length == 1) {
    
    
				registry.getViewResolvers().add(new InternalResourceViewResolver());
			}
		}
}

configureViewResolvers () 메소드는 서브클래스 구현에 연결될 수 있는 후크 메소드인 것으로 나타났습니다 . 이는 확장 및 사용자 지정 구현을 용이하게 하기 위한 숨겨진 지점입니다. 일반적으로 이 템플릿 메서드는 하위 클래스에서 구현되므로 하위 클래스 DelegatingWebMvcConfiguration 에서configureViewResolvers() 메서드가 어떻게 재정의되는지 확인할 수 있습니다. 소스 코드는 다음과 같습니다.

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	@Override
	protected void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		this.configurers.configureViewResolvers(registry);
	}
}

이 하위 클래스는 아무 작업도 수행하지 않고 WebMvcConfigurerComposite 클래스 메서드를 호출하므로 여기에 하나가 있습니다 委托模式. 여기에 특히 주의하세요. WebMvcConfigurerComposite 인스턴스 는 new로 직접 생성할 수 있으며 관리를 위해 Spring 컨테이너로 넘겨지지 않습니다. 그런 다음 WebMvcConfigurerComposite 소스 코드를 다음과 같이 입력합니다 .

class WebMvcConfigurerComposite implements WebMvcConfigurer {
    
    

	private final List<WebMvcConfigurer> delegates = new ArrayList<>();

	public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
    
    
		if (!CollectionUtils.isEmpty(configurers)) {
    
    
			this.delegates.addAll(configurers);
		}
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		for (WebMvcConfigurer delegate : this.delegates) {
    
    
			delegate.configureViewResolvers(registry);
		}
	}
}

위의 소스 코드에서 아무것도 하지 않고 WebMvcConfigurer 클래스에 다시 제공한다는 것을 알 수 있으므로 委托模式기능을 사용자 정의하려면 WebMvcConfigurer 인터페이스를 구현한 다음 내부의 메서드를 다시 작성할 수 있습니다.

예를 들어 여기의 MyWebMvcConfigure는 WebMvcConfigurer 인터페이스를 구현하고 configureViewResolvers() 뷰 확인자를 사용자 정의합니다. 코드는 다음과 같습니다.

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
	
		registry.freeMarker();
		registry.jsp("/",".jsp");
	}

이 클래스는 Spring에서 관리해야 하므로 @Component 또는 @Configuration 주석을 추가해야 합니다 . WebMvcConfigurationSupport 하위 클래스 DelegatingWebMvcConfiguration 클래스에서 이 클래스는 WebMvcConfigurerComposite 클래스 의 대리자 컬렉션 에 추가됩니다 . 소스 코드는 다음과 같습니다. :

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
    

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
    
    
		if (!CollectionUtils.isEmpty(configurers)) {
    
    
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
}

class WebMvcConfigurerComposite implements WebMvcConfigurer {
    
    

	private final List<WebMvcConfigurer> delegates = new ArrayList<>();

	public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
    
    
		if (!CollectionUtils.isEmpty(configurers)) {
    
    
			this.delegates.addAll(configurers);
		}
	}
	
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    
		for (WebMvcConfigurer delegate : this.delegates) {
    
    
			delegate.configureViewResolvers(registry);
		}
	}
}

WebMvcConfigurerComposite 클래스 configureViewResolvers () 메소드를 순회할 때 사용자 정의 구현 클래스 MyWebMvcConfigureconfigureViewResolvers () 메소드를 콜백하여 사용자 정의를 완료할 수 있습니다.

마지막으로, 커스터마이징 시 소스코드가 어떻게 실행되는지 먼저 확인해야 하며, 그렇지 않으면 세부적인 오류가 발생하기 쉽다는 점을 상기시켜드리고 싶습니다.

2. DispatcherServlet.properties 구성 파일

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

5. 레지스트리 또는 handlerMap 매핑 관계 설정

마지막으로 위의 빨간색 글꼴로 표시된 질문을 다시 살펴보세요. handlerMap을 어디에서 초기화합니까 ? 이전 분석을 통해 handlerMap은 Controller, AbstractController 및 HttpRequestHandler 인터페이스 유형을 구현하는 Controller 매핑 관계를 저장하는 데 사용됩니다. SimpleHandlerMapping 클래스에는 urlPath와 Controller 사이의 매핑 관계를 저장할 수 있는 urlMap 컨테이너가 있는데, 최종적으로 urlMap 컨테이너의 값이 handlerMap 컨테이너에 채워지게 된다 . 다음으로 Controller 예제를 살펴보고 handlerMap 에 대한 매핑 관계를 저장하는 위치를 확인하세요 . 코드는 다음과 같습니다.

@org.springframework.stereotype.Controller("/controller")
public class HttpController implements Controller {
    
    
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
        return null;
    }
}

소스 코드를 추적해 보면 호출이 Spring 컨테이너 시작에서 트리거되는 것을 발견했습니다. 소스 코드는 다음과 같습니다.

public abstract class ApplicationObjectSupport implements ApplicationContextAware {
    
    

	@Override
	public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
    
    
		if (context == null && !isContextRequired()) {
    
    
			initApplicationContext(context);
		}
	}
}

그런 다음 계속해서 initApplicationContext() 메서드를 추적하고 마지막으로 RegisterHandler() 메서드를 호출합니다. 소스 코드는 다음과 같습니다.

	protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
    
    
		Assert.notNull(urlPaths, "URL path array must not be null");
		for (String urlPath : urlPaths) {
    
    
			registerHandler(urlPath, beanName);
		}
	}
	
	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    
    
	
		this.handlerMap.put(urlPath, resolvedHandler);
	}

Spring 컨테이너가 시작될 때 handlerMap 컨테이너에 매핑 관계가 저장되는 것으로 확인되었습니다 . 따라서 나중에 DispatcherServlet 클래스에서 get() 메서드를 통해 직접 값을 가져올 수 있습니다.

여기서 초기화된 레지스트리 컨테이너의 값은 무엇입니까? 위의 분석을 통해 @RequestMapping 형식의 Controller 매핑 관계가 이 Map 컨테이너에 저장됩니다. 구체적으로 이 컨테이너의 값이 어떻게 초기화되는지 살펴보겠습니다.

추적 코드는 Spring 컨테이너임을 발견하면 호출을 트리거합니다. 핵심 소스 코드는 다음과 같습니다.

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    
    

	@Override
	public void afterPropertiesSet() {
    
    
		initHandlerMethods();
	}
}

Spring 선언사이클 클래스인 InitializingBean을 이용하여 구현된 것으로 확인되며, initHandlerMethods() 코드를 입력하고, 핵심 소스코드는 다음과 같다.

	protected void detectHandlerMethods(Object handler) {
    
    

		if (handlerType != null) {
    
    
			Class<?> userType = ClassUtils.getUserClass(handlerType);

			methods.forEach((method, mapping) -> {
    
    
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

@RequestMapping 형식으로 정의된 Controller 의 모든 메소드를 가져오고 , 각 메소드에 대한 매핑 관계를 설정합니다. 계속해서 RegisterHandlerMethod() 메소드로 진행하세요. 핵심 소스 코드는 다음과 같습니다.

	protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    
    
		this.mappingRegistry.register(mapping, handler, method);
	}

계속해서 Register() 메소드를 입력하면 핵심 소스 코드는 다음과 같습니다.

class MappingRegistry {
    
    

		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

		private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

		private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

		private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

		public Map<T, HandlerMethod> getMappings() {
    
    
			return this.mappingLookup;
		}

		@Nullable
		public List<T> getMappingsByUrl(String urlPath) {
    
    
			return this.urlLookup.get(urlPath);
		}
		
		public void register(T mapping, Object handler, Method method) {
    
    
			// Assert that the handler method is not a suspending one.
			if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
    
    
				Class<?>[] parameterTypes = method.getParameterTypes();
				if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
    
    
					throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
				}
			}
			this.readWriteLock.writeLock().lock();
			try {
    
    
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				validateMethodMapping(handlerMethod, mapping);
				this.mappingLookup.put(mapping, handlerMethod);

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
    
    
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
    
    
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
    
    
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			}
			finally {
    
    
				this.readWriteLock.writeLock().unlock();
			}
		}
}

이것매핑레지스트리#register()가장 중요한 것은 각 메소드의 매핑 관계를 Map 컨테이너이기도 한 레지스트리에 저장하는 것입니다. 단지 해당 키가 레이어에 래핑되고 @RequestMapping 속성이 구문 분석되어 RequestMappingInfo 객체로 캡슐화된다는 것입니다. 그런 다음 값은 HandlerMethod 객체 로 캡슐화되는 @RequestMapping 수정된 메서드입니다 .

6. 매개변수 파서 및 반환값 파서 초기화

마지막으로 호출 과정 중 매개변수 파서와 반환값 파서 의 값이 어디서 초기화되는지 살펴보자.
소스 코드를 추적하고 최종적으로 초기화Bean 선언 사이클 클래스를 사용하여 RequestMappingHandlerAdapter 클래스에 구현합니다. 핵심 소스 코드는 다음과 같습니다.

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    
    

	@Override
	public void afterPropertiesSet() {
    
    
		// Do this first, it may add ResponseBody advice beans
		initControllerAdviceCache();

		if (this.argumentResolvers == null) {
    
    
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		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);
		}
	}
}

getDefaultArgumentResolvers() 메소드를 입력합니다. 핵심 소스 코드는 다음과 같습니다.

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

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

이에 대해 기본적으로 30개의 매개변수 해석기가 생성되고 최종적으로 나중에 사용할 수 있는 전역 변수 인수Resolvers 컬렉션에 할당됩니다. 마찬가지로 반환값도 같은 방식으로 이루어지며 핵심 소스코드는 다음과 같다.

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

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

그런 다음 나중에 사용할 수 있는 전역 변수 returnValueHandlers 컬렉션에 값을 할당합니다. 여기에는 RequestResponseBodyMethodProcessorRequestPartMethodArgumentResolver 라는 두 가지 특수 매개변수 구문 분석이 있음을 알 수 있습니다 . 여기서는 첫 번째 매개변수만 살펴보겠습니다. 이를 구문 분석하고 캡슐화하려면 MessageConverter 메시지 변환기 를 사용해야 합니다 (주로 @RequestBody 및 @ResponseBody 매개변수 구문 분석 및 변환 , 주로 JSON을 엔터티로 변환하고 엔터티는 JSON으로 변환합니다.

7. MessageConverter 메시지 변환기 초기화

getMessageConverters() 메소드를 입력합니다. 소스 코드는 다음과 같습니다.

	public List<HttpMessageConverter<?>> getMessageConverters() {
    
    
		return this.messageConverters;
	}

그런 다음 messageConverters 변수가 어디에 할당되어 있는지 확인해야 합니다. 추적 소스 코드는 다음과 같습니다.

	@Bean
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcValidator") Validator validator) {
    
    

		RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
		adapter.setContentNegotiationManager(contentNegotiationManager);
		adapter.setMessageConverters(getMessageConverters());
		adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
		adapter.setCustomArgumentResolvers(getArgumentResolvers());
		adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

		if (jackson2Present) {
    
    
			adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
			adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
		}

		AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
		configureAsyncSupport(configurer);
		if (configurer.getTaskExecutor() != null) {
    
    
			adapter.setTaskExecutor(configurer.getTaskExecutor());
		}
		if (configurer.getTimeout() != null) {
    
    
			adapter.setAsyncRequestTimeout(configurer.getTimeout());
		}
		adapter.setCallableInterceptors(configurer.getCallableInterceptors());
		adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

		return adapter;
	}

호출 코드Adapter.setMessageConverters(getMessageConverters());getMessageConverters()의 핵심 소스 코드를 입력하세요.

	protected final List<HttpMessageConverter<?>> getMessageConverters() {
    
    
		if (this.messageConverters == null) {
    
    
			this.messageConverters = new ArrayList<>();
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
    
    
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}

messageConverters를 직접 구성하지 않으면 기본 messageConverters가 있게 됩니다. addDefaultHttpMessageConverters () 메서드를 입력하세요. 소스 코드는 다음과 같습니다.


	static {
    
    
		ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
	}


	protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    
    
		messageConverters.add(new ByteArrayHttpMessageConverter());
		messageConverters.add(new StringHttpMessageConverter());
		messageConverters.add(new ResourceHttpMessageConverter());
		messageConverters.add(new ResourceRegionHttpMessageConverter());
		try {
    
    
			messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Throwable ex) {
    
    
			// Ignore when no TransformerFactory implementation is available...
		}
		messageConverters.add(new AllEncompassingFormHttpMessageConverter());

		if (romePresent) {
    
    
			messageConverters.add(new AtomFeedHttpMessageConverter());
			messageConverters.add(new RssChannelHttpMessageConverter());
		}

		if (jackson2XmlPresent) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
		}
		else if (jaxb2Present) {
    
    
			messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
		}

		if (jackson2Present) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
		}
		else if (gsonPresent) {
    
    
			messageConverters.add(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
    
    
			messageConverters.add(new JsonbHttpMessageConverter());
		}

		if (jackson2SmilePresent) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
		}
		if (jackson2CborPresent) {
    
    
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
			if (this.applicationContext != null) {
    
    
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
		}
	}

위의 소스 코드에서 해당 종속 타사 jar 패키지를 도입하는 한 해당 변환기가 그에 따라 자동으로 추가된다는 것을 알 수 있습니다. 예를 들어 관련 패키지를 도입하면
com.fasterxml.jackson.databindMappingJackson2HttpMessageConverter 메시지 변환기가 있을 것입니다. 따라서 @RequestBody, @ResponseBody 어노테이션을 사용할 때는 먼저 해당 패키지가 존재하는지 확인하고, 존재하지 않으면 변환이 불가능하고 직접 오류를 보고할 수 없습니다.

한 가지 더 참고할 사항: MessageConverter를 사용자 정의하려면 configureMessageConverters() 메서드 대신 extendMessageConverters() 메서드를 재정의하는 것이 좋습니다. 명확성을 위해 다음과 같이 소스 코드를 살펴보세요.

	protected final List<HttpMessageConverter<?>> getMessageConverters() {
    
    
		if (this.messageConverters == null) {
    
    
			this.messageConverters = new ArrayList<>();
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
    
    
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}

Supongo que te gusta

Origin blog.csdn.net/qq_35971258/article/details/128921548
Recomendado
Clasificación