spring高级源码50讲-20-36(springMVC)
文章目录
- WEB
- 20) RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
- 演示1 - DispatcherServlet 初始化
- 代码参考
- 收获💡
- 演示2 - 自定义参数与返回值处理器
- 代码参考
- 收获💡
- 21) 参数解析器
- 演示 - 常见参数解析器
- 代码参考
- 收获💡
- 22) 参数名解析
- 演示 - 两种方法获取参数名
- 代码参考
- 收获💡
- 23) 对象绑定与类型转换
- 底层第一套转换接口与实现
- 底层第二套转换接口
- 高层接口与实现
- 演示1 - 类型转换与数据绑定
- 代码参考
- 收获💡
- 演示2 - 数据绑定工厂
- 代码参考
- 收获💡
- 演示3 - 获取泛型参数
- 代码参考
- 收获💡
- 24) @ControllerAdvice 之 @InitBinder
- 演示 - 准备 @InitBinder
- 收获💡
- 25) 控制器方法执行流程
- 图1
- 图2
- 图3
- 26) @ControllerAdvice 之 @ModelAttribute
- 演示 - 准备 @ModelAttribute
- 代码参考
- 收获💡
- 27) 返回值处理器
- 演示 - 常见返回值处理器
- 代码参考
- 收获💡
- 28) MessageConverter
- 演示 - MessageConverter 的作用
- 代码参考
- 收获💡
- 29) @ControllerAdvice 之 ResponseBodyAdvice
- 演示 - ResponseBodyAdvice 增强
- 代码参考
- 收获💡
- 30) 异常解析器
- 演示 - ExceptionHandlerExceptionResolver
- 代码参考
- 收获💡
- 31) @ControllerAdvice 之 @ExceptionHandler
- 演示 - 准备 @ExceptionHandler
- 代码参考
- 收获💡
- 32) Tomcat 异常处理
- 演示1 - 错误页处理
- 关键代码
- 收获💡
- 演示2 - BasicErrorController
- 关键代码
- 收获💡
- 33) BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
- 演示 - 本组映射器和适配器
- 关键代码
- 收获💡
- 34) RouterFunctionMapping 与 HandlerFunctionAdapter
- 演示 - 本组映射器和适配器
- 关键代码
- 收获💡
- 35) SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter
- 演示1 - 本组映射器和适配器
- 代码参考
- 关键代码
- 收获💡
- 演示2 - 静态资源解析优化
- 关键代码
- 收获💡
- 演示3 - 欢迎页
- 关键代码
- 收获💡
- 映射器与适配器小结
- 36) mvc 处理流程
WEB
20) RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对,分别用来
- 处理 @RequestMapping 映射
- 调用控制器方法、并处理方法参数与方法返回值
演示1 - DispatcherServlet 初始化
代码参考
package com.itheima.a20;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;public class A20 {private static final Logger log = LoggerFactory.getLogger(A20.class);public static void main(String[] args) throws Exception {AnnotationConfigServletWebServerApplicationContext context =new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);// 作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);// 获取映射结果Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();handlerMethods.forEach((k, v) -> {System.out.println(k + "=" + v);});// 请求来了,获取控制器方法 返回处理器执行链对象MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test4");request.setParameter("name", "张三");request.addHeader("token", "某个令牌");MockHttpServletResponse response = new MockHttpServletResponse();HandlerExecutionChain chain = handlerMapping.getHandler(request);System.out.println(chain);System.out.println(">>>>>>>>>>>>>>>>>>>>>");// HandlerAdapter 作用: 调用控制器方法MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());// 检查响应byte[] content = response.getContentAsByteArray();System.out.println(new String(content, StandardCharsets.UTF_8));/*System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有参数解析器");for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) {System.out.println(resolver);}System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有返回值解析器");for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) {System.out.println(handler);}*//*学到了什么a. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化b. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等本章介绍两个最为重要的组件a. RequestMappingHandlerAdapter, 以 @RequestMapping 作为映射路径b. RequestMappingHandlerAdapter, 调用 handlerc. 控制器的具体方法会被当作 handler- handler 的参数和返回值多种多样- 需要解析方法参数, 由 HandlerMethodArgumentResolver 来做- 需要处理方法返回值, 由 HandlerMethodReturnValueHandler 来做*/}
}
package com.itheima.a20;import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {// ⬅️内嵌 web 容器工厂@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {return new TomcatServletWebServerFactory(serverProperties.getPort());}// ⬅️创建 DispatcherServlet@Beanpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}// ⬅️注册 DispatcherServlet, Spring MVC 的入口@Beanpublic DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());return registrationBean;}// 如果用 DispatcherServlet 初始化时默认添加的组件, 并不会作为 bean, 给测试带来困扰// ⬅️1. 加入RequestMappingHandlerMapping@Beanpublic RequestMappingHandlerMapping requestMappingHandlerMapping() {return new RequestMappingHandlerMapping();}// ⬅️2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter@Beanpublic MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();handlerAdapter.setCustomArgumentResolvers(List.of(tokenArgumentResolver));handlerAdapter.setCustomReturnValueHandlers(List.of(ymlReturnValueHandler));return handlerAdapter;}public HttpMessageConverters httpMessageConverters() {return new HttpMessageConverters();}// ⬅️3. 演示 RequestMappingHandlerAdapter 初始化后, 有哪些参数、返回值处理器// ⬅️3.1 创建自定义参数处理器// ⬅️3.2 创建自定义返回值处理器}
收获💡
- DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化
- 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化
- RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
- key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
- value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
- 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
- RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
- HandlerMethodArgumentResolver 解析控制器方法参数
- HandlerMethodReturnValueHandler 处理控制器方法返回值
演示2 - 自定义参数与返回值处理器
代码参考
package com.itheima.a20;import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;public class TokenArgumentResolver implements HandlerMethodArgumentResolver {@Override// 是否支持某个参数public boolean supportsParameter(MethodParameter parameter) {Token token = parameter.getParameterAnnotation(Token.class);return token != null;}@Override// 解析参数public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {return webRequest.getHeader("token");}
}
package com.itheima.a20;import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.yaml.snakeyaml.Yaml;import javax.servlet.http.HttpServletResponse;public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {@Overridepublic boolean supportsReturnType(MethodParameter returnType) {Yml yml = returnType.getMethodAnnotation(Yml.class);return yml != null;}@Override // 返回值public void handleReturnValue(Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 1. 转换返回结果为 yaml 字符串String str = new Yaml().dump(returnValue);// 2. 将 yaml 字符串写入响应HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);response.setContentType("text/plain;charset=utf-8");response.getWriter().print(str);// 3. 设置请求已经处理完毕mavContainer.setRequestHandled(true);}
}
收获💡
- 体会参数解析器的作用
- 体会返回值处理器的作用
21) 参数解析器
演示 - 常见参数解析器
代码参考
package com.itheima.a21;import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*目标: 解析控制器方法的参数值常见的参数处理器如下:org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523corg.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacbaorg.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060forg.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77aorg.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21borg.springframework.web.method.annotation.MapMethodProcessor@16c3ca31org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988forg.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3*/
public class A21 {public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();// 准备测试 RequestHttpServletRequest request = mockRequest();// 要点1. 控制器方法被封装为 HandlerMethodHandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));// 要点2. 准备对象绑定与类型转换ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果ModelAndViewContainer container = new ModelAndViewContainer();// 要点4. 解析每个参数值for (MethodParameter parameter : handlerMethod.getMethodParameters()) {// 多个解析器组合HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();composite.addResolvers(// false 表示必须有 @RequestParamnew RequestParamMethodArgumentResolver(beanFactory, false),new PathVariableMethodArgumentResolver(),new RequestHeaderMethodArgumentResolver(beanFactory),new ServletCookieValueMethodArgumentResolver(beanFactory),new ExpressionValueMethodArgumentResolver(beanFactory),new ServletRequestMethodArgumentResolver(),new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttributenew RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttributenew RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam);String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());String str = annotations.length() > 0 ? " @" + annotations + " " : " ";parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());if (composite.supportsParameter(parameter)) {// 支持此参数Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
// System.out.println(v.getClass());System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);System.out.println("模型数据为:" + container.getModel());} else {System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());}}/*学到了什么a. 每个参数处理器能干啥1) 看是否支持某种参数2) 获取参数的值b. 组合模式在 Spring 中的体现c. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取*/}private static HttpServletRequest mockRequest() {MockHttpServletRequest request = new MockHttpServletRequest();request.setParameter("name1", "zhangsan");request.setParameter("name2", "lisi");request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");System.out.println(map);request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);request.setContentType("application/json");request.setCookies(new Cookie("token", "123456"));request.setParameter("name", "张三");request.setParameter("age", "18");request.setContent("""{"name":"李四","age":20}""".getBytes(StandardCharsets.UTF_8));return new StandardServletMultipartResolver().resolveMultipart(request);}static class Controller {public void test(@RequestParam("name1") String name1, // name1=张三String name2, // name2=李四@RequestParam("age") int age, // age=18@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据@RequestParam("file") MultipartFile file, // 上传文件@PathVariable("id") int id, // /test/124 /test/{id}@RequestHeader("Content-Type") String header,@CookieValue("token") String token,@Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}HttpServletRequest request, // request, response, session ...@ModelAttribute("abc") User user1, // name=zhang&age=18User user2, // name=zhang&age=18@RequestBody User user3 // json) {}}static class User {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}
}
收获💡
- 初步了解 RequestMappingHandlerAdapter 的调用过程
- 控制器方法被封装为 HandlerMethod
- 准备对象绑定与类型转换
- 准备 ModelAndViewContainer 用来存储中间 Model 结果
- 解析每个参数值
- 解析参数依赖的就是各种参数解析器,它们都有两个重要方法
- supportsParameter 判断是否支持方法参数
- resolveArgument 解析方法参数
- 常见参数的解析
- @RequestParam
- 省略 @RequestParam
- @RequestParam(defaultValue)
- MultipartFile
- @PathVariable
- @RequestHeader
- @CookieValue
- @Value
- HttpServletRequest 等
- @ModelAttribute
- 省略 @ModelAttribute
- @RequestBody
- 组合模式在 Spring 中的体现
- @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取
22) 参数名解析
演示 - 两种方法获取参数名
代码参考
package com.itheima.a22;import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;/*目标: 如何获取方法参数名, 注意把 a22 目录添加至模块的类路径1. a22 不在 src 是避免 idea 自动编译它下面的类2. spring boot 在编译时会加 -parameters3. 大部分 IDE 编译时都会加 -g*/
public class A22 {public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {// 1. 反射获取参数名Method foo = Bean2.class.getMethod("foo", String.class, int.class);/*for (Parameter parameter : foo.getParameters()) {System.out.println(parameter.getName());}*/// 2. 基于 LocalVariableTable 本地变量表LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();String[] parameterNames = discoverer.getParameterNames(foo);System.out.println(Arrays.toString(parameterNames));/*学到了什么a. 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名b. 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况1. 普通类, 会包含局部变量表, 用 asm 可以拿到参数名2. 接口, 不会包含局部变量表, 无法获得参数名 (这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名)*/}}
收获💡
- 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
- 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
- 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
- 接口, 不会包含局部变量表, 无法获得参数名
- 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名
23) 对象绑定与类型转换
底层第一套转换接口与实现
- Printer 把其它类型转为 String
- Parser 把 String 转为其它类型
- Formatter 综合 Printer 与 Parser 功能
- Converter 把类型 S 转为类型 T
- Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
- FormattingConversionService 利用其它们实现转换
底层第二套转换接口
- PropertyEditor 把 String 与其它类型相互转换
- PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
- 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配
高层接口与实现
- 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
- 首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
- 再看有没有 ConversionService 转换
- 再利用默认的 PropertyEditor 转换
- 最后有一些特殊处理
- SimpleTypeConverter 仅做类型转换
- BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property
- DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field
- ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能
演示1 - 类型转换与数据绑定
代码参考
package com.itheima.a23;import org.springframework.beans.SimpleTypeConverter;import java.util.Date;public class TestSimpleConverter {public static void main(String[] args) {// 仅有类型转换的功能SimpleTypeConverter typeConverter = new SimpleTypeConverter();Integer number = typeConverter.convertIfNecessary("13", int.class);Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);System.out.println(number);System.out.println(date);}
}
package com.itheima.a23;import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.GenericTypeResolver;
import org.springframework.format.Formatter;
import org.springframework.format.support.FormatterPropertyEditorAdapter;
import org.springframework.format.support.FormattingConversionService;import java.util.Date;public class TestBeanWrapper {public static void main(String[] args) {// 利用反射原理, 为 bean 的属性赋值MyBean target = new MyBean();BeanWrapperImpl wrapper = new BeanWrapperImpl(target);wrapper.setPropertyValue("a", "10");wrapper.setPropertyValue("b", "hello");wrapper.setPropertyValue("c", "1999/03/04");System.out.println(target);}static class MyBean {private int a;private String b;private Date c;public int getA() {return a;}public void setA(int a) {this.a = a;}public String getB() {return b;}public void setB(String b) {this.b = b;}public Date getC() {return c;}public void setC(Date c) {this.c = c;}@Overridepublic String toString() {return "MyBean{" +"a=" + a +", b='" + b + '\'' +", c=" + c +'}';}}
}
package com.itheima.a23;import org.springframework.beans.MutablePropertyValues;
import org.springframework.validation.DataBinder;import java.util.Date;public class TestDataBinder {public static void main(String[] args) {// 执行数据绑定MyBean target = new MyBean();DataBinder dataBinder = new DataBinder(target);dataBinder.initDirectFieldAccess();MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("a", "10");pvs.add("b", "hello");pvs.add("c", "1999/03/04");dataBinder.bind(pvs);System.out.println(target);}static class MyBean {private int a;private String b;private Date c;@Overridepublic String toString() {return "MyBean{" +"a=" + a +", b='" + b + '\'' +", c=" + c +'}';}}
}
package com.itheima.a23;import org.springframework.beans.MutablePropertyValues;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;import java.util.Date;public class TestServletDataBinder {public static void main(String[] args) {// web 环境下数据绑定MyBean target = new MyBean();ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);MockHttpServletRequest request = new MockHttpServletRequest();request.setParameter("a", "10");request.setParameter("b", "hello");request.setParameter("c", "1999/03/04");dataBinder.bind(new ServletRequestParameterPropertyValues(request));System.out.println(target);}static class MyBean {private int a;private String b;private Date c;public int getA() {return a;}public void setA(int a) {this.a = a;}public String getB() {return b;}public void setB(String b) {this.b = b;}public Date getC() {return c;}public void setC(Date c) {this.c = c;}@Overridepublic String toString() {return "MyBean{" +"a=" + a +", b='" + b + '\'' +", c=" + c +'}';}}
}
package com.itheima.a23;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.GenericTypeResolver;
import org.springframework.format.Formatter;import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;public class MyDateFormatter implements Formatter<Date> {private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class);private final String desc;public MyDateFormatter(String desc) {this.desc = desc;}@Overridepublic String print(Date date, Locale locale) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");return sdf.format(date);}@Overridepublic Date parse(String text, Locale locale) throws ParseException {log.debug(">>>>>> 进入了: {}", desc);SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");return sdf.parse(text);}}
收获💡
基本的类型转换与数据绑定用法
- SimpleTypeConverter
- BeanWrapperImpl
- DirectFieldAccessor
- ServletRequestDataBinder
演示2 - 数据绑定工厂
代码参考
package com.itheima.a23;import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;import java.util.Date;public class TestServletDataBinderFactory {public static void main(String[] args) throws Exception {MockHttpServletRequest request = new MockHttpServletRequest();request.setParameter("birthday", "1999|01|02");request.setParameter("address.name", "西安");User target = new User();// "1. 用工厂, 无转换功能"
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);// "2. 用 @InitBinder 转换" PropertyEditorRegistry PropertyEditor
// InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);// "3. 用 ConversionService 转换" ConversionService Formatter
// FormattingConversionService service = new FormattingConversionService();
// service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
// ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
// initializer.setConversionService(service);
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);// "4. 同时加了 @InitBinder 和 ConversionService"
// InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
//
// FormattingConversionService service = new FormattingConversionService();
// service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
// ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
// initializer.setConversionService(service);
//
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), initializer);// "5. 使用默认 ConversionService 转换"ApplicationConversionService service = new ApplicationConversionService();ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();initializer.setConversionService(service);ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");dataBinder.bind(new ServletRequestParameterPropertyValues(request));System.out.println(target);}static class MyController {@InitBinderpublic void aaa(WebDataBinder dataBinder) {// 扩展 dataBinder 的转换器dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));}}public static class User {@DateTimeFormat(pattern = "yyyy|MM|dd")private Date birthday;private Address address;public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}@Overridepublic String toString() {return "User{" +"birthday=" + birthday +", address=" + address +'}';}}public static class Address {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Address{" +"name='" + name + '\'' +'}';}}
}
收获💡
ServletRequestDataBinderFactory 的用法和扩展点
- 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器
- 控制器私有范围
- 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器
- 公共范围
- 同时加了 @InitBinder 和 ConversionService 的转换优先级
- 优先采用 @InitBinder 的转换器
- 其次使用 ConversionService 的转换器
- 使用默认转换器
- 特殊处理(例如有参构造)
演示3 - 获取泛型参数
代码参考
package com.itheima.a23.sub;import org.springframework.core.GenericTypeResolver;
import org.springframework.core.ResolvableType;import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;public class TestGenericType {public static void main(String[] args) {// 小技巧// 1. java apiSystem.out.println(">>>>>>>>>>>>>>>>>>>>>>>");Type type = TeacherDao.class.getGenericSuperclass();System.out.println(type);if (type instanceof ParameterizedType parameterizedType) {System.out.println(parameterizedType.getActualTypeArguments()[0]);}// 2. spring api 1System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");Class<?> t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);System.out.println(t);// 3. spring api 2System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");System.out.println(ResolvableType.forClass(TeacherDao.class).getSuperType().getGeneric().resolve());}}
收获💡
- java api 获取泛型参数
- spring api 获取泛型参数
24) @ControllerAdvice 之 @InitBinder
演示 - 准备 @InitBinder
准备 @InitBinder 在整个 HandlerAdapter 调用过程中所处的位置
- RequestMappingHandlerAdapter 在图中缩写为 HandlerAdapter
- HandlerMethodArgumentResolverComposite 在图中缩写为 ArgumentResolvers
- HandlerMethodReturnValueHandlerComposite 在图中缩写为 ReturnValueHandlers
package com.itheima.a24;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;/*@InitBinder 的来源*/
public class A24 {private static final Logger log = LoggerFactory.getLogger(A24.class);public static void main(String[] args) throws Exception {/*@InitBinder 的来源有两个1. @ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并记录2. @Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并记录*/AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(WebConfig.class);RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();handlerAdapter.setApplicationContext(context);handlerAdapter.afterPropertiesSet();log.debug("1. 刚开始...");showBindMethods(handlerAdapter);Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);getDataBinderFactory.setAccessible(true);log.debug("2. 模拟调用 Controller1 的 foo 方法时 ...");getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));showBindMethods(handlerAdapter);log.debug("3. 模拟调用 Controller2 的 bar 方法时 ...");getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar")));showBindMethods(handlerAdapter);context.close();/*学到了什么a. Method 对象的获取利用了缓存来进行加速b. 绑定器工厂的扩展点(advice 之一), 通过 @InitBinder 扩展类型转换器*/}@SuppressWarnings("all")private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException {Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");initBinderAdviceCache.setAccessible(true);Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(handlerAdapter);log.debug("全局的 @InitBinder 方法 {}",globalMap.values().stream().flatMap(ms -> ms.stream().map(m -> m.getName())).collect(Collectors.toList()));Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");initBinderCache.setAccessible(true);Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCache.get(handlerAdapter);log.debug("控制器的 @InitBinder 方法 {}",controllerMap.entrySet().stream().flatMap(e -> e.getValue().stream().map(v -> e.getKey().getSimpleName() + "." + v.getName())).collect(Collectors.toList()));}
}
收获💡
- RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法
- RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
- 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
- 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂
25) 控制器方法执行流程
图1
HandlerMethod 需要
- bean 即是哪个 Controller
- method 即是 Controller 中的哪个方法
ServletInvocableHandlerMethod 需要
- WebDataBinderFactory 负责对象绑定、类型转换
- ParameterNameDiscoverer 负责参数名解析
- HandlerMethodArgumentResolverComposite 负责解析参数
- HandlerMethodReturnValueHandlerComposite 负责处理返回值
图2
图3
26) @ControllerAdvice 之 @ModelAttribute
演示 - 准备 @ModelAttribute
代码参考
package com.itheima.a26;import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.*;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.*;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;import static com.itheima.a26.WebConfig.*;public class A26 {public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(WebConfig.class);RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();adapter.setApplicationContext(context);adapter.afterPropertiesSet();MockHttpServletRequest request = new MockHttpServletRequest();request.setParameter("name", "张三");/*现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起, 并完成控制器方法的调用, 如下*/ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Controller1(), Controller1.class.getMethod("foo", User.class));ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);handlerMethod.setDataBinderFactory(factory);handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));ModelAndViewContainer container = new ModelAndViewContainer();// 获取模型工厂方法Method getModelFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getModelFactory", HandlerMethod.class, WebDataBinderFactory.class);getModelFactory.setAccessible(true);ModelFactory modelFactory = (ModelFactory) getModelFactory.invoke(adapter, handlerMethod, factory);// 初始化模型数据modelFactory.initModel(new ServletWebRequest(request), container, handlerMethod);handlerMethod.invokeAndHandle(new ServletWebRequest(request), container);System.out.println(container.getModel());context.close();/*学到了什么a. 控制器方法是如何调用的b. 模型数据如何产生c. advice 之二, @ModelAttribute 补充模型数据*/}public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();composite.addResolvers(new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),new PathVariableMethodArgumentResolver(),new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),new ServletRequestMethodArgumentResolver(),new ServletModelAttributeMethodProcessor(false),new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),new ServletModelAttributeMethodProcessor(true),new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true));return composite;}}
package com.itheima.a26;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;@Configuration
public class WebConfig {@ControllerAdvicestatic class MyControllerAdvice {@ModelAttribute("a")public String aa() {return "aa";}}@Controllerstatic class Controller1 {@ModelAttribute("b")public String aa() {return "bb";}@ResponseStatus(HttpStatus.OK)public ModelAndView foo(@ModelAttribute("u") User user) {System.out.println("foo");return null;}}static class User {private String name;public void setName(String name) {this.name = name;}public String getName() {return name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}}
}
准备 @ModelAttribute 在整个 HandlerAdapter 调用过程中所处的位置
收获💡
- RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法
- RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @ModelAttribute 方法
- 以上两种 @ModelAttribute 的解析结果都会缓存来避免重复解析
- 控制器方法调用时,会综合利用本类的 @ModelAttribute 方法和 @ControllerAdvice 中的 @ModelAttribute 方法创建模型工厂
27) 返回值处理器
演示 - 常见返回值处理器
代码参考
package com.itheima.a27;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.*;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.util.UrlPathHelper;import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;/*目标: 解析控制器方法的返回值常见的返回值处理器org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@4c9e38org.springframework.web.method.annotation.ModelMethodProcessor@5d1e09bcorg.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@4bdc8b5dorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@3bcd426corg.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@5f14a673org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@726a17c4org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@5dc3fcb7org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@c4c0b41org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@76911385org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@5467eea4org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@160396dborg.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@7a799159org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@40ab8a8org.springframework.web.method.annotation.MapMethodProcessor@6ff37443org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@65cc8228*/
public class A27 {private static final Logger log = LoggerFactory.getLogger(A27.class);public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(WebConfig.class);// 1. 测试返回值类型为 ModelAndView// 2. 测试返回值类型为 String 时, 把它当做视图名// 3. 测试返回值添加了 @ModelAttribute 注解时, 此时需找到默认视图名// 4. 测试返回值不加 @ModelAttribute 注解且返回非简单类型时, 此时需找到默认视图名// 5. 测试返回值类型为 ResponseEntity 时, 此时不走视图流程// 6. 测试返回值类型为 HttpHeaders 时, 此时不走视图流程// 7. 测试返回值添加了 @ResponseBody 注解时, 此时不走视图流程test7(context);/*学到了什么a. 每个返回值处理器能干啥1) 看是否支持某种返回值2) 返回值或作为模型、或作为视图名、或作为响应体 ...b. 组合模式在 Spring 中的体现 + 1*/}private static void test7(AnnotationConfigApplicationContext context) throws Exception {Method method = Controller.class.getMethod("test7");Controller controller = new Controller();Object returnValue = method.invoke(controller); // 获取返回值HandlerMethod methodHandle = new HandlerMethod(controller, method);ModelAndViewContainer container = new ModelAndViewContainer();HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();MockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ServletWebRequest webRequest = new ServletWebRequest(request, response);if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);System.out.println(container.getModel());System.out.println(container.getViewName());if (!container.isRequestHandled()) {renderView(context, container, webRequest); // 渲染视图} else {for (String name : response.getHeaderNames()) {System.out.println(name + "=" + response.getHeader(name));}System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));}}}private static void test6(AnnotationConfigApplicationContext context) throws Exception {Method method = Controller.class.getMethod("test6");Controller controller = new Controller();Object returnValue = method.invoke(controller); // 获取返回值HandlerMethod methodHandle = new HandlerMethod(controller, method);ModelAndViewContainer container = new ModelAndViewContainer();HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();MockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ServletWebRequest webRequest = new ServletWebRequest(request, response);if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);System.out.println(container.getModel());System.out.println(container.getViewName());if (!container.isRequestHandled()) {renderView(context, container, webRequest); // 渲染视图} else {for (String name : response.getHeaderNames()) {System.out.println(name + "=" + response.getHeader(name));}System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));}}}private static void test5(AnnotationConfigApplicationContext context) throws Exception {Method method = Controller.class.getMethod("test5");Controller controller = new Controller();Object returnValue = method.invoke(controller); // 获取返回值HandlerMethod methodHandle = new HandlerMethod(controller, method);ModelAndViewContainer container = new ModelAndViewContainer();HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();MockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ServletWebRequest webRequest = new ServletWebRequest(request, response);if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);System.out.println(container.getModel());System.out.println(container.getViewName());if (!container.isRequestHandled()) {renderView(context, container, webRequest); // 渲染视图} else {System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));}}}private static void test4(AnnotationConfigApplicationContext context) throws Exception {Method method = Controller.class.getMethod("test4");Controller controller = new Controller();Object returnValue = method.invoke(controller); // 获取返回值HandlerMethod methodHandle = new HandlerMethod(controller, method);ModelAndViewContainer container = new ModelAndViewContainer();HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();MockHttpServletRequest request = new MockHttpServletRequest();request.setRequestURI("/test4");UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);System.out.println(container.getModel());System.out.println(container.getViewName());renderView(context, container, webRequest); // 渲染视图}}private static void test3(AnnotationConfigApplicationContext context) throws Exception {Method method = Controller.class.getMethod("test3");Controller controller = new Controller();Object returnValue = method.invoke(controller); // 获取返回值HandlerMethod methodHandle = new HandlerMethod(controller, method);ModelAndViewContainer container = new ModelAndViewContainer();HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();MockHttpServletRequest request = new MockHttpServletRequest();request.setRequestURI("/test3");UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);System.out.println(container.getModel());System.out.println(container.getViewName());renderView(context, container, webRequest); // 渲染视图}}private static void test2(AnnotationConfigApplicationContext context) throws Exception {Method method = Controller.class.getMethod("test2");Controller controller = new Controller();Object returnValue = method.invoke(controller); // 获取返回值HandlerMethod methodHandle = new HandlerMethod(controller, method);ModelAndViewContainer container = new ModelAndViewContainer();HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);System.out.println(container.getModel());System.out.println(container.getViewName());renderView(context, container, webRequest); // 渲染视图}}private static void test1(AnnotationConfigApplicationContext context) throws Exception {Method method = Controller.class.getMethod("test1");Controller controller = new Controller();Object returnValue = method.invoke(controller); // 获取返回值HandlerMethod methodHandle = new HandlerMethod(controller, method);ModelAndViewContainer container = new ModelAndViewContainer();HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);System.out.println(container.getModel());System.out.println(container.getViewName());renderView(context, container, webRequest); // 渲染视图}}public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler() {HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();composite.addHandler(new ModelAndViewMethodReturnValueHandler());composite.addHandler(new ViewNameMethodReturnValueHandler());composite.addHandler(new ServletModelAttributeMethodProcessor(false));composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));composite.addHandler(new HttpHeadersReturnValueHandler());composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));composite.addHandler(new ServletModelAttributeMethodProcessor(true));return composite;}@SuppressWarnings("all")private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,ServletWebRequest webRequest) throws Exception {log.debug(">>>>>> 渲染视图");FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());log.debug("没有获取到视图名, 采用默认视图名: {}", viewName);// 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化View view = resolver.resolveViewName(viewName, Locale.getDefault());view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));}static class Controller {private static final Logger log = LoggerFactory.getLogger(Controller.class);public ModelAndView test1() {log.debug("test1()");ModelAndView mav = new ModelAndView("view1");mav.addObject("name", "张三");return mav;}public String test2() {log.debug("test2()");return "view2";}@ModelAttribute
// @RequestMapping("/test3")public User test3() {log.debug("test3()");return new User("李四", 20);}public User test4() {log.debug("test4()");return new User("王五", 30);}public HttpEntity<User> test5() {log.debug("test5()");return new HttpEntity<>(new User("赵六", 40));}public HttpHeaders test6() {log.debug("test6()");HttpHeaders headers = new HttpHeaders();headers.add("Content-Type", "text/html");return headers;}@ResponseBodypublic User test7() {log.debug("test7()");return new User("钱七", 50);}}// 必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败public static class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}
}
package com.itheima.a27;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;@Configuration
public class WebConfig {@Beanpublic FreeMarkerConfigurer freeMarkerConfigurer() {FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();configurer.setDefaultEncoding("utf-8");configurer.setTemplateLoaderPath("classpath:templates");return configurer;}@Bean // FreeMarkerView 在借助 Spring 初始化时,会要求 web 环境才会走 setConfiguration, 这里想办法去掉了 web 环境的约束public FreeMarkerViewResolver viewResolver(FreeMarkerConfigurer configurer) {FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() {@Overrideprotected AbstractUrlBasedView instantiateView() {FreeMarkerView view = new FreeMarkerView() {@Overrideprotected boolean isContextRequired() {return false;}};view.setConfiguration(configurer.getConfiguration());return view;}};resolver.setContentType("text/html;charset=utf-8");resolver.setPrefix("/");resolver.setSuffix(".ftl");resolver.setExposeSpringMacroHelpers(false);return resolver;}
}
收获💡
- 常见的返回值处理器
- ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer
- 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer
- 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer
- 此时需找到默认视图名
- 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer
- 此时需找到默认视图名
- 返回值类型为 ResponseEntity 时
- 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
- 返回值类型为 HttpHeaders 时
- 会设置 ModelAndViewContainer.requestHandled 为 true
- 返回值添加了 @ResponseBody 注解时
- 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
- 组合模式在 Spring 中的体现 + 1
28) MessageConverter
演示 - MessageConverter 的作用
代码参考
package com.itheima.a28;import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;public class A28 {public static void main(String[] args) throws IOException, NoSuchMethodException, HttpMediaTypeNotAcceptableException {
// test1();
// test2();
// test3();test4();/*学到了什么a. MessageConverter 的作用, @ResponseBody 是返回值处理器解析的, 但具体转换工作是 MessageConverter 做的b. 如何选择 MediaType- 首先看 @RequestMapping 上有没有指定- 其次看 request 的 Accept 头有没有指定- 最后按 MessageConverter 的顺序, 谁能谁先转换*/}private static void test4() throws IOException, HttpMediaTypeNotAcceptableException, NoSuchMethodException {MockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ServletWebRequest webRequest = new ServletWebRequest(request, response);request.addHeader("Accept", "application/xml");response.setContentType("application/json");RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter(), new MappingJackson2XmlHttpMessageConverter()));processor.handleReturnValue(new User("张三", 18),new MethodParameter(A28.class.getMethod("user"), -1),new ModelAndViewContainer(),webRequest);System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));}@ResponseBody@RequestMapping(produces = "application/json")public User user() {return null;}private static void test3() throws IOException {MockHttpInputMessage message = new MockHttpInputMessage("""{"name":"李四","age":20}""".getBytes(StandardCharsets.UTF_8));MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();if (converter.canRead(User.class, MediaType.APPLICATION_JSON)) {Object read = converter.read(User.class, message);System.out.println(read);}}private static void test2() throws IOException {MockHttpOutputMessage message = new MockHttpOutputMessage();MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {converter.write(new User("李四", 20), MediaType.APPLICATION_XML, message);System.out.println(message.getBodyAsString());}}public static void test1() throws IOException {MockHttpOutputMessage message = new MockHttpOutputMessage();MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {converter.write(new User("张三", 18), MediaType.APPLICATION_JSON, message);System.out.println(message.getBodyAsString());}}public static class User {private String name;private int age;@JsonCreatorpublic User(@JsonProperty("name") String name, @JsonProperty("age") int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}
}
收获💡
- MessageConverter 的作用
- @ResponseBody 是返回值处理器解析的
- 但具体转换工作是 MessageConverter 做的
- 如何选择 MediaType
- 首先看 @RequestMapping 上有没有指定
- 其次看 request 的 Accept 头有没有指定
- 最后按 MessageConverter 的顺序, 谁能谁先转换
29) @ControllerAdvice 之 ResponseBodyAdvice
演示 - ResponseBodyAdvice 增强
代码参考
package com.itheima.a29;import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.*;import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;public class A29 {// {"name":"王五","age":18}// {"code":xx, "msg":xx, data: {"name":"王五","age":18} }public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(WebConfig.class);ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(context.getBean(WebConfig.MyController.class),WebConfig.MyController.class.getMethod("user"));handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(), null));handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers(context));MockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();ModelAndViewContainer container = new ModelAndViewContainer();handlerMethod.invokeAndHandle(new ServletWebRequest(request, response), container);System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));/*学到了什么a. advice 之三, ResponseBodyAdvice 返回响应体前包装*/}public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();composite.addResolvers(new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),new PathVariableMethodArgumentResolver(),new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),new ServletRequestMethodArgumentResolver(),new ServletModelAttributeMethodProcessor(false),new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),new ServletModelAttributeMethodProcessor(true),new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true));return composite;}public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers(AnnotationConfigApplicationContext context) {// 添加 adviceList<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);List<Object> collect = annotatedBeans.stream().filter(b -> ResponseBodyAdvice.class.isAssignableFrom(b.getBeanType())).collect(Collectors.toList());HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();composite.addHandler(new ModelAndViewMethodReturnValueHandler());composite.addHandler(new ViewNameMethodReturnValueHandler());composite.addHandler(new ServletModelAttributeMethodProcessor(false));composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));composite.addHandler(new HttpHeadersReturnValueHandler());composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()), collect));composite.addHandler(new ServletModelAttributeMethodProcessor(true));return composite;}
}
package com.itheima.a29;import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@Configuration
public class WebConfig {@ControllerAdvicestatic class MyControllerAdvice implements ResponseBodyAdvice<Object> {// 满足条件才转换public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {if (returnType.getMethodAnnotation(ResponseBody.class) != null ||AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) {
// returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)) {return true;}return false;}// 将 User 或其它类型统一为 Result 类型public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof Result) {return body;}return Result.ok(body);}}// @Controller// @ResponseBody@RestControllerpublic static class MyController {public User user() {return new User("王五", 18);}}public static class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
}
ResponseBodyAdvice 增强 在整个 HandlerAdapter 调用过程中所处的位置
收获💡
- ResponseBodyAdvice 返回响应体前包装
30) 异常解析器
演示 - ExceptionHandlerExceptionResolver
代码参考
package com.itheima.a30;import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;public class A30 {public static void main(String[] args) throws NoSuchMethodException {ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));resolver.afterPropertiesSet();MockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();// 1.测试 json
// HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));
// Exception e = new ArithmeticException("被零除");
// resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));// 2.测试 mav
// HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));
// Exception e = new ArithmeticException("被零除");
// ModelAndView mav = resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(mav.getModel());
// System.out.println(mav.getViewName());// 3.测试嵌套异常
// HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
// Exception e = new Exception("e1", new RuntimeException("e2", new IOException("e3")));
// resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));// 4.测试异常处理方法参数解析HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));Exception e = new Exception("e1");resolver.resolveException(request, response, handlerMethod, e);System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));/*学到了什么a. ExceptionHandlerExceptionResolver 能够重用参数解析器、返回值处理器,实现组件重用b. 能够支持嵌套异常*/}static class Controller1 {public void foo() {}@ExceptionHandler@ResponseBodypublic Map<String, Object> handle(ArithmeticException e) {return Map.of("error", e.getMessage());}}static class Controller2 {public void foo() {}@ExceptionHandlerpublic ModelAndView handle(ArithmeticException e) {return new ModelAndView("test2", Map.of("error", e.getMessage()));}}static class Controller3 {public void foo() {}@ExceptionHandler@ResponseBodypublic Map<String, Object> handle(IOException e3) {return Map.of("error", e3.getMessage());}}static class Controller4 {public void foo() {}@ExceptionHandler@ResponseBodypublic Map<String, Object> handler(Exception e, HttpServletRequest request) {System.out.println(request);return Map.of("error", e.getMessage());}}
}
收获💡
- 它能够重用参数解析器、返回值处理器,实现组件重用
- 它能够支持嵌套异常
31) @ControllerAdvice 之 @ExceptionHandler
演示 - 准备 @ExceptionHandler
代码参考
package com.itheima.a31;import com.itheima.a30.A30;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;public class A31 {public static void main(String[] args) throws NoSuchMethodException {MockHttpServletRequest request = new MockHttpServletRequest();MockHttpServletResponse response = new MockHttpServletResponse();// ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
// resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
// resolver.afterPropertiesSet();AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo"));Exception e = new Exception("e1");resolver.resolveException(request, response, handlerMethod, e);System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));}static class Controller5 {public void foo() {}}
}
package com.itheima.a31;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;import java.util.List;
import java.util.Map;@Configuration
public class WebConfig {@ControllerAdvicestatic class MyControllerAdvice {@ExceptionHandler@ResponseBodypublic Map<String, Object> handle(Exception e) {return Map.of("error", e.getMessage());}}@Beanpublic ExceptionHandlerExceptionResolver resolver() {ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));return resolver;}
}
收获💡
- ExceptionHandlerExceptionResolver 初始化时会解析 @ControllerAdvice 中的 @ExceptionHandler 方法
- ExceptionHandlerExceptionResolver 会以类为单位,在该类首次处理异常时,解析此类的 @ExceptionHandler 方法
- 以上两种 @ExceptionHandler 的解析结果都会缓存来避免重复解析
32) Tomcat 异常处理
-
我们知道 @ExceptionHandler 只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内,那么如果是 Filter 出现了异常,如何进行处理呢?
-
在 Spring Boot 中,是这么实现的:
- 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚
- 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为
/error
也可以通过${server.error.path}
进行配置 - 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至
/error
这个地址- 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理
- Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为
/error
,所以处理异常的职责就又回到了 Spring - 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到
- 具体异常信息会由 DefaultErrorAttributes 封装好
- BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应
- 如果要的不是 text/html,走 MessageConverter 流程
- 如果需要 text/html,走 mvc 流程,此时又分两种情况
- 配置了 ErrorViewResolver,根据状态码去找 View
- 没配置或没找到,用 BeanNameViewResolver 根据一个固定为 error 的名字找到 View,即所谓的 WhitelabelErrorView
评价
- 一个错误处理搞得这么复杂,就问恶心不?
演示1 - 错误页处理
关键代码
@Bean // ⬅️修改了 Tomcat 服务器默认错误地址, 出错时使用请求转发方式跳转
public ErrorPageRegistrar errorPageRegistrar() {return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
}@Bean // ⬅️TomcatServletWebServerFactory 初始化前用它增强, 注册所有 ErrorPageRegistrar
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {return new ErrorPageRegistrarBeanPostProcessor();
}
收获💡
- Tomcat 的错误页处理手段
演示2 - BasicErrorController
关键代码
@Bean // ⬅️ErrorProperties 封装环境键值, ErrorAttributes 控制有哪些错误信息
public BasicErrorController basicErrorController() {ErrorProperties errorProperties = new ErrorProperties();errorProperties.setIncludeException(true);return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}@Bean // ⬅️名称为 error 的视图, 作为 BasicErrorController 的 text/html 响应结果
public View error() {return new View() {@Overridepublic void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {System.out.println(model);response.setContentType("text/html;charset=utf-8");response.getWriter().print("""<h3>服务器内部错误</h3>""");}};
}@Bean // ⬅️收集容器中所有 View 对象, bean 的名字作为视图名
public ViewResolver viewResolver() {return new BeanNameViewResolver();
}
收获💡
- Spring Boot 中 BasicErrorController 如何工作
33) BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
演示 - 本组映射器和适配器
关键代码
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {return new BeanNameUrlHandlerMapping();
}@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {return new SimpleControllerHandlerAdapter();
}@Bean("/c3")
public Controller controller3() {return (request, response) -> {response.getWriter().print("this is c3");return null;};
}
收获💡
- BeanNameUrlHandlerMapping,以 / 开头的 bean 的名字会被当作映射路径
- 这些 bean 本身当作 handler,要求实现 Controller 接口
- SimpleControllerHandlerAdapter,调用 handler
- 模拟实现这组映射器和适配器
34) RouterFunctionMapping 与 HandlerFunctionAdapter
演示 - 本组映射器和适配器
关键代码
@Bean
public RouterFunctionMapping routerFunctionMapping() {return new RouterFunctionMapping();
}@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {return new HandlerFunctionAdapter();
}@Bean
public RouterFunction<ServerResponse> r1() {// ⬇️映射条件 ⬇️handlerreturn route(GET("/r1"), request -> ok().body("this is r1"));
}
收获💡
- RouterFunctionMapping, 通过 RequestPredicate 条件映射
- handler 要实现 HandlerFunction 接口
- HandlerFunctionAdapter, 调用 handler
35) SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter
演示1 - 本组映射器和适配器
代码参考
org.springframework.boot.autoconfigure.web.servlet.A35
关键代码
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();Map<String, ResourceHttpRequestHandler> map = context.getBeansOfType(ResourceHttpRequestHandler.class);handlerMapping.setUrlMap(map);return handlerMapping;
}@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {return new HttpRequestHandlerAdapter();
}@Bean("/**")
public ResourceHttpRequestHandler handler1() {ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();handler.setLocations(List.of(new ClassPathResource("static/")));return handler;
}@Bean("/img/**")
public ResourceHttpRequestHandler handler2() {ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();handler.setLocations(List.of(new ClassPathResource("images/")));return handler;
}
收获💡
- SimpleUrlHandlerMapping 不会在初始化时收集映射信息,需要手动收集
- SimpleUrlHandlerMapping 映射路径
- ResourceHttpRequestHandler 作为静态资源 handler
- HttpRequestHandlerAdapter, 调用此 handler
演示2 - 静态资源解析优化
关键代码
@Bean("/**")
public ResourceHttpRequestHandler handler1() {ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();handler.setLocations(List.of(new ClassPathResource("static/")));handler.setResourceResolvers(List.of(// ⬇️缓存优化new CachingResourceResolver(new ConcurrentMapCache("cache1")),// ⬇️压缩优化new EncodedResourceResolver(),// ⬇️原始资源解析new PathResourceResolver()));return handler;
}
收获💡
- 责任链模式体现
- 压缩文件需要手动生成
演示3 - 欢迎页
关键代码
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {Resource resource = context.getResource("classpath:static/index.html");return new WelcomePageHandlerMapping(null, context, resource, "/**");
}@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {return new SimpleControllerHandlerAdapter();
}
收获💡
- 欢迎页支持静态欢迎页与动态欢迎页
- WelcomePageHandlerMapping 映射欢迎页(即只映射 ‘/’)
- 它内置的 handler ParameterizableViewController 作用是不执行逻辑,仅根据视图名找视图
- 视图名固定为 forward:index.html
- SimpleControllerHandlerAdapter, 调用 handler
- 转发至 /index.html
- 处理 /index.html 又会走上面的静态资源处理流程
映射器与适配器小结
- HandlerMapping 负责建立请求与控制器之间的映射关系
- RequestMappingHandlerMapping (与 @RequestMapping 匹配)
- WelcomePageHandlerMapping (/)
- BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)
- RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)
- SimpleUrlHandlerMapping (静态资源 通配符 /** /img/**)
- 之间也会有顺序问题, boot 中默认顺序如上
- HandlerAdapter 负责实现对各种各样的 handler 的适配调用
- RequestMappingHandlerAdapter 处理:@RequestMapping 方法
- 参数解析器、返回值处理器体现了组合模式
- SimpleControllerHandlerAdapter 处理:Controller 接口
- HandlerFunctionAdapter 处理:HandlerFunction 函数式接口
- HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)
- 这也是典型适配器模式体现
- RequestMappingHandlerAdapter 处理:@RequestMapping 方法
36) mvc 处理流程
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
-
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
- 路径:默认映射路径为
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器- jsp 不会匹配到 DispatcherServlet
- 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
- 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
- HandlerMapping,初始化时记录映射关系
- HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
- HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
- ViewResolver
- 路径:默认映射路径为
-
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
-
例如根据 /hello 路径找到 @RequestMapping(“/hello”) 对应的控制器方法
-
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
-
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
-
-
DispatcherServlet 接下来会:
- 调用拦截器的 preHandle 方法
- RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
- @ControllerAdvice 全局增强点1️⃣:补充模型数据
- @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器
- 使用 HandlerMethodArgumentResolver 准备参数
- @ControllerAdvice 全局增强点3️⃣:RequestBody 增强
- 调用 ServletInvocableHandlerMethod
- 使用 HandlerMethodReturnValueHandler 处理返回值
- @ControllerAdvice 全局增强点4️⃣:ResponseBody 增强
- 根据 ModelAndViewContainer 获取 ModelAndView
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
- 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 调用拦截器的 postHandle 方法
- 处理异常或视图渲染
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
- 正常,走视图解析及渲染流程
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- 调用拦截器的 afterCompletion 方法
相关文章:
spring高级源码50讲-20-36(springMVC)
文章目录 WEB20) RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter演示1 - DispatcherServlet 初始化代码参考 收获💡演示2 - 自定义参数与返回值处理器代码参考 收获💡 21) 参数解析器演示 - 常见参数解析器代码参考 收获💡 2…...

Leetcode Top 100 Liked Questions(序号141~189)
141. Linked List Cycle 题意:给你一个链表,判断链表有没有环 我的思路 两个指针,一个每次走两步,一个每次走一步,如果走两步的那个走到了NULL,那说明没有环,如果两个指针指向相等&…...
网络编程day3-FTP客户端项目
FTP协议 FTP 的独特的优势同时也是与其它客户服务器程序最大的不同点就在于它在两台通信的主机之间使用了两条 TCP 连接,一条是数据连接,用于数据传送;另一条是控制连接,用于传送控制信息(命令和响应)&…...

音频母带制作::AAMS V4.0 Crack
自动音频母带制作简介。 使用 AAMS V4 让您的音乐听起来很美妙! 作为从事音乐工作的音乐家,您在向公众发布材料时需要尽可能最好的声音,而为所有音频扬声器系统提供良好的商业声音是一项困难且耗时的任务。AI掌握的力量! 掌控您…...

【SpringCloud】SpringCloud整合openFeign
文章目录 前言1. 问题分析2. 了解Feign3. 项目整合Feign3.1 引入依赖3.2 添加注解3.3 编写Feign客户端3.4 测试3.5 总结 4. 自定义配置4.1 配置文件方式4.2 Java代码方式 5. Feign使用优化5.1 引入依赖5.2 配置连接池 6. Feign最佳实践6.1 继承方式6.2 抽取方式 前言 微服务远…...

成集云 | 飞书审批同步金蝶云星空 | 解决方案
源系统成集云目标系统 方案介绍 飞书员工报销审批通过后,审批单据内容和审批状态实时同步金蝶云星空 飞书是字节跳动于2016年自研的新一代一站式协作平台,将即时沟通、日历、云文档、云盘和工作台深度整合,通过开放兼容的平台,…...

【计算机组成 课程笔记】3.2 算数运算和逻辑运算的硬件实现
课程链接: 计算机组成_北京大学_中国大学MOOC(慕课) 3 - 2 - 302-门电路的基本原理(11-39--)_哔哩哔哩_bilibili 现代计算机的CPU和其他很多功能部件都是基于晶体管的集成电路,想要了解计算机组成的基本原理,还是需要有…...
python元组的不可变性和应用场景
Python元组是一种不可变的数据类型,也就是说一旦创建后,其元素无法被修改、添加或删除。元组使用圆括号来表示,元素之间使用逗号进行分隔。 以下是创建和访问元组的方法和语法: 创建元组: 使用圆括号直接创建ÿ…...
配置化开发的核心设计 - Schema
前端配置化SchemaServerless FaaS BaaS useImperativeHandle() react-helmet 参考链接 schema进入...

HTTP协议概述
HTTP 协议定义 HTTP协议,直译为超文本传输协议,是一种用于分布式、协作、超媒体的信息系统的应用协议。HTTP协议是万维网数据通信的基础。HTTP协议在客户端-服务器计算模型中充当请求-响应协议。客户端向服务器提交HTTP请求消息。服务器提供HTML文件和其…...
fastjson2 打开 AutoType
1. 功能简介 FASTJSON支持AutoType功能,这个功能在序列化的JSON字符串中带上类型信息,在反序列化时,不需要传入类型,实现自动类型识别。 2. AutoType安全机制介绍 必须显式打开才能使用。和fastjson 1.x不一样,fast…...

封装(个人学习笔记黑马学习)
1、格式 #include <iostream> using namespace std;const double PI 3.14;//设计一个圆类,求圆的周长 class Circle {//访问权限//公共权限 public://属性//半径int m_r;//行为//获取圆的周长double calculateZC() {return 2 * PI * m_r;} };int main() {//通…...

PyTorch 模型性能分析和优化 - 第 3 部分
这[1]是关于使用 PyTorch Profiler 和 TensorBoard 分析和优化 PyTorch 模型主题的系列文章的第三部分。我们的目的是强调基于 GPU 的训练工作负载的性能分析和优化的好处及其对训练速度和成本的潜在影响。特别是,我们希望向所有机器学习开发人员展示 PyTorch Profi…...

【力扣每日一题】2023.9.1 买钢笔和铅笔的方案数
目录 题目: 示例: 分析: 代码: 题目: 示例: 分析: 题目给我们三个数,一个是我们拥有的钱,一个是钢笔的价格,另一个是铅笔的价格。 问我们一共有几种买笔…...

实现不同局域网间的文件共享和端口映射,使用Python自带的HTTP服务
文章目录 1. 前言2. 本地文件服务器搭建2.1 python的安装和设置2.2 cpolar的安装和注册 3. 本地文件服务器的发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 数据共享作为和连接作为互联网的基础应用,不仅在商业和办公场景有广泛的应用…...
Kubernetes技术--k8s核心技术Pod
(1).概述 Pod 是 k8s 系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型。 k8s不会直接处理容器,而是 Pod,Pod 是由一个或多个 container 组成。 一个pod中的容器共享网络命名空间。 Pod是一个短暂存在的。 (2).为什么k8s中最小单元是…...

基于Springboot实现的Echarts图表
概述 ECharts是百度开源的一个前端组件。它是一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等)&…...
adb server version (41) doesn‘t match this client (39)
异常: adb server version (41) doesnt match this client (39); killing... ADB server didnt ACK安装ADB后:查看版本 $ adb version Android Debug Bridge version 1.0.39 Version 1:8.1.1-1r23-5.4-1eagle Installed as /usr/lib/android-sdk/platf…...

B080-RabbitMQ
目录 RabbitMQ认识概念使用场景优点AMQP协议JMS RabbitMQ安装安装elang安装RabbitMQ安装管理插件登录RabbitMQ消息队列的工作流程 RabbitMQ常用模型HelloWorld-基本消息模型生产者发送消息导包获取链接工具类消息的生产者 消费者消费消息模拟消费者手动签收消息 Work QueuesSen…...

关于岛屿的三道leetcode原题:岛屿周长、岛屿数量、统计子岛屿
题1:岛屿周长 给定一个 row x col 的二维网格地图 grid ,其中:gridi 1 表示陆地, gridi 0 表示水域。 网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...