SpringBoot(四) :最佳实践

x33g5p2x  于2021-10-25 转载在 Spring  
字(14.1k)|赞(0)|评价(0)|浏览(181)

1、请求处理-【源码分析】-Rest映射及源码解析

1.1、请求映射

  • @xxxMapping;
  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

以前:不同功能使用不同路径

  • /getUser 获取用户
  • /deleteUser 删除用户
  • /editUser 修改用户
  • /saveUser保存用户

Rest风格支持(使用HTTP请求方式动词来表示对资源的操作):保持访问路径不变

现在: /user 

  • GET-获取用户
  • DELETE-删除用户
  • PUT-修改用户
  • POST-保存用户
  • 核心Filter;HiddenHttpMethodFilter

1.2、用法

  • 开启页面表单的Rest功能,因为默认时关闭的
  • 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
  • 编写请求映射
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能
<form action="/user" method="get">
    <input value="REST-GET提交" type="submit" />
</form>

<form action="/user" method="post">
    <input value="REST-POST提交" type="submit" />
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT" />
    <input value="REST-PUT提交"type="submit" />
<form>

1.3、Rest原理(表单提交要使用REST的时候)

 

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);
        if (StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            if (ALLOWED_METHODS.contains(method)) {
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }

    filterChain.doFilter((ServletRequest)requestToUse, response);
}
PUT,
PATCH,
DELETE,
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;
        }
    }
}
  • 1、表单提交会带上.  _method=PUT   _method=DELETE

  • 2、请求过来被HiddenHttpMethodFilter拦截

  • 请求是否正常,并且是POST

  • 获取到_method的值

  • 兼容以下请求;PUT、DELETE、PATCH

  • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。

  • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

注意:

@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)。  看出rest风格默认时关闭的,使用时需要手动开启
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   /#开启页面表单的Rest功能

public class WebMvcAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}
}

public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {

}

public class HiddenHttpMethodFilter extends OncePerRequestFilter {

	private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

	public static final String DEFAULT_METHOD_PARAM = "_method";

	private String methodParam = DEFAULT_METHOD_PARAM;

	public void setMethodParam(String methodParam) {
		Assert.hasText(methodParam, "'methodParam' must not be empty");
		this.methodParam = methodParam;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		HttpServletRequest requestToUse = request;

		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);
	}

	private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

		private final String method;

		public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
			super(request);
			this.method = method;
		}

		@Override
		public String getMethod() {
			return this.method;
		}
	}

}

1.4、Rest使用客户端工具。

  • 如PostMan可直接发送put、delete等方式请求。

  • 表单只能写get、post方式,所以需要走过滤器:HiddenHttpMethodFilter。

  • @RequestMapping(value = "/user",method = RequestMethod.GET) 等价与           @GetMapping("/user") 

2、请求处理-【源码分析】-怎么改变默认的_method

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

    ...
    
    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    
    ...
}

@ConditionalOnMissingBean(HiddenHttpMethodFilter.class):意味着在没有HiddenHttpMethodFilter时,才执行hiddenHttpMethodFilter()。因此,我们可以自定义filter,改变默认的_method。例如:

@Configuration(proxyBeanMethods = false)
public class WebConfig{
    //自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }    
}

\_method改成_m

<form action="/user" method="post">
    <input name="_m" type="hidden" value="DELETE"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>

3、请求处理-【源码分析】-请求映射原理

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:处理器映射。/xxx->>xxxx
    ...
}

getHandler()方法如下:

@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;
}

**this.handlerMappings**在Debug模式下展现的内容:有5个handlerMapping

其中,RequestMappingHandlerMapping保存了所有@RequestMapping 和handler的映射规则

所有的映射:

所有的请求映射都在HandlerMapping中:

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

  • 如果有就找到这个请求对应的handler

  • 如果没有就是下一个 HandlerMapping

  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping,可以自定义 HandlerMapping

4、请求处理-常用参数注解使用

注解:

http:8080:localhost/car/{id}/owner/{username}?age=18&inters=basketball&inters=game

  • @PathVariable 路径变量 : id、username
  • @RequestHeader 获取请求头
  • @RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2): age、inters
  • @CookieValue 获取Cookie值
  • @RequestAttribute 获取request域属性
  • @RequestBody 获取请求体[POST]
  • @MatrixVariable 矩阵变量
  • @ModelAttribute

4.1、@PathVariable原理:

//If the method parameter is {@link java.util.Map Map<String, String>
//then the map is populated with all path variable names and values.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {

}

通过@PathVariable可以将请求中的所有参数放到map中。

使用用例

@RequestMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
                  @PathVariable("username") String username,
                  @PathVariable Map<String,String> map){

    Map<String,Object> maps = new HashMap<>();
    maps.put("id",id);
    maps.put("username",username);
    maps.put("kv",map);
    return maps;
}

4.2、@RequestHeader原理

通过@RequestHeader可以将请求头中的所有参数放到map中。

//If the method parameter is {@link java.util.Map Map<String, String>,
//{@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or {@link org.springframework.http}HttpHeaders HttpHeaders}@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {

}

@RequestMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
                  PathVariable("username") String username,
                  @PathVariable Map<String,String> map,
                 @RequestHeader("User-Agent") String agent,
                  @RequestHeader Map<String,String> headers) {

    Map<String,Object> maps = new HashMap<>();
    maps.put("id",id);
    maps.put("username",username);
    maps.put("kv",map);

    maps.put("header",agent);
    maps.put("headers",headers);

    return maps;

 4.3、@RequestParam

通过@RequestParam可以将请求参数中的所有参数放到map中。

//If the method parameter is {@link java.util.Map Map<String, String>,
//{@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or {@link org.springframework.http}HttpHeaders HttpHeaders}@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {

}

@RequestMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
                       @PathVariable("username") String username,
                       @PathVariable Map<String,String> map,
                  @RequestHeader("User-Agent") String agent,
                  @RequestHeader Map<String,String> headers,
                 @RequestParam("age")String age,
                  @RequestParam("inters") List<String> inters,
                  @RequestParam Map<String,String> params){

    Map<String,Object> maps = new HashMap<>();
    maps.put("id",id);
    maps.put("username",username);
    maps.put("kv",map);

    maps.put("header",agent);
    maps.put("headers",headers);

    maps.put("age",age);
    maps.put("inters",inters);
    maps.put("prams",params);
    return maps;
}

4.4、@CookieValue

通过@CookieValue可以将cookie 放到Cookie中

//The method parameter may be declared as type {@link javax.servlet.http.Cookie} or as cookie value type (String, int, etc.).
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CookieValue {

}

@RequestMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
                  @PathVariable("username") String username,
                  @PathVariable Map<String,String> map,
                  @RequestHeader("User-Agent") String agent,
                  @RequestHeader Map<String,String> headers,
                  @RequestParam("age")String age,
                  @RequestParam("inters") List<String> inters,
                  @RequestParam Map<String,String> params,
                  @CookieValue("_ga")String _ga,
                  @CookieValue("_ga") Cookie cookie){

    Map<String,Object> maps = new HashMap<>();
    maps.put("id",id);
    maps.put("username",username);
    maps.put("kv",map);

    maps.put("header",agent);
    maps.put("headers",headers);

    maps.put("age",age);
    maps.put("inters",inters);
    maps.put("prams",params);

    map.put("_ga",_ga);
    System.out.println(cookie.getName() + ":" + cookie.getValue());
    return maps;
}
_ga:GA1.1.1466056184.1630465944

4.5、@RequestBody

获取表单数据,因为表单有请求体。
@PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }

 4.6、@RequestAttribute

@Controller
public class RequestController {

  @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

      request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        return "forward:/success";  //转发到  /success请求
    }

    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg") String msg,
                       @RequestAttribute(value = "code")Integer code,
                       HttpServletRequest request){
        Object msg1 = request.getAttribute("msg");

      map.put("reqMethod_msg",msg1);
        map.put("annotation_msg",msg);

      return map;
    }
}

4.7、@MatrixVariable与UrlPathHelper

  • 语法:

  • 请求路径:/cars/sell;low=34;brand=byd,audi,yd

  • SpringBoot默认是禁用了矩阵变量的功能

  • 手动开启:原理是对于路径的处理,都是使用UrlPathHelper的removeSemicolonContent(移除分号内容)设置为false,让其支持矩阵变量的。

  • 矩阵变量必须有url路径变量才能被解析

获取请求参数的两种方式:查询字符串和矩阵变量。

正常:服务端生成session存放内容,返回给客户端封装到cookie中生成jsessionid标识,再次请求时客户端携带cookie到服务端。

当cookie被禁用时,可以使用矩阵变量的方式将jsessionid传给后端。

@RestController
public class ParameterTestController {

  ///cars/sell;low=34;brand=byd,audi,yd
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                         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;

  }

}

手动开启矩阵变量

  • 实现WebMvcConfigurer接口:
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {

        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 不移除;后面的内容。矩阵变量功能就可以生效
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
  • 创建返回WebMvcConfigurerBean:
@Configuration(proxyBeanMethods = false)
public class WebConfig{
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
                        @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        }
    }
}

ctrl + F12 : 继承树 

相关文章