简介
MVC设计模式
MVC的全名是Model View Controller,是模型(Model)-视图(view)-控制器(controller)的缩写
它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间,提高代码复用性。
模型层(Model):封装了数据和对数据的操作,是实际进行数据处理的地方(模型层与数据库进行交互)
视图层(View):是应用和用户之间的接口,它负责将应用显示给用户和显示模型的状态。
控制器(Controller):控制器负责视图和模型之间的交互,控制对用户输入的响应、响应方式和流程;它主要负责两方面的动作,一是把用户的请求分发到相应的模型,二是吧模型的改变及时地反映到视图上。
spring mvc架构
架构流程
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
- 执行处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器
- ViewReslover解析后返回具体View
- DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户
组件说明
以下组件通常使用框架提供实现:
DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
HandlerMapping:处理器映射器
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
BeanNameUrlHandlerMapping
BeanNameUrl处理器映射器,根据请求的url与spring容器中定义的bean的name进行匹配,从而从spring容器中找到bean实例。
SimpleUrlHandlerMapping
simpleUrlHandlerMapping是BeanNameUrlHandlerMapping的增强版本,它可以将url和处理器bean的id进行统一映射配置。
Handler:处理器
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。这里使用了适配器设计模式。
SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter简单控制器处理器适配器,所有实现了org.springframework.web.servlet.mvc.Controller 接口的Bean通过此适配器进行适配、执行。
HttpRequestHandlerAdapter
HttpRequestHandlerAdapter,http请求处理器适配器,所有实现了org.springframework.web.HttpRequestHandler 接口的Bean通过此适配器进行适配、执行。
View Resolver:视图解析器
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
注解开发
示例:
@RequestMapping(value="/editItem")
public String editItem(Model model, Integer id) throws Exception{
//调用service查询商品信息
Items item = itemService.findItemById(id)
model.addAttribute("item", item);
return "item/editItem";
}
//商品修改提交
@RequestMapping("/editItemSubmit")
public String editItemSubmit(Items items)throws Exception{
itemService.saveItem(items);
return "success";
}
页面
/WEB-INF/jsp/item/itemsList.jsp
/WEB-INF/jsp/item/editItem.jsp
controller方法返回值
返回ModelAndView
controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view。
ModelAndView view = new ModelAndView(url);
//读配置文件
view.addObject("http", http);
构造函数可以指定视图名ModelAndView(String viewName)
addObject指定返回的参数和值
返回void
在controller方法形参上可以定义request和response,使用request或response指定响应结果:
1、使用request转向页面,如下:
request.getRequestDispatcher("页面路径").forward(request, response);
2、也可以通过response页面重定向:
response.sendRedirect("url")
3、也可以通过response指定响应结果,例如响应json数据如下:
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json串");
返回字符串
逻辑视图名
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
//指定逻辑视图名,经过视图解析器解析为jsp物理路径:/WEB-INF/jsp/item/editItem.jsp
return "item/editItem";
可以通过参数Model或ModelMap向页面传递数据
@RequestMapping(value = "/toAdd")
public String toAdd( Model model, Integer type) {
model.addAttribute("type", type);
return “item/add”;
}
Redirect重定向
Contrller方法返回结果重定向到一个url地址,如下商品修改提交后重定向到商品查询方法,参数无法带到商品查询方法中。
//重定向到queryItem.action地址,request无法带过去
return "redirect:queryItem.action";
redirect方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的request和response。
由于新发起一个request原来的参数在转发时就不能传递到下一个url,如果要传参数可以/item/queryItem.action后边加参数,如下:
/item/queryItem?...&…..
forward转发
controller方法执行后继续执行另一个controller方法,如下商品修改提交后转向到商品修改页面,修改商品的id参数可以带到商品修改方法中。
//结果转发到editItem.action,request可以带过去
return "forward:editItem.action";
forward方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。
参数绑定
处理器适配器在执行Handler之前需要把http请求的key/value数据绑定到Handler方法形参数上。
默认支持的参数类型
处理器形参中添加如下类型的参数处理适配器会默认识别并进行赋值。
HttpServletRequest
通过request对象获取请求信息
HttpServletResponse
通过response处理响应信息
HttpSession
通过session对象得到session中存放的对象
Model/ModelMap
ModelMap是Model接口的实现类,通过Model或ModelMap向页面传递数据,如下:
//调用service查询商品信息
Items item = itemService.findItemById(id);
model.addAttribute("item", item);
页面通过${item.XXXX}获取item对象的属性值。
使用Model和ModelMap的效果一样,如果直接使用Model,springmvc会实例化ModelMap。
简单类型
当请求的参数名称和处理器形参名称一致时会将请求参数与形参进行绑定。
整型
public String editItem(Model model,Integer id) throws Exception{
}
字符串
例子略
单精度/双精度
例子略
布尔型
处理器方法:
public String editItem(Model model,Integer id,Boolean status) throws Exception
请求url:
http://localhost:8080/springmvc_mybatis/item/editItem.action?id=2&status=false
说明:对于布尔类型的参数,请求的参数值为true或false。
@RequestParam
使用@RequestParam常用于处理简单类型的绑定。
value:参数名字,即入参的请求参数名字,如value=“item_id”表示请求的参数区中的名字为item_id的参数的值将传入;
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报;
TTP Status 400 - Required Integer parameter 'XXXX' is not present
defaultValue:默认值,表示如果请求中没有同名参数时的默认值
定义如下:
public String editItem(@RequestParam(value="item_id",required=true) String id) {
}
形参名称为id,但是这里使用value=" item_id"限定请求的参数名为item_id,所以页面传递参数的名必须为item_id。
注意:如果请求参数中没有item_id将跑出异常:
HTTP Status 500 - Required Integer parameter 'item_id' is not present
这里通过required=true限定item_id参数为必需传递,如果不传递则报400错误,可以使用defaultvalue设置默认值,即使required=true也可以不传item_id参数值
pojo
简单pojo
将pojo对象中的属性名与传递进来的属性名对应,如果传进来的参数名称和对象中的属性名称一致则将参数值设置在pojo对象中
页面定义如下:
<input type="text" name="name"/>
<input type="text" name="price"/>
Contrller方法定义如下:
@RequestMapping("/editItemSubmit")
public String editItemSubmit(Items items)throws Exception{
System.out.println(items);
请求的参数名称和pojo的属性名称一致,会自动将请求参数赋值给pojo的属性。
包装pojo
如果采用类似struts中对象.属性的方式命名,需要将pojo对象作为一个包装对象的属性,action中以该包装对象作为形参。
包装对象定义如下:
Public class QueryVo {
private Items items;
}
页面定义:
<input type="text" name="items.name" />
<input type="text" name="items.price" />
Controller方法定义如下:
public String useraddsubmit(Model model,QueryVo queryVo)throws Exception{
System.out.println(queryVo.getItems());
自定义参数绑定
需求
根据业务需求自定义日期格式进行参数绑定。
Converter
自定义Converter
public class CustomDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.parse(source);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
配置方式
<mvc:annotation-driven conversion-service="conversionService">
</mvc:annotation-driven>
<!-- conversionService -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 转换器 -->
<property name="converters">
<list>
<bean class="cn.itcast.ssm.controller.converter.CustomDateConverter"/>
</list>
</property>
</bean>
集合类
字符串数组
页面定义如下:
页面选中多个checkbox向controller方法传递
<input type="checkbox" name="item_id" value="001"/>
<input type="checkbox" name="item_id" value="002"/>
<input type="checkbox" name="item_id" value="002"/>
传递到controller方法中的格式是:001,002,003
Controller方法中可以用String[]接收,定义如下:
public String deleteitem(String[] item_id)throws Exception{
System.out.println(item_id);
}
也可以用其他类型的数组接收,如Long[] ids
List
List<String>类型的数据
jq:
var _list = [];
$("input[name='selectBox']:checked").each(function(){
_list.push(this.value); //push 进数组
});
然后ajax设置data为_list
data : "list="+_list
java类中:
@PostMapping("/bachDownLoad")
@ResponseBody
public void bachDownLoad(@RequestParam("list")List<Long> list){
}
List<Obj>类型的数据
//声明list
var _list = [];
//创建两个user对象
var a= {};a.name="tom";a.age=23;a.city="上海";
var b = {};b.name="jack";b.age=25;b.city="安徽";
//将user放入_list
_list.push(a);_list.push(b);
$.ajax({
url : '/ajax/test1',
data : "list="+JSON.stringify(_list),
type : "POST",
success : function(data) {
alert(data);
}
});
@RequestMapping(value="test",method=RequestMethod.POST)
@ResponseBody
public String ajaxList(@RequestParam("list")String userList){
}
List中对象:成绩对象
Public class QueryVo {
Private List<Items> itemList;//商品列表
//get/set方法..
}
包装类中定义List对象,并添加get/set方法如下:
页面定义如下:
<tr>
<td>
<input type="text" name=" itemsList[0].id" value="${item.id}"/>
</td>
<td>
<input type="text" name=" itemsList[0].name" value="${item.name }"/>
</td>
<td>
<input type="text" name=" itemsList[0].price" value="${item.price}"/>
</td>
</tr>
<tr>
<td>
<input type="text" name=" itemsList[1].id" value="${item.id}"/>
</td>
<td>
<input type="text" name=" itemsList[1].name" value="${item.name }"/>
</td>
<td>
<input type="text" name=" itemsList[1].price" value="${item.price}"/>
</td>
</tr>
上边的静态代码改为动态jsp代码如下:
<c:forEach items="${itemsList }" var="item" varStatus="s">
<tr>
<td><input type="text" name="itemsList[${s.index }].name" value="${item.name }"/></td>
<td><input type="text" name="itemsList[${s.index }].price" value="${item.price }"/></td>
.....
.....
</tr>
</c:forEach>
Contrller方法定义如下:
public String useraddsubmit(Model model,QueryVo queryVo)throws Exception{
System.out.println(queryVo.getItemList());
}
Map
在包装类中定义Map对象,并添加get/set方法,action使用包装对象接收。
包装类中定义Map对象如下:
Public class QueryVo {
private Map<String, Object> itemInfo = new HashMap<String, Object>();
//get/set方法..
}
页面定义如下:
<tr>
<td>学生信息:</td>
<td>
姓名:<inputtype="text"name="itemInfo['name']"/>
年龄:<inputtype="text"name="itemInfo['price']"/>
.. .. ..
</td>
</tr>
Contrller方法定义如下:
public String useraddsubmit(Model model,QueryVo queryVo)throws Exception{
System.out.println(queryVo.getStudentinfo());
}
常用注解说明
@Controller
@Controller 负责注册一个bean 到spring 上下文中,bean 的ID 默认为类名称开头字母小写
方法一:
@Controller
public class TestController {}
你也可以自己指定,如下:
方法二:
@Controller("tmpController")
public class TestController {}
@RequestMapping
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
@PostMapping是一个@RequestMapping(method = RequestMethod.POST)的快捷方式。@GetMapping、@PutMapping、@PatchMapping和@DeleteMapping,与@PostMapping实现类似
RequestMapping注解有六个属性,下面我们把她分成三类进行说明。
属性 | 说明 | 备注 |
value、path | 指定请求的实际地址,value是path的别名 |
value的uri值为以下三类:
|
method | 指定请求的method类型 |
GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE
|
consumes |
指定处理请求的提交内容类型(Content-Type) |
例如application/json, text/html; |
produces |
指定返回值类型和字符编码,仅当request请求头中的(Accept)类型中包含该指定类型才返回; |
如果返回json类型,使用了@ResponseBody可以忽略produces指定。 |
params |
指定request中必须包含某些参数值是,才让该方法处理。 |
多个请求参数以&隔开 例如: http://localhost/SpringMVC/user/login?username=kolbe&password=123456 |
headers |
指定request中必须包含某些指定的header值,才能让该方法处理请求。 |
value示例:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:d.d.d}.{extension:.[a-z]}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// ...
}
}
配置的get如果用的post会报如下错误:
HTTP Status 405 - Request method 'POST' not supported
get和post可以都支持
@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})
@RequestMapping(value={"url1","url2","url3"....})
还可以配置通配符匹配多个请求
有一点需要注意的,如果为类定义了访问地址为*.do,*.html之类的,则在方法级的@RequestMapping,不能再定义value值,否则会报错,例如 Java代码:
@RequestMapping("/bbs.do")
public class BbsController {
@RequestMapping(params = "method=getList")
public String getList() {
return "list";
}
@RequestMapping(value= "/spList")
public String getSpecialList() {
return "splist";
}
}
如上例:/bbs.do?method=getList 可以访问到方法getList() ;而访问/bbs.do/spList则会报错
params示例:
该属性表示请求参数,也就是追加在URL上的键值对,多个请求参数以&隔开,例如:
http://localhost/SpringMVC/user/login?username=kolbe&password=123456
// 该方法将接收 /user/login 发来的请求,且请求参数必须为 username=kolbe&password=123456
@RequestMapping(path = "/login", params={"username=kolbe","password=123456"})
public String login() {
return "success";
}
headers示例:
该属性表示请求头,可以限制客户端发来的请求
// 表示只接收本机发来的请求
@RequestMapping(path = "/login", headers="Host=localhost:8080")
public String login() {
return "success";
}
produces示例:
produces="MediaType.APPLICATION_JSON_VALUE;charset=utf-8"
consumes示例:
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
表示方法仅处理request Content-Type为“application/json”类型的请求。
@PathVariable
@PathVariable用于方法中的参数,表示方法参数绑定到地址URL的模板变量。
例如:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
@PathVariable用于地址栏使用{xxx}模版变量时使用。
如果@RequestMapping没有定义类似"/{ownerId}" 这种变量,则使用在方法中@PathVariable会报错。
@ModelAttribute
1.应用于方法参数,参数可以在页面直接获取,相当于request.setAttribute(,)
2.应用于方法,将任何一个拥有返回值的方法标注上 @ModelAttribute,使其返回值将会进入到模型对象的属性列表中.
3.应用于方法参数时@ModelAttribute("xx"),须关联到Object的数据类型,基本数据类型 如:int,String不起作用
例如:
@ModelAttribute("items")//<——①向模型对象中添加一个名为items的属性
public List<String> populateItems() {
List<String> lists = new ArrayList<String>();
lists.add("item1");
lists.add("item2");
return lists;
}
@RequestMapping(params = "method=listAllBoard")
public String listAllBoard(@ModelAttribute("currUser")User user, ModelMap model) {
bbtForumService.getAllBoard();
//<——②在此访问模型中的items属性
System.out.println("model.items:" + ((List<String>)
model.get("items")).size());
return "listBoard";
}
在 ① 处,通过使用 @ModelAttribute 注解,populateItem() 方法将在任何请求处理方法执行前调用,Spring MVC 会将该方法返回值以“items”为名放入到隐含的模型对象属性列表中。
所以在 ② 处,我们就可以通过 ModelMap 入参访问到 items 属性,当执行 listAllBoard() 请求处理方法时,② 处将在控制台打印出“model.items:2”的信息。当然我们也可以在请求的视图中访问到模型对象中的 items 属性。
@RequestBody
作用:
@RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将读到的内容转换为json、xml等格式的数据并绑定到controller方法的参数上。
@ResponseBody
作用:
该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端
@RequestParam
@RequestParam是一个可选参数,例如:@RequestParam("id") 注解,所以它将和URL所带参数 id进行绑定
如果入参是基本数据类型(如 int、long、float 等),URL 请求参数中一定要有对应的参数,否则将抛出 org.springframework.web.util.NestedServletException 异常,提示无法将 null 转换为基本数据类型.
@RequestParam包含3个配置 @RequestParam(required = ,value="", defaultValue = "")
required :参数是否必须,boolean类型,可选项,默认为true
value: 传递的参数名称,String类型,可选项,如果有值,对应到设置方法的参数
defaultValue:String类型,参数没有传递时为参数默认指定的值
@SessionAttributes
session管理
Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。@SessionAttributes 只能声明在类上,而不能声明在方法上。
例如
@SessionAttributes("currUser") // 将ModelMap 中属性名为currUser 的属性
@SessionAttributes({"attr1","attr2"})
@SessionAttributes(types = User.class)
@SessionAttributes(types = {User.class,Dept.class})
@SessionAttributes(types = {User.class,Dept.class},value={"attr1","attr2"})
将ModelMap中指定的属性放到session中。示例代码如下:
@Controller
@RequestMapping("/user.do")
@SessionAttributes({"u","a"}) //将ModelMap中属性名字为u、a的再放入session中。这样,request和session中都有了。
publicclass UserController {
@RequestMapping(params="method=reg4")
public String reg4(ModelMap map) { System.out.println("HelloController.handleRequest()");
map.addAttribute("u","uuuu"); //将u放入request作用域中,这样转发页面也可以取到这个数据。
return"index";
}
}
<body>
<h1>**********${requestScope.u.uname}</h1>
<h1>**********${sessionScope.u.uname}</h1>
</body>
@CookieValue
获取cookie信息
@RequestHeader
获取请求的头部信息
@DateTimeFormat
用于pojo的Date类型上,是将String转换成Date,一般前台给后台传值时用
如:
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date optTime;//操作时间
这样当页面传过来这个格式的参数就可以直接绑定上了
@ResponseBody
@RequestMapping("test")
public String dateTest(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")Date myDate){
return myDate.toLocaleString();
}
http://127.0.0.1:8080/myssm/date/test?myDate=2018-08-02%2013:48:00
这样也是可以的,如果不指定@DateTimeFormat,会报这个错
Failed to convert from type java.lang.String to type java.util.Date for value '2018-08-02 13:48:00';
如果注解是写成这样的@DateTimeFormat(pattern = "yyyy-MM-dd")
这样就会把时间都格式化成0点