import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.http.HttpMethod;
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.RequestMappingHandlerMapping;
import java.util.List;
import java.util.Map;
/
* 测试Spring MVC中的DispatcherServlet
*
* @author lzlg
* 2023/3/15 20:08
*/
public class TestSpringDispatcherServlet {
public static void main(String[] args) throws Exception {
AnnotationConfigServletWebServerApplicationContext context
= new AnnotationConfigServletWebServerApplicationContext(MyWebConfig.class);
// handlerMapping处理请求路径和处理请求的处理器的映射关系,初始化就创建了映射关系
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取请求路径和请求处理方法的结果Map
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k, v) -> {
System.out.println(k + "==" + v);
});
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.PUT.name(), "/test4");
request.addParameter("name", "李四");
request.addHeader("token", "用户令牌");
MockHttpServletResponse response = new MockHttpServletResponse();
// 获取调用链,不仅有HandlerMethod,还有拦截器等等
HandlerExecutionChain chain = handlerMapping.getHandler(request);
// System.out.println(chain);
System.out.println("<><><><><><><><><><><><><><><><><><><><><><>");
// 处理器适配器,用于调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
// 调用控制器的方法
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
String content = response.getContentAsString();
System.out.println(content);
}
/
* 测试参数解析器和返回值解析器
*/
private static void testArgAndReturnResolver(MyRequestMappingHandlerAdapter handlerAdapter) {
// 参数解析器
System.out.println("<><><><><><><><><><><><><><><><><><><><><><> 所有的参数解析器: ");
List<HandlerMethodArgumentResolver> argumentResolvers = handlerAdapter.getArgumentResolvers();
for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {
System.out.println(argumentResolver);
}
System.out.println("<><><><><><><><><><><><><><><><><><><><><><> 所有的返回值解析器: ");
List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
System.out.println(returnValueHandler);
}
}
}
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.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.Collections;
/
* @author lzlg
* 2023/3/15 20:09
*/
@Configuration
@ComponentScan
// 导入配置文件
@PropertySource("classpath:application.properties")
// 启用配置类
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class MyWebConfig {
// 1.注册Servlet容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
return new TomcatServletWebServerFactory(serverProperties.getPort());
}
// 2.注册DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
// 3.关联DispatcherServlet和Servlet容器
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(
dispatcherServlet, "/");
// 设置是否直接加载DispatcherServlet,默认值是-1(懒加载)
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registrationBean;
}
/
* RequestMappingHandlerMapping用于生成路径和请求处理器的映射关系
*/
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
// 设置自定义参数解析器
TokenArgumentResolver argumentResolver = new TokenArgumentResolver();
handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(argumentResolver));
// 设置自定义返回值解析器
YamlReturnValueResolver returnValueResolver = new YamlReturnValueResolver();
handlerAdapter.setCustomReturnValueHandlers(Collections.singletonList(returnValueResolver));
return handlerAdapter;
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
/
* @author lzlg
* 2023/3/15 20:54
*/
@Slf4j
@Controller
public class TestController {
@GetMapping("/test1")
public ModelAndView test1() {
log.info("test1()");
return null;
}
@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
log.info("test2({})", name);
return null;
}
@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
log.info("test3({})", token);
return null;
}
@RequestMapping("/test4")
@Yaml
public User test4() {
log.info("test4()");
return new User("张三", 32);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class User {
private String name;
private Integer age;
}
}
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/
* 自定义请求处理适配器
*
* @author lzlg
* 2023/3/15 21:13
*/
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
@Override
public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
return super.invokeHandlerMethod(request, response, handlerMethod);
}
}
自定义参数和返回值解析器:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/
* 自定义注解
*
* @author lzlg
* 2023/3/15 21:20
*/
@Target(value = ElementType.PARAMETER)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Token {
}
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;
/
* 自定义参数解析器,需将自定义的参数解析器加入到HandlerAdapter中
*
* @author lzlg
* 2023/3/15 21:26
*/
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 {
// 直接从请求头中获取token信息
return webRequest.getHeader("token");
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/
* @author lzlg
* 2023/3/15 21:46
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Yaml {
}
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 javax.servlet.http.HttpServletResponse;
/
* Yaml返回值解析器
*
* @author lzlg
* 2023/3/15 21:47
*/
public class YamlReturnValueResolver implements HandlerMethodReturnValueHandler {
/
* 判断是否进行返回值解析
*/
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Yaml yaml = returnType.getMethodAnnotation(Yaml.class);
return yaml != null;
}
/
* 进行返回值解析
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 1.对返回值进行处理为yaml格式字符串
String str = new org.yaml.snakeyaml.Yaml().dump(returnValue);
// 2.写入响应中
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType("text/plain;charset=utf-8");
response.getWriter().println(str);
// 3.通知MVC不再进行视图解析
mavContainer.setRequestHandled(true);
}
}
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
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.Collections;
import java.util.Map;
import java.util.stream.Collectors;
/
* 测试Spring MVC中Controller上的参数主键处理器
*
* @author lzlg
* 2023/3/28 20:34
*/
public class TestSpringMVCArgumentResolver {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
HttpServletRequest request = mockRequest();
// 1.控制器方法被封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new TestController(),
TestController.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 binderFactory = new ServletRequestDataBinderFactory(null, null);
// 3.准备ModelAndViewContainer存储中间数据
ModelAndViewContainer container = new ModelAndViewContainer();
// 4.解析每个参数
for (MethodParameter methodParameter : handlerMethod.getMethodParameters()) {
// Spring中使用组合模式,把一系列的参数解析器组合进行调用
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// @RequestParam注解参数解析器
new RequestParamMethodArgumentResolver(
// 添加beanFactory才能使用其中的EL表达式解析
beanFactory,
// 如果为false,则必须有@RequestParam注解才解析
false),
// @PathVariable注解参数解析器
new PathVariableMethodArgumentResolver(),
// @RequestHeader注解参数解析器
new RequestHeaderMethodArgumentResolver(beanFactory),
// @CookieValue注解参数解析器
new ServletCookieValueMethodArgumentResolver(beanFactory),
// EL表达式解析器
new ExpressionValueMethodArgumentResolver(beanFactory),
// 特殊对象HttpServletRequest解析器
new ServletRequestMethodArgumentResolver(),
// @ModelAttribute注解解析器,如果为false则必须有@ModelAttribute注解才解析
new ServletModelAttributeMethodProcessor(false),
// @RequestBody注解解析器
new RequestResponseBodyMethodProcessor(Collections
.singletonList(new MappingJackson2HttpMessageConverter())),
// @ModelAttribute注解解析器,如果为true则没有@ModelAttribute注解也解析
// 注意顺序要在参数为false的之后,否则会覆盖@RequestBody注解解析器的解析结果
new ServletModelAttributeMethodProcessor(true),
// @RequestParam注解参数解析器
new RequestParamMethodArgumentResolver(
// 添加beanFactory才能使用其中的EL表达式解析
beanFactory,
// 如果为true,则没有@RequestParam注解也可以解析
// 注意顺序要在参数为false的之后
true)
);
// 解析参数名称需要解析器
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
// 解析参数上的注解数据(转换为字符串进行打印)
String annotations = Arrays.stream(methodParameter.getParameterAnnotations())
.map(a -> a.annotationType().getSimpleName())
.collect(Collectors.joining());
// 注解字符串
String annotationStr = annotations.length() > 0 ? " @" + annotations + " " : " ";
// 判断是否支持注解解析器
if (composite.supportsParameter(methodParameter)) {
// 解析出请求参数的值
Object arg = composite.resolveArgument(methodParameter,
// 存储解析结果的容器
container,
// 请求数据
new ServletWebRequest(request),
// 数据类型绑定解析工厂,将String类型转换为指定的类型比如age字段
binderFactory);
System.out.println("[" + methodParameter.getParameterIndex() + "] " + annotationStr
+ methodParameter.getParameterType().getSimpleName() + " "
+ methodParameter.getParameterName()
+ "=====>>>>>" + arg);
System.out.println("======>>>>>>>>>>模型数据有: " + container.getModel());
} else {
System.out.println("[" + methodParameter.getParameterIndex() + "] " + annotationStr
+ methodParameter.getParameterType().getSimpleName() + " "
+ methodParameter.getParameterName());
}
}
}
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");
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("{\n \"name\": \"李四\",\n \"age\": 20\n}".getBytes(StandardCharsets.UTF_8));
return new StandardServletMultipartResolver().resolveMultipart(request);
}
static class TestController {
public void test(@RequestParam("name1") String name1,
String name2,
@RequestParam("age") int age,
@RequestParam(name = "home1", defaultValue = "${JAVA_HOME}") String home1,
@RequestParam("file") MultipartFile file,
@PathVariable("id") int id,
@RequestHeader("Content-Type") String header,
@CookieValue("token") String token,
@Value("${JAVA_HOME}") String home2,
HttpServletRequest request,
@ModelAttribute("abc") User user1,
User user2,
@RequestBody User user3) {
}
}
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Configuration
static class WebConfig {
}
}
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Objects;
/
* 测试Spring如何获取参数名称
*
* @author lzlg
* 2023/3/29 21:11
*/
public class TestSpringGetArgumentName {
public static void main(String[] args) throws Exception {
// 1.通过反射进行获取,此时编译时需加上 -parameters 命令
// 反射只能获取Method Parameters中的参数名称
// javac -parameters TestBean.java
Method method = TestBean.class.getMethod("foo", String.class, int.class);
for (Parameter parameter : method.getParameters()) {
System.out.println(parameter.getName());
}
// 2.通过Spring提供的工具从LocalVariableTable中获取
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
if (Objects.nonNull(parameterNames)) {
for (String parameterName : parameterNames) {
System.out.println(parameterName);
}
}
}
}
/
* 接口的参数名称只能通过Method Parameter进行保存
*
* @author lzlg
* 2023/3/29 21:15
*/
public interface TestInterface {
public void foo(String name, int age);
}
/
* 1.将参数名称写入Method Parameter表中
* javac -parameters TestBean.java
* 2.将参数名称写入LocalVariableTable中
* javac -g TestBean.java
* 反编译
* javap -c -v TestBean.class
*
* @author lzlg
* 2023/3/29 21:13
*/
public class TestBean {
public void foo(String name, int age) {
}
}
import org.springframework.beans.SimpleTypeConverter;
import java.util.Date;
/
* 测试简单的类型转换器
*
* @author lzlg
* 2023/3/29 21:48
*/
public class TestSimpleConverter {
public static void main(String[] args) {
SimpleTypeConverter converter = new SimpleTypeConverter();
Integer number = converter.convertIfNecessary("13", int.class);
Date date = converter.convertIfNecessary("2020/10/01", Date.class);
System.out.println(number);
System.out.println(date);
}
}
import org.springframework.beans.BeanWrapperImpl;
import java.util.Date;
/
* 测试Bean包装器中的类型转换
* 适用反射调用get和set方法来实现
*
* @author lzlg
* 2023/3/29 21:51
*/
public class TestBeanWrapper {
public static void main(String[] args) {
MyBean bean = new MyBean();
BeanWrapperImpl wrapper = new BeanWrapperImpl(bean);
wrapper.setPropertyValue("a", "13");
wrapper.setPropertyValue("b", "hello");
wrapper.setPropertyValue("c", new Date() + "");
System.out.println(bean);
}
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;
}
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
import org.springframework.beans.DirectFieldAccessor;
import java.util.Date;
/
* 通过访问字段来实现类型转换
* 也是通过反射使用Field对象来实现,不用get,set方法
*
* @author lzlg
* 2023/3/29 21:55
*/
public class TestFieldAccessor {
public static void main(String[] args) {
MyBean bean = new MyBean();
DirectFieldAccessor accessor = new DirectFieldAccessor(bean);
accessor.setPropertyValue("a", "14");
accessor.setPropertyValue("b", "hello,world");
accessor.setPropertyValue("c", new Date().toString());
System.out.println(bean);
}
static class MyBean {
private int a;
private String b;
private Date c;
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import java.util.Date;
/
* 测试Web环境下的数据绑定
*
* @author lzlg
* 2023/3/29 22:04
*/
public class TestWebServletBinder {
public static void main(String[] args) {
MyBean bean = new MyBean();
ServletRequestDataBinder binder = new ServletRequestDataBinder(bean);
// 设置直接访问Field字段对象
// binder.initDirectFieldAccess();
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("a", "15");
request.addParameter("b", "world");
request.addParameter("c", "2023/02/22");
ServletRequestParameterPropertyValues mpv = new ServletRequestParameterPropertyValues(request);
binder.bind(mpv);
System.out.println(bean);
}
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;
}
@Override
public String toString() {
return "MyBean{" + "a=" + a + ", b='" + b + '\'' + ", c=" + c + '}';
}
}
}
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
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.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import java.util.Collections;
import java.util.Date;
/
* 测试Web环境下的绑定工厂
*
* @author lzlg
* 2023/3/29 22:22
*/
public class TestWebServletBinderFactory {
public static void main(String[] args) throws Exception {
// 被绑定对象
MyUser user = new MyUser();
// 模拟请求信息
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("date", "2022|10|09");
request.addParameter("address.name", "上海市青浦区");
// 1.使用转换器工厂
// testBinderFactory(user, request);
// 2.使用注解@InitBinder进行绑定
// testInitBinderAnnotation(user, request);
// 3.使用ConversionService进行绑定
// testConversionService(user, request);
// 4.同时使用注解@InitBinder和ConversionService,注解的优先级较高
// testBinderFirst(user, request);
// 5.使用默认的ConversionService,可解析@DateTimeFormat注解上标注的格式
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
DefaultFormattingConversionService service = new DefaultFormattingConversionService();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
binder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
/
* 同时使用注解@InitBinder和ConversionService,注解的优先级较高
*/
private static void testBinderFirst(MyUser user, MockHttpServletRequest request) throws Exception {
// 4.同时使用注解@InitBinder和ConversionService,注解的优先级较高
InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(),
MyController.class.getMethod("binder", WebDataBinder.class));
ApplicationConversionService service = new ApplicationConversionService();
service.addFormatter(new MyDateFormatter("SpringBoot用 ApplicationConversionService 进行绑定 ==>>> "));
// 初始化器
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(
Collections.singletonList(method),
initializer);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
binder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
/
* 使用ConversionService进行绑定
*/
private static void testConversionService(MyUser user, MockHttpServletRequest request) throws Exception {
// 3.使用ConversionService进行绑定
FormattingConversionService service = new FormattingConversionService();
// 如果是SpringBoot环境可使用ApplicationConversionService
// ApplicationConversionService service = new ApplicationConversionService();
// 添加自定义格式化器
service.addFormatter(new MyDateFormatter("用 ConversionService 进行绑定 ==>>> "));
// 初始化器
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
// 生成绑定对象
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
// 进行绑定
binder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
/
* 使用注解@InitBinder进行绑定
*/
private static void testInitBinderAnnotation(MyUser user, MockHttpServletRequest request) throws Exception {
// 2.使用注解@InitBinder进行绑定
InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(),
MyController.class.getMethod("binder", WebDataBinder.class));
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(
Collections.singletonList(method), null);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
// 进行绑定
binder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
/
* 使用转换器工厂
*/
private static void testBinderFactory(MyUser user, MockHttpServletRequest request) throws Exception {
// 1.使用转换器工厂
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
// 进行绑定
binder.bind(new ServletRequestParameterPropertyValues(request));
System.out.println(user);
}
static class MyController {
@InitBinder
public void binder(WebDataBinder binder) {
binder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 进行绑定 ==>>> "));
}
}
static class MyUser {
@DateTimeFormat(pattern = "yyyy|MM|dd")
private Date date;
private Address address;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "MyUser{" +
"date=" + date +
", address=" + address +
'}';
}
}
static class Address {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Address{" +
"name='" + name + '\'' +
'}';
}
}
}
import org.springframework.core.GenericTypeResolver;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/
* 测试Spring泛型API
*
* @author lzlg
* 2023/3/29 22:54
*/
public class TestSpringGeneric {
public static void main(String[] args) {
// 1.使用JDK的API
Type type = StudentDao.class.getGenericSuperclass();
System.out.println(type);
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.println(parameterizedType.getActualTypeArguments()[0]);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
// 2.使用Spring的API
Class<?> typeArgument = GenericTypeResolver.resolveTypeArgument(StudentDao.class, BaseDao.class);
System.out.println(typeArgument);
}
static class BaseDao<T> {
T find() {
return null;
}
}
static class StudentDao extends BaseDao<Student> {
}
static class Student {
}
}
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注解的时机
*
* @author lzlg
* 2023/3/29 23:15
*/
public class TestInitBinderController {
private static final Logger log = LoggerFactory.getLogger(TestInitBinderController.class);
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
// 请求映射处理器适配器
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setApplicationContext(context);
handlerAdapter.afterPropertiesSet();
System.out.println("1.系统初始化...");
showBindMethods(handlerAdapter);
System.out.println("2.请求控制器Controller1的foo方法");
Method method = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory",
HandlerMethod.class);
method.setAccessible(true);
method.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(),
WebConfig.Controller1.class.getMethod("foo")));
showBindMethods(handlerAdapter);
System.out.println("3.请求控制器Controller2的bar方法");
method.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(),
WebConfig.Controller2.class.getMethod("bar")));
showBindMethods(handlerAdapter);
context.close();
}
@SuppressWarnings("all")
private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException,
IllegalAccessException {
// ControllerAdvice中的InitBinder缓存
Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
initBinderAdviceCache.setAccessible(true);
Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>)
initBinderAdviceCache.get(handlerAdapter);
System.out.printf("全局的 @InitBinder 方法 %s\n",
globalMap.values().stream()
.flatMap(ms -> ms.stream().map(m -> m.getName()))
.collect(Collectors.toList())
);
// Controller中的InitBinder缓存
Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
initBinderCache.setAccessible(true);
Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>)
initBinderCache.get(handlerAdapter);
System.out.printf("控制器的 @InitBinder 方法 %s\n",
controllerMap.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(v -> e.getKey().getSimpleName() + "." + v.getName()))
.collect(Collectors.toList())
);
}
}
import com.lzlg.study.spring.converter.MyDateFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
/
* @author lzlg
* 2023/3/29 23:09
*/
@Configuration
public class WebConfig {
/
* 注解@ControllerAdvice上标注的@InitBinder是全局的绑定器
*/
@ControllerAdvice
static class MyControllerAdvice {
@InitBinder
public void binder(WebDataBinder dataBinder) {
dataBinder.addCustomFormatter(new MyDateFormatter(" ControllerAdvice @InitBinder 转换器"));
}
}
/
* 注解@Controller上标注的@InitBinder只在当前Controller中生效
*/
@Controller
static class Controller1 {
@InitBinder
public void binder(WebDataBinder dataBinder) {
dataBinder.addCustomFormatter(new MyDateFormatter(" Controller1 @InitBinder 转换器"));
}
public void foo() {
}
}
@Controller
static class Controller2 {
@InitBinder
public void binder1(WebDataBinder dataBinder) {
dataBinder.addCustomFormatter(new MyDateFormatter(" Controller2 @InitBinder binder1 转换器"));
}
@InitBinder
public void binder2(WebDataBinder dataBinder) {
dataBinder.addCustomFormatter(new MyDateFormatter(" Controller2 @InitBinder binder2 转换器"));
}
public void bar() {
}
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.Formatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/
* 自定义日期格式转换器
*
* @author lzlg
* 2023/3/29 22:22
*/
@Slf4j
public class MyDateFormatter implements Formatter<Date> {
private final String desc;
public MyDateFormatter(String desc) {
this.desc = desc;
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
log.info("{} 解析日期: {}", desc, text);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(text);
}
@Override
public String print(Date object, Locale locale) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
log.info("{} 转换日期: {}", desc, object);
return sdf.format(object);
}
}
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
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.web.bind.support.WebDataBinderFactory;
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.ModelFactory;
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.servlet.mvc.method.annotation.*;
import java.lang.reflect.Method;
import java.util.Collections;
/
* 测试Spring中MVC的handleMethod执行流程
*
* @author lzlg
* 2023/3/30 21:07
*/
public class TestSpringHandleMethod {
public static void main(String[] args) throws Exception {
// 模拟请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("name", "张三");
// 容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 请求映射处理适配器,能解析@ModelAttribute注解,并将返回数据放入ModelAndViewContainer中
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setApplicationContext(context);
handlerAdapter.afterPropertiesSet();
// 控制器方法调用对象
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
new WebConfig.TestController(),
WebConfig.TestController.class.getMethod("foo", WebConfig.User.class)
);
// 设置参数解析器
handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(beanFactory));
// 绑定数据类型转换器
ServletRequestDataBinderFactory binderFactory = new ServletRequestDataBinderFactory(null, null);
handlerMethod.setDataBinderFactory(binderFactory);
// 控制器方法参数名称解析器
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
// ModelAndView容器,存储请求中的一些对象
ModelAndViewContainer container = new ModelAndViewContainer();
// ==========RequestMappingHandlerAdapter===========
// 获取模型工厂方法
Method getModelFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod(
"getModelFactory", HandlerMethod.class, WebDataBinderFactory.class);
getModelFactory.setAccessible(true);
ModelFactory modelFactory = (ModelFactory) getModelFactory.invoke(handlerAdapter,
handlerMethod, binderFactory);
// 初始化模型数据,将解析@ModelAttribute中的注解
modelFactory.initModel(new ServletWebRequest(request), container, handlerMethod);
// 调用控制器方法
handlerMethod.invokeAndHandle(new ServletWebRequest(request), container);
// 打印模型数据
System.out.println(container.getModel());
context.close();
}
/
* 常见参数解析器组合
*/
private static HandlerMethodArgumentResolverComposite getArgumentResolvers(DefaultListableBeanFactory beanFactory) {
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
// @RequestParam注解参数解析器,如果为false,则必须有@RequestParam注解才解析
new RequestParamMethodArgumentResolver(beanFactory, false),
// @PathVariable注解参数解析器
new PathVariableMapMethodArgumentResolver(),
// @RequestHeader注解参数解析器
new RequestHeaderMethodArgumentResolver(beanFactory),
// @CookieValue注解参数解析器
new ServletCookieValueMethodArgumentResolver(beanFactory),
// EL表达式解析器
new ExpressionValueMethodArgumentResolver(beanFactory),
// 特殊对象HttpServletRequest解析器
new ServletRequestMethodArgumentResolver(),
// @ModelAttribute注解解析器,如果为false则必须有@ModelAttribute注解才解析
new ServletModelAttributeMethodProcessor(false),
// @RequestBody注解解析器
new RequestResponseBodyMethodProcessor(Collections
.singletonList(new MappingJackson2HttpMessageConverter())),
// @ModelAttribute注解解析器,如果为true则没有@ModelAttribute注解也解析
new ServletModelAttributeMethodProcessor(true),
// 如果为true,则没有@RequestParam注解也可以解析
new RequestParamMethodArgumentResolver(beanFactory, true)
);
return composite;
}
}
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;
/
* @author lzlg
* 2023/3/30 21:04
*/
@Configuration
public class WebConfig {
@Controller
static class TestController {
@ModelAttribute("hello")
public String hello() {
return "hello";
}
@ResponseStatus(HttpStatus.OK)
public ModelAndView foo(@ModelAttribute("u") User user) {
System.out.println("TestController foo()....");
return null;
}
}
@ControllerAdvice
static class TestControllerAdvice {
@ModelAttribute("test")
public String test() {
return "test";
}
}
static class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
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.Collections;
import java.util.Locale;
/
* 测试Spring MVC中对返回值的处理器
*
* @author lzlg
* 2023/3/30 21:55
*/
public class TestSpringReturnValueHandler {
/
* 返回值解析器组合
*/
private static HandlerMethodReturnValueHandlerComposite returnValueHandlerComposite() {
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
// 解析返回ModelAndView返回值的处理器
composite.addHandler(new ModelAndViewMethodReturnValueHandler());
// 解析返回String视图的处理器
composite.addHandler(new ViewNameMethodReturnValueHandler());
// 解析注解@ModeAndView的返回值处理器,如果为false,则必须有@ModelAttribute注解
composite.addHandler(new ServletModelAttributeMethodProcessor(false));
// 解析返回值是HttpEntity的处理器
composite.addHandler(new HttpEntityMethodProcessor(Collections
.singletonList(new MappingJackson2HttpMessageConverter())));
// 解析返回值是HttpHeaders的处理器
composite.addHandler(new HttpHeadersReturnValueHandler());
// 解析@ResponseBody注解返回json数据的处理器
composite.addHandler(new RequestResponseBodyMethodProcessor(Collections
.singletonList(new MappingJackson2HttpMessageConverter())));
// 解析注解@ModeAndView的返回值处理器,如果为true,则可以没有@ModelAttribute注解
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
public static void main(String[] args) throws Exception {
// Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FreeMarkerConfig.class);
// 1.测试解析ModelAndView返回值
// testModelAndView(context);
// 2.测试返回值是String(视图)
// testStringView(context);
// 3.测试@ModelAttribute注解返回值
// testModelAttribute(context);
// 4.测试没有@ModelAttribute注解返回值
// testNoModelAttribute(context);
// 5.测试HttpEntity返回值
// testHttpEntity(context);
// 6.测试HttpHeaders返回值
// testHttpHeaders(context);
// 7.测试@ResponseBody返回值
testResponseBody(context);
context.close();
}
/
* 6.测试HttpHeaders返回值
*/
private static void testResponseBody(AnnotationConfigApplicationContext context) throws Exception {
// 控制器对象
ReturnController controller = new ReturnController();
// 请求方法
Method method = ReturnController.class.getMethod("test7");
// 反射调用生成返回值
Object returnValue = method.invoke(controller);
// 请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
// 响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 封装请求和响应信息
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 将对控制器方法的请求封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
// 模型和视图容器
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取返回值解析器组合
HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
// 是否支持返回的类型
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue,
handlerMethod.getReturnType(),
container,
webRequest);
System.out.println("模型数据: " + container.getModel());
System.out.println("视图名称: " + container.getViewName());
// 返回值处理器通过设置requestHandled为true来表示请求已处理完成,不再进行视图渲染
if (container.isRequestHandled()) {
for (String headerName : response.getHeaderNames()) {
System.out.println(headerName + " = " + response.getHeader(headerName));
}
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
} else {
renderView(context, container, webRequest);
}
}
}
/
* 6.测试HttpHeaders返回值
*/
private static void testHttpHeaders(AnnotationConfigApplicationContext context) throws Exception {
// 控制器对象
ReturnController controller = new ReturnController();
// 请求方法
Method method = ReturnController.class.getMethod("test6");
// 反射调用生成返回值
Object returnValue = method.invoke(controller);
// 请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
// 响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 封装请求和响应信息
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 将对控制器方法的请求封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
// 模型和视图容器
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取返回值解析器组合
HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
// 是否支持返回的类型
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue,
handlerMethod.getReturnType(),
container,
webRequest);
System.out.println("模型数据: " + container.getModel());
System.out.println("视图名称: " + container.getViewName());
// 返回值处理器通过设置requestHandled为true来表示请求已处理完成,不再进行视图渲染
if (container.isRequestHandled()) {
for (String headerName : response.getHeaderNames()) {
System.out.println(headerName + " = " + response.getHeader(headerName));
}
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
} else {
renderView(context, container, webRequest);
}
}
}
/
* 5.测试HttpEntity返回值
*/
private static void testHttpEntity(AnnotationConfigApplicationContext context) throws Exception {
// 控制器对象
ReturnController controller = new ReturnController();
// 请求方法
Method method = ReturnController.class.getMethod("test5");
// 反射调用生成返回值
Object returnValue = method.invoke(controller);
// 请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
// 响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 封装请求和响应信息
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 将对控制器方法的请求封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
// 模型和视图容器
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取返回值解析器组合
HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
// 是否支持返回的类型
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue,
handlerMethod.getReturnType(),
container,
webRequest);
System.out.println("模型数据: " + container.getModel());
System.out.println("视图名称: " + container.getViewName());
// 返回值处理器通过设置requestHandled为true来表示请求已处理完成,不再进行视图渲染
if (container.isRequestHandled()) {
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
} else {
renderView(context, container, webRequest);
}
}
}
/
* 4.测试没有@ModelAttribute注解时返回值
*/
private static void testNoModelAttribute(AnnotationConfigApplicationContext context) throws Exception {
// 控制器对象
ReturnController controller = new ReturnController();
// 请求方法
Method method = ReturnController.class.getMethod("test4");
// 反射调用生成返回值
Object returnValue = method.invoke(controller);
// 请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
// 设置请求路径,请求路径映射到视图上
request.setRequestURI("/test4");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
// 响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 封装请求和响应信息
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 将对控制器方法的请求封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
// 模型和视图容器
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取返回值解析器组合
HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
// 是否支持返回的类型
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue,
handlerMethod.getReturnType(),
container,
webRequest);
System.out.println("模型数据: " + container.getModel());
System.out.println("视图名称: " + container.getViewName());
renderView(context, container, webRequest);
}
}
/
* 3.测试@ModelAttribute注解返回值
*/
private static void testModelAttribute(AnnotationConfigApplicationContext context) throws Exception {
// 控制器对象
ReturnController controller = new ReturnController();
// 请求方法
Method method = ReturnController.class.getMethod("test3");
// 反射调用生成返回值
Object returnValue = method.invoke(controller);
// 请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
// 设置请求路径,请求路径映射到视图上
request.setRequestURI("/test3");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
// 响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 封装请求和响应信息
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 将对控制器方法的请求封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
// 模型和视图容器
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取返回值解析器组合
HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
// 是否支持返回的类型
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue,
handlerMethod.getReturnType(),
container,
webRequest);
System.out.println("模型数据: " + container.getModel());
System.out.println("视图名称: " + container.getViewName());
renderView(context, container, webRequest);
}
}
/
* 2.测试返回值是String(视图)
*/
private static void testStringView(AnnotationConfigApplicationContext context) throws Exception {
// 控制器对象
ReturnController controller = new ReturnController();
// 请求方法
Method method = ReturnController.class.getMethod("test2");
// 反射调用生成返回值
Object returnValue = method.invoke(controller);
// 请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
// 响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 封装请求和响应信息
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 将对控制器方法的请求封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
// 模型和视图容器
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取返回值解析器组合
HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
// 是否支持返回的类型
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue,
handlerMethod.getReturnType(),
container,
webRequest);
System.out.println("模型数据: " + container.getModel());
System.out.println("视图名称: " + container.getViewName());
renderView(context, container, webRequest);
}
}
/
* 1.测试解析ModelAndView返回值
*/
private static void testModelAndView(AnnotationConfigApplicationContext context) throws Exception {
// 控制器对象
ReturnController controller = new ReturnController();
// 请求方法
Method method = ReturnController.class.getMethod("test1");
// 反射调用生成返回值
Object returnValue = method.invoke(controller);
// 请求对象
MockHttpServletRequest request = new MockHttpServletRequest();
// 响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 封装请求和响应信息
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 将对控制器方法的请求封装为HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(controller, method);
// 模型和视图容器
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取返回值解析器组合
HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
// 是否支持返回的类型
if (composite.supportsReturnType(handlerMethod.getReturnType())) {
composite.handleReturnValue(returnValue,
handlerMethod.getReturnType(),
container,
webRequest);
System.out.println("模型数据: " + container.getModel());
System.out.println("视图名称: " + container.getViewName());
renderView(context, container, webRequest);
}
}
/
* FreeMarker视图渲染
*/
@SuppressWarnings("all")
private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
ServletWebRequest webRequest) throws Exception {
System.out.println(">>>>>> 渲染视图");
FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
String viewName = container.getViewName() != null ? container.getViewName() :
new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
System.out.println("没有获取到视图名, 采用默认视图名: " + viewName);
// 每次渲染时,会产生新的视图对象,它并非被 Spring 所管理,但确实借助了Spring容器来执行初始化
View view = resolver.resolveViewName(viewName, Locale.getDefault());
// 进行渲染
view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
// 打印渲染后的html内容
System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse())
.getContentAsByteArray(),
StandardCharsets.UTF_8));
}
static class ReturnController {
// 1.测试ModelAndView
public ModelAndView test1() {
System.out.println("ReturnController test1()...");
ModelAndView view = new ModelAndView("view1");
view.addObject("name", "张三");
return view;
}
// 2.测试直接返回视图名
public String test2() {
System.out.println("ReturnController test2()...");
return "view2";
}
// 3.测试@ModelAttribute,有模型数据,视图通过@RequestMapping(请求路径)来确定
@ModelAttribute("user")
public User test3() {
System.out.println("ReturnController test3()...");
return new User("李四", 32);
}
// 4.测试默认的,走@ModelAttribute逻辑
public User test4() {
System.out.println("ReturnController test4()...");
return new User("王五", 18);
}
// 5.直接返回数据HttpEntity
public HttpEntity<User> test5() {
System.out.println("ReturnController test5()...");
return new HttpEntity<>(new User("赵六", 21));
}
// 6.直接返回数据HttpHeaders
public HttpHeaders test6() {
System.out.println("ReturnController test6()...");
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "text/html");
return headers;
}
// 6.直接返回Json数据@ResponseBody
@ResponseBody
public User test7() {
return new User("钱七", 27);
}
}
public static class User {
private String name;
private int age;
public User() {
}
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
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;
import java.nio.charset.StandardCharsets;
/
* @author lzlg
* 2023/3/30 21:58
*/
@Configuration
public class FreeMarkerConfig {
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setDefaultEncoding(StandardCharsets.UTF_8.name());
configurer.setTemplateLoaderPath("classpath:templates");
return configurer;
}
@Bean
public FreeMarkerViewResolver freeMarkerViewResolver(FreeMarkerConfigurer freeMarkerConfigurer) {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() {
@Override
protected AbstractUrlBasedView instantiateView() {
FreeMarkerView view = new FreeMarkerView() {
@Override
protected boolean isContextRequired() {
return false;
}
};
view.setConfiguration(freeMarkerConfigurer.getConfiguration());
return view;
}
};
resolver.setContentType("text/html;charset=utf-8");
resolver.setPrefix("/");
resolver.setSuffix(".ftl");
resolver.setExposeSpringMacroHelpers(false);
return resolver;
}
}
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.MediaType;
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.Objects;
import java.util.stream.Collectors;
/
* 测试Spring MVC中消息做统一处理
*
* @author lzlg
* 2023/3/31 19:42
*/
public class TestSpringMessageAdvice {
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("test", WebConfig.User.class)
);
// 设置数据绑定转换工厂
handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(), null));
// 设置参数名称查找器
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
// 设置请求参数解析器
handlerMethod.setHandlerMethodArgumentResolvers(argumentResolverComposite(context));
// 设置返回值处理器
handlerMethod.setHandlerMethodReturnValueHandlers(returnValueHandlerComposite(context));
// 模拟请求
MockHttpServletRequest request = new MockHttpServletRequest();
String json = "{\n" +
" \"method\": \"test\",\n" +
" \"LocalDateTime\": \"2022-03-31 20:43:00\",\n" +
" \"requestId\": \"1234567890\",\n" +
" \"data\": {\n" +
" \"name\": \"王五\",\n" +
" \"age\": 20\n" +
" }\n" +
"}";
// 模拟Json
request.setContent(json.getBytes(StandardCharsets.UTF_8));
request.setContentType(MediaType.APPLICATION_JSON_VALUE);
// 模拟响应
MockHttpServletResponse response = new MockHttpServletResponse();
// ModelAndView容器
ModelAndViewContainer container = new ModelAndViewContainer();
// Web请求响应封装
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 调用控制器方法,进行请求处理
handlerMethod.invokeAndHandle(webRequest, container);
// 打印响应结果
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
context.close();
}
/
* 设置常见的参数解析器组合
*/
private static HandlerMethodArgumentResolverComposite argumentResolverComposite(AnnotationConfigApplicationContext context) {
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 添加请求处理advice
List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);
List<Object> requestAdvices = annotatedBeans.stream()
.filter(b -> RequestBodyAdvice.class.isAssignableFrom(Objects.requireNonNull(b.getBeanType())))
.collect(Collectors.toList());
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
new RequestParamMethodArgumentResolver(beanFactory, false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false),
new RequestResponseBodyMethodProcessor(Collections
.singletonList(new MappingJackson2HttpMessageConverter()), requestAdvices),
new ServletModelAttributeMethodProcessor(true),
new RequestParamMethodArgumentResolver(beanFactory, true)
);
return composite;
}
/
* 设置常见的返回值处理器组合
*/
private static HandlerMethodReturnValueHandlerComposite returnValueHandlerComposite(AnnotationConfigApplicationContext context) {
// 添加响应advice
List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);
List<Object> responseAdvices = annotatedBeans.stream()
.filter(b -> ResponseBodyAdvice.class.isAssignableFrom(Objects.requireNonNull(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(Collections
.singletonList(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new HttpHeadersReturnValueHandler());
composite.addHandler(new RequestResponseBodyMethodProcessor(Collections
.singletonList(new MappingJackson2HttpMessageConverter()), responseAdvices));
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
}
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.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.Arrays;
/
* 测试Spring MVC中的消息转换器
*
* @author lzlg
* 2023/3/31 19:01
*/
public class TestSpringMVCMessageConverter {
public static void main(String[] args) throws Exception {
// 测试Json消息格式的消息转换器
// testJsonConverter();
// 测试XML消息格式的消息转换器
// testXmlConverter();
// 测试输入时的消息转换器
// testInputMessageConverter();
// 测试消息转换器的优先级
testMessageConverterRank();
}
/
* 测试消息转换器的优先级,默认按照添加的顺序指定
* 优先: 如果响应指定Content-Type,则按照Content-Type进行转换,同@RequestMapping中使用produces
* 其次: 如果请求头Accept指定,则按照Accept进行转换
* 最后: 按照转换器添加的顺序指定
*/
private static void testMessageConverterRank() throws Exception {
// 模拟请求
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "application/xml");
// 模拟响应
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentType("application/json");
// Web请求封装
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 请求体和响应体处理器
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
Arrays.asList(
new MappingJackson2HttpMessageConverter(),
new MappingJackson2XmlHttpMessageConverter()
)
);
// 处理返回值
processor.handleReturnValue(
new User("赵六", 25),
new MethodParameter(TestSpringMVCMessageConverter.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 testInputMessageConverter() throws IOException {
// 创建模拟输入消息
MockHttpInputMessage message = new MockHttpInputMessage(
"{\n\"name\": \"王五\",\n\"age\": 20\n}".getBytes(StandardCharsets.UTF_8));
// 消息转换器
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 查看是否支持读取数据
if (converter.canRead(User.class, MediaType.APPLICATION_JSON)) {
// 进行读取
User user = (User) converter.read(User.class, message);
System.out.println(user);
}
}
/
* 测试XML消息格式的消息转换器
*/
private static void testXmlConverter() throws IOException {
// 模拟Http输出消息
MockHttpOutputMessage message = new MockHttpOutputMessage();
// 转换为XML格式的消息转换器
MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
// 判断能够输出为XML格式
if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {
converter.write(
new User("李四", 15),
MediaType.APPLICATION_XML,
message
);
System.out.println(message.getBodyAsString());
}
}
/
* 测试Json消息格式的消息转换器
*/
private static void testJsonConverter() throws IOException {
// 模拟Http输出消息
MockHttpOutputMessage message = new MockHttpOutputMessage();
// 转换为json格式的消息转换器
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 判断能否输出
if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {
converter.write(
new User("张三", 12),
MediaType.APPLICATION_JSON,
message
);
System.out.println(message.getBodyAsString());
}
}
static class User {
private String name;
private int age;
@JsonCreator
public 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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
import com.alibaba.fastjson.JSON;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
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.mock.http.MockHttpInputMessage;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
/
* @author lzlg
* 2023/3/31 19:42
*/
@Configuration
public class WebConfig {
/
* 定义请求消息处理
*/
@ControllerAdvice
public static class MyRequestAdvice implements RequestBodyAdvice {
/
* 是否进行请求信息的处理
*/
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
if (methodParameter.hasParameterAnnotation(RequestBody.class)) {
System.out.println("方法参数上有@RequestBoday注解...");
return true;
}
return false;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
System.out.println(inputMessage.getClass());
try (InputStream is = inputMessage.getBody()) {
byte[] bytes = new byte[is.available()];
int read = is.read(bytes, 0, bytes.length);
System.out.println("read===>" + read);
String content = new String(bytes, StandardCharsets.UTF_8);
System.out.println(content);
Result<?> result = JSON.parseObject(content, Result.class);
return new MockHttpInputMessage(JSON.toJSONString(result.getData()).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
System.out.println("afterBodyRead========>>>>>>>>>" + body);
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
/
* 自定义响应处理器
*/
@ControllerAdvice
public static class MyResponseAdvice implements ResponseBodyAdvice<Object> {
/
* 是否进行响应消息处理,返回true则进行消息处理
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 情况1: 请求方法上有注解@ResponseBody
if (returnType.hasMethodAnnotation(ResponseBody.class)
// 情况2: 控制器方法上有注解@ResponseBody
|| returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)
// 情况3: 使用组合注解(组合注解中包含@ResponseBody)@RestController
|| AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) {
System.out.println("进行响应消息的处理.....");
return true;
}
return false;
}
/
* 进行消息处理
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
System.out.println("返回结果已经是Result类型");
return body;
}
return Result.ok(body);
}
}
/
* 控制器
*/
// @Controller
// @ResponseBody
@RestController
public static class MyController {
// @ResponseBody
public User user() {
return new User("张三", 23);
}
public Result<User> test(@RequestBody User user) {
System.out.println("解析请求信息信息成功..." + user);
return Result.ok(user);
}
}
public static class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", 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;
}
}
}
/
* 统一返回结果
*
* @author lzlg
* 2023/3/31 19:42
*/
public class Result<T> {
private int code;
private String message;
private T data;
public Result() {
}
private Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> ok(T data) {
return new Result<>(200, "ok", data);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
import java.time.LocalDateTime;
import java.util.UUID;
/
* 统一请求结果
*
* @author lzlg
* 2023/3/31 19:43
*/
public class Request<T> {
private String method;
private LocalDateTime requestTime;
private String requestId;
private T data;
private Request(String method, LocalDateTime requestTime, String requestId, T data) {
this.method = method;
this.requestTime = requestTime;
this.requestId = requestId;
this.data = data;
}
public static <T> Request<T> build(T data) {
return new Request<>("method",
LocalDateTime.now(),
UUID.randomUUID().toString(),
data);
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public LocalDateTime getRequestTime() {
return requestTime;
}
public void setRequestTime(LocalDateTime requestTime) {
this.requestTime = requestTime;
}
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
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 java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/
* 测试Spring MVC中的异常处理器
*
* @author lzlg
* 2023/3/31 21:36
*/
public class TestSpringExceptionResolver {
public static void main(String[] args) throws Exception {
// 异常处理器
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
resolver.afterPropertiesSet();
// 1.测试解析JSON的异常处理器
// testJsonExceptionHandler(resolver);
// 2.测试解析ModelAndView的异常处理器
// testModelAndViewExceptionHandler(resolver);
// 3.测试嵌套异常
// testNestExceptionHandler(resolver);
// 4.测试参数解析
testExceptionArgumentResolver(resolver);
}
/
* 测试参数解析
*/
private static void testExceptionArgumentResolver(ExceptionHandlerExceptionResolver resolver) throws NoSuchMethodException {
// 模拟请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 模拟响应
MockHttpServletResponse response = new MockHttpServletResponse();
// 请求方法
HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
// 进行异常嵌套
Exception e = new RuntimeException("运行时", new IOException("IO异常", new ArithmeticException("算术异常")));
// 嵌套中的出现的异常都能进行相应的处理
resolver.resolveException(request, response, handlerMethod, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
/
* 测试嵌套异常
*/
private static void testNestExceptionHandler(ExceptionHandlerExceptionResolver resolver) throws NoSuchMethodException {
// 模拟请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 模拟响应
MockHttpServletResponse response = new MockHttpServletResponse();
// 请求方法
HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
// 进行异常嵌套
Exception e = new RuntimeException("运行时", new IOException("IO异常", new ArithmeticException("算术异常")));
// 嵌套中的出现的异常都能进行相应的处理
resolver.resolveException(request, response, handlerMethod, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
/
* 测试解析ModelAndView的异常处理器
*/
private static void testModelAndViewExceptionHandler(ExceptionHandlerExceptionResolver resolver)
throws NoSuchMethodException {
// 模拟请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 模拟响应
MockHttpServletResponse response = new MockHttpServletResponse();
// 请求方法
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());
}
/
* 测试异常处理器
*/
private static void testJsonExceptionHandler(ExceptionHandlerExceptionResolver resolver)
throws NoSuchMethodException {
// 模拟请求
MockHttpServletRequest request = new MockHttpServletRequest();
// 模拟响应
MockHttpServletResponse response = new MockHttpServletResponse();
// 请求方法
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));
}
static class Controller1 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> test(ArithmeticException e) {
Map<String, Object> map = new HashMap<>(1);
map.put("error1", e.getMessage());
return map;
}
}
static class Controller2 {
public void foo() {
}
@ExceptionHandler
public ModelAndView test(ArithmeticException e) {
Map<String, Object> map = new HashMap<>(1);
map.put("error2", e.getMessage());
return new ModelAndView("view1", map);
}
}
static class Controller3 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> test(ArithmeticException e) {
Map<String, Object> map = new HashMap<>(1);
map.put("error3", e.getMessage());
return map;
}
}
static class Controller4 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> test(ArithmeticException e, HttpServletRequest request) {
System.out.println("解析到request参数: " + request);
Map<String, Object> map = new HashMap<>(1);
map.put("error4", e.getMessage());
return map;
}
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import java.nio.charset.StandardCharsets;
/
* 测试Spring MVC中全局异常处理
*
* @author lzlg
* 2023/3/31 21:55
*/
public class TestSpringExceptionHandlerAdvice {
public static void main(String[] args) throws Exception {
// 容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExceptionWebConfig.class);
// 从容器中获取异常解析处理器
ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);
// 模拟请求/响应
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
// 控制器方法
HandlerMethod handlerMethod = new HandlerMethod(new TestController(), TestController.class.getMethod("foo"));
Exception e = new Exception("hello异常");
resolver.resolveException(request, response, handlerMethod, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
context.close();
}
static class TestController {
public void foo() {
}
}
}
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.Collections;
import java.util.HashMap;
import java.util.Map;
/
* @author lzlg
* 2023/3/31 21:55
*/
@Configuration
public class ExceptionWebConfig {
@ControllerAdvice
public static class ExceptionHandlerControllerAdvice {
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(Exception e) {
Map<String, Object> map = new HashMap<>(1);
map.put("exception", e.getMessage());
return map;
}
}
// 注册全局异常处理解析器
@Bean
public ExceptionHandlerExceptionResolver exceptionResolver() {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
// 不需要调用afterPropertiesSet方法,因为ExceptionHandlerExceptionResolver实现了InitialingBean接口
return resolver;
}
}
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/
* 测试Spring Tomcat容器异常处理
*
* @author lzlg
* 2023/4/2 20:46
*/
public class TestSpringTomcatException {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(TomcatWebConfig.class);
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
mapping.getHandlerMethods().forEach(((RequestMappingInfo k, HandlerMethod v) ->
System.out.println("映射路径: " + k + "\t方法信息: " + v)
));
}
}
import org.springframework.boot.autoconfigure.web.ErrorProperties;
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.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/
* @author lzlg
* 2023/4/2 20:47
*/
@Configuration
// 导入配置文件
@PropertySource("classpath:application.properties")
// 启用配置类
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class TomcatWebConfig {
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(
dispatcherServlet, "/");
// 设置是否直接加载DispatcherServlet,默认值是-1(懒加载)
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registrationBean;
}
/
* RequestMappingHandlerMapping用于生成路径和请求处理器的映射关系
*/
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
/
* 请求处理适配器
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
return adapter;
}
/
* 注册错误处理页面
*/
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
// 将错误请求[转发]到error路径下
return registry -> registry.addErrorPages(new ErrorPage("/error"));
}
/
* 注册错误页面后置处理器
*/
@Bean
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
return new ErrorPageRegistrarBeanPostProcessor();
}
@Controller
public static class TestController {
@RequestMapping("/test")
public ModelAndView test() {
int i = 1 / 0;
return null;
}
/
* 处理error请求
*/
@RequestMapping("/error-666")
@ResponseBody
public Map<String, Object> error(HttpServletRequest request) {
Exception error = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
System.out.println(error + ">>>>" + error.getClass());
Map<String, Object> map = new HashMap<>(1);
map.put("error", "服务器错误异常:" + error.getMessage());
return map;
}
}
/
* Spring Boot中处理异常方式
* 使用BasicErrorController进行处理的
*/
@Bean
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
// 将异常对象添加到响应中
errorProperties.setIncludeException(true);
BasicErrorController controller = new BasicErrorController(
// 错误信息封装在DefaultErrorAttributes中
new DefaultErrorAttributes(),
// 错误处理的参数配置在ErrorProperties中
errorProperties);
return controller;
}
/
* 定制错误响应页面
* 对浏览器Accept返回Html格式的请求进行处理
* 1.定义视图名称,方法名称必须和转发的错误处理路径(error)一致
* 2.定义视图解析器,根据Bean名称进行视图解析的解析器
*/
@Bean
public View error() {
return new View() {
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("响应数据: " + model);
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("<h3>服务器响应错误" + model.get("exception") + "</h3>");
}
};
}
@Bean
public ViewResolver viewResolver() {
// 根据Bean名称进行视图解析的解析器
return new BeanNameViewResolver();
}
}
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
/
* 测试Spring中的HandleMapping
*
* @author lzlg
* 2023/4/3 19:00
*/
public class TestSpringHandleMapping {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context =
// new AnnotationConfigServletWebServerApplicationContext(TomcatWebConfig.class);
// new AnnotationConfigServletWebServerApplicationContext(CustomWebConfig.class);
// new AnnotationConfigServletWebServerApplicationContext(FunctionWebConfig.class);
new AnnotationConfigServletWebServerApplicationContext(StaticResourceWebConfig.class);
/
* HandlerMapping: 负责建立请求与控制器之间的映射关系,有顺序要求,优先级如下:
* 1.RequestMappingHandlerMapping (处理@RequestMapping注解)
* 2.WelcomePageHandlerMapping (处理/的欢迎页请求)
* 3.BeanNameUrlHandlerMapping (与Bean的名字匹配,Bean的名字以/开头)
* 4.RouterFunctionMapping (函数式RequestPredicate, HandlerFunction)
* 5.SimpleUrlHandlerMapping (处理静态资源,通配符 /, /img/)
*
* HandlerAdapter: 负责实现对各种各样的Handler的适配调用,典型的适配器模式体现
* 1.RequestMappingHandlerAdapter: 处理@RequestMapping注解标注的方法,参数解析器,返回值处理器体系了组合模式
* 2.SimpleControllerHandlerAdapter: 处理实现Controller接口的控制器
* 3.HandlerFunctionAdapter: 处理HandlerFunction函数式接口
* 4.HttpRequestHandlerAdapter: 处理HttpRequestHandler接口(静态资源处理)
*/
}
}
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/
* Web配置类
*
* @author lzlg
* 2023/4/3 19:01
*/
@Configuration
public class TomcatWebConfig {
/
* Tomcat的Servlet容器
*/
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
/
* Spring中转发请求的Servlet
*/
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
/
* 将DispatcherServlet注入到Tomcat的容器中的Bean
*/
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
// 启动时就加载
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
/
* 根据Bean的名称作为映射的HandlerMapping
*/
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
/
* 简单的控制器处理适配器,处理实现Controller接口的控制器方法的调用
*/
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
@Component("/c1")
static class TestController1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("This is c1.");
return null;
}
}
@Component("/c2")
static class TestController2 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("This is c2.");
return null;
}
}
@Bean("/c3")
public Controller controller() {
return (request, response) -> {
response.getWriter().write("This is c3.");
return null;
};
}
}
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.function.*;
import org.springframework.web.servlet.function.support.HandlerFunctionAdapter;
import org.springframework.web.servlet.function.support.RouterFunctionMapping;
/
* 函数式Web配置类
*
* @author lzlg
* 2023/4/3 19:01
*/
@Configuration
public class FunctionWebConfig {
/
* Tomcat的Servlet容器
*/
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
/
* Spring中转发请求的Servlet
*/
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
/
* 将DispatcherServlet注入到Tomcat的容器中的Bean
*/
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
// 启动时就加载
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
/
* 根据函数式路由匹配规则的处理器映射
*/
@Bean
public RouterFunctionMapping routerFunctionMapping() {
return new RouterFunctionMapping();
}
/
* 处理函数式匹配规则的控制器适配器
*/
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
/
* 注册函数式路由规则,请求路径为/r1
*/
@Bean
public RouterFunction<ServerResponse> r1() {
// RequestPredicates.GET("/r1")表示匹配GET请求的/r1路径
// HandlerFunction真正的处理请求方法(相当于Controller接口)
return RouterFunctions.route(RequestPredicates.GET("/r1"), new HandlerFunction<ServerResponse>() {
@Override
public ServerResponse handle(ServerRequest request) throws Exception {
return ServerResponse.ok().body("This is r1.");
}
});
}
/
* 注册函数式路由规则,请求路径为/r2
*/
@Bean
public RouterFunction<ServerResponse> r2() {
// RequestPredicates.GET("/r2")表示匹配GET请求的/r1路径
// HandlerFunction真正的处理请求方法(相当于Controller接口)
return RouterFunctions.route(RequestPredicates.GET("/r2"), new HandlerFunction<ServerResponse>() {
@Override
public ServerResponse handle(ServerRequest request) throws Exception {
return ServerResponse.ok().body("This is r2.");
}
});
}
}
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.resource.CachingResourceResolver;
import org.springframework.web.servlet.resource.EncodedResourceResolver;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.zip.GZIPOutputStream;
/
* 静态资源处理Web配置类
*
* @author lzlg
* 2023/4/3 19:01
*/
@Configuration
public class StaticResourceWebConfig {
/
* Tomcat的Servlet容器
*/
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
/
* Spring中转发请求的Servlet
*/
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
/
* 将DispatcherServlet注入到Tomcat的容器中的Bean
*/
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
// 启动时就加载
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
/
* 用于处理静态资源的处理器映射,此映射没有实现初始化接口,故需自己添加请求处理器
*/
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
// 把ResourceHttpRequestHandler添加到URL集合中
mapping.setUrlMap(context.getBeansOfType(ResourceHttpRequestHandler.class));
return mapping;
}
/
* 用于处理静态资源请求的处理器适配器
*/
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
/
* 欢迎页设置,通过请求根路径http://localhost:8080能转发到http://localhost:8080/index.html
*/
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {
Resource resource = context.getResource("classpath:static/index.html");
return new WelcomePageHandlerMapping(null, context, resource, "/");
}
/
* 简单控制器处理器适配器,能够处理欢迎页的请求
* 因WelcomePageHandlerMapping内置了ParameterizableViewController
* 通过SimpleControllerHandlerAdapter能调用到ParameterizableViewController中处理请求的方法
*/
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
/
* 处理静态资源的处理器,用于处理类路径下的static目录下的静态资源文件
* 注解@Bean标注的是处理器的请求路径
*/
@Bean("/")
public ResourceHttpRequestHandler handlerStatic() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(Collections.singletonList(new ClassPathResource("static/")));
// 添加资源请求解析器,是典型的责任链模式
handler.setResourceResolvers(Arrays.asList(
// 提供缓存静态资源的解析器
new CachingResourceResolver(new ConcurrentMapCache("myCache")),
// 提供压缩请求为gzip格式的资源解析器
new EncodedResourceResolver(),
// 资源路径解析器
new PathResourceResolver()
));
return handler;
}
/
* 处理静态资源的处理器,用于处理类路径下的images目录下的静态资源文件
* 注解@Bean标注的是处理器的请求路径
*/
@Bean("/img/")
public ResourceHttpRequestHandler handlerImage() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(Collections.singletonList(new ClassPathResource("images/")));
return handler;
}
@PostConstruct
public void initGzip() {
// 从类路径下获取资源信息
ClassPathResource resource = new ClassPathResource("static");
// 找到静态资源文件夹
File dir;
try {
dir = resource.getFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (dir.exists()) {
// 过滤出需要压缩的Html文件
File[] htmlFiles = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".html");
}
});
if (Objects.isNull(htmlFiles)) {
System.out.println("类路径static下没有html文件");
return;
}
for (File htmlFile : htmlFiles) {
// 把html文件进行gzip压缩
try (FileInputStream fis = new FileInputStream(htmlFile);
GZIPOutputStream gos = new GZIPOutputStream(Files
.newOutputStream(Paths.get(htmlFile.getAbsoluteFile() + ".gz")))) {
byte[] bytes = new byte[8 * 1024];
int len;
while ((len = fis.read(bytes)) != -1) {
gos.write(bytes, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/
* WelcomePageHandlerMapping
*/
final static class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
private static final Log logger = LogFactory.getLog(WelcomePageHandlerMapping.class);
private static final List<MediaType> MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL);
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
setRootViewName("forward:index.html");
} else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
private boolean welcomeTemplateExists(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext) {
return templateAvailabilityProviders.getProvider("index", applicationContext) != null;
}
private void setRootViewName(String viewName) {
ParameterizableViewController controller = new ParameterizableViewController();
controller.setViewName(viewName);
setRootHandler(controller);
setOrder(2);
}
@Override
public Object getHandlerInternal(HttpServletRequest request) throws Exception {
for (MediaType mediaType : getAcceptedMediaTypes(request)) {
if (mediaType.includes(MediaType.TEXT_HTML)) {
return super.getHandlerInternal(request);
}
}
return null;
}
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) {
String acceptHeader = request.getHeader(HttpHeaders.ACCEPT);
if (StringUtils.hasText(acceptHeader)) {
return MediaType.parseMediaTypes(acceptHeader);
}
return MEDIA_TYPES_ALL;
}
}
}
自定义处理器映射器和处理器适配器:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.mvc.Controller;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/
* 自定义Web配置类
* 自定义实现HandlerMapping和HandlerAdapter接口
*
* @author lzlg
* 2023/4/3 19:01
*/
@Configuration
public class CustomWebConfig {
/
* Tomcat的Servlet容器
*/
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
/
* Spring中转发请求的Servlet
*/
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
/
* 将DispatcherServlet注入到Tomcat的容器中的Bean
*/
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
// 启动时就加载
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
/
* 自定义HandlerMapping
* 扫描实现Controller接口的Bean,判断Bean名称是否前缀是否有/
* 如果有则是合格的请求控制器,加入到控制器集合中
*/
@Component
static class MyHandlerMapping implements HandlerMapping {
// 注入Spring容器对象,在调用MyHandlerMapping的初始化方法(@PostConstruct注解上的)之前已经实例化
@Autowired
private ApplicationContext context;
private Map<String, Controller> controllerMap;
/
* 初始化方法获取Controller
*/
@PostConstruct
public void getController() {
// 从容器中获取Controller这一类型的Bean的集合
Map<String, Controller> map = context.getBeansOfType(Controller.class);
// 找到那些Bean名称以/开头的Controller实例
controllerMap = map.entrySet().stream()
.filter(e -> e.getKey().startsWith("/"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取请求URI
String requestURI = request.getRequestURI();
// 从Controller的集合中获取控制器
Controller controller = controllerMap.get(requestURI);
if (Objects.isNull(controller)) {
return null;
}
// 返回控制器执行链对象
return new HandlerExecutionChain(controller);
}
}
/
* 自定义HandlerAdapter
* 找到指定的控制器进行请求的处理
*/
@Component
static class MyHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
// 实现Controller接口的都进行处理
return handler instanceof Controller;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 进行请求的处理
if (handler instanceof Controller) {
Controller controller = (Controller) handler;
controller.handleRequest(request, response);
}
// 返回null,表示不再进行视图渲染
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
@Component("/c1")
static class TestController1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("This is c1.");
return null;
}
}
/
* 这是不符合自定义HandlerMapping的控制器
*/
@Component("c2")
static class TestController2 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("This is c2.");
return null;
}
}
@Bean("/c3")
public Controller controller() {
return (request, response) -> {
response.getWriter().write("This is c3.");
return null;
};
}
}
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
DispatcherServlet 接下来会: