1.SpringMVC 从易用性,效率上 比曾经流行的Struts2更好

2.SpringMVC 是WEB层(框架SpringMVC接管了Web层组件,比如控制器,视图,视图解析,返回给用户的数据格式)

3.SpringMVC通过注解,让POJ0成为控制器,不需要实现任何接口。

4.SpringMVC采用低耦合的组件设计方式,具有更的扩展和灵活性

5.支持REST 格式的URL请求.

6.讲的SpringMVC是基于Spring5.x的,也就是SpringMVC是在Spring基础上的SpringMVC的核心包spring-webmvc-5.3.8.jar 和spring-web-5.3.8.jar

Spring SpringMVc SpringBoot 的关系

XML配置

<?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">

    <!--配置自动扫描包-->
    <context:component-scan base-package="com.hspedu.web"/>

    <!--配置视图解析器[默认视图解析器]-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置属性suffix 和 prefix-->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
        <!--调整优先级-->
        <property name="order" value="10"/>
    </bean>

    <!--
        老师解读
        1. 配置自定义视图解析器BeanNameViewResolver
        2. BeanNameViewResolver可以去解析我们自定义的视图
        3. 配置 属性 order, 表示视图解析器执行的顺序, 值越小, 优先级越高
        4. 属性 order 的默认值是最低优先级 ,值为 Integer.MAX_VALUE
           int LOWEST_PRECEDENCE = 2147483647
    -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="99"/>
    </bean>

    <!-- 配置国际化错误信息的资源处理bean -->
    <bean id="messageSource" class=
            "org.springframework.context.support.ResourceBundleMessageSource">
        <!-- 配置国际化文件名字
            如果你这样配的话,表示messageSource回到 src/i18nXXX.properties去读取错误信息
         -->
        <property name="basename" value="i18n"></property>
    </bean>

    <!--配置文件上传需要的bean-->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
          id="multipartResolver"/>


    <!--配置自定义拦截器-spring配置文件-->
    <mvc:interceptors>
        <!--
        老韩解读
        1. 第一种配置方式
        2. 使用ref 引用到对应的myInterceptor01
        3. 这种方式,会拦截所有的目标方法
        -->
        <!--<ref bean="myInterceptor01"/>-->

        <!--老韩解读
        1. 第二种配置方式
        2. mvc:mapping path="/hi" 指定要拦截的路径
        3. ref bean="myInterceptor01" 指定对哪个拦截器进行配置
        -->
        <!--<mvc:interceptor>-->
        <!--    <mvc:mapping path="/hi"/>-->
        <!--    <ref bean="myInterceptor01"/>-->
        <!--</mvc:interceptor>-->

        <!--解读
        1. 第3种配置方式
        2. mvc:mapping path="/h*" 通配符方式 表示拦截 /h 打头的路径
        3. mvc:exclude-mapping path="/hello" /hello不拦截
        4. ref bean="myInterceptor01" 指定对哪个拦截器配置
        -->
        <mvc:interceptor>
            <mvc:mapping path="/h*"/>
            <mvc:exclude-mapping path="/hello"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>

        <!--老师解读
        1.配置的第二个拦截器
        2.多个拦截器在执行时,是顺序执行
        -->
        <mvc:interceptor>
            <mvc:mapping path="/h*"/>
            <ref bean="myInterceptor02"/>
        </mvc:interceptor>
    </mvc:interceptors>


    <!--配置统一处理异常Bean-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>-->
                <!--<prop key="java.lang.Exception">allEx</prop>-->
            </props>
        </property>
    </bean>



    <!--加入两个常规配置-->
    <!--支持SpringMVC的高级功能,比如JSR303校验, 映射动态请求-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!--将springmvc不能处理的请求,交给tomcat处理,比如css, js-->
    <mvc:default-servlet-handler/>
</beans>

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">

    <!--配置处理中文乱码的过滤器
    拦截所有请求,处理编码, 提醒,把过滤器配置到web.xml前面
    -->
    <!--<filter>-->
    <!--    <filter-name>MyCharacterFilter</filter-name>-->
    <!--    <filter-class>com.hspedu.web.filter.MyCharacterFilter</filter-class>-->
    <!--</filter>-->
    <!--<filter-mapping>-->
    <!--    <filter-name>MyCharacterFilter</filter-name>-->
    <!--    <url-pattern>/*</url-pattern>-->
    <!--</filter-mapping>-->


    <!--配置Spring提供的过滤器,解决中文乱码问题-->

    <filter>
        <filter-name>CharacterEncodingFilter</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>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--配置HiddenHttpMethodFilter
    1. 作用是 把 以post方式提交的delete和put请求进行转换
    2. 配置url-pattern 是 /* 表示请求都经过 hiddenHttpMethodFilter过滤
    3. 后面通过debug源码,就看的很清楚
    -->
    <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>

    <!--配置前端控制器/中央控制器/分发控制器
    1.用户的请求都会经过它的处理
    -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--解读
        1. 如果我们没有配置 contextConfigLocation
        2. 默认按照这样的位置去定位spring配置文件 /WEB-INF/springDispatcherServlet-servlet.xml
        -->
        <!--配置属性 contextConfigLocation, 指定DispatcherServlet 去操作的spring配置文件-->
        <!--<init-param>-->
        <!--    <param-name>contextConfigLocation</param-name>-->
        <!--    <param-value>classpath:applicationContext-mvc.xml</param-value>-->
        <!--</init-param>-->
        <!--在web项目启动时,就自动的加载DispatcherServlet-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <!--说明
        1. 这里我们配置的url-pattern是 / ,表示用户的请求都经过 DispatcherServlet
        2. 这样配置也这次rest 风格的url请求
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

SpringMVC 工作流程

1c2c608b8403ee771aeee58ba5e0beb2.png

如上图,Spring MVC 程序的完整执行流程如下:

1.用户通过浏览器发送请求,请求会被 Spring MVC 的『前端控制器』DispatcherServlet 接收。

2.DispatcherServlet 拦截到请求后,会调用『处理器映射器』HandlerMapping 。

3.『处理器映射器』根据请求 URL 找到具体的『处理器』Handler ,生成『处理器』对象(如果有,还会生成拦截器对象)并返回给 DispatcherServlet 。

4.DispatcherServlet 根据返回信息(Handler)选择合适的『处理器适配器』HandlerAdapter 。

5.HandlerAdapter 会调用并指定 Handler 。此处和上述所说的处理器 Handler ,就是我们所编写的 Controller 类。

6.Controller 执行完成后,会返回一个 ModelAndView 对象,该对象中会包含『视图名』和『模型对象』。

7.HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet 。

8.DispatcherServlet 会根据返回信息(ModelAndView)选择一个合适的『视图解析器』ViewResolver 。

9.视图解析器 ViewResolver 解析视图后,会向 DispatcherServlet 返回具体的 View 对象。

10.DispatcherServlet 对 View 进行渲染。即,将『模型数据』填充至『视图』中。

11.DispatcherServlet 将渲染后的结果返回、发送给客户端浏览器。

@RequestMapping

@RequestMapping 是 Spring MVC 中常用的注解,用于映射 HTTP 请求到处理器方法上。它可以用在类级别和方法级别上,用来指定处理请求的路径和方法。

@RequestMapping 的常用参数包括:

1. value / path:

- @RequestMapping(value = "/path")@RequestMapping(path = "/path")

- 指定请求的路径,可以是一个字符串或字符串数组。例如,`"/home"` 或 {"home", "index"}

2. method:

- @RequestMapping(method = RequestMethod.GET)

- 指定处理请求的 HTTP 方法,如 GET、POST、PUT、DELETE 等。默认是支持所有方法的。

3. params:

- @RequestMapping(params = "param=value")

- 指定请求参数的条件,例如指定某个参数必须存在或者等于特定值才处理请求。

4. headers:

- @RequestMapping(headers = "Content-Type=text/html")

- 指定请求头的条件,例如指定请求的 Content-Type。

5. consumes:

- @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)

- 指定处理请求的 Content-Type,限定请求的 MIME 类型。

6. produces:

- @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)

- 指定返回的 Content-Type,限定响应的 MIME 类型。

7. name:

- @RequestMapping(name = "handlerName")

- 指定处理器的名称,用于识别不同的处理器方法。

8. paramsCondition:

- @RequestMapping(paramsCondition = "param1=value1")

- 进一步指定请求参数的条件,支持 SpEL 表达式。

9. headersCondition:

- @RequestMapping(headersCondition = "Content-Type=text/html")

- 进一步指定请求头的条件,支持 SpEL 表达式。

这些参数可以组合使用,以更精确地定义请求映射规则。通过这些参数,`@RequestMapping` 注解可以灵活地定义处理器方法如何响应特定的 HTTP 请求。

10.Ant风格路径:

注意事项

为了简化常见的请求映射,Spring 提供了 @GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping 等注解,这些注解是 @RequestMapping 的简写形式,分别对应常用的 HTTP 方法。

注意!!!!

如果在所有的配置都对的情况下,还是出现404错误,那就一定要加上这个配置,因为web.xml默认匹配.jsp文件 大坑!!!!

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

Rest风格请求

基本说明

<%--
  Created by IntelliJ IDEA.
  User: zfc
  Version: 1.0
  Filename: rest
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>rest </title>
<%--    引入jquery--%>
    <script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
    <script type="text/javascript">
        $(function () { //当页面加载完成后,就执行=> 如果你完全忘记,去回顾我们讲过的jquery
            //给删除超链接绑定一个点击事件
            $("#deleteBook").click(function (){
                //alert("点击。。。。");
                //我们自己定义给提交的行为
                $("#hiddenForm").attr("action", this.href);
                $(":hidden").val("DELETE");
                $("#hiddenForm").submit();
                return false; //改变点击超链接的行为, 不在提交
            })
        })
    </script>
</head>
<body>
<h3>Rest风格的crud操作案例</h3>
<br><hr>
<h3>rest风格的url 查询书籍[get]</h3>
<a href="user/book/200">点击查询书籍</a>
<br><hr>
<h3>rest风格的url 添加书籍[post]</h3>
<form action="user/book" method="post">
    name:<input name="bookName" type="text"><br>
    <input type="submit" value="添加书籍">
</form>
<br><hr>
<h3>rest风格的url, 删除一本书</h3>
<%--解读
1. 默认情况下 <a href="user/book/600">删除指定id的书</a> 是get
2. 怎么样将 get 请求转成 springmvc 可以识别的 delete 就要考虑HiddenHttpMethodFilter机制
   public static final String DEFAULT_METHOD_PARAM = "_method";
   ---------------------------------------------------
   private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
  ---------------------------------------------------
   if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			String paramValue = request.getParameter(this.methodParam);
			if (StringUtils.hasLength(paramValue)) {
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				if (ALLOWED_METHODS.contains(method)) {
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}
3. 上面代码可以看到 HiddenHttpMethodFilter 过滤器可以对以Post方式提交的delete,put,patch进行转换,成springmvc
   识别的 RequestMethod.DELETE / RequestMethod.PUT /...
4. 我们需要将 get <a href="user/book/600">删除指定id的书</a> 以post方式提交给后端handler, 这样过滤器还会生效
5. 我们可以同jquery来处理-引入jquery
--%>
<a href="user/book/600" id="deleteBook">删除指定id的书</a>
<form action="" method="post" id="hiddenForm">
    <input type="hidden" name="_method"/>
</form>
<br><hr>
<h3>rest风格的url 修改书籍[put]~</h3>
<form action="user/book/666" method="post">
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="修改书籍~">
</form>
</body>
</html>
package com.zfc.web.rest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @author zfc
 * @version 1.0
 * BookHandler处理rest风格的请求-增删改查
 */
@RequestMapping("/user")
@Controller
public class BookHandler {

    //查询[GET]
    @RequestMapping(value = "/book/{id}", method = RequestMethod.GET)
    public String getBook(@PathVariable("id") String id) {
        System.out.println("查询书籍 id=" + id);
        return "success";
    }

    //添加[POST]
    @PostMapping(value = "/book")
    public String addBook(String bookName) {
        System.out.println("添加书籍 bookName== " + bookName);
        return "success";
    }

    //删除[DELETE]
    @RequestMapping(value = "/book/{id}", method = RequestMethod.DELETE)
    public String delBook(@PathVariable("id") String id) {
        System.out.println("删除书籍 id= " + id);
        //return "success"; //[如果这样返回会报错 JSPs only permit GET POST or HEAD]
        //解读
        //1. redirect:/user/success重定向
        //2. 会被解析成 /springmvc/user/success
        return "redirect:/user/success";
    }

    //如果请求是 /user/success , 就转发到 success.jsp
    //successGenecal对应的url http://ip:port/springmvc/user/success
    @RequestMapping(value = "/success")
    public String successGenecal() {
        return "success";  //由该方法 转发到success.jsp页面
    }

    //修改[PUT]
    @PutMapping(value = "/book/{id}")
    public String updateBook(@PathVariable("id") String id) {
        System.out.println("修改书籍 id=" + id);
        return "redirect:/user/success";
    }
}

细节(model and view)

package com.zfc.web.requestparam;

import com.hspedu.web.requestparam.entity.Master;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * @author 韩顺平
 * @version 1.0
 */
@RequestMapping("/vote")
@Controller
public class VoteHandler {


    /**
     * 解读 @RequestParam(value="name", required=false)
     * 1. 获取到超链接传递的数据 请求 http://localhost:8080/springmvc/vote/vote01?name=xx
     * 2. @RequestParam 表示会接收提交的参数
     * 3. value="name" 表示提交的参数名是name
     * 4. required=false 表示该参数可以没有, 默认是true,表示必须有这个参数
     * 5. 当我们使用了@RequestParam(value="name", required=false)后就请求的参数名和方法的形参名可以不一致
     */
    @RequestMapping(value = "/vote01")
    public String test01(@RequestParam(value = "name", required = false) String username) {

        System.out.println("得到的username= " + username);
        //返回到一个结果
        return "success";
    }

    /**
     * 需求: 获取http请求头信息, 获取到Accept-Encoding 和 Host
     * 1. 这里涉及到前面讲过的http协议,小伙伴可以进行回顾
     *
     * @RequestHeader("Http请求头字段")
     */
    @RequestMapping(value = "/vote02")
    public String test02(@RequestHeader("Accept-Encoding") String ae,
                         @RequestHeader("Host") String host) {
        System.out.println("Accept-Encoding= " + ae);
        System.out.println("Host= " + host);
        //返回到一个结果
        return "success";
    }

    /**
     * 演示如果获取到提交数据->封装成java对象
     *
     * @return 说明
     * 1. 方法的形参用对应的类型来指定即可, SpringMVC会自动的进行封装
     * 2. 如果自动的完成封装, 要求提交的数据,参数名和对象的字段名保持一致
     * 3. 如果属性是对象,这里就是仍然是通过 字段名.字段名 比如Master [pet]
     * , 即提交的数据 参数名 是 pet.id pet.name, 这就是级联操作
     * 4. 如果提交的数据 的参数名和对象的字段名不匹配,则对象的属性值就是null
     * 5. 小伙伴疑惑,怎么就封装成功[底层仍然是反射+注解..]
     */
    @RequestMapping(value = "/vote03")
    public String test03(Master master) {

        System.out.println("master=" + master);
        //返回结果
        return "success";
    }

    /**
     * 使用servlet api, 来获取提交的数据
     *
     * @return
     */
    @RequestMapping(value = "/vote04")
    public String test04(HttpServletRequest request,
                         HttpServletResponse response,
                         HttpSession hs) {

        //获取到session
        //servlet原生的方式
        HttpSession session = request.getSession();
        System.out.println("session=" + session);
        //注意:通过参数传入的 hs 和 通request.getSession() 得到的对象是
        //同一个
        System.out.println("hs= " + hs);

        String username = request.getParameter("username");
        String pwd = request.getParameter("pwd");
        System.out.println("username= " + username);
        System.out.println("pwd= " + pwd);
        //返回结果
        return "success";
    }

    /**
     * 解读
     * 1. 演示将提交的数据->springmvc封装到java对象->springmvc 会自动的将其放入到request域
     * 2. 这样我们就可以在跳转到的页面取出数据.
     *
     * @return
     */
    @RequestMapping(value = "/vote05")
    public String test05(Master master, HttpServletRequest request) {

        //老韩解读
        //1. springmvc会自动把获取的model模型,放入到request域中,名字就是master
        //2. 也可以手动将master放入到request[一会在讲]
        request.setAttribute("address", "beijing");
        //3. 如果我们希望修改master的属性值
        master.setName("nono");
        //4. 分析一下springmvc默认存放对象到request域中,属性名是
        //   request域 ("master", master) 属性名是类名/类型名 首字母小写
        //返回到一个结果
        return "vote_ok";
    }

    /**
     * 演示通过Map<String,Object> 设置数据到request域
     *
     * @return
     */
    @RequestMapping(value = "/vote06")
    public String test06(Master master, Map<String, Object> map) {
        System.out.println("------test06-----");
        //老韩解读
        //1. 需求是通过map对象,添加属性到request中
        //2. 原理分析:springmvc会遍历map,然后将map的k-v, 存放到request域
        map.put("address", "beijing...");

        //map.put("master", null);
        //返回到一个结果
        return "vote_ok";
    }


    /**
     * 演示通过返回ModelAndView对象,将数据放入到request域
     * @return
     */
    @RequestMapping(value = "/vote07")
    public ModelAndView test07(Master master) {

        System.out.println("----test07----");
        ModelAndView modelAndView = new ModelAndView();
        //放入属性到modelAndView对象
        modelAndView.addObject("address", "shanghai");
        //modelAndView.addObject("master", null);
        //可以把从数据库得到的数据->对象-》放入modelAndView[Service-dao-db]
        //这里指定跳转的视图名称
        modelAndView.setViewName("vote_ok");
        //返回结果
        return modelAndView;
    }

    /**
     * 演示如何将数据设置到session域中
     * @return
     */
    @RequestMapping(value = "/vote08")
    public String test08(Master master, HttpSession httpSession) {
        System.out.println("----test08----");
        //master对象是默认放在request域
        //我们将master对象放入到session域
        httpSession.setAttribute("master", master);
        httpSession.setAttribute("address", "guangzhou");

        return "vote_ok";//请求转发
    }
    /**
     * 解读
     * 1. 当Handler的方法被标识 @ModelAttribute,就视为一个前置方法
     * 2. 当调用该Handler的其它的方法时,都会先执行该前置方法
     * 3. 类似我们前面讲解Spring时,AOP的前置通知[底层是AOP机制]
     * 4. prepareModel前置方法,会切入到其它方法前执行..
     */
    @ModelAttribute
    public void prepareModel(){
        System.out.println("prepareModel()-----完成准备工作-----");
    }
}

视图和视图解析器

1.在默认情况下,我们都是返回默认的视图,然后返回的视图交由 SpringMVC的InternalResourceViewResolver 视图解析器器来处理的

2.在实际开发中,我们有时需要自定义视图,这样可以满足更多更复杂的需求

配置自定义视图解析器

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置属性suffix 和 prefix-->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
        <!--调整优先级-->
        <property name="order" value="10"/>
    </bean>

    <!--
        解读
        1. 配置自定义视图解析器BeanNameViewResolver
        2. BeanNameViewResolver可以去解析我们自定义的视图
        3. 配置 属性 order, 表示视图解析器执行的顺序, 值越小, 优先级越高
        4. 属性 order 的默认值是最低优先级 ,值为 Integer.MAX_VALUE
           int LOWEST_PRECEDENCE = 2147483647
    -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="99"/>
    </bean>

自定义视图总结

重定向(Redirect)不能直接跳转到 WEB-INF 目录下的路径是因为 WEB-INF 目录是受保护的,客户端无法直接访问该目录中的内容。

1. WEB-INF 目录的特殊性

在 Java Web 应用中,WEB-INF 目录是一个特殊的目录,位于这个目录中的资源(如 JSP 文件、HTML 文件)不能被直接通过 URL 访问。这是为了安全考虑,目的是防止用户直接通过浏览器访问某些敏感页面或资源。通常,WEB-INF 中的内容包括配置文件、类文件、JSP 文件等,只有通过服务器端(如请求转发)才能访问这些资源。

  • 请求转发(forward) 是服务器内部行为,允许服务器从 WEB-INF 中调取资源,因为它不涉及客户端的直接访问。

  • 重定向(redirect) 是客户端行为,浏览器会发起新的 HTTP 请求,但由于 WEB-INF 目录不对外开放,所以重定向时无法访问该目录中的资源。

2. 为什么重定向不能访问 WEB-INF 目录?

重定向的本质是让客户端(浏览器)发送一个新的请求去访问新的 URL,而 WEB-INF 目录下的文件无法被客户端直接请求访问。服务器会自动阻止任何尝试通过浏览器直接访问 WEB-INF 路径的请求。

举个例子:

  • 如果在浏览器地址栏中直接输入 http://localhost:8080/YourApp/WEB-INF/views/success.html,你会得到一个 404 错误,因为服务器不允许直接访问 WEB-INF 目录下的文件。

  • 当你使用 response.sendRedirect("/WEB-INF/views/success.html"),实际上会告诉浏览器发起一个新的请求去访问这个路径。但由于客户端无法直接访问 WEB-INF,因此重定向到这个路径会失败并返回 404 Not Found 错误。

数据格式化

特殊数据类型的转换

package com.zfc.web.datavalid.entity;

import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;

import javax.validation.constraints.NotNull;
import java.util.Date;

/**
 * @author zfc
 * @version 1.0
 */
public class Monster {
    private Integer id;

    //email是string,使用@NotEmpty
    @NotEmpty
    private String email;

    //@Range(min = 1,max = 100)
    //表示接收的age值,在 1-100之间

    @NotNull(message = "age不能为空")
    @Range(min = 1,max = 100)
    private Integer age;
    //@NotEmpty 表示name不能为空
    //Asserts that the annotated string, collection, map or array is not {@code null} or empty.
    @NotEmpty
    private String name;

    @NotNull(message = "生日不能为空")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    @NotNull(message = "薪水不能为空")
    @NumberFormat(pattern = "###,###.##")
    private Float salary;

    public Monster(Integer id, String email, Integer age, String name, Date birthday, Float salary) {
        this.id = id;
        this.email = email;
        this.age = age;
        this.name = name;
        this.birthday = birthday;
        this.salary = salary;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public Monster() {
    }



    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", email='" + email + '\'' +
                ", age=" + age +
                ", name='" + name + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                '}';
    }
}

package com.zfc.web.datavalid;

import com.hspedu.web.datavalid.entity.Monster;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.validation.Valid;
import java.util.List;
import java.util.Map;

/**
 * @author zfc
 * @version 1.0
 * MonsterHandler 处理器响应用户提交数据
 * @Scope(value = "prototype") 表示每次请求MonsterHandler会生成一个新的对象
 */
@Controller
@Scope(value = "prototype")
public class MonsterHandler {


    /**
     * 显示添加monster的界面
     * 1. 这里Map<String, Object> map
     * 2. 当我们向map添加的数据时,会默认存放到request域
     *
     * @param map
     * @return
     */
    @RequestMapping(value = "/addMonsterUI")
    public String addMonsterUI(Map<String, Object> map) {
        /**解读:
         1. 这里的表单,我们使用springMVC的标签来完成
         2. SpringMVC 表单标签在显示之前必须在 request 中有一个 bean, 该 bean 的属性和表单标签的字段要对应!
         request 中的 key 为: form 标签的 modelAttrite 属性值, 比如这里的monsters
         3. SpringMVC 的 form:form 标签的 action 属性值中的 / 不代表 WEB 应用的根目录.
         4. <form:form action="?" method="POST" modelAttribute="monster">
         //这里需要给request增加一个 monster,因为jsp 页面 的modelAttribute="monster"需要
         //这时是springMVC的内部的检测机制  即使是一个空的也需要,否则报错.
         */
        //老韩再次说明,如果你跳转的页面使用springmvc标签
        //就需要准备一个对象,放入request域中,这个对象的属性名 monster, 对于
        //springmvc表单标签的 modelAttribute="monster"
        map.put("monster", new Monster());
        return "datavalid/monster_addUI";
    }


    /**
     * 编写方法,处理添加妖怪
     * 1. springmvc可以将提交的数据,按照参数名和对象的属性名匹配
     * 2. 直接封装到对象中->老师前面讲解模型数据时,讲过
     * String => Integer
     * 3. @Valid Monster monster :表示对monster接收的数据进行校验
     * 4. Errors errors 表示如果校验出现错误,将校验的错误信息保存 errors
     * 5. Map<String, Object> map  表示如果校验出现错误, 将校验的错误信息保存 map 同时保存monster对象
     * 6. 校验发生的时机: 在springmvc底层,反射调用目标方法时,会接收到http请求的数据,然后根据注解来进行验证
     * , 在验证过程中,如果出现了错误,就把错误信息填充errors 和 map
     *
     * @param monster
     * @return
     */
    @RequestMapping(value = "/save")
    public String save(@Valid Monster monster, Errors errors, Map<String, Object> map) {
        System.out.println("----monster---" + monster);
        //我们为了看到验证的情况,我们输出map 和 errors
        System.out.println("===== map ======");
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            System.out.println("key= " + entry.getKey() + " value=" + entry.getValue());
        }

        System.out.println("===== errors ======");
        if (errors.hasErrors()) {//判断是否有错误
            List<ObjectError> allErrors = errors.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println("error=" + error);
            }
            return "datavalid/monster_addUI";
        }
        return "datavalid/success";
    }

    //取消绑定 monster的name表单提交的值给monster.name属性
    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {
        /**
         * 解读
         * 1. 方法上需要标注 @InitBinder  springmvc底层会初始化 WebDataBinder
         * 2. 调用 webDataBinder.setDisallowedFields("name") 表示取消指定属性的绑定
         *    即:当表单提交字段为 name时, 就不在把接收到的name值,填充到model数据monster的name属性
         * 3. 机制:springmvc 在底层通过反射调用目标方法时, 接收到http请求的参数和值,使用反射+注解技术
         *    取消对指定属性的填充
         * 4. setDisallowedFields支持可变参数,可以填写多个字段
         * 5. 如果我们取消某个属性绑定,验证就没有意义了,应当把验证的注解去掉, name属性会使用默认值null
         *  //@NotEmpty
         *  private String name;
         *
         */
        //webDataBinder.setDisallowedFields("name");
    }
}

验证及国际化

概述

1.对输入的数据(比如表单数据),进行必要的验证并给出相应的提示信息。

2.对于验证表单数据,springMVC提供了很多实用的注解,这些注解由JSR 303验证框架提供。

JSR 303 验证框架

供的标准框架,它已经包含在JavaEE中1.JSR 303 是Java为 Bean 数据合法性校验otNul、@Max 等标准的注解指定校验规则,并通过标准2.JSR 303 通过在 Bean 属性上标注类似于 @的验证接口对 Bean 进行验证

3.JSR 303 提供的基本验证注解有:

细节

DataBinder

取消属性绑定

说明

在默认情况下,表单提交的数据都会和pojo类型的iavabean属性绑定,如果程序员在开发中,希望取消某个属性的绑定,也就是说,不希望接收到某个表单对应的属性的值,则可以通过@InitBinder 注解取消绑定.

1.编写一个方法,使用@InitBinder 标识的该方法,可以对 WebDataBinder 对象进行初始化。NebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定

2.@InitBinder方法不能有返回值,它必须声明为void。

3.@InitBinder方法的参数通常是是 WebDataBinder

配置解决中文乱码问题的过滤器

    <filter>
        <filter-name>characterEncodingFilter</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> 
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

处理JSON-@ResponseBody

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // 这里使用了 @RestController,等同于 @Controller + @ResponseBody
@RequestMapping("/api")
public class DemoController {

    // 返回一个简单的字符串
    @GetMapping("/hello")
    @ResponseBody // @ResponseBody 注解表示返回的是数据而不是视图
    public String sayHello() {
        return "Hello, World!";
    }

    // 返回一个 JSON 对象
    @GetMapping("/user")
    @ResponseBody
    public User getUser() {
        return new User(1, "John Doe", "john@example.com");
    }
}

处理JSON-@RequestBody

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    // 处理 POST 请求,并通过 @RequestBody 接收 JSON 数据
    @PostMapping("/api/user")
    public String createUser(@RequestBody User user) {
        // 接收到的 User 对象已经由 JSON 自动转换而来
        return "User created: " + user.getName() + ", Email: " + user.getEmail();
    }
}

HttpMessageconverter<T>

文件下载

package com.zfc.web.json;

import com.hspedu.web.json.entity.Dog;
import com.hspedu.web.json.entity.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zfc
 * @version 1.0
 */
//@Controller
//@ResponseBody
@RestController
public class JsonHandler {

    /**
     * 解读
     * 1. 目标方法 @ResponseBody,表返回的数据是json格式
     * 2. springmvc底层根据目标方法@ResponseBody, 返回指定格式, 根据的http请求来进行处理
     * 3. 底层原理我们在前面自定义@ResponseBody讲过, 这里原生的springmvc使用转换器
     * 4. HttpMessageConverter [一会老师debug]
     *
     * @return
     */
    @RequestMapping(value = "/json/dog")
    //@ResponseBody
    public Dog getJson() {

        //返回对象
        //springmvc会根据你的设置,转成json格式数据返回
        Dog dog = new Dog();
        dog.setName("大黄狗");
        dog.setAddress("小新的家");
        return dog;

    }

    //编写方法,以json格式返回多个Dog
    @RequestMapping(value = "/json/dogs")
    //@ResponseBody
    public List<Dog> getJsons() {

        List<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog("大黄狗", "小新的家"));
        dogs.add(new Dog("大黄狗2", "小新2的家"));
        dogs.add(new Dog("大黄狗3", "小新3的家"));

        return dogs;

    }

    /**
     * 老师解读
     * 1. @RequestBody User user 在形参指定了 @RequestBody
     * 2. springmvc就会将提交的json字符串数据填充给指定Javabean
     *
     * @param user
     * @return
     */
    @RequestMapping(value = "/save2")
    //@ResponseBody
    public User save2(@RequestBody User user) {
        //将前台传过来的数据 以json的格式相应回浏览器
        System.out.println("user~= " + user);
        return user;
    }

    //响应用户下载文件的请求
    @RequestMapping(value = "/downFile")
    public ResponseEntity<byte[]> downFile(HttpSession session)
            throws Exception {


        //1. 先获取到下载文件的inputStream
        InputStream resourceAsStream =
                session.getServletContext().getResourceAsStream("/img/2.jpg");

        //2. 开辟一个存放文件的byte数组, 这里老师使用byte[] 是可以支持二进制数据(图片,视频。)
        byte[] bytes = new byte[resourceAsStream.available()];
        //3. 将下载文件的数据,读入到byte[]
        resourceAsStream.read(bytes);

        //public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {}
        //4. 创建返回的HttpStatus
        HttpStatus httpStatus = HttpStatus.OK;
        //5. 创建 headers
        HttpHeaders headers = new HttpHeaders();
        //指定返回的数据,客户端应当以附件形式处理
        headers.add("Content-Disposition", "attachment;filename=2.jpg");

        //构建一个ResponseEntity 对象1. 的http响应头headers 2. http响应状态 3. 下载的文件数据
        ResponseEntity<byte[]> responseEntity =
                new ResponseEntity<>(bytes, headers, httpStatus);
        //如果出现找不到文件,解决方法 rebuild project -> 重启tomcat
        return responseEntity;
    }
}

文件的上传

基本介绍

1.Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResovler

2.Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver

package com.zfc.web.fileupload;

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

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

/**
 * @author zfc
 * @version 1.0
 * 处理文件上传的handler
 */
@Controller
public class FileUploadHandler {

    //编写方法,处理文件上传的请求

    @RequestMapping(value = "/fileUpload")
    public String fileUpload(@RequestParam(value = "file") MultipartFile file,
                             HttpServletRequest request, String introduce) throws IOException {

        //接收到提交的文件名
        String originalFilename = file.getOriginalFilename();
        System.out.println("你上传的文件名= " + originalFilename);
        System.out.println("introduce=" + introduce);
        //得到要把上传文件保存到哪个路径[全路径:包括文件名]
        String fileFullPath =
                request.getServletContext().getRealPath("/img/" + originalFilename);
        //创建文件
        File saveToFile = new File(fileFullPath);
        //将上传的文件,转存到saveToFile
        file.transferTo(saveToFile);
        return "success";

    }
}

自定义拦截器

1.Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能..自定义的拦截器必须实现HandlerInterceptor接口

自定义拦截器的三个方法

1.preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理

2.postHandle():这个方法在目标方法处理完请求后执行

3.afterCompletion():这个方法在完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作

自定义拦截器执行流程说明

1.如果preHandle 方法 返回 false,则不再执行目标方法,可以在此指定返回页面

2.postHandle 在目标方法被执行后执行,可以在方法中访问到目标方法返回的 ModelAndView 对象

3.若 preHandle 返回 true,则afterCompletion 方法 在渲染视图之后被执行,

4.若 preHandle 返回 false,则aftercompletion 方法不会被调用在配置拦截器时,可以指定该拦截器对哪些请求生效,哪些请求不生效

多个拦截器的执行流程

异常处理

    @ExceptionHandler({ArithmeticException.class,NullPointerException.class,NumberFormatException.class})
    public String localException(Exception ex, HttpServletRequest request){
        System.out.println("局部异常信息是-" + ex.getMessage());
        //如何将异常的信息带到下一个页面.
        request.setAttribute("reason", ex.getMessage());
        return "exception_mes";
    }

全局异常

// 如果类上标注了@ControllerAdvice,就是一个全局异常处理类
@ControllerAdvice
public class MyGlobalException {

    /**
     * 解读
     * 1. 全局异常就不管是哪个Handler抛出的异常,都可以捕获 , @ExceptionHandler({异常类型})
     * 2. 这里处理的全局异常是NumberFormatException.class,ClassCastException.class
     * 3. Exception ex 接收抛出的异常对象
     *
     * @return
     */
    //
    @ExceptionHandler({NumberFormatException.class, ClassCastException.class, AgeException.class})
    public String globalException(Exception ex, HttpServletRequest request) {
        System.out.println("全局异常处理-" + ex.getMessage());
        //如何将异常的信息带到下一个页面.
        request.setAttribute("reason", ex.getMessage());
        return "exception_mes";
    }

}

自定义异常

通过@Responsestatus注解,可以自定义异常的说明

@ResponseStatus(reason = "年龄需要在1-120之间", value = HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException {

    public AgeException() {
    }

    public AgeException(String message) {
        super(message);
    }
}

统一处理异常信息

基本说明

1.如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver

2.它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常

3.需要在ioc容器中配置

4.对未知异常进行统一处理,使用java.lang.Exception

    <!--配置统一异常处理-->

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="java.lang.Exception">error/error.jsp</prop>
            </props>
        </property>
    </bean>

异常处理的优先级处理

局部异常 > 全局异常 > SimpleMappingExceptionResolver>tomcat 默认机制