测试环境搭建: 本次搭建是基于springboot来实现的,代码在码云的链接:https://gitee.com/yangxioahui/thymeleaf.git

DispatcherServlet核心流程在上一篇源码分析已经做了详细讲解 了,参考: https://www.cnblogs.com/yangxiaohui227/p/13229413.html

1. 自定义处理器映射器和处理器适配器:

本次目标是使得我自定义的controller生效

//自定义映射器,使得springmvc 可以通过/test22 找到我的MyTestController

@Component
public class MyHandlerMapping implements HandlerMapping, ApplicationContextAware, Ordered {
private ApplicationContext applicationContext; private Map<String,Object> handlerMap=new HashMap<>(); //spring 在启动时,会将所有被我的@MyRequestMapping注解标注的bean 存到这里,例如我们定义的MyTestController @Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
String requestURI = request.getRequestURI(); //从请求参数中拿到对应的url
Object o = handlerMap.get(requestURI); //通过url或者handler
if(null!=o){
HandlerExecutionChain handlerExecutionChain = new HandlerExecutionChain(o);
return handlerExecutionChain;
}
return null;
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
this.detectHandler(applicationContext); //spring 容器创建该bean时会回调该方法 } private void detectHandler(ApplicationContext applicationContext) {
//获取所有的BeanName
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String beanName : definitionNames) {
//获取对应的bean
Object bean = applicationContext.getBean(beanName);
//判断是否贴有我们要的注解
MyRequestMapping annotation = bean.getClass().getAnnotation(MyRequestMapping.class);
if(null!=annotation){
String url = annotation.url();
handlerMap.put(url,bean);
}
}
} @Override
public int getOrder() { //因为springmvc 中有很多HandlerMapping,只要其中一个匹配到对应的url,就会返回,所以我们要将这里的优先级设置最高
return Ordered.HIGHEST_PRECEDENCE;
}
}

自定义适配器:

@Component
public class MyHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof ModlerInterface; //我们定义的适配器,处理的类型就是ModlerInterface
} @Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ModlerInterface modlerInterface=(ModlerInterface)handler;
modlerInterface.doHandler(request, response); //直接调用目标方法
return null;
} @Override
public long getLastModified(HttpServletRequest request, Object handler) {
return 0;
}
}

浏览器调用:

原理分析:

自定义映射器和自定义适配器添加到DispatcherServlet原理:

DispatcherServlet 的List<HandlerMapping> handlerMappings 初始化方法:

DispatcherServlet的List<HandlerAdapter> handlerAdapters 的原理同上;

我们再debug调试下自定义HandlerMapping的过程: 

启动项目:

浏览器debug 调用我们的目标方法:

这样就找到我们的适配器了,之后通过适配器调用目标方法:

至此,我们自定义映射器和适配器原理完成了;

2. 自定义参数解析器: 针对的是普通使用@RequestMaping实现的controller

// 自定义参数解析器:

package com.yang.xiao.hui.thymeleaf.resovler;

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; import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringToDateResovler implements HandlerMethodArgumentResolver{ @Override
public boolean supportsParameter(MethodParameter parameter) { //@RequsetMapping注解标注的方法参数有没指定注解
StringToDate annotation = parameter.getParameter().getAnnotation(StringToDate.class);
if(null!=annotation){
return true;
}
return false;
} @Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String parameterName = parameter.getParameterName(); //获取参数名称
HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(); //获取请求对象
String value = request.getParameter(parameterName); //请求对象获取参数值
StringToDate annotation = parameter.getParameterAnnotation(StringToDate.class); //获取参数的注解
String format = annotation.value(); //获取注解的值
SimpleDateFormat dateFormat = new SimpleDateFormat(format); //格式化器
Date date = dateFormat.parse(value);;//解析参数值为date
return date;
}
}

//自定定义返回值解析器

public class MyReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) { //判断@RequestMapping注解标注的方法有没有我们定义的注解
MyResponseBody annotation = returnType.getMethod().getAnnotation(MyResponseBody.class);
if(null!=annotation){
return true;
}
return false;
} @Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true);//不会进行视图解析了
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.getWriter().write(returnValue.toString());
}
}

//根据之前的源码分析知道,@RequestMapping 注解实现controller的方式,最终是由RequestMappingHandlerAdapter处理的,因此我们要将自定义的解析器加到RequestMappingHandlerAdapter中去;

而RequestMappingHandlerAdapter这个bean的创建是通过WebMvcConfigurationSupport这个配置类来创建的,我们看看它的创建源码

所以我们分析下: getArgumentResolvers()方法

到此,我们发现,自定义的参数解析器和返回值解析器生效的话,要继承WebMvcConfigurationSupport这个类,并重写相应方法:

@Configuration //重写父类的方法可以实现很多功能
public class MyWebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new StringToDateResovler()); //添加我们自定义的参数解析器
} @Override
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.add(new MyReturnValueHandler());//添加我们自定义的返回值解析器
}
}

启动代码测试: 浏览器输入: http://localhost:8081/find/product/3?date=2020-10-12

debug 调试,中间省略n步骤:下图在(InvocableHandlerMethod类中)

下图是我们自定义的参数解析器

浏览器拿到了结果:

至此,我们自定义参数解析器和参数返回值解析器完成,springmvc很多自带的解析器用于解析一些常用注解,如@ResponseBody,@ModlerAttribute@RequestBody等,他们的原理跟我们自定义的是一样的

3. 自定义异常处理器: 我们希望指定的异常,跳到指定的页面 : 需求,我们希望NumberFormatException异常,跳到500.html这个页面:

我们自定义异常解析器,跟映射器和适配器一样,只要注入spring容器即可,DispacherServlet中有个异常解析器集合:

下面我们自定义异常解析器:

我们定义一个测试Controller:

//浏览器调用该方法:http://localhost:8081/test/error

springmvc 源码分析(三) -- 自定义处理器映射器和自定义处理器适配器,以及自定义参数解析器 和错误跳转自定页面的更多相关文章

  1. springMVC源码分析--ViewNameMethodReturnValueHandler返回值处理器(三)

    之前两篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)和springMVC源码分析--HandlerMethodReturnValu ...

  2. springMVC源码分析--AbstractUrlHandlerMapping(三)

    上一篇博客springMVC源码分析--AbstractHandlerMapping(二)中我们介绍了AbstractHandlerMapping了,接下来我们介绍其子类AbstractUrlHand ...

  3. springMVC源码分析--HttpMessageConverter写write操作(三)

    上一篇博客springMVC源码分析--HttpMessageConverter参数read操作中我们已经简单介绍了参数值转换的read操作,接下来我们介绍一下返回值的处理操作.同样返回值的操作操作也 ...

  4. springMVC源码分析--RequestParamMethodArgumentResolver参数解析器(三)

    之前两篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)和springMVC源码解析--HandlerMethodArgumentResol ...

  5. springMVC源码分析--SimpleControllerHandlerAdapter(三)

    上一篇博客springMVC源码分析--HandlerAdapter(一)中我们主要介绍了一下HandlerAdapter接口相关的内容,实现类及其在DispatcherServlet中执行的顺序,接 ...

  6. springMVC源码分析--拦截器HandlerExecutionChain(三)

    上一篇博客springMVC源码分析--HandlerInterceptor拦截器调用过程(二)中我们介绍了HandlerInterceptor的执行调用地方,最终HandlerInterceptor ...

  7. SpringMVC源码分析--容器初始化(三)HttpServletBean

    在上一篇博客springMVC源码分析--容器初始化(二)DispatcherServlet中,我们队SpringMVC整体生命周期有一个简单的说明,并没有进行详细的源码分析,接下来我们会根据博客中提 ...

  8. springMVC源码分析--页面跳转RedirectView(三)

    之前两篇博客springMVC源码分析--视图View(一)和springMVC源码分析--视图AbstractView和InternalResourceView(二)中我们已经简单的介绍了View相 ...

  9. 框架-springmvc源码分析(一)

    框架-springmvc源码分析(一) 参考: http://www.cnblogs.com/heavenyes/p/3905844.html#a1 https://www.cnblogs.com/B ...

  10. [心得体会]SpringMVC源码分析

    1. SpringMVC (1) springmvc 是什么? 前端控制器, 主要控制前端请求分配请求任务到service层获取数据后反馈到springmvc的view层进行包装返回给tomcat, ...

随机推荐

  1. Docker学习笔记-Dockerfile文件详解

    什么是Dockerfile? Docker中有个非常重要的概念叫做--镜像(Image).Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序.库.资源.配置等文件外,还包含了一些为运 ...

  2. 微信支付宝个人免签约即时到帐接口开发附demo

    支付界面: 扫码支付界面: 付成功后可以跳转到你程序指定的地址! demo: http://likeyunba.com/WeChat-Pay/

  3. Vue 通过调用百度API获取地理位置-经度纬度省份城市

    一.首先在百度api注册获得ak密钥 二.新建js文件,我命名为loadBMap.js,里面创建script,代码如下: /** * 加载地图 * @param {Function} callback ...

  4. 好看的css渐变颜色大全网址

    60个渐变颜色 https://webkul.github.io/coolhue/ 60个非常有用的CSS代码片段 https://baijiahao.baidu.com/s?id=160278735 ...

  5. 关于js中循环遍历中顺序执行ajax的问题(vue)

    js里的循环,每次都是自顾自的走,它不等ajax执行好走完到success代码,就继续循环下一条数据了,这样数据就全乱了. 后来,想到试试ajax里async这个属性,async默认是true,即为异 ...

  6. P1090 合并果子(哈弗曼树)

    题目描述 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和.可 ...

  7. 2申请高德地图key 初始化地图

    https://console.amap.com/dev/key/app vue-amap-基于-vue-2x-与高德的地图组件 https://elemefe.github.io/vue-amap/ ...

  8. 跟着尚硅谷系统学习Docker-【day03】

    day03-20200715   p15.docker容器命令(下)   以守护进程的形式启动 [docker run -d 容器名]后台运行 启动了后又关闭了,与前台没有交互,立刻就kill了.前台 ...

  9. Activiti7 任务人员动态分配(UEL-Value方式)

    先修改流程图 测试之前记得先删除之前发布的流程定义,并重新发布改过的 /** * 使用UEL-Value动态分配任务人员 */ @Test public void uelValue(){ // 获取R ...

  10. 借助rownum中求Oracle表中前三名(三甲:状元榜眼探花)的方法(总计三种方法,以讲述rownum的使用为主)

    要求前三名,MySQL中有order by排序,limit限制数量,结果很容易得到,而且limit的执行顺序也在order by之后,写出的sql高效易懂而不易出错. 但在oracle中,由于没有li ...