How Tomcat works阅读笔记

How Tomcat works阅读笔记

知其然更要知其所以然,为了更好的理解Tomcat的运行逻辑和内部结构,这本《深入剖析Tomcat》是不错的选择。

第一章:一个简单的Web服务器

Socket类

套接字socket相当于应用程序从网络获取、写入数据的接口。

ServerSocket

Socket更像是一个客户端的套接字(当然,服务端从网络中获取数据也要使用Socket)。对于服务器来说,需要时刻保持警惕。因为客户端的请求会不定时的传来,ServerSocket的存在就是为了解决这个问题。它的accept()方法等待连接请求,创建一个Socket来与客户端通信。

Socket与ServerSocket

// An highlighted block
public void await() {
    ServerSocket serverSocket = null;
    int port = 8080;
    serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    // Loop waiting for a request
    while (!shutdown) {
      Socket socket = null;
      InputStream input = null;
      OutputStream output = null;
      try {
        socket = serverSocket.accept();
        //构建request与response
        input = socket.getInputStream();
        output = socket.getOutputStream();
        // create Request object and parse
        Request request = new Request(input);
        request.parse();
        // create Response object
        Response response = new Response(output);
        response.setRequest(request);
        response.sendStaticResource();
        // Close the socket
        socket.close();
        //check if the previous URI is a shutdown command
        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
      }
      catch (Exception e) {
        e.printStackTrace();
        continue;
      }
    }
  }

程序调用流程:

调用
创建
调用
出现http请求,生成
HttpServer
await方法
ServerSocket
accept方法
Socket

第二章:一个简单的servlet容器

本章节通过两个简单的应用程序说明了一下servlet容器的大致运行过程。

应用程序1

在HttpServer中添加了对于请求的判断,servlet or 静态文件,确定了请求的类型后,交由ServletProcessor或StaticResourceProcessor的process()方法
servlet or 静态文件

public class HttpServer1 {
public static void main(String[] args) {
    HttpServer1 server = new HttpServer1();
    server.await();
  }
public void await() {
	......
	if (request.getUri().startsWith("/servlet/")) {
	    ServletProcessor1 processor = new ServletProcessor1();
	    processor.process(request, response);
	}
	else {
	     StaticResourceProcessor processor = new StaticResourceProcessor();
	     processor.process(request, response);
	}
	......
	}
}

servletProcessor的process方法

public class ServletProcessor1 {
	public void process(Request request, Response response) {
	URLClassLoader loader = null;
    try {
      // 构造一个URL类载入器
      ......
    } catch (IOException e) {
      System.out.println(e.toString());
    }
    Class myClass = null;
    try {
	  //在仓库repository中获取目标servlet类
      myClass = loader.loadClass(servletName);
    } catch (ClassNotFoundException e) {
      System.out.println(e.toString());
    }
    Servlet servlet = null;
    try {
      //实例化servlet
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request, (ServletResponse) response);
    } catch (Exception e) {
      System.out.println(e.toString());
    } catch (Throwable e) {
      System.out.println(e.toString());
    }
	}
}

程序调用流程:

调用
创建
调用
出现http请求,生成
生成
请求servlet
调用
请求静态文件
调用
HttpServer1
await方法
ServerSocket
accept方法
Socket
Request
ServletProcessor1
process方法
StaticResourceProcessor
process方法

应用程序2

在应用程序1中出现了一些不安全的代码:

try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request, (ServletResponse) response);
    }

出现了Request向上转型为ServletRequest的情况,Request的parse()方法在servlet中是不应该出现的。居心不良的人可以将构造servlet的ServletRequest向下转型为Request。于是parse()方法就可以使用了,所以需要修改相关代码。

RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
  servlet = (Servlet) myClass.newInstance();
  servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
} 

外观模式

第三章:连接器

连接器的作用就是接收并解析Http请求,根据收到的Http请求来包装request,并将request和response传给Servlet容器。

StringManager类

此类用来处理Tomcat中的错误信息。

应用程序

在拥有了连接器后,request以及response的创建都留在HttpProcess类的process()方法中创建。

Bootstrap类

该类用来启动应用程序

public final class Bootstrap {
  public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    connector.start();
  }
}

HttpConnector类

该类的作用是等待HTTP请求,创建Socket套接字。将套接字作为参数传入HttpProcess类的process()方法中。

public class HttpConnector implements Runnable {
	public void run() {
	......
	socket = serverSocket.accept();
	HttpProcessor processor = new HttpProcessor(this);
    processor.process(socket);
	......
	}
}

HttpProcessor类

该类的主要方法是process()方法,该方法的工作是创建HttpRequest和HttpResponse;判断请求的是静态文件还是servlet后,将request和response传给StaticResourceProcessor或者servletProcessor的process()方法(两个process()方法
servletProcessor的process()方法在上一章有介绍。

程序调用流程:

创建
调用
生成
参数Socket,调用
生成
生成
生成
生成
参数request,response调用
Bootstrap
Connection
run方法
HttpProcessor
process方法
Socket
Response
Request
ServletProcessor
process

当然,生成request和response需要解析HTTP请求。对HTTP请求进行解析的方法parseHeaders()、parseRequest()也全部放在HttpProcessor类中。

public class HttpProcessor {
	public void process(Socket socket) {
	......
	parseRequest(input, output);
    parseHeaders(input);
	......
	}
	
	private void parseHeaders(SocketInputStream input){
	......
	}
	
	private void parseRequest(SocketInputStream input, OutputStream output){
	......
	}
}

第四章:Tomcat默认连接器

本章使用的默认连接器与上一章的连接器工作原理类似,但是与第三章相比加入了一些优化。首先,第三章中的HttpConnection必须等待当前正在处理的Http请求返回才能接收下一个Http请求。

对第三章连接器的升级

第三章Connector的run方法

public class HttpConnector implements Runnable {
	public void run() {
	...
		while (!stopped) {
		...
		socket = serverSocket.accept();
		HttpProcessor processor = new HttpProcessor(this);
		//这里可以看出,想要接收下一个http请求必须等待process()方法执行完
      	processor.process(socket);
      	...
      	}
    ...
	}
}

需要知道的是start()方法会使得该线程开始执行;java虚拟机会去调用该线程的run()方法。
具体原因是,HttpConnector中的start()方法中执行了threadStart()方法,在此方法中执行了thread.start()方法,而进一步看thread.start()方法,最关键的一步是start0()方法,此方法表示若实现了Runnable接口则执行器run()方法。这就是上面标黄部分的解释。

本章中,HttpProcessor类也实现了Runnable接口。也就是说,当HttpConnector新建HttpProcessor实例时,会自动调用其start()方法,HttpProcessor就可以在自己的线程中执行了。
HttpConnector连接器使HttpProcessor执行在自己线程的过程

public final class HttpConnector
    implements Connector, Lifecycle, Runnable {
    public void start(){
    	......
    	HttpProcessor processor = newProcessor();
    	//processor入栈
        recycle(processor);
    	......
    }
    
    private HttpProcessor newProcessor() {
    	......
    	((Lifecycle) processor).start();
    	......
    }
    
    public void run() {
    	......
    	HttpProcessor processor = createProcessor();
    	//重要方法,用来配合processor的await()方法
    	processor.assign(socket);
    	......
    }
    private HttpProcessor createProcessor() {
    	if(......){
    		return ((HttpProcessor) processors.pop());
    	}
    	else if(......){
    		return (newProcessor());
    	}
    }
}

HttpProcessor在连接器中创建时会触发start()方法,但是在执行run()方法时,await()方法需要等待连接器HttpConnector的run()方法中的assign()。

Socket
Socket
connector
assign方法
processor
await方法
public final class HttpConnector{
	......
	public void run() {
    	......
    	//重要方法,用来配合processor的await()方法
    	processor.assign(socket);
    	......
    }
    ......
}
final class HttpProcessor{
    public void run() {
    	......
    	Socket socket = await();
    	process(socket);
    	connector.recycle(this);
    	......
    }
}

处理请求

就是解析Http请求的各个部分,当遇到问题的时候再具体看,目前对于理解Tomcat的运行机制没太大帮助。

应用程序

因为本章的主题是Tomcat默认连接器,但是一个应用程序需要servlet容器。所以提供了一个简单的servlet容器SimpleContainer。
其中,invoke()方法的作用是接收HttpProcessor的process()方法传递的request、response;创建一个类载入器;载入相关servlet的service()方法。
值得一提的是,invoke()方法与第三章ServletProcessor类的process()方法类似

public class SimpleContainer implements Container {
	public void invoke(Request request, Response response){
		......
		URLClassLoader loader = new URLClassLoader(urls);
		Class myClass = loader.loadClass(servletName);
		Servlet servlet = (Servlet) myClass.newInstance();
		servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
		......
	}
}

第五章:Servlet容器

Servlet容器的作用就是接收到连接器传来的request和response。根据request中的请求来调用servlet对象,为客户端填充response对象。

Container接口

各级Container接口的关系

包含
包含多个
包含多个
Engine
Host
Context
Wrapper

其中Engine表示整个Catalina servlet引擎,
Host表示包含若干个Context容器的虚拟主机,
Context表示一个Web应用程序,可能包含多个Wrapper,
Wrapper表示一个独立的Servlet。

管道任务

管道相当于Servlet容器需要执行的任务列表,管道中的阈就是一个个任务。还记得上一章中HttpProcessor将request和response传入servlet容器的invoke()方法吗?它在管道任务的调用上发挥重要作用。
对于一个Context容器,执行过程如下所示:

获取
传入request和response
传入request和response
HttpProcessor
run方法
process方法
getContaine方法
Context容器
invoke方法
pipeline的invoke方法
pipeline上的阈的invoke方法
ValveContext
invokeNext
基础阈
映射器
Wrapper容器
Wrapper容器
invoke方法
pipeline的invoke方法
ValveContext
invokeNext
基础阈
l载入器
Servlet
service方法

上图中值得注意的是在具体的阈中实现遍历靠的是ValveContext,具体的执行原理可以查看从SimpleContext类的invoke()方法开始的调用过程。

Context容器查找Wrapper使用的是Mapper映射器;Wrapper容器查找Servlet使用的是Loader载入器。

第六章:生命周期

Lifecycle接口

Catalina允许一种父组件包含子组件的的结构。例如,Context容器包含Wrapper,映射器等子组件。子组件时刻被其父组件“监管”,当父组件启动时,所有的子组件会自动启动。
Lifecycle接口的作用就是实现这种单一启动/关闭机制。大概有下面三类方法。

public interface Lifecycle {
	//组件在运行过程中可能会触发的事件
	public static final String START_EVENT = "start";
    public static final String BEFORE_START_EVENT = "before_start";
    public static final String AFTER_START_EVENT = "after_start";
    public static final String STOP_EVENT = "stop";
    public static final String BEFORE_STOP_EVENT = "before_stop";
    public static final String AFTER_STOP_EVENT = "after_stop";
    //启动和终止的方法(最重要)
	public void start() throws LifecycleException;
	public void stop() throws LifecycleException;
	//与监听器相关的方法
	public void addLifecycleListener(LifecycleListener listener);
	public LifecycleListener[] findLifecycleListeners();
	public void removeLifecycleListener(LifecycleListener listener);
}

LifecycleEvent类

该类的实例就是之前所说的六个事件:

  1. START_EVENT
  2. BEFORE_START_EVENT
  3. AFTER_START_EVENT
  4. STOP_EVENT
  5. BEFORE_STOP_EVENT
  6. AFTER_STOP_EVENT

LifecycleListener接口

LifecycleListener接口只有一个方法lifecycleEvent(),当监听器监听到相关事件发生时,会调用该方法。

public interface LifecycleListener {
	public void lifecycleEvent(LifecycleEvent event);
}
}

LifecycleSupport类

该类的作用是帮助组件管理监听器、触发相应的生命周期事件。

public final class LifecycleSupport {
	......
	private Lifecycle lifecycle = null;
	private LifecycleListener listeners[] = new LifecycleListener[0];
	
	//下面是方法
	public void addLifecycleListener(LifecycleListener listener) {
		......
	}
	public void removeLifecycleListener(LifecycleListener listener) {
		......
	}	
	public LifecycleListener[] findLifecycleListeners() {
		......
	}
	public void fireLifecycleEvent(String type, Object data) {
		......
	}
	......
}

所有实现了Lifecycle接口的类通过引入LifecycleSupport类来对监听器进行管理。

public final class HttpConnector
    implements Connector, Lifecycle, Runnable {
    ......
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);
    ......
}

应用程序

本章的应用程序使用了第五章应用程序的主体,包含了一个Context实例、两个Wrapper实例、一个载入器、一个映射器。但是为了让读者更好的理解Lifecycle机制,应用程序中的组件都实现了Lifecycle接口,其中Context实例中使用了事件监听器。

值得一提的是,虽然本章使用的连接器Connector也实现了Lifecycle接口且使用了类似((Lifecycle) connector).start()来执行程序,但是在其start()方法中并见将其子组件一同启动起来的代码。

SimpleContext类

public class SimpleContext implements Context, Pipeline, Lifecycle {
	......
	//LifecycleSupport帮助管理监听器
	protected LifecycleSupport lifecycle = new LifecycleSupport(this);
	......
	public synchronized void start() throws LifecycleException {
		//判断是否已经开始执行
		if (started)
      		throw new LifecycleException("SimpleContext has already started");
      	//触发事件BEFORE_START_EVENT
      	lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
		......
		//启动子组件
		if ((loader != null) && (loader instanceof Lifecycle))
        ((Lifecycle) loader).start();
        //启动子容器
        Container children[] = findChildren();
        for (int i = 0; i < children.length; i++) {
          if (children[i] instanceof Lifecycle)
            ((Lifecycle) children[i]).start();
        }
        //启动管道中的任务
        if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();
		//触发START_EVENT事件
		lifecycle.fireLifecycleEvent(START_EVENT, null);
	}
	......
}

在start()方法执行的过程中,触发了BEFORE_START_EVENT、START_EVENT、AFTER_START_EVENT事件,在stop()方法中还会触发BEFORE_STOP_EVENT、STOP_EVENT、AFTER_STOP_EVENT事件。

SimpleContextLifecycleListener类

为什么触发了某些事件,lifecycleEvent()方法就会被执行?我们需要了解一下“触发事件”的方法fireLifecycleEvent()具体是怎么执行的。

public final class LifecycleSupport {
	......
	private LifecycleListener listeners[] = new LifecycleListener[0];
	private Lifecycle lifecycle = null;

	public void addLifecycleListener(LifecycleListener listener) {
		......
	}
	public void removeLifecycleListener(LifecycleListener listener) {
		......
	}	
	//下面是关键方法,拷贝当前所有监听器并执行lifecycleEvent()方法
	public void fireLifecycleEvent(String type, Object data) {
		LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener interested[] = null;
        synchronized (listeners) {
            interested = (LifecycleListener[]) listeners.clone();
        }
        for (int i = 0; i < interested.length; i++)
            interested[i].lifecycleEvent(event);
	}
	......
}

监听器类中的lifecycleEvent()就是在触发后需要执行的方法。这些监听器实例化以后使用LifecycleSupport类的addLifecycleListener()方法加入监听器数组中。在fireLifecycleEvent()方法中会被重新调出来。

public class SimpleContextLifecycleListener implements LifecycleListener {

  public void lifecycleEvent(LifecycleEvent event) {
    Lifecycle lifecycle = event.getLifecycle();
    System.out.println("SimpleContextLifecycleListener's event " +
      event.getType().toString());
    if (Lifecycle.START_EVENT.equals(event.getType())) {
      System.out.println("Starting context.");
    }
    else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
      System.out.println("Stopping context.");
    }
  }
}

SimpleWrapper类

该类的实现与SimpleContext类类似,都是将其子组件一起运行并触发事件。但是在stop()方法中略有不同,它需要destory()servlet实例。

第七章:日志记录器

本章的内容较简单,大致就是如何记录servlet提供的日志信息的问题。等有实际应用时在添加内容。

第八章:载入器

前几章的载入器属于系统的载入器,使用系统的载入器就相当于servlet容器完全信任它正在运行的servlet,这就会导致该servlet可以访问所有的类—包括Java虚拟机中环境变量CLASSPATH指明的路径下所有的类和库,不妥。所以servlet容器需要一个自定义的载入器,使其运行的servlet只能访问WEB-INF/classes目录下的文件

  1. 仓库:类载入器在哪里搜索要载入的类
  2. 资源:类载入器中的DirContext对象,它的文件根路径就是指上下文的文件根路径

还有一个使用自定义载入器的原因—为了实现自动重载功能。当WEB-INF/classes目录下的类发生变化时,,Web应用能重新载入这些类。类载入器使用一个额外的线程来检查目录下的变化。

Java的类载入器

简单的介绍了一下Java类载入器的结构以及其代理模式。举例介绍了代理模式下是如何解决类载入过程中的安全问题的。

Loader接口

这里的载入器是指Web应用程序载入器,应用程序载入器的实现中需要:

  1. 一个自定义的载入器(WebappClassLoader类)
  2. 与Servlet容器Context相关联
  3. 仓库操作
  4. 重载问题
  5. 应用程序中的servlet只能引用部署在WEB-INF/classes目录及其子目录下的类
  6. servlet类只能访问WEB-INF/lib目录下的库
  7. WEB-INF/classes和WEB-INF/lib作为仓库添加到载入器中
public interface Loader {
	//获取类载入器
	public ClassLoader getClassLoader();
	//与容器的关联
	public Container getContainer();
	public void setContainer(Container container);
	//关于重载问题
	public boolean getReloadable();
	public void setReloadable(boolean reloadable);
	public boolean modified();
	//仓库相关操作
	public void addRepository(String repository);
	public String[] findRepositories();
	......
}

Reloader接口

支持自动重载功能的类载入器需要实现Reloader接口。

WebappLoader类

该类就是Web应用程序的载入器,实现了Loader接口。它的作用就是载入Web应用程序中所使用的类。还实现了Runnable、Lifecycle接口。当调用WebappLoader的start()方法时,要完成以下的工作:

  1. 创建一个类载入器
  2. 设置仓库
  3. 设置类路径
  4. 设置访问权限
  5. 启动新进程来支持自动重载
public class WebappLoader
    implements Lifecycle, Loader, PropertyChangeListener, Runnable {
    ......
    public void start() throws LifecycleException {
    	//判断生命周期是否已经启动
    	if (started)
            throw new LifecycleException
                (sm.getString("webappLoader.alreadyStarted"));
        if (debug >= 1)
            log(sm.getString("webappLoader.starting"));
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

		//主体部分
		try {
			//新建类载入器,下一小节要说
			classLoader = createClassLoader();
			//向类载入器中添加仓库
            for (int i = 0; i < repositories.length; i++) {
                classLoader.addRepository(repositories[i]);
            }
            //设置仓库,与上面的代码对比
            setRepositories();
            setClassPath();
            setPermissions();
            if (classLoader instanceof Lifecycle)
                ((Lifecycle) classLoader).start();
		}
		......
    }
    ......
}

创建类载入器

Web应用程序实现对类的载入需要一个类载入器,在start()方法中使用了createClassLoader()方法来创造类载入器。该方法的实现如下所示:

public class WebappLoader
    implements Lifecycle, Loader, PropertyChangeListener, Runnable {
    ......
    private WebappClassLoader createClassLoader()
        throws Exception {
        Class clazz = Class.forName(loaderClass);
        WebappClassLoader classLoader = null;

		//可以使用默认的类载入器也可以自定义类载入器
        if (parentClassLoader == null) {
            classLoader = (WebappClassLoader) clazz.newInstance();
        } else {
            Class[] argTypes = { ClassLoader.class };
            Object[] args = { parentClassLoader };
            Constructor constr = clazz.getConstructor(argTypes);
            classLoader = (WebappClassLoader) constr.newInstance(args);
        }
        return classLoader;
    }
}

设置仓库、类路径、访问权限

public class WebappLoader
    implements Lifecycle, Loader, PropertyChangeListener, Runnable {
    ......
    public void start() throws LifecycleException {
    	......
    	//设置仓库
    	for (int i = 0; i < repositories.length; i++) {
        	classLoader.addRepository(repositories[i]);
        }
        setRepositories();
        //设置类路径
        setClassPath();
        //设置访问权限
        setPermissions();
    }

自动重载功能的实现

自动重载功能是指使用一个线程,每隔checkInterval时间间隔检查一遍WEB-INF/classes或WEB-INF/lib目录下是不是有类被重新编译了,如果有则该类需要被重新编译。

public class WebappLoader
    implements Lifecycle, Loader, PropertyChangeListener, Runnable {
    ......
    public void run() {
    	......
    	while (!threadDone) {
            // 等待一段时间
            threadSleep();
            if (!started)
                break;
            try {
                // 判断文件是否被修改,未被修改则重新while
                if (!classLoader.modified())
                    continue;
            } 
            //通知Context容器重新载入 
            notifyContext();
            break;
        }
        ......
    }
    ......
}

WebappClassLoader类

优化:缓存之前已经载入的类来提升性能、缓存之前加载失败的类的名字来防止反复查找。
安全:WebappClassLoader类不允许载入某些类、某些包下的类。这些“禁忌”存在一些特定的数组中。
在载入类时需要执行的操作是:

  1. 检查缓存
  2. 若无缓存则使用系统的类载入器
  3. 检查是否允许载入
  4. 检查标志位delegate决定使用父载入器(自定义)还是系统载入器
  5. 若找不到,抛出ClassNotFoundException异常

类缓存

所有的已缓存的类都被视为资源,该资源的类是ResourceEntry类,存在一个名为resourceEntries的HashMap类型的变量中。

应用程序

监听器类:SimpleContextConfig

在第六章中我们具体说明了一下监听器运行的流程,不再赘述。

public class SimpleContextConfig implements LifecycleListener {

  public void lifecycleEvent(LifecycleEvent event) {
    if (Lifecycle.START_EVENT.equals(event.getType())) {
      Context context = (Context) event.getLifecycle();
      context.setConfigured(true);
    }
  }
}

与之前不同的地方是,本章使用的servlet容器是StandardContext类,需要设置一个系统属性来通知StandardContext在哪里查找应用程序目录,且在StandardContext中,servlet类只能放在应用程序目录下的WEB-INF/classes目录下。应用程序目录名为myApp。

public final class Bootstrap {
  public static void main(String[] args) {
  	System.setProperty("catalina.base", System.getProperty("user.dir"));
  	......
  	Context context = new StandardContext();
    // StandardContext's start method adds a default mapper
    context.setPath("/myApp");
    context.setDocBase("myApp");
    ......
  }
}

???可以通过修改Context的path属性来指定url最终访问的是哪个应用,而docBase指明该应用的具体物理地址。

<Context path="/test" docBase="testapp" reloadable="true"/>

对于以下的内容还是存有疑惑,但是我觉得应该就是指myApp这个目录下的三个servlet。

  1. setPath("/myApp"):设置应用程序路径
  2. setDocBase(“myApp”):设置上下文的文档根路径(应用程序的路径或WAR文件存放的路径)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

第九章:Session管理

session对象的管理是通过Session管理器来进行的,Session管理器是由实现了mannager接口的实例来实现的。
获取session的过程:

«interface» HttpServletRequest getSession() HttpRequestBase getSession() Servlet实例 实现 生成

可见,接口HttpServletRequest的getSession()方法是获取session的关键方法,那么getSession()方法具体是如何实现的呢?

public class HttpRequestBase
    extends RequestBase
    implements HttpRequest, HttpServletRequest {
	......
	public HttpSession getSession(boolean create){
		......
		return doGetSession(create);
	}
	public HttpSession doGetSession(boolean create){
		......
		Manager manager = null;
		if(context != null){
			manager = context.getManager();
		}
		......
		//创建一个session
		session = manager.createSession();
		......
	}
}

Session对象

通过相关的UML图,可以看到StandardSession类实现了HttpSession接口和Session接口。但是Session管理器并不会直接将StandardSession实例直接交给servlet使用,而是会将StandardSession的外观类StandardSessionFacade传给servlet
在这里插入图片描述

Session接口

作为Catalina内部的外观类???

StandardSession类

StandardSession类的构造函数需要一个参数Manager(StandardSession类必须有一个Session管理器)
注意StandardSession类中的getSession()方法,通过传入一个自身实例来创建并返回StandardSessionFacade类的实例。

class StandardSession
    implements HttpSession, Session, Serializable {
    public HttpSession getSession() {
    	if (facade == null)
            facade = new StandardSessionFacade(this);
        return (facade);
    }
}

如果某个Session管理器中的Session对象在maxInactiveInterval的时间内都没有被访问的话,将被设置为过期。设置的方法是expire()方法

class StandardSession
    implements HttpSession, Session, Serializable {
    public void expire(boolean notify) {
    	if (expiring)
            return;
        expiring = true;
        setValid(false);
        // 移除Session管理器的Session实例
        if (manager != null)
            manager.remove(this);
        //触发一些事件
        ......
    }
}

StandardSessionFacade类

该类是StandardSession类的外观类,为什么要使用此外观类呢?通过下面的UML类图可以看出来,StandardSessionFacade类只实现了HttpSession接口而没有实现Session接口,这样就导致servlet在获得StandardSessionFacade类后没法将其向下转换为StandardSession类型,更安全。
在这里插入图片描述

Manager(Session管理器组件)

Session管理器负责管理Session对象(删除、添加等),相关UML类图如下所示:
在这里插入图片描述
注意类图的结构,Session管理器相关的类都实现了Manager接口;ManagerBase是所有类的基类,其中PersistentManagerBase会将Session对象存储到辅助存储器中。

Manager接口

许多需要实现的方法:

  1. getContainer()、setContainer()方法
  2. createSession()方法
  3. add()、remove()方法:从Session池中添加或删除session对象
  4. load()、unload()

ManagerBase类

该类为抽象类,所有的Session管理器组件都会继承此类。其实现了的许多方法可以方便子类使用:

  1. createSession()方法
  2. generateSessionId()方法

Session管理器对应Context容器,Session管理器会管理该Context容器中的所有session对象,所有的Session对象都存在一个名为sessions的HashMap中。
其中add()、remove()方法都是往这个HashMap中添加或者删除session对象。

StandardManager类

Manager的标准实现,它实现了LifeCycle接口与其关联的Context容器一同启动或关闭。它将session存在内存中,当Catalina关闭时,将session对象存在一个名为SESSION.ser的文件中。该文件位于环境变量CATALINA_HOME指定的目录下的work目录中。
StandardManager还要销毁那些已经失效的session对象,在其run()方法中的processExpire()方法会遍历所有的session对象,如果session长时间未访问则调用Session接口的expire()方法使这个Session实例过期。

public class StandardManager
    extends ManagerBase
    implements Lifecycle, PropertyChangeListener, Runnable {
    ......
    public void run() {
        // Loop until the termination semaphore is set
        while (!threadDone) {
            threadSleep();
            //遍历session查找是否有过期的,若有则调用expire()
            processExpires();
        }
    }
}

PersistentManagerBase类

该类是持久化Session管理器的父类,也是个抽象类(所有持久化session管理器的父类);持久化Session管理器和StandardSession的区别在于持久化Session管理器存储Session对象的辅助存储器的形式(Store对象)。
Store对象的作用就是将Session对象存入某个文件中。

public abstract class PersistentManagerBase
    extends ManagerBase
    implements Lifecycle, PropertyChangeListener, Runnable {
    ......
    public void run() {
        // Loop until the termination semaphore is set
        while (!threadDone) {
            threadSleep();
            processExpires();
            processPersistenceChecks();
        }
    }
}

其中,processPersistenceChecks()方法负责调用processMaxIdleSwaps()、processMaxActiveSwaps()、processMaxIdleBackups()三个方法。
分别负责换出和备份。
由于可以换出和备份,findSession()方法会先在内存中查找,再在存储器中查找。

PersistentManager类

和PersistentManagerBase类差不多。

DistributedManager类

用于两个或多个节点的集群环境,一个节点表示部署的一台Tomcat服务器。每个节点都使用DistributedManager实例,来复制Session对象。为了实现这一主要功能,集群中的节点需要能够接受和发送数据给其他的节点。

public final class DistributedManager extends PersistentManagerBase {
	......
	private ClusterSender clusterSender = null;
	......
	public Session createSession() {
		Session session = super.createSession();
        ObjectOutputStream oos = null;
        ByteArrayOutputStream bos = null;
        ByteArrayInputStream bis = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(new BufferedOutputStream(bos));
            ((StandardSession)session).writeObjectData(oos);
            oos.close();
            byte[] obs = bos.toByteArray();
            
            //与其他节点交互
            clusterSender.send(obs);
            
            if(debug > 0)
                log("Replicating Session: "+session.getId());
        } catch (IOException e) {
            log("An error occurred when replicating Session: "+session.getId());
        }
        return (session);
	}
	......
	public void run() {
        // Loop until the termination semaphore is set
        while (!threadDone) {
            threadSleep();
            //接收其他节点的消息
            processClusterReceiver();
            processExpires();
            processPersistenceChecks();
        }
    }
}

存储器

在这里插入图片描述

存储器都是Store接口的实例,在Store接口中两个方法比较重要:

  1. save()方法:将指定的Session对象存到某种持久存储器
  2. load()方法:从存储器中拿出某个Session对象放入内存中

StoreBase类

StoreBase类是一个抽象类,是FileStore和JDBCStore类的父类。但是StoreBase类没有实现save()、load()方法;因为这些方法需要根据具体的存储器来定。

public abstract class StoreBase
    implements Lifecycle, Runnable, Store {
    ......
    public void run() {
        // Loop until the termination semaphore is set
        while (!threadDone) {
            threadSleep();
            processExpires();
        }
    }
	......
}

FileStore

JDBCStore

应用程序

本章的应用程序和上一章的差不多,不同的是本章的Context容器使用StandardManager实例来管理Session对象。回顾之前的内容,servlet对于session对象的获取是通过创建HttpRequestBase实例,调用其getSession()方法。
获取session的过程:

«interface» HttpServletRequest getSession() HttpRequestBase getSession() Servlet实例 实现 生成

可见,接口HttpServletRequest的getSession()方法是获取session的关键方法,那么getSession()方法具体是如何实现的呢?

public class HttpRequestBase
    extends RequestBase
    implements HttpRequest, HttpServletRequest {
	......
	public HttpSession getSession(boolean create){
		......
		return doGetSession(create);
	}
	public HttpSession doGetSession(boolean create){
		......
		Manager manager = null;
		if(context != null){
			manager = context.getManager();
		}
		......
		//创建一个session
		session = manager.createSession();
		......
	}
}

所以传递给servlet的request必须与Context容器相关联。在HttpRequestBase实例中使用其相关联的context容器获取session管理器Manner

那么如何将request和Context容器进行关联呢?在wrapper容器的阈中进行关联

public class SimpleWrapperValve implements Valve, Contained {
	......
	public void invoke(Request request, Response response, ValveContext valveContext)
    throws IOException, ServletException {
    	
    	ServletRequest sreq = request.getRequest();
    	ServletResponse sres = response.getResponse();
    	
    	HttpServletRequest hreq = null;
	    if (sreq instanceof HttpServletRequest)
	      hreq = (HttpServletRequest) sreq;
	      
	    HttpServletResponse hres = null;
	    if (sres instanceof HttpServletResponse)
	      hres = (HttpServletResponse) sres;
	    
    	......
    	//将request对象和COntext容器相关联
    	Context context = (Context) wrapper.getParent();
    	request.setContext(context);
    	
    	//将request和response传入servlet
    	try {
	      servlet = wrapper.allocate();
	      if (hres!=null && hreq!=null) {
	        servlet.service(hreq, hres);
	      }
	      else {
	        servlet.service(sreq, sres);
	      }
	    }
	    catch (ServletException e) {
	    }
    }
}

提一嘴,我将bootstrap中的addServletMapping方法中的“/myApp/Session”修改为“/my/Session”时,在浏览器中无法看到previous value,是因为程序路径path和上下文文档根路径被设置为了/myApp,该路径被用来发送Cookie,只有当访问的路径也是/myApp时,浏览器才会将Cookie发送回服务器。

另外,我将“myApp”修改为“webroot”后依旧可以正常执行

还有,对于书上没有提到的第九章其他内容也做一些了解

第十章:安全性

Web应用程序的一些内部内容是受限的,只有提供正确的用户名和密码才能访问。servlet容器通过验证器阈来支持安全限制。容器启动时,验证器阈被加入到Context容器的管道中。

在调用wrapper阈(Context的管道的基础阈)之前,会先调用验证器阈,只有输入正确才会继续调用后面的阈。

领域(Realm接口)

  1. 领域对象:用来对用户进行身份验证的组件
  2. 领域对象会与一个Context容器相关联,Context容器关联领域对象的方法是setRealm()
  3. 领域对象通过访问(默认)tomcat-user.xml文件来验证用户信息
  4. 领域对象是Realm接口的实例
  5. 领域对象对用户信息进行验证的方法为authenticate()
  6. 领域对象关联Context容器的方法是setContainer()
  7. 领域对象的基本实现类是RealmBase类(抽象类),默认使用的是MemoryRealm类的实例作为验证用的领域对象

GenericPrincipal类(主体)

  1. 主体对象是Principal接口的实例
  2. 在Catalina中的实现是GenericPrincipal类
  3. GenericPrincipal实例必须和一个领域对象相关联
  4. GenericPrincipal实例包含一个用户名和密码对,以及一个角色列表
  5. 可以通过hasRole()方法来检测该主体对象是否拥有该指定角色

LoginConfig类(登录配置)

  1. 包含一个领域对象的名字

  2. 包含一个所使用的身份验证方法:BASIC、DIGEST、FORM、CLIENT-CERT取其一。

  3. Tomcat启动时会读取web.xml文件,如果文件中包含login-config元素,则Tomcat会创建一个LoginConfig对象并设置相应的属性

  4. 验证器阈会调用LoginConfig实例的getRealmName()方法获取领域对象名并发送到浏览器。

Authenticator接口

验证器就是实现了Authenticator接口的实例:

  1. 该接口没有声明方法,只是起到了标记的作用
  2. 其他部件可以使用instanceof关键字来检查某个组件是不是一个验证器。
  3. Authenticator接口的一个基本实现:AuthenticatorBase类(它也是一个阈)

Authenticator包下的各个功能的UML类图:
在这里插入图片描述
我们发现AuthenticatorBase下有许多子类,
4. 验证器的主要工作就是对用户身份进行验证
5. 前面说过AuthenticatorBase类也是一个阈,当使用invoke()方法时会调用authenticate()方法时依赖于子类

安装验证器阈

  1. 一个Context实例只有一个LoginConfig实例
  2. AuthenticatorBase的哪个子类作为Context实例中的验证器阈依赖于LoginConfig的auth-method值

应用程序

SimpleContextConfig类(LifecycleListener)

public class SimpleContextConfig implements LifecycleListener {
	private Context context;
	

//这里的方法是在Lifecycle的start()方法被调用后使用的
  public void lifecycleEvent(LifecycleEvent event) {
    if (Lifecycle.START_EVENT.equals(event.getType())) {
      context = (Context) event.getLifecycle();
      authenticatorConfig();
      context.setConfigured(true);
    }
  }

  private synchronized void authenticatorConfig() {
  	......
  }
}

该类其实就是一个实现了LifecycleListener接口的类,其实现了LifecycleListener接口的方法lifecycleEvent(),在此方法的START_EVENT中调用了authenticatorConfig()方法,authenticatorConfig()方法主要做了四个工作:

  1. 检查关联的Context容器是否有安全限制,若有则继续
  2. 检查该Context实例是否有LoginConfig对象,若没有就创建个新的
  3. 检查当前StandardContext对象的pipeline中的阈中(基础阈和其他阈)是否有验证器,若有则直接返回(因为该方法的目的就是在pipeline中安装验证器阈)
  4. 发现pipeline中并未安装验证器阈,则在Context实例中检查是否有领域(realm)对象;若没有则返回(安装不了验证器阈了)
  5. 若找到了领域(realm)对象, 则开始动态的载入BasicAuthenticator

SimpleRealm类

该类的作用是说明领域对象是如何工作的,包含两个硬编码的用户名和密码对

public class SimpleRealm implements Realm {
	//构造函数
	public SimpleRealm() {createUserDatabase();}
  	private void createUserDatabase() {
	    User user1 = new User("ken", "blackcomb");
	    user1.addRole("manager");
	    user1.addRole("programmer");
	    User user2 = new User("cindy", "bamboo");
	    user2.addRole("programmer");
	
	    users.add(user1);
	    users.add(user2);
	}	
	......
	//此处的Principal是代表该用户的对象
	public Principal authenticate(String username, String credentials) {
	    System.out.println("SimpleRealm.authenticate()");
	    if (username==null || credentials==null)
	      return null;
	    User user = getUser(username, credentials);
	    if (user==null)
	      return null;
	    return new GenericPrincipal(this, user.username, user.password, user.getRoles());
  	}
  	
    //根据给出的密码对来得到对应的user对象
  	private User getUser(String username, String password) {
	    Iterator iterator = users.iterator();
	    while (iterator.hasNext()) {
	      User user = (User) iterator.next();
	      if (user.username.equals(username) && user.password.equals(password))
	        return user;
	    }
	    return null;
	}
	
	class User {

	    public User(String username, String password) {
	      this.username = username;
	      this.password = password;
	    }
	
	    public String username;
	    public ArrayList roles = new ArrayList();
	    public String password;
	
	    public void addRole(String role) {
	      roles.add(role);
	    }
	    public ArrayList getRoles() {
	      return roles;
	    }
  }
}

authenticate()方法是由验证器调用的。

SimpleUserDatabaseRealm

该类还是一个领域对象,但是稍微复杂一些。其并不将用户列表存储到对象自身中,而是读取conf文件下的tomcat-users.xml文件并将其内容存入内存。

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <role rolename="manager"/>
  <role rolename="admin"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="role1" password="tomcat" roles="role1"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="admin" password="password" roles="admin,manager"/>
</tomcat-users>

该类的authenticate()方法没有看懂,但是其通过createDatabase()方法来向xml文件传递路径,解析xml文件的内容。

Bootstrap1

只展示与之前章节不同的代码:

public final class Bootstrap1 {
  public static void main(String[] args) {
  ......
  	// add constraint
    SecurityCollection securityCollection = new SecurityCollection();
    securityCollection.addPattern("/");
    securityCollection.addMethod("GET");

    SecurityConstraint constraint = new SecurityConstraint();
    constraint.addCollection(securityCollection);
    constraint.addAuthRole("manager");
    LoginConfig loginConfig = new LoginConfig();
    loginConfig.setRealmName("Simple Realm");
    // add realm
    Realm realm = new SimpleRealm();

    context.setRealm(realm);
    context.addConstraint(constraint);
    context.setLoginConfig(loginConfig);

    connector.setContainer(context);
  }
  ......
}

其中进行了一些设置:

  1. 哪些URL需要遵循安全限制(原先为所有URL,我改为/Modern后只对/Modern有效)
  2. 哪些提交的方法需要遵循安全限制(get)
  3. 哪些角色可以访问受限资源(Manner)
  4. 将Context容器与realm、SecurityConstraint(已经与SecurityCollection联系起来了)、LoginConfig联系起来

Bootstrap2

Bootstrap2与Bootstrap1相似,区别就在于其使用的领域对象(Realm)是SimpleUserDatabaseRealm对象

public final class Bootstrap2 {
  public static void main(String[] args) {
  ......
    loginConfig.setRealmName("Simple User Database Realm");
    // add realm
    Realm realm = new SimpleUserDatabaseRealm();
    ((SimpleUserDatabaseRealm) realm).createDatabase("conf/tomcat-users.xml");
    context.setRealm(realm);
    ......
  }
  ......
}

第十一章:StandardWrapper

我们还记得Context容器包含一个或多个Wrapper实例,其中每一个Wrapper容器表示一个具体的servlet定义;
本章要对Catalina中Wrapper接口的标准实现(StandardWrapper)进行说明

方法调用序列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值