SpringMVC
概述
SpringMVC是Spring框架提供的构建web程序的模块
流程
-
导包
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar -
写配置
web.xml的配置内容
配置SpringMVC的前端控制器
<!--配置前端控制器拦截所有请求-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.dispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--
/:拦截所有请求,不包括*.jsp
/*:拦截所有请求,且包括*.jsp
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
若不在前端控制器指定SpringMVC配置文件的位置,则需在WEB-INF目录下创建一个名为“前端控制器名-servlet.xml”的SpringMVC配置文件
dispatcherServlet-servlet.xml的配置内容
<context:component-scan base-package="com.th1024"></context:component-scan>
<!--配置视图解析器,简化方法返回值,在跳转时视图解析器可以自动拼串跳转-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
- 测试
@Controller //添加@Controller表名这个类是一个处理器
public class MyController {
//表名该方法用于处理哪个请求
@RequestMapping("/handle01")
public String handle01(){
System.out.println("MyController handle01...");
return "success";
}
}
细节
- 运行流程
- 客户端点击链接,发送http://localhost:8080/1_HelloWorld/handle01请求
- 请求发送到Tomcat服务器,并被SpringMVC的前端控制器dispatcherServlet接收
- 查看请求地址是否和@RequestMapping标注的值匹配,确定处理请求调用的类及方法
- 前端控制器利用反射执行目标方法
- 方法执行完成之后会返回跳转的页面地址
- 利用视图解析器进行拼串得到完整的页面地址
- 前端控制器将请求转发到页面
- / 和 /*
Tomcat服务器中有两个Servlet,分别是DefaultServlet和JspServlet,DefaultServlet的url-pattern为/,JspServlet的url-pattern为/*,DefaultServlet是Tomcat服务器用于处理请求静态资源的Servlet,JspServlet是Tomcat服务器用于处理请求动态资源的Servlet,而配置的dispatcherServlet的url-pattern也为/,则dispatcherServlet相当于覆盖了Tomcat服务器的DefaultServlet,请求静态资源会由dispatcherServlet处理,而.jsp等动态资源仍由Tomcat服务器处理
- @RequestMapping
@RequestMapping("/handle01")标注在方法上,表名此方法应该处理什么类型的请求,“/handle01”表名请求的地址为当前项目下加上handle01
@RequestMapping("/haha")标注在类上,则表示此类下的所有方法处理的请求地址均为当前工程下/haha/各方法的@RequestMapping的value值
@RequestMapping("/haha")
@Controller
public class RequestMappingTestController {
@RequestMapping("/handle01")
public String handle01(){
System.out.println("RequestMappingTestController handle01...");
return "error";
}
/*
RequestMapping的其他属性
method:限定请求方式
method = RequestMethod.POST:限定只接受POST请求
params:规定请求的参数,支持简单的表达式
param1:表示请求必须包含名为param1的请求参数
!param1:表示请求不能包含名为param1的请求参数
param1 != value1:表示请求包含名为param1的请求参数,但其值不能为value1
{"param1=value1","param2"}:请求必须同时满足两个表达式,即包含param1和param2,且param1的值必须为value1
params = {"username!=123","pwd","!age"}):发送请求时必须带有username,pwd参数,必须没有age参数,且username参数不能为123
headers:规定请求头,和params一样能写简单的表达式
consumes:
produces
*/
@RequestMapping(value = "/handle02",method = RequestMethod.POST)
public String handle02(){
System.out.println("handle02...");
return "success";
}
@RequestMapping(value = "/handle03",params = {"username!=123","pwd","!age"})
public String handle03(){
System.out.println("handle03...");
return "success";
}
/*
设置headers,User-Agent:浏览器信息
让Edge不能访问,Chrome能访问
Chrome:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36
Edge:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36 Edg/90.0.818.42
*/
@RequestMapping(value = "/handle04",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36"})
public String handle04(){
System.out.println("handle04...");
return "success";
}
}
- 通配符
@Controller
public class RequestMappingTest {
@RequestMapping("/antTest01")
public String antTest01(){
System.out.println("antTest01...");
return "success";
}
/*
?匹配一个字符,0个多个都不行;
模糊和精确多个匹配情况下,精确优先
*/
@RequestMapping("/antTest0?")
public String antTest02(){
System.out.println("antTest02...");
return "success";
}
/*
*匹配任意多个字符
*/
@RequestMapping("/antTest0*")
public String antTest03(){
System.out.println("antTest03...");
return "success";
}
/*
*:匹配一层路径
*/
@RequestMapping("/a/*/antTest01")
public String antTest04(){
System.out.println("antTest04...");
return "success";
}
@RequestMapping("/a/**/antTest01")
public String antTest05(){
System.out.println("antTest05...");
return "success";
}
//路径上可以有占位符: 占位符 语法就是可以在任意路径的地方写一个{变量名}
// /user/admin
// 路径上的占位符只能占一层路径
//@PathVariable("变量名")获取请求路径上的参数
@RequestMapping("/user/{id}")
public String pathVariableTest(@PathVariable("id")String id){
System.out.println("路径上的占位符的值"+id);
return "success";
}
}
REST
系统希望以非常简洁的URL地址来发送请求
之前的增删改查请求
/getBook?id=1:查询1号图书
/deleteBook?id=1:删除1号图书
/updateBook?id=1:更新1号图书
/addBook:添加图书
怎么用请求方式来区分对一个资源的增删改查?
REST风格的URL地址:/资源名/资源标识符
/book/1:GET--查询1号图书
/book/1:PUT--更新1号图书
/book/1:DELETE--删除1号图书
/book:POST--添加图书
问题:从页面只能发起两种请求,GET、POST
使用REST来构建一个增删改系统
处理程序
@RequestMapping("/book")
@Controller
public class BookController {
@RequestMapping(value = "",method = RequestMethod.POST)
public String addBook(){
System.out.println("添加图书");
return "success";
}
@RequestMapping(value = "/{bid}",method = RequestMethod.DELETE)
public String deleteBook(@PathVariable("bid") Integer id){
System.out.println("删除"+ id +"号图书");
return "success";
}
@RequestMapping(value = "/{bid}",method = RequestMethod.PUT)
public String updateBook(@PathVariable("bid") Integer id){
System.out.println("更新"+ id +"号图书");
return "success";
}
@RequestMapping(value = "/{bid}",method = RequestMethod.GET)
public String getBook(@PathVariable("bid") Integer id){
System.out.println("获取"+ id +"号图书");
return "success";
}
}
配置将请求转化为规定形式的filter
<!--将普通请求转化为规定形式的请求-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
HiddenHttpMethodFilter类中转换请求方式的方法
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws servletexception, IOException {
String paramValue = request.getParameter(this.methodParam);
if ("POST".equals(request.getmethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toupperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
}
else {
filterChain.doFilter(request, response);
}
}
使用REST风格的URL地址:
<%--
使用REST风格的URL地址
请求url 请求方式 表示含义
/book/1 GET 查询1号图书
/book/1 DELETE 删除1号图书
/book/1 PUT 更新1好图书
/book POST 添加1号图书
--%>
<h1>测试REST风格的URL地址</h1>
<a href="book/1">查询1号图书</a><br/>
<form action="book" method="post">
<input type="submit" value="添加图书">
</form>
<%--发送DELETE请求--%>
<form action="book/1" method="post">
<input type="submit" value="删除1号图书">
<input name="_method" value="delete" type="hidden">
</form>
<%--发送PUT请求--%>
<form action="book/1" method="post">
<input type="submit" value="更新1号图书">
<input name="_method" value="PUT" type="hidden">
</form>
添加isErrorPage="true"
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
请求参数
- 获取请求参数
/*
SpringMVC如何获取请求带来的各种信息,默认获取请求参数的方式:直接给方法形参列表写一个和请求参数同名的变量,此变量就用来接收请求参数的值
@RequestParam:获取请求参数,默认请求必须携带该参数
@RequestParam("user") String user == String user = request.getParameter("user");
@RequestParam("user")
@PathVariable("user")
/book/【{user}pathvariable】?【user=admin(requestparam)】
value:指定要获取的参数的key required:这个参数是否必须的
defaultValue:默认值。没带默认是null;
@RequestHeader:获取请求头中某个key的值
@RequestHeader("User-Agent") String userAgent == String userAgent = request.getParameter("User-Agent");
@CookieValue:获取某个Cookie的值
*/
@RequestMapping(value = "/handle01")
public String handle01(@RequestParam(value = "user",required = false,defaultValue = "defaultUser") String user,
@RequestHeader(value = "User-Agent",required = false,defaultValue = "defaultUserAgent") String userAgent,
@CookieValue(value = "JSESSIONID",required = false) String cookieValue){
System.out.println("变量:" + user);
System.out.println("请求头信息" + userAgent);
System.out.println("cookie的值" + cookieValue);
return "success";
}
- 请求参数为一个POJO
/*
请求的参数如果是一个POJO,SpringMVC会自动为这个POJO赋值
1. 将POJO中的每一个属性,从request参数中尝试获取并封装
2. 可以进行级联封装
3. 请求参数的参数名和对象中的属性名一一对应
*/
@RequestMapping("/book")
public String addBook(Book book){
System.out.println("获取的图书信息:" + book);
return "success";
}
- 传入原生API
/*
SpringMVC可以在参数上写原生API
HttpServletRequest
HttpServletResponse
HttpSession
*/
@RequestMapping("/handle02")
public String handle02(HttpServletRequest request, HttpServletResponse response, HttpSession session){
request.setAttribute("reqParam","reqAttr");
session.setAttribute("sessionParam","sessionAttr");
return "success";
}
- 配置字符编码集Filter解决乱码问题
<!--配置字符编码的filter,注意:此filter一般在其他的filter之前-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--解决POST请求乱码-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!--结局响应乱码:response.setCharacterEncoding(this.encoding)-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
数据输出
SpringMVC可以通过在方法形参列表传入Map、Model或者ModelMap,将数据保存在这些参数中,参数再将数据保存在请求域中的方式,在页面中获取数据
@RequestMapping("/output")
@Controller
@SessionAttributes(value = "haha",types = {String.class})
public class OutputController {
/*
1. SpringMVC可以通过在方法形参列表传入Map、Model或者ModelMap,将数据保存在这些参数中,参数再将数据保存在请求域中的方式,在页面中获取数据
Map、Model或者ModelMap实际运行时都为同一种类型:BindingAwareModelMap
Map(interface(jdk)) Model(interface(spring))
|| //
|| //
\/ //
ModelMap(class) //
\\ //
\\ //
ExtendedModelMap
||
\/
BindingAwareModelMap
2. 方法的返回值可以为ModelAndView类型,既包含视图信息(页面地址)也包含模型数据(给页面带的数据)
3. @SessionAttributes(value = "msg", types = {String.class})
value = "msg":在往请求域中保存数据时,只要是key的值为"msg",这份数据也会同时保存在Session域中
types = {String.class}:在往请求域中保存数据时,只要value是String类型的,这份数据也会同时保存在Session域中
不推荐使用,往Session域中保存数据使用原生API
*/
@RequestMapping("/handle01")
public String handle01(Map<String,Object> map){
map.put("msg","map存入的数据");
map.put("haha","哈哈");
System.out.println("map类型" + map.getClass());//org.springframework.validation.support.BindingAwareModelMap
return "success";
}
@RequestMapping("/handle02")
public String handle02(Model model){
model.addAttribute("msg","model存入的数据");
model.addAttribute("haha",18);
System.out.println("model的类型" + model.getClass());//org.springframework.validation.support.BindingAwareModelMap
return "success";
}
@RequestMapping("/handle03")
public String handle03(ModelMap modelMap){
modelMap.addAttribute("msg","modelMap存入的数据");
System.out.println("modelMap的类型" + modelMap.getClass());//org.springframework.validation.support.BindingAwareModelMap
return "success";
}
/*
返回值可以为ModelAndView,为页面携带数据
*/
@RequestMapping("/handle04")
public ModelAndView handle04(){
ModelAndView mv = new ModelAndView();
mv.addobject("msg","modelAndView存入的数据");
mv.setViewName("success");
return mv;
}
}
@modelattribute注解解决全字段更新问题
@Controller
public class modelattributeTestController {
/*
测试modelattribute注解
使用场景:书城图书修改
页面端:显示所有要修改的图书信息,包含所有字段
servlet:调用dao,String sql = "update t_book set `name` = ?,
`author` = ?,`price` = ?,
`sales` = ?,`stock` = ?,
`img_path` = ? where `id` = ?";
实际场景?
并非全字段修改,只会修改部分字段,如price,stock,sales等
1. 不修改的字段可以直接展示,不提供输入框进行修改
2. Controller中的处理方法参数直接写Book对象,SpringMVC会自动封装(没有带的值为null)
3. 若此时调用全字段更新,可能会将某些字段更新为null
如何保证全字段更新的时候,只更新页面携带的数据?
1. 修改dao,不现实
2. 不直接创建对象,而是更新从数据库中获取的对象的某些字段值
1) 从数据库中获取对应id的对象
2) 将页面携带的数据更新到获取的对象中,页面中没携带的数据则为原来的值
3) 调用全字段更新保存到数据库
@modelattribute
标注在参数上:取出保存的数据
标注在方法上:提前于目标方法运行
*/
private Object o1;
private Object o2;
private Object b1;
private Object b2;
@RequestMapping("/updateBook")
public String updateBook(Map<String,Object> map,
@modelattribute("book") Book book){
System.out.println("页面提交的信息" + book);
Object book1 = map.get("book");
b2 = book;
o2 = map;
System.out.println("o1 == o2?" + (o1 == o2));
System.out.println("b1 == b2?" + (b1 == b2) + "; b2 == book1?" + (b2 == book1) );//均为true,说明使用的均为同一个对象
return "success";
}
@modelattribute
public void mymodelattribute(Map<String, Object> map){
Book book = new Book(100,"西游记","吴承恩",98.98,11,200,null);
System.out.println("数据库中获取的信息" + book);
map.put("book",book);
b1 = book;
o1 = map;
System.out.println("modelattribute保存数据,类型为" + map.getClass());//org.springframework.validation.support.BindingAwareModelMap
}
}
请求处理源码分析
- dispatcherServlet结构
- dodispatch()
protected void dodispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//1. 检查是否是文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//2. 为当前请求确定处理器,即能处理该请求的类
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
//若没找到处理器则抛出异常
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//3. 为当前处理器获取适配器
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getmethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
//4. 处理器的适配器处理请求,目标方法返回值作为视图名,返回ModelAndView对象
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applydefaultviewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
//5. 转发到页面,ModelAndView对象中的数据可以从请求域中获取
processdispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
- getHandler()
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in dispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
handlerMappings:处理器映射,里面保存了每个处理器能处理哪些请求的映射信息
- getHandlerAdapter()
protected HandlerAdapter getHandlerAdapter(Object handler) throws servletexception {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new servletexception("No adapter for handler [" + handler +
"]: The dispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
AnnotationMethodHandlerAdapter:注解驱动的处理器适配器
总结:
- 请求发送到dispatcherServlet,调用dodispatch()方法进行处理
- getHandler():根据当前请求在handlerMappings中找到这个请求的映射信息,获取目标处理器类
- getHandlerAdapter():根据当前处理器类获取到能执行这个处理器方法的适配器
- 使用获取到的适配器执行目标方法(AnnotationMethodHandlerAdapter)
- 目标方法执行之后返回一个ModelAndView对象,封装了要跳转的页面信息及模型数据
- 转发到具体页面,并可在请求域中获取模型数据
SpringMVC九大组件
dispatcherServlet中的几个引用类型的属性,成为SpringMVC九大组件,且均为接口,提供了非常强大的拓展性
/** MultipartResolver used by this servlet 文件上传解析器*/
private MultipartResolver multipartResolver;
/** LocaleResolver used by this servlet 区域信息解析器,和国际化有关*/
private LocaleResolver localeResolver;
/** ThemeResolver used by this servlet 主题解析器*/
private ThemeResolver themeResolver;
/** List of HandlerMappings used by this servlet Handler映射信息*/
private List<HandlerMapping> handlerMappings;
/** List of HandlerAdapters used by this servlet Handler适配器*/
private List<HandlerAdapter> handlerAdapters;
/** List of HandlerExceptionResolvers used by this servlet Handler异常解析器*/
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet 请求转换器*/
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMapManager used by this servlet FlashMap管理,提供重定向携带数据功能*/
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet 视图解析器*/
private List<ViewResolver> viewResolvers;
初始化
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
初始化HandlerMappings
在容器中根据类型或id寻找该组件,若没找到则使用默认配置
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
beanfactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDeFinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler())--->return invokeHandlerMethod(request, response, handler);
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取方法解析器
ServletHandlerMethodResolver methodResolver = getmethodResolver(handler);
//使用方法解析器获取目标方法
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
//创建方法执行器
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
//包装原生API:request和response
ServletWebRequest webRequest = new ServletWebRequest(request, response);
//创建一个BindingAwareModelMap对象--隐含模型
ExtendedModelMap implicitModel = new BindingAwareModelMap();
//实际执行目标方法
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
//返回ModelAndView对象
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updatemodelattributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
invokeHandlerMethod()细节
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
try {
boolean debug = logger.isDebugEnabled();
for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
if (attrValue != null) {
implicitModel.addAttribute(attrName, attrValue);
}
}
//获取所有标注了@modelattribute注解的方法
for (Method attributeMethod : this.methodResolver.getmodelattributeMethods()) {
Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
//获取方法执行需要的参数
Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
}
String attrName = AnnotationUtils.findAnnotation(attributeMethod, modelattribute.class).value();
if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
continue;
}
ReflectionUtils.makeAccessible(attributeMethodToInvoke);
//标注@modelattribute注解的方法运行
Object attrValue = attributeMethodToInvoke.invoke(handler, args);
if ("".equals(attrName)) {
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
attrName = Conventions.getvariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
}
if (!implicitModel.containsAttribute(attrName)) {
//将标注@modelattribute注解的方法的返回值放入隐含模型中
implicitModel.addAttribute(attrName, attrValue);
}
}
//获取目标方法执行需要的参数
Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
}
ReflectionUtils.makeAccessible(handlerMethodToInvoke);
//目标方法执行
return handlerMethodToInvoke.invoke(handler, args);
}
catch (IllegalStateException ex) {
// Internal assertion Failed (e.g. invalid signature):
// throw exception with full handler method context...
throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
}
catch (InvocationTargetException ex) {
// user-defined @modelattribute/@InitBinder/@RequestMapping method threw an exception...
ReflectionUtils.rethrowException(ex.getTargetException());
return null;
}
}
根据分析可得,@modelattribute标注在方法上,若给定了value属性,则attrName=value属性值,若未给定value属性值,则attrName=方法返回值首字母小写,然后以attrName为key,方法返回值为value,放入隐含模型
resolveHandlerArguments():获取方法执行时需要的参数
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
//创建一个长度等于参数个数的数组,用来保存每一个参数的值
Class<?>[] paramTypes = handlerMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
//循环解析每一个参数
for (int i = 0; i < args.length; i++) {
MethodParameter methodParam = new MethodParameter(handlerMethod, i);
methodParam.initParameterNamediscovery(this.parameterNamediscoverer);
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
String paramName = null;
String headerName = null;
boolean requestBodyFound = false;
String cookieName = null;
String pathVarName = null;
String attrName = null;
boolean required = false;
String defaultValue = null;
boolean validate = false;
Object[] validationHints = null;
int annotationsFound = 0;
Annotation[] paramAnns = methodParam.getParameterannotations();
//找到方法的此参数标注的所有注解并解析保存
for (Annotation paramAnn : paramAnns) {
if (RequestParam.class.isinstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.value();
required = requestParam.required();
defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
annotationsFound++;
}
else if (RequestHeader.class.isinstance(paramAnn)) {
RequestHeader requestHeader = (RequestHeader) paramAnn;
headerName = requestHeader.value();
required = requestHeader.required();
defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
annotationsFound++;
}
else if (RequestBody.class.isinstance(paramAnn)) {
requestBodyFound = true;
annotationsFound++;
}
else if (CookieValue.class.isinstance(paramAnn)) {
CookieValue cookieValue = (CookieValue) paramAnn;
cookieName = cookieValue.value();
required = cookieValue.required();
defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
annotationsFound++;
}
else if (PathVariable.class.isinstance(paramAnn)) {
PathVariable pathVar = (PathVariable) paramAnn;
pathVarName = pathVar.value();
annotationsFound++;
}
else if (modelattribute.class.isinstance(paramAnn)) {
//若标注了@modelattribute注解,则将注解的value属性值赋值给attrName
modelattribute attr = (modelattribute) paramAnn;
attrName = attr.value();
annotationsFound++;
}
else if (Value.class.isinstance(paramAnn)) {
defaultValue = ((Value) paramAnn).value();
}
else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
validate = true;
Object value = AnnotationUtils.getValue(paramAnn);
validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});
}
}
if (annotationsFound > 1) {
throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
"do not specify more than one such annotation on the same parameter: " + handlerMethod);
}
//没有标注注解的情况
if (annotationsFound == 0) {
//解析普通参数,之后会进入resolveStandardArgument(),解析标准参数,判断是否为原生API
Object argValue = resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
else {
Class<?> paramType = methodParam.getParameterType();
//判断是否为Model或Map类型,如果是则将隐含模型赋值给此参数
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
if (!paramType.isAssignableFrom(implicitModel.getClass())) {
throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +
"Model or Map but is not assignable from the actual model. You may need to switch " +
"newer MVC infrastructure classes to use this argument.");
}
args[i] = implicitModel;
}
else if (SessionStatus.class.isAssignableFrom(paramType)) {
args[i] = this.sessionStatus;
}
else if (httpentity.class.isAssignableFrom(paramType)) {
args[i] = resolvehttpentityRequest(methodParam, webRequest);
}
else if (Errors.class.isAssignableFrom(paramType)) {
throw new IllegalStateException("Errors/BindingResult argument declared " +
"without preceding model attribute. Check your handler method signature!");
}
else if (BeanUtils.isSimpleProperty(paramType)) {
paramName = "";
}
else {
attrName = "";
}
}
}
//确定值的环节
if (paramName != null) {
args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
}
else if (headerName != null) {
args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
}
else if (requestBodyFound) {
args[i] = resolveRequestBody(methodParam, webRequest, handler);
}
else if (cookieName != null) {
args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
}
else if (pathVarName != null) {
args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
}
else if (attrName != null) {
WebDataBinder binder =
resolvemodelattribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
dobind(binder, webRequest, validate, validationHints, !assignBindingResult);
}
args[i] = binder.getTarget();
if (assignBindingResult) {
args[i + 1] = binder.getBindingResult();
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
}
}
return args;
}
resolveStandardArgument():解析是否是原生API
@Override
protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
if (ServletRequest.class.isAssignableFrom(parameterType) ||
MultipartRequest.class.isAssignableFrom(parameterType)) {
Object nativeRequest = webRequest.getNativeRequest(parameterType);
if (nativeRequest == null) {
throw new IllegalStateException(
"Current request is not of type [" + parameterType.getName() + "]: " + request);
}
return nativeRequest;
}
else if (ServletResponse.class.isAssignableFrom(parameterType)) {
this.responseArgumentUsed = true;
Object nativeResponse = webRequest.getNativeResponse(parameterType);
if (nativeResponse == null) {
throw new IllegalStateException(
"Current response is not of type [" + parameterType.getName() + "]: " + response);
}
return nativeResponse;
}
else if (HttpSession.class.isAssignableFrom(parameterType)) {
return request.getSession();
}
else if (Principal.class.isAssignableFrom(parameterType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(parameterType)) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(parameterType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(parameterType)) {
return request.getReader();
}
else if (OutputStream.class.isAssignableFrom(parameterType)) {
this.responseArgumentUsed = true;
return response.getoutputStream();
}
else if (Writer.class.isAssignableFrom(parameterType)) {
this.responseArgumentUsed = true;
return response.getWriter();
}
return super.resolveStandardArgument(parameterType, webRequest);
}
流程
若标了注解
保存注解的详细信息
如果参数有modelattribute注解,则将注解的value属性值赋给attrName
若没标注解
先判断是否是普通参数(原生API)
再判断是否是Model或者Map类型,有就传入隐含模型(implicitModel)
自定义类型的参数没有标注modelattribute
先判断是否是原生API
再判断是否是Model或者Map
再判断是否是其他类型,例如SessionStatus、httpentity、Errors
再判断是否是简单类型的属性,例如Integer、String和基本类型,如果是则paramName=""
attrName=""
即如果是自定义类型的对象,则会有两种情况
- 标注了@modelattribute注解,attrName=value值
- 没有标注@modelattribute注解,attrName=""
确定自定义类型参数的值
private WebDataBinder resolvemodelattribute(String attrName, MethodParameter methodParam,
ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {
// Bind request parameter onto object...
String name = attrName;
if ("".equals(name)) {
//如果attrName="",则将参数类型首字母小写作为值
name = Conventions.getvariableNameForParameter(methodParam);
}
Class<?> paramType = methodParam.getParameterType();
//确定目标对象的值
Object bindobject;
if (implicitModel.containsKey(name)) {
bindobject = implicitModel.get(name);
}
else if (this.methodResolver.isSessionAttribute(name, paramType)) {
bindobject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
if (bindobject == null) {
raiseSessionrequiredException("Session attribute '" + name + "' required - not found in session");
}
}
else {
bindobject = BeanUtils.instantiateClass(paramType);
}
WebDataBinder binder = createBinder(webRequest, bindobject, name);
initBinder(handler, name, binder, webRequest);
return binder;
}
先判断隐含模型中是否有以变量name为key的对象(若attrName不为空串则name=attrName,若attrName=空串则name=参数类型首字母小写),若有则将这个值赋给bindobject
再判断是否为标注了SessionAttribute的属性,若标注了则从Session中获取
若以上不满足则利用反射创建对象
最后利用该对象使用数据绑定器(WebDataBinder)将请求中的数据绑定到这个对象中并返回
视图解析源码分析
第一部分:获取View对象
执行方法之后返回值均会包装为ModelAndView对象
processdispatchResult()方法作用是来到页面,内部调用render()方法
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws servletexception if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
//解析视图名得到View对象
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new servletexception(
"Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +
getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new servletexception("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in dispatcherServlet with name '" + getServletName() + "'");
}
try {
//View对象进行页面渲染
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in dispatcherServlet with name '"
+ getServletName() + "'", ex);
}
throw ex;
}
}
resolveViewName()
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
//循环遍历所有的ViewResolver:配置的InternalResourceViewResolver
for (ViewResolver viewResolver : this.viewResolvers) {
//ViewResolver根据方法返回值获取View对象
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
resolveViewName()
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
return createView(viewName, locale);
}
else {
//检查缓存中是否有已经创建好的duiyingdeView对象
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
//根据方法返回值创建View对象
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
//将创建好的View对象放入缓存中
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
createView()
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
//判断是否是redirect:前缀
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
//判断是否是forward:前缀
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
//没有前缀则使用父类默认创建一个View
return super.createView(viewName, locale);
}
loadView()-->buildView()
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//根据方法返回值拼串并创建View对象
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
if (this.exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
//InternalResourceView
return view;
}
第二部分:View对象进行页面渲染
view.render()
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//渲染要给页面输出的所有数据
renderMergedOutputModel(mergedModel, request, response);
}
renderMergedOutputModel()
/**
* Render the internal resource given the specified model.
* This includes setting the model as request attributes.
*/
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the Requestdispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
// Expose the model object as request attributes.
//将模型中的数据放入请求域中
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
//确定页面地址
String dispatcherPath = prepareForRendering(requestToExpose, response);
// Obtain a Requestdispatcher for the target resource (typically a JSP).
Requestdispatcher rd = getRequestdispatcher(requestToExpose, dispatcherPath);
if (rd == null) {
throw new servletexception("Could not get Requestdispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
//请求转发至页面
rd.forward(requestToExpose, response);
}
}
exposeModelAsRequestAttributes():取出模型中的所有数据并放入请求域中
/**
* Expose the model objects in the given map as request attributes.
* Names will be taken from the model Map.
* This method is suitable for all resources reachable by {@link javax.servlet.Requestdispatcher}.
* @param model Map of model objects to expose
* @param request current HTTP request
*/
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
for (Map.Entry<String, Object> entry : model.entrySet()) {
String modelName = entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
if (logger.isDebugEnabled()) {
logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
"] to request in view with name '" + getBeanName() + "'");
}
}
else {
request.removeAttribute(modelName);
if (logger.isDebugEnabled()) {
logger.debug("Removed model object '" + modelName +
"' from request in view with name '" + getBeanName() + "'");
}
}
}
}
视图解析器解析方法返回值并创建View对象,View对象完成页面数据的存储并转发至页面
RestFulCRUD
利用SpringMVC,编写一个REST风格的CRUD
C:Create--创建
R:Retrieve--查询
U:Update--更新
D:Delete--删除
员工列表页面
更新员工信息
增删改查的URL地址 /资源名/资源表示
/emp/1 GET:查询id为1号的员工
/emp/1 PUT:修改id为1号的员工
/emp/1 DELETE:删除id为1号的员工
/emp POST:新增员工
/emps GET:查询所有员工
- 展示所有员工
访问index.jsp,请求直接转发到/emps,控制器查询出所有员工放入请求域中,转发到页面展示
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:forward page="/emps"></jsp:forward>
控制器
/**
* 查询所有员工信息用于展示
* @param model
* @return
*/
@RequestMapping("/emps")
public String getEmps(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("employees",employees);
return "list";
}
展示页面
<h1>员工列表页面</h1>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
<th>Gender</th>
<th>Department</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<c:forEach items="${requestScope.employees}" var="employee">
<tr>
<td>${employee.id}</td>
<td>${employee.lastName}</td>
<td>${employee.email}</td>
<td>${employee.gender==0?"Female":"Male"}</td>
<td>${employee.department.departmentName}</td>
<td><a href="${ctp}/emp/${employee.id}">Edit</a></td>
<td><a href="${ctp}/emp/${employee.id}" class="deleteBtn">Delete</a></td>
</tr>
</c:forEach>
</table>
点击添加员工按钮,请求发送到控制器,保存表单标签所需数据,跳转至员工添加页面,添加完成后点击提交,请求发送到控制器,控制器保存数据之后转发到列表页面
添加按钮
<%
request.setAttribute("ctp",request.getcontextpath());
%>
<h1>员工列表页面</h1>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
<th>Gender</th>
<th>Department</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<c:forEach items="${requestScope.employees}" var="employee">
<tr>
<td>${employee.id}</td>
<td>${employee.lastName}</td>
<td>${employee.email}</td>
<td>${employee.gender==0?"Female":"Male"}</td>
<td>${employee.department.departmentName}</td>
<td><a href="${ctp}/emp/${employee.id}">Edit</a></td>
<td><a href="${ctp}/emp/${employee.id}" class="deleteBtn">Delete</a></td>
</tr>
</c:forEach>
</table>
<a href="${ctp}/toAddEmp">添加员工</a>
控制器保存数据
/**
* 跳转至添加员工操作,并将部门信息以及表单标签要求的对象存入隐含模型中
* @param model
* @return
*/
@RequestMapping("/toAddEmp")
public String toAddEmp(Model model){
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
model.addAttribute("employee",new Employee());
return "addEmp";
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加员工</title>
</head>
<body>
<%
request.setAttribute("ctp",request.getcontextpath());
%>
<h1>添加员工</h1>
<%--
表单标签:
1. 使用SpringMVC表单标签可以实现将模型中的属性和HTML表单元素相绑定,以实现表单数据更便捷的编辑和表单值的回显
2. 在SpringMVC中使用form表单标签,path指定的属性,必须要在隐含模型中找到某个对象,该对象拥有这一属性,默认对象名为command,使用modelattribute可以修改默认寻找的对象名
--%>
<form:form action="${ctp}/emp" modelattribute="employee" method="post">
<%--
path:
1. 原生input标签的name属性值
2. 自动回显隐含模型中某个对象对应的该属性的值
--%>
LastName:<form:input path="lastName"/><br/>
Email:<form:input path="email"/><br/>
Gender:<form:radiobutton path="gender" value="1"/>Male <form:radiobutton path="gender" value="0"/>Female<br/>
<%--
items:指定要遍历的集合
itemLabel:指定遍历出来的对象的哪个属性作为option标签体的值
itemValue:指定历出来的对象的哪个属性作为要提交的Value属性值
--%>
Department:<form:select path="department.id" items="${departments}" itemLabel="departmentName" itemValue="id"></form:select><br/>
<input type="submit" value="Submit"/>
</form:form>
<%--<form action="addEmp">
LastName:<input type="text" name="lastName"><br/>
Email:<input type="text" name="email"><br/>
Gender:<input type="radio" name="gender" value="1">Male <input type="radio" name="gender" value="0">Female<br/>
Department:
<select>
<c:forEach items="${requestScope.departments}" var="department">
<option value="${department.id}">${department.departmentName}</option>
</c:forEach>
</select>
<input type="submit" value="Submit">
</form>--%>
</body>
</html>
控制器添加员工
/**
* 添加员工
* @param employee
* @return
*/
@RequestMapping(value = "/emp",method = RequestMethod.POST)
public String addEmp(Employee employee){
// System.out.println("要添加的员工信息为" + employee);
employeeDao.save(employee);
//返回列表页面
return "redirect:/emps";
}
- 修改员工信息
点击修改按钮,控制器根据id获取员工信息和部门信息并放入隐含模型,转发到修改页面,修改之后点击提交,并封装到@modelattribute提前获取的员工对象中,控制器保存修改
控制器获取员工信息和部门信息
/**
* 根据id获取员工信息和部门信息
* @param id
* @param model
* @return
*/
@RequestMapping(value = "/emp/{id}",method = RequestMethod.GET)
public String getEmp(@PathVariable("id")Integer id,Model model){
//1. 根据id查询出员工信息
Employee employee = employeeDao.get(id);
//2. 放入隐含模型中
model.addAttribute("employee",employee);
//3. 查询出部门信息,放入隐含模型中
model.addAttribute("departments",departmentDao.getDepartments());
return "editEmp";
}
员工修改页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>修改员工信息</title>
</head>
<body>
<%
request.setAttribute("ctp",request.getcontextpath());
%>
<h1>修改 ${requestScope.employee.lastName}</h1>
<form:form action="${ctp}/emp/${employee.id}" modelattribute="employee" method="post">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" value="${employee.id}"/>
Email:<form:input path="email"/><br/>
Gender:<form:radiobutton path="gender" value="1"/>Male<form:radiobutton path="gender" value="0"/>Female<br/>
Departments:<form:select items="${departments}" path="department.id" itemLabel="departmentName" itemValue="id"></form:select><br/>
<input type="submit" value="Submit"/>
</form:form>
</body>
</html>
/**
* 提前获取员工信息并放入隐藏域中
* @param id
* @param model
*/
@modelattribute
public void mymodelattribute(@RequestParam(value = "id",required = false)Integer id, Model model){
if(id != null){
// System.out.println(employeeDao.get(id));
model.addAttribute("employee",employeeDao.get(id));
}
}
更新员工信息
/**
* 更新员工信息
* @param employee
* @return
*/
@RequestMapping(value = "/emp/{id}",method = RequestMethod.PUT)
public String updateEmp(@modelattribute("employee") Employee employee){
// System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
- 删除员工
利用JavaScript脚本,完成点击超链接,能向服务器发送DELETE请求功能
JavaScript脚本和表单
<form id="deleteForm" action="" method="post">
<input type="hidden" name="_method" value="delete"/>
</form>
<script type="text/javascript">
$(function () {
// alert("hello");
$(".deleteBtn").click(function () {
var isDelete = confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() +"】吗?");
if(isDelete){
//1. 更改表单的提交请求地址
$("#deleteForm").attr("action",this.href);
//2. 表单提交
$("#deleteForm").submit();
}
return false;
});
});
</script>
控制器
/**
* 删除员工
* @param id
* @return
*/
@RequestMapping(value = "/emp/{id}",method = RequestMethod.DELETE)
public String deleteEmp(@PathVariable("id")Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
数据绑定
SpringMVC在封装自定义类型的对象时,如何进行数据的绑定?
页面提交的数据都是字符串,因此数据的绑定涉及到以下的操作
- 数据绑定期间的数据类型转换,String-->Integer,String-->Boolean...
- 数据绑定期间的数据格式化问题,如提交的日期进行转换,字符串2021-06-15转换成为Date对象
- 数据校验,包括前端校验和后台校验,只有合法的数据才能进行绑定
SpringMVC主框架将ServletRequest对象及目标方法的参数传递给WebDataBinderFactory实例,创建WebDataBinder对象,WebDataBinder负责数据绑定的工作
WebDataBinder对象调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换和格式化工作
调用Validators组件对已绑定的数据进行数据合法性校验,生成数据绑定结果bindingResult
WebDataBinder是核心部件
@H_955_4@modelattributeMethodProcessor/**
* Resolve the argument from the model or if not found instantiate it with
* its default if it is available. The model attribute is then populated
* with request values via data binding and optionally validated
* if {@code @java.validation.Valid} is present on the argument.
* @throws BindException if data binding and validation result in an error
* and the next method parameter is not of type {@link Errors}.
* @throws Exception if WebDataBinder initialization fails.
*/
@Override
public final Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest request, WebDataBinderFactory binderFactory)
throws Exception {
String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name)) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) {
//将页面提交的数据封装进JavaBean中
bindRequestParameters(binder, request);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionrequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return binder.getTarget();
}
conversionService组件中的converters,可以进行各种类型的转换
自定义类型转换器?
ConversionService是一个接口,利用converter实现类型转换
自定义类型转换器,需实现Converter接口,重写convert()方法,自定义转换规则
<%--将所有员工信息都作为字符串快速添加--%>
<form action="${ctp}/quickAdd">
<input type="text" name="empInfo" value="[email protected]" />
<input type="submit" value="quickAdd"/>
</form>
自定义类型转换器
public class MyConverter implements Converter<String,Employee> {
@Autowired
DepartmentDao departmentDao;
//自定义的转换规则
@Override
public Employee convert(String s) {
Employee employee = new Employee();
//s:[email protected]
if(s.contains("-")){
String[] split = s.split("-");
employee.setLastName(split[0]);
employee.setEmail(split[1]);
employee.setGender(Integer.parseInt(split[2]));
employee.setDepartment(departmentDao.getDepartment(Integer.parseInt(split[3])));
}
return employee;
}
}
Converter是ConversionService中的组件,因此需要配置包含了我们自定义的类型转换器的ConversionService组件
ConversionServicefactorybean:创建ConversionService组件
<!--配置自定义converter的ConversionService
使用FormattingConversionServicefactorybean,既具有格式化功能,也具有类型转换功能-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServicefactorybean">
<property name="converters">
<set>
<bean class="com.th1024.bean.MyConverter"></bean>
</set>
</property>
</bean>
<!--conversion-service="conversionService":使用自定义的ConversionService-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
解析<mvc:annotation-driven></mvc:annotation-driven>
class AnnotationDrivenBeanDeFinitionParser implements BeanDeFinitionParser {
private static final boolean JSR303Present = ClassUtils.isPresent(
"javax.validation.Validator", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());
private static final boolean jacksonPresent =
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());
private static boolean romePresent =
ClassUtils.isPresent("com.sun.syndication.Feed.WireFeed", AnnotationDrivenBeanDeFinitionParser.class.getClassLoader());
@Override
public BeanDeFinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
CompositeComponentDeFinition compDeFinition = new CompositeComponentDeFinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDeFinition);
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
RootBeanDeFinition handlerMappingDef = new RootBeanDeFinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedname(handlerMappingDef);
if (element.hasAttribute("enable-matrix-variables") || element.hasAttribute("enableMatrixVariables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute(
element.hasAttribute("enable-matrix-variables") ? "enable-matrix-variables" : "enableMatrixVariables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);
RootBeanDeFinition bindingDef = new RootBeanDeFinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
String asyncTimeout = getAsyncTimeout(element, source, parserContext);
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
RootBeanDeFinition handlerAdapterDef = new RootBeanDeFinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
if (element.hasAttribute("ignore-default-model-on-redirect") || element.hasAttribute("ignoreDefaultModelOnRedirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute(
element.hasAttribute("ignore-default-model-on-redirect") ? "ignore-default-model-on-redirect" : "ignoreDefaultModelOnRedirect"));
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
if (argumentResolvers != null) {
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
handlerAdapterDef.getPropertyValues().add("customreturnValueHandlers", returnValueHandlers);
}
if (asyncTimeout != null) {
handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
}
if (asyncExecutor != null) {
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
}
handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedname(handlerAdapterDef);
String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
RootBeanDeFinition uriCompContribDef = new RootBeanDeFinition(CompositeUriComponentsContributorfactorybean.class);
uriCompContribDef.setSource(source);
uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
parserContext.getReaderContext().getRegistry().registerBeanDeFinition(uriCompContribName, uriCompContribDef);
RootBeanDeFinition csInterceptorDef = new RootBeanDeFinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getconstructorargumentValues().addindexedArgumentValue(0, conversionService);
RootBeanDeFinition mappedCsInterceptorDef = new RootBeanDeFinition(MappedInterceptor.class);
mappedCsInterceptorDef.setSource(source);
mappedCsInterceptorDef.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
mappedCsInterceptorDef.getconstructorargumentValues().addindexedArgumentValue(0, (Object) null);
mappedCsInterceptorDef.getconstructorargumentValues().addindexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedname(mappedCsInterceptorDef);
RootBeanDeFinition exceptionHandlerExceptionResolver = new RootBeanDeFinition(ExceptionHandlerExceptionResolver.class);
exceptionHandlerExceptionResolver.setSource(source);
exceptionHandlerExceptionResolver.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
String methodExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedname(exceptionHandlerExceptionResolver);
RootBeanDeFinition responseStatusExceptionResolver = new RootBeanDeFinition(ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedname(responseStatusExceptionResolver);
RootBeanDeFinition defaultExceptionResolver = new RootBeanDeFinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDeFinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedname(defaultExceptionResolver);
parserContext.registerComponent(new BeanComponentDeFinition(handlerMappingDef, methodMappingName));
parserContext.registerComponent(new BeanComponentDeFinition(handlerAdapterDef, handlerAdapterName));
parserContext.registerComponent(new BeanComponentDeFinition(uriCompContribDef, uriCompContribName));
parserContext.registerComponent(new BeanComponentDeFinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
parserContext.registerComponent(new BeanComponentDeFinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
parserContext.registerComponent(new BeanComponentDeFinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDeFinition(mappedCsInterceptorDef, mappedInterceptorName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
parserContext.popAndRegisterContainingComponent();
return null;
}
<mvc:default-servlet-handler/>和<mvc:annotation-driven/>
- 若两个标签都不配置,则工程动态资源能访问,静态资源无法访问
HandlerMapping
DefaultAnnotationHandlerMapping保存了请求的映射信息,保证动态资源的正确访问,而静态资源的请求映射没有保存在其中,故静态资源无法访问
AnnotationMethodHandlerAdapter执行目标方法,过时的类
- 只配置<mvc:default-servlet-handler/>,能访问静态资源,无法访问动态资源
HandlerMapping
SimpleUrlHandlerMapping直接将所有请求都交给服务器处理,能访问静态资源而不能访问动态资源
- 需同时配置两个标签,才能保证既能访问动态资源,也能访问静态资源
RequestMappingHandlerMapping中的handlerMethods属性保存了请求应该由哪个方法来处理的信息
AnnotationMethodHandlerAdapter被替换成了RequestMappingHandlerAdapter,该适配器执行目标方法期间,利用各种各样的参数解析器解析参数
日期格式化
<!--配置自定义converter的ConversionService
使用FormattingConversionServicefactorybean,既具有格式化功能,也具有类型转换功能-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServicefactorybean">
<property name="converters">
<set>
<bean class="com.th1024.bean.MyConverter"></bean>
</set>
</property>
</bean>
使用注解@DateTimeFormat规定日期格式
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
数据校验
不仅需要前端校验,同时也需要后端校验
利用JSR303进行数据校验
导包:
classmate-0.8.0.jar
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar
@NotEmpty//该属性必须非空
@Length(min = 6,max = 18)//该属性长度必须位于6-18之间
private String lastName;
@Email//该属性必须是一个合法的电子邮箱地址
private String email;
//1 male, 0 female
private Integer gender;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past//该属性必须是一个过去的时间
private Date birth;
利用@Valid注解,使SpringMVC封装数据时进行数据校验,在要封装数据的对象之后紧跟一个BindingResult对象,封装校验结果
@RequestMapping(value = "/emp",method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result){
// System.out.println("要添加的员工信息为" + employee);
boolean hasErrors = result.hasErrors();
if(hasErrors){
return "addEmp";//?如何跳转到/toAddEmp的同时携带错误信息
}else{
employeeDao.save(employee);
//返回列表页面
return "redirect:/emps";
}
}
LastName:<form:input path="lastName"/><form:errors path="lastName"/><br/>
Email:<form:input path="email"/><form:errors path="lastName"/><br/>
Gender:<form:radiobutton path="gender" value="1"/>Male <form:radiobutton path="gender" value="0"/>Female<br/>
Brith:<form:input path="birth"/><form:errors path="birth"/><br/>
@NotEmpty(message = "用户名不能为空!")
@Length(min = 6,max = 18)
private String lastName;
SpringMVC支持Ajax
导包
jackson-annotations-2.1.5.jar
jackson-core-2.1.5.jar
jackson-databind-2.1.5.jar
/**
* @ResponseBody
* 将返回的数据放入响应体中,如果是对象则自动转换为json字符串
* @return
*/
@ResponseBody
@RequestMapping("/ajaxGetAll")
public Collection<Employee> ajaxGetAll(){
Collection<Employee> all = employeeDao.getAll();
return all;
}
对JavaBean属性进行设置
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past
//转换成json字符串时进行类型转换
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birth;
//转换成json字符串时忽略该属性
@JsonIgnore
private Department department;
在页面发送Ajax请求,并回显返回的json数据
<a href="ajaxGetAll">ajax获取所有员工</a>
<div></div>
<script type="text/javascript">
$("a:first").click(function () {
$.ajax({
url:"${ctp}/ajaxGetAll",
type:"GET",
success:function (data) {
$.each(data,function () {
var empInfo = this.lastName + "-" + this.birth;
$("div").append(empInfo + "<br/>");
});
}
});
return false;
});
</script>
@RequestBody获取请求体数据
/**
* @RequestBody
* 接收json字符串并封装
* @param employee
* @return
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Employee employee){
System.out.println("对象信息:" + employee);
return "success";
}
测试页面发送数据和json字符串
<body>
<form action="${ctp}/testRequestBody" enctype="multipart/form-data" method="post">
<input name="username" value="Tomcat" />
<input name="password" value="666666" />
<input type="file" name="file"/>
<input type="submit" />
</form>
<a href="${ctp}/testRequestBody">ajax发送json字符串</a>
</body>
<script type="text/javascript">
$("a:first").click(function() {
//点击发送ajax请求,请求带的数据是json
var emp = {
lastName : "张三",
email : "[email protected]",
gender : 0
};
//alert(typeof emp);
//js对象
var empStr = JSON.stringify(emp);
//alert(typeof empStr);
$.ajax({
url : '${ctp}/testRequestBody',
type : "POST",
data : empStr,
contentType : "application/json",
success : function(data) {
alert(data);
}
});
return false;
});
</script>
拦截器
SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作
preHandle():在目标方法之前运行,返回Boolean值
postHandle():在目标方法运行之后运行
afterCompletion():在请求完成来到页面之后运行
重写这三个方法
<!--配置拦截器-->
<mvc:interceptors>
<!--配置某个拦截器,默认为拦截所有请求-->
<bean class="com.th1024.component.MyFirstInterceptor"></bean>
</mvc:interceptors>
运行流程
MyFirstInterceptor...preHandle...
test01....
MyFirstInterceptor...postHandle...
success.jsp....
MyFirstInterceptor...afterCompletion
若preHandle()返回false,则没有后面的流程,若preHandle()放行,则afterCompletion()都会执行
如果配置了多个拦截器?
<!--配置拦截器-->
<mvc:interceptors>
<!--配置某个拦截器,默认为拦截所有请求-->
<bean class="com.th1024.component.MyFirstInterceptor"></bean>
<!--具体配置拦截器-->
<mvc:interceptor>
<!--拦截器只拦截test01请求-->
<mvc:mapping path="/test01"/>
<bean class="com.th1024.component.MySecondInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
正常流程,类似JavaWeb中Filter的运行流程,“先进后出”原则
MyFirstInterceptor...preHandle...
MySecondInterceptor...preHandle...
test01....
MySecondInterceptor...postHandle...
MyFirstInterceptor...postHandle...
success.jsp....
MySecondInterceptor...afterCompletion...
MyFirstInterceptor...afterCompletion
异常处理
SpringMVC默认装配以下三个异常解析器
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
若配置了<mvc:annotation-driven/>,SpringMVC会将AnnotationMethodHandlerExceptionResolver替换成ExceptionHandlerExceptionResolver
三个异常解析器的作用
ExceptionHandlerExceptionResolver:处理注解@ExceptionHandler所指定的异常类型
ResponseStatusExceptionResolver:处理标注了@ResponseStatus的自定义异常
DefaultHandlerExceptionResolver:处理SpringMVC自带的异常
处理异常
private void processdispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
//判断是否发生异常
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
//处理异常
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
if (mv != null && !mv.wasCleared()) {
//来到页面
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Null ModelAndView returned to dispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
processHandlerException()
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ModelAndView exMv = null;
Iterator var6 = this.handlerExceptionResolvers.iterator();
//利用循环,用每一个异常解析器轮流处理异常
while(var6.hasNext()) {
HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var6.next();
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
return null;
} else {
if (!exMv.hasView()) {
exMv.setViewName(this.getdefaultviewName(request));
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
return exMv;
}
} else {
//若所有异常解析器均无法处理异常,则抛出给服务器
throw ex;
}
}
利用@ExceptionHandler自定义异常处理解析器
/**
* @ExceptionHandler注解,处理本类发生的异常
* 参数位置添加Exception对象用于接收异常信息
* 参数位置不能写Model或Map或ModelMap对象,因此返回ModelAndView对象用于携带异常信息至页面
* 如果有多个@ExceptionHandler都能处理同一种异常,处理范围更精确的优先
* 全局的异常处理和本类的异常处理,无论处理范围精确与否,本类的处理优先
* @param e
* @return
*/
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public ModelAndView handleException01(Exception e){
System.out.println("本类的:handleException01..." + e);
ModelAndView view = new ModelAndView("myError");
view.addobject("ex",e);
return view;
}
自定义集中处理异常的类
/**
* 集中处理异常
* @ControllerAdvice:标注在集中处理异常的类上,将该类加入ioc容器中
*
* @author TuHong
* @create 2021-06-22 11:21
*/
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView handleException01(Exception e){
System.out.println("全局:handleException01..." + e);
ModelAndView view = new ModelAndView("myError");
view.addobject("ex",e);
return view;
}
@ExceptionHandler(value = Exception.class)
public ModelAndView handleException02(Exception e){
System.out.println("全局:handleException02..." + e);
ModelAndView view = new ModelAndView("myError");
view.addobject("ex",e);
return view;
}
}
/**
* @ResponseStatus:标注在自定义异常类上
*
* @author TuHong
* @create 2021-06-22 11:39
*/
@ResponseStatus(reason = "用户名不正确",value = HttpStatus.NOT_ACCEPTABLE)
public class UserNameNotFoundException extends RuntimeException{
static final long serialVersionUID = -7034897190461466939L;
}
@RequestMapping("/handle02")
public String handle02(String username){
System.out.println("handle02..." + username);
if(!"admin".equals(username)){
System.out.println("登录失败");
throw new UserNameNotFoundException();
}
System.out.println("登录成功");
return "success";
}
DefaultHandlerExceptionResolver处理SpringMVC产生的异常,如HttpRequestMethodNotSupportedException,来到错误页面
DefaultHandlerExceptionResolver能处理的异常
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return this.handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException)ex, request, response, handler);
}
if (ex instanceof HttpRequestMethodNotSupportedException) {
return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotSupportedException) {
return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotAcceptableException) {
return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);
}
if (ex instanceof MissingServletRequestParameterException) {
return this.handleMissingServletRequestParameter((MissingServletRequestParameterException)ex, request, response, handler);
}
if (ex instanceof ServletRequestBindingException) {
return this.handleServletRequestBindingException((ServletRequestBindingException)ex, request, response, handler);
}
if (ex instanceof ConversionNotSupportedException) {
return this.handleConversionNotSupported((ConversionNotSupportedException)ex, request, response, handler);
}
if (ex instanceof TypeMismatchException) {
return this.handleTypeMismatch((TypeMismatchException)ex, request, response, handler);
}
if (ex instanceof HttpMessageNotReadableException) {
return this.handleHttpMessageNotReadable((HttpMessageNotReadableException)ex, request, response, handler);
}
if (ex instanceof HttpMessageNotWritableException) {
return this.handleHttpMessageNotWritable((HttpMessageNotWritableException)ex, request, response, handler);
}
if (ex instanceof MethodArgumentNotValidException) {
return this.handleMethodArgumentNotValidException((MethodArgumentNotValidException)ex, request, response, handler);
}
if (ex instanceof MissingServletRequestPartException) {
return this.handleMissingServletRequestPartException((MissingServletRequestPartException)ex, request, response, handler);
}
if (ex instanceof BindException) {
return this.handleBindException((BindException)ex, request, response, handler);
}
if (ex instanceof NoHandlerFoundException) {
return this.handleNoHandlerFoundException((NoHandlerFoundException)ex, request, response, handler);
}
} catch (Exception var6) {
this.logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", var6);
}
return null;
}
基于配置的方式自定义异常处理解析器,SimpleMappingExceptionResolver
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- exceptionMappings:配置哪些异常去哪些页面 -->
<property name="exceptionMappings">
<props>
<!-- key:异常全类名;value:要去的页面视图名; -->
<prop key="java.lang.NullPointerException">myerror</prop>
</props>
</property>
<!--指定错误信息取出时使用的key -->
<property name="exceptionAttribute" value="ex"></property>
</bean>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。