Analyse du code source DispatcherServlet et @EnableWebMvc de Spring et quelques problèmes

1. Analyse de la classe DispatcherServlet

Entrez directement la classe DispatcherServlet. Le code source principal est le suivant :


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

Vient ensuite un très long processus d'analyse, analysant les quatre méthodes ci-dessus : getHandler (), getHandlerAdapter (), handle (), processDispatchResult ().

1. Analyse de la méthode getHandler()

Lorsqu'une requête arrive, la première entre dans la méthode getHandler () et la relation de mappage correspondante est trouvée via cette méthode. Le code source principal est le suivant :

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

Conseils : Pourquoi HandlerMethod ou la classe elle-même devrait-elle à nouveau envelopper HandlerExecutionChain ? Étant donné que SpringMVC possède de nombreux intercepteurs, il est nécessaire d'envelopper la méthode cible et l'intercepteur dans le contexte HandlerExecutionChain.

Où la collection handlerMappings est-elle initialisée ici ? Analysez d’abord l’ensemble du processus, puis regardez en arrière. Continuer à entrermapping.getHandler (demande)Logique interne, le code source principal est le suivant :

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

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

Les gestionnaires renvoyés ici ne sont pas nécessairement tous des types HandlerMethod . Il peut s'agir de la classe elle-même . Seuls ceux qui sont annotés avec @RequestMapping renverront le type HandlerMethod . Renvoie la classe elle-même, comme les classes qui implémentent les interfaces Controller , AbstractController et HttpRequestHandler , car les méthodes Il n'y en a qu'une. Si vous trouvez la classe, vous pouvez déterminer quelle méthode gérera la requête en cours, donc le gestionnaire renvoyé à ce moment est la classe elle-même. C'est pourquoi l'objet gestionnaire de retour est accepté via le type Object .

Vérifiez que la méthode getHandlerInternal() est une méthode hook. Le code source est le suivant :

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

Il existe ici deux implémentations de sous-classes évidentes : AbstractHandlerMethodMapping et AbstractUrlHandlerMapping. En regardant les noms, vous pouvez dire que la première est pour l'annotation @RequestMapping et la seconde est pour l'implémentation des interfaces Controller , AbstractController et HttpRequestHandler .

Analysez d’abord la classe simple AbstractUrlHandlerMapping et entrez le code source principal comme suit :

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

initLookupPath () génère un chemin de mappage basé sur les données transmises par la requête, qui est équivalent à la clé. Trouvez le gestionnaire correspondant en fonction de la clé.

Par exemple, lors de l'accès à la classe HelloHttpRequestHandler ci-dessous , key = /hello, la valeur du gestionnaire est l'instance de classe de HelloHttpRequestHandler elle-même.

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

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

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

Enfin lookupPath = /hello, appelez la méthode lookupHandler(), le code source principal est le suivant :

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

Continuez avec getDirectMatch(). Le code source principal est le suivant :

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

D'après le code source ci-dessus, il est évident que la valeur du gestionnaire est finalement récupérée à partir de handlerMap , urlPath = /hello, elle peut donc être résumée ici : Les gestionnaires qui implémentent les types d'interface Controller , AbstractController et HttpRequestHandler sont tous des instances de la classe elle-même. , et la relation de mappage est Elle est enregistrée dans la collection handlerMap . Quand la valeur attribuée à ce handlerMap est-elle initialisée ? , analysez l’ensemble du processus et regardez en arrière.

Analysez ensuite la classe AbstractHandlerMethodMapping et entrez le code source principal comme suit :

	@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();
		}
	}

Le code source ci-dessus observe principalement cette phrasethis.mappingRegistry.getRegistrations(), saisissez le code source principal comme suit :

	class MappingRegistry {
    
    

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

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

On peut constater que la relation de mappage de l' annotation @RequestMapping est également stockée dans un conteneur de registre de type Map . L' annotation @RequestMapping est encapsulée dans un objet RequestMappingInfo en tant que clé dans le conteneur de registre de requête.

La valeur du conteneur de registre est l' instance de type HandlerMethodMapping#MappingRegistration . MappingRegistration encapsule le type HandlerMethod du gestionnaire, c'est-à-dire la méthode spécifique correspondante +@ dans le contrôleur. Les informations RequestMapping sont encapsulées dans un objet HandlerMethod . On peut donc conclure que la relation de mappage sous la forme de @RequestMapping est finalement stockée dans un conteneur Map .

Quelle est la valeur dans le conteneur de registre initialisé ici ? Analysez l’ensemble du processus, puis revenez en arrière et analysez-le.

Résumé

Après avoir analysé cela, nous pouvons savoir que la méthode getHandler () renverra éventuellement deux valeurs de type, l'une est HandlerMethod (utilisez l'annotation @RequestMapping pour renvoyer ce type) et l'autre est la classe elle-même (l'implémentation des interfaces Controller et HttpRequestHandler renvoie le classe elle-même, instance ).

Remarque : une méthode capable de gérer la requête en cours a été trouvée via la requête de requête. L'étape suivante consiste à voir comment appeler la méthode. Cela dépend ensuite de la façon dont la méthode getHandlerAdapter () adapte l'appel, car la méthode se forme dans chaque type de contrôleur est différent et identique, donc une couche d'adaptateur est nécessaire pour encapsuler l'appel.

2. Analyse de la méthode getHandlerAdapter()

Ensuite, regardez la deuxième grande étape, la méthode getHandlerAdapter (). Le code source principal est le suivant :

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

On peut voir qu'il existe de nombreux adaptateurs HandlerAdapter fournis. Où est l'initialisation des handlerAdapters ? Une analyse ultérieure est utilisée pour adapter et traiter les appels de méthode des différentes formes de Controller. Étant donné que la forme de chaque type de Controller est un peu différente, un Un adaptateur est nécessaire pour adapter le traitement. Pour parler franchement, il enveloppe simplement une couche d'appels au milieu. Voici un exemple courant :

  • HandlerFunctionAdapter : méthode de contrôleur qui gère les types de programmation fonctionnelle, non encore utilisée.

  • HttpRequestHandlerAdapter : Traite l'appel de la méthode Controller qui implémente l'interface HttpRequestHandler. Cette méthode n'a pas besoin de renvoyer de valeur, vous pouvez donc définir un Controller de type HttpRequestHandler lorsqu'il n'est pas nécessaire de renvoyer de valeur.

  • SimpleControllerHandlerAdapter : gère l'appel de la méthode Controller qui implémente les interfaces Controller et AbstractController. Cette méthode doit renvoyer une valeur.

  • AbstractHandlerMethodAdapter : gère les appels de méthode du formulaire d'annotation @RequestMapping, qui peuvent ou non avoir une valeur de retour. Il s'agit plutôt d'une combinaison de types Controller et HttpRequestHandler.

La méthode d'écriture ci-dessus est très typique 动态策略模式et a une grande importance en matière de référence. Sans plus tarder, procédons à l’analyse.

Retour à la demande ci-dessushttp://localhost:9292/bonjourLe type de contrôleur demandé est de type HttpRequestHandler, il sera donc traité par cet adaptateur HttpRequestHandlerAdapter . Cela dépend de la façon dont il le gère. Le code source principal est le suivant :

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

À partir du code source ci-dessus, nous pouvons savoir que le gestionnaire est actuellement HelloHttpRequestHandler et que le type est HttpRequestHandler, donc la méthode de traitement handle() sera appelée. Cette méthode fait deux choses. Elle rappelle la méthode handleRequest() de la classe HelloHttpRequestHandler, puis renvoie une valeur nulle. En créant simplement un processus d'appel simple, l'adaptateur HttpRequestHandlerAdapter fait ces deux choses.

Prenons un exemple, comme le Controller suivant, le code est le suivant :

@Controller
public class JspController {
    
    

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

en demandehttp://localhost:9292/toJsp, il sera traité par l'adaptateur AbstractHandlerMethodAdapter. L'adaptateur doit également encapsuler comment appeler la méthode toJsp() dans JspController. Entrez le code source principal comme suit :

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

À partir du code source ci-dessus, nous pouvons savoir que JspController est composé du formulaire @RequestMapping, donc le gestionnaire dans la méthode supports() est le type HandlerMethod , qui peut être traité par l'adaptateur AbstractHandlerMethodAdapter, puis appelle la méthode de traitement handle() et entre le code source principal de handleInternal() comme suit :

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

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

Continuez avec la méthode InvokeHandlerMethod(). Le code source principal est le suivant :

	@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();
		}
	}

À partir du code source ci-dessus, j'ai trouvé que beaucoup de choses ont été faites :

  • Obtenir l'usine de liaison de paramètres binderFactory

  • Encapsuler à nouveau handlerMethod dans ServletInvocableHandlerMethod

  • Définir l'analyseur de paramètres

  • Définir l'analyseur de valeur de retour

  • Définir la fabrique de liaison de données (l'annotation (@RequestBody est utile)

  • new a créé un conteneur ModelAndViewContainer, similaire à Context

Où sont initialisés les éléments obtenus ci-dessus ? Revenons sur tout cela, continuons à analyser et entrons le code source principal de la méthode EnsureAndHandle() comme suit :

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

Elle peut être divisée selon les étapes suivantes :

  • Appeler des méthodes spécifiques dans le contrôleur
  • Définissez le commutateur de rendu de vue requestHandled, false signifie que le rendu de vue est requis, true signifie que le rendu de vue n'est pas requis.
  • Traitement de la valeur de retour de la méthode dans le contrôleur

Examinons d'abord la méthode InvokeForRequest(). Le code source principal est le suivant :

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

On peut constater que cette méthode analyse principalement les valeurs des paramètres et appelle des méthodes spécifiques dans le contrôleur par réflexion. Voyons comment il analyse les paramètres et entre dans la méthode getMethodArgumentValues(). Le code source principal est le suivant :

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

À partir du code source ci-dessus, nous pouvons savoir que chaque paramètre est traité et que chaque paramètre de type aura une stratégie pour le traiter. S'il ne peut pas être traité, une exception sera levée directement et le processus de demande se terminera. C'est aussi une dynamique typique 策略模式. Par exemple:

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

Tout d'abord, chaque type de paramètre est différent et une stratégie doit être utilisée pour le gérer. Il existe donc de nombreux analyseurs de paramètres, actuellement au moins >17.
Après avoir analysé les valeurs des paramètres ici, vous pouvez enfin appeler des méthodes spécifiques par réflexion et effectuer un traitement logique personnalisé.

Ensuite, définissez le commutateur requestHandled en fonction de la valeur de retour. S'il y a une valeur de retour, elle est définie sur false, ce qui signifie que le rendu de la vue est requis. S'il n'y a pas de valeur de retour, elle est définie sur true, ce qui signifie qu'il y a pas de rendu de vue.

Ensuite, la dernière étape consiste à traiter la valeur de retour. Entrez la méthode handleReturnValue(). Le code source principal est le suivant :

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

Cela nécessite également un certain nombre de stratégies pour gérer différents types de valeurs de retour. Comme indiqué ci-dessous:

Insérer la description de l'image ici

Une fois le traitement de la valeur de retour terminé, revenez à l'appel de niveau supérieur et continuez à saisirreturn getModelAndView (mavContainer, modelFactory, webRequest);Le code source principal est le suivant :

3. Créez un objet 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;
	}

À partir du code source ci-dessus, nous pouvons savoir que lorsque requestHandled=true, null est renvoyé directement, indiquant que le rendu de la vue n'est pas requis et qu'il n'est pas nécessaire d'effectuer la préparation des données ModeAndView suivante. S'il est faux, le rendu de la vue est requis, donc le code suivant sera exécuté ici pour préparer les données de mode, spécifier la vue logique et préparer les données de la mémoire flash s'il y a de la mémoire flash. Revenez enfin à ModelAndView. Continuez ensuite à exécuter le processus d’analyse de la vue.

4. Afficher l'analyse et le rendu

Entrez processDispatchResult() et le code source principal est le suivant :

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

Comme le montre le petit code ci-dessus, mv est l'objet ModelAndView. S'il est nul, le rendu de la vue n'est pas nécessaire. Quand est-ce que mv est nul ? Comme cela a été analysé ci-dessus, il est contrôlé par un commutateur requestHandled. Lorsque requestHandled = true, null est renvoyé directement, indiquant que le rendu de la vue n'est pas requis et qu'il n'est pas nécessaire d'effectuer la préparation des données ModeAndView suivante. Si faux, le rendu de la vue est requis.

Entrez le code source principal de render() comme suit :

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

Voici quelques analyseurs de vues courants :

  • InternalResourceViewResolver : génère la vue View de la page JSP. Spring ne détermine pas si la page JSP doit exister. Tomcat le gère tout seul.
  • FreeMarkerViewResolver : génère la vue View de la page du modèle .ftl. Spring déterminera si la ressource du fichier .ftl existe.
  • ThymeleafViewResolver : génère la vue View de la page du modèle .html. Spring déterminera si la ressource du fichier .html existe.

Une fois la vue View générée avec succès, il est nécessaire d'afficher l'effet. Ensuite, vous devez appeler la méthode de rendu render() dans la vue View pour afficher l'effet afin que vous puissiez le voir à l'œil nu.

Enfin, l'ensemble du processus d'appel SpringMVC se termine. Le reste consiste à analyser quelles données sont obtenues par get() et où elles sont initialisées.

Lorsqu'une requête arrive, la première entre dans la méthode getHandler () et la relation de mappage correspondante est trouvée via cette méthode. Le code source principal est le suivant :

2. Initialisation du 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;
	}

Regardez le code ci-dessous, où se trouve la valeur d'initialisation de handlerMappings ? Vous pouvez voir un morceau de code source comme suit :

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

Astuces : Lorsque le conteneur démarre, initStrategies() sera appelé par la classe d'écoute ContextRefreshListener pour initialiser les composants requis par webmvc.

Vous pouvez constater que HandlerMapping, HandlerAdapter et ViewResolver sont initialisés ici. Examinons ensuite la méthode initHandlerMappings(). Le code source principal est le suivant :

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

D'après le code source ci-dessus, on peut voir que l'initialisation se fait à deux endroits : @EnableWebMvcouDispatcherServlet.properties :

1. Méthode d'annotation @EnableWebMvc

La méthode beansOfTypeIncludingAncestors(HandlerMapping.class) peut obtenir la classe d'implémentation HandlerMapping configurée dans le conteneur. Généralement, l'annotation @EnableWebMvc introduira la classe de configuration et la classe d'implémentation HandlerMapping est injectée via @Bean dans la classe de configuration.

Le code source de l'annotation @EnableWebMvc est le suivant :

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

}

La classe de configuration DelegatingWebMvcConfiguration est introduite via l'annotation Spring @Import. Le code source principal est le suivant :

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

}

Le code source principal se trouve dans la classe parent WebMvcConfigurationSupport. Examinons d'abord le code source principal d'initialisation de HandlerMapping comme suit :

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. Méthode de configuration DispatcherServlet.properties

Si la classe d'implémentation HandlerMapping n'est pas trouvée via les classes de configuration ci-dessus, il y aura une solution secrète. La méthode getDefaultStrategies () recherchera DispatcherServlet.propertiesla classe d'implémentation HandlerMapping configurée dans le fichier de configuration. Comme indiqué ci-dessous:

Insérer la description de l'image ici

DispatcherServlet.propertiesLe fichier de configuration est le suivant :

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 : traite la relation de mappage du contrôleur qui implémente les interfaces Controller, AbstractController et HttpRequestHandler. En fait, la couche inférieure maintient une carte.

RequestMappingHandlerMapping : il gère le mappage des relations entre les contrôleurs sous la forme de @RequestMapping. La couche inférieure maintient un conteneur Map.

SimpleUrlHandlerMapping : mappage de relation d'URL simple. Vous pouvez personnaliser une URL, puis lier un processeur. Il est souvent utilisé pour l'expansion, comme l'accès aux ressources statiques, le saut de contrôleur, etc.

3. Brève introduction à SimpleUrlHandlerMapping

Le code source de cette classe est le suivant :

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

}

J'ai découvert qu'un conteneur urlMap est conservé à l'intérieur de , qui doit être utilisé pour maintenir la relation de mappage entre urlPath et le gestionnaire. Lorsque vous utilisez cette classe, il vous suffit de stocker la relation entre urlPath et le gestionnaire dans le conteneur urlMap, mais veuillez noter qu'ici ne prend en charge que le gestionnaire qui est le contrôleur de la classe elle-même, car lors de l'enregistrement de la relation, vous ne pouvez en remplir qu'un seul. valeur, et cette valeur ne peut pas être précise à une certaine méthode, donc @RequestMapping ne peut pas être lié à cela. Cela ne prend en charge que les contrôleurs qui implémentent les types Controller, AbstractController et HttpRequestHandler.

Un cas d'utilisation est le suivant :

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

De cette façon, un certain chemin d'accès à l'URL peut être lié à un certain contrôleur et accessible sur le navigateur :http://localhost:8887/area/index
Vous pouvez accéder au contrôleur.

Ensuite, nous voyons deux autres cas d'utilisation de cette classe comme suit :

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

Tout le monde aurait dû utiliser la fonction webmvc personnalisée. Vous pouvez spécifier l'accès aux ressources statiques via addResourceHandlers() et spécifier à quelle page accéder via la méthode addViewControllers(). La couche inférieure de ces deux fonctions est complétée par la classe SimpleUrlHandlerMapping.

Analysons d'abord la méthode d'accès aux ressources statiques ajoutée par addResourceHandlers ().La trace de débogage est finalement appelée dans le code source principal suivant :

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

À partir du code source ci-dessus, nous pouvons voir que les chemins d'accès aux ressources statiques entrantes sont encapsulés dans le conteneur urlMap de la classe SimpleUrlHandlerMapping. C'est pourquoi tu écris une ligne de code comme celle-ciregistre.addResourceHandler(“/**”).addResourceLocations(“classpath:/static/”)Vous pouvez accéder aux ressources statiques sur le navigateur, car la classe SimpleUrlHandlerMapping vous aide à établir la relation de mappage du chemin d'accès et du contrôleur. Le contrôleur ici est ResourceHttpRequestHandler. Chaque chemin correspond à une nouvelle classe ResourceHttpRequestHandler.

Analysons la méthode addViewControllers () et traçons le débogage jusqu'au point appelant. Le code source principal est le suivant :

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

À partir du code ci-dessus, vous pouvez savoir que la classe SimpleUrlHandlerMapping établit une relation de mappage entre urlPath et ParameterizingViewController, vous savez donc pourquoi vous ajoutez une ligne dans la méthode addViewControllers().registre.addViewController("/gotoJsp").setViewName("abc2");Ce code est accessible sur le navigateur. Le chemin d'accès est :http://localhost:8887/gotoJsp

Parmi eux, abc.jsp doit exister sous la webapp , sinon une erreur 404 se produira.

Insérer la description de l'image ici

L'effet d'accès final est le suivant :

Insérer la description de l'image ici

Par conséquent, la classe SimpleUrlHandlerMapping est très importante. Vous pouvez utiliser cette classe pour effectuer des liaisons de relations de mappage dynamique, etc.

3. Initialisation du HandlerAdapter

HandlerAdapter et HandlerMapping sont fondamentalement similaires. Le code source principal est le suivant :

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

C'est la même chose de deux manières @EnableWebMvcet DispatcherServletl.properties.

1. Méthode d'annotation @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. Fichier de configuration 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. Initialisation de ViewResolver

L'analyseur de vue est initialisé et le code source principal est saisi comme suit :

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

Vous pouvez constater que l'initialisation de l'analyse des vues est fondamentalement la même routine que HandlerAdapter et HandlerMapping, mais ce qui doit être noté ici est que l'annotation @EnableWebMvc contient certains détails lors de l'injection de l'analyseur de vues. Comme suit:

1. @EnableWebMvc injecte le résolveur de vue

Saisissez le code source comme suit :

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

Lisez attentivement le code source ci-dessus et recherchez la classe d'implémentation de l'interface ViewResolver à partir du conteneur Spring via la méthode beanNamesForTypeIncludingAncestors (). Si vous ne configurez aucune classe d'implémentation ViewResolver , il n'y en aura qu'une dans le conteneur, qui est la classe d'implémentation actuelle. @Bean a modifié la méthode mvcViewResolver (), il ne fait aucun doute qu'un seul sera trouvé. Pour le moment, SpringMVC nous aidera à construire un résolveur de vue InternalResourceViewResolver . Ce résolveur de vue analyse les fichiers de ressources JSP par défaut. Pensez à configurer le suffixe et le préfixe . S'ils ne correspondent pas, ajoutez le suffixe et le préfixe sur le chemin d'accès.http://localhost:9292/toJsp?cx=abc2.jspVoilà, si l'accès direct est configuréhttp://localhost:9292/toJsp?cx=abc2, de toute façon, il s'agit de former une route complète.

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

En plus du chargement par défaut, il peut également être personnalisé, mais vous devez implémenter une interface WebMvcConfigurer puis réécrire les méthodes correspondantes. Par exemple :

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

Cependant, veuillez noter que lorsque vous personnalisez vous-même des fonctions, vous devez d'abord examiner comment le code source est implémenté, car il existe en effet de nombreux détails qui peuvent facilement conduire à des erreurs si vous n'y faites pas attention. Par exemple, dans la méthode configureViewResolvers (), lorsque les fichiers JSP et FTL existent en même temps, vous devez placer le fichier FLT devant le JSP pour analyser la ressource normalement.

Pour savoir comment ces fonctions personnalisées sont chargées, voici un modèle de conception : 委托模式. Cela signifie que la mise en œuvre d'une certaine fonction est laissée à une autre personne pour la faire, et je l'appelle simplement.

Voici une brève analyse de la façon dont la méthode personnalisée configureViewResolvers () est appelée. Nous savons que toutes les fonctions webmvc sont essentiellement codées en dur dans la classe WebMvcConfigurationSupport, mais ce n'est pas bon. Les développeurs y pensent aussi, donc afin de pouvoir étendre ou personnaliser certaines implémentations, ils enterrent des points dans chaque méthode de cette classe L' essentiel est de permettre aux développeurs de personnaliser certaines fonctions. Par exemple, injectez l'analyseur de vue dans la classe WebMvcConfigurationSupport . Le code source est le suivant :

	@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());
			}
		}
}

On constate que la méthode configureViewResolvers () est une méthode hook qui peut être accrochée à l'implémentation de la sous-classe. Il s'agit d'un point caché pour faciliter l'expansion et la mise en œuvre personnalisée. Généralement, cette méthode de modèle sera implémentée dans une sous-classe, vous pouvez donc voir comment la méthode configureViewResolvers() est remplacée dans la sous-classe DelegatingWebMvcConfiguration . Le code source est le suivant :

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

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

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

On peut constater que cette sous-classe ne fait rien d'autre qu'appeler la méthode de classe WebMvcConfigurerComposite, donc en voici une 委托模式. Une attention particulière doit être portée ici. L'instance de WebMvcConfigurerComposite peut être directement créée avec new et n'est pas transmise au conteneur Spring pour la gestion. Saisissez ensuite le code source WebMvcConfigurerComposite comme suit :

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

À partir du code source ci-dessus, nous pouvons constater qu'il ne fait rien, mais le donne à nouveau à la classe WebMvcConfigurer委托模式 , donc si nous voulons personnaliser la fonction, nous pouvons implémenter l'interface WebMvcConfigurer puis réécrire les méthodes à l'intérieur.

Par exemple, MyWebMvcConfigure implémente ici l' interface WebMvcConfigurer et personnalise le résolveur de vue configureViewResolvers(). Le code est le suivant :

@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
    
    

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

Notez que cette classe doit être gérée par Spring, donc l' annotation @Component ou @Configuration doit être ajoutée. Dans la sous-classe WebMvcConfigurationSupport DelegatingWebMvcConfiguration , cette classe sera ajoutée à la collection de délégués dans la classe WebMvcConfigurerComposite . Le code source est le suivant :

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

Lors du parcours de la méthode configureViewResolvers () dans la classe WebMvcConfigurerComposite, vous pouvez rappeler la méthode configureViewResolvers () dans la classe d'implémentation personnalisée MyWebMvcConfigure pour terminer la personnalisation.

Enfin, je voudrais vous rappeler que lors de la personnalisation, vous devez d'abord vérifier comment le code source est exécuté, sinon il est facile de commettre des erreurs de détail.

2. Fichier de configuration DispatcherServlet.properties

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

5. Établissement d'une relation de mappage de registre ou de handlerMap

Enfin, revenez à la question en rouge ci-dessus. Où initialiser handlerMap ? Grâce à l'analyse précédente, handlerMap est utilisé pour enregistrer la relation de mappage Controller qui implémente les types d'interface Controller, AbstractController et HttpRequestHandler. Il existe un conteneur urlMap dans la classe SimpleHandlerMapping, qui peut enregistrer la relation de mappage entre urlPath et Controller.En fin de compte, la valeur du conteneur urlMap sera remplie dans le conteneur handlerMap . Ensuite, jetez un œil à un exemple de Controller pour voir où il enregistre la relation de mappage avec handlerMap . Le code est le suivant :

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

En suivant le code source, nous avons finalement découvert que l'appel est déclenché dès le démarrage du conteneur Spring. Le code source est le suivant :

public abstract class ApplicationObjectSupport implements ApplicationContextAware {
    
    

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

Continuez ensuite à tracer la méthode initApplicationContext(), et enfin appelez la méthode registerHandler(). Le code source est le suivant :

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

Il a été constaté que la relation de mappage avait été enregistrée dans le conteneur handlerMap au démarrage du conteneur Spring . Ainsi, plus tard dans la classe DispatcherServlet , vous pourrez directement obtenir la valeur via la méthode get().

Quelle est la valeur dans le conteneur de registre initialisé ici ? Grâce à l'analyse ci-dessus, la relation de mappage du contrôleur sous la forme de @RequestMapping sera enregistrée dans ce conteneur Map. Plus précisément, regardons comment la valeur dans ce conteneur est initialisée ?

Le code de suivi déclenche l'appel lorsqu'il est découvert qu'il s'agit également d'un conteneur Spring. Le code source principal est le suivant :

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

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

On constate qu'il est implémenté à l'aide de la classe de cycle de déclaration Spring InitializingBean . Entrez le code initHandlerMethods() . Le code source principal est le suivant :

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

Obtenez toutes les méthodes du contrôleur définies dans le formulaire @RequestMapping et établissez une relation de mappage pour chaque méthode. Continuez dans la méthode registerHandlerMethod(). Le code source principal est le suivant :

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

Continuez à saisir la méthode register(), le code source principal est le suivant :

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

ceMappingRegistry#register()L'essentiel est de sauvegarder la relation de mappage de chaque méthode dans le registre, qui est également un conteneur Map. C'est juste que leur clé est enveloppée dans une couche et que l'attribut @RequestMapping est analysé et encapsulé dans un objet RequestMappingInfo . Ensuite, la valeur est la méthode modifiée @RequestMapping qui est encapsulée dans un objet HandlerMethod .

6. Initialisation de l'analyseur de paramètres et de l'analyseur de valeurs de retour

Enfin, regardons où les valeurs de l'analyseur de paramètres et de l'analyseur de valeurs de retour sont initialisées lors du processus appelant ?
Tracez le code source et enfin implémentez-le dans la classe RequestMappingHandlerAdapter à l'aide de la classe de cycle de déclaration InitializingBean. Le code source principal est le suivant :

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

Entrez la méthode getDefaultArgumentResolvers(), le code source principal est le suivant :

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

Sur celui-ci, 30 résolveurs de paramètres sont créés par défaut, et finalement affectés à la collection argumentResolvers de variable globale, qui pourra être utilisée ultérieurement. De la même manière, la valeur de retour est également effectuée de la même manière. Le code source principal est le suivant :

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

Attribuez ensuite la valeur à la collection variable globale returnValueHandlers , qui pourra être utilisée ultérieurement. Vous constaterez ici qu'il existe deux paramètres spéciaux d'analyse, RequestResponseBodyMethodProcessor et RequestPartMethodArgumentResolver . Ici, nous examinons uniquement le premier. Vous devez utiliser le convertisseur de messages MessageConverter pour l'analyser et l'encapsuler (principalement en analysant et en convertissant les paramètres @RequestBody et @ResponseBody , et principalement convertir JSON en entités , entités converties en JSON).

7. Initialisation du convertisseur de messages MessageConverter

Entrez la méthode getMessageConverters(), le code source est le suivant :

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

Ensuite, vous devez voir où la variable messageConverters est affectée. Le code source de suivi est le suivant :

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

code d'appeladapter.setMessageConverters(getMessageConverters());Entrez le code source principal de 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;
	}

Si nous ne configurons pas nous-mêmes messageConverters, alors il y aura des messageConverters par défaut, entrez la méthode addDefaultHttpMessageConverters (), le code source est le suivant :


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

À partir du code source ci-dessus, nous pouvons constater que tant que vous introduisez le package jar tiers dépendant correspondant, le convertisseur correspondant sera automatiquement ajouté en conséquence. Par exemple, si vous introduisez
com.fasterxml.jackson.databinddes packages associés, il y aura un convertisseur de messages MappingJackson2HttpMessageConverter. Ainsi, lorsque vous utilisez les annotations @RequestBody et @ResponseBody, vérifiez d'abord si ces packages existent, sinon vous ne pourrez pas les convertir et signaler directement une erreur.

Encore une chose à noter : si vous souhaitez personnaliser MessageConverter, il est recommandé de remplacer la méthode extendMessageConverters() au lieu de la méthode configureMessageConverters(). Regardez le code source pour plus de clarté, comme suit :

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

Je suppose que tu aimes

Origine blog.csdn.net/qq_35971258/article/details/128921548
conseillé
Classement