带你深入学习spring mvc

Posted 左沩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你深入学习spring mvc相关的知识,希望对你有一定的参考价值。

     现在开发都在用微服务框架,spring boot ,那么由于spring boot 自动配置的原因,我们在开发时通常只需要了解这么几个@responsebody ,@requestMapping ,@requestParam 等常用的几个注解就可以了,但是当我们做架构设计的时候,如果仅仅是了解这么几个注解的话,是无法做好架构设计的,那么我们需要深入的了解spring mvc的架构,以及如何用spring boot 自定义我们的spring mvc 配置。

   首先我们通过一张图来了解spring mvc 的执行流程

 

 

通过 上面我们 可以明确的知道,springmvc 由前端控制器调用处理器映射器,找到handler,(也就是我们写的http接口),处理器适配器执行handler,返回结果通过视图解析器解析后,返回给前端控制器,前端控制器渲染到前端。

总结一句话就是 一个中心,三个组件,那么一个中心,和三个组件spring boot  都会自动配置好,但是当我们需要一些自定义的时候,我们如何通过spring boot 实现自定义配置呢?那么我们需要掌握以下技术点

@EnableWebMvc  注解

@EnableWebMvc 

1:@EnableWebMvc 默认  类似实现了<mvc:annotation-driven/>这个配置。

2. @EnableWebMvc添加给@Configuration类来导入SpringMvc的配置;3.自定义MVC配置,实现接口WebMvcConfigurer或更可能继承WebMvcConfigurerAdapter,并且使用@EnableWebMvc;

4.如果还想要自定义配置,移除@EnableWebMvc,并且继承WebMvcConfigurationSupport或DelegatingWebMvcConfiguration。

参考文档:https://www.cnblogs.com/lvbinbin2yujie/p/10624584.html

上面讲到了spring boot 的默认配置和如何实现自定义配置

首先我们来学习下 WebMvcConfigurationSupport 这个类,然后了解下WebMvcConfigurationSupport 他能实现哪些自定义配置

我们打开WebMvcConfigurationSupport 源码,很容易就可以发现可以自定义哪些东西

同时还有常用的配置静态资源放行的方法,下面展示作者是如何在项目中自定义配置的

 /**
     * 自定义媒体转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) 
        super.extendMessageConverters(converters);
        for (HttpMessageConverter converter :
                converters) 
            if (converter instanceof MappingJackson2HttpMessageConverter) 
                ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(new Jackson2ObjectMapperBuilder().simpleDateFormat("yyyy-MM-dd HH:mm:ss").serializationInclusion(JsonInclude.Include.NON_NULL).build());
            
        
    
    /**
     * 放行的静态资源
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) 
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    
    /**
     * 定义一个返回结果处理器
     * @return
     */
    @Bean
    public MethodReturnValueHandler mgMethodReturnValueHandler() 
        MethodReturnValueHandler formatJsonReturnValueHandler = new MethodReturnValueHandler(getMessageConverters());
        return formatJsonReturnValueHandler;
    

从上面的代码中,不知道大家发现没有作者自定义了一个消息返回处理器的bean,但是好像我并没有继承WebMvcConfigurationSupport  中添加返回结果处理器的方法,源码中的方法如下

那么作者在这里要做的其实并不是添加一个返回结果处理器,而是要重构spring mvc 默认的RequestResponseBodyMethodProcessor 这个处理器

那么如何替换掉默认的返回结果处理器呢,那么这个我们就要知道RequestResponseBodyMethodProcessor 属于处理器适配器里面的东西,源码如下

哈哈!我们发现我们自定义的很多配置都是处理器适配器的中心,处理器适配器也是作者一开始提到的三大组件的一个组件。

同时我们也发现 处理器适配器继承了InitializingBean 这个接口,这个接口我们从名字就可以看出来应该使用在初始化bean对象时用的,利用这个功能我们便可以在处理器适配器对象创建的时候,对默认的RequestResponseBodyMethodProcessor 返回结果处理器进行替换,下面贴上作者的源码,看我是如何操作的

/**
 * 对结果返回处理器进行加强
 */
@Configuration
public class InitializingAdvice implements InitializingBean 
    /**
     * 由于处理器适配器继承了 InitializingBean 那么在bean 创建对象的时候执行该方法,对responsebody 进行加强
     */
    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @Autowired
    private  MethodReturnValueHandler methodReturnValueHandler;
    /**
     * 使用装饰模式加强@responseBody ,对responseBody返回的结果进行加强。
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception 
        List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers);
        this.decorateHandlers(handlers);
        requestMappingHandlerAdapter.setReturnValueHandlers(handlers);
    

    private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) 
        for (HandlerMethodReturnValueHandler handler : handlers) 
            if (handler instanceof RequestResponseBodyMethodProcessor) 
                int index = handlers.indexOf(handler);
                handlers.set(index, methodReturnValueHandler);
                break;
            
        
    

下面也贴上作者是如何是自定义返回结果处理器的代码

/**
 * 对controller返回的数据统一封装为ResponseInfo,注意: 1、controller异常由spring mvc异常机制处理,会跳过该处理器
 * 2、该处理器仅处理包含@RestController、@ResponseBody注解的控制器
 */
public class MethodReturnValueHandler extends RequestResponseBodyMethodProcessor 

	public MethodReturnValueHandler(List<HttpMessageConverter<?>> converters) 
		super(converters);
	
	@Override
	public boolean supportsReturnType(MethodParameter returnType) 

		Class<?> controllerClass = returnType.getContainingClass();
		returnType.getMethodAnnotation(ResponseBody.class);
		return controllerClass.isAnnotationPresent(RestController.class)
				|| controllerClass.isAnnotationPresent(ResponseBody.class)
				|| returnType.getMethodAnnotation(ResponseBody.class) != null;
	
	@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException 
		if (((ServletWebRequest) webRequest).getResponse().isCommitted()) 
			super.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
			return;
		
		AjaxJson responseInfo = null;
		if (returnValue instanceof AjaxJson) 
			responseInfo = (AjaxJson) returnValue;
		 else 
			if (webRequest instanceof ServletWebRequest) 
				ServletWebRequest request = (ServletWebRequest) webRequest;
				String path = request.getRequest().getServletPath();
				if (request.getRequest().getServletPath().startsWith("/actuator")) 
					super.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
					return;
				
			
			responseInfo = new AjaxJson();
			responseInfo.setObj(returnValue);
		
		super.handleReturnValue(responseInfo, returnType, mavContainer, webRequest);
	

说到这里作者已经介绍完了如何通过继承WebMvcConfigurationSupport 实现自定义的配置,以及如何替换掉处理器适配器默认的配置。相信大家对springmvc 又有了更深的理解,这个也是作者比较得意的一个功能,因为在开始时,如果我们总是需要调用对返回结果进行封装的时候,这样的代码既累赘,有冗余,向作者的项目中是没有这种代码的

但是我们在前台最终看到的结果确是封装了一层

以上是关于带你深入学习spring mvc的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出Spring原理及实战「原理分析专题」不看源码就带你剖析MVC容器核心流程以及运作原理

Spring万字带你深入学习面向切面编程AOP

深入浅出Spring原理及实战「原理分析专题」不看源码就带你剖析IOC容器核心流程以及运作原理

深入学习SpringMVC

从原理带你掌握Spring MVC拦截处理器知识

3W字带你玩转SpringCloud