在上一篇文章Spring与MVC(三)中,我们简单地分析了DispatcherServlet
的处理请求过程。在这篇文章中,我们详细分析一下DispatcherServlet
在处理请求时是如何找到正确的Controller,以及执行。
Spring与MVC(三)
在上一篇文章Spring与MVC(二)中,我们分析了Spring MVC在启动过程中ContextLoaderListener
和DispatcherServlet
两个类的创建过程。
这篇文章中,我们来分析DispatcherServlet
初始化过程,以及DispatcherServlet
的处理请求过程。
Spring与MVC(二)
在上一篇文章Spring与MVC(一)中,我们配置了一个最基本的Web应用,这一章我们来看看这个最基本的Web应用是如何创建DispatcherServlet
和ContextLoaderListener
的。
Spring与MVC(一)
在请求离开浏览器时,会带有用户所请求内容的信息,至少会包含请求的URL。但是还可能带有其他的信息,例如用户提交的表单信息。
请求旅程的第一站是Spring的DispatcherServlet。与大多数基于Java的Web框架一样,Spring MVC所有的请求都会通过一个前端控制器Servlet。前端控制器是常用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。在Spring MVC中,DispatcherServlet就是前端控制器。
DispatcherServlet的任务就是将请求发送给Spring MVC控制器(controller)。控制器是一个用于处理请求的Spring组件。在典型的应用程序中可能会有多个控制器,DispatcherServlet需要知道应该将请求发送给哪个控制器。所以DispatcherServlet查询一个或多个处理器映射(handler mapping)来确定请求的下一站在哪里。处理器映射会根据请求所携带的URL信息来进行决策。
一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。到了控制器,请求会卸下负载(用户提交的信息)并耐心等待控制器处理这些信息。(实际上,设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个服务对象进行处理)
控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送一个视图(view),通常会是JSP。
控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet。
这样,控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑名称,这个名称将会用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器(view resolver)来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能不是JSP。
既然DisPatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是JSP),在这里它交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。
Spring与事务(二)
上回说到,使用事务的模板TransactionTemplate
可以极大地减少我们使用事务时的工作,我们只需将我们的业务逻辑写到TransactionCallback
接口方法中即可。
使用TransactionTemplate
虽然帮我们省略了一些相同的操作,但是每次数据库操作都要写到TransactionCallback
中,也业务逻辑还不是分离的。这就引出了AOP代理。
要将Spring AOP和事务结合起来,也有很多的表现形式,但原理都是一样的。
最简单的莫过于在Configuration类中增加@EnableTransactionManagement
,Spring将标注有@Transactional
的对象创建出代理对象。
Spring与事务(一)
上回说到,直接使用JDBC操作数据库增删改查是非常繁琐的,因此Spring提供了模板类(JdbcTemplate
, NamedParameterJdbcTemplate
)来简化我们和数据库的交互。同样的,直接使用JDBC进行事务的关系也需要写很多非业务代码,Spring提供了一个接口来完成事务的提交和回滚等功能,即接口PlatformTransactionManager
,如果是使用jdbc则使用DataSourceTransactionManager
来完成事务的提交和回滚,如果是使用hibernate则使用HibernateTransactionManager来完成事务的提交和回滚。
事务的定义接口为TransactionDefinition
,它有两个重要的属性,事务的传播属性和隔离级别:
Spring与JDBC
我们知道,如果在程序中直接使用JDBC访问数据的话,需要写一大堆与业务逻辑无关的代码。例如,我们都需要获取一个到数据存储的连接并在处理完成后释放资源。这都是在数据访问处理过程中的固定步骤,但是每种数据访问方法又会有些不同。我们会查询不同的对象或不同的方法更新数据,这都是数据访问过程中变化的部分。
Spring将数据访问的过程中固定的和可变的部分明确划分为两个不同的类:模板(template)和回调(callback)。Spring的模板类处理数据访问的固定部分——事务控制、资源管理、处理异常。同时,应用程序相关的数据访问——语句、绑定参数以及整理结果集——在回调的实现中处理。
Spring AOP源码分析
在invokeBeanFactoryPostProcessors
中注册beanDefinition
invokeBeanFactoryPostProcessors
->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
->invokeBeanDefinitionRegistryPostProcessors
->ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)
->processConfigBeanDefinitions(registry)
->ConfigurationClassBeanDefinitionReader.loadBeanDefinitions
->ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass
->loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars())
->AspectJAutoProxyRegistrar.registerBeanDefinitions
ThreadPoolExecutor
在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理。如果每次请求都新创建一个线程的话实现起来非常简单,但是存在一个问题:
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁地创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
那么有没有一种办法使执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?
这就是线程池的目的了。线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。
什么时候使用线程池?
- 单个任务处理时间比较短
- 需要处理的任务数量很大
使用线程池的好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性。线程时稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executor框架接口
Executor框架是一个根据一组执行策略调动,调度,执行和控制的异步任务的框架,目的是提供一种将“任务提交”与“任务如何运行”分离开来的机制。
JUC中有三个Executor接口:
- Executor:一个运行新任务的简单接口
- ExecutorService:扩展了Executor接口。添加了一些用来管理执行器生命周期和任务生命周期的方法
- ScheduledExecutorService:扩展了ExecutorService。支持Future和定期执行任务。
Executor接口
1 | public interface Executor { |
Executor接口只有一个execute方法,用来替代通常创建或启动线程的方法。
ExecutorService接口
ExecutorService接口继承自Executor接口,提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成的Future方法。增加了shutDown()
、shutDownNow()
、invokeAll()
、invokeAny()
、submit()
等方法。
ScheduledExecutorService接口
ScheduledExecutorService扩展ExecutorService接口并增加了schedule方法。调用schedule方法可以在指定的延时后执行一个Runnable或者Callable任务。ScheduledExecutorService接口还定义了按照指定时间间隔定期执行任务的scheduleAtFixedRate()
方法和scheduleWithFixedDelay()
方法。
几个重要的字段
1 | private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); |
ctl
是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段,它包含两部分的信息:线程池的运行状态(runState)和线程池内有效线程的数量(workerCount),这里可以看到,使用了Integer类型来保存,高3位保存runState,低29位保存workerCount。COUNT_BITS就是29,CAPACITY就是1左移29位减1(29个1),这个常量表示workerCount的上限值,大约是5亿。
下面再介绍下线程池的运行状态. 线程池一共有五种状态, 分别是:
- RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
- SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
- STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
- TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
- TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
- 进入TERMINATED的条件如下:
- 线程池不是RUNNING状态;
- 线程池状态不是TIDYING状态或TERMINATED状态;
- 如果线程池状态是SHUTDOWN并且workerQueue为空;
- workerCount为0;
- 设置TIDYING状态成功。
Condition与AbstractQueuedSynchronizer
前两篇文章中分析了AQS的独占功能和共享功能,AQS中还实现了Condition的功能。它可以替代传统的Object中的wait()、notify()、notifyAll()方法来实现线程间的通信,使线程间协作更加安全和高效。
使用synchronized关键字时,所有没有获取锁的线程都会等待,这时相当于只有1个等待队列;而在实际应用中可能有时需要多个等待队列,比如ReadLock和WriteLock。Lock中的等待队列和Condition中的等待队列是分开的,例如在独占模式下,Lock的独占保证了在同一时刻只会有一个线程访问临界区,也就是lock()方法返回后,Condition中的等待队列保存着被阻塞的线程,也就是调用await()方法后阻塞的线程。所以使用lock比使用synchronized关键字更加灵活。