网站建设前期如何做好市场定位分析,服装网站技术解决方案,帮企业外卖网站做推,wordpress打字特效目录
◆ SpringMVC简介
- SpringMVC概述
- SpringMVC快速入门
- Controller中访问容器中的Bean
- SpringMVC关键组件浅析
◆ SpringMVC的请求处理
- 请求映射路径的配置
- 请求数据的接收
- Javaweb常用对象获取
- 请求静态资源
- 注解驱动 标签
◆ SpringMV…目录
◆ SpringMVC简介
- SpringMVC概述
- SpringMVC快速入门
- Controller中访问容器中的Bean
- SpringMVC关键组件浅析
◆ SpringMVC的请求处理
- 请求映射路径的配置
- 请求数据的接收
- Javaweb常用对象获取
- 请求静态资源
- 注解驱动 标签
◆ SpringMVC的响应处理
- 传统同步业务数据响应
- 前后端分离异步业务数据响应
◆ SpringMVC的拦截器
- 拦截器 Interceptor 简介
- 拦截器快速入门
- 拦截器执行顺序
- 拦截器执行原理
◆ SpringMVC的全注解开发
- spring-mvc.xml 中组件转化为注解形式
- DispatcherServlet加载核心配置类
- 消除web.xml
◆ SpringMVC的组件原理剖析
- 前端控制器初始化
- 前端控制器执行主流程
◆ SpringMVC的异常处理机制
- SpringMVC 异常的处理流程
- SpringMVC 的异常处理方式
- 异常处理机制原理剖析
- SpringMVC 常用的异常解析器 ◆ SpringMVC简介
- SpringMVC概述
SpringMVC是一个基于Spring开发的MVC轻量级框架Spring3.0后发布的组件SpringMVC和Spring可以无缝整合使用DispatcherServlet作为前端控制器且内部提供了处理器映射器、处理器适配器、视图解析器等组件可以简化JavaBean封装Json转化、文件上传等操作。 - SpringMVC快速入门 导入Spring整合SpringMVC的坐标
dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.3.7/version
/dependency编写一个控制器Controller配置映射信息
Controller
public class UserController { RequestMapping(/show) public String show(){System.out.println(show 执行....);//视图跳转到index.jspreturn /index.jsp; }
}在web.xml中配置SpringMVC的前端控制器ServletDispatcher
servletservlet-nameDispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class!--指定springMVC配置文件位置--init-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-mvc.xml/param-value/init-param!--服务器启动就创建--load-on-startup2/load-on-startup
/servlet
servlet-mappingservlet-nameDispatcherServlet/servlet-nameurl-pattern//url-pattern
/servlet-mapping创建springMVC的核心配置文件 spring-mvc.xml并配置组件扫描web层
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/xmlSchema-instance xmlns:mvchttp://www.springframework.org/schema/mvc xmlns:contexthttp://www.springframework.org/schema/context xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
!-- 组件扫描web层 --context:component-scan base-packagecom.itheima.controller/
/beans- Controller中访问容器中的Bean
DispatcherServlet在进行初始化时加载的spring-mvc.xml配置文件创建的SpringMVC容器那么web层Controller被扫描进入到了容器中而之前Spring容器中的Service是否可以获取到呢下面搭建Spring的web环境进行验证
创建一个applicationContext.xml文件
!-- 组件扫描非web层 --
context:component-scan base-packagecom.itheima!--排除com.itheima包下使用Controller注解的类--context:exclude-filter typeannotationexpressionorg.springframework.stereotype.Controller/
/context:component-scan在web.xml中配置ContextLoaderListener
!--配置ContextLoaderListener--
context-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:applictionContext.xml/param-value
/context-param
listenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class
/listener编写UserService和UserServiceImpl
public interface UserService { public void show();
}Service(userService)
public class UserServiceImpl implements UserService { Overridepublic void show() {System.out.println(UserServiceImpl show running ... ...); }
}修改UserController从Spring容器中匹配Service进行注入
Controller
public class UserController { Autowiredprivate UserService userService; RequestMapping(/show)public String show(){System.out.println(show 执行....);//调用userService的show方法userService.show();//视图跳转到index.jsp return /index.jsp;}
}- SpringMVC关键组件浅析
当请求到达服务器时是哪个组件接收的请求是哪个组件帮我们找到的Controller是哪个组件帮我们调用的方法又是哪个组件最终解析的视图 组件 描述 常用组件 处理器映射器HandlerMapping 匹配映射路径对应的Handler返回可执行的处理器链对象HandlerExecutionChain对象 RequestMappingHandlerMapping 处理器适配器HandlerAdapter 匹配HandlerExecutionChain对应的适配器进行处理器调用返回视图模型对象 RequestMappingHandlerAdapter 视图解析器ViewResolver 对视图模型对象进行解析 InternalResourceViewResolver SpringMVC的默认组件SpringMVC 在前端控制器 DispatcherServlet加载时就会进行初始化操作在进行初始化时就会加载SpringMVC默认指定的一些组件这些默认组件配置在 DispatcherServlet.properties 文件中该文件存在与spring-webmvc-5.3.7.jar包下的 org\springframework\web\servlet\DispatcherServlet.properties
org.springframework.web.servlet.HandlerMappingorg.springframework.web.servlet.handler.BeanNameUrl HandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\ org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapterorg.springframework.web.servlet.mvc.HttpRequestHand lerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.ViewResolverorg.springframework.web.servlet.view.InternalResource ViewResolver这些默认的组件是在DispatcherServlet中进行初始化加载的在DispatcherServlet中存在集合存储着这些组件SpringMVC的默认组件会在 DispatcherServlet 中进行维护但是并没有存储在与SpringMVC的容器中
public class DispatcherServlet extends FrameworkServlet {//存储处理器映射器private ListHandlerMapping handlerMappings;//存储处理器适配器private ListHandlerAdapter handlerAdapters;//存储视图解析器private ListViewResolver viewResolvers;// ... 省略其他代码 ...
}配置组件代替默认组件如果不想使用默认组件可以将替代方案使用Spring Bean的方式进行配置例如在spring-mvc.xml中配置RequestMappingHandlerMapping
bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping/当我们在Spring容器中配置了HandlerMapping则就不会在加载默认的HandlerMapping策略了原理比较简单DispatcherServlet 在进行HandlerMapping初始化时先从SpringMVC容器中找是否存在HandlerMapping如果存在直接取出容器中的HandlerMapping在存储到 DispatcherServlet 中的handlerMappings集合中去。
◆ SpringMVC的请求处理
- 请求映射路径的配置
配置映射路径映射器处理器才能找到Controller的方法资源目前主流映射路径配置方式就是RequestMapping 相关注解 作用 使用位置 RequestMapping 设置控制器方法的访问资源路径可以接收任何请求 方法和类上 GetMapping 设置控制器方法的访问资源路径可以接收GET请求 方法和类上 PostMapping 设置控制器方法的访问资源路径可以接收POST请求 方法和类上
RequestMapping注解主要使用在控制器的方法上用于标识客户端访问资源路径常用的属性有value、path、method、headers、params等。当RequestMapping只有一个访问路径需要指定时使用value属性、path属性或省略value和path当有多个属性时value和path不能省略
RequestMapping(value /show)//使用value属性指定一个访问路径
public String show(){}RequestMapping(value {/show,/haohao,/abc})//使用value属性指定多个访问路径
public String show(){}RequestMapping(path /show)//使用path属性指定一个访问路径
public String show(){}RequestMapping(path {/show,/haohao,/abc})//使用path属性指定多个访问路径
public String show(){}RequestMapping(/show)//如果只设置访问路径时value和path可以省略
public String show(){} RequestMapping({/show,/haohao,/abc})
public String show(){}当RequestMapping 需要限定访问方式时可以通过method属性设置
//请求地址是/show,且请求方式必须是POST才能匹配成功
RequestMapping(value /show,method RequestMethod.POST)
public String show(){}method的属性值是一个枚举类型源码如下
public enum RequestMethod { GET,HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;private RequestMethod() {}
}GetMapping当请求方式是GET时我们可以使用GetMapping替代RequestMapping
GetMapping(/show)
public String show(){}PostMapping当请求方式是POST时我们可以使用PostMapping替代RequestMapping
PostMapping(/show)
public String show(){}RequestMapping 在类上使用RequestMapping 、GetMapping、PostMapping还可以使用在Controller类上使用在类上后该类所有方法都公用该RequestMapping设置的属性访问路径则为类上的映射地址方法上的映射地址例如
Controller
RequestMapping(/xxx)
public class UserController implements ApplicationContextAware, ServletContextAware { GetMapping(/aaa)public ModelAndView aaa(HttpServletResponse response) throws IOException, ModelAndViewDefiningException {return null; }
}此时的访问路径为/xxx/aaa- 请求数据的接收
接收普通请求数据当客户端提交的数据是普通键值对形式时直接使用同名形参接收即可
usernamehaohaoage35GetMapping(/show)
public String show(String username, int age){ System.out.println(usernameage); return /index.jsp;
}接收普通请求数据当请求参数有特殊格式数据如日期时
usernamehaohaoage35birthday1986/01/01GetMapping(/show)
public String show(String username,int age,Date birthday){ System.out.println(usernameagebirthday); return /index.jsp;
}Date可以正常接收因为Spring内置的类型解析器可以识别的日期格式是 yyyy/MM/dd但是如果我们提交其他格式例如yyyy-MM-dd 时类型转换会报错如下 解决方案使用DateTimeFormat 指定日期格式修改UserController如下
GetMapping(/show)
public String show(String username,int age,DateTimeFormat(pattern yyyy-MM-dd) Date birthday){System.out.println(usernameagebirthday); return /index.jsp;
}接收普通请求数据当请求参数的名称与方法参数名不一致时可以使用RequestParam注解进行标注
usernamehaohaoage35GetMapping(/show)
public String show(RequestParam(name username,required true) String name, int age){ System.out.println(nameage);return /index.jsp;
}接收实体JavaBean属性数据单个JavaBean数据提交的参数名称只要与Java的属性名一致就可以进行自动封装
usernamehaohaoage35hobbieseathobbiessleeppublic class User {private String username; private Integer age; private String[] hobbies; private Date birthday; private Address address;//... 省略get和set方法 ...
}GetMapping(/show)
public String show(User user){ System.out.println(user); return /index.jsp;
}接收实体JavaBean属性数据嵌套JavaBean数据提交的参数名称用 . 去描述嵌套对象的属性关系即可
usernamehaohaoaddress.citytianjinaddress.areajinghai同上JavaBean中如果有日期数据且日期格式不符合yyyy/MM/dd时需要通过DateTimeFormat指定日期格式
public class User {private String username; private Integer age; private String[] hobbies;DateTimeFormat(pattern yyyy-MM-dd) private Date birthday;private Address address;//... 省略get和set方法 ...
}接收数组或集合数据客户端传递多个同名参数时可以使用数组接收
hobbieseathobbiessleepGetMapping(/show)
public String show(String[] hobbies){ for (String hobby : hobbies) {System.out.println(hobby);} return /index.jsp;
}接收数组或集合数据客户端传递多个同名参数时也可以使用单列集合接收但是需要使用RequestParam告知框架传递的参数是要同名设置的不是对象属性设置的
GetMapping(/show)
public String show(RequestParam ListString hobbies){for (String hobby : hobbies) { System.out.println(hobby);}return /index.jsp;
}接收数组或集合数据客户端传递多个不同命参数时也可以使用MapString,Object 进行接收同样需要用RequestParam 进行修饰
usernamehaohaoage18PostMapping(/show)
public String show(RequestParam MapString,Object params){ params.forEach((key,value)-{System.out.println(keyvalue); });return /index.jsp;
}接收Json数据格式数据Json数据都是以请求体的方式提交的且不是原始的键值对格式的所以要使用RequestBody注解整体接收该数据。
{username:haohao, age:18, hobbies:[eat,sleep], birthday:1986-01-01, address:{city:tj, area:binhai}
} PostMapping(/show6)
public String show6(RequestBody String body){System.out.println(body); return /index.jsp;
}使用Json工具 jackson 将Json格式的字符串转化为JavaBean进行操作
//导入maven坐标依赖
dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.9.0/version
/dependency PostMapping(/show)
public String show(RequestBody String body) throws IOException {System.out.println(body);//获得ObjectMapperObjectMapper objectMapper new ObjectMapper();//将json格式字符串转化成指定的UserUser user objectMapper.readValue(body, User.class); System.out.println(user);return /index.jsp;
}配置RequestMappingHandlerAdapter指定消息转换器就不用手动转换json格式字符串了
bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapterproperty namemessageConverterslistbean classorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter//list/property
/bean PostMapping(/show)
public String show(RequestBody User user){System.out.println(user); return /index.jsp;
}接收Json数据格式数据使用Map接收json格式字符串
PostMapping(/show)
public String show(RequestBody Map map){System.out.println(map); return /index.jsp;
}接收Restful风格数据 什么是Rest风格
RestRepresentational State Transfer表象化状态转变表述性状态转变在2000年被提出基于HTTP、URI、xml、JSON等标准和协议支持轻量级、跨平台、跨语言的架构设计。是Web服务的一种新网络应用程序的设计风格和开发方式。
Restful风格的请求常见的规则有如下三点
● 用URI表示某个模块资源资源名称为名词 模块 URI资源 用户模块 user http://localhost/user 商品模块 product http://localhost/product 账户模块 account http://localhost/account 日志模块 log http://localhost/log
● 用请求方式表示模块具体业务动作例如GET表示查询、POST表示插入、PUT表示更新、DELETE表示删除 URI资源 请求方式 参数 解释 http://localhost/user/100 GET 存在URL地址中100 查询id100的User数据 http://localhost/user POST 存在请求体中Json {username:haohao,age:18} 插入User数据 http://localhost/user PUT 存在请求体中Json {id:100,username:haohao,age:18} 修改id100的User数据 http://localhost/user/100 DELETE 存在URL地址中100 删除id100的User数据 http://localhost/product/5 GET 存在URL地址中5 查询id5的Product数据 http://localhost/product POST 存在请求体中Json{proName:小米手机 ,price:1299} 插入Product数据 http://localhost/product PUT 存在请求体中Json{id:5,proName:小米手机,price:1299} 修改id5的Product数据 http://localhost/product/5 DELETE 存在URL地址中5 删除id5的Product数据
● 用HTTP响应状态码表示结果国内常用的响应包括三部分状态码、状态信息、响应数据
{code:200, message:成功, data:{username:haohao, age:18}
}{code:300, message:执行错误, data:,
}
接收Restful风格数据Restful请求数据一般会在URL地址上携带可以使用注解 PathVariable(占位符参数名称)
http://localhost/user/100 PostMapping(/user/{id})
public String findUserById(PathVariable(id) Integer id){ System.out.println(id);return /index.jsp;
}请求URL资源地址包含多个参数情况
http://localhost/user/haohao/18 PostMapping(/user/{username}/{age})public String findUserByUsernameAndAge(PathVariable(username) String username,PathVariable(age) Integer age){System.out.println(usernameage); return /index.jsp;
}接收文件上传的数据文件上传的表单需要一定的要求如下
● 表单的提交方式必须是POST
● 表单的enctype属性必须是multipart/form-data
● 文件上传项需要有name属性
form action enctypemultipart/form-datainput typefile namemyFile
/form服务器端由于映射器适配器需要文件上传解析器而该解析器默认未被注册所以手动注册
!--配置文件上传解析器注意id的名字是固定写法--
bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolverproperty namedefaultEncoding valueUTF-8/!--文件的编码格式 默认是ISO8859-1--property namemaxUploadSizePerFile value1048576/!--上传的每个文件限制的大小 单位字节--property namemaxUploadSize value3145728/!--上传文件的总大小--property namemaxInMemorySize value1048576/!--上传文件的缓存大小--
/bean而CommonsMultipartResolver底层使用的Apache的是Common-fileuplad等工具API进行的文件上传
dependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactIdversion1.4/version
/dependencydependencygroupIdcommons-io/groupIdartifactIdcommons-io/artifactIdversion2.7/version
/dependency使用MultipartFile类型接收上传文件
PostMapping(/fileUpload)
public String fileUpload(RequestBody MultipartFile myFile) throws IOException { System.out.println(myFile);//获得上传的文件的流对象InputStream inputStream myFile.getInputStream();//使用commons-io存储到C:\haohao\abc.txt位置FileOutputStream outputStream newFileOutputStream(C:\\Users\\haohao\\myFile.getOriginalFilename()); IOUtils.copy(inputStream,outputStream);//关闭资源inputStream.close(); outputStream.close();return /index.jsp;
}如果进行多文件上传的话则使用MultipartFile数组即可
接收Http请求头数据接收指定名称的请求头
GetMapping(/headers)
public String headers(RequestHeader(Accept-Encoding) String acceptEncoding){ System.out.println(Accept-Encoding:acceptEncoding);return /index.jsp;
}接收所有的请求头信息
GetMapping(/headersMap)
public String headersMap(RequestHeader MapString,String map){ map.forEach((k,v)-{System.out.println(k:v); });return /index.jsp;
}获得客户端携带的Cookie数据
GetMapping(/cookies)
public String cookies(CookieValue(value JSESSIONID,defaultValue ) String jsessionid){ System.out.println(jsessionid);return /index.jsp;
}获得转发Request域中数据在进行资源之间转发时有时需要将一些参数存储到request域中携带给下一个资源
GetMapping(/request1)
public String request1(HttpServletRequest request){//存储数据request.setAttribute(username,haohao); return forward:/request2;
} GetMapping(/request2)
public String request2(RequestAttribute(username) String username){ System.out.println(username);return /index.jsp;
}
请求参数乱码的解决方案Spring已经提供好的CharacterEncodingFilter来进行编码过滤
!--配置全局的编码过滤器--
filterfilter-nameCharacterEncodingFilter/filter-namefilter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-classinit-paramparam-nameencoding/param-nameparam-valueUTF-8/param-value/init-param
/filter
filter-mappingfilter-nameCharacterEncodingFilter/filter-nameurl-pattern/*/url-pattern
/filter-mapping- Javaweb常用对象获取
获得Javaweb常见原生对象有时在我们的Controller方法中需要用到Javaweb的原生对象例如Request、Response等我们只需要将需要的对象以形参的形式写在方法上SpringMVC框架在调用Controller方法时会自动传递实参
GetMapping(/javawebObject)
public String javawebObject(HttpServletRequest request, HttpServletResponse response, HttpSession session){System.out.println(request); System.out.println(response); System.out.println(session); return /index.jsp;
}- 请求静态资源
静态资源请求失效的原因当DispatcherServlet的映射路径配置为 / 的时候那么就覆盖的Tomcat容器默认的缺省Servlet在Tomcat的config目录下有一个web.xml 是对所有的web项目的全局配置其中有如下配置
servletservlet-namedefault/servlet-nameservlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-classload-on-startup1/load-on-startup
/servlet
servlet-mappingservlet-namedefault/servlet-nameurl-pattern//url-pattern
/servlet-mappingurl-pattern配置为 / 的Servlet称其为缺省的Servlet作用是当其他Servlet都匹配不成功时就找缺省的Servlet静态资源由于没有匹配成功的Servlet所以会找缺省的DefaultServlet该DefaultServlet具备二次去匹配静态资源的功能。但是我们配置DispatcherServlet后就将其覆盖掉了而DispatcherServlet会将请求的静态资源的名称当成Controller的映射路径去匹配即静态资源访问不成功了
静态资源请求的三种解决方案
第一种方案可以再次激活Tomcat的DefaultServletServlet的url-pattern的匹配优先级是精确匹配目录匹配扩展名匹配缺省匹配所以可以指定某个目录下或某个扩展名的资源使用DefaultServlet进行解析
servlet-mappingservlet-namedefault/servlet-nameurl-pattern/img/*/url-pattern
/servlet-mapping
servlet-mappingservlet-namedefault/servlet-nameurl-pattern*.html/url-pattern
/servlet-mapping第二种方案在spring-mvc.xml中去配置静态资源映射匹配映射路径的请求到指定的位置去匹配资源
!-- mapping是映射资源路径location是对应资源所在的位置 --
mvc:resources mapping/img/* location/img//
mvc:resources mapping/css/* location/css//
mvc:resources mapping/css/* location/js//
mvc:resources mapping/html/* location/html//第三种方案在spring-mvc.xml中去配置 mvc:default-servlet-handler 该方式是注册了一个DefaultServletHttpRequestHandler 处理器静态资源的访问都由该处理器去处理这也是开发中使用最多的
mvc:default-servlet-handler/- 注解驱动 mvc:annotation-driven 标签
静态资源配置的第二第三种方式可以正常访问静态资源了但是Controller又无法访问了报错404即找不到对应的资源
第二种方式是通过SpringMVC去解析mvc命名空间下的resources标签完成的静态资源解析第三种方式式通过SpringMVC去解析mvc命名空间下的default-servlet-handler标签完成的静态资源解析根据前面所学习的自定义命名空间的解析的知识可以发现不管是以上哪种方式最终都会注册SimpleUrlHandlerMapping
public BeanDefinition parse(Element element, ParserContext context) {//创建SimpleUrlHandlerMapping类型的BeanDefinition RootBeanDefinition handlerMappingDef new RootBeanDefinition(SimpleUrlHandlerMapping.class);//注册SimpleUrlHandlerMapping的BeanDefinition context.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
}又结合组件浅析知识点一旦SpringMVC容器中存在 HandlerMapping 类型的组件时前端控制器DispatcherServlet在进行初始化时就会从容器中获得HandlerMapping 不在加载 dispatcherServlet.properties中默认处理器映射器策略那也就意味着RequestMappingHandlerMapping不会被加载到了。
手动将RequestMappingHandlerMapping也注册到SpringMVC容器中就可以了这样DispatcherServlet在进行初始化时就会从容器中同时获得RequestMappingHandlerMapping存储到DispatcherServlet中名为handlerMappings的List集合中对RequestMapping 注解进行解析。
bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping/根据上面的讲解可以总结一下要想使用RequestMapping正常映射到资源方法同时静态资源还能正常访问还可以将请求json格式字符串和JavaBean之间自由转换就需要在spring-mvc.xml中尽心如下配置
!-- 显示配置RequestMappingHandlerMapping --
bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping/!-- 显示配置RequestMappingHandlerAdapter --
bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapterproperty namemessageConverterslistbean classorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter//list/property
/bean
!--配置DefaultServletHttpRequestHandler--
mvc:default-servlet-handler/这么复杂繁琐的配置是不是看上去有点头大Spring是个暖男将上述配置浓缩成了一个简单的配置标签那就是mvc的注解驱动该标签内部会帮我们注册RequestMappingHandlerMapping、注册RequestMappingHandlerAdapter并注入Json消息转换器等上述配置就可以简化成如下
!--mvc注解驱动--
mvc:annotation-driven/
!--配置DefaultServletHttpRequestHandler--
mvc:default-servlet-handler/PSmvc:annotation-driven 标签在不同的版本中注册的组件不同Spring 3.0.X 版本注册是DefaultAnnotationHandlerMapping 和 AnnotationMethodHandlerAdapter由于框架的发展从Spring 3.1.X开始注册组件变为 RequestMappingHandlerMapping和RequestMappingHandlerAdapter
◆ SpringMVC的响应处理 - 传统同步业务数据响应
响应数据主要分为两大部分
● 传统同步方式准备好模型数据在跳转到执行页面进行展示此方式使用越来越少了基于历史原因一些旧项目还在使用
● 前后端分离异步方式前端使用Ajax技术Restful风格与服务端进行Json格式为主的数据交互目前市场上几乎都是此种方式了。
传统同步业务在数据响应时SpringMVC又涉及如下四种形式
● 请求资源转发
● 请求资源重定向
● 响应模型数据
● 直接回写数据给客户端
请求资源转发 请求资源重定向 响应模型数据响应模型数据本质也是转发在转发时可以准备模型数
GetMapping(/forward5)
public ModelAndView forward5(ModelAndView modelAndView){//准备JavaBean模型数据User user new User(); user.setUsername(haohao);//设置模型modelAndView.addObject(user,user);//设置视图modelAndView.setViewName(/index.jsp); return modelAndView;
}直接回写数据直接通过方法的返回值返回给客户端的字符串但是SpringMVC默认的方法返回值是视图可以通过ResponseBody 注解显示的告知此处的返回值不要进行视图处理是要以响应体的方式处理的
GetMapping(/response2)
ResponseBody
public String response2() throws IOException { return Hello haohao!;
}- 前后端分离异步业务数据响应
● 同步方式回写数据是将数据响应给浏览器进行页面展示的而异步方式回写数据一般是回写给Ajax引擎的即谁访问服务器端服务器端就将数据响应给谁
● 同步方式回写的数据一般就是一些无特定格式的字符串而异步方式回写的数据大多是Json格式字符串
回写普通数据使用ResponseBody标注方法直接返回字符串即可
回写Json格式的字符串即将直接拼接Json格式的字符串或使用工具将JavaBean转换成Json格式的字符串回写
GetMapping(/response3)
ResponseBody
public String response3(HttpServletResponse response) { return {\username\:\haohao\,\age\:18};
} GetMapping(/response4)
ResponseBody
public String response4() throws JsonProcessingException {//创建JavaBeanUser user new User(); user.setUsername(haohao); user.setAge(18);//使用Jackson转换成json格式的字符串String json new ObjectMapper().writeValueAsString(user); return json;
}在SringMVC接收请求数据时客户端提交的Json格式的字符串也是使用Jackson进行的手动转换成JavaBean可以当我们使用了RequestBody时直接用JavaBean就接收了Json格式的数据原理其实就是SpringMVC底层做了转换此处ResponseBody也可以将JavaBean自动转换成Json格式字符串回响应 GetMapping(/response5)
ResponseBody
public User response5() throws JsonProcessingException {//创建JavaBeanUser user new User(); user.setUsername(haohao); user.setAge(18);//直接返回User对象return user;
}ResponseBody注解使用优化在进行前后端分离开发时Controller的每个方法都是直接回写数据的所以每个方法上都得写ResponseBody可以将ResponseBody写到Controller上那么该Controller中的所有方法都具备了返回响应体数据的功能了 Controller
ResponseBody
public class UserController{ GetMapping(/response7) public ResultInfo response7() {//省略其他代码return info; }GetMapping(/response5)public User response5() throws JsonProcessingException {//省略其他代码return user; }// ... 省略其他方法 ...
}进一步优化可以使用RestController替代Controller和ResponseBodyRestController内部具备的这两个注解的功能
RestController
public class UserController{ GetMapping(/response7) public ResultInfo response7() {//省略其他代码return info; }GetMapping(/response5)public User response5() throws JsonProcessingException {//省略其他代码return user; }// ... 省略其他方法 ...
}◆ SpringMVC的拦截器 - 拦截器 Interceptor 简介
SpringMVC的拦截器Interceptor规范主要是对Controller资源访问时进行拦截操作的技术当然拦截后可以进行权限控制功能增强等都是可以的。拦截器有点类似 Javaweb 开发中的Filter拦截器与Filter的区别如下图 由上图对Filter 和 Interceptor 做个对比 Filter技术 Interceptor技术 技术范畴 Javaweb原生技术 SpringMVC框架技术 拦截/过滤资源 可以对所有请求都过滤包括任何Servlet、Jsp、其他资源等 只对进入了SpringMVC管辖范围的才拦截主要拦截Controller请求 执行时机 早于任何Servlet执行 晚于DispatcherServlet执行
实现了HandlerInterceptor接口且被Spring管理的Bean都是拦截器接口定义如下
public interface HandlerInterceptor {default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Nullable ModelAndView modelAndView) throws Exception {}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Nullable Exception ex) throws Exception {}
}HandlerInterceptor接口方法的作用及其参数、返回值详解如下 作用 参数 返回值 preHandle 对拦截到的请求进行预处理返回true放行执行处理器方法false不放行 Handler是拦截到的Controller方法处理器 一旦返回false代表终止向后 执行所有后置方法都不执行最终方法只执行对应preHandle返回了true的 postHandle 在处理器的方法执行后对拦截到的请求进行后处理可以在方法中对模型数据和视图进行修改 Handler是拦截到的Controller方法处理器modelAndView是返回的模型视图对象 无 afterCompletion 视图渲染完成后(整个流程结束之后)进行最后的处理如果请求流程中有异常可以处理异常对象 Handler是拦截到的Controller方法处理器ex是异常对象
- 拦截器快速入门
编写MyInterceptor01实现HandlerInterceptor接口
public class MyInterceptor01 implements HandlerInterceptor { Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println(Controller方法执行之前...); return true;//放行} Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println(Controller方法执行之后...); }Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println(渲染视图结束整个流程完毕...); }
}配置Interceptor
!--配置拦截器--
mvc:interceptorsmvc:interceptor!--配置对哪些资源进行拦截操作--mvc:mapping path/*/bean classcom.itheima.interceptor.MyInterceptor01/bean/mvc:interceptor
/mvc:interceptors- 拦截器执行顺序
拦截器三个方法的执行顺序
当每个拦截器都是放行状态时三个方法的执行顺序如下 拦截器执行顺序取决于 interceptor 的配置顺序
mvc:interceptorsmvc:interceptormvc:mapping path/target/bean classcom.itheima.interceptor.MyInterceptor02/bean/mvc:interceptormvc:interceptormvc:mapping path/*/bean classcom.itheima.interceptor.MyInterceptor01/bean/mvc:interceptor
/mvc:interceptors- 拦截器执行原理
请求到来时先会使用组件HandlerMapping去匹配Controller的方法Handler和符合拦截路径的InterceptorHandler和多个Interceptor被封装成一个HandlerExecutionChain的对象HandlerExecutionChain 定义如下
public class HandlerExecutionChain {//映射的Controller的方法private final Object handler;//当前Handler匹配的拦截器集合private final ListHandlerInterceptor interceptorList;// ... 省略其他代码 ...
}在DispatcherServlet的doDispatch方法中执行拦截器
protected void doDispatch(HttpServletRequest request, HttpServletResponse response){//根据请求信息获得HandlerExecutionChainHandlerExecutionChain mappedHandler this.getHandler(request);//获得处理器适配器HandlerAdapter ha this.getHandlerAdapter(mappedHandler.getHandler());//执行Interceptor的前置方法前置方法如果返回false则该流程结束if (!mappedHandler.applyPreHandle(request, response)) { return;}//执行handler一般是HandlerMethodModelAndView mv ha.handle(processedRequest, response, mappedHandler.getHandler());//执行后置方法mappedHandler.applyPostHandle(processedRequest, response, mv);
//执行最终方法this.triggerAfterCompletion(processedRequest, response, mappedHandler, e);
}跟踪 HandlerExecutionChain的applyPreHandle方法源码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {//对interceptorList进行遍历,正向遍历,与此同时使用interceptorIndex进行计数for(int i 0; i this.interceptorList.size(); this.interceptorIndex i) {//取出每一个Interceptor对象HandlerInterceptor interceptor (HandlerInterceptor)this.interceptorList.get(i);//调用Interceptor的preHandle方法如果返回false则直接执行Interceptor的最终方法if (!interceptor.preHandle(request, response, this.handler)) {//执行Interceptor的最终方法this.triggerAfterCompletion(request, response, (Exception)null); return false;}}return true;
}跟踪 HandlerExecutionChain的applyPostHandle方法源码
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, Nullable ModelAndView mv) throws Exception {//对interceptorList进行遍历逆向遍历for(int i this.interceptorList.size() - 1; i 0; --i) {//取出每一个InterceptorHandlerInterceptor interceptor (HandlerInterceptor)this.interceptorList.get(i);//执行Interceptor的postHandle方法interceptor.postHandle(request, response, this.handler, mv);}
} 跟踪HandlerExecutionChain的triggerAfterCompletion方法源码
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Nullable Exception ex) {//逆向遍历interceptorList遍历的个数为执行的applyPreHandle次数-1for(int i this.interceptorIndex; i 0; --i) {//取出每一个InterceptorHandlerInterceptor interceptor (HandlerInterceptor)this.interceptorList.get(i); try {//执行Interceptor的afterCompletion方法interceptor.afterCompletion(request, response, this.handler, ex);} catch (Throwable var7) {logger.error(HandlerInterceptor.afterCompletion threw exception, var7); }}
} ◆ SpringMVC的全注解开发 - spring-mvc.xml 中组件转化为注解形式
xml配置文件使用核心配置类替代xml中的标签使用对应的注解替代
!-- 组件扫描web层 --
context:component-scan base-packagecom.itheima.controller/
!--注解驱动--
mvc:annotation-driven/
!--配置文件上传解析器--
bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver/
!--配置拦截器--
mvc:interceptorsmvc:interceptormvc:mapping path/*/bean classcom.itheima.interceptor.MyInterceptor01/bean/mvc:interceptor
/mvc:interceptors
!--配置DefaultServletHttpRequestHandler--
mvc:default-servlet-handler/● 组件扫描可以通过ComponentScan注解完成
● 文件上传解析器multipartResolver可以通过非自定义Bean的注解配置方式即Bean注解完成
Configuration ComponentScan(com.itheima.controller)
public class SpringMVCConfig {Beanpublic CommonsMultipartResolver multipartResolver(){CommonsMultipartResolver multipartResolver new CommonsMultipartResolver(); multipartResolver.setDefaultEncoding(UTF-8); multipartResolver.setMaxUploadSize(3145728); multipartResolver.setMaxUploadSizePerFile(1048576); multipartResolver.setMaxInMemorySize(1048576);return multipartResolver; }
}mvc:default-servlet-handler / 和 mvc:interceptor 怎么办呢SpringMVC 提供了一个注解叫做EnableWebMvc我们看一下源码内部通过Import 导入DelegatingWebMvcConfiguration类
Retention(RetentionPolicy.RUNTIME)
Target({ElementType.TYPE})
Documented
Import({DelegatingWebMvcConfiguration.class})
public interface EnableWebMvc {}Configuration(proxyBeanMethods false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {private final WebMvcConfigurerComposite configurers new WebMvcConfigurerComposite();//从容器中注入WebMvcConfigurer类型的BeanAutowired(required false)public void setConfigurers(ListWebMvcConfigurer configurers) { if (!CollectionUtils.isEmpty(configurers)) {this.configurers.addWebMvcConfigurers(configurers); }}//省略其他代码
}WebMvcConfigurer类型的Bean会被注入进来然后被自动调用所以可以实现WebMvcConfigurer接口完成一些解析器、默认Servlet等的指定WebMvcConfigurer接口定义如下
public interface WebMvcConfigurer {//配置默认Servet处理器default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { }//添加拦截器default void addInterceptors(InterceptorRegistry registry) { }//添加资源处理器default void addResourceHandlers(ResourceHandlerRegistry registry) { }//添加视图控制器default void addViewControllers(ViewControllerRegistry registry) { }//配置视图解析器default void configureViewResolvers(ViewResolverRegistry registry) { }//添加参数解析器default void addArgumentResolvers(ListHandlerMethodArgumentResolver resolvers) { }//... 省略其他代码 ...
}创建MyWebMvcConfigurer实现WebMvcConfigurer接口实现addInterceptors 和configureDefaultServletHandling方法
Component
public class MyWebMvcConfigurer implements WebMvcConfigurer { Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {//开启DefaultServlet可以处理静态资源了configurer.enable(); }Overridepublic void addInterceptors(InterceptorRegistry registry) {//创建拦截器对象进行注册//Interceptor的执行顺序也取决于添加顺序registry.addInterceptor(new MyInterceptor01()).addPathPatterns(/*); }
}最后在SpringMVC核心配置类上添加EnableWebMvc注解
Configuration ComponentScan(com.itheima.controller)
EnableWebMvc
public class SpringMVCConfig { Beanpublic CommonsMultipartResolver multipartResolver(){CommonsMultipartResolver multipartResolver new CommonsMultipartResolver(); multipartResolver.setDefaultEncoding(UTF-8); multipartResolver.setMaxUploadSize(3145728); multipartResolver.setMaxUploadSizePerFile(1048576); multipartResolver.setMaxInMemorySize(1048576);return multipartResolver; }
}- DispatcherServlet加载核心配置类
DispatcherServlet在进行SpringMVC配置文件加载时使用的是以下方式
!--配置springMVC前端控制器--
servletservlet-nameDispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class!--指定springMVC配置文件位置--init-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-mvc.xml/param-value/init-param!--服务器启动就创建--load-on-startup2/load-on-startup
/servlet
servlet-mappingservlet-nameDispatcherServlet/servlet-nameurl-pattern//url-pattern
/servlet-mapping现在是使用SpringMVCConfig核心配置类提替代的spring-mvc.xml怎么加载呢参照Spring的 ContextLoaderListener加载核心配置类的做法定义了一个AnnotationConfigWebApplicationContext通过代码注册核心配置类
public class MyAnnotationConfigWebApplicationContext extends AnnotationConfigWebApplicationContext {public MyAnnotationConfigWebApplicationContext(){//注册核心配置类super.register(SpringMVCConfig.class);}
}!--指定springMVC的applicationContext全限定名 --
init-paramparam-namecontextClass/param-nameparam-valuecom.itheima.config.MyAnnotationConfigWebApplicationContext/param-value
/init-param- 消除web.xml
目前几乎消除了配置文件但是web工程的入口还是使用的web.xml进行配置的如下
!--配置springMVC前端控制器--
servletservlet-nameDispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class!--指定springMVC的applicationContext全限定名 --init-paramparam-namecontextClass/param-nameparam-valuecom.itheima.config.MyAnnotationConfigWebApplicationContext/param-value/init-param!--服务器启动就创建--load-on-startup2/load-on-startup
/servlet
servlet-mappingservlet-nameDispatcherServlet/servlet-nameurl-pattern//url-pattern
/servlet-mapping● Servlet3.0环境中web容器提供了javax.servlet.ServletContainerInitializer接口实现了该接口后在对应的类加载路径的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件文件内容指定具体的ServletContainerInitializer实现类那么当web容器启动时就会运行这个初始化器做一些组件内的初始化工作
● 基于这个特性Spring就定义了一个SpringServletContainerInitializer实现了ServletContainerInitializer接口
● 而SpringServletContainerInitializer会查找实现了WebApplicationInitializer的类Spring又提供了一个WebApplicationInitializer的基础实现类AbstractAnnotationConfigDispatcherServletInitializer当编写类继承AbstractAnnotationConfigDispatcherServletInitializer时容器就会自动发现自己的类在该类中就可以配置Spring和SpringMVC的入口了。
按照下面的配置就可以完全省略web.xml
public class MyAnnotationConfigDispatcherServletInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {//返回的带有Configuration注解的类用来配置ContextLoaderListener protected Class?[] getRootConfigClasses() {System.out.println(加载核心配置类创建ContextLoaderListener); return new Class[]{ApplicationContextConfig.class};}//返回的带有Configuration注解的类用来配置DispatcherServletprotected Class?[] getServletConfigClasses() {System.out.println(加载核心配置类创建DispatcherServlet); return new Class[]{SpringMVCConfig.class};}//将一个或多个路径映射到DispatcherServlet上protected String[] getServletMappings() {return new String[]{/}; }
}◆ SpringMVC的组件原理剖析 - 前端控制器初始化
前端控制器DispatcherServlet是SpringMVC的入口也是SpringMVC的大脑主流程的工作都是在此完成的梳理一下DispatcherServlet 代码。DispatcherServlet 本质是个Servlet当配置了 load-on-startup 时会在服务器启动时就执行创建和执行初始化init方法每次请求都会执行service方法
DispatcherServlet 的初始化主要做了两件事
● 获得了一个 SpringMVC 的 ApplicationContext容器
● 注册了 SpringMVC的 九大组件。 SpringMVC 的ApplicationContext容器创建时机Servlet 规范的 init(ServletConfig config) 方法经过子类重写最终会调用 FrameworkServlet 抽象类的initWebApplicationContext() 方法该方法中最终获得 一个根Spring容器Spring产生的一个子Spring容器SpringMVC产生的
HttpServletBean 的初始化方法
public final void init() throws ServletException { this.initServletBean();
}FrameworkServlet的initServletBean方法
protected final void initServletBean() throws ServletException {//初始化ApplicationContextthis.webApplicationContext this.initWebApplicationContext(); //模板设计模式供子类覆盖实现但是子类DispatcherServlet没使用 this.initFrameworkServlet();
}在initWebApplicationContext方法中体现的父子容器的逻辑关系
//初始化ApplicationContext是一个及其关键的代码
protected WebApplicationContext initWebApplicationContext() {//获得根容器其实就是通过ContextLoaderListener创建的ApplicationContext//如果配置了ContextLoaderListener则获得根容器没配置获得的是null WebApplicationContext rootContext
WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());//定义SpringMVC产生的ApplicationContext子容器WebApplicationContext wac null;if (wac null) {//创建SpringMVC的子容器创建同时将Spring的创建的rootContext传递了过去wac this.createWebApplicationContext(rootContext);}//将SpringMVC产生的ApplicationContext子容器存储到ServletContext域中//key名是org.springframework.web.servlet.FrameworkServlet.CONTEXT.DispatcherServlet if (this.publishContext) {String attrName this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac);}
}跟进创建子容器的源码
protected WebApplicationContext createWebApplicationContext(Nullable ApplicationContext parent) {//实例化子容器ApplicationContext ConfigurableWebApplicationContext wac
(ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);//设置传递过来的ContextLoaderListener的rootContext为父容器wac.setParent(parent);//获得web.xml配置的classpath:spring-mvc.xmlString configLocation this.getContextConfigLocation(); if (configLocation ! null) {//为子容器设置配置加载路径wac.setConfigLocation(configLocation);}//初始化子容器(就是加载spring-mvc.xml配置的Bean) this.configureAndRefreshWebApplicationContext(wac); return wac;
}子容器中的parent维护着父容器的引用 父容器和子容器概念和关系
● 父容器Spring 通过ContextLoaderListener为入口产生的applicationContext容器内部主要维护的是applicationContext.xml或相应配置类配置的Bean信息
● 子容器SpringMVC通过DispatcherServlet的init() 方法产生的applicationContext容器内部主要维护的是spring-mvc.xml或相应配置类配置的Bean信息且内部还通过parent属性维护这父容器的引用。
● Bean的检索顺序根据上面子父容器的概念可以知道Controller存在与子容器中而Controller中要注入Service时会先从子容器本身去匹配匹配不成功时在去父容器中去匹配于是最终从父容器中匹配到的UserService这样子父容器就可以进行联通了。但是父容器只能从自己容器中进行匹配不能从子容器中进行匹配。
注册 SpringMVC的 九大组件在初始化容器initWebApplicationContext方法中执行了onRefresh方法进而执行了初始化策略initStrategies方法注册了九个解析器组件
//DispatcherServlet初始化SpringMVC九大组件
protected void initStrategies(ApplicationContext context) { this.initMultipartResolver(context);//1、初始化文件上传解析器this.initLocaleResolver(context);//2、初始化国际化解析器this.initThemeResolver(context);//3、初始化模板解析器this.initHandlerMappings(context);//4、初始化处理器映射器this.initHandlerAdapters(context);//5、初始化处理器适配器 this.initHandlerExceptionResolvers(context);//6、初始化处理器异常解析器this.initRequestToViewNameTranslator(context);//7、初始化请求视图转换器this.initViewResolvers(context);//8、初始化视图解析器this.initFlashMapManager(context);//9、初始化lashMapManager策略组件
}以 this.initHandlerMappings(context) 为例进一步看一下初始化处理器映射器的细节
//定义List容器存储HandlerMapping
private ListHandlerMapping handlerMappings;
//初始化HandlerMapping的方法
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings null;//初始化集合为null//detectAllHandlerMappings默认为true代表是否从所有容器中(父子容器)检测HandlerMapping if (this.detectAllHandlerMappings) {//从Spring容器中去匹配HandlerMappingMapString, HandlerMapping matchingBeans BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);//如果从容器中获取的HandlerMapping不为null就加入到事先定义好的handlerMappings容器中if (!matchingBeans.isEmpty()) {this.handlerMappings new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings);}//如果从容器中没有获得HandlerMapping意味着handlerMappings集合是空的if (this.handlerMappings null) {//加载默认的HandlerMapping就是加载DispatcherServlet.properties文件中的键值对this.handlerMappings this.getDefaultStrategies(context, HandlerMapping.class);}}
}- 前端控制器执行主流程
当服务器启动时DispatcherServlet 会执行初始化操作接下来每次访问都会执行service 方法先宏观的看一下执行流程在去研究源码和组件执行细节 FrameworkServlet 复写了service(HttpServletRequest request, HttpServletResponse response) 、doGet(HttpServletRequest request, HttpServletResponse response) doPost(HttpServletRequest request, HttpServletResponse response)等方法这些方法都会调用processRequest方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response){ this.doService(request, response);
}进一步调用了doService方法该方法内部又调用了doDispatch方法而SpringMVC 主流程最核心的方法就是doDispatch 方法
protected void doService(HttpServletRequest request, HttpServletResponse response) { this.doDispatch(request, response);
}doDispatch方法源码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { HttpServletRequest processedRequest request;HandlerExecutionChain mappedHandler null; //定义处理器执行链对象ModelAndView mv null; //定义模型视图对象//匹配处理器映射器HandlerMapping返回处理器执行链对象mappedHandler this.getHandler(processedRequest);//匹配处理器适配器HandlerAdapter返回处理器适配器对象HandlerAdapter ha this.getHandlerAdapter(mappedHandler.getHandler());//执行Interceptor的前置方法preHandlemappedHandler.applyPreHandle(processedRequest, response);//处理器适配器执行控制器Handler返回模型视图对象mv ha.handle(processedRequest, response, mappedHandler.getHandler());//执行Interceptor的后置方法postHandlemappedHandler.applyPostHandle(processedRequest, response, mv);//获取视图渲染视图this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}◆ SpringMVC的异常处理机制
- SpringMVC 异常的处理流程
异常分为编译时异常和运行时异常编译时异常我们 try-cache 进行捕获捕获后自行处理而运行时异常是不可预期的就需要规范编码来避免在SpringMVC 中不管是编译异常还是运行时异常都可以最终由SpringMVC提供的异常处理器进行统一处理这样就避免了随时随地捕获处理的繁琐性。
当然除了繁琐之外在进行前后端分离异步开发时往往返回统一格式的结果给客户端例如{code:200,message:,data:{username:haohao,age:null}}即使报异常了也不能把状态码500直接扔给客户端丢给用户需要将异常转换成符合上面格式的数据响应给客户端更友好。
SpringMVC 处理异常的思路是一路向上抛都抛给前端控制器 DispatcherServlet DispatcherServlet 在调用异常处理器ExceptionResolver进行处理如下图 - SpringMVC 的异常处理方式
SpringMVC 提供了以下三种处理异常的方式
● 简单异常处理器使用SpringMVC 内置的异常处理器处理 SimpleMappingExceptionResolver
● 自定义异常处理器实现HandlerExceptionResolver接口自定义异常进行处理
● 注解方式使用ControllerAdvice ExceptionHandler 来处理 使用SimpleMappingExceptionResolver处理一些简单异常配置开启SimpleMappingExceptionResolver并指定异常捕获后的处理动作当发生了异常后会被 SimpleMappingExceptionResolver 处理跳转到我们配置的错误页面error.html给用户进行友好展示
!--配置简单异常处理器--
bean classorg.springframework.web.servlet.handler.SimpleMappingExceptionResolver!-- 异常捕获后动作展示视图 --property namedefaultErrorView value/error.html/
/bean可以在配置SimpleMappingExceptionResolver时指定一些参数例如异常的类型
bean classorg.springframework.web.servlet.handler.SimpleMappingExceptionResolverproperty namedefaultErrorView value/error.html/property nameexceptionMappingsprops!-- 配置异常类型对应的展示视图 --prop keyjava.lang.RuntimeException/error.html/propprop keyjava.io.FileNotFoundException/io.html/prop/props/property
/bean注解方式配置简单映射异常处理器
Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){//创建SimpleMappingExceptionResolverSimpleMappingExceptionResolver resolver new SimpleMappingExceptionResolver();//设置默认错误展示视图resolver.setDefaultErrorView(/error.html);//定义Properties设置特殊异常对应的映射视图Properties properties new Properties(); properties.setProperty(java.lang.RuntimeException,/error.html); properties.setProperty(java.io.FileNotFoundException,/io.html); resolver.setExceptionMappings(properties);return resolver;
}自定义异常处理器实现HandlerExceptionResolver接口自定义异常处理器可以完成异常逻辑的处理
public class MyHandlerExceptionResolver implements HandlerExceptionResolver { Override//参数Object是当前目标方法处理器对象HandlerMethodpublic ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {ModelAndView modelAndView new ModelAndView(); modelAndView.setViewName(/error.html);return modelAndView; }
}交给Spring管理异常处理器
bean classcom.itheima.exception.MyHandlerExceptionResolver/bean自定义异常处理器返回Json格式字符串信息
Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver { Overridepublic ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {//编写要返回的json格式的字符串String jsonStr {\code\:0,\message\:\error\,\data\:\\}; try {httpServletResponse.getWriter().write(jsonStr);} catch (IOException e1) {e1.printStackTrace();}return null; }
}使用注解 ControllerAdvice ExceptionHandler 配置异常ControllerAdvice 注解本质是一个Component也会被扫描到与此同时具备AOP功能默认情况下对所有的Controller都进行拦截操作拦截后干什么呢就需要在结合ExceptionHandler、InitBinder、ModelAttribute 注解一起使用了所以是ControllerAdvice ExceptionHandler的组合形式。
ControllerAdvice
public class GlobalExceptionHandler { ExceptionHandler(RuntimeException.class)public ModelAndView runtimeHandleException(RuntimeException e){System.out.println(全局异常处理器执行....e);ModelAndView modelAndView new ModelAndView(/error.html); return modelAndView;} ExceptionHandler(IOException.class) ResponseBodypublic ResultInfo ioHandleException(IOException e){//模拟一个ResultInfoResultInfo resultInfo new ResultInfo(0,IOException,null); return resultInfo;}
}如果全局异常处理器响应的数据都是Json格式的字符串的话可以使用RestControllerAdvice替代ControllerAdvice 和 ResponseBody
RestControllerAdvice
public class GlobalExceptionHandler { ExceptionHandler(RuntimeException.class)public ResultInfo runtimeHandleException(RuntimeException e){//模拟一个ResultInfoResultInfo resultInfo new ResultInfo(0,RuntimeException,null); return resultInfo;} ExceptionHandler(IOException.class)public ResultInfo ioHandleException(IOException e){//模拟一个ResultInfoResultInfo resultInfo new ResultInfo(0,IOException,null); return resultInfo;}
}- 异常处理机制原理剖析
初始化加载的处理器异常解析器SpringMVC 的前置控制器在进行初始化的时候会初始化处理器异常解析器HandlerExceptionResolver
//初始化处理器异常解析器
this.initHandlerExceptionResolvers(context);
private void initHandlerExceptionResolvers(ApplicationContext context) {//从容器中获得HandlerExceptionResolver的Map集合MapString, HandlerExceptionResolver matchingBeans BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);//如果容器中没有HandlerExceptionResolver的话则加载默认的HandlerExceptionResolverif (this.handlerExceptionResolvers null) {//从dispatcherServlet.properties中加载this.handlerExceptionResolvers this.getDefaultStrategies(context, HandlerExceptionResolver.class);}
}加载DispatcherServlet.properties中默认的异常处理器
org.springframework.web.servlet.HandlerExceptionResolverorg.springframework.web.servlet.mvc.me thod.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver配置了自定义的异常处理器后默认的异常处理器就不会被加载当配置mvc:annotation-driven / 或配置了注解EnableWebMvc后默认异常处理器和自定的处理器异常解析器都会被注册 异常处理器加载完毕后当发生异常时就会进行处理跟踪 DispatcherServlet 的 doDispatch() 方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { Object dispatchException null;//定义异常try {// ... 省略代码 ...} catch (Exception e) { dispatchException e;} catch (Throwable te) {dispatchException new NestedServletException(Handler dispatch failed, te); }//视图处理、拦截器最终方法调用、异常处理都在该方法内this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}跟踪processDispatchResult方法
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
Nullable HandlerExecutionChain mappedHandler, Nullable ModelAndView mv, Nullable Exception exception) throws Exception {boolean errorView false;//定义错误视图标识默认为false if (exception ! null) {//判断当前捕获的异常是否是ModelAndViewDefiningException类型的异常if (exception instanceof ModelAndViewDefiningException) {//获得ModelAndViewDefiningException异常对象中的ModelAndView对象mv ((ModelAndViewDefiningException)exception).getModelAndView();} else {//捕获到其他异常获得当前发生异常的Handler对象Object handler mappedHandler ! null ? mappedHandler.getHandler() : null;//执行processHandlerException 方法mv this.processHandlerException(request, response, handler, exception);//如果异常处理返回了ModelAndView 则修改错误视图的标识为true errorView mv ! null;}}// ... 省略其他代码 ...
}跟踪processHandlerException 方法
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Nullable Object handler, Exception ex) throws Exception {ModelAndView exMv null;//定义ModelAndView//判断处理器异常解析器集合是否是空的if (this.handlerExceptionResolvers ! null) {//遍历处理器异常解析器List集合Iterator var6 this.handlerExceptionResolvers.iterator(); while(var6.hasNext()) {//取出每一个异常解析器HandlerExceptionResolver resolver (HandlerExceptionResolver)var6.next();//执行异常解析器的resolveException方法exMv resolver.resolveException(request, response, handler, ex);//只要有一个异常处理器返回ModelAndView 则不在执行后面的异常处理器if (exMv ! null) {break;} }}//如果视图解析器不为nullif (exMv ! null) {return exMv; } else { throw ex; }}- SpringMVC 常用的异常解析器
SpringMVC 相关的处理器异常解析器继承体系如下 接口或类 说明 HandlerExceptionResolver 异常处理器类的顶级接口实现了该接口的类都会作为异常处理器类 MyHandlerExceptionResolver 自定义的处理器类实现了HandlerExceptionResolver接口 HandlerExceptionResolverComposite 异常解析器混合器内部存在集合存储多种异常解析器 SimpleMappingExceptionResolver 简单映射异常处理器可以配置异常与对应的错误视图 ExceptionHandlerExceptionResolver 异常处理器异常解析器默认会被注册到Spring容器中ExceptionHandler方式异常处理就是该解析器解析的 DefaultHandlerExceptionResolver 默认处理器异常解析器所有异常处理器都不匹配时最后执行的异常处理器 ResponseStatusExceptionResolver 响应状态异常解析器结合使用ResponseStatus标注的异常使用
来源黑马程序员新版Spring零基础入门到精通一套搞定spring全套视频教程含实战源码