SpringBoot------MVC装配原理
官方文档:https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications
SpringMVC自动配置:
1.包含一些视图解析器,(ContentNegotiatingViewResolver、BeanNameViewResolver)
2.支持静态资源,包括webJars。
3.自动注册类型转换器以及格式化器(FormatterRegistry)。(前端提供对象,后台自动封装接收)【默认使用dd/MM/yyyy格式】,可以通过在yaml文件中自定义日期格式化规则。
4.支持HttpMessage消息转换。转换http的请求和响应。
5.自动注册定义一些错误消息的控制 。
6.首页映射
7.图标自定义
8.支持一些初始化配置的数据绑定。
SpringBoot在自动配置很多组件的时候,先看容器中是否有用户自己的配置(如果用户自己配置@Bean),如果有用户配置的,如果没有就用自动配置的;如果组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来。
如果想自定义的添加配置这些内容,只需要在配置类(配置类需要实现WebMvcConfigurer)上加入@Configuration,并且不能写@EnableWebMvc注解(这个注解代表mvc全面接管)。
ContentNegotiatingViewResolver
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
ViewResolver视图解析器接口。
public class ContentNegotiatingViewResolver extends
WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
实现了视图解析器接口(ViewResolver )的类,可以称作视图解析器。
看一下ViewResolver的源码:
public interface ViewResolver {
@Nullable
View resolveViewName(String var1, Locale var2) throws Exception;
//用于解析视图
}
这个ViewResolver接口中只有一个方法,看ContentNegotiatingViewResolver 是怎么重写该方法的。
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
//获取候选的视图
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
//选择最好的视图
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
} else {
this.logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
this.getCandidateViews:获取候选的视图。
通过不断的遍历,获取候选视图,并将候选视图加入到List集合中,获取所有的候选视图。本质上是从容器中获取这些视图,也就是从Bean中获得,Spring所有东西都是在IOC容器中。
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
List<View> candidateViews = new ArrayList();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
Iterator var5 = this.viewResolvers.iterator();
while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
Iterator var8 = requestedMediaTypes.iterator();
while(var8.hasNext()) {
MediaType requestedMediaType = (MediaType)var8.next();
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
Iterator var11 = extensions.iterator();
while(var11.hasNext()) {
String extension = (String)var11.next();
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
自己实现一个视图解析器。
//全面扩展SpringMVC. 所有请求经过DispatcherServlet
//将这个类变成配置类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//通过@Bean将视图解析器放到容器中
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义了一个视图解析器
public static class MyViewResolver implements ViewResolver{
//只需要重写这个方法
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
DispatcherServlet .doDispatch
public class DispatcherServlet extends FrameworkServlet {
.....
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
在doDispatch方法上打上断点,DeBug启动项目,访问页面时能看到会执行到doDispatch方法。
其中自定义的视图解析器正在其中。自己写一个视图解析器,并且把它注册到Bean里面,就会自动装配。
如果想定制化的功能,只要写这个组件,然后将它交给SpringBoot,SpringBoot就会自动装配。
视图跳转
//全面扩展SpringMVC.建议使用@Configuration这种操作
//将这个类变成配置类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
//想要通过地址zhaoyun访问到success页面,那么这个页面必须加入<html lang="en" xmlns:th="http://www.thymeleaf.org"> 并且在templates目录下
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/zhaoyun").setViewName("success");
}
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义了一个视图解析器
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
访问:http://localhost:8080/zhaoyun
官方文档上同样注明使用@Configuration,但是不能写@EnableWebMvc。
但是为什么不能加入@EnableWebMvc注解
@EnableWebMvc源码:可见EnableWebMvc 没有方法,只有一些注解配置。
导入了一个类:@Import({DelegatingWebMvcConfiguration.class})
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
@EnableWebMvc 这个注解就是导入DelegatingWebMvcConfiguration这个类。
再看WebMvcAutoConfiguration的WebMvcAutoConfigurationAdapter类,也使用到@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})导入的EnableWebMvcConfiguration这个类。
@Configuration(
proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
final WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
而EnableWebMvcConfiguration继承了 DelegatingWebMvcConfiguration,
@Configuration(
proxyBeanMethods = false
)
@EnableConfigurationProperties({WebProperties.class})
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final Resources resourceProperties;
private final WebMvcProperties mvcProperties;
private final WebProperties webProperties;
private final ListableBeanFactory beanFactory;
private final WebMvcRegistrations mvcRegistrations;
private final WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private ResourceLoader resourceLoader;
public EnableWebMvcConfiguration(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, WebProperties webProperties, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ListableBeanFactory beanFactory) {
this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
this.mvcProperties = mvcProperties;
this.webProperties = webProperties;
DelegatingWebMvcConfiguration 类的setConfigurers方法,
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
@Autowired(
required = false
)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
//获取容器中的所有webMvcConfigurer
this.configurers.addWebMvcConfigurers(configurers);
}
}
WebMvcAutoConfiguration有@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}),如果WebMvcConfigurationSupport这个Bean不存在时,这个类才会生效。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = "";
private static final String SERVLET_LOCATION = "/";
public WebMvcAutoConfiguration() {
}
因此一旦加入@EnableWebMvc注解,DelegatingWebMvcConfiguration类就会生效,而DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport,一旦WebMvcConfigurationSupport存在WebMvcAutoConfiguration 就不会生效,则整个WebMvc自动装配就不起作用。
在SpringBoot中,有非常多的xxxConfiguration帮助我们扩展配置。它会对Spring原有的东西进行改变或者扩展。