SpringMVC 二(Ajax 异步交互、RESTful、文件上传、异常处理、拦截器)

目录

1. Ajax 异步交互

1.1 @RequestBody

1.2 @ResponseBody

2. RESTful

2.1 什么是 RESTful

2.2 代码实现

3. 文件上传

3.1 文件上传三要素

3.2 文件上传原理

3.3 单文件上传

3.4 多文件上传

4. 异常处理

 4.1 异常处理的思路

 4.2 自定义异常处理器

 4.3 web 的处理异常机制

5. 拦截器

5.1 拦截器(interceptor)的作用

5.2 拦截器和过滤器区别

5.3 快速入门

5.4 拦截器链

5.5 小结


1. Ajax 异步交互

SpringMVC 默认用 MappingJackson2HttpMessageConverter 对 JSON 数据进行转换,需要加入 Jackson 的包;同时在 spring-mvc.xml 使用 <mvc:annotation-driven />

pom.xml

...
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.8</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>
...

 spring-mvc.xml

...

<!-- 处理器映射器-处理器适配器。进行了功能的增强:支持 json 的读写 -->
<mvc:annotation-driven />

...

1.1 @RequestBody

该注解用于 Controller 的方法的形参声明,当使用 Ajax 提交并指定 contentType 为 JSON 形式时,通过 HttpMessageConverter 接口转换为对应的 POJO 对象。(注意:@RequestBody是获取请求体中的参数并将其封装到POJO 对象中,所以当请求方式为get请求时可以不写该注解,因为get方式无请求体)

src\main\webapp\ajax.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Ajax</title>
</head>
<body>
<%-- ajax 异步交互 --%>
<button id="btn1">ajax 异步提交</button>

<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
<script>
    $("#btn1").click(function () {
        let url = '${pageContext.request.contextPath}/user/ajaxRequest';
        let data = '[{"id":1,"username":"张三"},{"id":2,"username":"李四"}]';

        $.ajax({
            type: 'POST',
            url: url,
            data: data,
            contentType: 'application/json;charset=utf-8',
            success: function (resp) {
                alert(JSON.stringify(resp));
            }
        })
    });
</script>
</body>
</html>

在 UserController 中添加方法

@RequestMapping("/ajaxRequest")
public List<User> ajaxRequest(@RequestBody List<User> list){
    System.out.println(list);
}

1.2 @ResponseBody

该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的数据如:JSON,xml 等,通过 Response 响应给客户端。

@RequestMapping("/ajaxRequest")
@ResponseBody
public List<User> ajaxRequest(@RequestBody List<User> list){
    System.out.println(list);
    return list;
}

2. RESTful

2.1 什么是 RESTful

Restful 是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful 风格的请求是使用“URL + 请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:

  • GET:读取(Read)
  • POST:新建(Create)
  • PUT:更新(Update)
  • DELETE:删除(Delete)
客户端请求原来风格URL地址RESTFUL风格URL地址
查询所有/user/findAll/user
根据 ID 查询/user/findById?id=1/user/{1}
新增/user/save/user
修改/user/update/user
删除/user/delete?id=1/user/{1}

2.2 代码实现

@PathVariable
用来接收 RESTful 风格请求地址中占位符的值。

@RestController
RESTful 风格多用于前后端分离项目开发,前端通过 Ajax 与服务器进行异步交互,我们处理器通常返回的是 JSON 数据所以使用 @RestController 来替代 @Controller 和 @ResponseBody 两个注解。

/**
 * 没有 ResponseBody 的话,会把 return 的值作为逻辑视图进行解析;
 * 带有 ResponseBody 则直接进行数据的响应
 */
@RestController // @RestController = @Controller + @ResponseBody
@RequestMapping("/restful")
public class RestfulController {

    /**
     * 根据 id 进行查询
     */
    @GetMapping("/user/{id}") // @RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
    public String findById(@PathVariable Integer id) {
        // 获取 restful 编程风格中 url 里面占位符的值
        return "findById: " + id;
    }

    /**
     * 新增方法
     * POST 对应的是新增
     */
    @PostMapping("/user") // @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String post(){
        return "post";
    }

    /**
     * 更新方法
     * PUT 对应的是更新操作
     */
    @PutMapping("/user")
    public String put(){
        return "put";
    }

    /**
     * 删除方法
     */
    @DeleteMapping("/user/{id}")
    public String delete(@PathVariable Integer id){
        return "delete" + id;
    }

}

3. 文件上传

3.1 文件上传三要素

  • 表单项 type="file"
  • 表单的提交方式 method="POST"
  • 表单的 enctype 属性是多部分表单形式 enctype=“multipart/form-data"
<form action="${pageContext.request.contextPath}/fileupload" method="post" enctype="multipart/form-data">
    名称:<input type="text" name="username"> <br>
    文件:<input type="file" name="filePic"> <br>
    <input type="submit" value="单文件上传">
</form>

3.2 文件上传原理

- 当 form 表单修改为多部分表单时,request.getParameter() 将失效。

- 当 form 表单的 enctype 取值为 application/x-www-form-urlencoded 时,

            -- form 表单的正文内容格式是: name=value&name=value。

- 当 form 表单的 enctype 取值为 mutilpart/form-data 时,请求正文内容就变成多部分形式:

3.3 单文件上传

步骤分析:

  1. 导入 fileupload 和 io 坐标
  2. 配置文件上传解析器
  3. 编写文件上传代码

1. 导入 fileupload 和 io 坐标

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>

2. 配置文件上传解析器
spring-mvc.xml

<!-- 此处的id为固定写法,不能随便写 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设定文件上传的最大值为 5 MB = 5*1024*1024 B -->
    <property name="maxUploadSize" value="5242880"/>
    <!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为 10240 -->
    <property name="maxInMemorySize" value="40960"/>
</bean>

3. 编写文件上传代码

<form action="${pageContext.request.contextPath}/fileupload" method="post" enctype="multipart/form-data">
    名称:<input type="text" name="username"> <br>
    文件:<input type="file" name="filePic"> <br>
    <input type="submit" value="单文件上传">
</form>

@RequestMapping("/fileupload")
public String fileUpload(String username, MultipartFile filePic) throws IOException {
    // 获取表单的提交参数,完成文件上传
    System.out.println(username);
    // 获取原始的文件上传名
    String originalFilename = filePic.getOriginalFilename();
    filePic.transferTo(new File("E:/upload/" + username + "_" + originalFilename));
    // 转发到成功页面
    return "success";
}

3.4 多文件上传

<form action="${pageContext.request.contextPath}/filesupload" method="post" enctype="multipart/form-data">
    名称:<input type="text" name="username"> <br>
    文件1:<input type="file" name="filePic"> <br>
    文件2:<input type="file" name="filePic"> <br>
    <input type="submit" value="多文件上传">
</form>


@RequestMapping("/filesupload")
public String filesUpload(String username, MultipartFile[] filePic) throws IOException {
    //获取表单的提交参数,完成文件上传
    System.out.println(username);
    // 获取原始的文件上传名
    for (MultipartFile multipartFile : filePic) {
        String originalFilename = multipartFile.getOriginalFilename();
        multipartFile.transferTo(new File("E:/upload/" + username + "_" + originalFilename));
    }
    // 转发到成功页面
    return "success";
}

4. 异常处理

 4.1 异常处理的思路

在 Java 中,对于异常的处理一般有两种方式:

  • 一种是当前方法捕获处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合。
  • 另一种是自己不处理,而是抛给调用者处理(throws),调用者再抛给它的调用者,也就是一直向上抛。在这种方法的基础上,衍生出了 SpringMVC 的异常处理机制。

系统的 Dao、Service、Controller 出现都通过 throws Exception 向上抛出,最后由 SpringMVC 前端控制器交由异常处理器(HandlerExceptionResolver)进行异常处理:

请求往下传:客户端 -> 前端控制器 -> Controller -> Service -> Dao

异常往上抛:Dao -> Service -> Controller -> 前端控制器 -> 异常处理器

 4.2 自定义异常处理器

步骤分析:

  1. 创建异常处理器类实现 HandlerExceptionResolver
  2. 配置异常处理器
  3. 编写异常页面
  4. 测试异常跳转

1. 创建异常处理器类实现 HandlerExceptionResolver
com.zm.exception.GlobalExceptionResolver

public class GlobalExceptionResolver implements HandlerExceptionResolver {

    /**
     * @param e 实际抛出的异常对象
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        // 具体的异常处理:产生异常后,跳转到一个最终的异常页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("error", e.getMessage());
        modelAndView.setViewName("error");
        return modelAndView;
    }

}

2. 配置异常处理器

异常处理器的配置就是要生成一个该类的实例对象存到 IOC 容器中,所以可以用@Component注解或者在XML文件中配置<bean>标签。
spring-mvc.xml

<bean id="globalExceptionResolver" class="com.zm.exception.GlobalExceptionResolver"/>

3. 编写异常页面
src\main\webapp\WEB-INF\pages\error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>ERROR</title>
</head>
<body>

ERROR:
${error}

</body>
</html>

4. 测试异常跳转
com.zm.controller.ExceptionController

@Controller
public class ExceptionController {
    @RequestMapping("/testException")
    public String testException(){
        int i = 1/0;
        return "success";
    }
}

 4.3 web 的处理异常机制

404.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>404 Error</title>
</head>
<body>
    提示: 您请求的资源已经删除
</body>
</html>

 500.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>500 Error</title>
</head>
<body>
    提示: 网络故障,请稍后再试
</body>
</html>

web.xml 


<!-- 处理 404 异常 -->
<error-page>
    <error-code>404</error-code>
    <location>/404.jsp</location>
</error-page>
<!-- 处理 500 异常 -->
<error-page>
    <error-code>500</error-code>
    <location>/500.jsp</location>
</error-page>

5. 拦截器

 5.1 拦截器(interceptor)的作用

Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链 InterceptorChain。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是 AOP 思想的具体实现。

5.2 拦截器和过滤器区别

使用范围:

filter - 是 servlet 规范中的一部分,任何 Java Web 工程都可以使用 。

interceptor - 是 SpringMVC 框架的一部分,只有使用了 SpringMVC 框架的工程才能用。

拦截范围:

filter - 在 url-pattern 中配置了 /* 后,可以对所有资源进行过滤拦截。

interceptor - 只会拦截访问控制器方法,如果访问的是 JSP、HTML、CSS、Image、JS 就不会进行拦截。

5.3 快速入门

步骤分析:

  1. 创建拦截器类实现 HandlerInterceptor 接口
  2. 配置拦截器
  3. 测试拦截器的拦截效果

1. 创建拦截器类实现 HandlerInterceptor 接口
com.zm.interceptor.MyInterceptor1

public class MyInterceptor1 implements HandlerInterceptor {
    /**
     * 在目标方法执行之前进行拦截
     *
     * @return false:不放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle1....");
        return true;
    }

    /**
     * 在目标方法执行之后,视图对象返回之前,执行的方法
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle1....");
    }

    /**
     * 在流程都执行完成后,执行的方法
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion1....");
    }
}

2. 配置拦截器
spring-mvc.xml

<mvc:interceptors>
    <mvc:interceptor>
        <!-- 对所有 controller 类里面的所有方法都进行拦截 -->
        <mvc:mapping path="/**"/>
        <bean class="com.zm.interceptor.MyInterceptor1"></bean>
    </mvc:interceptor>
</mvc:interceptors>

3. 测试拦截器的拦截效果
编写 com.zm.controller.TargetController,请求到 controller,跳转页面

@Controller
public class TargetController {
    @RequestMapping("/target")
    public String targetMethod() {
        System.out.println("目标方法执行了 ...");
        return "success";
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>SUCCESS</title>
</head>
<body>

    <h3>success... ${username}</h3>
    <% System.out.println("Success...");%>

</body>
</html>

控制台输出结果:

preHandle1....
目标方法执行了 ...
postHandle1....
Success...
afterCompletion1....

5.4 拦截器链

开发中拦截器可以单独使用,也可以同时使用多个拦截器形成一条拦截器链。开发步骤和单个拦截器是一样的,只不过注册的时候注册多个,注意这里注册的顺序就代表拦截器执行的顺序。

同上,再编写一个 MyHandlerInterceptor2 操作:

public class MyInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle2....");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle2....");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion2...");
    }
}

spring-mvc.xml

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.zm.interceptor.MyInterceptor1"></bean>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.zm.interceptor.MyInterceptor2"></bean>
    </mvc:interceptor>
</mvc:interceptors>

控制台输出结果:(注意:只有前置增强方法是按照注册顺序执行的,后置方法和最终增强方法与之相反)

preHandle1....
preHandle2....
目标方法执行了 ...
postHandle2....
postHandle1....
Success...
afterCompletion2...
afterCompletion1....

当MyInterceptor1 的 preHandler 方法返回 true,MyHandlerInterceptor2 的 preHandler 方法返回 false;此时,MyInterceptor1 的 preHandler 方法优先执行并返回 true,即便MyHandlerInterceptor2 的 preHandler 方法返回 false,MyInterceptor1 的 afterCompletion 方法仍然被执行了。控制台输出结果:

preHandle1....
preHandle2....
afterCompletion1....

当 MyInterceptor1 的 preHandler 方法返回 false,MyHandlerInterceptor2 的 preHandler 方法返回 true;此时,MyInterceptor1 的 preHandler 方法优先执行并返回 false,方法被拦截,MyHandlerInterceptor2 的 preHandler 方法也无法执行。控制台输出结果:

preHandle1....

所以形成拦截器链时,当拦截器 1 的 preHandler 方法成功执行并返回 true 后,被它拦截的方法即便被另一个拦截器 2 所拦截并返回了 false,拦截器 1 的afterCompletion 方法仍然会被执行。

源码解释:

/**
 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
 * has successfully completed and returned true.
 */
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
    throws Exception {
    ...
}

5.5 小结

拦截器中的方法说明:

  • preHandle() - 方法将在请求处理之前进行调用,该方法的返回值是布尔值类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时就会继续调用下一个 Interceptor 的 preHandler 方法

  • postHandle() - 该方法是在当前请求进行处理之后被调用,前提是 preHandler 方法的返回值为 true 时才能被调用,且它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作

  • afterCompletion() - 该方法在整个请求结束之后,就是在DispatcherServlet 渲染了对应的视图之后执行,前提是 preHandler 方法的返回值为 true 时才能被调用

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值