SpringMVC入门
一、SpringMVC原理解析
1、我们首先分析一下整个请求处理的流程:
①在B/S架构的系统中,用户首先从浏览器中发出Http请求(请求中会包含用户的请求内容信息或者表单信息),然后首先经过前端控制器(DispatcherServlet)进行处理,
②然后前端控制器需要接触处理器映射器知道自己使用哪一个处理器处理请求信息,
③然后处理器映射器会返回给前端控制器一个处理器执行链,
④前端控制器通过处理器适配器去执行处理器,然后让处理器适配器返回给自己模型和视图,
⑤处理器适配器去调用相应的处理器
⑥执行后的处理器返回给处理器适配器信息,具体就是返回处理结果(ModelAndView)
⑦处理器适配器得到模型视图(ModelAndView)之后,将之返回给前端控制器
⑧前端控制器自己本身不对ModelAndView进行解析,而是交给视图解析器进行视图解析
⑨视图解析器完成视图解析后,将视图(View)返回给前端控制器
⑩前端控制器得到view后,会交给视图进行渲染,具体就是jsp、freemaker等等,最后响应给用户
2、通过上面的解释和线面图例的理解,我们可以对SpringMVC这个框架的处理流程有一个大致的了解。上面只是介绍了一部分组件,我们下面可以简单的介绍各个组件的功能
①前端控制器DispatcherServlet:作为整个SpringMVC的中央处理器,可以发现整个流程中大部分都经过它。用户请求首先到达前端处理器(MVC模式中C层),作为整个流程的中心,由他调用其他组件,其存在大大降低了各个组件的耦合性
②处理器映射器HandlerMapping:顾名思义,处理器映射器就是根据用户请求找到相应的处理器Handler进行处理请求,SpringMVC中提供配置文件方式、接口方式、注解方式等等实现不同的映射方式
③处理器适配器HandlerAdapter:不同于处理器映射器,处理器适配器的作用是对处理器进行执行(处理器映射器是找到处理器),HandlerAdapter可以通过扩展适配器对多种类型的适配器进行执行
④处理器Handler:处理器也可以说是后端控制器,需要由自己根据处理器适配器的规范来进行编写,在DispatcherServlet的控制下对具体的用户请求进行处理
⑤视图解析器ViewResolver:负责将结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jsp、freemarker、pdf等。
二、搭建SpringMVC程序
1、首先我们需要知道SpringMVC是Spring的一个部分,所以在编写程序开始的时候需要导入SpringMVC的jar包
2、在导入springmvc的相关jar包之后,需要在web.xml中配置前端控制器DispatcherServlet,在配置前端控制器的时候,我们自己需要手动修改配置springmvc的配置文件以及他的路径
下面就是在web.xml中配置到的前端控制器
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" 5 version="4.0"> 6 <!--配置前端控制器--> 7 <servlet> 8 <servlet-name>SpringMvc</servlet-name> 9 <servlet-class> 10 org.springframework.web.servlet.DispatcherServlet 11 </servlet-class> 12 <!-- 13 配饰SpringMVC的配置文件(处理器映射器、适配器等) 14 注明需要这样配置的原因:自己配置contextConfigLocation,就不会自己默认加载/WEB-INF/下面的dispatch-servlet.xml 15 --> 16 <init-param> 17 <param-name>contextConfigLocation</param-name> 18 <param-value>classpath:springmvc.xml</param-value> 19 </init-param> 20 </servlet> 21 <servlet-mapping> 22 <servlet-name>SpringMvc</servlet-name> 23 <url-pattern>*.do</url-pattern> 24 </servlet-mapping> 25 </web-app>
3、下面说明一下上面配置的url-pattern的问题,在SpringMVC中,DispatcherServlet的拦截方式有下面两种
①*.do、*.action:表示拦截指定后缀的url,这种方式对于服务器端的静态资源(jsp、css、image等)不会拦截
②/ :表示拦截所有资源,这种拦截方式的设计可以实现RESTful风格,后面会介绍到
需要注意的是:不能使用/*(这种方式下,当请求到action后,action处理完返回ModelAndView的jsp信息时,又会被拦截,导致不能根据jsp映射成功)
4、我们上面修改了SpringMVC的配置文件,所以我们下来需要自己修改SpringMVC的配置文件。而在springmvc.xml中,我们需要配置处理器映射器,处理器(Handler),处理器适配器、视图解析器
①配置处理器映射器,处理器映射器会根据配置的beanname找到相应的URL,找到的就是制定的Handler,然后交给Handler进行处理
1 <!-- 2 处理器映射器 3 将bean的NAME作为url进行查找,需要配置handler的时候指定bean的NAME(即url) 4 --> 5 <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
②配置处理器适配器,我们配置的是SimpleControllerHandlerAdapter,查看该类的源代码,会发现下面这个方法,这个方法就是判断我们自己编写的处理器(Handler)是否满足条件(实现Controller接口),所以在接下来编写Handler时需要实现Controller接口以及其中的方法
下面这是处理器适配器的配置
1 <!-- 2 处理器适配器 3 所有处理器适配器都实现了Handler Adapter这个接口,通过观察SimpleControllerHandlerAdapter中的support方法可以发现, 4 我们自己需要实现Controller接口 才能执行 5 --> 6 <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
③根据上面讲的,我们就需要自己编写Handler,我们先查看一下Controller接口的代码,发现Controller只有一个方法,即请求处理然后返回ModelAndView对象
所以下来我们就自己实现这个方法,然后返回ModelAndView(包括其中的数据,和路径信息):
1 package cn.test.springmvc.controller; 2 3 import cn.test.springmvc.po.Product; 4 import org.springframework.web.servlet.ModelAndView; 5 import org.springframework.web.servlet.mvc.Controller; 6 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 /** 13 * 实现Controller的Handler 14 */ 15 public class ItemController implements Controller { 16 17 @Override 18 public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { 19 20 //模拟数据 21 List<Product> products = new ArrayList<>(); 22 23 Product product1 = new Product(); 24 product1.setPname("笔记本"); 25 product1.setShop_price(123); 26 products.add(product1); 27 28 Product product2 = new Product(); 29 product2.setPname("手机"); 30 product2.setShop_price(123); 31 products.add(product2); 32 33 //返回ModelAndView 34 ModelAndView modelAndView = new ModelAndView(); 35 //想模型视图中添加数据 36 modelAndView.addObject("products", products); 37 38 //指定视图 39 modelAndView.setViewName("/WEB-INF/items/itemsList.jsp"); 40 return modelAndView; 41 } 42 }
我们自己编写好Handler之后,就需要根据处理器映射器的要求,在springmvc.xml中配置Handler
1 <!--配置Handler--> 2 <bean name="/queryItemList.do" class="cn.test.springmvc.controller.ItemController"></bean>
配置的beanname就会处理器映射器进行映射,然后执行相应的处理器
④上面我们返回的是jsp页面信息,所以下俩就需要配置视图解析器,解析jsp。
1 <!--视图解析器--> 2 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean>
我们来看一下InternalResourceViewResolver这个类中的代码,该类默认支持jstl解析,所以我们就导入jstl的jar包,正好解释了前面第一步中导包(其中含有jstl包)
private static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
⑤在上面的Handler中,我们返回了一个jsp页面,下面就简单的根据返回信息编写一张简单的jsp页面
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> 5 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 6 <html> 7 <head> 8 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 9 <title>查询商品列表</title> 10 </head> 11 <body> 12 <form action="${pageContext.request.contextPath }/item/queryItem.action" method="post"> 13 查询条件: 14 <table width="100%" border=1> 15 <tr> 16 <td><input type="submit" value="查询"/></td> 17 </tr> 18 </table> 19 商品列表: 20 <table width="100%" border=1> 21 <tr> 22 <td>商品名称</td> 23 <td>商品价格</td> 24 <td>操作</td> 25 </tr> 26 <c:forEach items="${products }" var="item"> 27 <tr> 28 <td>${item.pname }</td> 29 <td>${item.shop_price }</td> 30 31 <td><a href="${pageContext.request.contextPath }/item/editItem.do?id=${item.pid}">修改</a></td> 32 33 </tr> 34 </c:forEach> 35 36 </table> 37 </form> 38 </body> 39 40 </html>
⑥然后在IDEA中部署Tomcat,进行测试
三、基于非注解方式进行编写
1、处理器映射器的非注解配置
上面使用的是BeanNameURLHandlerMapping来进行映射。其简单的执行过程就是:BeanNameUrlHandlerMapping根据请求的url与spring容器中定义的bean的name进行匹配,从而从spring容器中找到bean实例。下面我们使用简单映射器(SimpleSimpleUrlHandlerMapping)进行处理器映射器的配置
其中prop中的key就是配置好url,而value就是我们配置的Handler的id值:<bean id="test" name="/queryItemList.do" class="cn.test.springmvc.controller.ItemController"></bean>
<!--非注解的处理器映射器--> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!--对指定的Controller进行映射--> <prop key="test1.do">test</prop> </props> </property> </bean>
然后我们重启启动项目,在浏览器中输入http://localhost:8080/springmvc1/test1.do,就可以同样实现上面BeanNameUrlHandlerMapping实现的这个功能。
总结一下就是:simpleUrlHandlerMapping是BeanNameUrlHandlerMapping的增强版本,它可以将url和处理器bean的id进行统一映射配置。
2、处理器适配器的非注解配置,我们使用HttpRequestHandlerAdapter来实现,并且通过观察该类可以发现,该适配器所要求的处理器是需要实现HttpRequestHandler接口的
下面就是具体的实现方法
1 package cn.test.springmvc.controller; 2 3 import cn.test.springmvc.po.Product; 4 import org.springframework.web.HttpRequestHandler; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.io.IOException; 10 import java.util.ArrayList; 11 import java.util.List; 12 13 public class ItemHandler implements HttpRequestHandler { 14 @Override 15 public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { 16 //模拟数据 17 List<Product> products = new ArrayList<>(); 18 19 Product p1 = new Product(); 20 p1.setPid(1); 21 p1.setPname("test1"); 22 p1.setShop_price(12); 23 products.add(p1); 24 25 Product p2 = new Product(); 26 p1.setPid(2); 27 p2.setPname("test2"); 28 p2.setShop_price(13); 29 products.add(p2); 30 31 httpServletRequest.setAttribute("products", products); 32 33 httpServletRequest.getRequestDispatcher("/WEB-INF/items/itemsList.jsp").forward(httpServletRequest,httpServletResponse); 34 } 35 }
2、然后同样可以再浏览器中输入http://localhost:8080/springmvc1/test2.do得到测试结果
四、基于注解方式进行开发
1、注解方式和非注解方式开发的不同就是处理器映射器和处理器适配器的配置不同(而且需要注意,如果使用注解的方式进行,那么需要同时使用注解方式的处理器适配器和处理器映射器,不能一个使用非注解的一个使用注解的),我们首先配置基于注解的HandlerMapping
1 <!--基于注解的处理器映射器--> 2 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
2、然后配置基于注解的处理器适配器
1 <!--基于注解的处理器适配器--> 2 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
3、既然是基于注解的方式进行,那么可以不继承或者实现某个特定的接口来进行编写Handler。只需要注意使用Controller注解来表示编写的类是一个处理器,同时编写的方法的返回类型应该是前端控制器所需要的ModelAndView,下面就是简单的一个Handler的编写
1 package cn.test.springmvc.annotation; 2 3 import cn.test.springmvc.po.Product; 4 import org.springframework.stereotype.Controller; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.servlet.ModelAndView; 7 8 import java.util.ArrayList; 9 import java.util.List; 10 11 @Controller 12 public class QueryListHandler { 13 14 @RequestMapping("/queryList.do") 15 public ModelAndView queryList() throws Exception { 16 17 //模拟数据 18 List<Product> productList = new ArrayList<>(); 19 20 Product p1 = new Product(); 21 p1.setPname("AnnotationTest1"); 22 p1.setShop_price(12); 23 24 Product p2 = new Product(); 25 p2.setPname("AnnotationTest2"); 26 p2.setShop_price(12); 27 productList.add(p1); 28 productList.add(p2); 29 30 ModelAndView modelAndView = new ModelAndView(); 31 //想模型视图中添加数据 32 modelAndView.addObject("productList", productList); 33 34 //指定视图 35 modelAndView.setViewName("/WEB-INF/items/itemsList.jsp"); 36 return modelAndView; 37 } 38 }
4、然后在浏览器中进行测试如下:
五、SpringMVC执行过程
1、按照开始介绍的SpringMVC的执行原理框架图来进行分析,首先请求到达前端控制器,这里会调用一个叫做doDispatcher的方法
2、前端控制器中需要找到处理器映射器,即前端控制器将请求url交给处理器映射器,从而根据url得到handler
3、处理器映射器包装请求信息,返回给前端控制器一个处理器执行链
4、前端控制器调用HandlerAdapter(处理器适配器)对HandlerMapping找到Handler进行包装、执行。HandlerAdapter执行Handler完成后,返回了一个ModleAndView(springmvc封装对象)。这个过程就分为两步,第一步就是找到处理器适配器
第二步就是找到处理器适配器调用响应的处理器Handler。然后返回给前端控制器一个ModelAndView
5、这样现在前端控制器就得到了ModelAndView,下面就是交给视图解析器进行解析,解析得到视图View
6、前端控制器将模型数据进行渲染,然后放在reques中