基础环境: spring-boot :2.3.3.RELEASE、jdk1.8
0. 背景:
通过以下方式可以自定义视图,即访问/testView会跳转success.html页面
那么这样设置的原理是什么?本文就是探讨这个问题
@Configuration
public class MyConfigure implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/testView").setViewName("success");
}
}
1. 入口方法:
springboot启动类启动后,会自动配置mvc的组件,即WebMvcAutoConfiguration,
在配置的时候会自动配置适配器,即WebMvcAutoConfigurationAdapter,
该类在配置的时候会import一个组件,即EnableWebMvcConfiguration,
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
EnableWebMvcConfiguration继承DelegatingWebMvcConfiguration组件,该组件继承了WebMvcConfigurationSupport,
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
在该类中,注入了一个bean,即viewControllerHandlerMapping
*/
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
... ... ...
@Bean
@Nullable
public HandlerMapping viewControllerHandlerMapping(
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
addViewControllers(registry);
AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
if (handlerMapping == null) {
return null;
}
handlerMapping.setPathMatcher(pathMatcher);
handlerMapping.setUrlPathHelper(urlPathHelper);
handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
}
也就是说,启动后会进入视图控制处理器映射器viewControllerHandlerMapping进行初始化
重点关注addViewControllers(registry);
和registry.buildHandlerMapping();
2. registrations的初始化:
addViewControllers(registry);
方法调用后会进入自定义配置类(上文背景的代码)中addViewControllers方法,
最后会进入ViewControllerRegistry类中addViewControllers方法,进行registrations的初始化
public class ViewControllerRegistry {
... ... ...
private final List<ViewControllerRegistration> registrations = new ArrayList<>(4);
... ... ...
public ViewControllerRegistration addViewController(String urlPath) {
ViewControllerRegistration registration = new ViewControllerRegistration(urlPath);
registration.setApplicationContext(this.applicationContext);
this.registrations.add(registration);
return registration;
}
2-1. 初始化handle:
在自定义配置类中registry.addViewController("/testView")
.setViewName
(“success”);
调用setViewName后会初始化ParameterizableViewController,此为mvc执行流程中的处理器handle
public class ViewControllerRegistration {
private final String urlPath;
private final ParameterizableViewController controller = new ParameterizableViewController();
... ... ...
public ViewControllerRegistration(String urlPath) {
Assert.notNull(urlPath, "'urlPath' is required.");
this.urlPath = urlPath;
}
p
ublic void setViewName(String viewName) {
this.controller.setViewName(viewName);
}
public class ParameterizableViewController extends AbstractController {
@Nullable
private Object view;
... ... ...
public void setViewName(@Nullable String viewName) {
this.view = viewName;
}
3. HandlerMapping的初始化:
处理器映射器HandlerMapping,默认使用的是简单的url处理器映射器
SimpleUrlHandlerMapping
该处理器映射器的父类将请求url和handle进行key-value储存,以便后续根据请求url直接获取处理器handle
在上述1中执行
registry.buildHandlerMapping();
进入方法,从registrations中获取自定义配置的registration,然后进行url-handle的储存。
`
 urlMap.put(registration.getUrlPath(), registration.getViewController());
public class ViewControllerRegistry {
... ...
private final List<ViewControllerRegistration> registrations = new ArrayList<>(4);
private int order = 1;
... ... ...
@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);
}
将该urlMap传入SimpleUrlHandlerMapping构造进行SimpleUrlHandlerMapping初始化urlMap
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
private final Map<String, Object> urlMap = new LinkedHashMap<>();
... ... ...
public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) {
setUrlMap(urlMap);
setOrder(order);
}
public void setUrlMap(Map<String, ?> urlMap) {
this.urlMap.putAll(urlMap);
}
3-1. 注册储存url-handle:
在initApplicationContext时,会调用
registerHandlers(this.urlMap);
然后调用抽象父类里的实现方法registerHandler
进行handle储存
initApplicationContext启动的时候就会自动调用,在SimpleUrlHandlerMapping初始化urlMap之后,具体为什么,没有探究
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
private final Map<String, Object> urlMap = new LinkedHashMap<>();
... ... ...
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.trace("No patterns in " + formatMappingName());
}
else {
urlMap.forEach((url, handler) -> {
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
});
if (logger.isDebugEnabled()) {
... ... ... ... ...
抽象父类AbstractUrlHandlerMapping中registerHandler的实现,对handlerMap储存
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
... ... ...
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
... ... ...
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
... ... ...
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
//储存url-handle
this.handlerMap.put(urlPath, resolvedHandler);
... ... ...