How Tomcat works 4-5: Container
- Container概述
(1)主要包含四种:Engine:整个catalina servlet容器, Host:一个虚拟主机包含多个context容器, Context:一个web应用容器,包含一道多个Wrapper容器, Wrapper:独立servlet容器
(2)UML图
(3)添加/移除/查找子容器:
container.addChild(container) -> container.removeChild(container)->container.findChild(name)->Container[] container.findChildren
(4)包含组件:Loader/Logger/Manager/Realm
- Pipeline任务:
Tomcat启动时通过读取server.xml的配置决定使用哪个容器来执行,其中原理运用pipeline技术
- pipeline任务重要的四个接口:pipeline/valve/valveContext/Contained
- Pipeline interface code:
public interface Pipeline { Valve getBasic(); void setBasic(Valve basic); void addValves(Valve valve); Valve[] getValves(); void removeValves(); void invoke(Request request, Response response) throws IOException,ServletException; }
- Valve interface code:
public interface Valve {
String getInfo();
void invoke(Request request, Response response, VavleContext context) throws IOException, ServletException;
}
- ValveContext interface code:
public interface ValveContext {
String getInfo();
void invokeNext(Request request, Response response) throws IOException, ServletException;
}
- Wrapper接口:
(1)wrapper的实现类作用:
用于管理独立servlet类的lifecycle,调用servlet的init, destroy, service方法。wrapper处于容器层次底部,不能添加子容器
(2)重要方法
public interface Wrapper {
Servlet allocate() throws ServletException; // 是否实现SingleThreadModel
void load() throws ServletException; // 加载和初始化servlet实例
}
- Wrapper容器应用实例:
业务流程图:
(1)UML结构图
(2)SimpleWrapperLoader code:
public class SimpleWrapperLoader implements Loader{
URLClassLoader loader = null;
Container container = null;
public SimpleWrapperLoader() {
// 实现URL class loader
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_SERVLET_ROOT);
// 从org.apache.catalina.loader.StandardClassLoader/startup.ClassLoaderFactory中获取该code
String repository = (new URL("file", null, classPath.getCanonicalPath()+File.separator)).toString();
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}catch (IOException e) {
System.out.println(e.toString());
}
}
(3)SimplePipeline code:
package com.cisco.tomcat.container;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.Container;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
public class SimplePipeline implements Pipeline {
Valve basic;
Valve[] valves;
Container container;
public SimplePipeline(Container container) {
this.container = container;
}
@Override
public Valve getBasic() {
return basic;
}
@Override
public void setBasic(Valve basic) {
this.basic = basic;
((Contained)basic).setContainer(container);
}
@Override
public void addValves(Valve valve) {
if(valve instanceof Contained) {
((Contained)valve).setContainer(container);
}
synchronized (valves) {
Valve results[] = new Valve[valves.length+1];
System.arraycopy(valves,0, results, 0, valves.length);
results[valves.length] = valve;
valves = results;
}
}
@Override
public Valve[] getValves() {
return valves;
}
@Override
public void removeValves() {
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
// 调用内部类的StandardValveContext的invokeNext方法,内部会调用每个valves的invoke方法
// 在调用的valve中会继续调用valveContext.invokeNext的方法,然后调用自身的动作,实现pipeline的所有动作
(new StandardValveContext()).invokeNext(request, response);
}
protected class StandardValveContext implements ValveContext{
int stage = 0;
@Override
public String getInfo() {
return null;
}
@Override
public void invokeNext(Request request, Response response) throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
if(subscript < valves.length) {
valves[subscript].invoke(request, response, this);
}else if((subscript == valves.length) && basic != null){
basic.invoke(request, response, this);
}else {
throw new ServletException("no valves");
}
}
}
}
(4)SimpleWrapperValve:代表basic valve,所以只需要执行加载执行servlet动作,无需调用valveContext.invokeNext()
Code:
package com.cisco.tomcat.container;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Container;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
public class SimpleWrapperValve implements Valve, Contained {
protected Container container;
@Override
public String getInfo() {
System.out.println("this is SimpleWrapperValve");
return null;
}
@Override
public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException {
SimpleWrapper wrapper = (SimpleWrapper)getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sresp = response.getResponse();
HttpServletRequest hreq = null;
HttpServletResponse hresp = null;
Servlet servlet = null;
if(sreq instanceof HttpServletRequest) {
hreq = (HttpServletRequest)sreq;
}
if(sresp instanceof HttpServletResponse) {
hresp = (HttpServletResponse) sresp;
}
try {
servlet = wrapper.allocate();
if(hreq!=null && hresp!=null) {
servlet.service(hreq, hresp);
}else {
servlet.service(sreq, sresp);
}
}catch(ServletException e) {
}
}
@Override
public void setContainer(Container container) {
this.container = container;
}
@Override
public Container getContainer() {
return container;
}
}
(5)ClientIPValve:
添加的addition valve. 实现打印client ip功能:首先调用context.invokeNext(),然后执行自身动作
Code:
package com.cisco.tomcat.container;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import org.apache.catalina.Container;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
public class ClientIPValve implements Valve, Contained{
private Container container;
@Override
public void setContainer(Container container) {
this.container = container;
}
@Override
public Container getContainer() {
return container;
}
@Override
public String getInfo() {
return null;
}
@Override
public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException {
context.invokeNext(request, response);
System.out.println("ClinetIP Valve invoke method...");
ServletRequest sreq = request.getRequest();
System.out.println("IP:"+sreq.getRemoteAddr());
}
}
(6)HeaderLoggerValve
添加的addition valve.实现打印header功能,首先调用context.invokeNext()。然后执行自身动作
package com.cisco.tomcat.container;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Container;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
public class HeaderLoggerValve implements Valve, Contained{
private Container container;
@Override
public void setContainer(Container container) {
this.container = container;
}
@Override
public Container getContainer() {
return container;
}
@Override
public String getInfo() {
return null;
}
@Override
public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException {
context.invokeNext(request, response);
System.out.println("打印Http Header日志......");
if(request instanceof HttpServletRequest){
HttpServletRequest hreq = (HttpServletRequest) request;
Enumeration<String> headerNames = hreq.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement().toString();
String headerValue = hreq.getHeader(headerName);
System.out.println(headerName + ": " + headerValue);
}
}else {
System.out.println("没有Header");
}
}
}
(7)BootStrap类:
package com.cisco.tomcat.container;
import java.io.IOException;
import org.apache.catalina.Loader;
public class Bootstrap {
public static void main(String[] args) {
Wrapper wrapper = new SimpleWrapper();
Loader loader = new SimpleWrapperLoader();
wrapper.setLoader(loader);
Valve clientIpValve = new ClientIPValve();
Valve headerLoggerValve = new HeaderLoggerValve();
wrapper.addValves(clientIpValve);
wrapper.addValves(headerLoggerValve);
wrapper.setServletClass("PrimitiveServlet");
HttpConnector connector = new HttpConnector();
connector.setContainer(wrapper);
connector.initialize();
connector.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}