tomcat部署应用的三种方式
三种方法被写死在tomcat的HostConfig.java中:org/apache/catalina/startup/HostConfig.java
- 使用xml描述部署:在tomcat的server.xml文件中添加<context path=“name” docBase=“应用文件的路径”></context>节点
- 使用war包部署:将一个war包放到tomcat中
- 使用文件部署:将要部署的项目的文件夹告诉tomcat,让tomcat自己去找
/**
* Deploy applications for any directories or WAR files that are found
* in our "application root" directory.
*/
protected void deployApps() {
// Migrate legacy Java EE apps from legacyAppBase
migrateLegacyApps();
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
tomcat四大容器
Context是一个接口,tomcat提供了一个它的实现:StandardContext,里面有许多的Hashmap<string,String>
Wrapper也是一个接口,Tomcat提供了一个它的实现:StandardWrapper,wrapper里面可能只有一个servlet实例,也有可能有多个,如果只有一个就将其存在 instance里面,如果有多个就放在一个Stack实现的实例池中:instancePool
/**
* The (single) possibly uninitialized instance of this servlet.
*/
protected volatile Servlet instance = null;
/**
* Maximum number of STM instances.
*/
protected int maxInstances = 20;
/**
* Number of instances currently loaded for a STM servlet.
*/
protected int nInstances = 0;
/**
* Stack containing the STM instances.
*/
protected Stack<Servlet> instancePool = null;
/**
Host也是一个接口,tomcat提供了StandardHost实现类,
Engine也是一个接口,tomcat提供了StandardEngine实现类,
Pipeline是tomcat提供的一个组件,PipeLine中存储这许多valve(阀门), Pipeline–Valve是一种责任链模式,它和普通责任链模式有两点区别:
- 每个Pipeline都是有特定的Valve,而且是在管道的最后一个执行,这个Valve叫BaseValve,并且BaseValve是不可删除的;
- 在上层容器的管道的BaseValve中会调用下层容器的管道。
- "容器的 过滤器”
// 在standardWrapper构造器中就将最后一个wrapper写死:使用StandardWrapperValve
/**
* Create a new StandardWrapper component with the default basic Valve.
*/
public StandardWrapper() {
super();
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
broadcaster = new NotificationBroadcasterSupport();
}
Engine 组件
Engine 是 Servlet 处理器的一个实例,即 Servlet 引擎,定义在 server.xml 中的 Service 标记中。Engine 需要 defaultHost 属性来为其定义一个接收所有发往非明确定义虚拟主机的请求的 host 组件。
<Engine name="Catalina" defaultHost="localhost">
name:Engine 组件的名称;
defaultHost:Tomcat 支持基于 FQDN(Fully Qualified Domain Name 全限定域名)的虚拟主机,这些虚拟主机可以通过在 Engine 容器中定义多个不同的 Host 组件来实现;但如果此引擎的连接器收到一个发往非明确定义虚拟主机的请求时则需要将此请求发往一个默认的虚拟主机进行处理,因此,在 Engine 中定义的多个虚拟主机的主机名称中至少要有一个跟 defaultHost 定义的主机名称同名;
Host 组件
位于 Engine 容器中用于接收请求并进行相应处理的虚拟主机。通过该容器可以运行 Servlet 或者 JSP 来处理请求。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
name:虚拟主机的名称,Tomcat 通过在请求 URL 中的域名与 name 中的值匹配,用于查找能够处理该请求的虚拟主机。如果未找到则交给在 Engine 中 defaultHost 指定的主机处理;
appBase:此 Host 的 webapps目录,即指定存放 web 应用程序的目录的路径,默认指向 ROOT 目录下的 index.jsp;
unpackWARs:在启用此 webapps 时是否对 WAR 格式的归档文件先进行展开;默认为 true;
autoDeploy:在 Tomcat 处于运行状态时放置于 appBase 目录中的应用程序文件是否自动进行 部署,默认为 true;
Context 组件
Context 是 Host 的子标签,代表指定一个 Web 应用,它运行在某个指定的虚拟主机(Host) 上;每个 Web 应用都是一个 WAR 文件,或文件的目录;server.xml 配置中没有配置 Context 属性,默认支持运行多个项目。
<Context path="/test" docBase="D:\bjsxt\itbaizhan.war"/>
path:context path 既浏览器访问项目的访问路径。
docBase:相应的 Web 应用程序的存放位置;也可以使用相对路径,起始路径为此 Context 所属 Host 中 appBase 定义的路径;
doGet方法在哪里调用
总的来说Valve其实就是过滤器,在dorequest前后都执行filter方法。
我们要将用户发过来的请求:request,wrapper,servlet一起创建filterChain,他会将doRequest等方法嵌入到filterChain中
// Create the filter chain for this request
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
**filterchain主要调用dofilter方法, 在filterChain.dofilter中,它主要执行这个方法:internalDoFilter(req,res); 在这个方法中,它首先会执行过滤操作,在做完过滤操作后,会调用传进来的Httpservlet的service()方法,在service方法中调用servlet的doGet()方法,也就是我们实现的doGet方法!!! **
所以我们自己重写的doGet,doPost等,都与tomcat无关,他们的调用都是在servlet的service()方法中,tomcat只不过是使用了servlet封装好的service方法!! 当tomcat调用了Http.service(),由servlet规范自己去判断掉doGet还是doPost.
-
filterchain.dofilter():它将请求和响应传进去,request.getRequest(),和response.getResponse()都会返回一个facade,这是一个门面模式,
filterChain.doFilter(request.getRequest(), response.getResponse()); /** * @return the <code>ServletRequest</code> for which this object * is the facade. This method must be implemented by a subclass. */ public HttpServletRequest getRequest() { if (facade == null) { facade = new RequestFacade(this); } if (applicationRequest == null) { applicationRequest = facade; } return applicationRequest; }
internalDoFilter(req, rep)
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if (dispatcherWrapsSameObject) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (dispatcherWrapsSameObject) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
connector组件与Http11NioProtocol类
tomcat会使用connector组件来处理连接
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443
This connector uses the NIO implementation. The default
SSLImplementation will depend on the presence of the APR/native
library and the useOpenSSL attribute of the
AprLifecycleListener.
Either JSSE or OpenSSL style configuration may be used regardless of
the SSLImplementation selected. JSSE style configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
This connector uses the APR/native implementation which always uses
OpenSSL for TLS.
Either JSSE or OpenSSL style configuration may be used. OpenSSL style
configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an AJP 1.3 Connector on port 8009 -->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
-->
当我们在xml中配置了connector后,会创建一个connector对象,如下为connector的构造器:
public Connector(String protocol) {
configuredProtocol = protocol;
ProtocolHandler p = null;
try {
p = ProtocolHandler.create(protocol);
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
}
if (p != null) {
protocolHandler = p;
protocolHandlerClassName = protocolHandler.getClass().getName();
} else {
protocolHandler = null;
protocolHandlerClassName = protocol;
}
// Default for Connector depends on this system property
setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}
public static ProtocolHandler create(String protocol)
throws ClassNotFoundException, InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
if (protocol == null || "HTTP/1.1".equals(protocol) || org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol)) {
return new org.apache.coyote.http11.Http11NioProtocol();}
else if ("AJP/1.3".equals(protocol)|| org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol)) {
return new org.apache.coyote.ajp.AjpNioProtocol();}
else {
// Instantiate protocol handler
Class<?> clazz = Class.forName(protocol);
return (ProtocolHandler) clazz.getConstructor().newInstance();
}
}
在构造器中,会调用ProtocolHandler.creat(),来解析我们xml的配置,判断HTTP/1.1
还是 AJP/1.3协议,还是自己实现的协议。
- 如果使用HTTP/1.1协议:它会创建Http11NioProtocol类,作为protocolHandler 来处理连接,这里默认使用NIO。
- 如果使用 AJP/1.3协议: 它会创建AjpNioProtocol类,作为protocolHandler来处理连接
- 如果是自定义的协议,首先你要实现一个处理自定协议的类,然后将自定义协议名配置在server.xml 文件中,它首先会获取你xml温江中协议类名,然后用反射加载这个协议对应的处理类,并将这个类返回,作为protocolHandler 来处理连接。
Nioendpiont
查看Http11NioProtocol构造器,发现,里面有一个类:Nioendpiont
public Http11NioProtocol() {
super(new NioEndpoint());
}
public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
public AbstractProtocol(AbstractEndpoint<S,?> endpoint) {
this.endpoint = endpoint;
setConnectionLinger(Constants.DEFAULT_CONNECTION_LINGER);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
int actualBufferPool =
socketProperties.getActualBufferPool(isSSLEnabled() ? getSniParseLimit() * 2 : 0);
if (actualBufferPool != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
actualBufferPool);
}
// Create worker collection
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
startAcceptorThread();
}
}
protected void startAcceptorThread() {
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
createExecutor(); 会创建一个线程池,startAcceptorThread() 会将Acceptor以守护线程在线程池中启动,
acceptor
从上面代码,启动endpoint的时候,会创建一个acceptor的类:
acceptor它是接收请求的接收器,它继承了Runnable接口,说明它可以是一个单独的线程,既然是runnable,我们肯定要关心run()方法,里面是一个循环,一直循环使endpoint从socket中取数据:endpoint.serverSocketAccept()
public void run() {
int errorDelay = 0;
try {
// Loop until we receive a shutdown command
while (!stopCalled) {
// Loop if endpoint is paused
while (endpoint.isPaused() && !stopCalled) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (stopCalled) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
endpoint.countUpOrAwaitConnection();
// Endpoint might have been paused while waiting for latch
// If that is the case, don't accept new connections
if (endpoint.isPaused()) {
continue;
}
U socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = endpoint.serverSocketAccept();
} catch (Exception ioe) {
// We didn't get a socket
endpoint.countDownConnection();
if (endpoint.isRunning()) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
} else {
break;
}
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (!stopCalled && !endpoint.isPaused()) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
} else {
endpoint.destroySocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("endpoint.accept.fail");
// APR specific.
// Could push this down but not sure it is worth the trouble.
if (t instanceof Error) {
Error e = (Error) t;
if (e.getError() == 233) {
// Not an error on HP-UX so log as a warning
// so it can be filtered out on that platform
// See bug 50273
log.warn(msg, t);
} else {
log.error(msg, t);
}
} else {
log.error(msg, t);
}
}
}
} finally {
stopLatch.countDown();
}
state = AcceptorState.ENDED;
}
endpoint.serverSocketAccept() 方法会调用 ServerSocketChannelImpl的accept()方法:
public SocketChannel accept() throws IOException {
synchronized(this.lock) {
if (!this.isOpen()) {
throw new ClosedChannelException();
} else if (!this.isBound()) {
throw new NotYetBoundException();
} else {
SocketChannelImpl var2 = null;
int var3 = 0;
FileDescriptor var4 = new FileDescriptor();
InetSocketAddress[] var5 = new InetSocketAddress[1];
InetSocketAddress var6;
try {
this.begin();
if (!this.isOpen()) {
var6 = null;
return var6;
}
this.thread = NativeThread.current();
do {
var3 = this.accept(this.fd, var4, var5);
} while(var3 == -3 && this.isOpen());
} finally {
this.thread = 0L;
this.end(var3 > 0);
assert IOStatus.check(var3);
}
if (var3 < 1) {
return null;
} else {
IOUtil.configureBlocking(var4, true);
var6 = var5[0];
var2 = new SocketChannelImpl(this.provider(), var4, var6);
SecurityManager var7 = System.getSecurityManager();
if (var7 != null) {
try {
var7.checkAccept(var6.getAddress().getHostAddress(), var6.getPort());
} catch (SecurityException var13) {
var2.close();
throw var13;
}
}
return var2;
}
}
}
}
最后会调用这个方法:this.accept(this.fd, var4, var5);
private int accept(FileDescriptor var1, FileDescriptor var2, InetSocketAddress[] var3) throws IOException {
return this.accept0(var1, var2, var3);
}
private native int accept0(FileDescriptor var1, FileDescriptor var2, InetSocketAddress[] var3) throws IOException;
最终调用我们熟悉的accept0 方法!!!
tomcat实现自定义加载类
tomcat就打破了双亲委派,因为tomact要隔离jar包。tomcat是个web容器,一个web容器可能要部署两个或者多个应用程序,不同的应用程序,因此要保证每一个应用程序的类库都是独立、相互隔离的。所有应用都在一个容器上跑,同一个内存,同一个JVM,但不同的应用要用到的类可能版本不同,使用双亲委派的话就无法保证每一个应用程序的类库都是独立、相互隔离的。
WebappClassLoader
tomcat实现了一个webappclassloader类,每一个web应用都new一个webappclassloader的实例,我们知道在JVM中,以全限定类名+加载器名来标示一个类,所以类加载实力不同,就算加载的是同一个类,JVM也认为他们不相等。WebappClassLoader应该负责的目录应该是:WEB-INF下的lib 和 classes。 当然如果想要他加载其他额外的目录也是可以的。
webappClassLoader的父加载器就是commonClassLoader,它继承于WebappClassLoaderBase,而WebappClassLoaderBase继承URLClassLoader.
commonClassLoader
同时tomcat还实现了commonClassLoader类,用来加载tomcat要依赖的jar包,应用程序的类就使用webAppClassLoader来加载,tomcat的核心依赖类使用commonClassLoader来加载,这样就避免了tomcat依赖的类的冲突。
commonClassLoader的父加载器就是AppClassLoader
URLClassLoader
URLClassLoader 是JDK提供的一个类,它用于指定一个类的路径:可以是目录,可以是文件,可以是远程的jar包。这样tomcat的WebappClassLoader就可以按照指定的目录加载类了。
tomcat 加载类的过程
当tomcat要加载类web应用的某个类时,使用WebappClassloader加载该类,WebappClassloader会调用它父类WebappClassloaderBase的loadClass()方法,
- 首先调用tomcat实现的findLoadedClass0() 方法,查找该类是否被tomcat加载过了,找到了直接返回
- 如果没找到,调用JDK的findLoadedClass0()方法,查找该类是否被JDK加载过了,找到了直接返回
- 如果没有找到,会判断该类是否以java.*开头的类,如果是,则让BootstrapClassLoader加载
- 如果不是,根据delegate(是否委派)属性是true还是false,来加载该类。默认是False,可以修改
- 如果为true,说明该次加载使用委派模式,先由它的父加载器commonClassLoader 来加载,如果commonClassLoader不能加载则使用WebappClassloader来加载, 如果两个都不能加载,就抛出异常
- 如果为False,说明该次加载不使用委派模式,先由自己加载,如果自己加载不了,在使用父加载器加载。两个都加载不了就抛出异常。
resolveClass(clazz)
加载的具体类为:resolveClass(clazz),Tomcat具体加载类的方法也是这个,由JDK提供的。因为Tomcat自定义的类也是继承了JDK的ClassLoader。如果resolveClass() 方法tomcat也自己实现的话那继承ClassLoader就没什么意义了。
resolveClass(clazz)方法调用的是resolveClass0(clazz) 方法,是一个native方法。
tomcat 实现的findLoadedClass0()
tomcat 自己实现了findLoadedClass0(), 它用来寻找该类是否被tomcat 加载过,在这个方法中维护的一个ConcurrentHashMap: Map<String, ResourceEntry>。 String 是类的ID, ResourceEntry是{long lastModified , Class<?> loadedClass}。
这样的一个ConcurrentHashMap 就起到了缓存的作用。
JDK 的findLoadedClass0()
JDK的findLoadedClass0()毫无疑问调用的是本地方法findLoadedClass0().
tomcat热加载
热加载与热部署不同,当一个类发生修改,tomcat会自动重新加载这个类,但是添加一个新类不会,因为加载时按需加载的,只有需要才会加载。在serve.xml 文件的context节点 将reloader设置为true就开启了热加载。
在WebappLoader类中,有backgroundProcess():
/**
* Execute a periodic task, such as reloading, etc. This method will be
* invoked inside the classloading context of this container. Unexpected
* throwables will be caught and logged.
*/
@Override
public void backgroundProcess() {
Context context = getContext();
if (context != null) {
if (context.getReloadable() && modified()) {//判断这个context是否设置了reload=true,并且该类是否发生了改变
ClassLoader originalTccl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(WebappLoader.class.getClassLoader());
context.reload();
} finally {
Thread.currentThread().setContextClassLoader(originalTccl);
}
}
}
}
modified() 判断文件是否改变,它不会一个一个的去比较文件的内容,而是比较上一次修改的时间。