웹 기술은 오늘날 주류 인터넷 웹 애플리케이션 기술 중 하나가 되었으며, 서블릿은 자바 웹 기술의 핵심 기반입니다. 따라서 Servlet의 작동 원리를 익히는 것은 자격을 갖춘 Java 웹 기술 개발자가 되기 위한 기본 요구 사항입니다. 이 기사에서는 서블릿을 기반으로 Java 웹 기술이 작동하는 방식을 이해하게 될 것입니다. 서블릿 컨테이너가 작동하는 방식을 이해하려면 Tomcat을 예로 들어 볼까요? 서블릿 컨테이너에서 웹 프로젝트는 어떻게 시작됩니까? 서블릿 컨테이너는 web.xml에 정의한 서블릿을 어떻게 구문 분석합니까? 사용자 요청은 지정된 서블릿에 어떻게 할당됩니까? 서블릿 컨테이너는 서블릿 수명주기를 어떻게 관리합니까? 또한 최신 Servlet API의 클래스 계층 구조와 Servlet의 몇 가지 어려운 문제에 대한 분석을 배우게 됩니다.
서블릿을 소개하려면 먼저 서블릿 컨테이너에 대해 명확하게 설명해야 합니다. 서블릿과 서블릿 컨테이너의 관계는 총과 총알의 관계와 비슷하며, 총알은 총을 치명적으로 만듭니다. 비록 상호 의존적이지만 서로 독립적으로 발전하며, 이는 모두 산업 생산에 적응한 결과입니다. 기술적인 관점에서는 표준화된 인터페이스를 통해 서로 분리하고 협업하는 것입니다. 인터페이스는 서블릿과 서블릿 컨테이너를 연결하는 핵심이므로 인터페이스부터 시작하겠습니다.
앞서 언급한 바와 같이 서블릿 컨테이너는 자체적으로 개발된 표준화된 제품으로 현재 많은 유형이 있지만 모두 고유한 시장 위치를 갖고 있으며 각각의 특성이 다릅니다. 예를 들어, 인기 있는 Jetty는 사용자 정의 및 모바일 분야에서 좋은 진전을 이루었습니다. 여기서는 모든 사람에게 가장 친숙한 Tomcat을 예로 들어 서블릿 컨테이너가 서블릿을 관리하는 방법을 소개하겠습니다. Tomcat 자체도 매우 복잡합니다. 먼저 Servlet과 Servlet 컨테이너 간의 인터페이스부터 시작하겠습니다. Tomcat에 대한 자세한 소개는 제가 쓴 다른 기사 "Tomcat System Architecture and Pattern Design Analysis"를 참조하세요.
Tomcat의 컨테이너 수준에서 Context 컨테이너는 컨테이너의 Servlet을 직접 관리하는 래퍼 클래스 Wrapper이므로 Context 컨테이너가 실행되는 방식은 Servlet이 작동하는 방식에 직접적인 영향을 미칩니다.
그림 1. Tomcat 컨테이너 모델
위 그림에서 볼 수 있듯이 Tomcat 컨테이너는 4가지 레벨로 나누어져 있는데 실제로 서블릿을 관리하는 컨테이너는 Context 컨테이너이며, 하나의 Context는 웹 프로젝트에 해당하며 다음과 같습니다.
목록 1 컨텍스트 구성 매개변수
<Context path="/projectOne " docBase="D:\projects\projectOne" reloadable="true" />
다음은 서블릿을 구축하는 방법을 포함하여 Tomcat이 Context 컨테이너를 구문 분석하는 프로세스에 대한 자세한 소개입니다.
Tomcat7은 또한 임베디드 기능을 지원하기 시작했으며 시작 클래스 org.apache.catalina.startup.Tomcat을 추가했습니다. 인스턴스 객체를 생성하고 시작 메소드를 호출하여 Tomcat을 쉽게 시작할 수 있습니다. 또한 이 객체를 사용하여 컨텍스트, 서블릿 등을 동적으로 추가하는 등 Tomcat의 구성 매개변수를 추가하고 수정할 수도 있습니다. 다음으로, 이 Tomcat 클래스를 사용하여 새로운 Context 컨테이너를 관리하겠습니다. Tomcat7과 함께 제공되는 예제 웹 프로젝트를 선택하고 이 Context 컨테이너에 어떻게 추가되는지 살펴보겠습니다.
목록 2. Tomcat에 웹 프로젝트 추가
Tomcat tomcat = getTomcatInstance(); File appDir = new File(getBuildDirectory(), "webapps/examples"); tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); tomcat.start(); ByteChunk res = getUrl("http://localhost:" + getPort() + "/examples/servlets/servlet/HelloWorldExample"); assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);
목록 1의 코드는 Tomcat 인스턴스를 생성하고 웹 애플리케이션을 추가한 다음 Tomcat을 시작하고 HelloWorldExample Servlet 중 하나를 호출하여 예상 데이터가 올바르게 반환되는지 확인하는 것입니다.
Tomcat의 addWebapp 메소드 코드는 다음과 같습니다.
목록 3 .Tomcat.addWebapp
public Context addWebapp(Host host, String url, String path) { silence(url); Context ctx = new StandardContext(); ctx.setPath( url ); ctx.setDocBase(path); if (defaultRealm == null) { initSimpleAuth(); } ctx.setRealm(defaultRealm); ctx.addLifecycleListener(new DefaultWebXmlListener()); ContextConfig ctxCfg = new ContextConfig(); ctx.addLifecycleListener(ctxCfg); ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; }
웹 애플리케이션은 서블릿이 실행될 때 서블릿 컨테이너인 Context 컨테이너에 해당한다고 이미 소개했습니다. 웹 애플리케이션을 추가하면 StandardContext 컨테이너가 생성되고 Context 컨테이너에 필요한 매개변수가 설정됩니다. URL과 경로는 각각 애플리케이션을 나타냅니다. Tomcat의 액세스 경로와 이 애플리케이션의 실제 물리적 경로는 목록 1의 두 매개변수와 일치합니다. 가장 중요한 구성은 ContextConfig입니다. 이 클래스는 나중에 자세히 소개할 전체 웹 애플리케이션 구성을 구문 분석하는 역할을 합니다. 마지막으로 이 컨텍스트 컨테이너를 상위 컨테이너 호스트에 추가합니다.
接下去将会调用 Tomcat 的 start 方法启动 Tomcat,如果你清楚 Tomcat 的系统架构,你会容易理解 Tomcat 的启动逻辑,Tomcat 的启动逻辑是基于观察者模式设计的,所有的容器都会继承 Lifecycle 接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener),关于这个设计模式可以参考《 Tomcat 的系统架构与设计模式,第二部分:设计模式》。Tomcat 启动的时序图可以用图 2 表示。
图 2. Tomcat 主要类的启动时序图(查看大图)
上图描述了 Tomcat 启动过程中,主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启动过程。
当 Context 容器初始化状态设为 init 时,添加在 Contex 容器的 Listener 将会被调用。ContextConfig 继承了 LifecycleListener 接口,它是在调用清单 3 时被加入到 StandardContext 容器中。ContextConfig 类会负责整个 Web 应用的配置文件的解析工作。
ContextConfig 的 init 方法将会主要完成以下工作:
创建用于解析 xml 配置文件的 contextDigester 对象
读取默认 context.xml 配置文件,如果存在解析它
读取默认 Host 配置文件,如果存在解析它
读取默认 Context 自身的配置文件,如果存在解析它
设置 Context 的 DocBase
ContextConfig 的 init 方法完成后,Context 容器的会执行 startInternal 方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:
创建读取资源文件的对象
创建 ClassLoader 对象
设置应用的工作目录
启动相关的辅助类如:logger、realm、resources 等
修改启动状态,通知感兴趣的观察者(Web 应用的配置)
子容器的初始化
获取 ServletContext 并设置必要的参数
初始化“load on startup”的 Servlet
Web 应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始化主要是要解析 web.xml 文件,这个文件描述了一个 Web 应用的关键信息,也是一个 Web 应用的入口。
Tomcat 首先会找 globalWebXml 这个文件的搜索路径是在 engine 的工作目录下寻找以下两个文件中的任一个 org/apache/catalin/startup/NO_DEFAULT_XML 或 conf/web.xml。接着会找 hostWebXml 这个文件可能会在 System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default,接着寻找应用的配置文件 examples/WEB-INF/web.xml。web.xml 文件中的各个配置项将会被解析成相应的属性保存在 WebXml 对象中。如果当前应用支持 Servlet3.0,解析还将完成额外 9 项工作,这个额外的 9 项工作主要是为 Servlet3.0 新增的特性,包括 jar 包中的 META-INF/web-fragment.xml 的解析以及对 annotations 的支持。
接下去将会将 WebXml 对象中的属性设置到 Context 容器中,这里包括创建 Servlet 对象、filter、listener 等等。这段代码在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代码片段:
清单 4. 创建 Wrapper 实例
for (ServletDef servlet : servlets.values()) { Wrapper wrapper = context.createWrapper(); String jspFile = servlet.getJspFile(); if (jspFile != null) { wrapper.setJspFile(jspFile); } if (servlet.getLoadOnStartup() != null) { wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null) { if (multipartdef.getMaxFileSize() != null && multipartdef.getMaxRequestSize()!= null && multipartdef.getFileSizeThreshold() != null) { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation(), Long.parseLong(multipartdef.getMaxFileSize()), Long.parseLong(multipartdef.getMaxRequestSize()), Integer.parseInt( multipartdef.getFileSizeThreshold()))); } else { wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation())); } } if (servlet.getAsyncSupported() != null) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } context.addChild(wrapper); }
这段代码清楚的描述了如何将 Servlet 包装成 Context 容器中的 StandardWrapper,这里有个疑问,为什么要将 Servlet 包装成 StandardWrapper 而不直接是 Servlet 对象。这里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 为了一个独立的 web 开发标准,不应该强耦合在 Tomcat 中。
除了将 Servlet 包装成 StandardWrapper 并作为子容器添加到 Context 中,其它的所有 web.xml 属性都被解析到 Context 中,所以说 Context 容器才是真正运行 Servlet 的 Servlet 容器。一个 Web 应用对应一个 Context 容器,容器的配置属性由应用的 web.xml 指定,这样我们就能理解 web.xml 到底起到什么作用了。
Servlet의 구문 분석 작업이 이전에 완료되어 StandardWrapper로 패키징되어 Context 컨테이너에 추가되었지만 인스턴스화되지 않았기 때문에 여전히 작동하지 않습니다. 아래에서는 Servlet 객체가 생성되고 초기화되는 방법을 소개합니다.
서블릿의 시작 시 로드 구성 항목이 0보다 크면 Context 컨테이너가 시작될 때 인스턴스화됩니다. 앞서 언급했듯이 web.xml에 있는 구성 파일을 구문 분석할 때 기본 globalWebXml을 읽습니다. conf 아래의 xml 파일에는 두 개의 서블릿, 즉 org.apache.catalina.servlets.DefaultServlet 및 org.apache.jasper.servlet.JspServlet을 정의하는 일부 기본 구성 항목이 정의되어 있습니다. 해당 항목의 시작 시 로드는 각각 1과 3입니다. 즉, 이 두 서블릿은 Tomcat이 시작될 때 시작됩니다.
Servlet 인스턴스를 생성하는 방법은 Wrapper.loadServlet으로 시작됩니다. loadServlet 메소드가 수행해야 하는 작업은 servletClass를 얻은 다음 이를 InstanceManager에 전달하여 servletClass.class를 기반으로 객체를 생성하는 것입니다. 이 서블릿이 jsp-file로 구성된 경우 이 servletClass는 conf/web.xml에 정의된 org.apache.jasper.servlet.JspServlet입니다.
Servlet 객체 생성에 관련된 클래스 구조 다이어그램은 다음과 같습니다.
그림 3. 서블릿 객체 생성 관련 클래스 구조
StandardWrapper의 initServlet 메소드에서 Servlet을 초기화합니다. 이 메소드는 단순히 Servlet의 init 메소드를 호출하는 동시에 StandardWrapper 객체를 ServletConfig로 래핑하는 StandardWrapperFacade를 전달하는 것입니다. Tomcat 컨테이너가 StandardWrapperFacade를 Servlet 객체에 전달하는 이유는 나중에 자세히 분석됩니다.
Servlet이 jsp 파일과 연관되어 있으면 JspServlet이 먼저 초기화됩니다. 다음으로 jsp 파일을 클래스로 컴파일하고 클래스를 초기화하기 위해 jsp 파일을 호출하는 간단한 요청이 시뮬레이션됩니다.
이런 식으로 Servlet 객체가 초기화되는 과정에서 실제로 웹에서 Servlet에 의해 구문 분석되는 과정에서 예측할 수 없는 오류가 발생하는 경우가 있습니다. 우리는 여기에서 자세히 설명할 몇 가지 주요 링크에만 초점을 맞추고 모든 사람에게 전체적인 맥락을 제공하려고 노력합니다.
다음은 이 프로세스의 전체 타이밍 다이어그램이며 일부 세부 사항은 생략되었습니다.
그림 4. 서블릿 초기화 시퀀스 다이어그램(더 크게 보기)
우리는 Java 웹 애플리케이션이 Servlet 사양을 기반으로 실행된다는 것을 알고 있습니다. 그러면 Servlet 자체는 어떻게 실행됩니까? 왜 그런 아키텍처를 설계해야 할까요?
그림 5. 서블릿 최상위 클래스 연관 다이어그램
위 그림에서 볼 수 있듯이 Servlet 사양은 이러한 클래스를 기반으로 작동합니다. ServletConfig, ServletRequest 및 ServletResponse라는 세 가지 클래스가 자동으로 연결됩니다. 이 세 클래스는 컨테이너를 통해 서블릿에 전달됩니다. ServletConfig는 서블릿이 초기화될 때 서블릿에 전달되고, 마지막 두 클래스는 요청이 도착할 때 서블릿이 호출될 때 전달됩니다. 우리는 서블릿 작업에서 ServletRequest와 ServletResponse의 중요성을 분명히 이해하고 있는데, 서블릿에 대한 ServletConfig와 ServletContext의 가치는 무엇입니까? ServletConfig 인터페이스에 선언된 메소드를 자세히 살펴보면 이러한 메소드가 모두 이 서블릿의 일부 구성 속성을 얻는 데 사용되며 이러한 구성 속성은 서블릿이 실행될 때 사용될 수 있음을 알 수 있습니다. 그리고 ServletContext는 무엇을 하는가? Servlet의 실행 모드는 일반적인 "handshake Interactive" 실행 모드입니다. 소위 "핸드셰이크 유형 상호 작용"은 두 모듈이 일반적으로 데이터 교환을 위해 트랜잭션 시나리오를 준비하고 이 시나리오는 트랜잭션이 완료될 때까지 트랜잭션 프로세스를 따르는 것을 의미합니다. 이 트랜잭션 시나리오의 초기화는 트랜잭션 개체에 의해 지정된 매개변수를 기반으로 사용자 정의됩니다. 이러한 지정된 매개변수는 일반적으로 구성 클래스입니다. 따라서 당신 말이 맞습니다. 트랜잭션 시나리오는 ServletContext에 의해 설명되고 사용자 정의 매개 변수 세트는 ServletConfig에 의해 설명됩니다. ServletRequest 및 ServletResponse는 상호작용할 특정 객체이며 일반적으로 상호작용 결과를 전달하는 전송 도구로 사용됩니다.
ServletConfig는 Servlet 초기화 중에 컨테이너에서 전달됩니다. 그러면 ServletConfig가 정확히 무엇입니까?
다음 그림은 Tomcat 컨테이너의 ServletConfig와 ServletContext의 클래스 관계 다이어그램입니다.
그림 6. 컨테이너 내 ServletConfig의 클래스 연관 다이어그램
위 그림에서 볼 수 있듯이 StandardWrapper와 StandardWrapperFacade는 모두 ServletConfig 인터페이스를 구현하고 StandardWrapperFacade는 StandardWrapper 파사드 클래스입니다. 따라서 Servlet에 전달되는 것은 StandardWrapperFacade 객체입니다. 이 클래스는 ServletConfig가 관심을 두지 않는 데이터를 Servlet에 노출시키지 않고도 ServletConfig가 지정한 데이터를 StandardWrapper에서 가져오도록 보장할 수 있습니다.
마찬가지로 ServletContext도 ServletConfig와 유사한 구조를 가지고 있습니다. Servlet에서 얻을 수 있는 ServletContext의 실제 객체도 ApplicationContextFacade 객체입니다. ApplicationContextFacade는 또한 ServletContex가 컨테이너에서 가져와야 하는 데이터만 가져올 수 있도록 보장하며 모두 데이터를 캡슐화하는 역할을 하며 모두 Facade 디자인 패턴을 사용합니다.
ServletContext를 통해 애플리케이션의 작업 경로, 컨테이너가 지원하는 최소 서블릿 버전 등과 같은 Context 컨테이너에서 필요한 정보를 얻을 수 있습니다.
Servlet에 정의된 두 ServletRequest와 ServletResponse의 실제 객체는 무엇입니까? , 자체 서블릿 클래스를 만들 때 일반적으로 ServletRequest 및 ServletResponse를 상속하는 HttpServletRequest 및 HttpServletResponse를 사용합니다. Context 컨테이너가 전달한 ServletRequest와 ServletResponse를 HttpServletRequest와 HttpServletResponse로 변환할 수 있는 이유는 무엇입니까?
그림 7. 요청 관련 클래스 구조 다이어그램
위 그림은 Tomcat에서 생성한 Request와 Response의 클래스 구조도이다. Tomcat이 요청을 받으면 먼저 org.apache.coyote.Request 및 org.apache.coyote.Response를 생성합니다. 이 두 클래스는 Tomcat에서 내부적으로 요청 및 해당 정보 클래스를 설명하는 데 사용됩니다. 기능은 서버가 요청을 받은 후 간단한 분석 후 처리를 위해 후속 스레드에 요청을 신속하게 할당하므로 개체가 매우 작고 JVM에서 쉽게 재활용할 수 있습니다. 다음으로 요청을 처리하기 위해 사용자 스레드로 넘겨지면 org.apache.catalina.connector.Request 및 org.apache.catalina.connector.Response 객체가 생성됩니다. 이 두 객체는 서블릿에 전달될 때까지 전체 서블릿 컨테이너를 통해 이동합니다. 서블릿에 전달되는 것은 Request 및 Response의 Facade 클래스 RequestFacade입니다. 여기서 사용되는 Facade 모드는 이전과 동일한 목적을 기반으로 합니다. 컨테이너에 데이터를 캡슐화합니다. 요청에 해당하는 Request와 Response의 클래스 변환은 아래 그림과 같습니다.
그림 8. 요청과 응답의 변환 과정
우리는 이미 서블릿이 로드되는 방식, 서블릿이 초기화되는 방식, 서블릿의 아키텍처를 이해했습니다. 이제 문제는 어떻게 호출되는지입니다.
사용자가 브라우저에서 서버로 요청을 시작하면 일반적으로 다음 정보가 포함됩니다: http://hostname: port /contextpath/servletpath 호스트 이름과 포트는 서버와의 TCP 연결을 설정하는 데 사용됩니다. URL은 서버의 해당 하위 컨테이너가 사용자의 요청을 처리하도록 선택하는 데 사용됩니다. 그렇다면 서버는 이 URL을 기반으로 올바른 서블릿 컨테이너에 어떻게 도달합니까?
이 매핑 작업은 org.apache.tomcat.util.http.mapper라는 특수 클래스에 의해 완료되기 때문에 이 문제는 Tomcat의 컨테이너 컨테이너 정보에 저장되기 때문에 쉽게 해결할 수 있습니다. org.apache.catalina.connector.Request 클래스가 컨테이너 컨테이너에 들어갈 때 매퍼는 이 요청의 호스트네인과 컨텍스트 경로를 기반으로 호스트와 컨텍스트 컨테이너를 요청의 mappingData 속성으로 설정합니다. 따라서 요청이 컨테이너 컨테이너에 들어가기 전에 액세스하려는 하위 컨테이너가 이미 결정되었습니다.
그림 9. 요청의 매퍼 클래스 다이어그램
궁금한 점이 있을 수 있습니다. 어떻게 매퍼에서 컨테이너 간에 완전한 관계가 있을 수 있습니까? 이는 그림 2의 MapperListener 클래스의 19단계 초기화 프로세스로 돌아갑니다. 다음은 MapperListener의 init 메소드 코드입니다.
목록 5. MapperListener.init
public void init() { findDefaultHost(); Engine engine = (Engine) connector.getService().getContainer(); engine.addContainerListener(this); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { host.addLifecycleListener(this); registerHost(host); } } }
这段代码的作用就是将 MapperListener 类作为一个监听者加到整个 Container 容器中的每个子容器中,这样只要任何一个容器发生变化,MapperListener 都将会被通知,相应的保存容器关系的 MapperListener 的 mapper 属性也会修改。for 循环中就是将 host 及下面的子容器注册到 mapper 中。
图 10.Request 在容器中的路由图
上图描述了一次 Request 请求是如何达到最终的 Wrapper 容器的,我们现正知道了请求是如何达到正确的 Wrapper 容器,但是请求到达最终的 Servlet 还要完成一些步骤,必须要执行 Filter 链,以及要通知你在 web.xml 中定义的 listener。
接下去就要执行 Servlet 的 service 方法了,通常情况下,我们自己定义的 servlet 并不是直接去实现 javax.servlet.servlet 接口,而是去继承更简单的 HttpServlet 类或者 GenericServlet 类,我们可以有选择的覆盖相应方法去实现我们要完成的工作。
Servlet 的确已经能够帮我们完成所有的工作了,但是现在的 web 应用很少有直接将交互全部页面都用 servlet 来实现,而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基本的原理都是将所有的请求都映射到一个 Servlet,然后去实现 service 方法,这个方法也就是 MVC 框架的入口。
当 Servlet 从 Servlet 容器中移除时,也就表明该 Servlet 的生命周期结束了,这时 Servlet 的 destroy 方法将被调用,做一些扫尾工作。
前面我们已经说明了 Servlet 如何被调用,我们基于 Servlet 来构建应用程序,那么我们能从 Servlet 获得哪些数据信息呢?
Servlet 能够给我们提供两部分数据,一个是在 Servlet 初始化时调用 init 方法时设置的 ServletConfig,这个类基本上含有了 Servlet 本身和 Servlet 所运行的 Servlet 容器中的基本信息。根据前面的介绍 ServletConfig 的实际对象是 StandardWrapperFacade,到底能获得哪些容器信息可以看看这类提供了哪些接口。还有一部分数据是由 ServletRequest 类提供,它的实际对象是 RequestFacade,从提供的方法中发现主要是描述这次请求的 HTTP 协议的信息。所以要掌握 Servlet 的工作方式必须要很清楚 HTTP 协议,如果你还不清楚赶紧去找一些参考资料。关于这一块还有一个让很多人迷惑的 Session 与 Cookie。
Session 与 Cookie 不管是对 Java Web 的熟练使用者还是初学者来说都是一个令人头疼的东西。Session 与 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷。然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的,例如使用 Cookie 来传递信息时,随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也很大,试想假如 Cookie 占用 200 个字节,如果一天的 PV 有几亿的时候,它要占用多少带宽。所以大访问量的时候希望用 Session,但是 Session 的致命弱点是不容易在多台服务器之间共享,所以这也限制了 Session 的使用。
不管 Session 和 Cookie 有什么不足,我们还是要用它们。下面详细讲一下,Session 如何基于 Cookie 来工作。实际上有三种方式能可以让 Session 正常工作:
基于 URL Path Parameter,默认就支持
基于 Cookie,如果你没有修改 Context 容器个 cookies 标识的话,默认也是支持的
基于 SSL,默认不支持,只有 connector.getAttribute("SSLEnabled") 为 TRUE 时才支持
第一种情况下,当浏览器不支持 Cookie 功能时,浏览器会将用户的 SessionCookieName 重写到用户请求的 URL 参数中,它的传递格式如 /path/Servlet;name=value;name2=value2? Name3=value3,其中“Servlet;”后面的 K-V 对就是要传递的 Path Parameters,服务器会从这个 Path Parameters 中拿到用户配置的 SessionCookieName。关于这个 SessionCookieName,如果你在 web.xml 中配置 session-config 配置项的话,其 cookie-config 下的 name 属性就是这个 SessionCookieName 值,如果你没有配置 session-config 配置项,默认的 SessionCookieName 就是大家熟悉的“JSESSIONID”。接着 Request 根据这个 SessionCookieName 到 Parameters 拿到 Session ID 并设置到 request.setRequestedSessionId 中。
클라이언트가 쿠키도 지원하는 경우 Tomcat은 여전히 쿠키의 세션 ID를 구문 분석하고 URL의 세션 ID를 덮어씁니다.
세 번째 경우에는 javax.servlet.request.ssl_session 속성 값에 따라 Session ID가 설정됩니다.
Session ID를 사용하여 서버는 요청 getSession() 메서드를 통해 HttpSession 객체를 생성할 수 있습니다. 현재 Session ID에 해당하는 HttpSession 객체가 없으면 새 객체를 생성하고 이 객체를 Saved in에 추가합니다. org.apache.catalina의 세션 컨테이너인 Manager 클래스는 모든 세션의 수명 주기를 관리합니다. 세션은 만료되면 재활용되고 서버는 종료되며 세션은 디스크에 직렬화됩니다. 이 HttpSession 객체가 존재하는 한 사용자는 Session ID를 기반으로 이 객체를 획득하여 상태를 유지할 수 있습니다.
그림 11. 세션 관련 클래스 다이어그램
위 그림에서 볼 수 있듯이 request.getSession에서 얻은 HttpSession 객체는 실제로 StandardSession 객체의 Facade 객체입니다. 이는 이전 Request 및 Servlet과 동일한 원리입니다. 다음 그림은 세션 작업의 타이밍 다이어그램입니다.
그림 12. 세션 작업 타이밍 다이어그램(큰 그림 보기)
또 다른 점은 세션과 관련된 쿠키가 다른 쿠키와 다르지 않다는 것입니다. 이 구성의 구성은 web.xml의 session-config 구성 항목을 통해 지정할 수 있습니다.
Listener는 Tomcat 서버 전체에서 널리 사용됩니다. 관찰자 패턴을 기반으로 설계되었습니다. Listener의 설계는 서블릿 애플리케이션을 빠르게 개발할 수 있는 방법을 제공하고 다른 수직 차원의 프로그램 및 데이터를 쉽게 제어할 수 있습니다. 현재 Servlet은 4개의 EventListeners 유형, ServletContextAttributeListener, ServletRequestAttributeListener, ServletRequestListener, HttpSessionAttributeListener 및 2개의 LifecycleListeners 유형, ServletContextListener, HttpSessionListener의 두 가지 유형의 이벤트에 대해 5개의 관찰자 인터페이스를 제공합니다. 아래 사진과 같습니다:
그림 13. 서블릿의 리스너(더 큰 이미지 보기)
기본적으로 전체 서블릿 라이프사이클 동안 관심 있는 모든 이벤트를 다룹니다. 이러한 리스너의 구현 클래스는 web.xml의 eae7f28921fa176b20e23abb843be090 태그에서 구성할 수 있습니다. 물론 리스너는 애플리케이션에 동적으로 추가될 수도 있습니다. 컨테이너가 시작된 후에는 ServletContextListener가 수신하는 이벤트가 더 이상 나타나지 않기 때문에 새 리스너를 추가할 수 없다는 점에 유의해야 합니다. 이러한 리스너의 사용을 마스터하면 프로그램 설계를 더욱 유연하게 만들 수 있습니다.
이 글은 내용이 많아 모든 세부 사항을 명확하게 설명하는 것은 불가능해 보입니다. 이 글에서는 서블릿 컨테이너의 시작부터 서블릿 초기화까지의 몇 가지 핵심 사항과 서블릿 아키텍처를 허용합니다. 독자들이 전체적이고 완전한 구조 다이어그램을 갖고, 일부 어려운 문제를 자세히 분석하는 것이 모든 사람에게 도움이 되기를 바랍니다.
위 내용은 Servlet 작동원리 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!