Spring web mvc 架构
架构图
流程
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器
- ViewReslover解析后返回具体View
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户
组件说明
以下组件通常使用框架提供实现:
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
下边两个组件通常情况下需要开发:
Handler:处理器,即后端控制器用controller表示。
View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。
第一个SpringMVC工程
配置前端控制器
在web.xml配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>MySpringMVC</display-name>
<!-- 解决post请求中文乱码 -->
<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--
指定springmvc配置的加载位置,如果不指定则默认加
载WEB-INF/[DispatcherServlet 的Servlet 名字]-servlet.xml。
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
SpringMVC配置文件
SpringMVC配置文件的加载位置
Springmvc默认加载WEB-INF/[前端控制器的名字]-servlet.xml,也可以在前端控制器定义处指定加载的配置文件,如下:
<!--
指定springmvc配置的加载位置,如果不指定则默认加
载WEB-INF/[DispatcherServlet 的Servlet 名字]-servlet.xml。
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
如上代码,通过contextConfigLocation加载classpath下的springmvc-servlet.xml配置文件,配置文件名称可以不限定[前端控制器的名字]-servlet.xml。
配置处理器映射器
<!-- 处理器映射器 -->
<!-- BeanNameUrlHandlerMapping:表示将定义的处理器(controller)Bean的名字作为请求的url,
且必须以.action结尾,比如下面的/hello.action -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<!-- 简单url映射器,key为路径名,值为处理器(controller)对应bean的id -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/hello0.action">hello_controller</prop>
<prop key="/hello1.action">hello_controller</prop>
<prop key="/hello2.action">hello_controller2</prop>
</props>
</property>
</bean>
配置处理器适配器
<!-- 处理器适配器,多个处理器适配器可以共存,适配器的意思就是根据不同的处理器寻找对应的适配器进行请求转换然后对接处理器 -->
<!-- SimpleControllerHandlerAdapter:即简单控制器处理适配器, 所有实现了org.springframework.web.servlet.mvc.Controller
接口的Bean作为 Springmvc的后端控制器。 -->
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<!-- HTTP请求处理器适配器 HTTP请求处理器适配器将http请求封装成HttpServletResquest 和HttpServletResponse对象,和servlet接口类似。
适配器配置如下: -->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" />
配置视图解析器
<!-- ViewResolver视图解析器 -->
<!-- InternalResourceViewResolver:支持JSP视图解析 viewClass:JstlView表示JSP模板页面需要使用JSTL标签库,
所以classpath中必须包含jstl的相关jar 包; prefix 和suffix:查找视图页面的前缀和后缀,最终视图的址为: 前缀+逻辑视图名+后缀,逻辑视图名需要在controller中返回ModelAndView指定,
比如逻辑视图名为hello,则最终返回的jsp视图地址 “WEB-INF/jsp/hello.jsp” -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
后端控制器配置
<!--处理器 -->
<bean name="/hello.action" class="com.jyh.controller.HelloWorldController" />
<bean id="hello_controller" class="com.jyh.controller.HelloWorldController" />
<bean id="hello_controller2" class="com.jyh.controller.HelloWorldController2" />
后端控制器开发
hello1
package com.jyh.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class HelloWorldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
ModelAndView mv = new ModelAndView();
//添加模型数据
mv.addObject("message", "Hello World1111!");
//设置逻辑视图名,最终视图地址=前缀+逻辑视图名+后缀
mv.setViewName("hello");
return mv;
}
}
hello2
package com.jyh.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.HttpRequestHandler;
public class HelloWorldController2 implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("message", "HelloWorld22222!");
request.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(request, response);
//也可以自定义响应内容
//response.setCharacterEncoding("utf-8");
//response.getWriter().write("HelloWorld!");
}
}
视图开发
<body>
${message}
</body>
注解开发
xml配置
<!-- ********************注解配置******************** -->
<!-- 注解映射器和注解适配器可以使用<mvc:annotation-driven/>代替,默认注册了注解映射器和注解适配器等bean。 -->
<mvc:annotation-driven />
<context:component-scan base-package="com.jyh.controller"></context:component-scan>
<!-- 注解处理映射器
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
-->
<!--注解适配器
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
-->
<!-- 处理器
<bean class="com.jyh.controller.AnnotationHelloWorld" />
-->
<!-- json数据传输
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>
</list>
</property>
</bean>
-->
编写控制器类
@Controller
// 表示处理器 value为类访问路径,即根路径 method为请求方式
@RequestMapping(value = "/student", method = { RequestMethod.GET,
RequestMethod.POST })
public class AnnotationHelloWorld extends Binder {
//value为方法访问路径,即子路径,要访问本方法需要这样写/student/getStudent.action
@RequestMapping(value = "/getStudent")
public String getStudent(Model model) throws Exception {
Student student = new Student();
student.setId(1);
student.setName("学生");
student.setGender("男");
student.setBirthday(new Date());
model.addAttribute("student", student);
return "getStudent";
}
}
url模板映射
@RequestMapping("/student/{groupid}/{userid}")
// 占位符{xxx}
public String useredit(@PathVariable String groupid,
@PathVariable String userid, Model model) throws Exception {
// 方法中使用@PathVariable获取路径中对应占位符的值,使用model传回页面
model.addAttribute("message", groupid + userid);
return "success";
}
请求数据绑定
默认参数类型
@RequestMapping("/userlist")
public String userlist(HttpServletRequest request,
HttpServletResponse response, HttpSession session, Model model) {
System.out.println(request.getClass().getName());
System.out.println(response.getClass().getName());
System.out.println(session.getClass().getName());
return "";
}
默认参数类型:
HttpServletRequest:通过request对象获取请求信息
HttpServletResponse:通过response处理响应信息
HttpSession:通过session对象得到session中存放的对象
Model:通过model向页面传递数据,如下:
model.addAttribute(“user”, new User(“李四”));
页面通过${user.XXXX}获取user对象的属性值。
基本类型
请求:http://localhost:8080/MySpringMVC/student/parameter.action?
param=bbb
处理:
//单个参数
@RequestMapping("/parameter")
public ModelAndView parameter(@RequestParam(defaultValue="aaa",value="param",required=true)String parameter) {
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
mv.addObject("message", parameter);
//return "redirect:/parameter.action" 重定向
//return "forward:/parameter.action" 转发
return mv;
}
POJO对象
直接POJO对象属性名命名的
页面表单:
<form action="${pageContext.request.contextPath}/student/updateStudent.action" method="post">
<input type="hidden" name="id" value="${student.id}"/><br/>
<input type="text" name="name" value="${student.name}"/><br/>
<input type="text" name="gender" value="${student.gender}"/><br/>
<input type="text" name="birthday" value="${student.birthday}"/><br/>
<input type="submit" value="提交"/>
</form>
controller:
@RequestMapping(value = "/updateStudent")
public String updateStudent(Model model, Student student) throws Exception {
System.out.println(student);
model.addAttribute("message", "编辑成功!");
return "success";
}
POJO对象点属性名命名的
页面:
<form action="${pageContext.request.contextPath}/student/updateStudent2.action" method="post">
<input type="hidden" name="student.id" value="${student.id}"/><br/>
<input type="text" name="student.name" value="${student.name}"/><br/>
<input type="text" name="student.gender" value="${student.gender}"/><br/>
<input type="text" name="student.birthday" value="${student.birthday}"/><br/>
<input type="submit" value="提交"/>
</form>
controller:
@RequestMapping(value = "/updateStudent2")
public String updateStudent2(Model model, Student2 student2) throws Exception {
System.out.println("------");
System.out.println(student2.getStudent());
model.addAttribute("student", student2.getStudent());
return "getStudent";
}
student2:
package com.jyh.domain;
import java.io.Serializable;
@SuppressWarnings("serial")
public class Student2 implements Serializable {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
注意:
有时候需要传递日期等无法识别的类型,这时候需要进行类型注册,在controller里面添加initBinder方法,上面加上一个注解@InitBinder,方法里面注册编辑器,也可以将该方法写在一个类里面,然后其他controller继承该类
@InitBinder
public void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),true));
//binder.registerCustomEditor(requiredType, propertyEditor);
}
集合数据
list集合
页面:
<form action="${pageContext.request.contextPath}/student/updateStudentList.action" method="post">
<table>
<c:forEach items="${studentList}" var="student" varStatus="num">
<tr>
<td><input type="text" name="studentList[${num.index}].id" value="${student.id}"/></td>
<td><input type="text" name="studentList[${num.index}].name" value="${student.name}"/></td>
<td><input type="text" name="studentList[${num.index}].gender" value="${student.gender}"/></td>
<td><input type="text" name="studentList[${num.index}].birthday" value="${student.birthday}"/></td>
</tr>
</c:forEach>
</table>
<input type="submit" value="提交"/>
</form>
controller:
@RequestMapping(value = "/updateStudentList")
public String updateStudentList(Model model, StudentList studentList) throws Exception {
System.out.println("------");
System.out.println(studentList.getStudentList());
model.addAttribute("studentList", studentList.getStudentList());
return "studentList";
}
StudentList:
package com.jyh.domain;
import java.io.Serializable;
import java.util.List;
@SuppressWarnings("serial")
public class StudentList implements Serializable {
private List<Student> studentList;
public List<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> studentList) {
this.studentList = studentList;
}
}
map集合
页面:
<form
action="${pageContext.request.contextPath}/student/updateStudentMap.action"
method="post">
<table>
<c:forEach items="${studentMap}" var="student">
<tr>
<td><input type="text" name="studentMap['${student.key}'].id"
value="${student.value.id}" /></td>
<td><input type="text" name="studentMap['${student.key}'].name"
value="${student.value.name}" /></td>
<td><input type="text" name="studentMap['${student.key}'].gender"
value="${student.value.gender}" /></td>
<td><input type="text" name="studentMap['${student.key}'].birthday"
value="${student.value.birthday}" /></td>
</tr>
</c:forEach>
</table>
<input type="submit" value="提交" />
</form>
json数据
js:
/**
* json数据传输测试
*/
$(function(){
$("#form").click(function(){
myEvent.request_form();
});
$("#json").click(function(){
myEvent.request_json();
});
});
var myEvent = {
request_json:function(){
//json格式数据提交
var user = JSON.stringify({id:1,name: "张三", gender: "男",birthday:"1995-04-10"});
$.ajax(
{
type:'post',
url:'student/requestjson.action',
contentType:'application/json;charset=utf-8', //请求内容为json
data:user,
success:function(data){
alert(data.name);
},
error:function(){
alert("没有返回");
}
}
);
},
request_form:function(){
//表单提交
var user = "id=1&name=张三&gender=男&birthday=1995-04-10"
$.ajax(
{
type:'post',
url:'student/formRequest.action',
data:user,
success:function(data){
alert(data.name);
},
error:function(){
alert("没有返回");
}
}
);
}
};
controller:
//接收json数据,返回json数据
@RequestMapping("/requestjson")
//@RequestBody接收json串自动转为student对象,@ResponseBody将student对象转为json数据响应给客户端
public @ResponseBody Student requestjson(@RequestBody Student student)throws Exception{
System.out.println(student);
return student;
}
//接收表单数据,返回json数据
@RequestMapping("/formRequest")
public @ResponseBody Student formRequest(Student student)throws Exception{
System.out.println(student);
return student;
}
拦截器
xml配置:
<!-- 对某种映射器配置拦截器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1" />
</list>
</property>
</bean>
<bean id="handlerInterceptor1" class="com.jyh.handlerinterceptor.MyHandlerInterceptor1" />
<!--全局拦截器 -->
<mvc:interceptors>
<!--多个拦截器,顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.jyh.handlerinterceptor.MyHandlerInterceptor1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.jyh.handlerinterceptor.MyHandlerInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
java代码:
package com.jyh.handlerinterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyHandlerInterceptor1 implements HandlerInterceptor {
/**
* controller执行前调用此方法
* 返回true表示继续执行,返回false中止执行
* 这里可以加入登录校验、权限拦截等
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
return true;
}
/**
* controller执行后但未返回视图前调用此方法
* 这里可在返回用户前对模型数据进行加工处理,比如这里加入公用信息以便页面显示
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
/**
* controller执行后且视图返回后调用此方法
* 这里可得到执行controller时的异常信息
* 这里可记录操作日志,资源清理等
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
与struts2不同
- springmvc的入口是一个servlet即前端控制器,而struts2入口是一个filter过虑器。
- springmvc是基于方法开发,请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。
- Struts采用值栈存储请求和响应的数据,通过OGNL存取数据, springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。