深入理解 Servlet

戳上方蓝字 “程序猿杂货铺” 关注我 并 置顶星标

你的关注意义重大!

640?wx_fmt=jpeg

阅读本文大概需要 15 分钟

本文首发地址 | http://t.cn/E9a8mIx

Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。

狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

最早支持Servlet标准的是JavaSoft的Java Web Server,此后,一些其它的基于Java的Web服务器开始支持标准的Servlet。

Servlet生命周期
  1. 客户端请求该 Servlet;

  2. 加载 Servlet 类到内存;

  3. 实例化并调用init()方法初始化该 Servlet;

  4. service()(根据请求方法不同调用 doGet() 或者 doPost(),此外还有 doHead()、doPut()、doTrace()、doDelete()、doOptions()、destroy())。

  5. 加载和实例化 Servlet。这项操作一般是动态执行的。然而,Server 通常会提供一个管理的选项,用于在 Server 启动时强制装载和初始化特定的 Servlet。


Servlet在Spring Web Mvc 中的实现
Servlet默认实现

Servlet 容器默认是采用单实例多线程的方式处理多个请求的:

  1. 当 web 服务器启动的时候(或客户端发送请求到服务器时),Servlet 就被加载并实例化(只存在一个Servlet实例);

  2. 容器初始化化 Servlet 主要就是读取配置文件(例如 tomcat ,可以通过 servlet.xml 的设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。

  3. 当请求到达时,Servlet 容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;

  4. 线程执行 Servlet 的 service 方法;

  5. 请求结束,放回线程池,等待被调用;

分析结果
  1. Servlet 单实例,减少了产生servlet的开销;

  2. 通过线程池来响应多个请求,提高了请求的响应时间;

  3. Servlet 容器并不关心到达的 Servlet 请求访问的是否是同一个 Servlet 还是另一个 Servlet,直接分配给它一个新的线程;如果是同一个 Servlet 的多个请求,那么 Servlet 的 service 方法将在多线程中并发的执行;

  4. 每一个请求由 ServletRequest 对象来接受请求,由 ServletResponse 对象来响应该请求;

Spring Boot代码分析
加载流程

本次分享采用的是 Spring Boot 2.0.2.RELEASE 内嵌 Tomcat 实现的方式

Spring Boot启动加载流程

 
 
  1. public ConfigurableApplicationContext run(String... args) {

  2. StopWatch stopWatch = new StopWatch();

  3. // 开始启动

  4. stopWatch.start();

  5. ConfigurableApplicationContext context = null;

  6. // 添加失败分析器

  7. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

  8. // 属性配置

  9. configureHeadlessProperty();

  10. // 添加监听,包括(Spring监听和Spring Boot监听)

  11. SpringApplicationRunListeners listeners = getRunListeners(args);

  12. // 启动监听

  13. listeners.starting();

  14. try {

  15. ApplicationArguments applicationArguments = new DefaultApplicationArguments(

  16. args);

  17. // 准备应用环境参数

  18. ConfigurableEnvironment environment = prepareEnvironment(listeners,

  19. applicationArguments);

  20. configureIgnoreBeanInfo(environment);

  21. Banner printedBanner = printBanner(environment);

  22. // 创建容器

  23. context = createApplicationContext();

  24. exceptionReporters = getSpringFactoriesInstances(

  25. SpringBootExceptionReporter.class,

  26. new Class[] { ConfigurableApplicationContext.class }, context);

  27. // 参数准备,监听器添加

  28. prepareContext(context, environment, listeners, applicationArguments,

  29. printedBanner);

  30. // 刷新容器

  31. refreshContext(context);

  32. // 容器启动完成后的处理

  33. afterRefresh(context, applicationArguments);

  34. stopWatch.stop();

  35. if (this.logStartupInfo) {

  36. new StartupInfoLogger(this.mainApplicationClass)

  37. .logStarted(getApplicationLog(), stopWatch);

  38. }

  39. // 开启监听

  40. listeners.started(context);

  41. // 开始执行

  42. callRunners(context, applicationArguments);

  43. }

  44. catch (Throwable ex) {

  45. handleRunFailure(context, ex, exceptionReporters, listeners);

  46. throw new IllegalStateException(ex);

  47. }


  48. try {

  49. // 执行监听

  50. listeners.running(context);

  51. }

  52. catch (Throwable ex) {

  53. handleRunFailure(context, ex, exceptionReporters, null);

  54. throw new IllegalStateException(ex);

  55. }

  56. return context;

  57. }

如何启动 Tomcat
 
 
  1. public void refresh() throws BeansException, IllegalStateException {

  2. // 加锁实容器启动时的线程安全

  3. synchronized (this.startupShutdownMonitor) {

  4. // 为容器刷新做准备

  5. prepareRefresh();

  6. // 告诉子类启动refreshFactory,Bean定义源文件载入从子类的refreshBeanFactory中加载

  7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

  8. // 为BeanFactory配置容器特性,如:类加载器,事件处理器

  9. prepareBeanFactory(beanFactory);


  10. try {

  11. // 为容器的某些子类指定特殊的BeanPost事件处理

  12. postProcessBeanFactory(beanFactory);

  13. // 调用所有注册BeanFactoryPostProcessor的Bean

  14. invokeBeanFactoryPostProcessors(beanFactory);

  15. // 为BeanFactory注册BeanPost事件处理器

  16. registerBeanPostProcessors(beanFactory);

  17. // 初始化信息源,和国际化相关

  18. initMessageSource();

  19. // 初始化容器事件传播器

  20. initApplicationEventMulticaster();

  21. // 调用子类的某些特殊Bean初始化方法

  22. onRefresh();

  23. // 为时间传播器注册事件监听器

  24. registerListeners();

  25. // 初始化所有所有剩余的单例Bean

  26. finishBeanFactoryInitialization(beanFactory);

  27. // 初始化容器的生命周期事件处理器,并发布容器的生命周期事件

  28. finishRefresh();

  29. }

  30. catch (BeansException ex) {

  31. // 销毁以前创建的单态Bean

  32. destroyBeans();

  33. // 取消refresh操作,重置容器的同步标识

  34. cancelRefresh(ex);

  35. // 异常处理

  36. throw ex;

  37. }

  38. finally {

  39. // 重置

  40. resetCommonCaches();

  41. }

  42. }

  43. }

在 finishRefresh()中启动 web服务器

内嵌的Tomcat如何创建Servlet
 
 
  1. protected void finishRefresh() {

  2. super.finishRefresh();

  3. // 启动web服务器

  4. WebServer webServer = startWebServer();

  5. if (webServer != null) {

  6. publishEvent(new ServletWebServerInitializedEvent(webServer, this));

  7. }

  8. }

具体实现

 
 
  1. private WebServer startWebServer() {

  2. WebServer webServer = this.webServer;

  3. if (webServer != null) {

  4. webServer.start();

  5. }

  6. return webServer;

  7. }

startWebServer

640?wx_fmt=png

开始启动 Tomcat

 
 
  1. public void start() throws WebServerException {

  2. // 此处实现了线程安全

  3. synchronized (this.monitor) {

  4. if (this.started) {

  5. return;

  6. }

  7. try {

  8. addPreviouslyRemovedConnectors();

  9. Connector connector = this.tomcat.getConnector();

  10. if (connector != null && this.autoStart) {

  11. // 当连接建立,getPort() >= 0;时启动Tomcat

  12. performDeferredLoadOnStartup();

  13. }

加载并且初始化所有的 servlets(被 load on startup 标记的)

 
 
  1. public boolean loadOnStartup(Container children[]) {

  2. for (ArrayList<Wrapper> list : map.values()) {

  3. for (Wrapper wrapper : list) {

  4. try {

  5. wrapper.load();

  6. ...


  7. }

初始化应用中已经描述,但没有被初始化的 servlet

 
 
  1. public synchronized void load() throws ServletException {

  2. instance = loadServlet();

  3. // 如果没有被初始化,则初始化servlet

  4. if (!instanceInitialized) {

  5. // 此处可以初始化 DefaultServlet 和 HttpServletBean,详见下文

  6. initServlet(instance);

  7. }


  8. }

 
 
  1. public synchronized Servlet loadServlet() throws ServletException {

  2. try {

  3. servlet = (Servlet) instanceManager.newInstance(servletClass);

  4. ...

  5. // 初始化servlet

  6. initServlet(servlet);

  7. //发送加载完成事件

  8. fireContainerEvent("load", this);

  9. }

 
 
  1. private synchronized void initServlet(Servlet servlet)

  2. throws ServletException {

  3. // 如果已经加载成功或者是实现了SingleThreadModel,直接返回

  4. if (instanceInitialized && !singleThreadModel) return;

  5. // 开始初始化

  6. servlet.init(facade);

  7. }

初始化 HttpServletBean 及相关组件

 
 
  1. public final void init() throws ServletException {

  2. // 让子类实现

  3. initServletBean();

  4. }

 
 
  1. protected final void initServletBean() throws ServletException {


  2. try {

  3. // 初始化WebApplicationContext,此处采用事件监听实现

  4. this.webApplicationContext = initWebApplicationContext();

  5. initFrameworkServlet();

  6. }

  7. }

 
 
  1. protected WebApplicationContext initWebApplicationContext() {

  2. WebApplicationContext rootContext =

  3. WebApplicationContextUtils.getWebApplicationContext(getServletContext());

  4. WebApplicationContext wac = null;

  5. if (!this.refreshEventReceived) {

  6. // refresh刷新容器

  7. onRefresh(wac);

  8. }


  9. if (this.publishContext) {

  10. //发布容器作为servlet的attribute

  11. String attrName = getServletContextAttributeName();

  12. getServletContext().setAttribute(attrName, wac);

  13. }

  14. return wac;

  15. }

DispatcherServlet 中 onRefresh 方法执行

 
 
  1. protected void onRefresh(ApplicationContext context) {

  2. initStrategies(context);

  3. }

初始化 DispatcherServlet 相关组件

 
 
  1. protected void initStrategies(ApplicationContext context) {

  2. initMultipartResolver(context);

  3. initLocaleResolver(context);

  4. initThemeResolver(context);

  5. initHandlerMappings(context);

  6. initHandlerAdapters(context);

  7. initHandlerExceptionResolvers(context);

  8. initRequestToViewNameTranslator(context);

  9. initViewResolvers(context);

  10. initFlashMapManager(context);

  11. }

至此 servlet 相关初始化工作已经完成

Servlet使用

HttpServlet 对 service 的实现

 
 
  1. protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

  2. String method = req.getMethod();

  3. long lastModified;

  4. if (method.equals("GET")) {

  5. lastModified = this.getLastModified(req);

  6. if (lastModified == -1L) {

  7. this.doGet(req, resp);

  8. } else {

  9. long ifModifiedSince;

  10. try {

  11. ifModifiedSince = req.getDateHeader("If-Modified-Since");

  12. } catch (IllegalArgumentException var9) {

  13. ifModifiedSince = -1L;

  14. }


  15. if (ifModifiedSince < lastModified / 1000L * 1000L) {

  16. this.maybeSetLastModified(resp, lastModified);

  17. this.doGet(req, resp);

  18. } else {

  19. resp.setStatus(304);

  20. }

  21. }

  22. } else if (method.equals("HEAD")) {

  23. lastModified = this.getLastModified(req);

  24. this.maybeSetLastModified(resp, lastModified);

  25. this.doHead(req, resp);

  26. } else if (method.equals("POST")) {

  27. this.doPost(req, resp);

  28. } else if (method.equals("PUT")) {

  29. this.doPut(req, resp);

  30. } else if (method.equals("DELETE")) {

  31. this.doDelete(req, resp);

  32. } else if (method.equals("OPTIONS")) {

  33. this.doOptions(req, resp);

  34. } else if (method.equals("TRACE")) {

  35. this.doTrace(req, resp);

  36. } else {

  37. String errMsg = lStrings.getString("http.method_not_implemented");

  38. Object[] errArgs = new Object[]{method};

  39. errMsg = MessageFormat.format(errMsg, errArgs);

  40. resp.sendError(501, errMsg);

  41. }


  42. }

处理请求并发布事件(不考虑结果)

 
 
  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  2. LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

  3. LocaleContext localeContext = buildLocaleContext(request);

  4. RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();

  5. ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

  6. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

  7. asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

  8. initContextHolders(request, localeContext, requestAttributes);

  9. try {

  10. // 执行 DispatcherServlet 的 doService方法

  11. doService(request, response);

  12. }

  13. publishRequestHandledEvent(request, response, startTime, failureCause);

  14. }

执行 DispatcherServlet 的 doDispatch 方法

 
 
  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

  2. HttpServletRequest processedRequest = request;

  3. HandlerExecutionChain mappedHandler = null;

  4. boolean multipartRequestParsed = false;


  5. //在Spring3.2之后,支持WebAsync异步处理

  6. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);


  7. try {

  8. ModelAndView mv = null;

  9. Exception dispatchException = null;


  10. try {

  11. //转换请求为多个部分并获得相应的处理器

  12. processedRequest = checkMultipart(request);

  13. multipartRequestParsed = (processedRequest != request);


  14. // 找到当前请求对应的handler

  15. mappedHandler = getHandler(processedRequest);

  16. if (mappedHandler == null) {

  17. noHandlerFound(processedRequest, response);

  18. return;

  19. }


  20. // 找到当前请求对应的handler适配器

  21. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

  22. //执行拦截器preHandle

  23. if (!mappedHandler.applyPreHandle(processedRequest, response)) {

  24. return;

  25. }

  26. // 反射调用实际执行的handler,返回ModelAndView

  27. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());


  28. applyDefaultViewName(processedRequest, mv);

  29. //执行拦截器postHandle

  30. mappedHandler.applyPostHandle(processedRequest, response, mv);

  31. }

  32. catch (Exception ex) {

  33. dispatchException = ex;

  34. }

  35. catch (Throwable err) {

  36. //此处主要是使@ExceptionHandler处理起作用

  37. dispatchException = new NestedServletException("Handler dispatch failed", err);

  38. }

  39. //执行拦截器processDispatchResult,当程序正常结束时执行

  40. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

  41. }

  42. catch (Exception ex) {

  43. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

  44. }

  45. catch (Throwable err) {

  46. triggerAfterCompletion(processedRequest, response, mappedHandler,

  47. new NestedServletException("Handler processing failed", err));

  48. }

  49. finally {

  50. if (asyncManager.isConcurrentHandlingStarted()) {

  51. // 相当于postHandle 和 afterCompletion

  52. if (mappedHandler != null) {

  53. //该实现在HandlerInterceptorAdapter中也有定义

  54. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

  55. }

  56. }

  57. else {

  58. // Clean up any resources used by a multipart request.

  59. //清除所有请求的处理所对应的资源占用

  60. if (multipartRequestParsed) {

  61. cleanupMultipart(processedRequest);

  62. }

  63. }

  64. }

  65. }

至此 servlet 初始化和使用的所有的代码已经完成

服务停止
 
 
  1. // 销毁资源并关闭服务

  2. public void destroy() {

  3. close();

  4. }


 
 
  1. public void close() {

  2. synchronized (this.startupShutdownMonitor) {

  3. doClose();

  4. // 注册 shutdownHook

  5. if (this.shutdownHook != null) {

  6. try {

  7. Runtime.getRuntime().removeShutdownHook(this.shutdownHook);

  8. ...

  9. }

实际执行销毁动作

 
 
  1. protected void doClose() {

  2. // 发布 shutdown 事件.

  3. publishEvent(new ContextClosedEvent(this));

  4. // 销毁所有的单例Bean

  5. destroyBeans();

  6. // 关闭容器

  7. closeBeanFactory();

  8. // 让子类执行清除

  9. onClose();


  10. }

设计线程安全的Servlet

如何创建单例多线程Servlet

在 Web 应用程序中,一个 Servlet 在一个时刻可能被多个用户同时访问。这时 Web 容器将为每个用户创建一个线程来执行 Servlet。如果 Servlet 不涉及共享资源的问题,不必关心多线程问题。但如果 Servlet 需要共享资源,需要保证 Servlet 是线程安全的。

下面是编写线程安全的 Servlet 的一些建议:

  1. 用方法的局部变量保存请求中的专有数据。对方法中定义的局部变量,进入方法的每个线程都有自己的一份方法变量拷贝。任何线程都不会修改其他线程的局部变量。如果要在不同的请求之间共享数据,应该使用会话来共享这类数据。

  2. 只用 Servlet的成员变量来存放那些不会改变的数据。有些数据在 Servlet 生命周期中不发生任何变化,通常是在初始时确定的,这些数据可以使用成员变量保存,如数据库连接名称、其他资源的路径等。

  3. 对可能被请求修改的成员变量同步。有时数据成员变量或者环境属性可能被请求修改。当访问这些数据时应该对它们同步,以避免多个线程同时修改这些数据。

  4. 如果 Servlet 访问外部资源,那么需要同步访问这些资源。例如,假设 Servlet 要从文件中读写数据。当一个线程读写一个文件时,其他线程也可能正在读写这个文件。文件访问本身不是线程安全的,所以必须编写同步访问这些资源的代码。在编写线程安全的 Servlet 时,

下面两种方法是不应该使用的:
  1. 在 Servlet API 中提供了一个 SingleThreadModel 接口,实现这个接口的 Servlet 在被多个客户请求时一个时刻只有一个线程运行。这个接口已被标记不推荐使用。

  2. 对 doGet() 或doPost() 方法同步。如果必须在 Servlet 中使用同步代码,应尽量在最小的代码块范围上进行同步。同步代码越小,Servlet 执行得才越好。)


送书活动马上开奖了,未参加的小伙伴抓紧了(点击文字直接跳转2_02.png


往期精彩回顾


互联网薅羊毛指南

互联网公司实习一年,我被迫知道了这些内幕 ...


640?wx_fmt=png

点击下方“阅读原文” 直达作者博客地址 获取更多干货文章
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值