SpringMVC
ViewResolver视图解析将逻辑视图名转换成物理视图;再调用render方法渲染视图返回给用户。
- 可以将任何一个java类当做控制器,只要标注@Controller
- 类里面返回值为String,表示返回的逻辑视图名,此时需要一个视图解析器来进行解析,需要在配置文件里配置该bean(InternalResourceViewResolver)
- DispacherServlet是核心控制器,用于处理请求,要在web.xml里面进行配置。
Spring MVC 标签
@RequestMapping
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
RequestMapping注解有六个属性,下面我们把她分成三类进行说明。
value, method;
value: 指定请求的实际地址,指定的地址可以是URI Template 模式;
method: 指定请求的method类型, GET、POST、PUT、DELETE等;consumes,produces
consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;params,headers
params: 指定request中必须包含某些参数值时,才让该方法处理。
headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求。
可以支持简单的操作,一定要有某属性或者没有某属性才能访问该函数。
@SessionAttributes
让同一个会话不同请求间可以共享数据。
@SessionAttributes即将值放到session作用域中,写在class上面。
此处的值必须是在模型数据里面,即request里面有的。
此外,可以通过模型里面的key值或者类型值将对象加到session中。用类型值的时候,比如types=[String.class],那么模型里面所有的String类型的数据都会保存到session中。
@ModelAttribute
主要用于对象合并,先从数据库里面取一遍(如修改时,把那些不能从表单中得到的信息从数据库拿出来),再进行绑定。
此处,注册时间无法修改,也无法通过前端获得,后端在用Hibernate时,更新那个user会把注册时间变成NULL,不合理。
@ModelAttribute可以修饰方法,使得他在所有业务方法之前执行,这样我们可以:
1. 先在那个方法里面获得对应的user,
2. 放到请求范围中
- 返回值为void,用map/model/modelmap手动放;
- 返回值为对应的类型(此时自动放到请求范围中,key值为类型名首字母小写,或者通过ModelAttribute的value属性设置)
3. 再将前端获得的数据“覆盖”原来的数据,而那个注册时间本来就有就不会覆盖了。
这里需要说明一下入参的绑定规则
入参绑定规则
首先在请求范围中查找指定key(POJO对应的类名首字母小写单词),如果是用ModelAttrubute修饰的入参,则按照指定key进行绑定。没找到就新建一个类放到请求范围中。
当@ModelAttribute修饰方法参数时,方法参数的实例来自于:
- 它可能已经存在与模型中了,因为使用了@SessionAttributes — 见“使用@SessionAttributes存储模型属性到 HTTP 会话中”一节.
- 它可能已经存在于模型中了,因为同一个控制器中的@ModelAttribute方法,就像上一节中解释的那样。
- 它可能是从URI模板变量和类型转换器中获取的(下面会详细解释)。
- 它可能是使用默认构造器初始化的。
@PathVariable
用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。
@requestParam
获取get 方式中queryString的值,也可以处理post方式中 body data的值。
@requestParam主要用于在SpringMVC后台控制层获取参数,类似一种是request.getParameter(“name”),它有三个常用参数:defaultValue = “0”, required = false, value = “isApp”;defaultValue 表示设置默认值,required 通过boolean设置是否是必须要传入的参数,value 值表示接受的传入的参数类型。
@ResponseBody
作用: 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。通常用来返回JSON数据或者是XML数据,需要注意的呢,在使用此注解之后不会再走试图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。
更多参考:
https://blog.csdn.net/lovincc/article/details/72800117
https://www.cnblogs.com/leskang/p/5445698.html
ModelAndView/Model/ModelMap/Map
ModelAndView需要手动new,将待返回页面需要的信息通过addObject放进mv里面,再return mv。
addObject加进去的模型数据是放在request范围中的。
Model 、ModelMap 、Map都不用new,直接在方法参数里面就行,Spring MVC会自动将已经创建好的模型引用赋给他们。然后方法里面直接addAttribute进需要的信息,也不需要去return model,直接在页面通过EL表达式就可以获取,因为是引用,函数里面的所有操作都是全局性的,实际request范围里面也会增加。
此外,这三者本质上一样,只是使用的API不同,一般用Map就行:
public ModelAndView returnJson(Model model, Map map, ModelMap modelmap) {...断点...}
Ajax交互
如果AJAX发送数组,而入参是List,则不支持直接绑定了,需要用到@RequestBody,需要在传到后端时指定数据类型是json字符串。
若干示例
package com.springmvc.controller;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.springmvc.entity.Address;
import com.springmvc.entity.UserInfo;
import com.springmvc.entity.UserInfoMap;
@SessionAttributes(value={"userInfo"})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCHandler {
@RequestMapping("/helloWorld")
public String sayHello() {
System.out.println("Hello World");
return "success";
}
/**
* 使用@RequestMapping注解的method属性指定请求方式
*/
@RequestMapping(value = "/requestMethod", method = RequestMethod.POST)
public String requestMethod() {
System.out.println("Request Method");
return "success";
}
/**
* Ant风格的URL路径映射
*/
@RequestMapping("/*/pathAnt")
public String pathAnt() {
System.out.println("Path Ant");
return "success";
}
/**
* 使用@PathVariable注解映射URL中的占位符到目标方法的参数中
*
*/
@RequestMapping("/pathVariable/{id}")
public String pathVariable(@PathVariable("id") Integer id) {
System.out.println("Path Variable:" + id);
return "success";
}
/**
* 使用@RequestParam注解绑定请求参数到控制器方法参数,与前端中name属性的值对应,
* 如果形参名字和一致请求参数名字一样,就不用标注@resquestParam
*/
@RequestMapping("/requestParam")
public String requestParam(
@RequestParam(value = "userName") String userName,
@RequestParam(value = "password") String password) {
System.out.println("Request Param:" + userName + " " + password);
return "success";
}
/**
* 将请求参数绑定到控制器方法的表单对象POJO,要名字一样才能绑定,可以支持级联属性,如UserInfo多了一个属性为address(类,包含city和street),则在前端表单中street需指定name属性为"address.street"
*/
@RequestMapping("/saveUserInfo")
public String saveUserInfo(UserInfo ui) {
System.out.println(ui);
return "success";
}
/**
* 将请求参数绑定到控制器方法的Map对象
*/
@RequestMapping("/getUserInfos")
public String getUserInfos(UserInfoMap uiMap) {
Set set=uiMap.getUiMap().keySet();
Iterator iterator=set.iterator();
while(iterator.hasNext()){
Object keyName=iterator.next();
UserInfo ui=uiMap.getUiMap().get(keyName);
System.out.println(ui);
}
return "success";
}
/**
* 控制器的返回类型:ModelAndView,model信息将被放到request域中
*/
@RequestMapping("/returnModelAndView")
public ModelAndView returnModelAndView() {
String viewName="success";
ModelAndView mv=new ModelAndView(viewName);
UserInfo ui=new UserInfo("zhangsan", "123456", "swimming", new Address("jiangsu", "nanjing"));
mv.addObject("ui", ui);
return mv;
}
/**
* 保存模型属性到HttpSession
*/
@RequestMapping("/sessionAttributes")
public String sessionAttributes(ModelMap model) {
UserInfo userInfo=new UserInfo("zhangsan", "123456", "swimming", new Address("jiangsu", "nanjing"));
model.put("userInfo", userInfo);
return "success";
}
/*
*
* @ModelAttribute的方法会在所有控制器前执行
*
*/
@ModelAttribute
public void getUserInfo(Map<String,Object> map, String str){
Address address=new Address("JiangSu","SuZhou");
UserInfo userInfo=new UserInfo("lisi", "123456", "swimming", address);
map.put("userInfo", userInfo);
str = "tes2t";
//map.put("str", str);
System.out.println("ModelAttribute called");
}
/*
* 测试modelAttribute
*/
@RequestMapping("/modelAttribute")
public String modelAttribute(UserInfo userInfo){
System.out.println(userInfo);
return "success";
}
/**
* 使用自定义视图
* @return
*/
@RequestMapping("/beanNameViewResolver")
public String beanNameViewResolver(){
return "myView";
}
/**
* 使用重定向
* @return
*/
@RequestMapping("/redirect")
public String redirect(){
return "redirect:/index.jsp";
}
/*
* WebDataBinder一开始就在request范围中,它对数据进行绑定,包括格式转换/校验。
* 使用@InitBinder注解进行类型转换,可以设置在底层绑定时进行的一些操作,如设置某些属性不进行绑定/设置转换格式等。
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
/**
* 测试类型转换
* @param ui
*/
@RequestMapping("/testInitBinder")
public String testInitBinder(UserInfo ui) {
System.out.println(ui.getRegDate());
return "success";
}
/**
* 表单数据校验
*/
@RequestMapping("/testValidate")
public String testValidate(@Valid UserInfo ui,BindingResult result) {
if(result.getErrorCount()>0){
for(FieldError error :result.getFieldErrors()){
System.out.println(error.getField()+":"+error.getDefaultMessage());
}
}
return "success";
}
/**
* 返回JSON格式数据,用了@ResponseBody会将返回值变成Json格式。
*/
@ResponseBody
@RequestMapping("/returnJson")
public Collection<UserInfo> returnJson() {
Map<Integer, UserInfo> ui =new HashMap<Integer, UserInfo>();
ui.put(1, new UserInfo("zhangsan","123456","swimming",new Address("Jiangsu","NanJing")));
ui.put(2, new UserInfo("lisi","123456","running",new Address("Jiangsu","YangZhou")));
ui.put(3, new UserInfo("wangwu","123456","reading",new Address("Jiangsu","SuZhou")));
return ui.values();
}
/**
* REST风格:处理GET方式请求
*/
@RequestMapping(value = "/rest/{id}", method = RequestMethod.GET)
public String restGET(@PathVariable("id") Integer id) {
System.out.println("Rest GET:" + id);
return "success";
}
/**
* REST风格:处理POST方式请求
*/
@RequestMapping(value = "/rest", method = RequestMethod.POST)
public String restPOST() {
System.out.println("Rest POST");
return "success";
}
/**
* REST风格:处理DELETE方式请求,返回重定向的URI
*/
@RequestMapping(value = "/rest/{id}", method = RequestMethod.DELETE)
public String restDELETE(@PathVariable("id") Integer id) {
System.out.println("Rest DELETE:" + id);
return "redirect:/springmvc/doTransfer";
}
/**
* REST风格:处理PUT方式请求
*/
@RequestMapping(value = "/rest/{id}", method = RequestMethod.PUT)
public String restPUT(@PathVariable("id") Integer id) {
System.out.println("Rest PUT:" + id);
return "redirect:/springmvc/doTransfer";
}
@RequestMapping("/doTransfer")
public String doTransfer() {
return "success";
}
/*
* 文件上传
*/
@RequestMapping(value = "/upload")
public String upload(@RequestParam(value="file", required=false) MultipartFile file, HttpServletRequest request, ModelMap model) {
//服务器端upload文件夹物理路径
String path = request.getSession().getServletContext().getRealPath("upload");
//获取文件名
String fileName = file.getOriginalFilename();
//实例化一个File对象,表示目标文件(含物理路径)
File targetFile = new File(path, fileName);
if(!targetFile.exists()){
targetFile.mkdirs();
}
try {
//将上传文件写到服务器上指定的文件
file.transferTo(targetFile);
} catch (Exception e) {
e.printStackTrace();
}
model.put("fileUrl", request.getContextPath()+"/upload/"+fileName);
return "success";
}
/**
* 处理国际化
*/
//注入ResourceBundleMessageSource的Bean实例
@Autowired
private ResourceBundleMessageSource messageSource;
@RequestMapping(value = "/localeChange")
public String localeChange(Locale locale){
String user = messageSource.getMessage("username", null, locale);
System.out.println("国际化资源文件Locale配置(username):"+user);
return "login";
}
}
UserInfo
package com.springmvc.entity;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
public class UserInfo {
@NotEmpty
@Size(min=6,max=20)
private String userName;
private String password;
private String favorate;
private Address address;
@Email
@NotEmpty
private String email;
@Range(min=18, max=45)
@NotNull
private Integer age;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
//@DateTimeFormat(pattern="yyyy-MM-dd")
private Date regDate;
public Date getRegDate() {
return regDate;
}
public void setRegDate(Date regDate) {
this.regDate = regDate;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFavorate() {
return favorate;
}
public void setFavorate(String favorate) {
this.favorate = favorate;
}
public UserInfo() {
}
public UserInfo(String userName, String password, String favorate,
Address address) {
super();
this.userName = userName;
this.password = password;
this.favorate = favorate;
this.address = address;
}
@Override
public String toString() {
return "UserInfo [userName=" + userName + ", password=" + password
+ ", favorate=" + favorate + ", address=" + address + "]";
}
}
Address
package com.springmvc.entity;
public class Address {
private String province;
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Address(String province, String city) {
super();
this.province = province;
this.city = city;
}
public Address() {
super();
}
@Override
public String toString() {
return "Address [province=" + province + ", city=" + city + "]";
}
}
MyView
package com.springmvc.view;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.View;
@Component
public class MyView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> arg0, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.getWriter().println("hello,this is my view");
}
}
index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
<script type="text/javascript" src="scripts/jquery.min.js"></script>
<script type="text/javascript">
function getUserInfoJson() {
var url = "springmvc/returnJson";
var args = {};
$.post(url, args, function(data) {
});
}
</script>
</head>
<body>
<a href="springmvc/helloWorld">Hello World</a>
<br>
<br>
<a href="springmvc/requestMethod">Request Method</a>
<br>
<br>
<form action="springmvc/requestMethod" method="post">
<input type="submit" value="Request Method">
</form>
<br>
<br>
<a href="springmvc/my/pathAnt">Path Ant</a>
<br>
<br>
<a href="springmvc/pathVariable/1">Path Variable</a>
<br>
<br>
<a href="springmvc/requestParam?userName=my&password=123456">Request
Param</a>
<br>
<br>
<form action="springmvc/saveUserInfo" method="post">
userName:<input type="text" name="userName"><br>
password:<input type="password" name="password"><br>
province:<input type="text" name="address.province"><br>
city:<input type="text" name="address.city"><br> <input
type="submit" value="提交">
</form>
<br>
<br>
<form action="springmvc/getUserInfos" method="post">
userName1:<input type="text" name="uiMap['u1'].userName"><br>
password1:<input type="password" name="uiMap['u1'].password"><br>
province1:<input type="text" name="uiMap['u1'].address.province"><br>
city1:<input type="text" name="uiMap['u1'].address.city"><br>
userName2:<input type="text" name="uiMap['u2'].userName"><br>
password2:<input type="password" name="uiMap['u2'].password"><br>
province2:<input type="text" name="uiMap['u2'].address.province"><br>
city2:<input type="text" name="uiMap['u2'].address.city"><br>
<input type="submit" value="提交">
</form>
<br>
<br>
<a href="springmvc/returnModelAndView">ModelAndView</a>
<br>
<br>
<a href="springmvc/sessionAttributes">Session Attributes</a>
<br>
<br>
<a href="springmvc/modelAttribute">Model Attribute</a>
<br>
<br>
<a href="springmvc/beanNameViewResolver">BeanNameViewResolver</a>
<br>
<br>
<a href="springmvc/redirect">Redirect</a>
<br>
<br>
<form action="springmvc/testInitBinder" method="post">
regDate:<input type="text" name="regDate"><br> <input
type="submit" value="提交" />
</form>
<br>
<br>
<!-- 使用JSR-303进行数据校验 -->
<form action="springmvc/testValidate" method="post">
userName:<input type="text" name="userName"><br> email:<input
type="text" name="email"><br> age:<input type="text"
name="age"><br> <input type="submit" value="提交" />
</form>
<br>
<br>
<a href="javascript:void(0)" id="returnJson"
onclick="getUserInfoJson()">Test Json</a>
<br>
<br>
<a href="springmvc/rest/1">Rest GET</a>
<br>
<br>
<form action="springmvc/rest" method="post">
<input type="submit" value="Rest POST">
</form>
<br>
<br>
<!-- HiddenHttpMethodFilter通过 "_method"的值,将post请求转化为DELETE请求 -->
<form action="springmvc/rest/1" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="Rest DELETE">
</form>
<br>
<br>
<form action="springmvc/rest/1" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="Rest PUT">
</form>
<br>
<br>
<form action="springmvc/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="上传" />
</form>
<br>
<br>
</body>
</html>
使用Servlet API作为入参
包括:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- java.security.Principal
- Locale
- InputStream
- OutputStream
- Reader
- Writer
Spring MVC直接给视图配置映射
这种用于不作任何处理,直接返回视图的情况。也相当于给视图资源文件一个别名来进行访问。
<mvc:view-controller path="/login" view-name="login"/>
如果给某一个试图配置了视图信息,则其他控制器的方法无效,解决方法是加上:
<mvc:annotation-driven></mvc:annotation-driven>
SpringMVC 拦截器
配置:
<!-- 注册一个自定义类型转换器,Spring自动识别IOC容器中的conversionService,数据绑定的时候 ,Spring自动调用对象
还需在driver里面注册一下让SpringMVC知道
-->
<bean id= "conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.common.converter.StringToUserConverter"></bean>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
SpringMVC格式化
数字格式化
- @NumberFormat进行标注
- 使用
<mvc:annotation-driven/>
- 与自定义转换器一起用时,conversionService采用的是FormattingConversionService;默认Spring加载的就是FormattingConversionService
日期格式化
- 在类属性上标注@DataTimeFormat,pattern指定输入的格式y,M,d,h,m,s;iso是套用已经有的样式;
- 使用
<mvc:annotation-driven/>
- 如果与自定义转换器一起用时,conversionService采用的是FormattingConversionService;默认Spring加载的就是FormattingConversionService
数据校验
JSR303验证
此外,第三步也可以不用显式配置bean,因为Spring已经帮我们创建好一个LocalValidator的bean了。
BindingResult在请求域中,可以作为入参,此时他必须在@Valid注解的类的后面并相邻,和底层解析参数的时候有关,需要注意。