请求映射
Rest使用与原理
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
以前:/getUser获取用户 /deleteUser删除用户 /editUser修改用户 /saveUser保存用户
现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
核心Filter:HiddenHttpMethodFilter
用法:
1、表单method=post,隐藏域 _method=put
2、SpringBoot中手动开启
spring.mvc.hiddenmethod.filter = true
自定义 _method 名字 :自己定义hiddenHttpMethodFilter
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
//原理:SpringBoot源码
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
Rest原理(表单提交要使用REST的时候)
1、表单提交会带上_method=PUT
2、请求过来被HiddenHttpMethodFilter拦截。
-
请求是否正常,并且是POST
-
获取到
_method
的值。兼容请求:PUT、DELETE、PATCH-
原生request(是post方式),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
-
过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
-
Rest使用客户端工具
- 如PostMan直接发送Put、delete等方式请求,无需Filter。
//HiddenHttpMethodFilter源码
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
//_method
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
//转大写
if (ALLOWED_METHODS.contains(method)) {
//PUT、DELETE、PATCH
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
//ALLOWED_METHODS的值为PUT、DELETE、PATCH
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
请求映射原理
DispatcherServlet继承树
在FrameworkServlet中重写了doGet()、doPost()方法。
//doGet()/doPost()→processRequest()→doService()
//子类中重写了doService()
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;
//DispatcherServlet
//doService()中this.doDispatch(request, response);
SpringMVC功能核心都在org.springframework.web.servlet.DispatcherServlet中doDispatch()中。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
//HandlerMapping:处理器映射
RequestMappingHandlerMapping:保存了所有@RequestMapping和handler的映射规则。
所有的请求映射都在HandlerMapping中。
-
SpringBoot自动配置欢迎页的WelcomePageHandlerMapping。访问能访问到index.html。
-
SpringBoot自动配置了默认的RequestMappingHandlerMapping。
-
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。如果有就找到这个请求对应的handler,如果没有就是下一个 HandlerMapping。
-
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
普通参数与基本注解
注解
@PathVariable(路径变量)
@RequestHeader(获取请求头)
@RequestParam(获取请求参数)
@CookieValue(获取cookie值)
@RequestAttribute(获取request域属性)
@RequestBody(获取请求体)
@Matrixvariable(矩阵变量)
页面开发,cookie禁用了,session里面的内容怎么使用;
没禁用前:session.set(a,b) → jsessionid → cookie → 每次发请求携带。
禁用后:url重写: /abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式传递
/cars/se11;1ow=34;brand=byd;brand=audi;brand=yd ;后的参数都是矩阵变量
@RestController
public class ParameterTestController {
// car/2/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie){
Map<String,Object> map = new HashMap<>();
map.put("age",age);
map.put("inters",inters);
System.out.println(cookie.getName()+"===>"+cookie.getValue());
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默认是禁用了矩阵变量的功能
// 手动开启:原理:对于路径的处理,UrlPathHelper进行解析。
// removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
// /boss/1;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
}
//配置类设置矩阵变量生效
@Bean
public webMvcconfigurer webMvcConfigurer(){
return new webMvcConfigurer() {
@override
public void configurePathMatch(PathMatchconfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemovesemicoloncontent(false);
configurer.setUrlPathHelper(urlPathHelper) ;
}
};
}
Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId。
ServletRequestMethodArgumentResolver 以上的部分参数:
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder。
Map<String,Object> map, Model model, HttpServletRequest request
都是可以给request域中放数据
request.getAttribute();
Map、Model类型的参数,会返回 mavContainer.getModel();
→ BindingAwareModelMap 是Model 也是Map
mavContainer.getModel(); 获取到值
自定义对象参数:可以自动类型转换与格式化,可以级联封装。
参数处理原理
-
HandlerMapping中找到能处理请求的Handler(Controller.method())
-
为当前Handler找一个适配器HandlerAdapter(RequestMappingHandlerAdapter)。
-
适配器执行目标方法并确定方法参数的每一个值。
1、HandlerAdapter
2、执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch()
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//执行目标方法
mav = invokeHandlerMethod(request, response, handlerMethod);
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3、设置参数解析器
HandlerMethodArgumentResolver确定将要执行的目标方法的每一个参数的值是什么,SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
当前解析器是否支持解析这种参数,支持就调用 resolveArgument()。
4、返回值处理器
5、确定目标方法每一个参数的值
(1)InvocableHandlerMethod:getMethodArgumentValues(),挨个判断所有参数解析器那个支持解析这个参数。
(2)解析这个参数的值:调用各自 HandlerMethodArgumentResolver 的 resolveArgument() 方法即可。
(3)自定义类型参数,封装POJO。ServletModelAttributeMethodProcessor这个参数处理器支持。
判断参数是否为简单类型:isSimpleValueType()。
POJO封装过程:在ServletModelAttributeMethodProcessor类中体现。
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型
(request带来参数的字符串)转换到指定的类型(JavaBean→Integer)
未来我们可以给WebDataBinder里面放自己的Converter。
private static final class StringToNumber<T extends Number> implements Converter<String, T>
自定义Converter
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 阿猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
6、目标方法执行完成
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。
7、处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
//InternalResourceView
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
//暴露模型作为请求域属性
exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
//model中的所有数据遍历挨个放在请求域中
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
评论区