Java架构师

Spring MVC高级框架

2021-06-24  本文已影响0人  小山雀

Spring MVC应用

Spring MVC介绍

MVC体系结构

三层架构
在B/S架构中,系统标准的三层架构包括:表现层、业务层、控制层。

MVC设计模式(Model View Controller)
MVC分为三个部分:Model、View、Controller,他们各司其职:

MVC提倡:每一层只编写自己的东西,不编写其他任何代码;分层是为了解耦,解耦是为了维护方便和分工协作。

Spring MVC是什么

SpringMVC 全名叫 Spring Web MVC,是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架,属于 SpringFrameWork 的后续产品。
Spring架构:


image.png

Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发


image.png

Spring MVC的工作流程:


image.png

Spring Web MVC 工作流程

Spring MVC开发流程:

流程说明
第⼀步:⽤户发送请求⾄前端控制器DispatcherServlet
第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截
器(如果 有则⽣成)⼀并返回DispatcherServlet
第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步:处理器适配器执⾏Handler
第六步:Handler执⾏完成给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个底层对 象,包括 Model 和 View
第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
第九步:视图解析器向前端控制器返回View
第⼗步:前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
第⼗⼀步:前端控制器向⽤户响应结果

Spring MVC九大组件

管理请求到handler和Intercepor的映射关系

因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责。

根据异常设置ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。

ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀ 个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回 的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情: ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到 视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个 InternalResourceViewResolver,是针对 JSP 类型视图的。

是从请求中获取 ViewName.
RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据
ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,
便要通过这个组件从请求中查找 ViewName。

做国际化支持
ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。
LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这
个组件也是 i18n 的基础。

多套样式主题配置管理。
ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。
Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图
⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资
源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有
ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,
ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和
具体的资源。

文件上传
MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实
现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还
可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就
是封装普通的请求,使其拥有⽂件上传的功能。

重定向参数传递
FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完
post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然
可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得
呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通
过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE
中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯
上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。

url-pattern配置:

为什么/不拦截jsp?
因为在web.xml的父配置文件中配置了一个jsp的Servlet用于处理jsp请求。

怎么解决/拦截静态资源?

但是这个配置只对webapp更目录下的静态资源有效。

请求参数绑定

参数绑定的几种方式(见代码):


/**
 * @author xdf
 * @version 1.0
 * @date Create in 17:12 2021/6/17
 * @description springmvc 示例的Controller
 * @modifiedBy
 */
@Controller
@RequestMapping("/demo")
public class DemoController {

     /**
      * 1、ModelAndView方式
      */
    @RequestMapping("/handle01")
    public ModelAndView handle01() {
        Date date = new Date();
        // 返回服务器时间
        // 封装了数据和页面信息的ModelAndView
        ModelAndView modelAndView = new ModelAndView();
        // 向请求域中设置 request.setAttribute("date",date)
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

    /**
     *  2、ModelMap 方式
     *
     * /
    @RequestMapping("/handle02")
    public String handle02(ModelMap map) {
        System.out.println(map.getClass());
        Date date = new Date();
        // 返回服务器时间
        // 封装了数据和页面信息的ModelAndView
        // 向请求域中设置 request.setAttribute("date",date)
        map.addAttribute("date",date);
        return "success";
    }

    /**
     * 3、Map模式
     */
    @RequestMapping("/handle03")
    public String handle03(Map<String,Object> map) {
        System.out.println(map.getClass());
        Date date = new Date();
        // 返回服务器时间
        // 封装了数据和页面信息的ModelAndView
        // 向请求域中设置 request.setAttribute("date",date)
        map.put("date",date);
        return "success";
    }

    /**
     * 4、Model模式
     */
    @RequestMapping("/handle04")
    public String handle04(Model map) {
        System.out.println(map.getClass());
        Date date = new Date();
        // 返回服务器时间
        // 封装了数据和页面信息的ModelAndView
        // 向请求域中设置 request.setAttribute("date",date)
        map.addAttribute("date",date);
        return "success";
    }
}

这四种方式都能将数据绑定到请求参数,然后传递到jsp中去。
为什么不管是用ModelModelMap 还是Map都能实现,参数的绑定?
我们分别打印这三个对象,返现都是
class org.springframework.validation.support.BindingAwareModelMap 类的实例。
查看这个类的类图:

image.png

发现Model、Map都是BindingAwareModeMap的接口,ModelMap是它的父类。这里使用的其实都是BindingAwareModeMap的实例,只是用了不同方式来引用它。

image.png

请求参数绑定:说⽩了SpringMVC如何接收请求参数
http协议(超⽂本传输协议)
原⽣servlet接收⼀个整型参数:
(1)String ageStr = request.getParameter("age");
(2) Integer age = Integer.parseInt(ageStr);
SpringMVC框架对Servlet的封装,简化了servlet的很多操作
SpringMVC在接收整型参数的时候,直接在Handler⽅法中声明形参即可
@RequestMapping("xxx")
public String handle(Integer age) {
System.out.println(age);
}
参数绑定:取出参数值绑定到handler⽅法的形参上

/** 
 * 
 * SpringMVC 对原⽣servlet api的⽀持 url:/demo/handle02?id=1 
 * 
 * 如果要在SpringMVC中使⽤servlet原⽣对象,⽐如 
HttpServletRequest\HttpServletResponse\HttpSession,直接在Handler⽅法形参中声 
明使⽤即可 
 * 
 */ 
 @RequestMapping("/handle02") 
 public ModelAndView handle02(HttpServletRequest request, 
HttpServletResponse response,HttpSession session) { 
     String id = request.getParameter("id"); 
     Date date = new Date(); 
     ModelAndView modelAndView = new ModelAndView(); 
     modelAndView.addObject("date",date); 
     modelAndView.setViewName("success"); 
     return modelAndView;
 }
 
/* 
 * SpringMVC 接收简单数据类型参数 url:/demo/handle03?id=1 
 * 
 * 注意:接收简单数据类型参数,直接在handler⽅法的形参中声明即可,框架会取出参数值 
然后绑定到对应参数上 
 * 要求:传递的参数名和声明的形参名称保持⼀致 
 */ 
 @RequestMapping("/handle03") 
 public ModelAndView handle03(@RequestParam("ids") Integer id,Boolean 
flag) { 
     Date date = new Date(); 
     ModelAndView modelAndView = new ModelAndView(); 
     modelAndView.addObject("date",date); 
     modelAndView.setViewName("success"); 
     return modelAndView; 
 }
/* 
 * SpringMVC接收pojo类型参数 url:/demo/handle04?id=1&username=zhangsan 
 * 
 * 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名⽆所谓 
 * 但是要求传递的参数名必须和Pojo的属性名保持⼀致 
 */ 
 @RequestMapping("/handle04") 
 public ModelAndView handle04(User user) { 
     Date date = new Date();
     ModelAndView modelAndView = new ModelAndView(); 
     modelAndView.addObject("date",date); 
     modelAndView.setViewName("success"); 
     return modelAndView; 
 }
package com.lagou.edu.pojo; 

public class QueryVo { 
     private String mail; 
     private String phone; 
     // 嵌套了另外的Pojo对象 
     private User user; 
     public String getMail() { 
         return mail; 
     } 
     public void setMail(String mail) { 
         this.mail = mail; 
     } 
     public String getPhone() { 
         return phone; 
     } 
     public void setPhone(String phone) { 
         this.phone = phone; 
     } 
     public User getUser() { 
         return user; 
     } 
     public void setUser(User user) { 
         this.user = user; 
     } 
}

Handler⽅法

/* 
 * SpringMVC接收pojo包装类型参数 url:/demo/handle05? 
user.id=1&user.username=zhangsan 
 * 不管包装Pojo与否,它⾸先是⼀个pojo,那么就可以按照上述pojo的要求来 
 * 1、绑定时候直接形参声明即可 
 * 2、传参参数名和pojo属性保持⼀致,如果不能够定位数据项,那么通过属性名 + "." 的 
⽅式进⼀步锁定数据 
 * 
 */ 
 @RequestMapping("/handle05") 
 public ModelAndView handle05(QueryVo queryVo) { 
     Date date = new Date(); 
     ModelAndView modelAndView = new ModelAndView(); 
     modelAndView.addObject("date",date); 
     modelAndView.setViewName("success"); 
     return modelAndView; 
 }

<fieldset> 
     <p>测试⽤例:SpringMVC接收⽇期类型参数</p> 
     <a href="/demo/handle06?birthday=2019-10-08">点击测试</a> 
</fieldset>
/** 
 * 绑定⽇期类型参数 
 * 定义⼀个SpringMVC的类型转换器 接⼝,扩展实现接⼝接⼝,注册你的实现 
 * @param birthday 
 * @return 
 */ 
 @RequestMapping("/handle06") 
 public ModelAndView handle06(Date birthday) { 
     Date date = new Date();ModelAndView modelAndView = new 
    ModelAndView(); 
     modelAndView.addObject("date",date); 
     modelAndView.setViewName("success"); 
     return modelAndView; 
 }

import org.springframework.core.convert.converter.Converter; 
import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
/** 
* @author 应癫 
* ⾃定义类型转换器 
* S:source,源类型 
* T:target:⽬标类型 
*/ 
public class DateConverter implements Converter<String, Date> { 
     @Override 
     public Date convert(String source) { 
         // 完成字符串向⽇期的转换 
         SimpleDateFormat simpleDateFormat = new 
         SimpleDateFormat("yyyy-MM-dd"); 
         try { 
             Date parse = simpleDateFormat.parse(source); 
             return parse; 
         } catch (ParseException e) { 
             e.printStackTrace(); 
         } 
         return null; 
     } 
}
<!-- 
 ⾃动注册最合适的处理器映射器,处理器适配器(调⽤handler⽅法) 
 --> 
 <mvc:annotation-driven conversion�
service="conversionServiceBean"/> 
 <!--注册⾃定义类型转换器--> 
 <bean id="conversionServiceBean" 
class="org.springframework.format.support.FormattingConversionServiceF 
actoryBean"> 
     <property name="converters"> 
         <set>
             <bean class="com.lagou.edu.converter.DateConverter"> 
            </bean> 
         </set> 
     </property> 
 </bean>

Restful风格请求支持

什么是Restful

REST(Representational State Transfer),资源表现层状态转移。他是一种网络接口风格。

Restful优点

Restful特性

资源:网络上的一个实体。
表现层:把资源呈现出来的形式。(txt、html、xml、json...)
状态转换:就是 HTTP 协议⾥⾯,四个表示操作⽅式的动词:
GET 、POST 、PUT 、DELETE 。它们分别对应四种基本操作:GET ⽤来获取资源,POST ⽤来新建资源,PUT ⽤来更新资源,DELETE ⽤来删除资源。

RESTful风格的URL:互联网上的所有事物都是资源,URL中只有表示资源的名称,没有操作的动词。
RESTful风格资源操作:使用HTTP请求中的method方法get、post、update、delete来操作资源。put 和 delete⼏乎不使⽤。
RESTful风格资源表述:可以有多种返回数据类型:json、xml等。

Spring MVC对Restful的支持

Restful代码示例:

<h2>SpringMVC对Restful⻛格url的⽀持</h2>
<fieldset>
    <p>测试⽤例:SpringMVC对Restful⻛格url的⽀持</p>
    <a href="/demo/handle/15">rest_get测试</a>
    <form method="post" action="/demo/handle">
        <input type="text" name="username"/>
        <input type="submit" value="提交rest_post请求"/>
    </form>
    <form method="post" action="/demo/handle/15/lisi">
        <input type="hidden" name="_method" value="put"/>
        <input type="submit" value="提交rest_put请求"/>
    </form>
    <form method="post" action="/demo/handle/15">
        <input type="hidden" name="_method" value="delete"/>
        <input type="submit" value="提交rest_delete请求"/>
    </form>
</fieldset>


@RequestMapping(value = "/handle/{id}", method = RequestMethod.GET)
public ModelAndView handleGet(@PathVariable("id") String id) {
    System.out.println(id);
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("data", id);
    modelAndView.setViewName("success");
    return modelAndView;
}

@RequestMapping(value = "/handle/{id}/{name}", method = RequestMethod.POST)
public ModelAndView handlePost(@PathVariable("id") String id, @PathVariable("name") String name) {
    System.out.println(id);
    String data = "id:" + id + ",name:" + name;
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("data", data);
    modelAndView.setViewName("success");
    return modelAndView;
}

@RequestMapping(value = "/handle/{id}/{name}", method = RequestMethod.PUT)
public ModelAndView handlePut(@PathVariable("id") String id, @PathVariable("name") String name) {
    System.out.println(id);
    String data = "id:" + id + ",name:" + name;
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("data", data);
    modelAndView.setViewName("success");
    return modelAndView;
}

@RequestMapping(value = "/handle/{id}", method = RequestMethod.DELETE)
public ModelAndView handleDelete(@PathVariable("id") String id) {
    System.out.println(id);
    String data = "id:" + id;
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("data", data);
    modelAndView.setViewName("success");
    return modelAndView;
}
<!--配置springmvc请求⽅式转换过滤器,会检查请求参数中是否有_method参数,如果有就 
按照指定的请求⽅式进⾏转换--> 
 <filter> 
     <filter-name>hiddenHttpMethodFilter</filter-name> 
     <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> 
 </filter>
 <filter-mapping> 
     <filter-name>encoding</filter-name> 
     <url-pattern>/*</url-pattern> 
 </filter-mapping> 
 <filter-mapping> 
     <filter-name>hiddenHttpMethodFilter</filter-name> 
     <url-pattern>/*</url-pattern> 
 </filter-mapping>

Ajax JSON交互

什么是JSON

@ResponseBody注解

将Controller的返回值转换为指定的格式写入到Response的body区,一般是Json 格式。

分析Spring MVC使用Json交互

<!--json数据交互所需jar,start--> 
<dependency> 
     <groupId>com.fasterxml.jackson.core</groupId> 
     <artifactId>jackson-core</artifactId> 
     <version>2.9.0</version> 
</dependency> 
<dependency> 
     <groupId>com.fasterxml.jackson.core</groupId> 
     <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version> 
</dependency> 
<dependency> 
     <groupId>com.fasterxml.jackson.core</groupId> 
     <artifactId>jackson-annotations</artifactId> 
     <version>2.9.0</version> 
</dependency> 
<!--json数据交互所需jar,end-->
@RequestMapping("/handle07") 
// 添加@ResponseBody之后,不再⾛视图解析器那个流程,⽽是等同于response直接输出数据 
public @ResponseBody User handle07(@RequestBody User user) { 
     // 业务逻辑处理,修改name为张三丰 
     user.setName("张三丰"); 
     return user; 
}

Spring MVC高级技术

拦截器(Interceptor)使用

监听器、过滤器和拦截器对比

拦截器工作原理

过滤器解决乱码:

<!-- 解决post乱码问题 --> 
<filter> 
     <filter-name>encoding</filter-name> 
     <filter-class> 
     org.springframework.web.filter.CharacterEncodingFilter 
     </filter-class> 
     <!-- 设置编码参是UTF8 --> 
     <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>encoding</filter-name> 
     <url-pattern>/*</url-pattern> 
</filter-mapping>

<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" 
protocol="HTTP/1.1" redirectPort="8443"/>

拦截器的执行流程

单个拦截器的执行流程 源码解析

1)程序先执⾏preHandle()⽅法,如果该⽅法的返回值为true,则程序会继续向下执⾏处理器中的⽅ 法,否则将不再向下执⾏。

2)在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()⽅法,然后会通过
DispatcherServlet向客户端返回响应。

3)在DispatcherServlet处理完请求后,才会执⾏afterCompletion()⽅法。

多个拦截器执行流程

执行流程 image.png

示例代码:

实现三个拦截器:

package com.xdf.mvcdemo.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author xdf
 * @version 1.0
 * @date Create in 14:18 2021/6/21
 * @description 自定义拦截器1
 * @modifiedBy
 */
public class GlobalInterceptor implements HandlerInterceptor {

    /**
     * 前置拦截
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @return true 继续执行,false拦截请求
     * @throws Exception 异常信息
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("GlobalInterceptor preHandle");
        return true;
    }

    /**
     * 后置拦截
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @param modelAndView 数据和视图
     * @throws Exception 异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("GlobalInterceptor postHandle");
    }

    /**
     * 视图渲染完拦截器
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @param ex 异常,可以在这个方法中处理异常
     * @throws Exception 异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("GlobalInterceptor afterCompletion");
    }
}

package com.xdf.mvcdemo.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author xdf
 * @version 1.0
 * @date Create in 14:18 2021/6/21
 * @description 自定义拦截器1
 * @modifiedBy
 */
public class MyInterceptor1 implements HandlerInterceptor {

    /**
     * 前置拦截
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @return true 继续执行,false拦截请求
     * @throws Exception 异常信息
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("interceptor1 preHandle");
        return true;
    }

    /**
     * 后置拦截
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @param modelAndView 数据和视图
     * @throws Exception 异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("interceptor1 postHandle");
    }

    /**
     * 视图渲染完拦截器
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @param ex 异常,可以在这个方法中处理异常
     * @throws Exception 异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("interceptor1 afterCompletion");
    }
}

package com.xdf.mvcdemo.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author xdf
 * @version 1.0
 * @date Create in 14:18 2021/6/21
 * @description 自定义拦截器1
 * @modifiedBy
 */
public class MyInterceptor2 implements HandlerInterceptor {

    /**
     * 前置拦截
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @return true 继续执行,false拦截请求
     * @throws Exception 异常信息
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("interceptor2 preHandle");
        return true;
    }

    /**
     * 后置拦截
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @param modelAndView 数据和视图
     * @throws Exception 异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("interceptor2 postHandle");
    }

    /**
     * 视图渲染完拦截器
     * @param request 请求
     * @param response 响应
     * @param handler 控制器
     * @param ex 异常,可以在这个方法中处理异常
     * @throws Exception 异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("interceptor2 afterCompletion");
    }
}

在springmvc.xml配置拦截器

<mvc:interceptors>
    <bean class="com.xdf.mvcdemo.interceptor.GlobalInterceptor"/>
    <mvc:interceptor>
    <!-- 拦截所有请求 -->
        <mvc:mapping path="/**"/>
        <bean class="com.xdf.mvcdemo.interceptor.MyInterceptor1"/>
    </mvc:interceptor>

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

随便请求一个接口,输出

GlobalInterceptor preHandle
interceptor1 preHandle
interceptor2 preHandle
15
interceptor2 postHandle
interceptor1 postHandle
GlobalInterceptor postHandle
interceptor2 afterCompletion
interceptor1 afterCompletion
GlobalInterceptor afterCompletion

因此:请求到控制器之间的拦截器执行顺序是配置顺序,控制器到响应返回拦截器执行顺序是配置顺序的反序。

处理multipart形式的数据

文件上传
原⽣servlet处理上传的⽂件数据的,springmvc⼜是对serlvet的封装
需要的依赖:

<!--⽂件上传所需jar坐标--> 
<dependency> 
     <groupId>commons-fileupload</groupId> 
     <artifactId>commons-fileupload</artifactId> 
     <version>1.3.1</version> 
</dependency>

配置⽂件上传解析器

<!--配置⽂件上传解析器,id是固定的multipartResolver--> 
<bean id="multipartResolver" 
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 
     <!--设置上传⼤⼩,单位字节--> 
     <property name="maxUploadSize" value="1000000000"/> 
</bean>

前端:

<%-- 
 1 method="post" 
 2 enctype="multipart/form-data" 
 3 type="file" 
--%> 
<form method="post" enctype="multipart/form-data" action="/demo/upload"> 
     <input type="file" name="uploadFile"/> 
     <input type="submit" value="上传"/> 
</form>

后台接收Handler

@RequestMapping("upload") 
public String upload(MultipartFile uploadFile, HttpServletRequest request) 
throws IOException { 
     // ⽂件原名,如xxx.jpg 
     String originalFilename = uploadFile.getOriginalFilename(); 
     // 获取⽂件的扩展名,如jpg 
     String extendName = 
    originalFilename.substring(originalFilename.lastIndexOf(".") + 1, 
    originalFilename.length()); 
     String uuid = UUID.randomUUID().toString(); 
     // 新的⽂件名字 
     String newName = uuid + "." + extendName; 
     String realPath = 
    request.getSession().getServletContext().getRealPath("/uploads"); 
     // 解决⽂件夹存放⽂件数量限制,按⽇期存放 
     String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 
     File floder = new File(realPath + "/" + datePath); 
     if(!floder.exists()) { 
     floder.mkdirs(); 
     } 
     uploadFile.transferTo(new File(floder,newName)); 
     return "success"; 
}

在控制器处理异常

// 可以让我们优雅的捕获所有Controller对象handler⽅法抛出的异常 
@ControllerAdvice 
public class GlobalExceptionResolver { 
     @ExceptionHandler(ArithmeticException.class) 
     public ModelAndView handleException(ArithmeticException exception, 
    HttpServletResponse response) { 
         ModelAndView modelAndView = new ModelAndView(); 
         modelAndView.addObject("msg",exception.getMessage()); 
         modelAndView.setViewName("error"); 
         return modelAndView; 
     } 
}

基于Flash属性的跨重定向请求数据传递

重定向时请求参数会丢失,我们往往需要重新携带请求参数,我们可以进⾏⼿动参数拼接如下:

return "redirect:handle01?name=" + name;

但是上述拼接参数的⽅法属于get请求,携带参数⻓度有限制,参数安全性也不⾼,此时,我们可以使
⽤SpringMVC提供的flash属性机制,向上下⽂中添加flash属性,框架会在session中记录该属性值,当跳转到⻚⾯之后框架会⾃动删除flflash属性,不需要我们⼿动删除,通过这种⽅式进⾏重定向参数传递,参数长度和安全性都得到了保障,如下:

/** 
 * SpringMVC 重定向时参数传递的问题 
 * 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A 
 * url不会变,参数也不会丢失,⼀个请求 
 * 重定向:A 找 B 借钱400,B 说我没有钱,你找别⼈借去,那么A ⼜带着400块的借钱需求找到 
C 
 * url会变,参数会丢失需要重新携带参数,两个请求 
 */ 
 @RequestMapping("/handleRedirect") 
 public String handleRedirect(String name,RedirectAttributes 
redirectAttributes) { 
     //return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有 局限 
     // addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在 跳转到⻚⾯之后该属性销毁 
     redirectAttributes.addFlashAttribute("name",name); 
     return "redirect:handle01"; 
 }

手写MVC框架

自定义MVC框架核心流程:


image.png

代码仓库:https://gitee.com/xdf-lg/custom-springmvc/tree/master

Spring MVC源码深度剖析

前端控制器DispatcherServlet继承结构

image.png

DispatcherServlet实现了Servlet接口(是一个Servlet),关键方法调用结构:


image.png

重要时机点分析

1)handler方法执行时机
打断点观察:


image.png

调用栈:


image.png
image.png

2)页面渲染时机


image.png
image.png
SpringMVC处理请求的流程图 image.png

SpringMVC处理请求的流程(doDispatch流程):

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Object dispatchException = null;

        try {
            // 1 检查是否是⽂件上传的请求
            processedRequest = this.checkMultipart(request);
            multipartRequestParsed = processedRequest != request;
            
            /* 
             2 取得处理当前请求的Controller,这⾥也称为Handler,即处理器 
             这⾥并不是直接返回 Controller,⽽是返回 HandlerExecutionChain 请求处 
            理链对象 
             该对象封装了Handler和Inteceptor 
             */
            mappedHandler = this.getHandler(processedRequest);
            if (mappedHandler == null) {
                // 如果 handler 为空,则返回404
                this.noHandlerFound(processedRequest, response);
                return;
            }
            // 3 获取处理请求的处理器适配器 HandlerAdapter
            HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
            // 处理 last-modified 请求头
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler. 
             // 4 实际处理器处理请求,返回结果视图对象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            // 结果视图对象的处理
            this.applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        } catch (Exception var20) {
            dispatchException = var20;
        } catch (Throwable var21) {
            dispatchException = new NestedServletException("Handler dispatch failed", var21);
        }
        // 5 跳转⻚⾯,渲染视图
        this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
    } catch (Exception var22) {
        //最终会调⽤HandlerInterceptor的afterCompletion ⽅法
        this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
    } catch (Throwable var23) {
        this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }

}

核心步骤getHandler方法剖析

遍历两个HandlerMapping,试图获取能够处理当前请求的执⾏链


image.png

核心步骤getHandlerAdapter方法剖析

遍历各个HandlerAdapter,看哪个Adapter⽀持处理当前Handler


image.png

核心步骤ha.handle方法剖析

image.png image.png
image.png
image.png
image.png

核心步骤processDispatchResult方法剖析

render⽅法完成渲染


image.png

视图解析器解析出View视图对象


image.png

在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的View实现


image.png

解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名


image.png
封装View视图对象之后,调⽤了view对象的render⽅法 image.png

渲染数据


image.png

把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求域取出来的根本原因


image.png
将数据设置到请求域中
image.png

Spring MVC九大组件初始化

九大组件初始化时机:DispatcherServlet中有一个内部类ContextRefreshListener实现了ApplicationListener,监听了IOC容器的onRefresh方法。当IOC容器执行onRefresh方法时,执行九大组件初始化。如图:
无法复制加载中的内容
在DispatcherServlet中定义了九个属性,每⼀个属性都对应⼀种组件

/** MultipartResolver used by this servlet. */ 
// 多部件解析器 
@Nullable 
private MultipartResolver multipartResolver; 
/** LocaleResolver used by this servlet. */ 
// 区域化 国际化解析器 
@Nullable 
private LocaleResolver localeResolver; 
/** ThemeResolver used by this servlet. */ 
// 主题解析器 
@Nullable 
private ThemeResolver themeResolver; 
/** List of HandlerMappings used by this servlet. */ 
// 处理器映射器组件 
@Nullable 
private List<HandlerMapping> handlerMappings; 
/** List of HandlerAdapters used by this servlet. */ 
// 处理器适配器组件 
@Nullable
private List<HandlerAdapter> handlerAdapters; 
/** List of HandlerExceptionResolvers used by this servlet. */ 
// 异常解析器组件 
@Nullable 
private List<HandlerExceptionResolver> handlerExceptionResolvers; 
/** RequestToViewNameTranslator used by this servlet. */ 
// 默认视图名转换器组件 
@Nullable 
private RequestToViewNameTranslator viewNameTranslator; 
/** FlashMapManager used by this servlet. */ 
// flash属性管理组件 
@Nullable 
private FlashMapManager flashMapManager; 
/** List of ViewResolvers used by this servlet. */ 
// 视图解析器 
@Nullable 
private List<ViewResolver> viewResolvers;

九⼤组件都是定义了接⼝,接⼝其实就是定义了该组件的规范,⽐如ViewResolver、HandlerAdapter等都是接⼝

九⼤组件的初始化时机
DispatcherServlet中的onRefresh(),该⽅法中初始化了九⼤组件

image.png

initStrategies⽅法

image.png
观察其中的⼀个组件initHandlerMappings(context)
image.png

如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进⾏注册初始化,默认策略在DispatcherServlet.properties⽂件中配置

image.png
DispatcherServlet.properties
image.png
注意:多部件解析器的初始化必须按照id注册对象(multipartResolver)
image.png

SSM整合

整合策略

SSM = Spring + SpringMVC + Mybatis = (Spring + Mybatis)+ SpringMVC
先整合 Spring + Mybatis
然后再整合 SpringMVC
基于的需求:查询 Account 表的全部数据显示到⻚⾯

MyBatis整合Spring

<!--junit--> 
<dependency> 
 <groupId>junit</groupId> 
 <artifactId>junit</artifactId> 
 <version>4.12</version> 
 <scope>test</scope> 
</dependency> 
<!--mybatis--> 
<dependency> 
 <groupId>org.mybatis</groupId> 
 <artifactId>mybatis</artifactId> 
 <version>3.4.5</version> 
</dependency> 
<!--spring相关--> 
<dependency> 
 <groupId>org.springframework</groupId> 
 <artifactId>spring-context</artifactId> 
 <version>5.1.12.RELEASE</version> 
</dependency> 
<dependency>
<groupId>org.springframework</groupId> 
 <artifactId>spring-test</artifactId> 
 <version>5.1.12.RELEASE</version> 
</dependency> 
<dependency> 
 <groupId>org.springframework</groupId> 
 <artifactId>spring-jdbc</artifactId> 
 <version>5.1.12.RELEASE</version> 
</dependency> 
<dependency> 
 <groupId>org.springframework</groupId> 
 <artifactId>spring-tx</artifactId> 
 <version>5.1.12.RELEASE</version> 
</dependency> 
<dependency> 
 <groupId>org.springframework</groupId> 
 <artifactId>spring-aop</artifactId> 
 <version>5.1.12.RELEASE</version> 
</dependency> 
<dependency> 
 <groupId>org.aspectj</groupId> 
 <artifactId>aspectjweaver</artifactId> 
 <version>1.8.9</version> 
</dependency> 
<!--mybatis与spring的整合包--> 
<dependency> 
 <groupId>org.mybatis</groupId> 
 <artifactId>mybatis-spring</artifactId> 
 <version>2.0.3</version> 
</dependency> 
<!--数据库驱动jar--> 
<dependency> 
 <groupId>mysql</groupId> 
 <artifactId>mysql-connector-java</artifactId> 
 <version>5.1.46</version> 
</dependency> 
<!--druid连接池--> 
<dependency> 
 <groupId>com.alibaba</groupId> 
 <artifactId>druid</artifactId> 
 <version>1.1.21</version> 
</dependency>
jdbc.driver=com.mysql.jdbc.Driver 
jdbc.url=jdbc:mysql://localhost:3306/bank 
jdbc.username=root 
jdbc.password=123456
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=" 
 http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd 
 http://www.springframework.org/schema/context 
 http://www.springframework.org/schema/context/spring-context.xsd 
 http://www.springframework.org/schema/tx 
 http://www.springframework.org/schema/tx/spring-tx.xsd 
">
    <!--包扫描-->
    <context:component-scan base-package="com.lagou.edu.mapper"/>
    <!--数据库连接池以及事务管理都交给Spring容器来完成-->
    <!--引⼊外部资源⽂件-->
    <context:property-placeholder
            location="classpath:jdbc.properties"/>
    <!--第三⽅jar中的bean定义在xml中-->
    <bean id="dataSource"
          class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--SqlSessionFactory对象应该放到Spring容器中作为单例对象管理 
    原来mybaits中sqlSessionFactory的构建是需要素材的:SqlMapConfig.xml中的内 
   容 
    -->
    <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--别名映射扫描-->
        <property name="typeAliasesPackage" value="com.lagou.edu.pojo"/>
        <!--数据源dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对 
   象-->
    <!--扫描mapper接⼝,⽣成代理对象,⽣成的代理对象会存储在ioc容器中-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--mapper接⼝包路径配置-->
        <property name="basePackage" value="com.lagou.edu.mapper"/>
        <property name="sqlSessionFactoryBeanName"
                  value="sqlSessionFactory"/>
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:lgContext="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=" 
 http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd 
 http://www.springframework.org/schema/context 
 http://www.springframework.org/schema/context/spring-context.xsd 
 http://www.springframework.org/schema/tx 
 http://www.springframework.org/schema/tx/spring-tx.xsd 
">
    <!--包扫描-->
    <lgContext:component-scan base-package="com.lagou.edu.service"/>
    <!--事务管理-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务管理注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
package com.lagou.edu.mapper; 
import com.lagou.edu.pojo.Account; 
import java.util.List; 
public interface AccountMapper { 
 // 定义dao层接⼝⽅法--> 查询account表所有数据 
 List<Account> queryAccountList() throws Exception; 
}
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
<mapper namespace="com.lagou.edu.mapper.AccountMapper"> 
 <select id="queryAccountList" resultType="com.lagou.edu.pojo.Account"> 
 select * from account 
 </select> 
</mapper>
import com.lagou.edu.pojo.Account; 
import com.lagou.edu.service.AccountService; 
import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.test.context.ContextConfiguration; 
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 
import java.util.List; 
@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = {"classpath*:application*.xml"})
public class MybatisSpringTest { 
     // 希望测试ioc容器中的哪个对象你注⼊即可。 
     @Autowired 
     private AccountService accountService; 
     @Test 
     public void testMybatisSpring() throws Exception { 
         List<Account> accounts = accountService.queryAccountList(); 
         for (int i = 0; i < accounts.size(); i++) { 
             Account account = accounts.get(i); 
             System.out.println(account); 
         } 
     } 
}

整合Spring MVC

<!--SpringMVC--> 
<dependency> 
 <groupId>org.springframework</groupId> 
 <artifactId>spring-webmvc</artifactId> 
 <version>5.1.12.RELEASE</version> 
</dependency> 
<!--jsp-api&servlet-api--> 
<dependency> 
 <groupId>javax.servlet</groupId> 
 <artifactId>jsp-api</artifactId> 
 <version>2.0</version> 
 <scope>provided</scope> 
</dependency> 
<dependency> 
 <groupId>javax.servlet</groupId> 
 <artifactId>javax.servlet-api</artifactId> 
 <version>3.1.0</version> 
 <scope>provided</scope> 
</dependency> 
<!--⻚⾯使⽤jstl表达式--> 
<dependency> 
 <groupId>jstl</groupId> 
 <artifactId>jstl</artifactId> 
 <version>1.2</version> 
</dependency>
<dependency> 
 <groupId>taglibs</groupId> 
 <artifactId>standard</artifactId> 
 <version>1.1.2</version> 
</dependency> 
<!--json数据交互所需jar,start--> 
<dependency> 
 <groupId>com.fasterxml.jackson.core</groupId> 
 <artifactId>jackson-core</artifactId> 
 <version>2.9.0</version> 
</dependency> 
<dependency> 
 <groupId>com.fasterxml.jackson.core</groupId> 
 <artifactId>jackson-databind</artifactId> 
 <version>2.9.0</version> 
</dependency> 
<dependency> 
 <groupId>com.fasterxml.jackson.core</groupId> 
 <artifactId>jackson-annotations</artifactId> 
 <version>2.9.0</version> 
</dependency> 
<!--json数据交互所需jar,end-->
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
 xmlns:context="http://www.springframework.org/schema/context" 
 xmlns:mvc="http://www.springframework.org/schema/mvc" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd 
 http://www.springframework.org/schema/context 
 http://www.springframework.org/schema/context/spring�
context.xsd 
 http://www.springframework.org/schema/mvc 
 http://www.springframework.org/schema/mvc/spring-mvc.xsd 
"> 
 <!--扫描controller--> 
 <context:component-scan base-package="com.lagou.edu.controller"/> 
 <mvc:annotation-driven/> 
</beans>
package com.lagou.edu.controller; 
import com.lagou.edu.pojo.Account; 
import com.lagou.edu.service.AccountService; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.ResponseBody; 
import java.util.List; 
@Controller 
@RequestMapping("/account") 
public class AccountController { 
 /** 
 * Spring容器和SpringMVC容器是有层次的(⽗⼦容器) 
 * Spring容器:service对象+dao对象 
 * SpringMVC容器:controller对象,,,,可以引⽤到Spring容器中的对象 
 */ 
 @Autowired 
 private AccountService accountService; 
 @RequestMapping("/queryAll") 
 @ResponseBody 
 public List<Account> queryAll() throws Exception { 
 return accountService.queryAccountList(); 
 } 
}
<!DOCTYPE web-app PUBLIC 
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
"http://java.sun.com/dtd/web-app_2_3.dtd" > 
<web-app> 
 <display-name>Archetype Created Web Application</display-name> 
 <context-param> 
 <param-name>contextConfigLocation</param-name> 
 <param-value>classpath*:applicationContext*.xml</param-value>
 </context-param> 
 <!--spring框架启动--> 
 <listener> 
 <listener�
class>org.springframework.web.context.ContextLoaderListener</listener�
class> 
 </listener> 
 <!--springmvc启动--> 
 <servlet> 
 <servlet-name>springmvc</servlet-name> 
 <servlet�
class>org.springframework.web.servlet.DispatcherServlet</servlet�
class> 
 <init-param> 
 <param-name>contextConfigLocation</param-name> 
 <param-value>classpath*:springmvc.xml</param-value> 
 </init-param> 
 <load-on-startup>1</load-on-startup> 
 </servlet> 
 
 <servlet-mapping> 
 <servlet-name>springmvc</servlet-name> 
 <url-pattern>/</url-pattern> 
 </servlet-mapping> 
</web-app>
上一篇下一篇

猜你喜欢

热点阅读