优秀的编程知识分享平台

网站首页 > 技术文章 正文

Springboot之强大的Servlet「长文慎入...」

nanyue 2025-02-28 16:51:04 技术文章 5 ℃

If I have seen further, it is by standing on the shoulders of giants

如果我比别人看得更远,那是因为我站在巨人的肩膀上

如今回头看下Servlet不仅如此强大,还具有很强烈的参考意义,能在现如今流行的大部分框架中找到它的影子。下面文章不止与探索Servlet,可能在其中穿插其他的关联知识点,旨在能从此次的学习中获取更多的知识点~参考资料总结,转化为自己的理解输出,在文中我尽量以截图+复制全限定类名的方式记录,以便感兴趣的再次查找。

Springboot与Servlet




在springboot中内嵌了Tomcat容器,而Tomcat又是Servlet的容器,Springboot就与Servlet产生了紧密的联系。
在分析各个类时,注意下每个类所在的包是如何在tomcat与boot之间跨越的~

生命周期

1、初始化
2、处理请求
3、销毁

应用上下文

应用上下文即可看做:一次请求到达,到响应结束的过程中间的catlog,即阅读中结合上下文语境,是一个广义定义。
为什么说到上下文呢?来看下ServletContext的实现,第一个经典实现既是ApplicationContext我们不止在一次源码和应用中见到它,另外加载器目前有两种选择:ContextLoaderListener和ContextLoaderServlet。其功能是完全相同。会在下文进行介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Standard implementation of ServletContext that represents
 * a web application's execution environment.  An instance of this class is
 * associated with each instance of StandardContext.
 * 代表web应用程序的执行环境。这个类的一个实例是
 *与StandardContext的每个实例关联。
 * @author Craig R. McClanahan
 * @author Remy Maucherat
 */
public class ApplicationContext implements ServletContext {

    protected static final boolean STRICT_SERVLET_COMPLIANCE;///翻译为是否严格遵守

    protected static final boolean GET_RESOURCE_REQUIRE_SLASH;//我的蹩脚英语翻译为获取资源是否需要斜线。

    static {
        STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;

        String requireSlash = System.getProperty("org.apache.catalina.core.ApplicationContext.GET_RESOURCE_REQUIRE_SLASH");
        if (requireSlash == null) {
            GET_RESOURCE_REQUIRE_SLASH = STRICT_SERVLET_COMPLIANCE;
        } else {
            GET_RESOURCE_REQUIRE_SLASH = Boolean.parseBoolean(requireSlash);
        }
    }

注:特别重要上述配置为tomcat中第一个开关配置,决定多个属性的值。来自于下面的
Globals.STRICT_SERVLET_COMPLIANCE;默认为false

验证:

1
public static final boolean STRICT_SERVLET_COMPLIANCE =Boolean.parseBoolean(System.getProperty("org.apache.catalina.STRICT_SERVLET_COMPLIANCE", "false"));

和官网截图


问题:会因为tomcat的版本配置不同改变此值,在8.5.57当中会改变为true,当cantroller中配置多个映射路径会出现访问不到的问题
此处参考博文:
https://blog.csdn.net/xing930408/article/details/111225064

Tomcat文档:
https://tomcat.apache.org/tomcat-8.5-doc/config/systemprops.html


GET_RESOURCE_REQUIRE_SLASH直接赋值为STRICT_SERVLET_COMPLIANCE

Servlet与HttpServlet


类图标明很是明显,在这个图中展示了servlet,tomcat,Springboot的关系,完美解释了那句Springboot是内嵌了tomcat的嵌入式引擎,嵌入式容器的说法~

1
2
3
4
而HttpServlet即是大部分请求的处理对象,嵌入式引擎----嵌入式容器----  webfilter ---weblistener
javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.Class)返回一个ServletRegistration对象,可用于进一步
配置已注册的servlet
javax.servlet.ServletRegistration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface ServletRegistration extends Registration {

    /**
     * TODO
     * @param urlPatterns The URL patterns that this Servlet should be mapped to
     * @return TODO
     * @throws IllegalArgumentException if urlPattern is null or empty
     * @throws IllegalStateException if the associated ServletContext has
     *                                  already been initialised
     */URL必须映射
    public Set addMapping(String... urlPatterns);

    public Collection getMappings();

    public String getRunAsRole();

    public static interface Dynamic
    extends ServletRegistration, Registration.Dynamic {
        public void setLoadOnStartup(int loadOnStartup);
        public Set setServletSecurity(ServletSecurityElement constraint);
        public void setMultipartConfig(MultipartConfigElement multipartConfig);
        public void setRunAsRole(String roleName);
    }
}

思考:为什么Applacationcontext会有那么多重载方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
   public ServletRegistration.Dynamic addServlet(String servletName, String className) {
       return addServlet(servletName, className, null, null);
   }


   @Override
   public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
       return addServlet(servletName, null, servlet, null);
   }


   @Override
   public ServletRegistration.Dynamic addServlet(String servletName,
           Class servletClass) {
       return addServlet(servletName, servletClass.getName(), null, null);
   }

–用在不同场景下解决同一类问题
而在HttpServlet中的关键方法service可看到平时请求接口的所有方法


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";

 protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();
        //GET
        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            //There's no need to override this method. 没有必要~
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }


    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String protocol = req.getProtocol();
    String msg = lStrings.getString("http.method_post_not_supported");
    if (protocol.endsWith("1.1")) {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
    } else {
        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
    }
}

注:在javaHttpServlet中,与Tomcat中的dopost方法如出一辙

真正的调用链(妥妥的责任链模式)是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Tomcat与SpringMVC的结合点:ApplicationFilterChain与DispatcherServlet(继承于FrameworkServlet);
(1)所有配置了路由信息的处理方法最终都是通过反射的方式进行调用的;
(2)在Java8中,反射方法调用最终落脚于NativeMethodAccessorImpl类的native方法:
private static native Object invoke0(Method var0, Object var1, Object[] var2);
在此处与JVM底层交互,实现跨代码衔接执行;
(3)观察到的比较重要的设计模式:职责链模式(ApplicationFilterChain)、委派模式(DelegatingFilterProxy)、
工厂模式、策略模式、代理模式(FilterChainProxy)、外观模式、适配器模式(HandlerAdapter);
(4)Tomcat与SpringMVC的结合点:ApplicationFilterChain与DispatcherServlet(继承于FrameworkServlet);
(5)在集成了Tomcat的SpringBoot项目中,先启动的不是Tomcat,而是Spring,Spring的工厂(默认DefaultListableBeanFactory)
读取注解完成各类Bean(WebApplicationContext、securityFilterChainRegistration、dispatcherServletRegistration、各类FilterInitializer与Filter)
的初始化,放入IoC容器,然后做路由Mapping,创建FilterChain,开启JMX等;
(6)Servlet、Filter是单实例多线程的,成员变量线程不安全,方法内局部变量线程安全;SingleThreadModel采用同步/实例池的方式来确保不会有两个线程同时执行servlet的service方法,但已被弃用,需自行确保成员变量线程安全;
————————————————
版权声明:本文为CSDN博主「wanxu12345678910」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wanxu12345678910/article/details/83352371

ContextLoaderServlet与下文中的ContextLoaderListener功能完全相同

....未完待续

最近发表
标签列表