添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

知乎

什么是SpringMVC?

SpringMVC是与Spring框架配合使用的一种实现了WebMVC设计思想的Web框架。

所谓MVC思想,是将Web层中的 模型(model) 视图(view) 控制器(controller) 在业务逻辑层中解耦出来,使之各司其职且互不影响的一种设计思想。

  • 模型:仅负责封装应用程序的数据
  • 视图:仅用于向用户展示模型中包含的数据
  • 控制器:接受用户请求,调用service层和dao层来处理业务逻辑。

它是目前流行的MVC思想框架,由于与spring高度适配且配置简便而风靡业界。

SpringMVC的架构

Spring MVC框架是一个基于请求驱动的Web框架,它使用了前端控制器模式。(每一个请求都会经过一个总的处理程序,然后被指配给对应的处理程序。)

首先让我们整体看一下Spring MVC处理请求的流程:

  1. 用户发送请求,请求被SpringMVC前端控制器DispatherServlet捕获
  2. 前端控制器DispatherServlet对请求URL解析获取请求URI,根据URI,调用处理器映射器HandlerMapping
  3. 处理器映射器HandlerMapping分析出该请求需要调用的方法及拦截器、过滤器等操作,由此形成一个业务逻辑链HandlerExecutionChain(包括Handler对象以及 Handler对象对应的拦截器),将之返回给前端控制器
  4. DispatcherServlet 根据HandlerExecutionChain,选择一个对应的HandlerAdapter。(附 注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
  5. HandlerAdapter根据请求的Handler适配并执行对应的Handler;HandlerAdapter(提取Request 中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程 中,根据配置,Spring将做一些额外的工作:
    1. HttpMessageConveter: 将请求消息(如json、xml等数据)转换成一个对象,将对象转换为指定的响应信息。
    2. 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    3. 数据格式化: 数据格式化。 如将字符串转换成格式化数字或格式化日期等
    4. 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
  6. Handler执行完毕,返回一个ModelAndView(即模型和视图)给HandlerAdaptor
  7. HandlerAdaptor适配器将执行结果ModelAndView返回给前端控制器。
  8. 前端控制器接收到ModelAndView后,请求对应的视图解析器。
  9. 视图解析器解析ModelAndView后返回对应View
  10. 渲染视图并返回渲染后的视图给前端控制器
  11. 最终前端控制器将渲染后的页面响应给用户或客户端
Spring MVC处理请求的流程

如此一来,便可知SpringMVC的架构何如。

地址映射

针对Web项目(无论资源还是servlet)的访问均是通过资源定位,如此一来,拥有对应的地址便是一项大事。传统的servlet是通过@HttpServlet(value="/X")进行调用,在SpringMVC中,将会利用另一个注解来实现其用法同@HttpServlet几乎一致。

@RequestMapping

通过此注解可以将请求地址与方法进行绑定,可以生命在类和方法上。类级别的注解负责将一个特定的请求路径映射到一个控制器(controller)上,将 url 和类绑定;通过方法级别的注解可以细化映射,能够将一个特定的请求路径映射到某个具体的方法上,将 url 和类的方法绑定。

@Controller
@RequestMapping("/urlController")// 声明在类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
public class UrlController {
    @RequestMapping("/test01")  // 映射单个url |* 路径开头是否加 斜杠"/" 均可
    // @RequestMapping(value = {"/test01","Test01"})  // 映射多个url
    public ModelAndView test01(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("hello","test01");
        modelAndView.setViewName("hello");
        return modelAndView;
}  // 此时访问路径为 http://ip:port/springmvc01/urlController/test01

数据响应

已知,MVC思想中负责数据展示的为视图,负责数据存储的为模型。想要看到对应的响应试图则必须先要有一个存数据Model和一个展示数据的View。在@RequestMapping修饰的方法中,SpringMVC默认会将返回的ModelAndView对象解析出来。这个对象中包含了存的数据与View的名称。

 @RequestMapping("/test01")
public ModelAndView test01(){
        ModelAndView modelAndView = new ModelAndView();  // 要用到ModelAndView对象那不得先有一个吗?此处选择new一个。
        modelAndView.addObject("hello","test01");  // 存储数据(Key-Value)
        modelAndView.setViewName("hello");  // 设置视图名称
        return modelAndView;  // 返回出供视图解析器解析
其实,这个ModelAndView对象,你如果需要,框架会主动为我们创建。
public ModelAndView test01( ModelAndView modelAndView ){  // 直接作为参数,框架会主动创建供我们使用
        // 不存数据其实也可以。
        modelAndView.setViewName("hello");  // 设置视图名称
        return modelAndView;  // 返回出供视图解析器解析
当然如果不返回ModelAndView对象也行,可以使用Model对象来单独存储数据。
@RequestMapping("/test01")
public String test01(Model model){
        model.addAttribute("hello","test01");  // 存储数据(Key-Value)
        return "test01";  // 返回的字符串加上前缀后缀会补足为要访问的视图文件路径。这是在配置文件中配置好的。
这么一来,不传递数据时,直接返回视图名字不就行了么?
@RequestMapping("/test01")
public String test01(){
        return "test01";  // 返回的字符串加上前缀后缀会补足为要访问的视图文件路径。
默认的,返回无论是对象还是字符串都是进行视图定位的。可如果我要是想直接在网页上回写信息,并不想跳转视图呢?
请看数据回写!

数据回写(json格式)

所谓数据回写,便是直接将信息打印在基础网页上,并不依赖视图模板进行数据展示。

可是直接返回String还是会被框架当成视图名称去检索,显然服务器会报找不到对应视图的错误,这时便需要用到@ResponseBody注解来向框架表明要返回的其实就是单纯的数据罢了。

@RequestMapping("/test01")
@ResponseBody
public String test01(){
        return "test01";
    }

很多时候我们要在页面上展示很多的信息,若是全部作为字符串返回的话,其繁琐程度可想而知。所以为了能够更加简便的返回或者诸多信息,一般把信息存在类对象中直接返回。

可是,返回的类对象能成功回写在网页上吗?其实是可以的,因为@RequestBody已为我们做了操作,实现了利用HttpMessageConverter将返回对象转化为json格式的操作。Json 在企业开发中已经作为通用的接口参数类型,在页面解析很方便。我们只需要修改相关配置,添加 json 数据支持功能便可享受SpringMVC 对于 json 提供的支持。

@Controller
@RequestMapping("/user")
public class UserController {
-----------------------------------------------
    @RequestMapping("queryUser01")
    @ResponseBody  //注解设置在方法体上
    public User queryUser01(){
        User user = new User();
        user.setId(1);
        user.setUserName("zhangsan");
        user.setUserPwd("123456");
        // 返回的是user对象
        return user;
------------------------------------------------
    @RequestMapping("queryUser02")
    public @ResponseBody User queryUser02(){  // 注解设置在方法返回对象前,修饰符之后
       User user = new User();
        user.setId(1);
        user.setUserName("zhangsan");
        user.setUserPwd("123456");
        // 返回的是user对象
        return user;
-------------------------------------------------
    @RequestMapping("/queryUser03")
    @ResponseBody
    public List<User> queryUser03(){
        List<User> list = new ArrayList<>();
        User user01 = new User();
        user01.setId(1);
        user01.setUserName("zhangsan");
        user01.setUserPwd("123456");
        User user02 = new User();
        user02.setId(2);
        user02.setUserName("lisi");
        user02.setUserPwd("123321");
        list.add(user01);
        list.add(user02);
        // 返回的是user集合
        return list;  //@ResponseBody 返回的是JOSN格式的数据,返回集合
}

数据绑定

客户端请求的参数到控制器功能处理方法上的参数的绑定,对于参数绑定非常灵活。

基本数据/包装类型/字符串类型绑定
* 通过注解 @RequestParam 标记一个形参为请求参数。(注解声明在形参的前面 
* 基本数据类型如果不自行设置是没有默认值会报500异常故需自己设置利用@defaultValue
* 而包装类型参数和字符串类型数据的默认值为null不会报异常
    @RequestMapping("data02")
    public void data02(@RequestParam(defaultValue = "18") int age,
                       @RequestParam(defaultValue = "10.0") double money){
        System.out.println("age:" + age + ", money:" + money);
* 也可以设置参数的别名来接收参数这个别名就是表单提交的时候的name值
    @RequestMapping("data03")
    public void data03(@RequestParam(defaultValue = "18", name = "userAge") int age,
                       @RequestParam(defaultValue = "10.0", name = "userMoney") double money){
        System.out.println("age:" + age + ", money:" + money);
数组类型绑定
这种一般都是多选表单传入的参数比如* 客户端传参形式ids=1&ids=2&ids=3
其实与普通类型无疑仅仅是需要修改形参的类型罢了
    @RequestMapping("/data06")
    public void data06(String[] ids){
        for(String id : ids){
            System.out.println(id + "---");
JavaBean类型绑定
这种情况下要求表单的name与bean对象的属性名保持一致比如表单中一条为
<input type=text name=user.name value=xxx>
则对应的bean对象必有一个属性为name
@RequestMapping("/data07")
public void data07(User user) {
    System.out.println(user);
集合类型绑定
此时 User 实体需要定义对应 list 属性。(对于集合的参数绑定一般需要使用 JavaBean 对象进行包
    public class User {
        private int id;
        private String userName;
        private String userPwd;
        private List<Phone> phones = new ArrayList<Phone>();
    public class Phone {
        private String num;
JSP 页面定义
<form action="data08" method="post">  // // 如果是map:
<input name="phones[0].num" value="123456" />  // // <input name="map['1'].num" value="123456" />
<input name="phones[1].num"value="4576" />  //  // <input name="map['2'].num" value="4576" />
// 其name为接受集合类型参数的关键。
<button type="submit"> 提交</button>
</form>
Controller 方法
    @RequestMapping("/data08")
    public void data08(User user){
        System.out.println(user);

请求转发与重定向

重定向

重定向是发一个302的状态码给浏览器,浏览器自己去请求跳转的网页。地址栏会发生改变。

@RequestMapping(value="/view02")
public String view02(){
    return "redirect:view.jsp";  // 利用字符串:redirect
    return "redirect:view.jsp?uname=zhangsan&upwd=123456";  //  * 传递参数
    return "redirect:view.jsp?uname=张三&upwd=123456";  //  * 传递参数 (传递中文参数会出现乱码)
 * 传递参数 (通过 @RedirectAttributes 对象设置重定向参数,避免中文乱码问题)
@RequestMapping(value="/view04")
public String view04(RedirectAttributes redirectAttributes){
    redirectAttributes.addAttribute("uname","张三");
    redirectAttributes.addAttribute("upwd","123456");
    return "redirect:view.jsp";
也可以直接返回ModelAndView对象
@RequestMapping(value="/view06")
public ModelAndView view06(ModelAndView modelAndView){  //  * 返回 ModelAndView 对象,并添加对象避免乱码。
    modelAndView.addObject("uname","李四");
    modelAndView.addObject("upwd","123321");
    modelAndView.setViewName("redirect:view.jsp");
    // modelAndView.setViewName("redirect:test01");  // 甚至可以重定向至Controller
    return modelAndView;
页面中获取参数值
${param.参数名}

请求转发

请求转发,直接调用跳转的页面,让它返回。对于浏览器来说,它无法感觉服务器有没有forward。 地址栏不发生改变。可以获取请求域中的数据。

@RequestMapping("/view09")
public String view09(){
    return "forward:view.jsp";
    return "forward:view.jsp?uname=张三&upwd=123456";  //  * 设置参数
@RequestMapping("/view10")
public String view10(Model model){  //  * 设置请求域
    model.addAttribute("uname","张三");
    return "forward:view.jsp";
@RequestMapping("/view12")
public ModelAndView view12(ModelAndView modelAndView){
    modelAndView.setViewName("forward:test01");  //  * 请求转发到 Controller
    modelAndView.setViewName("forward:test01?uname=admin");  // 传递参数
    return modelAndView;
}

拦截器

SpringMVC 中的 Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆等操作。

对于 SpringMVC 拦截器的定义方式有两种:

  • 实现接口:org.springframework.web.servlet.HandlerInterceptor
  • 继承适配器:org.springframework.web.servlet.handler.HandlerInterceptorAdapter

除了继承或者实现的接口和类的不同,内部的功能实现其实是一样的。

  1. preHandler:在handler执行之前执行的方法,是最为重要的拦截方法,一般的权限控制,准备工作都是在这一步完成的。
  2. postHandler:是在视图展示之前执行的方法。
  3. afterCompletion:目标handler执行完毕且视图生成之后执行的方法,一般用于收尾工作。

这三个方法就是继承类和实现接口后要实现的方法。

全局异常处理

在 JavaEE 项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,则系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 好在SpringMVC 对于异常处理这块提供了支持,通过 SpringMVC 提供的全局异常处理机制,能够将所有 类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统 一处理和维护。

全局异常实现方式 SpringMVC 处理异常有 3 种方式:

  1. 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver
  2. 实现 Spring 的异常处理接口 HandlerExceptionResolver 自定义自己的异常处理器
  3. 使用 @ExceptionHandler 注解实现异常处理

简单异常处理器配置

<!-- 配置全局异常统一处理的 Bean (简单异常处理器) -->
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 页面在转发时出现异常,设置默认的错误页面 (error代表的是一个视图) -->
<property name="defaultErrorView" value="error"></property>
<!-- 异常发生时,设置异常的变量名 -->
<property name="exceptionAttribute" value="ex"></property>
</bean>

实现接口自定义异常处理器

使用实现 HandlerExceptionResolver 接口的异常处理器进行异常处理,具有集成简单、有良好的扩 展性、对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提供更 详细的异常处理信息。

@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {  // 实现接口
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView("error");  // 指定视图名
        mv.addObject("ex","默认错误信息");  // 添加错误信息
        // 判断是否是自定义异常
        if (ex instanceof ParamsException) {  // 自定义异常判断类型
        mv.setViewName("params_error");
        ParamsException e = (ParamsException) ex;
        mv.addObject("ex", e.getMsg());
        return mv;
} 

利用注解实现全局异常处理

使用 @ExceptionHandler 注解实现异常处理,具有集成简单、有扩展性好(只需要将要异常处理的 Controller 类继承于 BaseController 即可)、不需要附加Spring 配置等优点,但该方法对已有代码存 在入侵性(需要修改已有代码,使相关类继承于 BaseController),在异常处理时不能获取除异常以外的 数据。

public class BaseController {
    @ExceptionHandler
    public String exc(HttpServletRequest request, HttpServletResponse
            response, Exception ex){
        request.setAttribute("ex", ex);
        if(ex instanceof ParamsException){
            return "error_param";
        if(ex instanceof BusinessException){
            return "error_business";
        return "error";
}

未捕获异常的处理

对于 Unchecked Exception 而言,由于代码不强制捕获,往往被忽略,如果运行期产生了 Unchecked Exception,而代码中又没有进行相应的捕获和处理,则我们可能不得不面对尴尬的 404、 500……等服务器内部错误提示页面。

此时需要一个全面而有效的异常处理机制。目前大多数服务器也都支持在 web.xml 中通过 (Websphere/Weblogic)或者(Tomcat)节点配置特定异常情况的显示页面。修改 web.xml 文件,增加以 下内容:

<!-- 出错页面定义 -->
<error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/500.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>