文章目录

1. 初识SpringMVC

SpringMVC概述

  • SpringMVC是Spring子框架

  • SpringMVC是Spring为**【展现层|表示层|表述层|控制层】**提供的基于MVC设计理念的优秀Web框架,是目前主流的MVC框架。

  • SpringMVC是非侵入式的,可以使用注解,将普通的pojo,作为请求处理器【Controller】。

  • 我们使用SpringMVC代替Servlet的功能

SpringMVC处理请求原理

请求发送

  • DispatcherServlet【前端控制器】
    • 将请求交给Controller|Handler
  • Controller|Handler【请求处理器】
    • 处理请求
    • 返回数据模型
  • ModelAndView
    • Model:数据模型
    • View:视图对象或视图名
  • DispatcherServlet渲染视图
    • 将数据共享到域中
    • 跳转页面【视图】
  • 响应

SpringMVC笔记-LMLPHP

搭建SpringMVC框架

  • 创建Web工程【详见之前的笔记】

  • 导入jar包

    <!--spring-webmvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</version>
    </dependency>
    
    <!-- 导入thymeleaf与spring5的整合包 -->
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.12.RELEASE</version>
    </dependency>
    
    <!--servlet-api-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    
  • 编写配置文件

    • web.xml注册DispatcherServlet

      • url配置:/
      • init-param:contextConfigLocation,设置springmvc.xml配置文件路径【管理容器对象】
      • <load-on-startup>:设置DispatcherServlet优先级【启动服务器时,创建当前Servlet对象】

      示例代码:

      <?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">
          <!--    注册DispatcherServlet【前端控制器】-->
          <servlet>
              <servlet-name>DispatcherServlet</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!--        设置springmvc.xml配置文件路径【管理容器对象】-->
              <init-param>
                  <param-name>contextConfigLocation</param-name>
                  <param-value>classpath:springmvc.xml</param-value>
              </init-param>
          <!--   设置DispatcherServlet优先级【在启动服务器时,就创建Servlet对象】     -->
              <load-on-startup>1</load-on-startup>
          </servlet>
          <servlet-mapping>
              <servlet-name>DispatcherServlet</servlet-name>
              <url-pattern>/</url-pattern>
          </servlet-mapping>
      
      </web-app>
      
    • springmvc.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"
             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">
      <!--    - 开启组件扫描-->
      <context:component-scan base-package="SpringMVC"></context:component-scan>
      <!--    - 配置视图解析器【解析视图(设置视图前缀&后缀)】-->
          <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver" id="viewResolver">
      <!--       配置字符集属性 -->
              <property name="characterEncoding" value="UTF-8"></property>
      <!--        配置模板引擎属性-->
              <property name="templateEngine">
      <!--            配置内部bean-->
                  <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
      <!--                配置模块的解析器属性-->
                      <property name="templateResolver">
      <!--                    配置内部bean-->
                          <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
      <!--                        配置前缀-->
                              <property name="prefix" value="/WEB-INF/pages/"></property>
      <!--                        配置后缀-->
                              <property name="suffix" value=".html"></property>
      <!--                        配置字符集-->
                              <property name="characterEncoding" value="UTF-8"></property>
                          </bean>
                      </property>
      
                  </bean>
              </property>
      
          </bean>
      </beans>
      
  • 编写请求处理器【Controller|Handler】

    • 使用**@Controller**注解标识请求处理器
    • 使用**@RequestMapping**注解标识处理方法【URL】

    示例代码:

    @Controller  //当前类是请求处理器类。
    public class HelloController {
    
        /*
        配置url【/】,就映射到WEB-INF/index.html
         */
        @RequestMapping("/")
        public String toIndex(){
            //逻辑视图名映射到物理视图名
            // WEB-INF/pages index      .html
             //视图前缀     +  逻辑视图名 + 视图后缀
            return "index";
        }
        @RequestMapping("/HelloController")
        public String HelloWorld(){
            System.out.println("HelloWorld in HelloController");
            return "success";
        }
    }
    
  • 准备页面进行,测试

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Hello</title>
    </head>
    <body>
    <a th:href="@{/HelloController}">发送请求</a>
    </body>
    </html>
    

2. @RequestMapping详解

作用:为指定的类/方法指定对应的URL。

@RequestMapping位置

  • 类上面的@RequestMapping:为当前类设置url,不可单独使用

    也就是先访问类上的url再访问方法上面的url。

  • 方法上面的@RequestMapping:为当前方法设置url

@RequestMapping注解属性

  • value属性和path属性

    • 类型:String[]
    • 作用:设置URL信息
  • method属性

    • 类型:RequestMethod[]

      public enum RequestMethod {
       GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
      }
      
    • 作用:为当前URL【类或方法】设置请求方式【POST、DELETE、PUT、GET】

    • 注意:

      • 默认情况:所有请求方式均支持
      • 如请求方式不支持,会报如下错误
        • 405【Request method ‘GET’ not supported】
  • params(了解)

    • 类型:String[]
    • 作用:指定需要传递的url请求参数。
    • 注意:如设置指定请求参数,但URL中未携带指定参数,会报错:
      • 400【Parameter conditions “lastName” not met for actual request parameters:】
  • headers(了解)

    • 类型:String[]
    • 作用:为当前URL设置请求头的信息。
    • 注意:如设置指定请求头,但URL中未携带请求头,会报如下错误
      • 404:请求资源未找到
  • 示例代码:

    @RequestMapping(value = {"/saveEmp","/insertEmp"},
                    method = RequestMethod.GET,
                    params = "lastName=lisi",
                    headers = "User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36")
    public String saveEmp(){
        System.out.println("添加员工信息!!!!");
    
        return SUCCESS;
    }
    

@RequsetMapping支持Ant风格路径

常用通配符

a) ?:匹配一个字符

b) *:匹配任意字符

c) **:匹配多层路径

示例代码:

@RequestMapping("/Ant/**")
    public String Ant(){
        System.out.println("ANT");
        return SUCCESS;
    }

3. @PathVariable 注解

  • 位置:只能写在参数前面,获取参数数值。

  • 作用:获取占位符中的参数。占位符语法“{ }”。

示例代码:

<a th:href="@{/testPathVariable/1001}">测试PathVariable</a>
@RequestMapping("/testPathVariable/{empId}")
    public String testPathVariable(@PathVariable("empId") Integer empId){
        System.out.println("empId = " + empId);
        return SUCCESS;
    }

@PathVariable注解属性

  • value属性与name属性

    • 类型:String[ ]
    • 作用:设置占位符的参数名
  • required属性

    • 类型:boolean

    • 作用:设置当前参数是否必须入参【默认值:true】

      • true:表示当前参数必须入参,如未入参会报如下错误:
        • Missing URI template variable ‘empId’ for method parameter of type Integer
      • false:表示当前参数不必须入参,如未入参,会装配参数为默认null值。
    • 代码示例

      @RequestMapping("/testPathVariable")
          public String testPathVariable(@PathVariable(value = "empId",required = false) Integer empId){
              System.out.println("empId = " + empId);
              return SUCCESS;
          }
      

4. REST的CRUD风格

REST的CRUD与传统风格的CRUD

  • 传统风格CRUD

  • REST风格CRUD

REST风格CRUD优势

  • 提高网站排名
    • 竞价排名
    • 技术排名
  • 便于第三方平台对接

URL中只使用名词来定位资源,用HTTP协议里的动词(GET、POST、PUT、DELETE)来实现资源的增删改查操作。

实现PUT & DELECT提交方式步骤

  • 注册过滤器HiddenHttpMethodFilter

    <!--    注册过滤器-->
        <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>
    
  • 设置表单的提交方式为POST,并设置参数:_method=PUT或_method=DELETE

    <h1>修改员工-PUT方式提交</h1>
    <form th:action="@{/emp}" method="POST">
        <input type="hidden" name="_method" value="PUT">
        <input type="submit" value="修改员工-PUT方式提交">
    </form>
    <h1>删除员工-DELETE方式提交</h1>
    <form th:action="@{/emp/1001}" method="POST">
        <input type="hidden" name="_method" value="DELETE">
        <input type="submit" value="删除员工-DELETE方式提交">
    </form>
    

源码解析HiddenHttpMethodFilter

public static final String DEFAULT_METHOD_PARAM = "_method";

private String methodParam = DEFAULT_METHOD_PARAM;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {

   HttpServletRequest requestToUse = request;

   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);
         }
      }
   }

   filterChain.doFilter(requestToUse, response);
}
/**
	 * Simple {@link HttpServletRequest} wrapper that returns the supplied method for
	 * {@link HttpServletRequest#getMethod()}.
	 */
	private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

		private final String method;

		public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
			super(request);
			this.method = method;
		}

		@Override
		public String getMethod() {
			return this.method;
		}
	}

5. SpringMVC处理请求数据

处理请求参数

  • 默认情况:可以将请求参数名与入参参数名一致,会自动入参和进行类型转换。
@RequestMapping("/requestParam01")
    public String requestParam01 (String stuname){
        System.out.println("stuname = " + stuname);
        return SUCCESS;
    }
<h1>处理请求参数</h1>
<a th:href="@{/requestParam01(stuname='zs')}">测试处理请求参数</a>
  • @RequestParam注解

    • 作用:如请求参数与入参参数名不一致时,可以使用@RequestParam注解设置入参参数名

    • 属性

      • value/name
        • 类型:String
        • 作用:设置需要入参的参数名
      • required
        • 类型:Boolean
        • 作用:设置当前参数,是否必须入参
          • true:表示当前参数必须入参,如未入参会报如下错误
            • 400【Required String parameter ‘sName’ is not present】
          • false:表示当前参数不必须入参,如未入参,装配null值
      • defaultValue
        • 类型:String
        • 作用:当装配数值为null时,指定当前defaultValue默认值
    • 示例代码

      <h1>处理请求参数</h1>
      <a th:href="@{/requestParam01(sName='zs')}">测试处理请求参数</a>
      
      /*
          获取请求参数
           */
          @RequestMapping("/requestParam01")
          public String requestParam01 (@RequestParam(value = "sName")String stuname, @RequestParam(value = "sId",required = false,defaultValue = "1234")Integer stuId){
              System.out.println("stuname = " + stuname);
              System.out.println("stuId = " + stuId);
              return SUCCESS;
          }
      
  • SpringMVC支持POJO入参

    • 要求:请求参数名name与POJO中的属性名保持一致

    • 示例代码

      <form th:action="@{/saveEmp}" method="POST">
          id:<input type="text" name="id"><br>
          LastName:<input type="text" name="lastName"><br>
          Email:<input type="text" name="email"><br>
          Salary:<input type="text" name="salary"><br>
          <input type="submit" value="添加员工信息">
      </form>
      
      /**
       * 获取请求参数POJO
       */
      @RequestMapping(value = "/saveEmp",method = RequestMethod.POST)
      public String saveEmp(Employee employee){
          System.out.println("employee = " + employee);
          return  SUCCESS;
      }
      

处理请求头

  • 语法:@RequestHeader注解

  • 属性

    • value/name
      • 类型:String
      • 作用:设置需要获取请求头名称
    • required
      • 类型:boolean
      • 作用:【默认值true】
        • true:设置当前请求头是否为必须入参,如未入参会报如下错误
          • 400【Required String parameter ‘sName’ is not present】
        • false:表示当前参数不必须入参,如未入参,装配null值
    • defaultValue
      • 类型:String
      • 作用:当装配数值为null时,指定当前defaultValue默认值
  • 示例代码

    <a th:href="@{/testGetHeader}">测试获取请求头</a>
    
    /**
     * 获取请求头
     * @return
     */
    @RequestMapping(value = "/testGetHeader")
    public String testGetHeader(@RequestHeader("Accept-Language")String al,
                                @RequestHeader("Referer") String ref){
        System.out.println("al = " + al);
        System.out.println("ref = " + ref);
        return SUCCESS;
    }
    

处理Cookie信息

  • 语法:@CookieValue获取Cookie数值

  • 属性

    • value/name
      • 类型:String
      • 作用:设置需要获取Cookie名称
    • required
      • 类型:boolean
      • 作用:【默认值true】
        • true:设置当前Cookie是否为必须入参,如未入参会报如下错误
          • 400【Required String parameter ‘sName’ is not present】
        • false:表示当前Cookie不必须入参,如未入参,装配null值
    • defaultValue
      • 类型:String
      • 作用:当装配数值为null时,指定当前defaultValue默认值
  • 示例代码

    <a th:href="@{/setCookie}">设置Cookie信息</a><br>
    <a th:href="@{/getCookie}">获取Cookie信息</a><br>
    
    /**
         * 设置Cookie
         * @return
         */
        @RequestMapping("/setCookie")
        public String setCookie(HttpSession session){
    //        Cookie cookie = new Cookie();
            System.out.println("session.getId() = " + session.getId());
            return SUCCESS;
        }
    
        /**
         * 获取Cookie
         * @return
         */
        @RequestMapping("/getCookie")
        public String getCookie(@CookieValue("JSESSIONID")String cookieValue){
            System.out.println("cookieValue = " + cookieValue);
            return SUCCESS;
        }
    

使用原生Servlet-API入参

  • 将原生Servlet相关对象入参即可。之后调用相应方法。
  <h1>使用原生Servlet-API</h1>
  <a th:href="@{/servlet}">获取request信息</a><br>
/*
    使用原生API,request入参
     */
    @RequestMapping("/servlet")
    public String servlet(HttpServletRequest request){
        //获取Servlet上下文
        ServletContext servletContext = request.getServletContext();
        //获取文件真实路径
        String realPath = servletContext.getRealPath("/webapp/WEB-INF/pages/index.html");
        System.out.println("realPath = " + realPath);
        return SUCCESS;
    }

6. SpringMVC处理响应数据

使用ModelAndView

  • 使用ModelAndViem作为方法返回值类型,处理响应数据。

  • ModelAndView是模型数据视图对象的集成对象。

  • 底层实现原理:

    • 数据共享到request域
    • 跳转路径方式:转发
  • 源码解析:

    public class ModelAndView {
    
       /** View instance or view name String. */
       //view代表view对象或viewName【建议使用viewName】
       @Nullable
       private Object view;
    
       /** Model Map. */
       //ModelMap集成LinkedHashMap,存储数据
       @Nullable
       private ModelMap model;
        
        /**
        	设置视图名称
    	 */
    	public void setViewName(@Nullable String viewName) {
    		this.view = viewName;
    	}
    
    	/**
    	 * 获取视图名称
    	 */
    	@Nullable
    	public String getViewName() {
    		return (this.view instanceof String ? (String) this.view : null);
    	}
    
        /**
    	 获取数据,返回Map【无序,model可以为null】
    	 */
    	@Nullable
    	protected Map<String, Object> getModelInternal() {
    		return this.model;
    	}
    
    	/**
    	 * 获取数据,返回 ModelMap【有序】
    	 */
    	public ModelMap getModelMap() {
    		if (this.model == null) {
    			this.model = new ModelMap();
    		}
    		return this.model;
    	}
    
    	/**
    	 * 获取数据,返回Map【无序】
    	 */
    	public Map<String, Object> getModel() {
    		return getModelMap();
    	}
        
        /**
        	设置数据
        */
        public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) {
    		getModelMap().addAttribute(attributeName, attributeValue);
    		return this;
    	}
        
        
    }
         
    
  • 测试代码:

    @Controller
    public class TestResponseController {
        @RequestMapping("/responce")
        public ModelAndView testmodeAndViev(){
            ModelAndView mv = new ModelAndView();
            //设置数据,将数据共享到请求域中
            mv.addObject("stuName","Sivan");
            //设置逻辑视图名
            mv.setViewName("responce_success");
            return mv;
        }
    }
    

使用Map、Model、ModelMap

  • 使用Map、Model、ModelMap入参也可以处理响应数据。

  • 示例代码

    /*
        使用Model、ModelMap、Map作为方法入参
         */
        @RequestMapping("/responce1")
        public String modelAndMap(/*Map<String,Object> map*/ Model model){
    //        map.put("stuName","LiSi");
            model.addAttribute("stuName","LISI");
            return "responce_success";
        }
    

更改域对象

SpringMVC封装数据,默认使用request域对象。

 /*
    使用session域——方式一
     */
    @RequestMapping("/responce2")
    public String session(HttpSession session){
        session.setAttribute("stuName","LLL");
        System.out.println(session.getAttribute("stuName"));
        return "responce_success";
    }
    /*
    使用session域—方式二
     */
    @RequestMapping("/responce3")
    public ModelAndView session1(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("stuName","LILI");
        mv.setViewName("responce_success");
        return mv;
    }

7. SpringMVC处理请求响应乱码

CharacterEncodingFilter源码解析

public class CharacterEncodingFilter extends OncePerRequestFilter {

   //需要设置字符集
   @Nullable
   private String encoding;
   //true:处理请乱码
   private boolean forceRequestEncoding = false;
   //true:处理响应乱码
   private boolean forceResponseEncoding = false;
    
    public String getEncoding() {
		return this.encoding;
	}
    
    public boolean isForceRequestEncoding() {
		return this.forceRequestEncoding;
	}
    
    public void setForceResponseEncoding(boolean forceResponseEncoding) {
		this.forceResponseEncoding = forceResponseEncoding;
	}
    //将请求和响应合二为一。
    public void setForceEncoding(boolean forceEncoding) {
		this.forceRequestEncoding = forceEncoding;
		this.forceResponseEncoding = forceEncoding;
	}
    
 	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		String encoding = getEncoding();
		if (encoding != null) {
			if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
	//解决请求乱码		
                request.setCharacterEncoding(encoding);
			}
			if (isForceResponseEncoding()) {
	//解决响应乱码			
                response.setCharacterEncoding(encoding);
			}
		}
        
		filterChain.doFilter(request, response);
	
    }
    
    
}

使用CharacterEncodingFilter

  • SpringMVC底层默认处理响应乱码
  • SpringMVC处理请求乱码步骤
    1. 注册CharacterEncodingFilter
      • 注册CharacterEncodingFilter必须是第一Filter位置
    2. 为CharacterEncodingFilter中属性encoding赋值
    3. 为CharacterEncodingFilter中属性forceEncoding/forceRequestEncoding赋值
<filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--   设置编码为UTF-8     -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    <!--   解决请求与响应乱码     -->
        <init-param>
    <!-- 调用方法setForceRequestEncoding,这里只是指定request,底层已经处理相应乱码了,可能会出现冲突-->
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

8. 源码解析SpringMVC工作原理

视图解析器对象【ViewResolver】

比如现在使用的ThymeleafViewResolver【配置在springmvc.xml中】

  • 概述:SpringMVC中所有视图解析器对象均实现ViewResolver接口

  • 作用:使用ViewResolver将View对象从ModelAndView中解析出来。

    • ThymeleafViewResolver的837行代码

      //底层使用反射的方式,newInstance()创建视图对象
      final AbstractThymeleafView viewInstance = BeanUtils.instantiateClass(getViewClass());
      

视图对象【View】

  • 概述:SpringMVC中所有View对象均实现View接口。
  • 作用:视图渲染
    • 将数据共享到域中【request、session、application(ServletContext)】
    • 跳转路径【转发或重定向】

源码解析:

  • 无论方法返回是ModelAndView还是String,最终SpringMVC底层,均会封装为ModelAndView对象【前端控制器调用Controller的入口,进入执行Controller】

    //DispatcherServlet的1061行代码
    ModelAndView mv = null;
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
  • SpringMVC解析mv【ModelAndView】

    //DispatcherServlet的1078行代码
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
  • ThymeleafView对象中344行代码【SpringMVC底层处理响应乱码】

    //computedContentType="text/html;charset=UTF-8"
    response.setContentType(computedContentType);
    
  • WebEngineContext对象中783行代码【SpringMVC底层将数据默认共享到request域】

    this.request.setAttribute(name, value);
    

SpringMVC笔记-LMLPHP

9. 视图控制器&重定向&加载静态资源

视图控制器

步骤

  1. 添加<mvc:view-controller>标签

  2. 添加<mvc:annotation-driven>标签

    有20+功能,配置后可以解决其他请求路径失效的问题。

  • 示例代码:
<!--    添加视图控制器,为指定URL映射视图-->
    <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    <mvc:view-controller path="/rest" view-name="rest_crud"></mvc:view-controller>
    <mvc:view-controller path="/request" view-name="request"></mvc:view-controller>

    <mvc:annotation-driven></mvc:annotation-driven>

重定向

语法:return “**redirect:/**xxx.html”;

发现报错404。此时我们需要将资源加载到服务器。

加载静态资源

  • DefaultServlet加载静态资源到服务器。

    • 静态资源:html、css、js

    • tomcat-> conf-> web.xml关键代码如下:

      <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>
      
  • 问题原因:

    • 表层原因:html请求被DispatcherServlet拦截,但是没有找到请求处理的对应方法,但是请求静态资源本来就不需要处理方法。
    • 深层次:DispatcherServlet与默认DefaultServlet配置均为/,导致DispatcherServlet中配置将DefaultServlet中配置的/覆盖了。【导致无法加载静态资源】
  • 解决方案:

        <!--  解决重定向时的静态资源加载问题  -->
        <mvc:default-servlet-handler></mvc:default-servlet-handler>
        <mvc:annotation-driven></mvc:annotation-driven>
    

源码解析重定向原理

  • 创建RedirectView对象【ThymeleafViewResolver的775行代码】

    // Process redirects (HTTP redirects)
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
        final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
        //创建RedirectView对象
        final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
        return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, REDIRECT_URL_PREFIX);
    }
    
  • RedirectView视图渲染

    • RedirectView对象URL处理【330行代码】

    SpringMVC笔记-LMLPHP

    • 执行重定向【RedirectView的627行代码】

    SpringMVC笔记-LMLPHP

10. REST风格CURD练习

搭建环境

  • 导入相关jar包

    <!--spring-webmvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</version>
    </dependency>
    
    <!-- 导入thymeleaf与spring5的整合包 -->
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.12.RELEASE</version>
    </dependency>
    
    <!--servlet-api-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    
  • 编写配置文件

    • web.xml
      • CharacterEncodingFilter
      • HiddenHttpMethodFilter
      • DispatcherServlet
    • springmvc.xml
  • 实现添加功能思路

    1. 跳转添加页面
    2. 实现添加功能
  • 实现删除操作思路

    方式一:直接使用表单实现DELETE提交方式

    <!--        实现删除员工的方式一:使用表格的方式删除-->
            <form th:action="@{/emps/}+${emp.id}" method="post">
                <input type="hidden" name="_method" value="DELETE">
                <input type="submit" value="删除">
            </form>
    

    方式二:使用超链接实现DELETE提交方式

    <div align="center" id="app">
        <a href="#" @click="deleteEmp">删除</a>
        <form id="delForm" th:action="@{/emps/}+${emp.id}" method="post">
            <input type="hidden" name="_method" value="DELETE">
        </form>
    </div>
    <script type="text/javascript" src="static/js/vue_v2.6.14.js"></script>
    <script type="text/javascript">
        new Vue({
            el:"#app",
            data:{},
            methods:{
                deleteEmp(){
                    alert("hehe");
                    //获取响应表单
                    var formEle = document.getElementById("delForm");
                    formEle.submit();
                    //取消超链接默认行为
                    event.preventDefault();
                }
            }
        });
    </script>
    

11. SpringMVC消息转换器

消息转换器概述

  • HttpMessageConverter:消息转换器作用

    将Java对象与请求报文和响应报文互相转换。
    SpringMVC笔记-LMLPHP

  • 使用HttpMessageCovter将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的相应信息,Spring提供了两种途径:

    • 使用@RequestBody / @ResponseBody对处理方法进行标注。
    • 使用HttpEntity / ResponseEntity作为处理方法的入参或返回值。

使用消息转换器获取处理请求报文

  • 使用@RequestBody的方式获取请求体,必须要以post的方式获取,因为get方式没有请求体。

    @PostMapping("/textRequestBody")
        public String messageconvert(@RequestBody String reqBody){
            System.out.println("reqBody = " + reqBody);
            return SUCCESS;
        }
    
  • 使用HttpEntity<T>获取请求体和请求头

    @RequestMapping("/textHttpEntity")
        public String messageconvert1(HttpEntity<String> httpEntity){
            //获取请求头
            HttpHeaders headers = httpEntity.getHeaders();
            System.out.println("headers = " + headers);
            //获取请求体
            String body = httpEntity.getBody();
            System.out.println("body = " + body);
            return SUCCESS;
        }
    

使用消息转换器处理响应报文

  • @ResponseBody

    • 作用:将指定数据,直接以响应流的方式,响应数据。不返回逻辑视图名。

      • 类似response.getWriter().write()
    • 写在类上:当前类的所有方法均返回文本,不跳转页面

    • 示例

      @ResponseBody
          @RequestMapping("/textresponseBody")
          public String textresponseBody(){
              System.out.println("SUCCESS = " + SUCCESS);
              return "hello";
          }
      

使用消息转换器处理JSON格式数据

  • 使用步骤

    1. 添加jackson支持

      <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.12.3</version>
      </dependency>
      
      
    2. 装配消息转换器MappingJackson2HttpMessageConvert

      配置mvc:annotation-driver标签

    3. 在需要转换json数据的方法上,添加注解@ResponseBody。需要转换的数据作为方法的返回值。

  • 示例代码

@RequestMapping("/textJSON")
    @ResponseBody
    public Employee textJSON(){
        Employee employee =new Employee(1001,"xin","123@qq.com",1);
        return employee;
    }

12. SpringMVC文件上传与文件下载

SpringMVC笔记-LMLPHP

文件下载

  • 步骤

    1. 准备文件下载的相关资源。
    2. 将ResponseEntity<T>对象作为方法的返回值类型。
    3. 为ResponseEntity<T>对象设置三个参数。
  • 示例代码

    @RequestMapping("/filedownLoadController")
        public ResponseEntity<byte[]> filedownload(HttpServletRequest request,String filename) throws UnsupportedEncodingException {
            byte [] bytes = null;
            try {
            //参数一:文件下载主体内容byte[]
            //获取目标资源文件路径
            String realPath = request.getServletContext().getRealPath("/WEB-INF/download/" + filename);
            //获取输入流
                InputStream inputStream = new FileInputStream(realPath);
                bytes = new byte[inputStream.available()];
                inputStream.read(bytes);
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
            //参数二:设置响应头
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add("Content-Disposition","attachment;filename=" + filename);
            //设置中文文件名问题
            httpHeaders.setContentDispositionFormData("attachment",new String(filename.getBytes("utf-8"),"ISO-8859-1"));
    
            //参数三:设置状态码
            HttpStatus ok = HttpStatus.OK;
    
            ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes,httpHeaders,ok);
    
            return responseEntity;
        }
    

文件上传

  • 实现文件上传思路

    • 准备工作

      • 准备文件上传页面

        • 表单的提交方式为POST
        • 设置表单enctype属性值为mulitipart/form-data
        • 表单中包含文件域【type=file】
        <div align="center">
            <h2>演示文件上传</h2>
            <form th:action="@{/fileuploadController}" method="post" enctype="multipart/form-data">
                上传姓名:<input type="text" name="name"><br>
                上传文件:<input type="file" name="upload"><br>
                <input type="submit" value="上传">
            </form>
        </div>
        
      • 导入jar包

        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
        
      • 装配CommonsMultipartResolver解析器

        • id必须是multipartResolver
        • 设置上传文件字符集:defaultEncoding:UTF-8
        <!--    装配**CommonsMultipartResolver**解析器-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <property name="defaultEncoding" value="UTF-8"></property>
        </bean>
        
    • 实现步骤

      • 将type=file【文件域】直接入参:MultipartFile类型。

      • 获取文件名称

      • 获取上传文件真实路径

      • 实现文件上传

        //入参的参数名和表单提交的名称一致
            @RequestMapping("/fileuploadController")
            public String upload(String name,
                                 MultipartFile upload,
                                 HttpSession session){
                try {
                //1. 获取文件名
                String filename = upload.getOriginalFilename();
                //2. 获取上传的真实路径
                String realPath = session.getServletContext().getRealPath("/WEB-INF/upload");
                //如果路径不存在,则创建
                File filePath = new File(realPath);
                if(!filePath.exists()){
                    filePath.mkdirs();
                }
                //3. 实现文件上传【File.separator】
                File upfile = new File(filePath + File.separator + filename);
                upload.transferTo(upfile);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                return "success";
            }
        
    • 优化文件上传

      • 允许同名文件上传

        • 使用UUID【32位16进制随机数,可以确保唯一性】
        • 使用时间戳
        //生成UUID
                String uuid = UUID.randomUUID().toString().replace("-", "");
                //生成时间戳
                long timeMillis = System.currentTimeMillis();
        
      • 设置文件上传大小上限

        <!--    装配**CommonsMultipartResolver**解析器-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <property name="defaultEncoding" value="UTF-8"></property>
        <!--    设置文件上传上限,单位byte-->
            <property name="maxUploadSize" value="102400"></property>
        </bean>
        

13. SpringMVC拦截器

拦截器与过滤器区别

  • 过滤器【Filter】属于web服务器组件
    • 主要作用:过滤Servlet请求
    • 执行时机:两处执行时机【Servlet前,Servlet后】
  • 拦截器【Interceptor】属于框架【SpringMVC】
    • 主要作用:拦截Controller请求
    • 执行时机:三处
      1. 执行Servlet之后,Controller之前
      2. 执行Controller之后,Servlet之前
      3. 执行Dispatcher(分发、调度)Servlet之后【渲染视图之后】

SpringMVC笔记-LMLPHP

拦截器概述

  • SpringMVC提供了拦截器拦截用户请求,用户可以自定义拦截器实现相应功能

  • 实现拦截器有两种方式:

    1. 实现接口:HandlerInterceptor
    2. 继承适配器类:HandlerInterceptorAdapter
  • 拦截器中的三种方法:

    1. preHandle():在Controller【业务处理器】处理请求之前被调用,可以做一些权限的校验。
    2. postHandle():在Controller【业务处理器】处理请求之后,渲染视图之前调用,可以对ModelAndView中的模型和视图进行处理。
    3. afterCompletion():在DispatcherServlet完全处理请求后被调用,可以在该方法中进行资源的清理。

实现拦截器步骤

  • 实现接口
  • 重写三个方法
  • 在SpringMVC配置文件中装配
@Component
public class Myinterceptor implements HandlerInterceptor {
    /**
     * 执行Controller之前执行
     * @param request
     * @param response
     * @param handler
     * @return 
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("==>preHandle");
        return true;
    }

    /**
     * 执行Controller之后执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("==>postHandle");
    }

    /**
     * 视图渲染之后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("==>afterCompletion");
    }
}
<!--    装配拦截器-->
    <mvc:interceptors>
        <!--        直接使用bean装配方式-->
        <!--        <bean class="Spring.interceptor.Myinterceptor"></bean>-->
        <!--        使用注解装配方式,全局配置-->
        <ref bean="myinterceptor"></ref>
        
        <!--        局部装配方式-->
        <mvc:interceptor>
            <mvc:mapping path="/textRequestBody"/>
            <ref bean="myinterceptor"></ref>
        </mvc:interceptor>
        
    </mvc:interceptors>

拦截器工作原理

拦截器执行顺序即为配置顺序

  • 单个拦截器工作原理

    • 浏览器向服务器发送请求
    • 执行拦截器第一个方法preHandle()
    • 执行Controller中方法,处理请求做出相应
    • 执行拦截器第二个方法postHandle()
    • 执行DispatcherServlet渲染视图
    • 执行第三个方法afterCompletion()
    • 响应
  • 多个拦截器工作原理

    • 浏览器向服务器发送请求
    • 执行拦截器一第一个方法preHandle()
    • 执行拦截器二第一个方法preHandle()
    • 执行Controller中方法,处理请求做出相应
    • 执行拦截器二第二个方法postHandle()
    • 执行拦截器一第二个方法postHandle()
    • 执行DispatcherServlet渲染视图
    • 执行拦截器二第三个方法afterCompletion()
    • 执行拦截器一第三个方法afterCompletion()
    • 响应
  • 源码解析:

    preHandle底层使用正序循环遍历拦截器。

    postHandle和afterCompletion底层使用倒序循环遍历拦截器。

preHandle()方法返回值

  • 第一个拦截器preHandle()方法返回false。

    • 直接终止执行。
  • 当不是第一个拦截器preHandle()方法返回false。

    • 执行当前拦截器和之前拦截器的preHandle()方法
    • 执行之前拦截器最后一个方法afterCompletion()方法。
  • 源码解析

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //正序循环
            for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
                //如果preHandle设置为false,就进入triggerAfterCompletion。
                if (!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
    
        void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
            //倒序循环
            for(int i = this.interceptorIndex; i >= 0; --i) {
                HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
    
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                } catch (Throwable var7) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
                }
            }
    
        }
    

14. SpringMVC异常处理器

为什么需要处理异常

  • 如程序中出现异常未处理,会导致程序终止【宕机】
  • 异常处理机制
    • try-catch-finally
    • throw或throws

SpringMVC中异常处理器

  • SpringMVC通过HandlerExceptionResolver处理程序异常,需要掌握两个异常处理器实现类
    • DafaultHandlerExceptionResolver:默认异常处理器,默认开启,可以支持10+种异常处理。
    • SimpleMappingExceptionResolver:映射自定义异常处理器,可以将指定的异常映射到指定页面。
<!--    装配异常处理器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="java.lang.ArithmeticException">error/error_arith</prop>
            </props>
        </property>
        //设置异常别名
        <property name="exceptionAttribute" value="ex"></property>
    </bean>

异常:<span th:text="${ex}"></span>
  • 总结
    • 出现异常,不会执行postHandler,但也会返回ModelAndView。

SpringMVC笔记-LMLPHP

15. SpringMVC工作原理

扩展三个对象

  • HandlerMapping

    • 请求处理器的映射器对象

    • 通过HandlerMapping可以获取HandlerMapping

    • 底层实现:HandlerMapping定义了一个映射关系,将所有请求与请求处理器映射。

  • HandlerExecutionChain

    • 请求处理器的执行链对象
    • 通过HandlerExecutionChain获取HandlerAdapter。
    • 底层实现:由当前请求处理器【Controller】和对应拦截器【Interceptors】组成。
  • HandlerAdapter

    • 请求处理器适配器对象
    • 通过HandlerAdapter调用ha.handle()方法,触发请求处理器的相应方法。

SpringMVC工作原理1【URL不存在】

  1. 发送请求URL【浏览器向服务器发送请求】

  2. 通过web.xml配置的DispatcherServlet【前端控制器】加载springmvc.xml,得到SpringMVC容器对象,加载Controller【请求处理器】

  3. 判断URL是否存在

    • 不存在:判断是否配置<mvc:default-servlet-handler>【解决静态资源加载】
      • 配置:出现404,提示URL不可用。
      • 未配置:出现404。

SpringMVC工作原理2【URL存在】

  1. 发送请求URL【浏览器向服务器发送请求】

  2. 通过web.xml配置的DispatcherServlet【前端控制器】加载springmvc.xml,得到SpringMVC容器对象,加载Controller【请求处理器】

    • 加载三个对象【HandlerMapping、HandlerExecutionChain、HandlerAdapter】
  3. 判断URL是否存在【存在】

  4. 执行Interceptor【拦截器】第一个方法preHandle()

    //DispatcherServlet 1056行
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    
  5. 执行Controller【请求处理器】中相应方法【处理请求,做出相应】

    //DispatcherServlet 1061
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
  6. 判断Controller中是否存在异常

    • 存在异常,就不执行拦截器的postHandle(),进入HandlerExceptionResolver异常解析器。返回ModelAndView
    • 不存在异常,Controller直接返回ModelAndView,并执行拦截器的postHandle()
  7. 通过ViewResolver【视图解析器ThymeleafViewResolver】将View【视图对象】从ModelAndView中解析出来。

    //DispatcherServlet 1119行
    render(mv, request, response);
    
    //render方法中:
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    
    //将给定的视图名称解析为view对象
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
    			Locale locale, HttpServletRequest request) throws Exception {
    
    		if (this.viewResolvers != null) {
    			for (ViewResolver viewResolver : this.viewResolvers) {
    				View view = viewResolver.resolveViewName(viewName, locale);
    				if (view != null) {
    					return view;
    				}
    			}
    		}
    		return null;
    	}
    
    //使用view对象进行视图渲染 DispatcherServlet 1394行
    view.render(mv.getModelInternal(), request, response);
    
    //进入ThymeleafView,处理响应乱码
    //computedContentType="text/html;charset=UTF-8"
    response.setContentType(computedContentType);
    
  8. View对象开始进行视图渲染

    • 数据共享
    //数据共享到request域
    this.request.setAttribute(name, value);
    
    • 路径跳转
  9. 执行拦截器的第三个方法afterCompletion()

  10. 响应
    L是否存在【存在】

  11. 执行Interceptor【拦截器】第一个方法preHandle()

//DispatcherServlet 1056行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
  1. 执行Controller【请求处理器】中相应方法【处理请求,做出相应】
//DispatcherServlet 1061
				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  1. 判断Controller中是否存在异常
  • 存在异常,就不执行拦截器的postHandle(),进入HandlerExceptionResolver异常解析器。返回ModelAndView
  • 不存在异常,Controller直接返回ModelAndView,并执行拦截器的postHandle()
  1. 通过ViewResolver【视图解析器ThymeleafViewResolver】将View【视图对象】从ModelAndView中解析出来。
//DispatcherServlet 1119行
render(mv, request, response);

//render方法中:
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

//将给定的视图名称解析为view对象
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {

		if (this.viewResolvers != null) {
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
		return null;
	}

//使用view对象进行视图渲染 DispatcherServlet 1394行
view.render(mv.getModelInternal(), request, response);

//进入ThymeleafView,处理响应乱码
//computedContentType="text/html;charset=UTF-8"
response.setContentType(computedContentType);
  1. View对象开始进行视图渲染
  • 数据共享
//数据共享到request域
this.request.setAttribute(name, value);
  • 路径跳转
  1. 执行拦截器的第三个方法afterCompletion(),最终响应。

完。

06-23 05:41