Spring全家通之SpringMVC核心技术

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架。

第四章 SpringMVC 核心技术


4.1 请求重定向和转发


  • 当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重 定向。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。
  • 注意,对于请求转发的页面,可以是WEB-INF中页面;而重定向的页面,是不能为WEB-INF 中页的。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问 WEB-INF 中资 源的

微信截图_20220610114928.png

SpringMVC 框架把原来 Servlet 中的请求转发和重定向操作进行了封装。现在可以使用简 单的方式实现转发和重定向。

  • forward:表示转发,实现 request.getRequestDispatcher("xx.jsp").forward()
  • redirect:表示重定向,实现 response.sendRedirect("xxx.jsp"


4.4.1 请求转发


  • 处理器方法返回 ModelAndView 时,需在 setViewName()指定的视图前添加 forward:,且 此时的视图不再与视图解析器一同工作,这样可以在配置了解析器时指定不同位置的视图。 视图页面必须写出相对于项目根的路径。forward 操作不需要视图解析器。
  • 处理器方法返回 String,在视图路径前面加入 forward: 视图完整路径。
@RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,int age){
        ModelAndView mv = new ModelAndView();
        mv.addObject("myName",name);
        mv.addObject("myAge",age);
        /*使用forward请求转发的方式*/
        /*这个是请求转发的方式,可以请求到WEB-INF下的页面*/
        mv.setViewName("forward:/WEB-INF/view/show.jsp");
        return mv;
    }
复制代码


4.1.2 请求重定向


  • 在处理器方法返回的视图字符串的前面添加 redirect:,则可实现重定向跳转。
@RequestMapping(value = "/some2.do")
    public ModelAndView doSome2(String name,int age){
        ModelAndView mv = new ModelAndView();
        /*此时的数据放到request作用域中*/
        mv.addObject("myName",name);
        mv.addObject("myAge",age);
        /*使用redirect请求转发的方式*/
        /*这个是重回定向的方式,请求不到WEB-INF下的网页*/
        mv.setViewName("redirect:/show.jsp");
        // http://localhost:8080/myWeb/show.jsp?myName=lisi&myAge=20
        return mv;
    }
复制代码


4.2 异常处理


  • SpringMVC 框架处理异常的常用方式:使用@ExceptionHandler 注解处理异常。


4.2.1 @ExceptionHandler 注解


  • 使用注解@ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可 选属性 value,为一个 Class数组,用于指定该注解的方法所要处理的异常类,即所要匹 配的异常。
  • 而被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法 参数可以是 Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会 自动为这些方法参数赋值。
  • 对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中。

(1) 自定义异常类

定义三个异常类:NameException、AgeException、MyUserException。其中 MyUserException 是另外两个异常的父类。

微信截图_20220610115010.png

public class MyUserException extends Exception{
    /*继承父类的有参构造个无参构造方法*/
    public MyUserException() {
        super();
    }
    public MyUserException(String message) {
        super(message);
    }
}
复制代码
/*
* 当输入的年龄存在异常的时候抛出异常
* */
public class AgeException extends MyUserException{
    public AgeException() {
        super();
    }
    public AgeException(String message) {
        super(message);
    }
}
复制代码
/*
* 当用户的姓名存在异常的时候抛出的异常,抛出NameException
* */
public class NameException extends MyUserException{
    public NameException() {
        super();
    }
    public NameException(String message) {
        super(message);
    }
}
复制代码

(2) 修改 Controller 抛出异常

/*
* 这个类中我们使用Java代码处理异常(也就是try...catch)
* 我们使用框架来处理异常
* */
@Controller
public class MyController {
    @RequestMapping(value = "/exception.do")
    public ModelAndView exception(String name,int age) throws MyUserException {
        ModelAndView mv = new ModelAndView();
        // 根据请求参数抛出异常
        /*
        * 处理的顺序:
        *   代码从上往下执行,如果满足第一个if条件的话,那么程序会进入到这个异常的类中,
        *   不会在继续执行这个处理器方法,此时跳转到异常处理的类中,也就是被@ControllerAdvice注解
        *   标注的类。这里就是 GlobalExceptionHandel 这个类。
        *
        * 在异常类中,会根据抛出异常的类型在异常类中寻找被@ExceptionHandler(value = NameException.class)
        * 标注的方法上的这个注解中的value属性的值,进行异常的处理。
        * */
        if (!"程云博".equals(name)){
            throw  new NameException("输入的姓名不正确!");
        }
        if (age == 0|| age > 80){
            /*
                这里指定的抛出异常的信息相当于系统抛出的异常,类似于NullPointException
                属于系统界别的异常
            * */
            throw new AgeException("年龄不符合要求!");
        }
        mv.addObject("myName",name);
        mv.addObject("myAge",age);
        mv.setViewName("show");
        return mv;
    }
}
复制代码

(3) 定义异常响应页面

定义三个异常响应页面。

微信截图_20220610115041.png

  • 不过,一般不这样使用。而是将异常处理方法专门定义在一个类中,作为全局的异常处 理类。
  • 需要使用注解@ControllerAdvice,字面理解就是“控制器增强”,是给控制器对象增强 功能的。使用@ControllerAdvice 修饰的类中可以使用@ExceptionHandler
  • 当使用@RequestMapping 注解修饰的方法抛出异常时,会执行@ControllerAdvice 修饰的 类中的异常处理方法。
  • @ControllerAdvice 是使用@Component 注解修饰的,可以 扫描到@ControllerAdvice 所在的类路径(包名),创建对象。

(4) 定义全局异常处理类

/*
* @ControllerAdvice :控制类增强(也就是给控制器类增加功能-->异常处理的功能)
*   位置:在类的上面
*   特点:必须让框架知道这个注解所在的包名,需要在SpringMVC的配置文件中加入组件扫描器。
*        指定@ControllerAdvice所在的包名
*   这个使用的是Spring中的AOP技术,就是在原有的业务代码中加入一些与业务无关的方法。
*   比如日志、错误信息等。
* */
@ControllerAdvice
public class GlobalExceptionHandel {
    // 定义方法来处理发生的异常
    /*
    * 处理异常的方法和处理控制器的方法一样,
    * 可以有多种参数:普通数据类型、Object对象类型、List、Map集合都可以。
    * 可以有多种返回值类型ModelAndView、String、void对象、list对象集合。
    *
    * 形参:Exception。表示Controller中抛出的异常对象。
    *      通过形参可以获取发生的异常信息。
    *
    * @ExceptionHandel(value = 异常类.class):表示异常的类型,当发生此类型异常时
    *                               由当前方法进行处理。
    * */
    /*
    * SpringMVC处理异常的方式,他会在Controller类中接收到一个异常,之后在这个处理异常的类中
    * 寻找处理这个异常的方法,通过@ExceptionHandler注解的value值进行匹配。
    * 如果Controller类抛出的异常和@ExceptionHandler注解的value值都没有匹配到,
    * 那么就执行最后没有value值的@ExceptionHandler注解所对应的方法。
    *
    * 注意 :@ExceptionHandler没有value属性的注解只能存在一个。相当于if...else中的else。
    * */
    @ExceptionHandler(value = NameException.class)
    public ModelAndView doNameException(Exception exception){
        // 处理NameException的异常。
        /*
        * 异常发生我们要处理的逻辑:
        *   1. 需要把异常记录下来,记录到日志文件或者数据库中。
        *      记录日志发生的时间,哪个方法发生的,异常信息内容。
        *   2. 发送通知,把异常的信息通过邮件、短信、维信发送给相关人员。
        *
        *   3. 给用户一个很好的复杂。
        * */
        ModelAndView mv = new ModelAndView();
        /*这里的异常信息只是一个提示用户的信息,这个信息是我们自己给的,显示给前端jsp页面的*/
        mv.addObject("msg","姓名必须的是程云博,其他用户不可以访问!");
        // 异常对象,这个就相当于系统抛出异常的那个对象。比如:NullPointException对象。
        // 或者是我们自定义的 AgeException、NameException。
        mv.addObject("ex",exception);
        mv.setViewName("nameError");
        return mv;
    }
    @ExceptionHandler(value = AgeException.class)
    public ModelAndView doAgeException(Exception exception){
        // 处理AgeException的异常。
        /*
         * 异常发生我们要处理的逻辑:
         *   1. 需要把异常记录下来,记录到日志文件或者数据库中。
         *      记录日志发生的时间,哪个方法发生的,异常信息内容。
         *   2. 发送通知,把异常的信息通过邮件、短信、维信发送给相关人员。
         *
         *   3. 给用户一个很好的复杂。
         * */
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","您的年龄不符合要求!");
        // 异常对象
        mv.addObject("ex",exception);
        mv.setViewName("ageError");
        return mv;
    }
    // 处理其他异常,NameException、AgeException以外的异常,不知道的异常类型
    // 当错误信息不是NameException、AgeException的时候,就交给这个方法来处理异常
    @ExceptionHandler
    public ModelAndView doOtherException(Exception exception){
        // 处理其他的异常。
        /*
         * 异常发生我们要处理的逻辑:
         *   1. 需要把异常记录下来,记录到日志文件或者数据库中。
         *      记录日志发生的时间,哪个方法发生的,异常信息内容。
         *   2. 发送通知,把异常的信息通过邮件、短信、维信发送给相关人员。
         *
         *   3. 给用户一个很好的复杂。
         * */
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","您的年龄不符合要求!");
        // 异常对象
        mv.addObject("ex",exception);
        mv.setViewName("defaultError");
        return mv;
    }
}
复制代码

(5) 定义 Spring 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--SpringMVC配置文件,声明controller和其他web相关的对象-->
    <!--声明组件扫描器,使用动态代理的方式创建Servlet的动态代理对象-->
    <context:component-scan base-package="com.yunbocheng.controller" />
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--这里使用value属性,因为使用的是set注入的方式进行赋值-->
        <property name="prefix" value="/WEB-INF/view/" />
        <property name="suffix" value=".jsp" />
    </bean>
    <!--注解驱动,因为处理ajax请求(转换格式)以及静态资源(解决冲突)都需要用到注解驱动-->
    <mvc:annotation-driven/>
    <!--处理异常的组件扫描器,指定处理异常类所在的包-->
    <context:component-scan base-package="com.yunbocheng.handel"/>
</beans>
复制代码


4.3 拦截器


  • SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提 交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器, 在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时, 已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。


4.3.1 拦截器的执行


自定义拦截器

/*
* 这是一个拦截器类,需要继承HandlerInterceptor接口
* 并且实现其中的三个类。
*
* 这个类用于拦截用户的请求。
* */
public class MyInterceptor implements HandlerInterceptor {
    // 实现HandlerInterceptor这个接口中的三个方法,查看源码可以看到者三个方法是使用Default声明的方法
    // 所以我们不需要全部的实现这三个方法
    // 我们这里实现这三个方法。
    /*
    * preHandle() : 这个方法叫做预处理方法。
    * 参数:
    *   Object handler :被拦截的控制器对象(也就是项目中的MyController对象)
    * 返回值:boolean
    *   true : 表示此时请求通过了拦截器的验证,可以执行处理器方法处理这个请求。
    *   false : 表示此时的请求没有通过拦截器的验证,不可以执行处理器方法处理这个请求。
    *
    *  特点:
    *   1. 方法是在控制器方法(MyController的doSome)之前先执行的。
    *      用户的请求首先到达此方法
    *   2. 在这个方法中可以获取请求的信息。验证请求是否符合要求。
    *      可以验证用户是否可以登录,验证用户是否有权限访问某个连接地址(url)
    *      如果验证失败,可以截断请求,请求不能被处理。
    *      如果验证成功,可以放行请求,此时控制器方法才可以执行。
    * */
    private long bTime;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        bTime = System.currentTimeMillis();
        System.out.println("拦截器的MyInterceptor的perHandler()");
        // 在这个方法中进行业务逻辑的判断,返回true还是false,进而控制这个请求可不可以交给处理器方法进行处理。
        /*当请求被拦截器拦截下来的时候,给浏览器一个返回页面结果*/
        /*request.getRequestDispatcher("/tips.jsp").forward(request,response);*/
        return true;
    }
    /*
    * postHandle : 后处理方法。
    *
    * 参数:
    *   Object handler : 被拦截的处理器对象MyController。
    *   ModelAndView modelAndView : 处理器方法的返回值。
    *
    * 返回值:void
    *
    * 特点:
    *   1. 方法是在处理方法之后执行的(MyController.doSome())
    *   2. 能够获取到处理器方法的返回值ModelAndView,可以修改ModelAndView中的
    *      数据和视图,可以影响最后的执行结果。
    *   3. 主要对原来的执行结果进行二次修饰。
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        /*执行这个处理结果的方法的前提是预处理方法返回的为true*/
        System.out.println("拦截器的MyInterceptor的postHandle()");
        // 对原来的处理器方法的返回值进行处理。
        if (modelAndView != null){
            // 添加返回值中的数据
            modelAndView.addObject("myDate",new Date());
            // 修改返回值的数据
            modelAndView.addObject("myAge",40);
            // 修改返回的视图
            modelAndView.setViewName("other");
        }
    }
    /*
    * afterCompletion : 最后执行的方法
    *   参数:
    *      Object handler : 被拦截的处理器对象。
    *      Exception ex : 程序中发生的异常。
    *  特点:
    *      1. 是在请求处理完成后执行的。框架中的规定是当你的视图处理完成后,对视图执行了forward。就认为是请求处理完成了。
    *      2. 一般是做资源回收工作的,程序请求过程中创建一些对象,在这里可以删除,把占用的内存回收。
    * */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器的MyInterceptor的afterCompletion");
        long eTime = System.currentTimeMillis();
        System.out.println("计算preHandler到请求处理完成的时间:" + (eTime - bTime));
    }
}
复制代码

自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:

➢ preHandle(request,response, Object handler): 该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方 法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。

➢ postHandle(request,response, Object handler,modelAndView): 该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。 由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修 改处理器方法的处理结果数据,且可以修改跳转方向。

➢ afterCompletion(request,response, Object handler, Exception ex): 当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有 工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此 时对 ModelAndView 再操作也对响应无济于事。

afterCompletion 最后执行的方法,清除资源,例如在 Controller 方法中加入数据

微信截图_20220610115128.png微信截图_20220610115146.png

拦截器中方法与处理器方法的执行顺序如下图:

微信截图_20220610115153.png

换一种表现方式,也可以这样理解:

微信截图_20220610115219.png

(1) 注册拦截器

微信截图_20220610115410.png

  • 用于指定当前所注册的拦截器可以拦截的请求路径,而/**表示拦截所 有请求。

(2) 修改 index 页面

微信截图_20220610115419.png

(3) 修改处理器

微信截图_20220610115433.png

(4) 修改 show 页面

微信截图_20220610115441.png

(5) 控制台输出结果

微信截图_20220610115448.png


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
XML Java 数据格式
技术好文:Spring基础篇——AOP切面编程
技术好文:Spring基础篇——AOP切面编程
|
13天前
|
缓存 NoSQL Java
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
Spring Cache 是 Spring 提供的简易缓存方案,支持本地与 Redis 缓存。通过添加 `spring-boot-starter-data-redis` 和 `spring-boot-starter-cache` 依赖,并使用 `@EnableCaching` 开启缓存功能。JetCache 由阿里开源,功能更丰富,支持多级缓存和异步 API,通过引入 `jetcache-starter-redis` 依赖并配置 YAML 文件启用。Layering Cache 则提供分层缓存机制,需引入 `layering-cache-starter` 依赖并使用特定注解实现缓存逻辑。
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
|
4天前
|
Dubbo Java Nacos
【实战攻略】破解Dubbo+Nacos+Spring Boot 3 Native打包后运行异常的终极秘籍——从零开始彻底攻克那些让你头疼不已的技术难题!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但使用Dubbo+Nacos+Spring Boot 3进行GraalVM native打包后常遇运行异常。本文剖析此问题及其解决策略:确认GraalVM版本兼容性;配置反射列表以支持必要类和方法;采用静态代理替代动态代理;检查并调整配置文件;禁用不支持的功能;利用日志和GraalVM诊断工具定位问题;根据诊断结果调整GraalVM配置。通过系统排查方法,能有效解决此类问题,确保服务稳定运行。
16 0
|
6天前
|
前端开发 Java Spring
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
Java 新手入门:Spring Boot 轻松整合 Spring 和 Spring MVC!
18 0
|
29天前
|
Java Spring
Java演进问题之Spring框架使用技术实现其核心功能如何解决
Java演进问题之Spring框架使用技术实现其核心功能如何解决
|
2月前
|
NoSQL 前端开发 Java
技术笔记:springboot分布式锁组件spring
技术笔记:springboot分布式锁组件spring
32 1
|
1月前
|
XML 前端开发 Java
Spring Boot与Spring MVC的区别和联系
Spring Boot与Spring MVC的区别和联系
|
1月前
|
Java API 网络架构
Spring Cloud中的服务路由与过滤技术实现
Spring Cloud中的服务路由与过滤技术实现
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
|
2月前
|
XML Java Apache
必知的技术知识:HHS整合(Struts2+Spring+Hibernate)
必知的技术知识:HHS整合(Struts2+Spring+Hibernate)
18 0