Spring MVC

0. 关于spring mvc的配置,封装了spring-mvc-conf

依靠spring-mvc-conf可以快速基于spring mvc搭建一个web框架,这种想法和做法有些类似于spring boot,解决的是spring配置比较繁杂的问题。

spring mvc的拦截器、异常处理、输入输出参数处理是最常用的功能。我对其进行了好些定制,基本上后续也不需要怎么修改了。详见:spring-mvc-conf说明

下面介绍部分是spring mvc 3的时候的了,可能有些陈旧了。

1. 简介

Spring MVC是一个和Spring完美结合的Web框架。我先前学过Struts2,再学Spring MVC,觉得Spring MVC更为简约、高效和优雅,它有不少东西借鉴自Struts2,如依赖注入等。它功能完整,推荐在新项目中替代Struts2,至于Struts,直接秒杀。

下面的配置均采用Spring 3版本,Spring MVC基于Java Servlet技术规范,主要处理流程图:

1.来自浏览器的请求URL由DispatcherServlet统一处理(除了css、js、图片等静态文件),它符合Java Servlet规范。在web.xml文件中配置哪些URL由该Servlet处理。

2.DispatcherServlet根据配置(通常由注解或xml文件配置)将URL请求发往相应的Controller处理,这个过程会带上服务器端传递过来的参数并自动装载给Controller。

3.Controller接收到参数后,调用相应的方法处理之,方法裡可以调用业务层接口或数据层接口等,然后将处理结果打包到一个ModelAndView对象中并返回。

4.DispatcherServlet将ModelAndView对象发往视图解析器viewResolver处理。

5.视图解析器根据ModelAndView对象中指定的视图模板位置和携带的数据渲染模板文件为html文件,返回渲染后的文件。

6.DispatcherServlet将html文件(有时候是纯文本或多媒体文件)返回给浏览器。

很好的教程:点击

2. 安装及配置文件

建议使用eclipse或netbeans编写程式,只需要导入Spring和Spring Web的所有jar包即可。

按照习惯,Spring的xml配置文件统一放在classpath根目录下并遵循applicationContext-*.xml的命名规范;web.xml配置css、js、图片文件为静态文件,靠后缀识别,其它URL由DispatcherServlet处理。web.xml配置文件如下。

  • Tomcat, Jetty, JBoss, and GlassFish 默认Servlet的名字 – “default”
  • Google App Engine 默认Servlet的名字 – “ahdefault”
  • Resin 默认Servlet的名字 – “resin-file”
  • WebLogic 默认Servlet的名字 – “FileServlet”
  • WebSphere 默认Servlet的名字 – “SimpleFileServlet”

3. 编写Controller之Hello World

3.1 编写Controller

Controller可以是任意一个普通类,不需要继承或实现任何东西。Controller类必须放在配置文件applicationContext-web.xml中指定的注解包下,如springmvc.web,应根据实际情况修改。HelloWorldController.java

package springmvc.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

// 通过注解使得该类变成Controller类
@Controller
// 根目录,默认是"/",指定后为"/hello"
@RequestMapping("hello")
public class HelloWorldController {

    // 该方法对应的URL,即访问"/hello/hello_world"将调用该方法
    @RequestMapping("hello_world")
    public ModelAndView handleRequest() {

        // modelAndView的名称将会结合viewResolver配置的prefix和suffix合成一个页面的路径
        ModelAndView modelAndView = new ModelAndView("hello_world");
        // modelAndView对象携带的信息
        modelAndView.addObject("message", "hello_world MVC!");
        return modelAndView;
    }

        // 一个类中可以写很多这样的方法,名称自取,使用注解映射到不同的URL

        // 如果没有附带数据,也可以直接返回String类型的名称
}

4. 获得来自浏览器的参数及环境变量

4.1 GET或POST传递的参数

Spring MVC採用控制反转的思想进行依赖注入,假设HTML页面需要传递参数name和age,则HTML页面有个form:

<form action="/some/url">
Name: <input type="text" name="name"/><br />
Age:  <input type="text" name="age"/><br />
</form>

这种提交形式是POST,也可以等同地使用GET方式提交:/some/url?name=pugwoo&age=24

然后Controller中对应的方法只需要在参数上加上对应名称和类型的参数:

    @RequestMapping("/some/url")
    public ModelAndView handleRequest(String name, Integer age) {
        // 此时name和age已经拥有服务器端传过来的值
        // 类型也会自动转换,比如从String转成Integer

        // 接下来,做你想做的事情
    }

如果想指定採用GET或POST方式,在@RequestMapping中指定method即可:

@RequestMapping(value = "/url", method = RequestMethod.GET)
或
@RequestMapping(value = "/url", method = RequestMethod.POST)

注:我不喜欢Spring 2.0使用Spring MVC标签来写HTML页面的方式。虽然它提供了参数验证功能,但这个由自己写的js来做更好,这样有利于开发的解藕。

类Struts2的OGNL传参形式

当页面的变量很多时,可以採用OGNL方式传递变量,加入有类Car,Car有属性String name和String model并提供对应的getter和setter方法,则HTML的<input>名称可以写为:”name”和”model”,然后在Controller的对应方法的参数中只写Car car,Spring MVC会将该对象及其值构建好。

注意:<input>的name属性不是填“car.name”,而是“name”。

Date类型的参数传递问题

页面传递过来的数据均是String类型,尔后Spring将其转化成对应类型。有些类型不是Spring默认可以转换的,如Date。只需要在Controller.java代码中加入一个binder方法即可:

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
    }

其中日期格式按照实际需要进行设置,如“yyyy-MM-dd HH:mm:ss”等。

4.2 获得Cookie的值

在Controller对应的方法的参数使用@CookieValue注解获得:

    @RequestMapping("/some/url")
    public ModelAndView handleRequest(@CookieValue("name") String name) {
        // 接下来,做你想做的事情
    }

4.3 上传文件

首先是HTML上传页面的form表格:

      <form action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="myfile" />
        <input type="submit" />
      </form>

然后编写Controller,注意一定要加上@RequestParam,原因未明, UploadController.java:

package springmvc.web;

import java.io.IOException;

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 org.springframework.web.servlet.ModelAndView;

@Controller
public class UploadController {

    @RequestMapping("upload")
    // 一定要加上@RequestParam
    public ModelAndView uploadFile(@RequestParam MultipartFile myfile) throws IOException{
        ModelAndView modelAndView = new ModelAndView("result");

        // 显示文件内容
        StringBuilder sb = new StringBuilder();

    // 判断文件是否已上传
    if (!myfile.isEmpty()) {
        sb.append("文件名称: " + myfile.getOriginalFilename() + "<br/>");

        String content = new String(myfile.getBytes());
        content = content.replace("\n", "<br/>");
        sb.append(content);
    } else {
        sb.append("没有选择文件");
    }

        modelAndView.addObject("result", sb.toString());
        return modelAndView;
    }
}

然后新建一个/WEB-INF/result.jsp文件:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
  <body>
    <p>上传的文件信息:</p>
    <p>${result}</p>
  </body>
</html>

如果出现java.lang.NoClassDefFoundError: org/apache/commons/io/output/DeferredFileOutputStream异常,加上org.apache.commons.io包即可。

4.4 获得环境变量

标准的Servlet规范中,有HttpServletRequest和HttpServletResponse等环节变量(关于这些变量的用途,请参见标准Servlet编程),Spring MVC可以很轻鬆地获得,只需要像上面获得参数一样在方法参数中写即可:

    @RequestMapping("/some/url")
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) {

        // 你已获得HttpServletRequest和HttpServletResponse对象,比Struts2实现***Aware接口还简单

        // 接下来,做你想做的事情
    }

5. REST形式的URL

REST形式的URL对搜索引擎友好,看起来也很简洁漂亮。Spring MVC实现REST形式URL很简单,例子:

    /**
     * 在RequestMapping中,变量用{}括起来
     * 然后在处理方法中用@PathVariable获取,名称要一致
     * @PathVariable 里面也可以加上value="xxx",名称可以不一致
     */
    @RequestMapping("/{name}/page/{p}")
    public void handleRequest(Writer writer, @PathVariable String name,
            @PathVariable Integer p) throws IOException{
        writer.write("you are reading " + name +"'s page " + p);
    }

URL中的数值会被作为变量传入到方法中,如name和p,同样是依赖注入的思想,类型会自动转换,访问下面的URL查看效果:

6. 其它技巧

输出纯文本

例子,支持中文:

    @RequestMapping("test")
    public void test(HttpServletResponse resp) throws IOException {
        resp.setContentType("text/plain;charset=utf-8");
        resp.getWriter().write("Test 测试中文");
    }

该方法适合做cgi接口,输出json等格式的数据。

客户端跳转

设定Controller对应的方法返回名称“redirect:hello_world”即可跳转到对应的页面,如hello_world。浏览器的地址栏会变化。

服务器端跳转

设定Controller对应的方法返回名称“forward:hello_world”即可跳转到对应的页面,如hello_world。

forward并不能将变量通过modelAndView传递给新的URL,只能通过将变量放在request或session里面来完成。而对于redirect,变量只能放在session里面,不能放在request里。

jsp页面标籤jstl

导入标籤:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

直接获取某个变量的值:${message} 或 ${car.name} 等

循环遍历容器,假如有类Car,Car有属性name并且提供getter方法,对象数组ArrayList cars,则java语句:

for(Car car : cars) { 
  System.out.println(car.getName());
}

等同于

    <c:forEach items="${cars}" var="car">
        ${car.name} <br />
    </c:forEach>

乱码问题

上面的web.xml中已经解决了POST数据的乱码问题,但是GET方式传递数据仍有乱码,这是URL编码的问题,需要在服务器容器端设置,以Tomcat为例,修改conf/server.xml文件对应位置新增“URIEncoding”:

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

关于spring redirect注意事项

在spring通过redirect发出302重定向时,浏览器并不会把Referer设置为302定向的页面。Referer的值是空的,要注意。

文档更新时间: 2018-11-10 22:56   作者:nick