springMVC前端控制器Dispatcher
一、springMVC执行流程
- 首先在接收到请求时,首先获取HandlerMapping
- 然后根据HandlerMapping获取对应的HandlerExecutionChain,这个执行链内置了Handler(可以理解为Controller)
- HandlerExecutionChain除了内置Handler外,还有拦截器HandlerInterceptor,首先有一个默认的拦截器,就是ConversionServiceExposingInterceptor(用于将请求里的参数转换为controller方法里的参数,这个转换拦截器没有提供日期转换的服务,所以使用springMVC时,要自己提供一个转换日期的)…还有其他拦截未完…
- 接着会根据Handler获取HandlerAdapter
- 然后就是执行HandlerExecutionChain里的拦截器,先执行HandlerInterceptorAdapter接口的preHandle方法,如果preHandle方法返回false的话,就直接执行afterCompletion方法
- preHandle方法如果返回true,那么接着就是由HandlerAdapter执行Handler,Handler执行时较复杂,放到后面讲解…
- 如果执行Handler时,没有出现异常,那么会执行postHanlder方法,反之不执行
- 不论Handler是否出现异常,Dispatcher都会处理结果,在处理时,会检查是否由全局异常处理器HandlerExceptionResolver,如果我们需要使用全局异常处理器,只要实现该接口并注入IOC容器即可
- 如果前面未执行afterCompletion方法,那么afterCompletion就会执行,afterCompletion不论什么情况,都一定会执行
二、简单流程
获取HandlerMapping->获取Handler(实际是这个HandlerExecutionChain)->
由HandlerApdter执行Handler,封装成ModelAndView->最后在处理处理ModelAndView
三、执行Handler时 - 在执行Handler时,首先最主要的就是获取参数了,因为反射时需要传入参数
- 所以第一步就是解析参数,解析Handler方法的参数
- 解析参数时,会根据参数的类型获取对应的参数解析器HandlerMethodArgumentResolver的实现类
- 我们一般使用到的参数解析器有
1.RequestParamMethodArgumentResolver支持基本类型以及对应的包装类型,还有常用的Date、Enum、URI、URL、Class...,还支持使用了@RequestParam注解的其他类型,还支持带有@RequestParam且有注解值的Map
此时获取参数是通过从Request.getParameter,所以这种情况不支持前端发送json字符串,只支持表单数据
2.MapMethodProcessor支持不使用注解的Map类型参数,没啥用,返回的是默认的BindingAwareModelMap容器
3.RequestParamMapMethodArgumentResolver支持使用注解@RequestParam("map")并且注解有值的Map,这时获取HttpServletRequest.getParameterMap容器,将值一一复制到Map里面
4.RequestResponseBodyMethodProcessor支持使用注解@ResquestBody的参数,并且一个方法里只能有一个@ResquestBody注解(留个心眼...)
此时通过jackson读取HttpServletRequest里的请求流(InputStream),并且将里面的请求体转换成json,还有流被读取完就关闭了,所以不能使用俩次@ResquestBody
所以这里要注意,请求体只有post请求才有,还有jackson转换成json,那么请求体里的数据必须是json字符串
所以使用@ResquestBody注解,必须在post请求下使用,并且必须提交json字符串,还有一个方法里只能有一个@ResquestBody注解,该注解不能用于单个List集合
5.ServletModelAttributeMethodProcessor支持我们自定义的类(一般用于pojo类),并且还有支持使用@ModelAttribute的类,所以我们使用自定义类时,也可以加上该注解,注解是否有值不影响
首先会尝试获取参数的构造器,一般都是获取空参构造器即可,然后进行实例化,接着会去Request里获取参数,然后按照方法参数的属性去寻找Request的参数,找到时对调用属性的set方法进行注入
当使用ArrayList作为参数时,并且没有注解,就会用该解析器,虽然实例化可以成功,但是无法添加数据,因为是采用set方法的
6.其他解析器就比较少见,可以去看HandlerMethodArgumentResolver对应的实现类,然后查看supportsParameter方法
-
解析器总结:
-
使用@ReuqestParam注解,底层通过参数名来去参数值,HttpServletRequest.getParameterValues()
-
使用@RequestBody注解,底层通过将请求体转换成json对象,将内容出入到参数里,使用完该HttpRequest的输入流就被关闭了,此时就不能在获取请求体了
-
不使用注解,针对Pojo来说,底层通过获取HttpServletRequest的所有参数,封装成类似key,value的结构,然后寻找pojo是否有对应的key值,也就是对应的属性值,有的话就调用set方法.
-
详细介绍:
-
对于一般类型
-
String、Integer、int…可以不使用注解@ReuqestParam,也可以使用,并且注解有值无值都可以。
-
对于List集合
-
必须使用@ReuqestParam注解!注解有值无值都可以,不使用@ReuqestParam注解会被当成pojo类来处理(需要调用调用属性的set方法),很明显List不适合;并且使用@RequestBody注解也不行,跟pojo处理类似,也不适合
-
对于Map集合
-
使用注解@RequestParam,注解有值时,可以等价于一般类型来使用,也就是通过注解值,获取实际参数。此时的参数是String类型或者String数组,底层没有提高将String类型的参数转为Map的转换器(org.springframework.core.convert.converter.Converter),所以要想这么使用,必须提供一个转换器(转换器配置在最下面)。
-
使用注解@RequestParam,注解无值时,此时情况就比较简单了,通过获取HttpServletRequest.getParameterMap获取参数集合,然后将参数集合的值一一复制到Map里。
-
使用@RequestBody,底层采取jackson将json对象值全部注入Map里
-
不适用注解,会注入一个BindingAwareModelMap容器
-
对于POJO类型
-
不使用注解时(或者@ModelAttribute注解,有无值都可以),先进行实例化(一般采取无参构造),底层通过获取HttpServletRequest的所有参数,封装成类似key、value的结构,然后再从pojo获取属性与key比对,存在的话就调用对应的set方法注入
-
使用@RequestBody注解,与Map使用一样的处理
-
不可以使用@RequestParam
四、Converter转换器配置
import org.springframework.core.convert.converter.Converter;
import java.util.HashMap;
import java.util.Map;
//将String类型转换为Map,source就是客户端上传的值
public class ArrayToMapConverter implements Converter<String[], Map> {
@Override
public Map convert(String[] source) {
Map map=new HashMap();
map.put("array",source);
return map;
}
}
<!--接着在xml这样配置,这个容器储存着所有的Converter转换器-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 转换器 -->
<property name="converters">
<list>
<!-- 有多个转换器,可以这里加入-->
<!--<bean class="com.xxf.demo.mvc.CustomDateConverter"/>-->
<bean class="com.xxf.demo.mvc.ArrayToMapConverter"/>
</list>
</property>
</bean>
五、拦截器配置
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("我进行拦截了");
return true;
}
}
<mvc:interceptors>
<mvc:interceptor>
<!--指定拦截路径-->
<mvc:mapping path="/user/**" />
<!--依赖对应的bean-->
<ref bean="loginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>