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对象的各属性上。