Spring MVC 是 Spring给我们提供的一个用于简化WEB开发的框架
第一部分 Spring MVC应用
第1节 概述
1.1 MVC体系结构
三层架构
开发架构一般是基于两种形式,一种是C/S架构,即客户端/服务器;另一种是B/S架构,即浏览器/服务器。
在JavaEE中,主要面向的是B/S架构模式。根据代码的分类,将其分为标准的三层架构包括 : 表现层、业务层、持久层。
经典三层架构,每一层都严格各司其职,做到了代码的解耦、提高了代码的可阅读性
-
表现层 : 负责接收网页发送的请求,在经历过一系列业务流程后,向网页响应结果,通常网页使用http协议请求后端服务器,后端服务器需要接收http请求,完成http响应
表现层包括展示层和控制层 : 控制层负责接收服务器解析过得http请求,而展示层负责结果的展示
表现层依赖于业务层,接收到网页请求一般会调用业务层进行复杂的业务处理,并将结果封装处理返回给展示层
Spring MVC框架主要就是对经典MVC三层架构中表现层的封装,与其它层没有关系,我们之前所学习过的Mybatis是对数据层的封装,Spring则是对整个代码书写的封装 -
业务层 : Service层,负责具体的业务逻辑处理,而业务层一般会调用多个数据层的方法来完成一项负责的需求,复杂的业务逻辑由多个数据层方法所支持,所以事务应该放到业务层来控制
-
持久层 : 数据的持久化操作
MVC设计模式
MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是⼀
种用于设计创建 Web 应⽤程序表现层的模式。MVC 中每个部分各司其职:
- Model : 模型包括业务模型和数据模型,数据模型用于封装模型,业务模型用于处理业务
- View : 前端展示页面,在之前前后端未分离的开发模式中就是我们的jsp或者html。作用是展示数据,通常依据模型数据所创建。但是现在的开发都是前后端分离开发,View层的应用已经不是那么明显
- Controller : 是应用程序中处理用户交互的部分,作用一般是处理程序逻辑
1.2 Spring MVC 是什么
Spring对web开发的封装,也就是对原生Servlet的封装
具体作用
- 接受请求
- 处理请求
- 返回响应
- 跳转页面(前后端未分离时)
模型分类的具体含义
- 数据模型 : 前端会传数据到达后端,经过MVC框架时,会使用反射的方式调用set方法将数据具体封装成Java中的基本数据类型或者是自定义的dto实体类
- 业务模型 : 具体的业务处理
第2节 Spring Web MVC工作流程
快速使用 : 前端访问后端,并返回当前时间
步骤
-
使用maven创建Web工程
-
配置tomcat服务器
-
引入坐标
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--引入spring webmvc的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.18</version> </dependency> <!--引入servlet的依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies>
-
编写springmvc.xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置注解扫描--> <context:component-scan base-package="com.huier.controller"/> <!--配置springmvc的视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--返回视图时自动添加前缀和后缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 自动注册最合适的处理器映射器,处理器适配器(调用handler方法) --> <mvc:annotation-driven/> </beans>
-
编写web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <!--本质依旧是一个Servlet,不过全局只有一个--> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--在初始化时加载springmvc配置文件--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!--映射--> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
编写相应的Controller层代码
@Controller @RequestMapping("/demo") public class DemoController { @RequestMapping("/handle01") public ModelAndView handle01(){ Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date",date); modelAndView.setViewName("success"); return modelAndView; } }
-
jsp代码
<html> <head> <title>Title</title> </head> <body> 跳转成功 时间 ${date} </body> </html>
2.1 Spring MVC请求处理流程
流程说明
- 用户发送请求至tmocat服务器中,tomcat服务器解析请求封装成对象交给内部的容器,容器再根据映射去寻找具体的Servlet处理请求,因为在MVC中可以认为只有一个Servlet对象,这个对象负责将请求分发出去,这个对象也就是DispatcherServlet
- DispatcherServlet收到请求调用HandlerMappering处理器映射器
- 处理器映射器根据请求Url找到具体的Handler(后端控制器),生成处理器对象及处理器拦截 器(如果有则生成)⼀并返回DispatcherServlet,再由DispatcherServlet派发
- DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler
- 处理器控制器并不只有Controller,还有HttpRequestHandler,Servlet 等处理器
- AnnotationMethodHandlerAdapter 主要是适配注解类处理器,注解类处理器就是我们经常使用的 @Controller 的这类处理器
- HttpRequestHandlerAdapter 主要是适配静态资源处理器,静态资源处理器就是实现了 HttpRequestHandler 接口的处理器,这类处理器的作用是处理通过 SpringMVC 来访问的静态资源的请求
- SimpleControllerHandlerAdapter 是 Controller 处理适配器,适配实现了 Controller 接口或 Controller 接口子类的处理器,比如我们经常自己写的 Controller 来继承 MultiActionController.
- SimpleServletHandlerAdapter 是 Servlet 处理适配器, 适配实现了 Servlet 接口或 Servlet 的子类的处理器,我们不仅可以在 web.xml 里面配置 Servlet,其实也可以用 SpringMVC 来配置 Servlet,不过这个适配器很少用到,而且 SpringMVC 默认的适配器没有他,默认的是前面的三种。
- 在目前后端分离开发中,主要还是以第一种注解类处理器为主要
- 处理适配器执行Handler,也就是具体的方法
- Handler处理完成后向处理器适配器返回ModelAndView信息
- 处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个底层对象,包括 Model 和 View
- 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图,如果返回了视图的话
- 视图解析器向前端控制器返回View,如果有视图
- 前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域,如果有视图
- 前端控制器向前端响应数据或者视图
在如今的开发环境中,SpringMVC框架更多的是直接向前端返回数据信息,而不是视图
2.2 Spring MVC九大组件[了解即可]
-
HandlerMapping(处理器映射器)
HandlerMapping 是用来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是
方法。比如,标注了@RequestMapping的每个方法都可以看成是⼀个Handler。Handler负责具
体实际的请求处理,在请求到达后,HandlerMapping 的作用便是找到请求相应的处理器 Handler 和 Interceptor,并形成一条执行链,类似原生Servlet中的过滤器链
-
HandlerAdapter(处理器适配器)
HandlerAdapter 是⼀个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理方法调用 Handler 来进行处理,便是 HandlerAdapter 的职责。 -
HandlerExceptionResolver(异常处理器)
HandlerExceptionResolver 用于处理 Handler 产生的异常情况,只能处理Handler抛出的异常,也就是我们所说的Controller层,所以在我们的开发中,将所有都异常都向上抛到Controller层,并由异常处理器统一处理,更加优雅、减少代码冗余
-
ViewResolver
ViewResolver即视图解析器,用于将String类型的视图名和Locale解析为View类型的视图,只有⼀ 个resolveViewName()方法。从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这里被解析成为View。View是用来渲染页面的,也就是说,它会将程序返回的参数和数据填入模板中,生成html⽂件。ViewResolver 在这个过程主要完成两件事情 : ViewResolver 找到渲染所用的模板(第⼀件大事)和JSP页面填入参数
-
RequestToViewNameTranslator
RequestToViewNameTranslator 组件的作用是从请求中获取 ViewName.因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。
-
LocaleResolver
ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。LocaleResolver ⽤于从请求中解析出 Locale,比如中国 Locale 是 zh-CN,用来表示⼀个区域。这个组件也是 i18n 的基础。
-
ThemeResolverThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。
Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图片、CSS样式等。创建主题非常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和具体的资源。
-
MultipartResolverMultipartResolver文件上传
MultipartHttpServletRequest 来实现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。
-
FlashMapManager(MVC对重定向的封装,更方便)
第3节 请求参数绑定
Handler处理时所接收的参数
原生Servlet方式
- .getParameter(“参数名”)
- 绑定
SpringMVC框架对Servlet的封装,简化了servlet的很多操作
SpringMVC在接收整型参数的时候,直接在Handler方法中声明形参即可
例如
@GetMapping("/demo")
public void getNumber(Integer num){
System.out.println(num);
}
参数绑定 : 取出参数绑定到Handler方法的形参上
-
默认支持Servlet API
如果要在SpringMVC中使⽤servlet原生对象,比如HttpServletRequest\HttpServletResponse\HttpSession,直接在Handler方法形参中声明使用即可
@RequestMapping("/api") public void servletApi(HttpServletRequest request, HttpServletResponse response, HttpSession session){ System.out.println("请求对象"+request); System.out.println("响应对象"+response); System.out.println("会话域"+session); } /* 请求对象org.apache.catalina.connector.RequestFacade@7d7a70a5 响应对象org.apache.catalina.connector.ResponseFacade@1c35a7fa 会话域org.apache.catalina.session.StandardSessionFacade@67cf534a */
-
绑定简单类型参数
简单数据类型 : 八中基本数据类型及其包装类
说明 : 对于布尔类型的参数,请求的参数值true或false。或者1或0注意:绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持⼀ 致,建议使⽤包装类型,当形参参数名和传递参数名不⼀致时可以使⽤@RequestParam注解进⾏ ⼿动映射)
-
绑定Dto类型参数,什么是Dto?就是专门用来接收前端参数的实体类
接收Dto类型参数,直接形参声明即可,类型就是Dto的类型,形参名⽆所谓 要求传递的参数名必须和Pojo的属性名保持⼀致,不一致的属性值不能绑定上 如果Dto中还有其它Dto实体类的属性,会使用属性名+'.'的方式进一步锁定数据
-
绑定日期类型参数(需要配置自定义类型转换器)
-
问题,页面会出异常
jsp<%--jsp--%> <fieldset> <p>测试用例:SpringMVC接收日期类型参数</p> <a href="/demo/handle03?birthday=2019-10-08">点击测试</a> </fieldset>
handler
@RequestMapping("/handle03") @ResponseBody public Date getDate(Date birthday){ return birthday; }
-
解决 : 配置自定义类型转换器,在框架中什么什么器就是实现接口
- 实现接口
public class DateConverter implements Converter<String, Date> { public Date convert(String source) { // 完成字符串向⽇期的转换 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date parse = null; try { parse = simpleDateFormat.parse(source); return parse; } catch (ParseException e) { e.printStackTrace(); } return parse; } }
-
注册自定义类型转换器
<!-- 自动注册最合适的处理器映射器,处理器适配器(调用handler方法) --> <!-- <mvc:annotation-driven/>--> <mvc:annotation-driven conversion-service="conversionServiceBean"/> <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.huier.converter.DateConverter"></bean> </set> </property> </bean>
-
第4节 对Resultful风格请求支持
4.1 什么是Resultful
Resultful是一种web软件,它既不是标准也不是协议,它倡导的是一种资源定位及资源操作的风格
Resultful 的优点
它结构清晰、符合标准、易于理解、扩展⽅便,所以正得到越来越多网站的采用
Resultful的特性
HTTP 协议,是⼀个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器, 必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建⽴在表现层之上的,所以就是 “ 表现层状态转化” 。具体说, 就是 HTTP 协议里面,四个表示操作方式的动词 : GET 、POST 、PUT 、DELETE 。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
RESTful的示例:
rest是⼀个url请求的⻛格,基于这种风格设计请求的url,没有rest的话,原有的url设计
http://localhost:8080/demo/select?id=3
url中定义了动作(操作),参数具体锁定到操作的是谁
有了rest风格之后
rest中,认为互联网中的所有东西都是资源,既然是资源就会有⼀个唯⼀的uri标识它,代表它
http://localhost:8080/demo/3 代表的是id为3的那个用户记录(资源)
锁定资源之后如何操作它呢?常规操作就是增删改查
根据请求方式不同,代表要做不同的操作
- get查询,获取资源
- post查询,新建资源
- put更新
- delete删除资源
rest风格带来的直观体系 : 就是传递参数方式的变化,参数可以直接在url中
/account/1 HTTP GET :得到 id = 1 的 account
/account/1 HTTP DELETE:删除 id = 1 的 account
/account/1 HTTP PUT:更新 id = 1 的 account
RESTful风格URL:互联⽹所有的事物都是资源,要求URL中只有表示资源的名称,没有动词
**RESTful风格资源操作:**使用HTTP请求中的method方法put、delete、post、get来操作资源
**RESTful风格资源表述:**可以根据需求对URL定位的资源返回不同的表述
Spring MVC 支持RESTful 风格请求,具体讲的就是使用@PathVariable 注解获取 RESTful 风格的请求
URL中的路径变量
-
示例代码
-
前端jsp页面
<%--设置isErrorPage="true"--%> <%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<div> <h2>SpringMVC对Restful风格url的支持</h2> <fieldset> <p>测试用例:SpringMVC对Restful风格url的支持</p> <a href="/demo/handle/15">rest_get测试</a> <form method="post" action="/demo/handle"> <input type="text" name="username"/> <input type="submit" value="提交rest_post请求"/> </form> <form method="post" action="/demo/handle/15/lisi"> <input type="hidden" name="_method" value="put"/> <input type="submit" value="提交rest_put请求"/> </form> <form method="post" action="/demo/handle/15"> <input type="hidden" name="_method" value="delete"/> <input type="submit" value="提交rest_delete请求"/> </form> </fieldset> </div>
-
后端Handler方法
@RequestMapping(value = "/handle/{id}", method = {RequestMethod.GET}) public ModelAndView handleGet(@PathVariable("id") Integer id) { System.out.println(id); Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } @RequestMapping(value = "/handle", method = {RequestMethod.POST}) public ModelAndView handlePost(String username) { System.out.println(username); Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } @RequestMapping(value = "/handle/{id}/{name}", method = {RequestMethod.PUT}) public ModelAndView handlePut(@PathVariable("id") Integer id, @PathVariable("name") String username) { System.out.println(id); System.out.println(username); Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } @RequestMapping(value = "/handle/{id}", method = {RequestMethod.DELETE}) public ModelAndView handleDelete(@PathVariable("id") Integer id) { System.out.println(id); Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; }
-
web.xml中配置请求方式过滤器(将特定的post请求转换为put和delete请求)
<!--springmvc提供的针对post请求的编码过滤器--> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换--> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
第5节 Ajax Json交互
交互 : 两个方向
1)前端到后台 : 前端ajax发送json格式字符串,后台直接接收为dto参数,使用注解@RequstBody从请求体中获取数据
2)后端到前端 : 后台直接返回pojo对象,前端直接接收为json对象或者字符串,使用注解@ResponseBody
5.1 什么是ajax?
ajax就是JS语言和服务端交互的手段
- ajax 全名 async javascript and XML(异步JavaScript和XML)
- 是前后台交互的能力也就是我们客户端给服务端发送消息的工具,以及接收响应的工具
- ajax不是编程语言,而是一种使用现有标准的新方法
- ajax是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下
- 是默认异步执行机制的功能,ajax分为同步(async = false)和异步(async = true)
同步请求
同步请求是指当前发出请求后,浏览器什么都不能做,
必须得等到请求完成返回数据之后,才会执行后续的代码,
相当于生活中的排队,必须等待前一个人完成自己的事物,后一个人才能接着办。
也就是说,当JS代码加载到当前AJAX的时候会把页面里所有的代码停止加载,页面处于一个假死状态,
当这个AJAX执行完毕后才会继续运行其他代码页面解除假死状态
异步请求
默认异步:异步请求就当发出请求的同时,浏览器可以继续做任何事,
Ajax发送请求并不会影响页面的加载与用户的操作,相当于是在两条线上,各走各的,互不影响。
一般默认值为true,异步。异步请求可以完全不影响用户的体验效果,
无论请求的时间长或者短,用户都在专心的操作页面的其他内容,并不会有等待的感觉。
至于ajax的工作原理,简单的说,前端会产生一个ajax对象,这个对象负责向后端发送请求以及接收来自后端的数据。所以,后端如果返回响应数据并不会直接被浏览器接收而是被这个对象接收,继而进行更复杂的操作然后才显示在浏览器
5.2 什么是JSON?
在JSON格式没有出来之前,xml就是前端和后端交互的载体;而在JSON格式出来后,优秀的性能替代xml作为前后端交互的载体
Json是⼀种与语言无关的数据交互格式,就是⼀种字符串,只是用特殊符号{}内表示对象、[]内表示数
组、""内是属性或值、:表示后者是前者的值
{“name”: “HuiEr”}可以理解为是⼀个包含name为HuiEr的对象
[{“name”: “HuiEr”},{“name”: “huier”}]就表示包含两个对象的数组
5.3 @ResponseBody注解
@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之
后,写⼊到response对象的body区,通常用来返回JSON数据或者是XML数据。 注意:在使用此注解之
后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定
格式的数据。
5.4 分析Spring MVC使用JSON交互
-
引入转json的坐标
<!--json数据交互所需jar,start--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.13.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.13.2</version> </dependency>
-
示例代码
-
前端jsp页面以及代码
<%--引入js--%> <script type="text/javascript" src="/js/jquery.min.js"></script> <script> $(function () { $("#ajaxBtn").bind("click",function () { // 发送ajax请求 $.ajax({ url: '/demo/handle04', type: 'POST', data: '{"id":"1","name":"灰二灰"}', contentType: 'application/json;charset=utf-8', dataType: 'json', success: function (data) { alert(data.name); } }) }) }) </script> <style> div{ padding:10px 10px 0 10px; } </style>
<div> <h2>Ajax json交互</h2> <fieldset> <input type="button" id="ajaxBtn" value="ajax提交"/> </fieldset> </div>
-
实体类
public class UserDto { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
后台Handler方法
@RequestMapping("/handle04") public @ResponseBody UserDto handle04(@RequestBody UserDto user) { // 业务逻辑处理,修改name为张三丰 user.setName("灰二"); return user; }
-
测试通过
-
第二部分 Spring MVC高级技术
第1节 拦截器(Inteceptor)使用
1.1 监听器、过滤器和拦截器对比
-
Servlet : 处理Request请求和Response响应
-
过滤器(Filter) : 对Request请求起到过滤的作用,作用在Servlet之前,如果配置为/*可以对所有资源访问(Servlet、js/css静态资源等)进行过滤处理
-
监听器(Listener) : 实现了javax.servlet.ServletContextListener 接口的服务器端组件,它随Web应用的启动而启动,只初始化⼀次,然后会⼀直运行监视,随Web应用的停止而销毁
作用一:做⼀些初始化⼯作,web应用中spring容器启动ContextLoaderListener
作用⼆:监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、
销毁和修改等。可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤
HttpSessionLisener等。
-
拦截器(Interceptor):是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler)
从配置的角度也能够总结发现:serlvet、fifilter、listener是配置在web.xml中的,而interceptor是配置在表现层框架自己的配置文件中的
- 在Handler业务逻辑执行之前拦截⼀次
- 在Handler逻辑执行完毕但未跳转页面之前拦截⼀次
- 跳转页面之后拦截一次
过滤器和拦截器的区别
过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的
-
实现原理不同,过滤器是基于函数回调的,拦截器则是基于Java反射机制的(动态代理)实现
在我们自定义的过滤器中,我们必须实现doFilter()方法,这个方法有一个FilterChain参数
public interface FilterChain { void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException; }
ApplicationFilterChain能够获取我们自定义的过滤器并形成一个链条,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法。而每一个xxxFilter会首先调用自己的doFilter()过滤逻辑,最后会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain的doFilter() 方法,以此循环执行实现函数回调。
总的概述,每一个过滤器再执行完自己的过滤逻辑后,返回到过滤器链中,由过滤器链去执行下一个过滤器,这个过程就是回调
-
使用范围不同
过滤器实现的是javax.servlet.Filter接口,而这个接口是在Servlet规范中定义的,也就是说过滤器的使用要依赖Tomcat服务器,所以它只能在web服务器中使用
而拦截器它是Spring MVC中的一个组件,并由Spring容器管理,并不依赖Tomcat服务器,可以单独使用
-
触发时机不同
-
拦截的请求范围不同
过滤器几乎可以对所有进入tomcat服务器的请求进行过滤,而拦截器只能拦截Controller的方法
-
注入Bean的情况不同
过滤器获得Spring管理的Bean对象
Tomcat容器初始化顺序 : 监听器 => 过滤器 => Servlet,因此springMVCservlet初始化之前,过滤器就已经初始化过了,如果在过滤器中需要注入spring容器管理的bean是注入不进去的,因此需要在spring监听器中初始化需要注入的bean,才能在过滤器中注入,而且过滤器的定义需要使用spring定义的DelegatingFilterProxy来实现
Spring 容器和 Spring MVC 容器的创建和实例化都是借助了 Tomcat 的特性,Spring 容器的创建时机为 ServletContext 实例化之后,ContextLoaderListener 监听器会调用 contextInitialized 方法来触发 Spring 容器的创建。而 Spring MVC 容器的创建则是借助 Servlet.init 方法,且 Spring 容器为 Spring MVC 的父容器,即 Spring MVC 可以访问父容器的 bean,反过来则不可以
-
web.xml配置
<filter> <filter-name>sysVisitFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>sysVisitFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
Spring配置文件中配置该Bean
<bean id="sysVisitFilter" class="自定义过滤器全路径"></bean>
拦截器注入Spring管理的Bean对象
拦截器加载的时间点在springcontext之前,而Bean又是由Spring进行管理
-
需要在执行拦截器之前,先将Interceptor手动进行注入
registry.addInterceptor()注册的是getMyInterceptor()实例。@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public MyInterceptor getMyInterceptor(){ System.out.println("注入了MyInterceptor"); return new MyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**"); } }
-
-
控制执行顺序不同,通过@Order控制过滤器和拦截器的执行顺序,值越小级别越高
1.2 拦截器的执行流程
单个拦截器,在程序中的执行流程
1)程序先执行preHandle()方法,如果该方法的返回值是true,则程序会继续向下执行处理器的方法,否则将不会再向下执行
2)在业务处理器(Controller类)处理完请求后,会执行postHandle()方法然后通过DispatchServlet向客户端返回响应
3)在DispatchServlet处理完请求后,才会执行afterCompletion()方法
总结,拦截器在三个特殊位置对Controller层拦截
1.3 多个拦截器的执行流程
多个拦截器,这里使用两个拦截器Interceptor1和Interceptor2作为例子说明,并且在配置⽂件中, Interceptor1拦截器配置在前)
具体实现
-
实现接口HandlerInterceptor
public class MyInterceptor01 implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor01 preHandle"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor01 postHandle"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor01 afterCompletion"); } }
public class MyInterceptor02 implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor02 preHandle"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor02 postHandle"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor02 afterCompletion"); } }
-
springmvc.xml中注册拦截器
<mvc:interceptors> <mvc:interceptor> <!--配置当前拦截器的url拦截规则,**表示代表拦截当前目录下及其子目录的所有url--> <mvc:mapping path="/**"/> <!-- <mvc:exclude-mapping path=""/> : 可以排除一些url的拦截 --> <bean class="com.huier.interceptor.MyInterceptor01"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.huier.interceptor.MyInterceptor02"/> </mvc:interceptor> </mvc:interceptors>
-
结果
/* MyInterceptor01 preHandle MyInterceptor02 preHandle MyInterceptor02 postHandle MyInterceptor01 postHandle MyInterceptor02 afterCompletion MyInterceptor01 afterCompletion */
第2节 处理multipart形式的数据
文件上传
原生Servlet处理上传的文件数据,springmvc又是对servlet的封装
-
引入坐标
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>
-
配置文件上传解析器
<!--配置⽂件上传解析器,id是固定的multipartResolver--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--设置上传⼤⼩,单位字节--> <property name="maxUploadSize" value="1000000000"/> </bean>
-
前端
<%-- 1 method="post" 2 enctype="multipart/form-data" 3 type="file" --%> <form method="post" enctype="multipart/form-data" action="/demo/upload"> <input type="file" name="uploadFile"/> <input type="submit" value="上传"/> </form>
-
后端
@RequestMapping("upload") public String upload(MultipartFile uploadFile, HttpServletRequest request) throws IOException { //1.获取文件原名 String originalFilename = uploadFile.getOriginalFilename(); //2.获取文件扩展名 String extendName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1, originalFilename.length()); //3.产生唯一的id String uuid = UUID.randomUUID().toString(); //4.新的文件名 String newName = uuid + "." + extendName; String realPath = "D:\\uploads"; //5.按照日期将文件分别保管 String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); File floder = new File(realPath + "\\" + datePath); if (!floder.exists()) { floder.mkdirs(); } //6.执行 uploadFile.transferTo(new File(floder, newName)); return "success"; }
第3节 在控制器中控制异常
-
编写全局异常处理类
@RestControllerAdvice//返回 public class GlobalException { @ExceptionHandler(Exception.class) public String doException(Exception ex){ return "嘿嘿,异常你哪里跑?"; } }
-
开启注解扫描,这个要记得
<context:component-scan base-package="扫描的包"/>
第4节 基于Flash属性的跨重定向请求数据传递
MVC对重定向又进一步的封装
return "redirect:handle01?name=" + name;
但是上述拼接参数的方法属于get请求,携带参数长度有限制,参数安全性也不高,此时,我们可以使SpringMVC提供的flflash属性机制,向上下文中添加flflash属性,框架会在session中记录该属性值,当跳转到页面之后框架会自动删除flflash属性,不需要我们手动删除,通过这种方式进行重定向参数传递,参数长度和安全性都得到了保障,如下:
/**
* SpringMVC 重定向时参数传递的问题
* 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A
* url不会变,参数也不会丢失,⼀个请求
* 重定向:A 找 B 借钱400,B 说我没有钱,你找别⼈借去,那么A ⼜带着400块的借钱需求找到
C
* url会变,参数会丢失需要重新携带参数,两个请求
*/
@RequestMapping("/handleRedirect")
public String handleRedirect(String name, RedirectAttributes
redirectAttributes) {
//return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有局限
// addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在跳转到页面之后该属性销毁
redirectAttributes.addFlashAttribute("name",name);
return "redirect:handle01";
}
第三部分 手写MVC框架
- 回顾SpringMVC执行的大致原理,后续根据这个模仿手写自己的mvc框架
- tomcat加载web.xml,配置了前端控制器,前端控制器加载指定的文件springmvc.xml
- 包扫描注解,扫描注解@Controller @Service @RequestMapping @AutoWired
- IOC容器相应的初始化以及Bean的生成
- Spring MVC相关组件的初始化,建立url和method的映射关系—HandlerMapping
- 等待请求,处理请求
实际开发
-
创建项目,pom+web工程
-
引入坐标
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> </dependencies>
-
创建自定义的前端控制器
//在前面的学习中我们可以认为,mvc是对Servlet的进一步封装,全局只有一个Servlet就是前端控制器 public class MyDispatchServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } }
-
web.xml中配置自定义的前端控制器
<!--配置前端控制器,当tomcat加载web时初始化mvc--> <servlet> <servlet-name>myDispatcherServlet</servlet-name> <servlet-class>com.huier.mvcframework.MyDispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>myDispatcherServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
-
创建初始注解
@Documented @Target(ElementType.FIELD)//作用在属性上 @Retention(RetentionPolicy.RUNTIME) public @interface MyAutoWired { String value() default ""; } @Documented @Target(ElementType.TYPE)//作用在方法上 @Retention(RetentionPolicy.RUNTIME) public @interface MyController { String value() default ""; } @Documented @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value() default ""; } @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyService { String value() default ""; }
-
搭建基本骨架
在properties文件中配置扫描的包
scanPackage=com.huier.demo
在web.xml中配置加载前端控制器时加载该properties文件
<servlet> <servlet-name>myDispatcherServlet</servlet-name> <servlet-class>com.huier.mvcframework.MyDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:springmvc.properties</param-value> </init-param> </servlet>
在MyDispatcherServlet中重写init()方法,在加载MyDispatcherServlet之前完成一些基本操作
@Override public void init(ServletConfig config) throws ServletException { //1.加载配置文件 String contextConfigLocation = config.getInitParameter("contextConfigLocation"); doLoadConfig(contextConfigLocation); //2.扫描相关类,扫描注解 doScan(""); //3.初始化Bean doInstance(); //4.实现依赖注入 doAutoWired(); //5.构造一个HandlerMapping处理器映射器,将处理好的url和相应的方法进行联系 initHandlerMapping() System.out.println("mvc初始化完成"); //6.等待请求进入 } //构造一个HandlerMapping处理器映射器,将处理好的url和相应的方法进行联系 private void initHandlerMapping() { } //维护注入 private void doAutoWired() { } //IOC容器 private void doInstance() { } //扫描类 private void doScan(String scanPackage) { } //加载配置文件 private void doLoadConfig(String contextConfigLocation) { } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //处理请求 }
-
完善加载配置文件
在MyDispatcherServlet定义一个Properties对象接收读取的信息
private Properties properties = new Properties();
完善doLoadConfig方法
//加载配置文件 private void doLoadConfig(String contextConfigLocation) { InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { properties.load(in); } catch (IOException e) { e.printStackTrace(); } }
-
完善扫描步骤
从properties中获取要扫描的全限定包名,并传入doScan()方法中
doScan(properties.getProperty("scanPackage"));
因为我们要扫描指定包并获得其中的类,所以我们需要得到包在磁盘上的绝对路径,扫描该文件夹即可,并且使用容器将所有的类名保存起来
private List<String> classNames = new ArrayList<String>();//缓存指定包下的所有全限定类名
//扫描类 private void doScan(String scanPackage) { //获得文件在磁盘上的绝对位置 //从com.huier.demo => com/huier/huier String absolutePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/"); File curFile = new File(absolutePath);//获得文件夹路径 File[] files = curFile.listFiles(); for (File file : files) { if(file.isDirectory()){ doScan(scanPackage + "."+ file.getName()); }else if(file.getName().endsWith(".class")){ String className = scanPackage + "." + file.getName().replaceAll(".class",""); classNames.add(className); } } }
-
初始化Bean
编写相应的测试
Service层
public interface DemoService { void outPut(); }
@MyService("demoService") public class DemoServiceImpl implements DemoService { public void outPut() { System.out.println("DemoServiceImpl ..."); } }
Controller层
@MyController @MyRequestMapping("/demo") public class DemoController { @MyAutoWired DemoService demoService; @MyRequestMapping("/demo1") public String demo1(HttpServletRequest request, HttpServletResponse response){ demoService.outPut(); return "ok"; } }
使用Map集合缓存Bean对象
private Map<String,Object> ioc = new HashMap<String, Object>();
初始化IOC
//IOC容器 private void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException { if(classNames.size() == 0){ return; } for(int i = 0;i < classNames.size();i++){//指定包下的所有的全类名的遍历 String className = classNames.get(i);//获得全类名 //反射 Class<?> aClass = Class.forName(className);//获得Class对象 //需要区分Controller和Service,因为Service是面向接口开发的 if(aClass.isAnnotationPresent(MyController.class)){//如果是Controller层,则直接实例化并加入ioc String simpleName = aClass.getSimpleName(); String newName = lowerFirst(simpleName); Object instance = aClass.newInstance(); ioc.put(newName,instance); }else if(aClass.isAnnotationPresent(MyService.class)){//如果是Service层,则需要判断是否有指定名称 MyService annotation = aClass.getAnnotation(MyService.class);//获得注解 String beanName = annotation.value();//获得注解中的value值 if(!"".equals(beanName.trim())){//如果value不为空,则使用指定的值作为key ioc.put(beanName,aClass.newInstance()); }else{//否则 使用类名小写首字母作为key beanName = lowerFirst(aClass.getSimpleName()); ioc.put(beanName,aClass.newInstance()); } //因为Service是面向接口开发的,需要给每一个父接口也存入这个对象实例 Class<?>[] interfaces = aClass.getInterfaces(); for (Class<?> anInterface : interfaces) { String interfaceName = anInterface.getName(); ioc.put(interfaceName,aClass.newInstance()); } } } } private String lowerFirst(String simpleName) { char[] chars = simpleName.toCharArray(); if(chars[0] >= 'A' && chars[0] <= 'Z'){ chars[0] += 32; } return String.valueOf(chars); }
-
完善依赖注入
//依赖注入 private void doAutoWired() throws IllegalAccessException { if(ioc.size() == 0){ return; } Set<Map.Entry<String, Object>> entries = ioc.entrySet(); for (Map.Entry<String, Object> entry : entries) { //获得字段 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if(!field.isAnnotationPresent(MyAutoWired.class)){ continue; } //有依赖注入的注解 MyAutoWired annotation = field.getAnnotation(MyAutoWired.class); String beanName = annotation.value();//需要注入的Bean的id if("".equals(beanName.trim())){//如果是空 beanName = field.getType().getName();//接口注入 } //赋值 field.setAccessible(true); field.set(entry.getValue(), ioc.get(beanName)); } } }
-
完善url和方法之间的映射关系
使用容器维护关系
private Map<String,Method> handlerMapping = new HashMap<String, Method>();
//构造一个HandlerMapping处理器映射器,将处理好的url和相应的方法进行联系 private void initHandlerMapping() { if(ioc.isEmpty()){ return; } Set<Map.Entry<String, Object>> entries = ioc.entrySet(); for (Map.Entry<String, Object> entry : entries) { Class<?> aClass = entry.getValue().getClass(); if(!aClass.isAnnotationPresent(MyController.class)){ continue; } String baseUrl = ""; if(aClass.isAnnotationPresent(MyRequestMapping.class)){ MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class); baseUrl = annotation.value();//demo } //处理方法 Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { if(!declaredMethod.isAnnotationPresent(MyController.class)){ continue; } MyController annotation = declaredMethod.getAnnotation(MyController.class); String childrenUrl = annotation.value();//demo1 String url = baseUrl + childrenUrl; //建立联系 handlerMapping.put(url,declaredMethod); } } }
-
测试
mvc初始化完成
表示大体的框架基本没有什么问题
-
完善doPost方法,根据路径从handlerMapping中映射获取具体的方法实现
出现了新的问题,虽然可以获得方法对象但是没有办法去调用,缺少对象和参数,所以我们应该再对映射关系进行封装,改造initHandlerMapping方法
创建Handler类来存储相依的信息,包括对象、方法、url、参数
public class Handler { private Object controller;//具体执行方法的对象 private Method method; private String pattern; private Map<String,Integer> paramIndexMapping;//参数名称+索引 public Handler(Object controller, Method method, String pattern) { this.controller = controller; this.method = method; this.pattern = pattern; this.paramIndexMapping = new HashMap<>(); } public Object getController() { return controller; } public void setController(Object controller) { this.controller = controller; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public Map<String, Integer> getParamIndexMapping() { return paramIndexMapping; } public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) { this.paramIndexMapping = paramIndexMapping; } }
改造initHandlerMapping()类
private List<Handler> handlerMapping = new ArrayList<Handler>();
private void initHandlerMapping() { if(ioc.isEmpty()) {return;} for(Map.Entry<String,Object> entry: ioc.entrySet()) { // 获取ioc中当前遍历的对象的class类型 Class<?> aClass = entry.getValue().getClass(); if(!aClass.isAnnotationPresent(MyController.class)) {continue;} String baseUrl = ""; if(aClass.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class); baseUrl = annotation.value(); // 等同于/demo } // 获取方法 Method[] methods = aClass.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; // 方法没有标识MyRequestMapping,就不处理 if(!method.isAnnotationPresent(MyRequestMapping.class)) {continue;} // 如果标识,就处理 MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class); String methodUrl = annotation.value(); String url = baseUrl + methodUrl; // 把method所有信息及url封装为一个Handler Handler handler = new Handler(entry.getValue(),method, url); // 计算方法的参数位置信息 Parameter[] parameters = method.getParameters(); for (int j = 0; j < parameters.length; j++) { Parameter parameter = parameters[j]; if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) { // 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j); }else{ handler.getParamIndexMapping().put(parameter.getName(),j); // <name,2> } } // 建立url和method之间的映射关系(map缓存起来) handlerMapping.add(handler); } } }
完善doPost()方法
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //处理请求 // String requestURI = req.getRequestURI(); //需要传入对象和参数 Handler handler = getHandler(req); if(handler == null){ resp.getWriter().write("404 NOT FOUND"); return; } //参数绑定 //获取所有参数类型数组 Class<?>[] parameterTypes = handler.getMethod().getParameterTypes(); Object[] paramValues = new Object[parameterTypes.length]; Map<String,String[]> parameterMap = req.getParameterMap(); //遍历传过来的所有参数,填充普通参数 for (Map.Entry<String, String[]> param : parameterMap.entrySet()) { String value = StringUtils.join(param.getValue(), ",");//数组拼接 //填充 if(handler.getParamIndexMapping().containsKey(param.getKey())){ Integer index = handler.getParamIndexMapping().get(param.getKey());//位置 paramValues[index] = value;//填充对应的位置 } } if(handler.getParamIndexMapping().containsKey(HttpServletRequest.class.getSimpleName())){ paramValues[handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName())] = req; } if(handler.getParamIndexMapping().containsKey(HttpServletResponse.class.getSimpleName())){ paramValues[handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName())] = resp; } try { handler.getMethod().invoke(handler.getController(),paramValues); } catch (Exception e) { e.printStackTrace(); } } private Handler getHandler(HttpServletRequest req) { if(handlerMapping.isEmpty()){ return null; } String url = req.getRequestURI(); for (Handler handler : handlerMapping) { if(handler.getPattern().equals(url)){ return handler; } } return null; }