加载中...

SpringMVC


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 运行流程

  1. 客户端点击链接发送请求:http://localhost:8080/hello01;
  2. 来到tomcat服务器;
  3. SpringMVC的前端控制器收到所有请求;
  4. 看请求地址和@RequestMapping标注的哪个匹配,来找到底使用哪个类的哪个方法来处理;
  5. 前端控制器找到目标处理器类和目标方法,直接利用反射执行目标方法;
  6. 方法执行完后有一个返回值,SpringMVC认为这个返回值就是要去的页面地址;
  7. 拿到方法返回值后,视图解析器进行拼串得到完整的页面地址
  8. 得到页面地址,前端控制器帮我们转发到页面

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请求的步骤:

  1. 把前端发送方式改为post 。
  2. 在web.xml中配置一个filter:HiddenHttpMethodFilter过滤器
  3. 必须携带一个键值对,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执行流程(==重点!!!!==)

总体概览

  1. 用户发出请求,DispatcherServlet接收请求并拦截请求。
  2. 调用doDispatch()方法进行处理:
    1. getHandler():根据当前请求地址中找到能处理这个请求的目标处理器类(处理器);
      • 根据当前请求在HandlerMapping中找到这个请求的映射信息,获取到目标处理器类
      • mappedHandler = getHandler(processedRequest);
    2. getHandlerAdapter():根据当前处理器类找到能执行这个处理器方法的适配器;
      • 根据当前处理器类,找到当前类的HandlerAdapter(适配器)
      • HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    3. 使用刚才获取到的适配器(AnnotationMethodHandlerAdapter)执行目标方法;
      • mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
    4. 目标方法执行后,会返回一个ModerAndView对象
      • mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
    5. 根据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;
    }
}
  • 让我们的视图解析器工作
  • 得到我们的视图对象
  • 我们的视图对象自定义渲染逻辑

自定义视图和视图解析器的步骤

  1. 编写自定义的视图解析器,和视图实现类
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("即将展现内容:");
    }
}
  1. 视图解析器必须放在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;不放行
  • postHandle:在目标方法运行之后调用

  • afterCompletion:资源响应之后调用

8.1 操作步骤

  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");
        }
    }
  2. 配置拦截器

    <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>--%>
  3. 拦截器的运行流程

    1. preHandle
    2. 目标方法
    3. postHandle
    4. 页面渲染
    5. afterCompletion

    其他流程:

    1. 只要preHandle不放行就没有以后的流程

      • preHandle 中return false
    2. 只要放行了,afterCompletion都会执行

      • 目标方法出现异常,afterCompletion也会执行

12.2 多个拦截器

与filter一样,拦截顺序与配置文件中的次序一样

MyFirstInterceptor...preHandle...
MySecondInterceptor...preHandle...
目标方法....
MySecondInterceptor...postHandle...
MyFirstInterceptor...postHandle...
响应页面....
MySecondInterceptor...afterCompletion...
MyFirstInterceptor...afterCompletion

异常流程:

  1. 哪一块Interceptor不放行

    • 哪一块不放行从此以后都没有
  2. 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


文章作者: DestiNation
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 DestiNation !
  目录