SpringMVC
1.SpringMVC概述
MVC:
- Model(模型): 数据模型,提供要展示的数据,:Value Object(数据Dao) 和 服务层(行为Service),提供数据和业务。
- View(视图): 负责进行模型的展示,即用户界面
- Controller(控制器): 调度员,接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。
SpringMVC的特点:
- Spring为展现层提供的基于MVC设计理念的Web框架
- SpirngMVC通过一套MVC注解,让POJO成为处理请求的控制器,而无须实现任何接口
- 支持REST风格的URL请求
- 采用了松散耦合可拔插组件结构,扩展性和灵活性
hello world的运行配置这里就不记录了
2. Url请求
2.1 运行流程
- 客户端点击链接发送请求:http://localhost:8080/hello01;
- 来到tomcat服务器;
- SpringMVC的前端控制器收到所有请求;
- 看请求地址和@RequestMapping标注的哪个匹配,来找到底使用哪个类的哪个方法来处理;
- 前端控制器找到目标处理器类和目标方法,直接利用反射执行目标方法;
- 方法执行完后有一个返回值,SpringMVC认为这个返回值就是要去的页面地址;
- 拿到方法返回值后,视图解析器进行拼串得到完整的页面地址
- 得到页面地址,前端控制器帮我们转发到页面
2.2 @RequestMapping
01 标注在方法上
告诉SpringMVC这个方法用来处理什么请求。
@RequestMapping("/hello01")
中的 /
可以省略,就是默认从当前项目下开始。
02 标注在类上
表示为当前类中的所有方法的请求地址,指定一个基准路径。toSuccess()方法处理的请求路径是/haha/hello01
。
@Controller
@RequestMapping("/haha")
public class HelloController {
@RequestMapping(value = "/hello01")
public String toSuccess(){
System.out.println("请求成功页面");
return "success";
}
}
03 规定请求方式
method属性规定请求方式,默认是所求请求方式都行。method = RequestMethod.GET,method = RequestMethod.POST。
如果方法不匹配会报:HTTP Status 405 错误 – 方法不被允许
@RequestMapping(value = "/hello01",method = RequestMethod.GET)
public String toSuccess(){
System.out.println("请求成功页面");
return "success";
}
组合用法
- @GetMapping 等价于 @RequestMapping(method =RequestMethod.GET)
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
04 规定请求参数
params属性规定请求参数。会造成错误:HTTP Status 400 – 错误的请求
不携带该参数,表示参数值为null;携带了不给值表示参数值是空串
//必须携带username参数
@RequestMapping(value = "/hello03",params ={"username"})
//必须不携带username参数
@RequestMapping(value = "/hello03",params ={"!username"})
//必须携带username参数,且值必须为123
@RequestMapping(value = "/hello03",params ={"username=123"})
//username参数值必须不为123,不携带或者携带了不是123都行
@RequestMapping(value = "/hello03",params ={"username=!123"})
//username参数值必须不为123,不携带password,携带page
@RequestMapping(value = "/hello03",params ={"username=!123","page","!password"})
05 规定请求头
headers属性规定请求头。其中User-Agent:浏览器信息
@RequestMapping(value="/test3",headers= {"Content-Type=application/json"})
谷歌浏览器:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.3
06 Ant风格URL
URL地址可以写模糊的通配符,模糊和精确多个匹配情况下精确优先。
?:替代任意一个字符
@RequestMapping( "/hello0?") /
:*替代任意多个字符或一层路径
@RequestMapping( "/hello0*") //任意多个字符
@RequestMapping( "/a/*/hello01") //一层路径
@RequestMapping(value = "/test/*/a")
public String myMethodTest01() {
System.out.println("post01");
return "success";
}
// test/[^\/]+/b ->post01
// /test/*/b ->post02
@RequestMapping(value = "/test/**/a")
public String myMethodTest02() {
System.out.println("post02");
return "success";
}
**:替代任意多层路径
@RequestMapping( "/a/**/hello01") //任意多层路径
07 PathVariable
用来获取Url上传的参数值
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET)
public String myMethodTest03(@PathVariable("id") String id) {
System.out.println(id);
return "success";
}
2.3 Spring配置文件的默认位置
默认位置是 /WEB-INF/xxx-servlet.xml,其中xxx是自己在web.xml文件中配置的servlet-name属性。
例如:
dispatcherServlet-servlet.xml
当然也可以手动指定文件位置。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
2.4 url-pattern
/
拦截所有的请求,不拦截jsp
/*
拦截所有的请求,包括*.jsp,一旦拦截jsp页面就不能显示了。. jsp是tomcat处理的事情
看Tomcat的配置文件web.xml中,有DefaultServlet和JspServlet,
DefaultServlet是Tomcat中处理静态资源的,Tomcat会在服务器下找到这个资源并返回。如果我们自己配置
url-pattern=/
,相当于禁用了Tomcat服务器中的DefaultServlet,这样如果请求静态资源,就会去找前端控制器找@RequestMapping,这样静态资源就不能访问了。解决办法:<!-- 告诉Spring MVC自己映射的请求就自己处理,不能处理的请求直接交给tomcat --> <mvc:default-servlet-handler /> <!--开启MVC注解驱动模式,保证动态请求和静态请求都能访问--> <mvc:annotation-driven/>
JspServlet,保证了jsp可以正常访问
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
3. REST风格
3.1 概述
REST就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。其强调HTTP应当以资源为中心,并且规范了URI的风格;规范了HTTP请求动作(GET/PUT/POST/DELETE/HEAD/OPTIONS)的使用,具有对应的语义。
- 资源(Resource):网络上的一个实体,每种资源对应一个特定的URI,即URI为每个资源的独一无二的识别符;
- 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层。比如txt、HTML、XML、JSON格式等;
- 状态转化(State Transfer):每发出一个请求,就代表一次客户端和服务器的一次交互过程。GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
3.2 页面上发出PUT请求
对一个资源的增删改查用请求方式来区分:
- /book/1 GET:查询1号图书
- /book/1 DELETE:删除1号图书
- /book/1 PUT:修改1号图书
- /book POST:新增图书
页面上只能发出GET请求和POST请求。将POST请求转化为put或者delete请求的步骤:
- 把前端发送方式改为post 。
- 在web.xml中配置一个filter:HiddenHttpMethodFilter过滤器
- 必须携带一个键值对,key=_method, value=put或者delete
<!--这个过滤器的作用 :就是将post请求转化为put或者delete请求-->
<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>
<form action="hello03" method="post">
<input type="hidden" name="_method" value="delete">
<input type="submit" name="提交">
</form>
注意
高版本Tomcat会出现问题:JSPs only permit GET POST or HEAD,在页面上加上异常处理即可
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
1
4 请求参数处理
4.1 传入参数
1. 如果提交的参数名称和处理方法的参数名一致,则无需处理,直接使用
提交数据 : http://localhost:8080/hello05?username=zhangsan,控制台会输出zhangsan
@RequestMapping("/hello05")
public String test03(String username) {
System.out.println(username);
return "success";
}
2. 提交的参数名称和处理方法的参数名不一致,使用@RequestParam注解
注解@RequestParam
可以获取请求参数,默认必须携带该参数,也可以指定required=false
,和没携带情况下的默认值defaultValue
@RequestMapping("/hello05")
public String test03(@RequestParam(value = "username",required = false, defaultValue ="hehe" ) String name) {
System.out.println(name);
return "success";
}
还有另外两个注解:
@RequestHeader
:获取请求头中的信息,比如User-Agent:浏览器信息@RequestMapping("/hello05") public String test03(@RequestHeader("User-Agent" ) String name) { System.out.println(name); return "success"; }
@CookieValue
:获取某个cookie的值@RequestMapping("/hello05") public String test03(@CookieValue("JSESSIONID" ) String name) { System.out.println(name); return "success"; }
4.2 传入一个对象
传入POJO,SpringMVC会自动封装,提交的表单域参数必须和对象的属性名一致,否则就是null,请求没有携带的字段,值也会是null。同时也还可以级联封装。
新建两个对象User和Address:
public class User {
private String username;
private Integer age;
private Address address;
//....
}
public class Address {
private String name;
private Integer num;
//....
}
前端请求:
<form action="hello06" method="post">
姓名: <input type="text" name="username"> <br>
年龄: <input type="text" name="age"><br>
地址名:<input type="text" name="address.name"><br>
地址编号:<input type="text" name="address.num"><br>
<input type="submit" name="提交">
</form>
后端通过对象名也能拿到对象的值,没有对应的值则为null
@RequestMapping("/hello06")
public String test03(User user) {
System.out.println(user);
return "success";
}
4.3 传入原生ServletAPI
处理方法还可以传入原生的ServletAPI:
@RequestMapping("/hello07")
public String test04(HttpServletRequest request, HttpSession session) {
session.setAttribute("sessionParam","我是session域中的值");
request.setAttribute("reqParam","我是request域中的值");
return "success";
}
通过EL表达式获取到值,${requestScope.reqParam}
:
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
<title>成功页面</title>
</head>
<body>
<h1>这里是成功页面</h1>
${requestScope.reqParam}
${sessionScope.sessionParam}
</body>
</html>
4.4 乱码问题
一定要放在在其他Filter前面。
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--解决请求乱码-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<!--解决响应乱码-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--在Tomcat的server.xml中的8080处 URLEncoding="UTF-8"-->
5. 数据输出
5.1 Map、Model、ModelMap
实际上都是调用的 BindingAwareModelMap(隐含模型),将数据放在请求域(requestScope)中进行转发,用EL表达式可以取出对应的值。
/**
* SpringMVC除过在方法上传入原生的request和session外还能怎么样把数据带给页面
*
* 1)、可以在方法处传入Map、或者Model或者ModelMap。
* 给这些参数里面保存的所有数据都会放在请求域中。可以在页面获取
* 关系:
* Map,Model,ModelMap:最终都是BindingAwareModelMap在工作;
* 相当于给BindingAwareModelMap中保存的东西都会被放在请求域中;
*
* Map(interface(jdk)) Model(interface(spring))
* || //
* || //
* \/ //
* ModelMap(class) //
* \\ //
* \\ //
* ExtendedModelMap
* ||
* \/
* BindingAwareModelMap
*
* 2)、方法的返回值可以变为ModelAndView类型;
* 既包含视图信息(页面地址)也包含模型数据(给页面带的数据);
* 而且数据是放在请求域中;
* request、session、application;
*
*
*/
- Map
@RequestMapping("/Api2")
public String api2(Map<String,Object> map){
map.put("msg","hello");
return "map";
}
- Model
@RequestMapping("/Api3")
public String api3(Model model){
model.addAttribute("msg","hello2");
return "map";
}
- ModelMap
@RequestMapping("/Api4")
public String api4(ModelMap modelMap){
modelMap.addAttribute("msg","hello3");
return "map";
}
map页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
pageScope: ${pageScope.msg}
requestScope : ${requestScope.msg}
sessionScope: ${sessionScope.msg}
applicationScope: ${applicationScope.msg}
</body>
</html>
【补充】jsp的4个作用域 pageScope、requestScope、sessionScope、applicationScope的区别:
- page指当前页面有效。在一个jsp页面里有效
- request 指在一次请求的全过程中有效,即从http请求到服务器处理结束,返回响应的整个过程,存放在HttpServletRequest对象中。在这个过程中可以使用forward方式跳转多个jsp。在这些页面里都可以使用这个变量。
- Session是用户全局变量,在整个会话期间都有效。只要页面不关闭就一直有效(或者直到用户一直未活动导致会话过期,默认session过期时间为30分钟,或调用HttpSession的invalidate()方法)。存放在HttpSession对象中
- application是程序全局变量,对每个用户每个页面都有效。存放在ServletContext对象中。它的存活时间是最长的,如果不进行手工删除,它们就一直可以使用
5.2 ModelAndView
返回一个模型视图对象ModerAndView, 既包含视图信息(页面地址),也包含模型数据(给页面带的数据)
@RequestMapping("/hello04")
public ModelAndView test04 (){
//新建一个模型视图对象,也可以直接传入名字
ModelAndView mv = new ModelAndView();
//封装要显示到视图中的数据
//相当于req.setAttribute("msg",HelloWorld!);
mv.addObject("msg","HelloWorld!");
//设置视图的名字,相当于之前的return "success";
mv.setViewName("success");
return mv;
}
5.3 @SessionAttributes
给Session域中携带数据使用注解@SessionAttributes
,只能标在类上,value属性指定key,type可以指定保存类型。这个注解会引发异常一般不用,就用原生API
@SessionAttributes(value = "msg")
:表示给BindingAwareModelMap中保存key为msg的数据时,在session中也保存一份;
@SessionAttributes(types = {String.class})
:表示只要保存String类型的数据时,给session中也放一份。
//表示给BindingAwareModelMap中保存key为msg的数据时,在session中也保存一份
@SessionAttributes(value = "msg")
@Controller
public class outputController {
@RequestMapping("/hello01")
public String test01 (Map<String,Object> map){
map.put("msg","HelloWorld!");
return "success";
}
}
5.4 @ModelAttribute
方法入参标注该注解后,入参的对象就会放到数据模型中,会提前于控制方法先执行,并发方法允许的结果放在隐含模型中。
处理这样的场景:
前端传来数据,SpringMVC自动封装成对象,实际上是创建了一个对象,每个属性都有默认值,然后将请求参数中对应是属性设置过来,但是如果没有的值将会是null,如果拿着这个数据去更新数据库,会造成其他字段也变为null。因此希望使用@ModelAttribute
,会在目标方法执行前先做一些处理
@ModelAttribute
public void myModelAttribute(ModelMap modelMap){
System.out.println("modelAttribute方法执行了");
//提前做一些处理
User user = new User("zhangsan",20);
//保存一个数据到BindingAwareModelMap中,目标方法可以从中取出来
modelMap.addAttribute("user",user);
}
@RequestMapping("/hello05")
public void test05(@ModelAttribute("user") User user){
System.out.println("目标方法执行了");
//在参数上加上@ModelAttribute注解,可以拿到提前存入的数据
System.out.println(user);
}
5.5 @ResponseBody
在控制器类中,在方法上使用**@ResponseBody**注解可以不走视图解析器,如果返回值是字符串,那么直接将字符串写到客户端;如果是一个对象,会将对象转化为JSON串,然后写到客户端。
或者在类上加 @RestController注解,可以让类中的所有方法都不走视图解析器,直接返回JSON字符串
6. 视图源码执行流程
6.1 SpringMVC的九大组件
/** 文件上传解析器*/
private MultipartResolver multipartResolver;
/** 区域信息解析器;和国际化有关 */
private LocaleResolver localeResolver;
/** 主题解析器;强大的主题效果更换 */
private ThemeResolver themeResolver;
/** Handler映射信息;HandlerMapping */
private List<HandlerMapping> handlerMappings;
/** Handler的适配器 */
private List<HandlerAdapter> handlerAdapters;
/** SpringMVC强大的异常解析功能;异常解析器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** */
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMap+Manager:SpringMVC中运行重定向携带数据的功能 */
private FlashMapManager flashMapManager;
/** 视图解析器; */
private List<ViewResolver> viewResolvers;
onRefresh()->initStrategies() DispatcherServlet中:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
6.2 SpringMVC执行流程(==重点!!!!==)
总体概览
用户发出请求,DispatcherServlet接收请求并拦截请求。
调用doDispatch()方法进行处理:
- getHandler():根据当前请求地址中找到能处理这个请求的目标处理器类(处理器);
- 根据当前请求在HandlerMapping中找到这个请求的映射信息,获取到目标处理器类
- mappedHandler = getHandler(processedRequest);
- getHandlerAdapter():根据当前处理器类找到能执行这个处理器方法的适配器;
- 根据当前处理器类,找到当前类的HandlerAdapter(适配器)
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- 使用刚才获取到的适配器(AnnotationMethodHandlerAdapter)执行目标方法;
- mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
- 目标方法执行后,会返回一个ModerAndView对象
- mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
- 根据ModerAndView的信息转发到具体页面,并可以在请求域中取出ModerAndView中的模型数据
- processDispatchResult(processedRequest, response, mappedHandler,
mv, dispatchException);
HandlerMapping为处理器映射器,保存了每一个处理器能处理哪些请求的映射信息,handlerMap
HandlerAdapter为处理器适配器,能解析注解方法的适配器,其按照特定的规则去执行Handler
过程源码
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;
// Determine handler for the current request.
//2、根据当前的请求地址找到那个类能来处理;
mappedHandler = getHandler(processedRequest);
//3、如果没有找到哪个处理器(控制器)能处理这个请求就404,或者抛异常
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//4、拿到能执行这个类的所有方法的适配器;(反射工AnnotationMethodHandlerAdapter)
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 {
// Actually invoke the handler.处理(控制)器的方法被调用
//控制器(Controller),处理器(Handler)
//5、适配器来执行目标方法;
//将目标方法执行完成后的返回值作为视图名,设置保存到ModelAndView中
//目标方法无论怎么写,最终适配器执行完成以后都会将执行后的信息封装成ModelAndView
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;
}
//转发到目标页面;
//6、根据方法最终执行完成后封装的ModelAndView;
//转发到对应页面,而且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);
}
}
}
7. 视图解析
7.1 forward和redirect前缀
通过SpringMVC来实现转发和重定向。
- 直接 return “success”,会走视图解析器进行拼串
- 转发:return “forward:/succes.jsp”;直接写绝对路径,/表示当前项目下,不走视图解析器
- 重定向:return “redirect:/success.jsp”;不走视图解析器
@Controller
public class ResultSpringMVC {
@RequestMapping("/hello01")
public String test1(){
//转发
//会走视图解析器
return "success";
}
@RequestMapping("/hello02")
public String test2(){
//转发二
//不走视图解析器
return "forward:/success.jsp";
}
@RequestMapping("/hello03")
public String test3(){
//重定向
//不走视图解析器
return "redirect:/success.jsp";
}
}
使用原生的ServletAPI时要注意,**/路径需要加上项目名才能成功**
@RequestMapping("/result/t2")
public void test2(HttpServletRequest req, HttpServletResponse resp) throwsIOException {
//重定向
resp.sendRedirect("/index.jsp");
}
@RequestMapping("/result/t3")
public void test3(HttpServletRequest req, HttpServletResponse resp) throwsException {
//转发
req.setAttribute("msg","/result/t3");
req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,resp);
}
7.2 mvc:view-controller
mvc:view-controller
:
直接将请求映射到某个页面,不需要写方法了:
注意:会走视图解析的功能
在ioc.xml中加入
<mvc:view-controller path="/toLoginPage" view-name="login"/>
<!--开启MVC注解驱动模式,不写这一句会导致只有上面toLginPage的映射可以找到,其他404-->
<mvc:annotation-driven/>
7.3 自定义视图解析器
扩展:加深视图解析器和视图对象;
- 视图解析器根据方法的返回值得到视图对象
- 多个视图解析器都会尝试能否得到视图对象
- 视图对象不同就可以具有不同功能
for (ViewResolver viewResolver : this.viewResolvers) {
//viewResolver视图解析器根据方法的返回值,得到一个View对象;
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
- 让我们的视图解析器工作
- 得到我们的视图对象
- 我们的视图对象自定义渲染逻辑
自定义视图和视图解析器的步骤
- 编写自定义的视图解析器,和视图实现类
public class MyViewResolver implements ViewResolver {
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (viewName.startsWith("myView:")){
return new MyView();
}else{
return null;
}
}
}
public class MyView implements View {
public String getContentType() {
return "text/html";
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("保存的数据:"+model);
response.getWriter().write("即将展现内容:");
}
}
- 视图解析器必须放在ioc容器中,让其工作,能创建出我们的自定义视图对象
<bean class="com.chenhui.view.MyViewResolver"></bean>
在源码中看到我们的编写的解析器
但是会被InternalResourceViewResolver先拦截了
MyViewResolver要实现Ordered接口
public class MyViewResolver implements ViewResolver, Ordered {
private Integer order = 0;
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (viewName.startsWith("myView:")) {
return new MyView();
} else {
return null;
}
}
public int getOrder() {
return this.order;
}
public void setOrder(Integer order) {
this.order = order;
}
}
<bean class="com.chenhui.view.MyViewResolver">
<property name="order" value="1"></property>
</bean>
发现顺序已经改变
到了我们的页面(虽然乱码),需要设置响应内容类型ContentType,过滤器filter只设置了编码格式
response.setContentType("text/html ");
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("保存的数据:"+model);
response.setContentType("text/html ");
response.getWriter().write("即将展现内容:");
//接着从Map获取相关键值对即可
}
即可成功!
8. 拦截器
SpringMVC提供了拦截器机制:
允许运行目标方法之前进行一些拦截工作,或者目标方法运行之后进行一些其他处理。Filter:javaWeb
HandlerInterceptor:SpringMVC
HandlerInterceptor:
preHandle:在目标方法运行之前调用:
- 返回boolean
- return true;(chain.doFilter())放行;
- return false;不放行
- 返回boolean
postHandle:在目标方法运行之后调用
afterCompletion:资源响应之后调用
8.1 操作步骤
实现HandlerInterceptor接口
package com.chenhui.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyFirstInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyFirstInterceptor...preHandle"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyFirstInterceptor...postHandle"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyFirstInterceptor...afterCompletion"); } }
配置拦截器
<mvc:interceptors> <!--默认拦截所有请求↓--> <!-- <bean class="com.chenhui.interceptor.MyFirstInterceptor"></bean>--> <!--拦截具体请求↓--> <mvc:interceptor> <!--只拦截path所对应的请求--> <mvc:mapping path="/testInter"/> <bean class="com.chenhui.interceptor.MyFirstInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
testInter控制器如下
@Controller public class InterceptorTestController { @RequestMapping("/testInter") public String testInterceptor(){ return "hello"; } }
hello.jsp:
<% pageContext.setAttribute("ctp",request.getContextPath());%> <%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %> <html> <head> <title>$Title$</title> </head> <body> <a href="testInter">测试拦截器</a> </body> </html> <%--<jsp:forward page="/emp"></jsp:forward>--%>
拦截器的运行流程
- preHandle
- 目标方法
- postHandle
- 页面渲染
- afterCompletion
其他流程:
只要preHandle不放行就没有以后的流程;
- preHandle 中return false
只要放行了,afterCompletion都会执行;
- 目标方法出现异常,afterCompletion也会执行
12.2 多个拦截器
与filter一样,拦截顺序与配置文件中的次序一样
MyFirstInterceptor...preHandle...
MySecondInterceptor...preHandle...
目标方法....
MySecondInterceptor...postHandle...
MyFirstInterceptor...postHandle...
响应页面....
MySecondInterceptor...afterCompletion...
MyFirstInterceptor...afterCompletion
异常流程:
哪一块Interceptor不放行
- 哪一块不放行从此以后都没有
MySecondInterceptor不放行
- 但是他前面已经放行了的拦截器的afterCompletion总会执行
总结interceptor的流程:
拦截器的preHandle:是按照顺序执行
拦截器的postHandle:是按照逆序执行
拦截器的afterCompletion:是按照逆序执行
已经放行了的拦截器的afterCompletion总会执行
9. 异常处理
9.1 异常源码
processDispatchResult(processedRequest, response, mappedHandler,
mv, dispatchException);
加了MVC异常处理,默认就是这个几个HandlerExceptionResolver
- ExceptionHandlerExceptionResolver
- ResponseStatusExceptionResolver
- DefaultHandlerExceptionResolver
顺序处理,如果异常解析器都不能处理就直接抛出去;
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) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//处理异常
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//===================================
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//来到页面
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
所有异常解析器尝试解析,解析完成进行后续,解析失败下一个解析器继续解析
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
exMv.setViewName(getDefaultViewName(request));
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
9.2 ExceptionHandler
局部异常处理
@Controller
public class ExceptionTestController {
@RequestMapping("/testException")
public String exceptionTest(Integer integer){
System.out.println("testException");
System.out.println(10/integer);
return "exception";
}
@ExceptionHandler(value = {ArithmeticException.class})
public String handleException01(){
System.out.println("handleException-Arithmetic");
return "myError";
}
}
Jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>运算出错</h1>
</body>
</html>
若要携带异常信息, 可以返回ModelAndView
注意:
- 异常信息不能给参数位置写Model
- 同个作用域,有多个Exception异常处理器,精确优先
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView handleException01(Exception exception){
System.out.println("handleException-Arithmetic");
System.out.println("exception:"+exception);
ModelAndView myError = new ModelAndView("myError");
myError.addObject("ex",exception);
return myError;
}
全局异常处理
异常处理控制器可以放在@ControllerAdvice下,作用域是全局
@ControllerAdvice
public class MyExceptionController {
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView handleException01(Exception exception){
System.out.println("handleException-Arithmetic");
System.out.println("exception:"+exception);
ModelAndView myError = new ModelAndView("myError");
myError.addObject("ex",exception);
return myError;
}
}
全局与本类都有匹配的异常处理器,本类的优先运行
9.3 @ResponseStatus
编写一个异常类
package com.chenhui.component;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(reason = "拒绝登录", value = HttpStatus.NOT_ACCEPTABLE)
public class UsernameNotFoundException extends RuntimeException {
static final long serialVersionUID = 1L;
}
测试:
@RequestMapping("/testException2")
public String exceptionTest2(String username){
System.out.println("testException");
if (!"admin".equals(username)){
System.out.println("登录失败");
//+++++抛出自己的错误信息
throw new UsernameNotFoundException();
}
System.out.println("登陆成功");
return "success";
}
结果:
9.4 DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver:
判断是否是SpringMVC自带的异常或Spring自己的异常:
如:HttpRequestMethodNotSupportedException。如果没人处理则它自己处理
默认的异常有
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response,
handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
9.5 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>
10. SpringMVC总结
SpringMVC运行流程:
**重点看大标题和7的小标题**
1、所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理
2、根据HandlerMapping中保存的请求映射信息找到,处理当前请求的,处理器执行链(包含拦截器)
3、根据当前处理器找到他的HandlerAdapter(适配器)
4、拦截器的preHandle先执行
5、适配器执行目标方法,并返回ModelAndView
1)、ModelAttribute注解标注的方法提前运行
2)、执行目标方法的时候(确定目标方法用的参数)
1)、有注解
2)、没注解:
1)、 看是否Model、Map以及其他的
2)、如果是自定义类型
1)、从隐含模型中看有没有,如果有就从隐含模型中拿
2)、如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,如果拿不到会抛异常
3)、都不是,就利用反射创建对象
6、拦截器的postHandle执行
7、处理结果;(页面渲染流程)
1)、如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
2)、调用render进行页面渲染
1)、视图解析器根据视图名得到视图对象
2)、视图对象调用render方法;
3)、执行拦截器的afterCompletion;
上图MVC:default-servlet-handler其实就是静态资源处理器,有配置就直接交给Tomcat