I am not a creator. I am just a porter.

简介:Spring MVC属于SpringFrameWork的后续产品,提供了构建Web应用程序的全功能MVC模块。Spring MVC框架并不知道使用的视图,它包含有多种视图技术,例如:JavaServer Pages技术、Velocity技术、Tiles、iText、POI和freemaker模板。Spring MVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。---来自百科

我们这里主要介绍Spring MVC的Controller和我们常用的模板技术freemaker,对于持久层等和SSH类似的在此略过。

Spring MVC3中的Controller####

在谈Controller前我们先谈谈Spring MVC3的配置。这里有一个简单的web.xml配置文件。

<web-app>
<display-name>cloud-web</display-name>

<!-- Spring 监听器 配置 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

<!-- 字符集 过滤器 -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置一个Servlet用用于拦截对应的请求 -->
<servlet>
    <servlet-name>device</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <!-- 对应的Servlet的配置文件 -->
        <param-value>/WEB-INF/device-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>device</servlet-name>
    <!-- 表示拦截所有的请求,注意静态资源也会被拦截,因此在Servlet的配置文件里面要配置静态资源文件的mapping -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <!-- 表示加载Spring的配置文件 -->
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
</web-app>

在web.xml中我们配置了一个叫device的Servlet用于拦截所有的请求,这是一个DispatcherServlet的分发器,可以定义多个。现在来看看我们的Servlet的配置文件。

<mvc:default-servlet-handler/>
<!-- 主要作用于@Controller,激活该模式,也可以手动配置-->
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- 自动扫描@Controller的包名,多个用逗号分开 -->
<context:component-scan base-package="your.controller.package"/>
<!-- 一般的视图解释类 -->
<bean id="viewResolver"
      class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/jsp/</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
</bean>
<!-- freemaker的视图解释类 -->
<bean id="freeMarkerViewResolver"
      class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="viewClass"
              value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
    <property name="suffix">
        <value>.ftl</value>
    </property>
    <property name="order" value="1"/>
    <property name="contentType" value="text/html;charset=utf-8"></property>
</bean>

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath">
        <!-- 这里写模板的加载路径,不要把这个路径配置到freeMarkerViewResolver中前缀(prefix)属性中 -->
        <value>/WEB-INF/ftl/</value>
    </property>
     <property name="freemarkerSettings"><!-- 设置FreeMarker环境属性 -->
        <props>
            <prop key="template_update_delay">5</prop><!--刷新模板的周期,单位为秒 -->
            <prop key="default_encoding">utf-8</prop><!--模板的编码格式 -->
            <prop key="locale">zh_CN</prop><!-- 本地化设置 -->
            <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
            <prop key="time_format">HH:mm:ss</prop>
            <prop key="number_format">0.####</prop>
            <prop key="boolean_format">true,false</prop>
            <prop key="whitespace_stripping">true</prop>
            <prop key="tag_syntax">auto_detect</prop>
            <prop key="url_escaping_charset">utf-8</prop>
        </props>
    </property>
</bean>
<!-- 对静态资源的访问 -->
<mvc:resources mapping="/images/**" location="/resources/images/"/>
<mvc:resources mapping="/js/**" location="/resources/js/"/>
<mvc:resources mapping="/css/**" location="/resources/css/"/>

定义好了一个Servlet后,我们来看看Controller类是如何写的,以Login为例

@Controller
@RequestMapping(value = "/account")
@SessionAttributes(types = {String.class}, value = {"username"})
class LoginController {
    private LoginService loginService;
    /**
     * Autowired注解表示依赖自动注入,当这个类初始化的时候会在Spring的bean中去找
     **/
    @Autowired
    public LoginController(LoginService loginService) {
        this.loginService = loginService;
    }
    /**
     * 请求的url为/account/login,type为GET,必须包含两个参数username和password
     * 请求格式可以是/account/login?username=xxx&password=xxx
     **/
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(@RequestParam String username, @RequestParam String password, HttpServletResponse response, ModelMap modelMap) {
        try {
            String token = loginService.login(username, password);
            response.addCookie(new Cookie("token", token));
            modelMap.addAttribute("username", username);
            return "login";
        } catch (LoginError le) {
            return "forward:/error/" + le.message();
        }
    }
    /**
     * 
     **/
    @RequestMapping(value = "/loginByJson", method = RequestMethod.POST)
    @ResponseBody
    public String loginByJson(@RequestBody String params, @CookieValue(value = "token", required = false) String token) {
        return "Json String"
    }
}

这是一个简单的LoginController类,简单的演示注解的使用。

注意:当配置多个viewResolver的时候由于默认的InternalResourceViewResolver中checkResource是一直返回true所以,只会搜索order最高的那一级的view file。这里有一个方法就是继承JstlView改变checkResource的行为:

public class IcomJstlView extends JstlView {

    public boolean checkResource(Locale locale) throws Exception {
        File file = new File(this.getServletContext().getRealPath("/")+getUrl());
        return file.exists();
    }
}

对应的配置修改:

<bean id="jspViewResolver"
             class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/jsp/</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
    <property name="viewClass" value="com.tplink.cloud.web.IcomJstlView"/>
    <property name="order" value="1"/>
</bean>

记住这里的order等级要高(值要小)

Spring MVC3的注解归纳####

• component

• @Controller
• @Service
• @Repository
• @Component
• request

• @RequestMapping
• @RequestParam
• @RequestBody
• @RequestPart
• @RequestHeader
• @PathVarible
• response

• @ResponseBody
• injection

• @Resource
• @Autowired
• transaction

• @Transaction
• cookie

• @CookieValue
• session

• @SessionAttributes
• model

• @ModelValue
Spring MVC的数据绑定#####

@RequestMapping,是用来绑定请求路径的,可以用在方法上,也可以用在路径上。

- value : default,请求路径
- method : GET或者POST
- headers : 指定请求头

@PathVariable,是用来绑定URI模板变量值。

用来处理绑定在url上的参数

@RequestParam,是用来绑定单个请求参数值。

- value : 绑定参数对应的名字
- required : 表示参数是否是必须的

A) 常用来处理简单类型的绑定,通过Request.getParameter() 获取的String可直接转换为简单类型的情况( String-->简单类型的转换操作由ConversionService配置的转换器来完成);因为使用request.getParameter()方式获取参数,所以可以处理get 方式中queryString的值,也可以处理post方式中 body data的值

B)用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容,提交方式GET、POST

C) 该注解有两个属性: value、required; value用来指定要传入值的id名称,required用来指示参数是否必须绑定

@RequestBody,是用来绑定请求的内容区数据并能进行自动类型转换等。

该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等;

它是通过使用HandlerAdapter 配置的HttpMessageConverters来解析post data body,然后绑定到相应的bean上的。因为配置有FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded的内容,处理完的结果放在一个MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter api

@CookieValue,是用来绑定Cookie数据值。

- value : 绑定参数对应的名字
- required : 表示参数是否是必须的

@ModelAttribute

该注解有两个用法,一个是用于方法上,一个是用于参数上;
用于方法上时: 通常用来在处理@RequestMapping之前,为请求绑定需要从后台查询的model;
用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数bean上;要绑定的值来源于:
A) @SessionAttributes 启用的attribute 对象上;
B) @ModelAttribute 用于方法上时指定的model对象;
C) 上述两种情况都没有时,new一个需要绑定的bean对象,然后把request中按名称对应的方式把值绑定到bean中。

最基本的,返回一个视图:

@RequestMapping(value = "/view")  
public String simpleView(){  
    System.out.println("return a view");  
    return "viewfile";  
}

则访问http://localhost/xxxx/view的时候,会返回viewfile视图

参数绑定

@RequestMapping(value = "/view")  
public String getView(  
  @RequestParam("viewname") String viewname){  
    System.out.println("get view with viewname: " + viewname);  
    return "viewname";  
  
}

形如这样的访问形式:/view?viewname=hello就可以触发访问getView方法了

REST[1]风格的参数

@RequestMapping(value = "/view/{viewId}")  
public String getView(@PathVariable String viewId){  
    System.out.println("get view with ID: " + viewId);  
    return "view-" + viewId;  
  
}

形如REST风格的地址访问,比如:/view/23,其中用(@PathVariable接收rest风格的参数

REST风格的参数绑定形式之2

@RequestMapping(value = "/view/{viewId}")  
public String getView(  
  @PathVariable("viewId") String id){  
    System.out.println("get view with ID: " + id);  
    return "view-" + id;  
  
}

这个有点不同,就是接收形如/view/23的URL访问,把23作为传入的viewId,,但是在实际的方法getView中,使用@PathVariable("viewId") String id,将其绑定为id,所以这里id为23

url中同时绑定多个id

@RequestMapping(value = "/view/{viewname}/value/{value}")  
public String getView(  
  @PathVariable String viewname,  
  @PathVariable String value,
  ModelMap modelMap){
    modelMap.addAttribute('value', value);
    return "viewname";  
}

这个其实也比较好理解了。

支持正则表达式

@RequestMapping(value = "/{textualPart:[a-z-]+}.{numericPart:[\\d]+}")  
public String regularExpression(  
  @PathVariable String textualPart,  
  @PathVariable String numericPart){  
    System.out.println("Textual part: " + textualPart +   
      ", numeric part: " + numericPart);  
    return "view";  
}

对于ajax的form的请求

@RequestMapping(value = "/json", RequestMethod.POST)
@ResponseBody
public String getJson(@RequestParam String requestParam,
                      @RequestParam String requestData) {
    System.out.println("param: " + requestParam);
    System.out.println("data: " + requestData);
    JSONObejct result = new JSONObject().put("success", true);
    return result.toString();
}

Ajax请求如下:

$.ajax {
    url : rootPath + "/json",
    type: 'POST',
    //请求参数是form封装的
    contentType: "application/x-www-form-urlencoded",
    data : {
        requestParam : "xxxxx",
        requestData : "xxxxx"
    },
    dataType: 'json',//返回时json,自动转换
    success : function(data) {
        if (data.success == true) {
            //todo
        }
    }
}

对于ajax的payload请求

@RequestMapping(value = "/json", RequestMethod.POST)
@ResponseBody
public String getJson(@RequestBody String json) {
    JSONObject params = new JSONObject(json);
    System.out.println("param: " + params.getString("requestParam"));
    System.out.println("data: " + params.getString("requestData"));
    JSONObejct result = new JSONObject().put("success", true);
    return result.toString();
}

Ajax请求如下:

$.ajax {
    url : rootPath + "/json",
    type: 'POST',
    //请求参数是form封装的
    contentType: "application/json",
    data : JSON.stringfly({
        requestParam : "xxxxx",
        requestData : "xxxxx"
    }),
    dataType: 'json',//返回时json,自动转换
    success : function(data) {
        if (data.success == true) {
            //todo
        }
    }
}

绑定Model

@ModelAttribute  
public Account addAccount(@RequestParam String number) {  

    return accountManager.findAccount(number);  
}

这种方式实际的效果就是在调用@RequestMapping的方法之前,为request对象的model里put(“account”, Account);

@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)  
public String processSubmit(@ModelAttribute Pet pet) {  
    //todo 
}

首先查询 @SessionAttributes有无绑定的Pet对象,若没有则查询@ModelAttribute方法层面上是否绑定了Pet对象,若没有则将URI template中的值按对应的名称绑定到Pet对象的各属性上。