如题,是吗?首先我们得搞清楚啥是单例。一聊起单例,条件反射的第一个想到的自然是单例模式。单例模式的定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。如果按照Java中单例的定义,那么当Servlet没有实现SingleThreadModel接口时,它确实是单例的。

  但如果往细处再进一步深究的话,又好像不是那么一回事了。还是先看单例模式,举个例子,Sigleton类是个单例,它为了保证实例的唯一性,坚决不给别人实例化它的机会,那么它会把构造器定位私有的(private)。而Servlet本身是一个接口,我们一般用的是HttpServlet,它继承了GenericServlet,而GenericServlet实现了Servlet。虽然HttpServlet是抽象类,然而它却有自己的构造器,而且是公有的(public)。我们知道子类初始化实例时必然先调用父类的构造器,也就是如果我现在有一个DemoServlet,那么实例化它将执行HttpServlet的构造器。当然了,父亲的父亲GenericServlet的构造器也会加载。如此看来,单例模式中的私有构造器与Servlet中的公有构造器明显匹配不上了。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

  是否单例就不纠结了,我还是偏向于广义上的范畴,只要满足在整个系统中仅有一个实例,就认为它是单例。回到最先前的那句话:当Servlet没有实现SingleThreadModel接口时,它才是单例的。虽然SingleThreadModel被标记为过期的了,但仍然可以用的。如果实现该接口,那么每次请求相同的Servlet,将创建一个新的实例。说白了就跟CGI一样了,每次web请求都起一个进程来处理。

  Servlet本身是规范,它需要实现了这组规范的Servlet容器来提供web能力。一提到servlet容器,条件反射的第一个想到的自然是Tomcat。Tomcat才是去实例化Servlet的那个他。而Tomcat里执行实例化Servlet的类叫StandardWrapper,它有个loadServlet的方法:

    /**
     * Load and initialize an instance of this servlet, if there is not already
     * at least one initialized instance.  This can be used, for example, to
     * load servlets that are marked in the deployment descriptor to be loaded
     * at server startup time.
     * @return the loaded Servlet instance
     * @throws ServletException for a Servlet load error
     */
    public synchronized Servlet loadServlet() throws ServletException {

        // Nothing to do if we already have an instance or an instance pool
        if (!singleThreadModel && (instance != null))
            return instance;

        PrintStream out = System.out;
        if (swallowOutput) {
            SystemLogHandler.startCapture();
        }

        Servlet servlet;
        try {
            long t1=System.currentTimeMillis();
            // Complain if no servlet class has been specified
            if (servletClass == null) {
                unavailable(null);
                throw new ServletException
                    (sm.getString("standardWrapper.notClass", getName()));
            }

            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
            try {
                servlet = (Servlet) instanceManager.newInstance(servletClass);
            } catch (ClassCastException e) {
                unavailable(null);
                // Restore the context ClassLoader
                throw new ServletException
                    (sm.getString("standardWrapper.notServlet", servletClass), e);
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                unavailable(null);

                // Added extra log statement for Bugzilla 36630:
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=36630
                if(log.isDebugEnabled()) {
                    log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
                }

                // Restore the context ClassLoader
                throw new ServletException
                    (sm.getString("standardWrapper.instantiate", servletClass), e);
            }

            if (multipartConfigElement == null) {
                MultipartConfig annotation =
                        servlet.getClass().getAnnotation(MultipartConfig.class);
                if (annotation != null) {
                    multipartConfigElement =
                            new MultipartConfigElement(annotation);
                }
            }

            // Special handling for ContainerServlet instances
            // Note: The InstanceManager checks if the application is permitted
            //       to load ContainerServlets
            if (servlet instanceof ContainerServlet) {
                ((ContainerServlet) servlet).setWrapper(this);
            }

            classLoadTime=(int) (System.currentTimeMillis() -t1);

            if (servlet instanceof SingleThreadModel) {
                if (instancePool == null) {
                    instancePool = new Stack<>();
                }
                singleThreadModel = true;
            }

            initServlet(servlet);

            fireContainerEvent("load", this);

            loadTime=System.currentTimeMillis() -t1;
        } finally {
            if (swallowOutput) {
                String log = SystemLogHandler.stopCapture();
                if (log != null && log.length() > 0) {
                    if (getServletContext() != null) {
                        getServletContext().log(log);
                    } else {
                        out.println(log);
                    }
                }
            }
        }
        return servlet;

    }

  我们看到该方法是同步的(synchronized修饰符)。先看上面标黄的第一行,如果实现了之前说到的SigleThreadModel接口,那么这里的singleThreadModel就是true,就不会因为有Servlet实例而返回原有的Servlet了。但若反之,就返回原有的Servlet实例,符合单例的定义。

  第二处标黄说明Tomcat是通过反射来实例化Servlet的。它先根据web.xml(或者起相同作用的@WebServlet)找到ServletClass的全路径类名,然后通过类加载器得到Class对象,最后获取Class的构造器,通过构造器实例化ServletClass。

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄