SpringMVC 实际上是 Spring 的一部分,它是一个表现层的框架。
用户的请求会到达 Servlet,然后根据请求调用相应的 Java Bean,并把所有的显示结果交给 JSP 去完成,这样的模式我们就称为 MVC 模式。
上面的解释可能比较难懂,下面我对此做一个更详细的讲解
用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
Handler是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
ViewResolver负责将处理结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
SpringMVC框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
其实,在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件。
springmvc.xml 是 SpringMVC的核心配置文件,其实发现,就算我们的 springmvc.xml 写成下面这个样子,程序也是可以运行的。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.itheima.springmvc.controller"/>
beans>
为啥只有这么一点东西呢?SpringMVC的三大组件哪去了?我们发现这几个组件并没配置,但却是可以的,就是因为它有一个默认配置,DispatcherServlet.properties这个默认配置文件里面默认加载了。
我们如果使用默认加载的注解方式的映射器和适配器,那么对它们的可控制性是比较小的,所以一般来说,我们都是自己配置的,因为有的时候我们需要扩展一些其他的组件。
使用组件扫描器可省去在 Spring 容器中配置每个 Controller 类的繁琐。使用
<context:component-scan base-package="com.itheima.springmvc.controller"/>
如果要扫描多个包,多个包中间使用半角逗号分隔。
注解式处理器映射器,对类中标记@ResquestMapping注解的方法进行映射,根据@ResquestMapping注解定义的url匹配@ResquestMapping注解标记的方法,匹配成功返回HandlerMethod对象给前端控制器,HandlerMethod对象中封装了url对应的方法Method。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">bean>
注解式处理器适配器,对标记@ResquestMapping注解的方法进行适配。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">bean>
使用注解要注意一个问题,就是注解适配器和映射器必须配对使用,也就是说,不能一个用注解,一个用非注解。要用一起用,要么都不用。其实在SpringMVC中还有更加简便的注解,SpringMVC使用
我们也可在 springmvc.xml 配置文件中自己手动配置视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/">property>
<property name="suffix" value=".jsp">property>
bean>
prefix和suffix:查找视图页面的前缀和后缀,最终视图的址为:前缀+逻辑视图名+后缀,逻辑视图名需要在Controller返回的ModelAndView中指定,比如逻辑视图名为hello,则最终返回的jsp物理视图地址就为 “WEB-INF/jsp/hello.jsp”。
假设现在我们有一个需求,需要根据 id 查询数据,那么我们需要从请求的参数中把请求的 id 取出来。id 应该包含在 Request 对象中。可以从 Request 对象中取 id。故我们可以写如下代码:
public ModelAndView editItem(HttpServletRequest request) {
// 从request中取出参数
String strId = request.getParameter("id");
int id = new Integer(strId);
// 调用服务
Items items = itemService.getItemById(id);
// 把结果传递给页面
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("item", items);
// 设置逻辑视图
modelAndView.setViewName("editItem");
return modelAndView;
}
如果想获得Request对象只需要在Controller类方法的形参中添加一个参数即可。SpringMVC框架会自动把Request对象传递给方法。这就是SpringMVC框架默认支持的参数类型。
处理器形参中添加如下类型的参数,处理适配器会默认识别并进行赋值。
model.addAttribute("item", items);
页面中通过${item.XXXX}获取item对象的属性值。
实际上,使用Model和ModelMap的效果是一样的,如果直接使用Model接口,SpringMVC会实例化ModelMap。如果使用Model接口,那么editItem方法可以改造成:
@RequestMapping("/itemEdit")
public String editItem(HttpServletRequest request,
HttpServletResponse response, HttpSession session, Model model) {
// 从request中取出参数
String strId = request.getParameter("id");
int id = new Integer(strId);
// 调用服务
Items items = itemService.getItemById(id);
// 使用模型设置返回结果,model是框架给我们传递过来的对象,所以这个对象也不需要我们返回
model.addAttribute("item", items); // 类似于:modelAndView.addObject("item", items);
// 返回逻辑视图
return "editItem";
}
如果使用Model接口则可以不使用ModelAndView对象,Model对象可以向页面传递数据(model是框架给我们传递过来的对象,所以这个对象不需要我们返回),View对象则可以使用String返回值替代。不管是Model还是ModelAndView,其本质都是使用Request对象向jsp传递数据。
当请求的参数名称和处理器形参名称一致时会将请求参数与形参进行绑定。从Request取参数的方法可以进一步简化。这样一来,editItem方法可以改造成:
@RequestMapping("/itemEdit")
public String editItem(Integer id, Model model) {
// 调用服务
Items items = itemService.getItemById(id);
// 把数据传递给页面,需要用到Model接口
model.addAttribute("item", items);
// 返回逻辑视图
return "editItem";
}
注意,参数类型推荐使用包装数据类型,因为基础数据类型不可以为null。
使用@RequestParam注解常用于处理简单类型的绑定。
使用@RequestParam注解,editItem方法可以改造成:
@RequestMapping("/itemEdit")
public String editItem(@RequestParam(value="id",defaultValue="1",required=true) Integer ids, Model model) {
// 调用服务
Items items = itemService.getItemById(ids);
// 把数据传递给页面,需要用到Model接口
model.addAttribute("item", items);
// 返回逻辑视图
return "editItem";
}
required=true限定id参数为必须传递,如果不传递则报400错误,可以使用defaultvalue设置默认值,即使required=true也可以不传id参数值。
如果提交的参数很多,或者提交的表单中的内容很多的时候可以使用pojo接收数据。要求pojo对象中的属性名和表单中input的name属性一致。就像下图所示:
请求的参数名称和pojo的属性名称一致,会自动将请求参数赋值给pojo的属性。注意:提交的表单中不要有日期类型的数据,否则会报400错误。
通过@RequestMapping注解可以定义不同的处理器映射规则。
例如:
@RequestMapping("/item")
@RequestMapping(value="/item")
value的值是数组,所以可以将多个url映射到同一个方法上。
在class上添加@RequestMapping(url)指定通用请求前缀, 限制此类下的所有方法请求url必须以请求前缀开头,通过此方法对url进行分类管理。如下:@RequestMapping放在类名上边,设置请求前缀。
@Controller
@RequestMapping("/item")
public class ItemController {
}
限定GET方法:
@RequestMapping(method = RequestMethod.GET)
@RequestMapping(value="/updateitem",method={RequestMethod.GET})
限定POST方法:
@RequestMapping(method = RequestMethod.POST)
GET和POST都可以:
@RequestMapping(value="/updateitem",method={RequestMethod.POST,RequestMethod.GET})
Controller类方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view。
在Controller类方法形参上可以定义request和response,使用request或response指定响应结果:
使用request转向页面,如下:
request.getRequestDispatcher("页面路径").forward(request, response);
之前我们实现查询,返回的是ModelAndView,如果现在该方法的返回值是void,那么就应使用request跳转页面,如下:
@RequestMapping("/itemList2")
public void itmeList2(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 查询商品列表
List<Items> itemList = itemService.getItemList();
// 向页面传递参数
request.setAttribute("itemList", itemList);
// 如果使用原始的方式做页面跳转,必须给的是jsp的完整路径
request.getRequestDispatcher("/WEB-INF/jsp/itemList.jsp").forward(request, response);
}
如果使用原始的方式做页面跳转,那么必须给定jsp页面的完整路径。
也可以通过response实现页面重定向:
response.sendRedirect("url")
也可以通过response指定响应结果,例如响应json数据如下:
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json串");
例如,将以上itmeList2方法修改为:
@RequestMapping("/itemList2")
public void itmeList2(HttpServletRequest request, HttpServletResponse response) throws Exception {
PrintWriter writer = response.getWriter();
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
writer.write("{\"id\":\"123\"}");
}
Controller类方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
Redirect重定向:
Contrller类方法返回结果重定向到一个url地址,如下商品信息修改提交后重定向到商品查询方法,参数无法直接带到商品查询方法中。
@RequestMapping(value="/updateitem",method={RequestMethod.POST,RequestMethod.GET})
public String updateItems(Items items) {
itemService.updateItem(items);
// '/'是不包含工程名的根目录,即http://localhost:8080/springmvc-web2/item/itemList.action
return "redirect:/item/itemList.action";
}
redirect方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的request和response。由于新发起一个request,原来的参数在转发时就不能传递到下一个url,如果要传参数可以在/item/itemList.action后边加参数,如下:
return "redirect:/item/itemList.action?id=xxx&name=xxx";
但如果你使用的是Model接口,那么SpringMVC框架会自动将Model中的数据拼装到/item/itemList.action后面。
forward转发:
Controller类方法执行后继续执行另一个Controller类方法,如下商品修改提交后转向到商品修改页面,修改商品的id参数可以直接带到商品修改方法中。
@RequestMapping(value="/updateitem",method={RequestMethod.POST,RequestMethod.GET})
public String updateItems(Items items) throws UnsupportedEncodingException {
itemService.updateItem(items);
return "forward:/item/itemList.action";
}
forward方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。
系统的dao、service、controller出现异常都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理,SpringMVC提供全局异常处理器(一个系统只有一个异常处理器)进行统一异常处理。
为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。
我们可在工程下编写一个自定义异常类——CustomerException.java,如下:
public class CustomerException extends Exception {
private String expMessage;
public CustomerException() {
}
public CustomerException(String msg) {
this.expMessage = msg;
}
public String getExpMessage() {
return expMessage;
}
public void setExpMessage(String expMessage) {
this.expMessage = expMessage;
}
}
全局异常处理器处理思路:
SpringMVC提供一个HandlerExceptionResolver接口,自定义全局异常处理器必须要实现这个接口,所以我们可编写一个自定义全局异常处理器,如下:
/**
* 全局异常处理器
*
*/
public class GlobalExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception exception) {
// 判断异常的种类
String msg = null;
if (exception instanceof CustomerException) {
// 如果是自定义异常,就从异常里面取出错误消息
CustomerException custExp = (CustomerException) exception;
msg = custExp.getExpMessage();
} else {
// 如果是运行时异常,则取错误的堆栈信息
exception.printStackTrace(); // 向控制台上打印堆栈信息
StringWriter s = new StringWriter();
PrintWriter printWriter = new PrintWriter(s);
exception.printStackTrace(printWriter);
msg = s.toString();
}
// 写日志、发短信、发邮件
// 在此省略这一步......
// 返回一个友好的错误页面,并显示错误消息
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", msg);
modelAndView.setViewName("error");
return modelAndView;
}
}
记得还要在/WEB-INF/jsp目录下提供一个错误页面——error.jsp
在springmvc.xml文件中配置这个自定义的异常处理器:
<!-- 配置全局异常处理器,且全局异常处理器只能有一个 -->
<bean class="com.itheima.springmvc.exception.GlobalExceptionResolver"></bean>
可以看出,前台传过来的方式有两种,一种是传json格式的数据过来,另一种就是在url的末尾传普通的key/value串过来,针对这两种方式,在Controller类中会有不同的解析,但是在Controller类中返回的json格式的数据都是一样的。下面来具体分析一下SpringMVC是如何与前台进行json数据的交互的。在讲之前先认识两个注解。
@RequestBody注解用于读取http请求的内容(字符串),通过SpringMVC提供的HttpMessageConverter接口将读到的内容转换为json、xml等格式的数据并绑定到Controller类方法的参数上。
本例子应用:@RequestBody注解实现接收http请求的json数据,将json数据转换为java对象。如下:
@ResponseBody注解用于将Controller类的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json、xml等,通过Response响应给客户端。
本例子应用:@ResponseBody注解实现将Controller类方法返回对象转换为json响应给客户端,如下:
Restful就是一个资源定位及资源操作的风格,不是标准也不是协议,只是一种风格,是对http协议的诠释。
资源定位:互联网所有的事物都是资源,要求url中没有动词,只有名词,没有参数。url请求的风格就像这样:
http://blog.csdn.net/eson_15/article/details/51743514
资源操作:使用put、delete、post、get等不同方法对资源进行操作,分别对应添加、删除、修改、查询。一般使用时还是post和get,put和delete几乎不使用。
现在有这样一个需求:使用RESTful方式实现商品信息查询。有需求,就要解决需求。我们可将ItemController类中的editItem方法改造为:
@RequestMapping("/itemEdit/{id}")
// 如果id和方法的形参一致,@PathVariable注解中可以不写内容
public String editItem(@PathVariable("id") Integer iid, Model model) {
// 调用服务
Items items = itemService.getItemById(iid);
// 把数据传递给页面,需要用到Model接口
model.addAttribute("item", items);
// 返回逻辑视图
return "editItem";
}
@RequestMapping(value="/itemEdit/{id}"):{×××}表示占位符,请求的URL可以是“/itemEdit/1”或“/itemEdit/2”,通过在方法中使用@PathVariable获取{×××}中的×××变量。@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。如果@RequestMapping中表示为”/viewItems/{id}”,id和形参名称一致,那么@PathVariable就不用指定名称。
除此之外,还要在前端控制器中针对REST进行配置,即将web.xml文件中的前端控制器配置改造为:
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring/springmvc.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>*.actionurl-pattern>
servlet-mapping>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
但是有个问题,使用上面的配置后会拦截所有的url(虽说不包括jsp),那么对静态资源也会拦截,所以DispatcherServlet也会解析静态资源,但是这样的话就会出错,所以我们要设置一下不让它解析静态资源。SpringMVC的
<mvc:resources location="/js/" mapping="/js/**"/>
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
在SpringMVC中,定义拦截器要实现HandlerInterceptor接口,并实现该接口中提供的三个方法,如下:
public class Interceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("Interceptor1 preHandle........");
// 执行的时机是在Handler执行之前执行此方法
// 返回值:如果返回true,就放行,不拦截,正常执行Handler进行处理
// 返回值:如果返回false,那就拦截,Handler就不能正常处理了
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// Handler执行之后,在返回ModelAndView之前,对modelAndView做些手脚
System.out.println("Interceptor1 postHandle........");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 返回ModelAndView之后
// Handler执行过程中出现异常,可以在此处理异常
System.out.println("Interceptor1 afterCompletion........");
}
}
针对这三个方法,我做一下简单的分析:
针对某种HandlerMapping配置拦截器:
在SpringMVC中,拦截器是针对具体的HandlerMapping进行配置的,也就是说如果在某个HandlerMapping中配置拦截,经过该HandlerMapping映射成功的Handler最终会使用该拦截器。比如,假设我们在springmvc.xml配置文件中配置的映射器是org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,那么我们可以这样来配置拦截器:
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
list>
property>
bean>
<bean id="handlerInterceptor1" class="com.itheima.springmvc.interceptor.HandlerInterceptor1"/>
<bean id="handlerInterceptor2" class="com.itheima.springmvc.interceptor.HandlerInterceptor2"/>
针对所有HandlerMapping配置全局拦截器:
那么在SpringMVC中,如何配置类似于全局的拦截器呢?上面也说了,SpringMVC中的拦截器是针对具体的映射器而言的,为了解决这个问题,SpringMVC框架将配置的类似全局的拦截器注入到每个HandlerMapping中,这样就可以成为全局的拦截器了。配置如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.itheima.springmvc.interceptor.Interceptor1">bean>
mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.itheima.springmvc.interceptor.Interceptor2">bean>
mvc:interceptor>
mvc:interceptors>
注意:path=”/**”表示拦截所有的url包括子url路径。在实际开发中,一般我们都用这种配置,
转载至:MyBatis+SpringMVC