-
- 简介
MVC模式简介
M-Model模型
模型(Model)的职责是负责业务数据。广义的模型包括实体类、DAO、service。
V-View视图
视图(View)的职责是负责显示界面和用户交互(收集用户信息)。属于视图的组件一般是不包含业务逻辑和控制逻辑的JSP。
C-Controller控制器
控制器(Controller)是模型层M和视图层V之间的桥梁,用于控制流程。
什么是SpringMVC
SpringMVC是一个mvc框架,用来简化基于mvc架构的web应用程序的开发。
SpringMVC是Spring框架的一个模块,SpringMVC和Spring无需通过中间整合层进行整合。
SpringMVC的核心组件
名称 | 中文名 | 作用 |
DispatcherServlet | 前端控制器 | 框架核心,接收请求,响应结果,负责调度 |
HanlderMapping | 处理器映射器 | 根据请求的URL查找Handler(Controller) |
HandlerAdapter | 处理器适配器 | 按照特定规则(HandlerAdapter要求的规则)去执行Handler |
Controller(Handler) | 处理器 | 负责请求处理流程。注意:按照HandlerAdapter要求的规则编写Handler,这样才能被HandlerAdapter正确执行 |
ModelAndView | 模型和视图 | 封装业务处理结果和逻辑视图名 |
ViewResolver | 视图解析器 | 进行视图解析,根据逻辑视图名解析成真正的视图 |
View | 视图 | View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...) |
SpringMVC的处理流程
1):用户发起请求到DispatcherServlet
2):DispatcherServlet请求HandlerMapping查找 Handler(可以根据xml配置、注解进行查找)
3):DispatcherServlet调用HandlerAdapter去执行Handler(Controller),Handler返回ModelAndView
4):DispatcherServlet请求ViewResolver去解析ModelAndView(根据逻辑视图名解析成真正的视图(jsp)),ViewResolver返回View
5):DispatcherServlet进行视图渲染,将Model填充到request域
6):DispatcherServlet向用户响应结果
前后端分离项目,从输入URL地址,到页面完成展示之间发生了什么
- 浏览器查找强缓存,如果命中则直接返回资源文件,否则进入下一步
- DNS解析,根据域名查到ip后向目标ip发送请求。DNS解析顺序:
-》浏览器DNS缓存
-》本地操作系统DNS缓存
-》路由器/交换机DNS缓存
-》互联网DNS缓存服务器
- 服务器对应端口侦听的web服务(以Tomcat为例)连接器(Connector)获得请求
- web服务连接器将请求交给服务引擎(Engine)
- 服务引擎根据域名查找主机(Host)
- 主机根据URI定位Web应用程序(Context)
- Web应用程序(以SpringMVC为例)根据URI定位资源:
-》DispatcherServlet接收请求,通过HandlerMapping查找Handler
-》DispatcherServlet调用HandlerAdapter去执行Handler(Controller)
-》Handler调用其他Spring组件处理业务,查询数据库,返回数据
-》DispatcherServlet请求HttpMessageConverter将数据转换为JSON
-》DispatcherServlet把JSON数据写入HttpServletResponse
- web服务(以Tomcat为例)原路返回HttpServletResponse:
Context-》Host-》Engine-》Connector-》浏览器
- 客户端浏览器接收响应,渲染页面
详见:一次搞定前端“核心主线”——从输入URL到页面展示发生了什么 - 知乎
-
- XML配置的SpringMVC编程步骤
搭建环境
1)导包
需要Spring的所有jar包和Commons Logging日志包。此处示例版本为spring-framework-3.2.8.RELEASE 和commons-logging-1.2
Spring包下载地址: http://repo.springsource.org/libs-release-local/org/springframework/spring/ 选择需要的版本
Commons Logging包下载地址: Apache Commons Logging - Download Apache Commons Logging 选择需要的版本
2)在src下添加Spring的xml配置文件,名称可自定,例如spring-mvc.xml
代码略,初始内容同applicationContext.xml
配置DispatcherServlet组件
3)在web.xml中配置前端控制器组件DispatcherServlet并指定上一步添加的Spring配置文件的路径。
web-app根元素 <!-- ========== 配置SpringMVC的前端控制器 ========== --> <servlet> <!-- DispatcherServlet的初始化方法会启动spring容器 --> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- contextConfigLocation用于指定SpringMVC的配置文件路径,不配置则默认为/WEB-INF/servlet名称-serlvet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <!-- 在应用启动时立即加载该servlet --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 第一种(*.后缀):访问以 .后缀 结尾,由DispatcherServlet进行解析 第二种(/):所以访问的地址都由DispatcherServlet进行解析,对于静态文件的解析需要配置不让DispatcherServlet进行解析 使用此种方式可以实现 RESTful风格的url 第三种(/*):这样配置不对,使用这种配置,最终要转发到一个jsp页面时,仍然会由DispatcherServlet解析jsp地址,不能根据jsp页面找到handler,会报错。 --> <url-pattern>*.do</url-pattern> </servlet-mapping> web-app根元素 |
步骤2)3)的原理:DispatcherServlet会根据配置文件信息创建一个WebApplicationContext容器对象,也称为上下文环境。WebApplicationContext继承自ApplicationContext容器,其初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也就是说,它必须在拥有Web容器的前提下才能完成启动Spring Web应用上下文的工作。有了WebApplicationContext,就可以使用Spring的IOC、AOP等其他功能了。
编写Controller组件和ModelAndView组件
4)写Controller(处理器)的实现类,实现handleRequest方法,该方法返回ModelAndView对象,该对象可封装模型数据和逻辑视图名信息
package com.tongwx.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 HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { /* * ModelAndView有两个构造器: * (1)ModelAndView(String viewName), viewName是视图名。 * (2)ModelAndView(String viewName, Map modelData), modelData是处理结果数据,将存储到request的attribute中。 */ return new ModelAndView("hello"); } } |
5)写jsp
/WEB-INF/hello.jsp:
<h1>Hello,SpringMVC!</h1> |
配置HandlerMapping组件和ViewResolver组件
6)在Spring配置文件中配置HandlerMapping和ViewResolver
HandlerMapping:
通过HandlerMapping组件,前端控制器DispatcherServlet可以将客户HTTP请求映射到Controller组件上。
Spring提供了多种基于XML配置的HandlerMapping实现类,具体如下:
SimpleUrlHandlerMapping:维护一个HTTP请求和Controller映射关系列表(map),根据列表对应关系调用Controller。
ViewResolver:
视图解析器ViewResolver通过Controller组件返回的ModelAndView实例中的逻辑视图名来解析视图。
Spring提供了多种视图解析器,具体如下:
UrlBasedViewResolver:将视图名直接解析成对应的URL,不需要显示地映射定义。 若视图名与视图资源名一致,就可以使用该解析器,而无需映射。
InternalResourceViewResolver:UrlBasedViewResolver的子类。它支持InternalResourceView(对Servlet和JSP的包装),以及其子类JstlView和TilesView响应类型。
VelocityViewResolver/FreeMarkerViewResolver:UrlBasedViewResolver的子类。它能支持Velocity和FreeMarker等视图技术。
beans根元素 <!-- ========== 配置处理器映射器HandlerMapping ========== --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!-- 第一步:指定url对应哪个handler名 --> <prop key="/hello.do">helloController</prop> </props> </property> </bean> <!-- 第二步:指定handler名对应哪个handler类 --> <bean id="helloController" class="com.tongwx.controller.HelloController" /> <!-- ========== 配置视图解析器ViewResolver ========== --> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> beans根元素 |
7)测试:
浏览器访问:http://localhost:8080/mySpringMvcDemo/hello.do
-
- SpringMVC编程步骤
使用注解声明Controller组件,可使Controller组件不用实现Controller接口,处理请求的方法因此可以灵活定义。
搭建环境
1)导包。方法同前。
2)添加一个Spring的配置文件。方法同前。
配置DispatcherServlet组件
3)配置DispatcherServlet并指定Spring配置文件路径。方法同前。
编写Controller组件和ModelAndView组件
4)写Controller(处理器)。在类上添加@Controller注解,在类或方法上添加@RequestMapping注解并指定请求URI参数。这样就不需实现Controller接口,方法可以自定义,返回值可以是ModelAndView, 也可以是String。
package com.tongwx.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HiController { @RequestMapping("/hi.do") public String hello() { //返回一个视图名 return "hi"; } } |
5)写jsp
/WEB-INF/hi.jsp:
<h1>Hi,SpringMVC!</h1> |
配置HandlerMapping组件和ViewResolver组件
6)在Spring配置文件中配置以下三项内容:
开启组件扫描,指定Controller组件所在包,使@Controller注解生效;
开启MVC注解扫描,以扫描MVC的@RequestMapping等注解;
配置视图解析器,方法同前。
context:component-scan会扫描指定包的@Component、@Controller,@Service,@Respository注解。
context:component-scan还会扫描@Required、@Autowired、@PostConstruct、@PersistenceContext、@Resource、@PreDestroy等注解,因此
context:component-scan配了就不用配<context:annotation-config/>了。
Spring3.1及之前的版本需作如下配置:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/><!-- 类前注解 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/><!-- 方法前注解 -->
Spring3.2及之后的版本除了可用简化的<mvc:annotation-driven />配置,和Spring3.1及之前的配置外,还可用如下配置:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/><!-- 类前注解 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/><!-- 方法前注解 -->
beans根元素 <!-- 配置Spring组件扫描 --> <context:component-scan base-package="com.tongwx.controller" /> <!-- 配置MVC注解扫描 --> <mvc:annotation-driven /> <!-- 配置视图解析器 --> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /><!-- 勿忘最后的斜杠 --> <property name="suffix" value=".jsp" /> </bean> beans根元素 |
7)测试:
浏览器访问:http://localhost:8080/mySpringMvcDemo/hi.do
接收请求参数值(前端为地址传参或表单传参时)
方式一 提供HttpServletRequest类型的方法参数
将request对象作为方法的入参即可,Spring会自动向入参注入请求参数值。
优点直接,缺点需要自己处理数据类型转换。
@RequestMapping("/checkLogin.do") public String checkLogin(HttpServletRequest request) { //从入参request中获取请求参数值 String username = request.getParameter("username"); String password = request.getParameter("password"); //向页面传值 request.setAttribute("username", username); if (null != username && null != password && "tongwx".equals(username) && "123456".equals(password)) { return "welcome"; } return "error"; } |
方式二 提供与请求参数对应的方法参数
参数名一致时,Spring会自动将请求参数注入到方法参数;参数名不一致时,使用 @RequestParam("页面参数名") 注解映射不一致的名称。
优点参数类型自动转换,但可能出现类型转换异常。
@RequestMapping("/checkLogin2.do") public ModelAndView checkLogin2(String username, @RequestParam("password") String pwd) { //参数名一致时,Spring会自动将表单参数注入到方法参数 System.out.println(username); //参数名不一致时,使用 @RequestParam("页面参数名") 注解映射不一致的名称 System.out.println(pwd); //封装向页面传的值 Map<String, String> map = new HashMap<String, String>(); map.put("username", username); if (null != username && null != pwd && "tongwx".equals(username) && "123456".equals(pwd)) { //向页面传值 return new ModelAndView("welcome", map); } return new ModelAndView("error"); } |
方式三 提供封装请求参数的JavaBean对象作为方法参数
step1. 写一个JavaBean类,封装各个请求参数,属性名与请求参数的name相同。
step2. 将JavaBean对象作为处理器的方法参数。String将自动将请求参数注入对象。
@RequestMapping("/checkLogin3.do") public ModelAndView checkLogin3(User user) { System.out.println(user.getUsername()); System.out.println(user.getPassword()); //封装向页面传的值 Map<String, String> map = new HashMap<String, String>(); map.put("username", user.getUsername()); if (null != user.getUsername() && null != user.getPassword() && "tongwx".equals(user.getUsername()) && "123456".equals(user.getPassword())) { //向页面传值 return new ModelAndView("welcome", map); } return new ModelAndView("error"); } |
特殊方式:路径参数
参数名一致时,可省略value属性
@Controller } |
前端请求url:http://localhost:8080/user/0013
向页面传值
(1)第一种方式 使用request对象
将数据绑订到request对象,然后转发给jsp。
(2)第二种方式 使用session对象
将数据绑订到session对象。
(3)第三种方式 使用ModelAndView对象
将数据封装到ModelAndView对象,然后作为方法的返回值。Model数据会通过HttpServletRequest的Attribute传到JSP页面中。
(4)第四种方式 使用ModelMap对象
将该对象作为方法的形参,将数据添加到该对象里面即可。ModelMap数据会通过HttpServletRequest的Attribute传到JSP页面中。
(4)第五种方式 使用@ModelAttribute注解
在处理器方法的参数部分或JavaBean属性方法上使用@ModelAttribute("JSP页面提取的名字")注解,@ModelAttribute数据就会通过HttpServletRequest的Attribute传到JSP页面中。
如果JSP的参数名和处理器方法的参数名一致,可省略该注解。
@RequestMapping("/checkLogin4.do") //在处理器方法的参数部分或JavaBean属性方法上使用@ModelAttribute("JSP页面提取的名字")注解, //@ModelAttribute数据就会通过HttpServletRequest的Attribute传到JSP页面中。 //如果JSP的参数名和处理器方法的参数名一致,可省略@ModelAttribute注解。 public String checkLogin4(@ModelAttribute("u") User user) {//JSP代码:${requestScope.u.username } System.out.println(user.getUsername()); System.out.println(user.getPassword()); if (null != user.getUsername() && null != user.getPassword() && "tongwx".equals(user.getUsername()) && "123456".equals(user.getPassword())) { return "welcome"; } return "error"; } |
@ModelAttribute("username") public String getUsername() { return username; } |
重定向
SpringMVC默认采用转发方式定位视图,如果需要重定向方式可采用下面几种方法:
(1)方法的返回值是String时加上redirect:前缀,例如:
return "redirect:toIndex.do";
(2)方法的返回值是ModelAndView时以RedirectView为其构造参数,例如:
return new ModelAndView(new RedirectView("toIndex.do"));
处理表单编码问题
在web.xml中添加SpringMVC提供的一个过滤器(CharacterEncodingFilter)来解决:
web-app根元素 <!-- ========== 配置中文乱码过滤器 ========== --> <filter> <filter-name>encodingFilter</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>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> web-app根元素 |
注意:
a. 表单数据要以post方式提交。
b. 页面编码要与过滤器指定的编码保持一致。
SpringMVC中获取HttpServletRequest的几种方法
- 方法参数:在Controller的方法参数上写上HttpServletRequest
- 从RequestContextHolder上下文获取:((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
- 依赖注入:@Autowired HttpServletRequest request;或@Resource HttpServletRequest request;注意不要使用
@ModelAttribute
- 先获取HttpServletRequest(方法见上面),再request.getSession()
- 方法参数:在Controller的方法参数上写上HttpSession session
- 使用@SessionAttributes:这种方式并不是直接获取到session,但是我们可以通过这种方式将我们想要值放入到session中去。
什么是拦截器
Spring提供的一个特殊的组件,前端控制器( DispatcherServlet)在收到请求之后,会先调用 拦截器,再调用处理器(Controller)。
注:
过滤器属于Servlet规范当中定义的特殊的组件。 而拦截器属于Spring框架。
如何写一个拦截器
step1. 写一个java类,实现HandlerInterceptor接口,在接口方法中实现具体的拦截处理逻辑。
拦截流程(两种):
preHandle(...)返回true --> 处理器方法 --> postHandle(...) --> 视图处理 -->afterCompletion(...);
preHandle(...)返回false --> 中断流程,不执行后续拦截器和处理器。
如果只需要实现某个方法,可以继承HandlerInterceptorAdapter。
package com.tongwx.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * 登录验证拦截器 */ public class LoginHandlerInterceptor implements HandlerInterceptor { /** * 拦截除了登录之外的所有请求,并判断session中是否存在登录信息,若不存在则重定向到登录页面。 */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(request.getRequestURI()); HttpSession session = request.getSession(); if (null == session.getAttribute("username")) { response.sendRedirect("toLogin.do"); return false; } return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } |
step2. 在Spring配置文件中配置拦截器。
beans根元素 <!-- 配置登录验证拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 拦截路径 --> <mvc:mapping path="/**"/> <!-- 要排除的路径 --> <mvc:exclude-mapping path="/toLogin.do"/> <mvc:exclude-mapping path="/login*.do"/> <!-- 拦截器类 --> <bean class="com.tongwx.interceptor.LoginHandlerInterceptor"></bean> </mvc:interceptor> </mvc:interceptors> beans根元素 |
将异常抛给Spring框架,让Spring框架来处理异常。
方式一:全局处理:配置简单异常处理器
step1.在Spring配置文件中,配置简单异常处理器(SimpleMappingExceptionResovler)。
<!-- 配置简单异常处理器 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- 指定异常类型和对应视图名 --> <prop key="java.lang.NumberFormatException">error1</prop> <prop key="java.lang.StringIndexOutOfBoundsException">error2</prop> </props> </property> </bean> |
step2.添加异常处理页面。获取异常对象名:${exception }
方式二:全局处理:实现HandlerExceptionResolver接口自定义异常处理器
适合全局处理有处理过程的异常。
step1.自定义一个异常处理器类实现HandlerExceptionResolver接口
import java.util.HashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; public class MyMappingExceptionResolver implements HandlerExceptionResolver { public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { HashMap<String, Object> model = new HashMap<String, Object>(); model.put("ex", ex); return new ModelAndView("exception", model); } } |
step2.在Spring配置文件中配置自定义的异常处理器
<!-- 配置自定义异常处理器 --> <bean id="exceptionHandler" class="com.tongwx.exception.MyMappingExceptionResolver"></bean> |
step3.添加异常处理页面。
方式三:局部处理:使用@ExceptionHandler注解
适合局部处理有处理过程的异常。
step1. 在处理器类当中,添加一个异常处理方法,处理当前处理器的其它方法所抛出的异常。
/** * 异常处理方法(处理当前处理器的其它方法所抛出的异常) */ @ExceptionHandler public String execute(Exception e, HttpServletRequest request) { //依据异常类型,分别做不同的处理 if (e instanceof NumberFormatException) { //异常的处理... request.setAttribute("errorMsg", "数字转换异常"); return "exception2"; } else if (e instanceof StringIndexOutOfBoundsException) { request.setAttribute("errorMsg", "下标越界"); return "exception2"; } request.setAttribute("errorMsg", e); return "system_error"; } |
step2.添加异常处理页面。
注:如要批量处理局部异常,可定义一个局部异常处理类,添加上述方法,然后让需要使用该异常处理方法的处理器继承该类。
-
- 定义一个统一返回对象包装类:
- 定义一个统一返回注解,用于注解要使用统一返回的Controller类或其方法:
- 定义一个实现ResponseBodyAdvice接口的统一返回通知类:
- 返回String类型的包装需要特殊处理:
- 定义一个统一异常类:
- 在需要统一返回的类或方法上添加@GlobalResponseBody注解:
- 测试POST请求:http://localhost:8080/user/testReturnString,将返回:
package com.tongwx.demo.config; |
package com.tongwx.demo.config; |
package com.tongwx.demo.config; |
package com.tongwx.demo.config; |
package com.tongwx.demo.config; log.error(ex.getMessage(), ex); |
package com.tongwx.demo.controller; |
{ "obj": "好", "success": true, "failureCode": null, "failureMessage": null } |
Spring MVC默认是单例模式,Controller、Service、Dao都是单例,减少了对象创建和垃圾收集的时间。
由于只有一个Controller的实例,当多个线程同时调用它的时候,它的成员变量就不是线程安全的,因此尽量避免在Controller中使用成员变量。如果一定要使用成员变量,则可以改用以下方式:
1. Controller中声明 scope=”prototype”,即设置为多例模式
2.在Controller中使用ThreadLocal变量,如:private ThreadLocal<Integer> count = new ThreadLocal<Integer>();
-
- + SpringMVC框架中的四种容器
容器 | Web容器 | Servlet容器 | Spring容器 | SpringMVC容器 |
英文 | 如Tomcat | ServletContext | Root WebApplicationContext | Servlet WebApplicationContext |
创建者 | 服务器 | 服务器 | ContextLoaderListener | DispatcherServlet |
默认配置文件 | server.xml | web.xml | applicationContext.xml | servlet名称-serlvet.xml |
SpringMVC的启动过程:
Servlet容器
项目启动时,Tomcat会扫描每个WebApp的web.xml配置文件,找到Filter、Listener和Servlet等配置,并为每个WebApp创建各自的全局上下文servlet容器(ServletContext),每个WebApp下的所有Filter、Listener和Servlet共享一个servlet容器。
Spring容器(父上下文)
web.xml中配置的ContextLoaderListener在监听到ServletContext初始化时,会加载context-param标签内指定的配置文件(默认名称applicationContext.xml)并在contextInitialized方法中创建Spring的Ioc容器(Root WebApplicationContext接口,实际实现类是XmlWebApplicationContext),管理service层、dao层的Bean。
ContextLoaderListener会将Spring容器的引用存放到ServletContext中一个属性中,key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值,可以使用Spring提供的工具类取出上下文对象:WebApplicationContextUtils.getWebApplicationContext(ServletContext);
SpringMVC容器(子上下文)
Spring容器初始化完毕后,第一个请求到达时,web.xml中配置的DispatcherServlet开始初始化,读取自己init-param标签内的配置文件(默认名称servlet名称-serlvet.xml)并在initStrategies方法中创建SpringMVC容器(Servlet WebApplicationContext接口,实际实现类也是XmlWebApplicationContext),管理Controller层的Bean。每个 DispatcherServlet有自己的SpringMVC容器。
DispatcherServlet会将SpringMVC容器的引用存放到ServletContext中一个属性中,key是"org.springframework.web.servlet.FrameworkServlet.CONTEXT"+Servlet名称,同时也存放到Request对象中,key是DispatcherServlet.class.getName() + ".CONTEXT"。可以使用工具类取出上下文对象:RequestContextUtils.getWebApplicationContext(request);
父子容器关系
DispatcherServlet通过ServletContext获取Spring容器,并将Spring容器设置为SpringMVC容器的父级容器。
子容器可以访问父容器,父容器不能访问子容器,从而在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。
父子容器的选用
说明 :Spring 并没有限制我们,必须使用父上下文。我们可以自己决定如何使用。
方案一,传统型:
父上下文容器中保存数据源、服务层、DAO层、事务的Bean。
子上下文容器中保存Mvc相关的Action的Bean.
事务控制在服务层。
由于父上下文容器不能访问子上下文容器中内容,事务的Bean在父上下文容器中,无法访问子上下文容器中内容,就无法对子上下文容器中Action进行AOP(事务)。aop要想切入Controller,需要在父容器中配置扫描@Controller,但可能导致子容器无法生成前端控制器,导致页面请求404。
当然,做为“传统型”方案,也没有必要这要做。
方案二,激进型:
Java世界的“面向接口编程”的思想是正确的,但在增删改查为主业务的系统里,Dao层接口,Dao层实现类,Service层接口,Service层实现类,Action父类,Action。再加上众多的O(vo\po\bo)和jsp页面。写一个小功能 7、8个类就写出来了。 开发者说我就是想接点私活儿,和PHP,ASP抢抢饭碗,但我又是Java程序员。最好的结果是大项目能做好,小项目能做快。所以“激进型”方案就出现了-----没有接口、没有Service层、还可以没有众多的O(vo\po\bo)。那没有Service层事务控制在哪一层?只好上升到Action层。
本文不想说这是不是正确的思想,我想说的是Spring不会限制你这样做。
由于有了父子上下文,你将无法实现这一目标。解决方案是只使用子上下文容器,不要父上下文容器。所以数据源、服务层、DAO层、事务的Bean、Action的Bean都放在子上下文容器中。就可以实现了,事务(注解事务)就正常工作了。这样才够激进。
总结:不使用listener监听器来加载spring的配置文件,只使用DispatcherServlet来加载spring的配置,不要父子上下文,只使用一个DispatcherServlet,事情就简单了,什么麻烦事儿也没有了。
Java--大项目能做好--按传统方式做,规规矩矩的做,好扩展,好维护。
Java--小项目能做快--按激进方式做,一周时间就可以出一个版本,先上线接受市场(用户)的反馈,再改进,再反馈,时间就是生命(成本)。
测试Spring父子容器配置
1)SpringMVC的配置文件一定要放在DispatcherServlet中。
2)SpringAOP若想切入MVC,那么就把Spring配置文件也放在DispatcherServlet中。
测试结果:
Spring配置文件 | SpringMVC配置文件 | <aop:after-throwing> 统一异常切入记日志 | HandlerExceptionResolver 统一异常拦截记日志并跳转错误页面 | 事务 |
全部配置文件放到DispatcherServlet中 | ||||
扫描所有注解 | 不扫描 | 切入MVC切入Spring切入dao | 拦截MVC拦截Spring拦截dao | 事务正常,Spring异常回滚 |
不扫描 | 扫描所有注解 | 切入MVC切入Spring切入dao | 拦截MVC拦截Spring拦截dao | 事务正常,Spring异常回滚 |
扫描除Controller以外所有注解 | 只扫描Controller注解 | 切入MVC切入Spring切入dao | 拦截MVC拦截Spring拦截dao | 事务正常,Spring异常回滚 |
扫描所有注解 | 扫描所有注解 | 切入MVC切入Spring切入dao | 拦截MVC拦截Spring拦截dao | 事务正常,Spring异常回滚 |
全部配置文件放到ContextLoaderListener中 | ||||
随你怎么配 | 随你怎么配 | 启动报错 | ||
DispatcherServlet导ApplicationContext-mvc,ContextLoaderListener导applicationContext-* | ||||
扫描所有注解 | 扫描所有注解 | 未切入MVC未切入Spring切入dao | 拦截MVC拦截Spring拦截dao | 事务失效,Spring异常未回滚 |
扫描除Controller以外所有注解 | 只扫描Controller注解 | 未切入MVC切入Spring切入dao | 拦截MVC拦截Spring拦截dao | 事务正常,Spring异常回滚 |
扫描所有注解 | 只扫描Controller注解 | 未切入MVC切入Spring切入dao | 拦截MVC拦截Spring拦截dao | 事务正常,Spring异常回滚 |
扫描所有注解 | 不扫描 | 前端404 | 前端404 | |
不扫描 | 扫描所有注解 | 未切入MVC未切入SpringC切入dao | 拦截MVC拦截Spring拦截dao | 事务失效,Spring异常未回滚 |