整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Spring MVC请求处理

Spring MVC请求处理

VC三层架构

  • 三层架构

  • MVC

Spring MVC的实现原理

  • DispatcherServlet===> 前端控制器


  • 配置DispatcherServlet映射

url-pattern

① /===> 除了.jsp的请求都会被匹配 (推荐使用)

② /*===> 所有的请求都会匹配

③ *.do、*.action===> 结尾以.do或者.action的请求会匹配

④ /request/*===> 要进行约定 将jsp放在/views/ 所有的servlet请求都用/request/

<!--web.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--初始化参数
            contextConfigLocation 配置springmvc的xml配置文件,指定路径
            也可以不配置: 会自动WEB-INF去找, springmvc-servlet.xml文件
        -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>

        <!--启动时加载servlet:
            当web服务器 启动时就会创建servlet(会自动调用servlet的构造函数及init()方法)
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--配置DispatcherServlet映射
        springmvc映射的路径为:
        /               除了.jsp的请求都会被匹配
        /*              所有的请求都会匹配
        *.do *.action   url结尾以.do或者.action的请求会匹配
        /request/*      要进行约定 将jsp放在/views/ 所有的servlet请求都用/request/
    -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  • Spring MVC 解决中文乱码问题
<!--配置编码过滤器, 解决中文乱码问题-->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

    <!--encoding编码格式-->
    <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>characterEncodingFilter</filter-name>
    <!--拦截规则 有两种规则-->
    <!--① 根据url请求进行匹配-->
    <!--<url-pattern></url-pattern>-->

    <!--② 指定过滤哪个servlet-->
    <servlet-name>springmvc</servlet-name>
</filter-mapping

请求参数

》》》RequestParam:获取请求的参数

  • 基本数据类型参数传递
package com.demo.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 在springmvc中只需要在处理方法中声明对应的的参数就可以自动接收请求的参数并且还可以自动转换类型
 *
 *  如果类型未匹配会报400
 *
 *  基本数据类型匹配规则:
 *      请求的参数必须跟处理方法的参数名一致
 *          如果处理方法的参数未传入的情况会自动传入null
 *  
 *      请求的参数跟处理方法的参数不一致
 *          @RequestParam 管理请求参数
 *              value: 用来重命名参数
 *              required: 用来指定参数是否必须传入值
 *                     true:默认 必须传入值,不传值会报400
 *                     false: 可以不用传入值
 *              defaultValue: 当参数为null的时候会自动设置一个默认值
 *              注意:基本数据类型是无法接收null;当设置了默认值可以省略required=false
 *					处理请求参数乱码:
 *									GET: 直接设置tomcat目录下conf/server.xml 里面<Connector URIEncoding="UTF-8"
 *									POST: ① 在servlet, 在获取参数之前 设置request.setCharacterEncoding("UTF-8"),每个请求都需要处理一次,非常麻烦
 *														 ② 推荐使用过滤器处理乱码问题。
 *														 ③ 在Spring MVC, 解决乱码问题:CharacterEncodingFilter, 需在web.xml中配置
 *								
 */
@Controller
public class ParamsController {

    @RequestMapping("/params")
    public String params(@RequestParam(value="username", required=true, defaultValue="heihei") String name) {
        System.out.println("name=" + name);

        // 响应
        // redirect  重定向
        // forward 转发===> 默认
        return "/index.jsp";
    }
}
  • 复杂类型参数传递
package com.demo.controllers.entity;

import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
public class User {
    private Integer id;
    private String name;
    private String[] alias;
    private List<String> hobbies;
    private Map<String, String> relatives;
    private Role role;
    private List<User> friends;
}
package com.demo.controllers.entity;

import lombok.Data;

@Data
public class Role {
    private Integer id;
    private String name;
}
package com.demo.controllers.entity;

import lombok.Data;

@Data
public class UserDTO {
    private User user;
    private Role role;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>简单参数</h2>
    <%--${pageContext.request.contextPath}: 动态获取项目路径--%>
    <form action="${pageContext.request.contextPath}/params01" method="post">
        姓名:<input name="username" type="text" /> <br />
        <input type="submit" value="提交">
    </form>

    <h2>复杂参数</h2>
    <form action="${pageContext.request.contextPath}/params02" method="post">
        id:<input name="user.id" type="text" /> <br />
        姓名:<input name="user.name" type="text" /> <br />
        别名: <input name="user.alias" type="checkbox" value="AA1" checked />AA1 <br />
             <input name="user.alias" type="checkbox" value="AA2" checked />AA2
        爱好: <input name="user.hobbies" type="checkbox" value="BB1" checked />BB1 <br />
            <input name="user.hobbies" type="checkbox" value="BB2" checked />BB2
        亲属: <input name="user.relatives['k1']" type="checkbox" value="CC1" checked />CC1 <br />
            <input name="user.relatives['k2']" type="checkbox" value="CC2" checked />CC2
        角色:<input name="user.role.name" type="text" /> <br />
        朋友:<input name="user.friends[0].name" type="text" value="张三"/> <br />
        <input name="user.friends[1].name" type="text" value="李四"/> <br />

        <h2>新增角色</h2>
        id:<input name="role.id" type="text" /> <br />
        角色:<input name="role.name" type="text" /> <br />
        <input type="submit" value="提交">
    </form>
</body>
</html>
package com.demo.controllers;

import com.demo.controllers.entity.User;
import com.demo.controllers.entity.UserDTO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 在springmvc中只需要在处理方法中声明对应的的参数就可以自动接收请求的参数并且还可以自动转换类型
 *
 *  如果类型未匹配会报400
 *
 *  类型匹配规则:
 *      请求的参数必须跟处理方法的参数名一致
 *          如果处理方法的参数未传入的情况会自动传入null
 *
 *      请求的参数跟处理方法的参数不一致
 *        基本数据类型参数:
 *          @RequestParam 管理请求参数
 *              value: 用来重命名参数
 *              required: 用来指定参数是否必须传入值
 *                     true:默认 必须传入值,不传值会报400
 *                     false: 可以不用传入值
 *              defaultValue: 当参数为null的时候会自动设置一个默认值
 *              注意:基本数据类型是无法接收null;当设置了默认值可以省略required=false
 *
 *       复杂类型参数:
 *             不用加上对象,直接传入对象的属性名
 *              包装类型:直接输入属性名===> name="id"
 *              数组: 表单元素要同样的name===> name="alias"
 *              List: 必须加上[index]===> name=list[0]  如果List<User>  name="list[0].name"
 *              Map: 必须加上[key]===> name="map['key']"
 *              实体类: 只能给某个属性赋值===> name="object.xxx"
 *              注意: 如果出现多个对象(User user, Role role)参数的情况 建议再次封装一层javaBean (DTO data transfer object)
 */
@Controller
public class ParamsController {

    @RequestMapping("/params01")
    public String params01(@RequestParam(value="username", required=true, defaultValue="heihei") String name) {
        System.out.println("name=" + name);

        // 响应
        // redirect  重定向
        // forward 转发===> 默认
        return "/index.jsp";
    }

    @RequestMapping("/params02")
    public String params02(UserDTO userDTO) {
        System.out.println("userDTO=" + userDTO);

        return "/index.jsp";
    }
}

》》》RequestHeader:获取请求头信息

/**
 * 获取请求头信息
 * @param host
 * @return
 */
@RequestMapping("/header")
public String header(@RequestHeader("host") String host) {
    System.out.println("host=" + host);

    return "/index.jsp";
}

》》》CookieValue:获取cookie的值

/**
 * 获取cookie
 * JSESSIONID: 客户端发送请求时,为了保证客户端和服务端数据一致性,服务端响应一个JSESSIONID给客户端,实际JSESSIONID依赖cookie
 * @param jsSessionId
 * @return
 */
@RequestMapping("/cookie")
public String cookie(@CookieValue("JSESSIONID") String jsSessionId){
    System.out.println("jsSessionId=" + jsSessionId);

    return "/index.jsp";
}

》》》Spring MVC + Servlet API

/**
 * 使用原生的servlet API, 但未解决耦合问题
 * Spring MVC 可以和 Servlet API结合一起使用
 * @param request
 * @param response
 * @return
 */
@RequestMapping("/servlet")
public String servlet(String username, HttpServletRequest request, HttpServletResponse response) {
    System.out.println("username=" + username);
    String name=request.getParameter("name");
    System.out.println("name=" + name);
    request.setAttribute("name", name);

    return "/index.jsp";
}

请求映射

  • RequestMapping

处理URL映射,将请求映射到处理方法中


如果放在类上,所有的方法都会被映射,进行模块化,防止方法中的请求重复映射

》》》RequestMapping| GetMapping/PostMapping

@RequestMapping(value="/mapping01", method=RequestMethod.POST)
public String mapping01() {
    System.out.println("映射成功");

    return "/index.jsp";
}

@PostMapping(value="/mapping02")
public String mapping02() {
    System.out.println("请求方式等同于上方的请求方式");

    return "/index.jsp";
}
  • RequestMapping中参数

》》》params

/**
 * params: 设置请求必须携带某些参数
 *            1. 必须要有某些参数 params={"username"}
 *            2. 必须没有某些参数 params={"!username"}
 *            3. 必须等于某值 params={"username=liliu"}
 *            4. 必须不等于某值 params={"username!=liliu"}
 * @return
 */
@RequestMapping(value="/params", params={"username"})
public String params() {
    System.out.println("请求参数!");

    return "/index.jsp";
}

》》》headers

/**
 * 请求头必须包含指定的值
 * @return
 */
@RequestMapping(value="/headers", headers={"Connection: keep-alive"})
public String headers() {
    System.out.println("请求映射!");

    return "/index.jsp";
}

》》》consumes===> 请求类型

/**
 *  consumes: 当前请求的内容类型必须为指定值
 *            请求内容类型不匹配,报415
 *
 * @return
 */
@RequestMapping(value="/consumes", consumes={"application/x-www-form-urlencoded"})
public String consumes() {
    System.out.println("请求映射!");

    return "/index.jsp";
}

》》》produces===> 响应内容类型

/**
 * 设置当前响应的内容类型
 * @return
 */
@RequestMapping(value="/produces", produces={"application/json"})
public String produces() {
    System.out.println("响应内容类型参数!");

    return "/index.jsp";
}
  • RequestMapping支持通配符

/**
 * 通配符的映射
 * @return
 */
@RequestMapping(value="/**/ant")
public String mapping06() {
    System.out.println("通配符 **");

    return "/index.jsp";
}
  • @PathVariable
/**
 * @PathVariable 用在参数上面的
 *               专门来获取URL目录级别的参数
 *               单个参数接收必须要使用@PathVariable来声明对应的参数占位符名字
 * @param id
 * @param username
 * @return
 */
@RequestMapping("/user/{id}/{username}")
public String path01(@PathVariable("id") Integer id, @PathVariable("username") String username) {
    System.out.println("id=" + id);
    System.out.println("username=" + username);

    return "/index.jsp";
}
/**
 * javaBean可以省略@PathVariable, 但是要保证占位符的名字和javaBean的属性名一致
 * @param user
 * @return
 */
@RequestMapping("/user02/{id}/{name}")
public String path02(User user) {
    System.out.println("user=" + user);

    return "/index.jsp";
}

REST

表述性状态传递(Representational State Tranfer, 简称REST)


RESTful:一种优雅的URL风格

》》》URL CRUD

》》》REST CRUD

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% request.setAttribute("basepath", request.getContextPath()); %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>简单参数</h2>
    <%--${pageContext.request.contextPath}: 动态获取项目路径--%>
    <form action="${basepath}/params01" method="post" >
    </form>  
</body>
</html>

解决报405的错误

form表单提交PUT和DELETE出现问题:会将PUT和DELETE作为GET提交,因为HTML无法支持PUT和DELETE


解决方案:

① 添加HiddenHttpMethodFilter过滤器

<!--处理HTML表单不支持REST中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>
    <servlet-name>springmvc</servlet-name>
</filter-mapping>

② 在表单中添加隐藏域

<form action="${basepath}/params01" method="post" >
    <!--form的method:post, value: put或delete请求方式-->
    <input type="hidden" name="_method" value="put" />
</form>

》》》基于服务API的方式使用restful

package com.demo.controllers;

import com.demo.controllers.entity.User;
import org.springframework.web.bind.annotation.*;

/**
 * 基于服务API的方式使用RESTful
 *      返回的数据基本是: JSON格式的
 */
@RestController
@RequestMapping("/restful")
public class RestfulController {

    // 修改
    @PutMapping("/user/{id}")
    public String update(User user) {
        System.out.println("修改用户: " + user);

        // 返回的数据是JSON格式
        return "{'msg':'success'}";
    }

    // 删除
    @DeleteMapping("/user/{id}")
    public String delete(@PathVariable("id") Integer id) {
        System.out.println("删除用户: " + id);

        // 返回的数据是JSON格式
        return "{'msg':'success'}";
    }
}

静态资源处理

:500错误

1、500 Internal Server Error 内部服务错误:顾名思义500错误?般是服务器遇到意外情况,??法完成请求。

2、500出错的可能性:

a、编程语?语法错误,web脚本错误

b、并发?时,因为系统资源限制,?不能打开过多的?件

3、?般解决思路:

a、查看nginx、php的错误?志?件,从?看出端倪

b、如果是too many open files,修改nginx的worker_rlimit_nofile参数,使?ulimit查看系统打开?件限制,修

改/etc/security/limits.conf,还是出现too many open files,那就要考虑做负载均衡,把流量分散到不同服务器上去了

c、如果是脚本的问题,则需要修复脚本错误,优化代码

?:502、504错误

1、502 Bad Gateway错误、504 Bad Gateway timeout ?关超时

2、502、504出现的可能性

web服务器故障、程序进程不够

3、?般解决思路

a、使?nginx代理,?后端服务器发?故障;或者php-cgi进程数不够?;php执?时间长,或者是php-cgi进程死掉;已经fastCGI使?情况等都会导致502、504错误。

b、502 是指请求的php-fpm已经执?,但是由于某种原因?没有执?完毕,最终导致php-fpm进程终?。?般来说,与php-fpm.conf的设置有关,也与php的执?程序性能有关,?站的访问量?,?php-cgi的进程数偏少。针对这种情况的502错误,只需增加 php-cgi的进程数。具体就是修改/usr/local/php/etc/ php-fpm.conf?件,将其中的max_children值适当增加。这个数据要依据你的服务器的配置进?设置。?般?个php-cgi进程占20M内存,你可以??计算下,适量增多。

/usr/local/php/sbin/php-fpm reload 然后重启?下.

c、504 表?超时,也就是客户端所发出的请求没有到达?关,请求没有到可以执?的php-fpm。与nginx.conf的配置也有关系。

501  服务器不具备完成请求的功能。例如,服务器?法识别请求?法时可能会返回此代码。

503  服务器?前?法使?(由于超载或停机维护)。通常,这只是暂时状态。(服务不可?)

505  服务器不?持请求中所?的 HTTP 协议版本。(HTTP 版本不受?持)

附:新增全部状态码含义

状态码分类

整体范围 已定义范围 分类

100~199 100~101 信息提?

200~299 200~206 成功

300~399 300~305 重定向

400~499 400~415 客户端错误

500~599 500~505 服务器错误

100(continue):说明收到了请求的初始部分,请客户端继续。

客户端发送?个携带值为100 Countinue的Expect请求?部,意味着客户端在发送实体前等待100 Continue响应。这是?种优化,客户端在避免向服务器发送?个?实体时,才使?。

101(Switching Protocols):说明服务器正在根据客户端的指定,将协议切换成Update?部所列的协议。

200(OK):请求没问题,实体的主体部分包含了所请求的资源。

201(Created):?于创建服务器对象的请求的响应(例如PUT)。实体主体部分包含各种引?了已创建的资源的URL。

202(Accepted):请求已被接受,但服务器还未对其执?任何动作,?法保证服务器会完成这个请求。

203(Non-Authoritative Information):实体?部包含的信息不是来?于源端服务器,?是来?资源的?份副本。

204(No Content):响应报?有状态?和?部,但没有实体的主体部分。

205(Reset Content):告知浏览器清除当前页?中的所有HTML表单元素。

206(Partial Content):成功执?了?个部分或Range(范围)请求。

重定向状态码要么告知客户端使?替代位置来访问他们所要访问的资源,要么提供?个替代的响应?不是资源的内容。

资源被移动的情况下,发送?个重定向状态码和?个可选的Location?部告知客户端资源已被移动,并且可以在哪?找到。

300(Multiple Choices):客户端请求?个实际指向多个资源的URL时会返回这个状态码。

301(Moved Permanently):请求的URL已经被移除,响应中Location?部包含资源现所处的URL。

302(Found):与301类似,但是客户端应该?Location?部的URL来临时定位资源,将来的请求仍??的URL。

303(See Other):告知客户端应该?另?个URL来获取资源,新URL位于Location?部,允许POST请求的响应将客户端定向到某个资源上去。

304(Not Modified):客户端可以通过所包含的请求?部,使其变成有条件的。

305(Use Proxy):说明必须通过?个代理来访问资源,代理位置由Location?部给出。

306(未使?)

307(Temporary Redirect):和302?样。

400(Bad Request):?于告知客户端它发送了?个错误的请求。

401(Unauthorized):与适当的?部?起返回,在这些?部中请求客户端在获取对资源的访问权之前,对??进?认证。

402(Payment Required):保留状态码,未来之?。

403(Forbidden):说明请求被访问权拒绝。拒绝原因可能在实体的主体部分。

404(Not Found):服务器?法找到所请求的URL。

405(Method Not Allowed):发起请求中带有所请求的URL不?持的?法。响应中有Allow?部,告知客户端对所请求的资源可以?哪些?法。

406(Not Acceptable):客户端可以通过参数说明它们接受什么类型的实体,服务器没有和客户端可接受的URL相匹配的资源时?此代码。

407(Proxy Authentication Required):和401相似,但?于要求对资源进?认证的代理服务器。

408(Request Timeout):如果完成请求所花时间太长,服务器可以返回此状态码。

409(Confict):说明请求可能在资源上引发的?些冲突。

410(Gone):与404类似,只是服务器之前有过此资源。

411(Length Required):服务器要求在请求报?中包含Content-Length?部。

412(Precondition Failed):客户端发起了条件请求,且其中?个条件失败了。

413(Request Entity Too Large):实体主体部分过?。

414(Request URI Too Long):URL?服务器能处理的长。

415(Unsupport Media Type):?法理解或?法?持客户端所发实体的内容类型。

416(Requested Range Not Satisfiable):请求报?请求的是指定资源的某个范围,?此范围?效或?法满?。

417(Expectation Failed):请求的Expect请求?部包含?个期望,但服务器?法满?此期望。

500(Internal Server Error):服务器遇到?个妨碍它为请求提供服务的错误。

501(Not Implemented):客户端发起的请求超出服务器的能?范围。

502(Bad Gateway):作为代理或?关使?的服务器从请求响应链的下?条链路上收到了?条伪响应。

503(Service Unavailable):说明服务器现在?法为请求提供服务,将来可以。

504(Gateway Timeout):与408相似。

505(HTTP Version Not Support):服务器收到的请求使?了它?法或不愿意?持的协议版本。

现在各大技术社区 Spring Boot 的文章越来越多,Spring Boot 相关的图文、视频教程越来越多,使用 Spring Boot 的互联网公司也越来越多; Java 程序员现在出去面试, Spring Boot 已经成了必问的内容。

一切都在证明,Spring Boot 已经成为了 Java 程序员必备的技能。并且可以预见的是未来 Spring Boot 的发展还会更好。

所以对Java程序员来说其中不乏说对 Spring Boot 非常熟悉的,然后当问到一些 Spring Boot 核心功能和原理的时候,没人能说得上来,或者说不到点上,可以说一个问题就问趴下了!(问题:你能讲下为什么我们要用 Spring Boot 吗?)

相信我,上面这些类似的问题,90%有经验的Java程序员超都曾遇见过!但很少有系统化的回答。

因此,总结了这份Spring Boot核心知识点实战教程,通过这份教程,带你梳理Spring Boot 技术体系。

文末有彩蛋~

Spring Boot2教程在Spring Boot项目中,正常来说是不存在XML配置,这是因为Spring Boot不推荐使用 XML ,注意,并非不支持,Spring Boot 推荐开发者使用 Java 配置来搭建框架,Spring Boot 中,大量的自动化配置都是通过 Java 配置来实现的,这一套实现方案,我们也可以自己做,即自己也可以使用纯 Java 来搭建一个 SSM 环境,即在项目中,不存在任何 XML 配置,包括 web.xml 。

环境要求:

使用纯 Java 来搭建 SSM 环境,要求 Tomcat 的版本必须在 7 以上。

1、创建工程

创建一个普通的 Maven工程(注意,这里可以不必创建Web工程),并添加SpringMVC的依赖,同时,这里环境的搭建需要用到 Servlet ,所以我们还需要引入 Servlet 的依赖(一定不能使用低版本的Servlet),最终的 pom.xml 文件如下:

<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-webmvc</artifactId>
	  <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
	  <groupId>javax.servlet</groupId>
	  <artifactId>javax.servlet-api</artifactId>
	  <version>4.0.1</version>
	  <scope>provided</scope>
</dependency>

2 、添加 Spring 配置

工程创建成功之后,首先添加 Spring 的配置文件,如下:

@Configuration
@ComponentScan(basePackages="org.javaboy", useDefaultFilters=true,
excludeFilters={@ComponentScan.Filter(type=FilterType.ANNOTATION, classes=Controller.class)})
public class SpringConfig {
}

关于这个配置,我说如下几点:

@Configuration 注解表示这是一个配置类,在我们这里,这个配置的作用类似于applicationContext.xml

@ComponentScan 注解表示配置包扫描,里边的属性和 xml 配置中的属性都是一一对应的,useDefaultFilters 表示使用默认的过滤器,然后又除去 Controller 注解,即在 Spring 容器中扫描除了 Controller 之外的其他所有 Bean 。

3、 添加 SpringMVC 配置

接下来再来创建 springmvc 的配置文件:

@Configuration
@ComponentScan(basePackages="org.javaboy",useDefaultFilters=false,includeFilters={@ComponentScan.Filter(type=FilterType.ANNOTATION,classes=Controller.class)})
public class SpringMVCConfig {
}

注意,如果不需要在SpringMVC中添加其他的额外配置,这样就可以了。即视图解析器、JSON解析、文件上传…等等,如果都不需要配置的话,这样就可以了。

4、配置 web.xml

此时,我们并没 web.xml 文件,这时,我们可以使用Java代码去代替 web.xml 文件,这里会用到WebApplicationInitializer ,具体定义如下:

public class WebInit implements WebApplicationInitializer {
  public void onStartup(ServletContext servletContext) throws ServletException
{
    //首先来加载 SpringMVC 的配置文件
    AnnotationConfigWebApplicationContext ctx=new
AnnotationConfigWebApplicationContext();
    ctx.register(SpringMVCConfig.class);
    // 添加 DispatcherServlet
    ServletRegistration.Dynamic springmvc=servletContext.addServlet("springmvc", new DispatcherServlet(ctx));
    // 给 DispatcherServlet 添加路径映射
    springmvc.addMapping("/");
    // 给 DispatcherServlet 添加启动时机
    springmvc.setLoadOnStartup(1);
 }
}

WebInit 的作用类似于 web.xml,这个类需要实现 WebApplicationInitializer 接口,并实现接口中的方法,当项目启动时,onStartup 方法会被自动执行,我们可以在这个方法中做一些项目初始化操作,例如加载 SpringMVC 容器,添加过滤器,添加 Listener、添加 Servlet 等。

注意:

由于我们在WebInit中只是添加了SpringMVC的配置,这样项目在启动时只会去加载SpringMVC容器,而不会去加载 Spring 容器,如果一定要加载 Spring 容器,需要我们修改 SpringMVC 的配置,在SpringMVC 配置的包扫描中也去扫描 @Configuration 注解,进而加载 Spring 容器,还有一种方案可以解决这个问题,就是直接在项目中舍弃 Spring 配置,直接将所有配置放到 SpringMVC 的配置中来完成,这个在 SSM 整合时是没有问题的,在实际开发中,较多采用第二种方案,第二种方案,SpringMVC 的配置如下:

@Configuration
@ComponentScan(basePackages="org.javaboy")
public class SpringMVCConfig {
}

这种方案中,所有的注解都在 SpringMVC 中扫描,采用这种方案的话,则 Spring 的配置文件就可以删除了。

5、测试

最后,添加一个 HelloController ,然后启动项目进行测试:

@RestController
public class HelloController {
  @GetMapping("/hello")
  public String hello() {
    return "hello";
 }
}

启动项目,访问接口,结果如下:

Spring Boot全局异常处理

在Spring Boot项目中 ,异常统一处理,可以使用Spring中@ControllerAdvice来统一处理,也可以自己来定义异常处理方案。Spring Boot 中,对异常的处理有一些默认的策略,我们分别来看。

默认情况下,Spring Boot 中的异常页面 是这样的:

我们从这个异常提示中,也能看出来,之所以用户看到这个页面,是因为开发者没有明确提供一个/error 路径,如果开发者提供了 /error 路径 ,这个页面就不会展示出来,不过在 Spring Boot 中,提供/error 路径实际上是下下策,Spring Boot本身在处理异常时,也是当所有条件都不满足时,才会去找 /error 路径。那么我们就先来看看,在 Spring Boot 中,如何自定义 error 页面,整体上来说,可以分为两种,一种是静态页面,另一种是动态页面。

静态异常页面

自定义静态异常页面,又分为两种,第一种 是使用HTTP响应码来命名页面,例如404.html、405.html、500.html …,另一种就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面。

默认是在 classpath:/static/error/ 路径下定义相关页面:

此时,启动项目,如果项目抛出 500 请求错误,就会自动展示 500.html 这个页面,发生 404 就会展示404.html 页面。如果异常展示页面既存在 5xx.html,也存在 500.html ,此时,发生500异常时,优先展示 500.html 页面。

动态异常页面

动态的异常页面定义方式和静态的基本 一致,可以采用的页面模板有 jsp、freemarker、thymeleaf。

动态异常页面,也支持 404.html 或者 4xx.html ,但是一般来说,由于动态异常页面可以直接展示异常详细信息,所以就没有必要挨个枚举错误了 ,直接定义 4xx.html(这里使用thymeleaf模板)或者5xx.html 即可。

注意,动态页面模板,不需要开发者自己去定义控制器,直接定义异常页面即可 ,Spring Boot 中自带的异常处理器会自动查找到异常页面。

页面定义如下:

页面内容如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>5xx</h1>
<table border="1">
  <tr>
    <td>path</td>
    <td th:text="${path}"></td>
  </tr>
  <tr>
    <td>error</td>
    <td th:text="${error}"></td>
  </tr>
  <tr>
    <td>message</td>
    <td th:text="${message}"></td>
  </tr>
  <tr>
    <td>timestamp</td>
    <td th:text="${timestamp}"></td>
  </tr>
  <tr>
    <td>status</td>
    <td th:text="${status}"></td>
  </tr>
</table>
</body>
</html>

默认情况下,完整的异常信息就是这5条,展示 效果如下 :

如果动态页面和静态页面同时定义了异常处理页面,例如 classpath:/static/error/404.html 和classpath:/templates/error/404.html 同时存在时,默认使用动态页面。即完整的错误页面查找

方式应该是这样:

发生了 500 错误–>查找动态 500.html 页面–>查找静态 500.html --> 查找动态 5xx.html–>查找静态5xx.html。

自定义异常数据

默认情况下,在 Spring Boot 中,所有的异常数据其实就是上文所展示出来的 5 条数据,这 5 条数据定义在 org.springframework.boot.web.reactive.error.DefaultErrorAttributes 类中,具体定义在 getErrorAttributes 方法中 :

public Map<String, Object> getErrorAttributes(ServerRequest request,
        boolean includeStackTrace) {
    Map<String, Object> errorAttributes=new LinkedHashMap<>();
    errorAttributes.put("timestamp", new Date());
    errorAttributes.put("path", request.path());
    Throwable error=getError(request);
    HttpStatus errorStatus=determineHttpStatus(error);
    errorAttributes.put("status", errorStatus.value());
    errorAttributes.put("error", errorStatus.getReasonPhrase());
    errorAttributes.put("message", determineMessage(error));
    handleException(errorAttributes, determineException(error),
includeStackTrace);
    return errorAttributes;
}

DefaultErrorAttributes 类本身则是在

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 异常自动配置类中定义的,如果开发者没有自己提供一个 ErrorAttributes 的实例的话,那么 Spring Boot 将自动提供一个 ErrorAttributes 的实例,也就是 DefaultErrorAttributes 。

基于此 ,开发者自定义 ErrorAttributes 有两种方式 :

  1. 直接实现 ErrorAttributes 接口
  2. 继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异常数据的处理已经完成,开发者可以直接使用。

具体定义如下:

@Component
public class MyErrorAttributes  extends DefaultErrorAttributes {
  @Override
  public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean
includeStackTrace) {
    Map<String, Object> map=super.getErrorAttributes(webRequest,
includeStackTrace);
    if ((Integer)map.get("status")==500) {
      map.put("message", "服务器内部错误!");
   }
    return map;
 }
}

定义好的 ErrorAttributes 一定要注册成一个 Bean ,这样,Spring Boot 就不会使用默认的DefaultErrorAttributes 了,运行效果如下图:

自定义异常视图

异常视图默认就是前面所说的静态或者动态页面,这个也是可以自定义的,首先 ,默认的异常视图加载逻辑在 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 类的errorHtml 方法中,这个方法用来返回异常页面+数据,还有另外一个 error 方法,这个方法用来返回异常数据(如果是 ajax 请求,则该方法会被触发)。

@RequestMapping(produces=MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request,
        HttpServletResponse response) {
    HttpStatus status=getStatus(request);
    Map<String, Object> model=Collections.unmodifiableMap(getErrorAttributes(
            request, isIncludeStackTrace(request,
MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    ModelAndView modelAndView=resolveErrorView(request, response, status,
model);
    return (modelAndView !=null) ? modelAndView : new ModelAndView("error",
model);
}

在该方法中 ,首先会通过 getErrorAttributes 方法去获取异常数据(实际上会调用到 ErrorAttributes的实例 的 getErrorAttributes 方法),然后调用 resolveErrorView 去创建一个 ModelAndView ,如果这里创建失败,那么用户将会看到默认的错误提示页面。

正常情况下, resolveErrorView 方法会来到 DefaultErrorViewResolver 类的 resolveErrorView 方法中:

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus
status,
        Map<String, Object> model) {
    ModelAndView modelAndView=resolve(String.valueOf(status.value()),
model);
    if (modelAndView==null && SERIES_VIEWS.containsKey(status.series())) {
        modelAndView=resolve(SERIES_VIEWS.get(status.series()),
model);
   }
    return modelAndView;
}

在这里,首先以异常响应码作为视图名分别去查找动态页面和静态页面,如果没有查找到,则再以 4xx或者 5xx 作为视图名再去分别查找动态或者静态页面。

要自定义异常视图解析,也很容易 ,由于 DefaultErrorViewResolver 是在ErrorMvcAutoConfiguration 类中提供的实例,即开发者没有提供相关实例时,会使用默认的DefaultErrorViewResolver ,开发者提供了自己的 ErrorViewResolver 实例后,默认的配置就会失效,因此,自定义异常视图,只需要提供 一个 ErrorViewResolver 的实例即可:

@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
  public MyErrorViewResolver(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
    super(applicationContext, resourceProperties);
 }
  @Override
  public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus
status, Map<String, Object> model) {
    return new ModelAndView("/aaa/123", model);
 }
}

实际上,开发者也可以在这里定义异常数据(直接在 resolveErrorView 方法重新定义一个 model ,将参数中的model 数据拷贝过去并修改,注意参数中的 model 类型为 UnmodifiableMap,即不可以直接修改),而不需要自定义 MyErrorAttributes。定义完成后,提供一个名为 123 的视图,如下图:

如此之后,错误试图就算定义成功了。

总结

实际上也可以自定义异常控制器 BasicErrorController ,不过我觉得这样太大动干戈了,没必要,前面几种方式已经可以满足我们的大部分开发需求了。如果是前后端分离架构,异常处理还有其他一些处理方案,这个以后和大家聊。

篇幅有限,其他内容就不在这里一一展示了,这份Spring Boot实战教程已整理成一份PDF文档,共有200多页。

关注公众号:程序零世界,回复 666 获取这份整理好的Spring Boot实战教程。

最后

欢迎大家一起交流,喜欢文章记得点ge 赞哟,感谢支持!