Spring从入门到精通—自动配置Spring MVC的原理

1、Spring MVC自动配置

Spring Boot对Spring MVC自动配置的详细可以参考管方文档。Spring Boot为Spring MVC提供的AutoConfiguration适用于大多数应用场景,Spring Boot对Spring MVC做了以下默认的自动配置:

  1. 引入ContentNegotiatingViewResolver 和 BeanNameViewResolver 。
  2. 对静态资源的支持,包括对WebJars 的支持
  3. 自动注册Converter,GenericConverter,Formatter。
  4. 对HttpMessageConverters 的支持。
  5. 自动注册MessageCodeResolver。
  6. 对静态资源的支持
  7. 对自定义Favicon的支持
  8. 自动使用 ConfigurableWebBindingInitializer bean。

Spring Boot默认情况下是自动配置好Spring MVC的,可以直接使用,但是Spring Boot也支持我们修改Spring Boot对SpringMVC的配置。如果保留Spring Boot MVC特性,我们只需添加其他的MVC配置(拦截器,格式化处理器,视图控制器等)。我们可以添加自己的WebMvcConfigurerAdapter 类型的@Configuration类(配置类),而不需要注解@EnableWebMvc。如果希望使用自定义的RequestMappingHandlerMapping,RequestMappingHandlerAdapter,或ExceptionHandlerExceptionResolver,我们可以声明一个WebMvcRegistrationsAdapter实例提供这些组件。

但是如果想全面控制Spring MVC,我们可以添加自己的@Configuration类,并使用@EnableWebMvc注解。这样Spring Boot就不会对MVC进行配置了。然后我们就可以像刚开始使用Spring MVC那样对他进行配置。

2、Spring MVC自动配置原理细节

Spring Boot对Spring MVC的自动配置主要是通过WebMvcAutoConfiguration这个类实现的,接下来我们就结合这个类来简单分析一下自动配置的细节。

2.1 ContentNegotiatingViewResolver 和 BeanNameViewResolver

这两个一听名字就知道是和视图解析器有关,也确实是这样的,他们自动配置了ViewReslover,然后由ViewReslover得到View对象,View对象调用他的render方法渲染页面等等。其中BeanNameViewResolver 就是SpringMVC中的一个视图解析器,他可以通过视图名来获得视图解析器,而ContentNegotiatingViewResolver的作用就是组合所有的视图解析器,下面他们的源码:

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
		@Bean
		@ConditionalOnBean(View.class)
		@ConditionalOnMissingBean   //只会创建一个
		public BeanNameViewResolver beanNameViewResolver() {
			BeanNameViewResolver resolver = new BeanNameViewResolver();
			//给这个视图解析器设置执行顺序order,他的级别是很低的
			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
			return resolver;
		}

		@Bean
		@ConditionalOnBean(ViewResolver.class)
		@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
		public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
			ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
			resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
			// ContentNegotiatingViewResolver uses all the other view resolvers to locate
			// a view so it should have a high precedence
			resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
			return resolver;
		}

}

问题:ContentNegotiatingViewResolver是如何组合所有视图解析器的

public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
		implements ViewResolver, Ordered, InitializingBean {
		
	@Nullable
	private List<ViewResolver> viewResolvers;


    @Override
	protected void initServletContext(ServletContext servletContext) {
		Collection<ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
		if (this.viewResolvers == null) {
			this.viewResolvers = new ArrayList<>(matchingBeans.size());
			//遍历BeanFactoryutils中的视图解析器
			for (ViewResolver viewResolver : matchingBeans) {
				if (this != viewResolver) {
				  //如果没有这个视图解析器,那就把它加入
					this.viewResolvers.add(viewResolver);
				}
			}
		}else {
			for (int i = 0; i < this.viewResolvers.size(); i++) {
				ViewResolver vr = this.viewResolvers.get(i);
				if (matchingBeans.contains(vr)) {
					continue;
				}
				String name = vr.getClass().getName() + i;
				obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
			}

		}
		AnnotationAwareOrderComparator.sort(this.viewResolvers);
		this.cnmFactoryBean.setServletContext(servletContext);
	}
}

因此,我们可以实现自己的视图解析器,然后ContentNegotiatingViewResolver把它注册到容器中。
定制自己的视图解析器,我们可以在启动类中实现ViewResolver接口,编写我们自己的视图解析器,然使用@Bean标签配置给IOC容器。

package com.xust.iot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;

import java.util.Locale;

@SpringBootApplication
public class SpringBootWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebApplication.class, args);
    }


     //直接在容器中添加我们的视图解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    /**
     * 实现ViewResolver
     */
    public static class MyViewResolver implements ViewResolver{

        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            //在这里面写我们自己的视图处理逻辑
            return null;
        }
    }
}
3、Converter,GenericConverter,Formatter

这些功能在Spring Boot中也有默认的自动配置,这里我们要了解的是如何扩展配置Converter和Formatter。源码:

@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
//在文件中配置日期格式化的规则
public Formatter<Date> dateFormatter() {
  return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
}

//添加格式化组件
@Override
public void addFormatters(FormatterRegistry registry) {
	for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
		registry.addConverter(converter);
	}
	for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
		registry.addConverter(converter);
	}
	for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
		registry.addFormatter(formatter);
	}
}

我们也可以定制自己的转换器

package com.xust.iot;

import com.xust.iot.bean.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.converter.Converter;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;

import java.util.Locale;

@SpringBootApplication
public class SpringBootWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebApplication.class, args);
    }

    @Bean
    public Converter<?,?> userToStringConverter(){
        return new UserToStringConverter();
    }

    //把User对象转换成String
    public static class UserToStringConverter implements Converter<User,String>{

        @Override
        public String convert(User source) {
           //写我们自己的转换规则
            return null;
        }
    }
}
4、HttpMessageConverters

Spring MVC 使用HttpMessageConverter 接口转换HTTP 请求和响应,合适的默认配置可以开箱即用,例如对象自动转换为JSON(使用Jackson库)或XML(如果Jackson XML扩展可用,否则使用JAXB),字符串默认使用UTF-8编码。可以使用Spring Boot 的HttpMessageConverters 类添加或自定义转换类:

@Configuration
public class FastJsonHttpMessageConvertersConfig extends WebMvcConfigurerAdapter {
 
	@Bean
	public FastJsonConfig fastJsonConfig() {
		FastJsonConfig fastJsonConfig = new FastJsonConfig();
		SerializerFeature writeMapNullValue = SerializerFeature.WriteMapNullValue;
		SerializerFeature WriteNullStringAsEmpty = SerializerFeature.WriteNullStringAsEmpty;
		SerializerFeature WriteNullNumberAsZero = SerializerFeature.WriteNullNumberAsZero;
		SerializerFeature WriteNullListAsEmpty = SerializerFeature.WriteNullListAsEmpty;
		fastJsonConfig.setSerializerFeatures(writeMapNullValue, WriteNullStringAsEmpty, 
				WriteNullNumberAsZero, WriteNullListAsEmpty);
		return fastJsonConfig;
	}
 
	@Bean
	public HttpMessageConverters fastJsonHttpMessageConverters(
			@Qualifier("fastJsonConfig") FastJsonConfig fastJsonConfig) {
		FastJsonHttpMessageConverter4 fastConverter = new FastJsonHttpMessageConverter4();
		fastConverter.setFastJsonConfig(fastJsonConfig);
		HttpMessageConverter<?> converter = fastConverter;
		return new HttpMessageConverters(converter);
	}
}

5、扩展Spring Boot对Spring MVC的配置

想要扩展Spring Boot的MVC功能,我们要WebMvcConfigurer接口,但是这样太麻烦了,因此Spring Boot提供了一个适配器类WebMvcConfigurerAdapter,它里面全部是一些空方法,我们可以继承WebMvcConfigurerAdapter类,然后我们只需要按照我们的需要重写里面的方法就好了。

package com.xust.iot.configurer;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

@Configuration
public class ApplicationMVCConfig extends WebMvcConfigurerAdapter {

     //在这里可以配置拦截器,文件上传解析器,异常解析器....只要是在原本spring-mvc.xml文件中可以配置的在这里都可以配置

    //视图映射
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //相当于<mvc:view-controller path="/hello" view="success.html"/>
        registry.addViewController("/hello").setViewName("success");
    }

    /**
     * 拦截器 拦截hello请求
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/hello");
    }

    //异常解析处理器
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        exceptionResolvers.add(new MyExceptionHandler()) ;
    }
}

6、全面接管Spring Boot对Spring MVC的自动配置

官网中的一句话:If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.意思就是我们可以配置类上加上EnableWebMvc来全面接管Spring MVC,这样一来SpringBoot就不会对Spring MVC进行配置了,一切都需要我们来配置。

package com.xust.iot.configurer;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


@EnableWebMvc
@Configuration
public class ApplicationMVCConfig implements WebMvcConfigurer {

     //在这里可以配置拦截器,文件上传解析器,异常解析器....只要是在原本spring-mvc.xml文件中可以配置的在这里都可以配置


   //配置视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        InternalResourceViewResolver resourceViewResolver=new InternalResourceViewResolver();
        resourceViewResolver.setPrefix("/**");
        resourceViewResolver.setSuffix("/.html");
        registry.viewResolver(resourceViewResolver);
        registry.order(10);
    }
    
   
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hello").setViewName("success.html");
    }

    /**
     * 拦截器 拦截hello请求
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/hello");
    }
    
}

留言区

还能输入500个字符