1.Spring背景
1.1.Spring四大原则:
- 使用POJO进行轻量级和最侵入式开发;
- 通过依赖注入和基于借口编程实现松耦合;
- 通过AOP和默认习惯进行声明式编程;
- 使用AOP和模板(template)减少模块化代码
Spring所有功能的设计和实现都是基于这四大原则的。
1.2.依赖注入
容器负责创建对象和维护对象之间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。依赖注入的主要目的是解耦,体现一种“组合”的理念,如果你希望你的类具备某项功能的时候,是继承自具有此项功能的父类好,还是组合另一个具有这个功能的类好,答案不言而喻。
Spring IoC容器负责创建Bean,并将功能类Bean注入到你需要的Bean中。Spring提供xml配置、注解、Java配置、groovy配置实现Bean的创建和注入。
声明Bean的注解:
@Component组件:没有明确的角色
@Service:业务逻辑层使用
@Repository:数据访问层(DAO)使用
@Controller:展现层使用
注入Bean的注解
@Autowire:Spring提供的注解
@Inject:JSR-330标准提供的注解
@Resource:JSR-250标准提供的注解
一般情况下通用
1.3.Java配置
Spring4.x推荐的配置方式,也是Spring Boot推荐的配置方式,可以完全替代xml配置。
@Configuration:Java配置配置方式,声明当前类是一个配置类,与使用xml配置文件效果一样
@ComponentScan 自动扫描报名下所有使用@Service @Component @Repository @Controller的类并注册为Bean
@Bean注解在方法上,声明当前方法的返回值为一个Bean
2.MVC模型
MVC模型是一种架构型的模式,本身不引入新功能,只是帮助我们将开发的结构组织的更加合理,使展示与模型分离、流程控制逻辑、业务逻辑调用与展示逻辑分离。
Web端的开发发展流程如下图
这里只讲述服务到工作者:Front Controller + Application Controller + Page Controller + Context
运行流程如下:
我们回顾了整个web开发架构的发展历程,可能不同的web层框架在细节处理方面不同,但的目的是一样的:
1、 干净的web表现层:
- 模型和视图的分离;
- 控制器中的控制逻辑与功能处理分离(收集并封装参数到模型对象、业务对象调用);
- 控制器中的视图选择与具体视图技术分离。
2、 轻薄的web表现层:
- 做的事情越少越好,薄薄的,不应该包含无关代码;
- 只负责收集并组织参数到模型对象,启动业务对象的调用;
- 控制器只返回逻辑视图名并由相应的应用控制器来选择具体使用的视图策略;
- 尽量少使用框架特定API,保证容易测试。
3.Spring MVC模型
Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发
Spring Web MVC也是服务到工作者模式的实现,但进行可优化。
- 前端控制器是DispatcherServlet;
- 应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器****(View Resolver)进行视图管理;
- 页面控制器/动作/处理器为Controller****接口(仅包含ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的POJO类);
- 支持本地化(Locale)解析、主题(Theme)解析及文件上传等;
- 提供了非常灵活的数据验证、格式化和数据绑定机制;
- 提供了强大的约定大于配置(惯例优先原则)的契约式编程支持。
3.1.Spring Web MVC能做什么
- 让我们能非常简单的设计出干净的Web层和薄薄的Web层;
- 进行更简洁的Web层的开发;
- 天生与Spring框架集成(如IoC容器、AOP等);
- 提供强大的约定大于配置的契约式编程支持;
- 能简单的进行Web层的单元测试;
- 支持灵活的URL到页面控制器的映射;
- 非常容易与其他视图技术集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用);
- 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API;
- 提供一套强大的JSP标签库,简化JSP开发;
- 支持灵活的本地化、主题等解析;
- 更加简单的异常处理;
- 对静态资源的支持;
- 支持Restful风格。
3.2.Spring Web MVC处理请求的流程
具体执行步骤如下:
1、 首先用户发送请求—>前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;图中的1、2步骤;
2、 页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在Spring Web MVC中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名);图2-1中的3、4、5步骤;
3、 前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;图2-1中的步骤6、7;
4、 前端控制器再次收回控制权,将响应返回给用户,图2-1中的步骤8;至此整个结束
3.3.疑惑(后面解答)
1、 请求如何给前端控制器?
2、 前端控制器如何根据请求信息选择页面控制器进行功能处理?
3、 如何支持多种页面控制器呢?
4、 如何页面控制器如何使用业务对象?
5、 页面控制器如何返回模型数据?
6、 前端控制器如何根据页面控制器返回的逻辑视图名选择具体的视图进行渲染?
7、 不同的视图技术如何使用相应的模型数据?
4.Spring核心框架
核心架构的具体流程步骤如下:
1、 用户发送请求 -> DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、 DispatcherServle -> HandlerMapping:HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、 DispatcherServlet -> HandlerAdapter:HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、 HandlerAdapter -> 处理器:HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
5、 ModelAndView -> ViewResolver:ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、 渲染View :View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、 DispatcherServlet****响应:返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
此处我们只是讲了核心流程,没有考虑拦截器、本地解析、文件上传解析等。
现在可以回答上述提出的问题:
1、 请求如何给前端控制器?
答:这个应该在web.xml中进行部署描述
2、 前端控制器如何根据请求信息选择页面控制器进行功能处理?
答:我们需要配置HandlerMapping进行映射
3、 如何支持多种页面控制器呢?
答:配置HandlerAdapter从而支持多种类型的页面控制器
4、 页面控制器如何使用业务对象?
答:利用Spring IoC容器的依赖注入功能
5、 页面控制器如何返回模型数据?
答:使用ModelAndView返回
6、 前端控制器如何根据页面控制器返回的逻辑视图名选择具体的视图进行渲染?
答: 使用ViewResolver进行解析
7、 不同的视图技术如何使用相应的模型数据?
答:因为Model是一个Map数据结构,很容易支持其他视图技术
org.springframework.web.servlet.DispatcherServlet # doDispatch :
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.(通过HandlerMapping映射获取)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug(
"Last-Modified value for [" + getRequestUri(request) + "] is: "
+ lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified)
&& isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler. 适配器执行处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
通过阅读源码,可以看出具体的核心开发步骤:
1、 DispatcherServlet在web.xml中的部署描述,从而拦截请求到Spring Web MVC
2、 HandlerMapping的配置,从而将请求映射到处理器
3、 HandlerAdapter的配置,从而支持多种类型的处理器
4、 ViewResolver的配置,从而将逻辑视图名解析为具体视图技术
5、 处理器(页面控制器)的配置,从而进行功能处理
5.注解式开发
Spring2.5引入注解式处理器支持,通过@Controller 和 @RequestMapping注解定义我们的处理器类。并且提供了一组强大的注解:
需要通过处理器映射DefaultAnnotationHandlerMapping和处理器适配器AnnotationMethodHandlerAdapter来开启支持@Controller 和 @RequestMapping注解的处理器。
- @Controller:用于标识是处理器类;
- @RequestMapping:请求到处理器功能方法的映射规则;
- @RequestParam:请求参数到处理器功能处理方法的方法参数上的绑定;
- @ModelAttribute:请求参数到命令对象的绑定;
- @SessionAttributes:用于声明session级别存储的属性,放置在处理器类上,通常列出模型属性(如@ModelAttribute)对应的名称,则这些属性会透明的保存到session中;
- @InitBinder:自定义数据绑定注册支持,用于将请求参数转换到命令对象属性的对应类型;
Spring3.0引入RESTful架构风格支持(通过@PathVariable注解和一些其他特性支持),且又引入了更多的注解支持:
- @CookieValue:cookie数据到处理器功能处理方法的方法参数上的绑定;
- @RequestHeader:请求头(header)数据到处理器功能处理方法的方法参数上的绑定;
- @RequestBody:请求的body体的绑定(通过HttpMessageConverter进行类型转换);
- @ResponseBody:处理器功能处理方法的返回值作为响应体(通过HttpMessageConverter进行类型转换);
- @ResponseStatus:定义处理器功能处理方法/异常处理器返回的状态码和原因;
- @ExceptionHandler:注解式声明异常处理器;
- @PathVariable:请求URI中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持RESTful架构风格的URI;
6.DispatcherServlet
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:
1、 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
2、 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
3、 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
4、 通过ViewResolver解析逻辑视图名到具体视图实现;
5、 本地化解析;
6、 渲染具体的视图等;
7、 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。
DispatcherServlet主要负责流程的控制,而且在流程中的每个关键点都是很容易扩展的。
DispatcherServlet默认使用WebApplicationContext作为上下文,因此我们来看一下该上下文中有哪些特殊的Bean:
1、 Controller:处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理;
2、 HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器;
3、 HandlerAdapter:HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且掉处理器的handleRequest方法进行功能处理;
4、 ViewResolver:ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图;
5、 LocalResover:本地化解析,因为Spring支持国际化,因此LocalResover解析客户端的Locale信息从而方便进行国际化;
6、 ThemeResovler:主题解析,通过它来实现一个页面多套风格,即常见的类似于软件皮肤效果;
7、 MultipartResolver:文件上传解析,用于支持文件上传;
8、 HandlerExceptionResolver:处理器异常解析,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息);
9、 RequestToViewNameTranslator:当处理器没有返回逻辑视图名等相关信息时,自动将请求URL映射为逻辑视图名;
10、 FlashMapManager:用于管理FlashMap的策略接口,FlashMap用于存储一个请求的输出,当进入另一个请求时作为该请求的输入,通常用于重定向场景。
ContextLoaderListener初始化的上下文加载的Bean是对于整个应用程序共享的,不管是使用什么表现层技术,一般如DAO层、Service层Bean;
DispatcherServlet初始化的上下文加载的Bean是只对Spring Web MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,该初始化上下文应该只加载Web相关组件。
Controller
Controller控制器,是MVC中的部分C,为什么是部分呢?因为此处的控制器主要负责功能处理部分:
1、 收集、验证请求参数并绑定到命令对象;
2、 将命令对象交给业务对象,由业务对象处理并返回模型数据;
3、 返回ModelAndView(Model部分是业务对象返回的模型数据,视图部分为逻辑视图名)。
还记得DispatcherServlet吗?主要负责整体的控制流程的调度部分:
1、负责将请求委托给控制器进行处理;
2、根据控制器返回的逻辑视图名选择具体的视图进行渲染(并把模型数据传入)。
因此MVC中完整的C(包含控制逻辑+功能处理)由(DispatcherServlet + Controller)组成。
Controller继承关系图
7.拦截器
请求的映射分为如下几种:
- URL路径映射:使用URL映射请求到处理器的功能处理方法;
- 请求方法映射限定:如限定功能处理方法只处理GET请求;
- 请求参数映射限定:如限定只处理包含“abc”请求参数的请求;
- 请求头映射限定:如限定只处理“Accept=application/json”的请求。
可以通过在一个POJO类上放置@Controller或@RequestMapping,即可把一个POJO类变身为处理器;
@RequestMapping(value = "/hello") 请求URL(/hello) 到 处理器的功能处理方法的映射;
模型数据和逻辑视图名的返回。
package cn.javass.chapter6.web.controller;
//省略import
@Controller // 或 @RequestMapping ①将一个POJO类声明为处理器
public class HelloWorldController {
@RequestMapping(value = "/hello") //②请求URL到处理器功能处理方法的映射
public ModelAndView helloWorld() {
//1、收集参数
//2、绑定参数到命令对象
//3、调用业务对象
//4、选择下一个页面
ModelAndView mv = new ModelAndView();
//添加模型数据 可以是任意的POJO对象
mv.addObject("message", "Hello World!");
//设置逻辑视图名,视图解析器会根据该名字解析到具体的视图页面
mv.setViewName("hello");
return mv; //③ 模型数据和逻辑视图名
}
}
如果您使用的是Spring3.1之前版本,开启注解式处理器支持的配置为:
<!—Spring3.1之前的注解 HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<!—Spring3.1之前的注解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
如果您使用的Spring3.1开始的版本,建议使用RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
<!--Spring3.1开始的注解 HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--Spring3.1开始的注解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
运行流程:
@Controller
public class HelloWorldController {
……
}
推荐使用这种方式声明处理器,它和我们的@Service、@Repository很好的对应了我们常见的三层开发架构的组件。
@RequestMapping
@RequestMapping
public class HelloWorldController {
}
这种方式也是可以工作的,但如果在类上使用@ RequestMapping注解一般是用于
窄化功能处理方法的映射的,详见6.4.3。
package cn.javass.chapter6.web.controller;
@Controller
@RequestMapping(value="/user") //①处理器的通用映射前缀
public class HelloWorldController2 {
@RequestMapping(value = "/hello2") //②相对于①处的映射进行窄化
public ModelAndView helloWorld() {
}
}
窄化请求映射
package cn.javass.chapter6.web.controller;
@Controller
@RequestMapping(value="/user") //①处理器的通用映射前缀
public class HelloWorldController2 {
@RequestMapping(value = "/hello2") //②相对于①处的映射进行窄化
public ModelAndView helloWorld() {
//省略实现
}
}
①类上的@RequestMapping(value="/user") 表示处理器的通用请求前缀;
②处理器功能处理方法上的是对①处映射的窄化。
因此http://localhost:9080/springmvc-chapter6/hello2 无法映射到HelloWorldController2的 helloWorld功能处理方法;而http://localhost:9080/springmvc-chapter6/user/hello2是可以的。
窄化请求映射可以认为是方法级别的@RequestMapping继承类级别的@RequestMapping,窄化请求映射还有其他方式,如在类级别指定URL,而方法级别指定请求方法类型或参数等等
Servlet
Servlet是一套Web应用的开发规范,我们按照这套规范编码就可以实现一个Web应用,使其在Web容器中运行。我们最开始学习J2EE时,学习和创建的就是Servlet的实现类,后来学习了MVC框架以后,尤其是SpringMVC,就很少直接创建Servlet的实现类了。虽然SpringMVC简化和隐藏了Servlet,但是我们也要了解Servlet的运行原理,这样对了解SpringMVC的原理也很有帮助。
Servlet 容器
Tomcat 的容器等级中,Context 容器是直接管理 Servlet 在容器中的包装类 Wrapper,所以 Context 容器如何运行将直接影响 Servlet 的工作方式。
真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程,在 Tomcat 的配置文件中可以很容易发现这一点,如下:
<Context path="/projectOne " docBase="D:\projects\projectOne"
reloadable="true" />
当 Context容器初始化状态设为init 时,添加在 Contex 容器的 Listener 将会被调用。ContextConfig 继承了 LifecycleListener 接口,它是在调用Tomcat的addWebapp方法时被加入到 StandardContext 容器中。ContextConfig 类会负责整个 Web 应用的配置文件的解析工作。
ContextConfig 的 init 方法将会主要完成以下工作:
- 创建用于解析 xml 配置文件的 contextDigester 对象
- 读取默认 context.xml 配置文件,如果存在解析它
- 读取默认 Host 配置文件,如果存在解析它
- 读取默认 Context 自身的配置文件,如果存在解析它
- 设置 Context 的 DocBase
ContextConfig 的 init 方法完成后,Context 容器的会执行 startInternal 方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:
- 创建读取资源文件的对象
- 创建 ClassLoader 对象
- 设置应用的工作目录
- 启动相关的辅助类如:logger、realm、resources 等
- 修改启动状态,通知感兴趣的观察者(Web 应用的配置)
- 子容器的初始化
- 获取 ServletContext 并设置必要的参数
- 初始化“load on startup”的 Servlet
为什么要将 Servlet 包装成 StandardWrapper 而不直接是 Servlet 对象。这里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 为了一个独立的 web 开发标准,不应该强耦合在 Tomcat 中。除了将 Servlet 包装成 StandardWrapper 并作为子容器添加到 Context 中,其它的所有 web.xml 属性都被解析到 Context 中,所以说 Context 容器才是真正运行 Servlet 的 Servlet 容器。一个 Web 应用对应一个 Context 容器,容器的配置属性由 应用的 web.xml 指定,这样我们就能理解 web.xml 到底起到什么作用了。
那服务器是如何根据这个 URL 来达到正确的 Servlet 容器中的呢?
Tomcat7.0 中这件事很容易解决,因为这种映射工作有专门一个类来完成的,这个就是 org.apache.tomcat.util.http.mapper,这个类保存了 Tomcat 的 Container 容器中的所有子容器的信息,当 org.apache.catalina.connector. Request 类在进入 Container 容器之前,mapper 将会根据这次请求的 hostnane 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中。所以当 Request 进入 Container 容器之前,它要访问那个子容器这时就已经确定了。
Session 工作的时序图
Servlet 中的 Listener
用@Configuration注解该类,等价 与XML中配置beans;用@Bean标注方法等价于XML中配置bean。