文章目录
一、 请求映射
1. rest使用与原理
如今大多主流网站都运用的Restful编程风格,不同于传统的,根据请求参数名区分,rest风格是通过请求方法作为区分; 简单来说,就是用HTTP请求方式动词来表示对资源的操作。
- 以前操作
/getUer:获取用户 /deleteUser:删除用户 /updateUser/更新用户
- resut风格
/user GET-获取用户 POST-新增用户 DELETE-删除用户 PUT-更新用户
- 核心:HiddenHttpMethodFilter
- 表单:method=post 隐藏域_method=put
- Springboot中手动开启
1.resut的使用
@RestController
public class RestFulControllerTest {
@RequestMapping(path = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
}
- 开启Filter支持
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启表单rest处理
<form method="post" action="user">
<input name="_method" type="hidden" value="PUT">
<input type="submit" value="PUT-张三" >
</form>
这样表单就可以提交put请求
2. rest原理
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
/**
核心代码
**/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
//判断该请求是否是post和请求是否异常
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
//返回包装类对象
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
-
当页面表单向后端发送请求,会携带_method参数;
- 该请求会被HiddenHttpMehtodFilter拦截下来:
- 先判断该请求是否正常和是否是POST请求
- 获取_method表单提交的参数
- 判断再ALLOWED_METHODS中是否有包含_method的value值
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器放行的时候调用requesWrappe,所以一直都是调用wrapper重写的方法。
-
兼容以下请求;PUT.DELETE.PATCH
-
要想更改隐藏表单的name,我们可以重写
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
在使用客户端工具时候,无需要Filter(PostMan)
2. 请求映射原理
- 当客户端浏览器发送第一次(GET)请求,回去调用doget方法,doget()又去调用procesRequest
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
- procesRequest()完成了一些初始化操作后,继续调用doService(),而这是一个抽象方法,默认由子类实现。
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
- 子类(DispatchServlet)实现了doservice(),完成了初始化操作后,又调用doDispatch()方法
/**
doService()中
**/
try {
doDispatch(request, response);
}
- 核心方法:每个请求都会去调用
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 {
//检测request是否正常
processedRequest = checkMultipart(request);
//是否是文件上传请求
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
}
-
所有的处理器映射器
-
RequestMappingHandlerMapping
- 保存了所有requestMapping和handler的映射规则
- 保存了所有requestMapping和handler的映射规则
-
所有的请求关系映射都封装在HandlerMapping当中
-
SpringBoot自动配置WelcomPageHandlerMapping,是我们可以访问Index.html;
-
SpringBoot自动配置RequestMappingHandlerMapping
-
当请求进来的时候,会挨个寻找对应的HandlerMapping;
- 如果找到,就返回。
- 没有则继续遍历下一个。
@Nullable
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;
}
到后期我们需要全面接管SpringMvc,需要我们自定义HandlerMapping
二、普通参数与基本注解
常用注解
@PathVariable: 路径变量
@RequestHeader: 请求头
@requestAttribute: 请求域
@RequestParam: 请求参数
@CookieValue: cookie值
@MatrixVariable: 矩阵变量
@RequestBody: 请求体内容
- @PathVariable
- 既可以根据预留的{name}获取
- 也可以直接使用Map接收
//请求URL car/1/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map getPath(@PathVariable("id") Integer id,
@PathVariable("username")String username,
@PathVariable Map<String,String> pv){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("username",username);
map.put("pv",pv);
return map;
}
- @RequestHander
- 获取头信息
- 一样可以使用Map封装所有
@GetMapping("/car/{id}/owner/{username}")
public Map getPath(@RequestHeader("user-agent")String userAgent,
@RequestHeader Map<String,String> headerMap){
Map<String,Object> map = new HashMap<>();
map.put("userAgent",userAgent);
map.put("headerMap",headerMap);
return map;
}
- @RequestAttribute
- 获取Request域当中的参数
@GetMapping("/res")
public String getRes(HttpServletRequest res){
res.setAttribute("code",200);
return "forward:getRes"; //转发
}
@GetMapping("/getRes")
public String getResw(@RequestAttribute("code") String code){
return code;
}
- @RequestParam
- 获取请求参数
// car/1/owner/zhangsan?age=20&username=88
@GetMapping("/car/{id}/owner/{username}")
public Map getPath(@RequestParam("age") String age,
@RequestParam Map<String,String> paramMap){
Map<String,Object> map = new HashMap<>();
map.put("paramAge",age);
map.put("paramMap",paramMap);
return map;
}
- @CookieValue
- 获取Cookie的值
- 获取Cookie的信息.
@GetMapping("/car/{id}/owner/{username}")
public Map getPath(@CookieValue("Idea-191d5581") String cookieValue,
@CookieValue("Idea-191d5581")Cookie cookie){
Map<String,Object> map = new HashMap<>();
map.put("cookieValue",cookieValue);
log.debug(cookie.toString());
return map;
}
- @RequestBody
- 接收请求体所有内容
@PostMapping("/code")
public Map<String,Object> postUser(@RequestBody String content){
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("content",content);
return map;
}
- @MatrixVariable
- SpringBoot默认关闭了对矩阵变量的支持;
- 手动开启;
- 所有路径解析都交给了UrlPathHelper进行处理
- 注入一个新的URLPathHelper
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false); //不删除;
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
- 使用矩阵变量URL
car/sell;low=80;brand=byd,bmw
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@Matrix Variable("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;
}
ServletAPI参数解析
- 原生ServletAPI
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) && !parameter.hasParameterAnnotations()) ||
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
- MapModel
- 无论Map或者Model类型的参数,在解析的时候,会返回msbContainer.getModel();
private final ModelMap defaultModel = new BindingAwareModelMap();
,时Model也是Map
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
//返回BindingAwareModelMap
return mavContainer.getModel();
}
- 这里存放了ModelAndViewContatiner,视图与数据,再处理结果的时候会将他们解析和渲染到页面
目标方法执行完成后
- 数据与返回值View都会存到这里面
处理派发结果
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//暴露模型作为请求域属性
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);
}
}
请求参数的解析原理
-
SpringBoot先从HandlerMapping中找到可以处理请求Handler(Controller);
-
再根据Handler找到与指对应的HandlerAdapter(处理器适配器)
-
适配器用于执行对应的方法,并且酶切确定方法参数中对应的值。
//找到能处理当前请求的对应handler
mappedHandler = getHandler(processedRequest);
//找到对应的处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
1. HandlerAdapter
- RequestMappingHandlerAdapter:初始方法上标注requestMapping
- HandlerFunctionAdapter: 支持函数编程
2. 执行目标方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3. 方法参数解析器
- 用于确定目标方法参数的值;
- SpringMvc能写多少种参数类型,取决于参数解析器
- 如果supportsParamter为True;就调用resolveArgument解析参数;
4. 获取参数当中的值
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
4.1 挨个判断那个参数解析器能解析当前值
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
4.2 再调用方法进行解析
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
//解析参数
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}