前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解 Servlet

深入理解 Servlet

作者头像
周三不加班
发布2019-06-04 18:47:10
8280
发布2019-06-04 18:47:10
举报
文章被收录于专栏:程序猿杂货铺程序猿杂货铺

阅读本文大概需要 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启动加载流程

代码语言:javascript
复制
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        // 开始启动
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        // 添加失败分析器
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 属性配置 
        configureHeadlessProperty();
        // 添加监听,包括(Spring监听和Spring Boot监听)
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 启动监听
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            // 准备应用环境参数
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            // 创建容器
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            // 参数准备,监听器添加
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            // 刷新容器
            refreshContext(context);
            // 容器启动完成后的处理
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            // 开启监听
            listeners.started(context);
            // 开始执行
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // 执行监听
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
如何启动 Tomcat
代码语言:javascript
复制
public void refresh() throws BeansException, IllegalStateException {
        // 加锁实容器启动时的线程安全
        synchronized (this.startupShutdownMonitor) {
            // 为容器刷新做准备
            prepareRefresh();
            // 告诉子类启动refreshFactory,Bean定义源文件载入从子类的refreshBeanFactory中加载
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // 为BeanFactory配置容器特性,如:类加载器,事件处理器
            prepareBeanFactory(beanFactory);

            try {
                // 为容器的某些子类指定特殊的BeanPost事件处理
                postProcessBeanFactory(beanFactory);
                // 调用所有注册BeanFactoryPostProcessor的Bean
                invokeBeanFactoryPostProcessors(beanFactory);
                // 为BeanFactory注册BeanPost事件处理器
                registerBeanPostProcessors(beanFactory);
                // 初始化信息源,和国际化相关
                initMessageSource();
                // 初始化容器事件传播器
                initApplicationEventMulticaster();
                // 调用子类的某些特殊Bean初始化方法
                onRefresh();
                // 为时间传播器注册事件监听器
                registerListeners();
                // 初始化所有所有剩余的单例Bean
                finishBeanFactoryInitialization(beanFactory);
                // 初始化容器的生命周期事件处理器,并发布容器的生命周期事件
                finishRefresh();
            }
            catch (BeansException ex) {
                // 销毁以前创建的单态Bean
                destroyBeans();
                // 取消refresh操作,重置容器的同步标识
                cancelRefresh(ex);
                // 异常处理
                throw ex;
            }
            finally {
                // 重置
                resetCommonCaches();
            }
        }
    }

在 finishRefresh()中启动 web服务器

内嵌的Tomcat如何创建Servlet
代码语言:javascript
复制
protected void finishRefresh() {
        super.finishRefresh();
        // 启动web服务器 
        WebServer webServer = startWebServer();
        if (webServer != null) {
            publishEvent(new ServletWebServerInitializedEvent(webServer, this));
        }
    }

具体实现

代码语言:javascript
复制
private WebServer startWebServer() {
        WebServer webServer = this.webServer;
        if (webServer != null) {
            webServer.start();
        }
        return webServer;
    }

startWebServer

开始启动 Tomcat

代码语言:javascript
复制
public void start() throws WebServerException {
        // 此处实现了线程安全 
        synchronized (this.monitor) {
            if (this.started) {
                return;
            }
            try {
                addPreviouslyRemovedConnectors();
                Connector connector = this.tomcat.getConnector();
                if (connector != null && this.autoStart) {
                    // 当连接建立,getPort() >= 0;时启动Tomcat
                    performDeferredLoadOnStartup();
    }

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

代码语言:javascript
复制
public boolean loadOnStartup(Container children[]) {
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
          ...

}

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

代码语言:javascript
复制
public synchronized void load() throws ServletException {
    instance = loadServlet();
    // 如果没有被初始化,则初始化servlet
     if (!instanceInitialized) {
         // 此处可以初始化 DefaultServlet 和 HttpServletBean,详见下文
            initServlet(instance);
        }

}
代码语言:javascript
复制
public synchronized Servlet loadServlet() throws ServletException {
        try {
            servlet = (Servlet) instanceManager.newInstance(servletClass);
       ...
        // 初始化servlet
        initServlet(servlet);
        //发送加载完成事件
        fireContainerEvent("load", this);
}
代码语言:javascript
复制
private synchronized void initServlet(Servlet servlet)
        throws ServletException {
    // 如果已经加载成功或者是实现了SingleThreadModel,直接返回
    if (instanceInitialized && !singleThreadModel) return;
    // 开始初始化
    servlet.init(facade);
}

初始化 HttpServletBean 及相关组件

代码语言:javascript
复制
public final void init() throws ServletException {
    // 让子类实现
    initServletBean();
}
代码语言:javascript
复制
protected final void initServletBean() throws ServletException {

   try {
       // 初始化WebApplicationContext,此处采用事件监听实现
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
}
代码语言:javascript
复制
protected WebApplicationContext initWebApplicationContext() {
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;
   if (!this.refreshEventReceived) {
     // refresh刷新容器
      onRefresh(wac);
   }

   if (this.publishContext) {
      //发布容器作为servlet的attribute
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }
   return wac;
}

DispatcherServlet 中 onRefresh 方法执行

代码语言:javascript
复制
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

初始化 DispatcherServlet 相关组件

代码语言:javascript
复制
protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

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

Servlet使用

HttpServlet 对 service 的实现

代码语言:javascript
复制
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

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

代码语言:javascript
复制
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    initContextHolders(request, localeContext, requestAttributes);
    try {
        // 执行 DispatcherServlet 的 doService方法
        doService(request, response);
    }
    publishRequestHandledEvent(request, response, startTime, failureCause);
}

执行 DispatcherServlet 的 doDispatch 方法

代码语言:javascript
复制
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

    //在Spring3.2之后,支持WebAsync异步处理
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                //转换请求为多个部分并获得相应的处理器
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // 找到当前请求对应的handler
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 找到当前请求对应的handler适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                //执行拦截器preHandle
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 反射调用实际执行的handler,返回ModelAndView
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                applyDefaultViewName(processedRequest, mv);
                //执行拦截器postHandle
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                //此处主要是使@ExceptionHandler处理起作用
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //执行拦截器processDispatchResult,当程序正常结束时执行
            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()) {
                // 相当于postHandle 和 afterCompletion
                if (mappedHandler != null) {
                    //该实现在HandlerInterceptorAdapter中也有定义
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                //清除所有请求的处理所对应的资源占用
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

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

服务停止

代码语言:javascript
复制
// 销毁资源并关闭服务
public void destroy() {
        close();
    }
代码语言:javascript
复制
public void close() {
   synchronized (this.startupShutdownMonitor) {
      doClose();
      // 注册 shutdownHook
      if (this.shutdownHook != null) {
         try {
            Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
        ...
}

实际执行销毁动作

代码语言:javascript
复制
protected void doClose() {
    // 发布 shutdown 事件.
    publishEvent(new ContextClosedEvent(this));
    // 销毁所有的单例Bean
    destroyBeans();
    // 关闭容器
    closeBeanFactory();
    // 让子类执行清除
    onClose();

}

设计线程安全的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 执行得才越好。)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员啊粥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Servlet默认实现
  • 分析结果
  • 加载流程
  • 如何启动 Tomcat
  • 内嵌的Tomcat如何创建Servlet
  • 如何创建单例多线程Servlet
    • 下面两种方法是不应该使用的:
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档