配置文件的组件配置是按照层级结构来划分的 Tomcat的配置文件为server.xml
<Server port="8005" shutdown="SHUTDOWN">
分别为配置版本日志的监听器
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
等等
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
• resourceName="UserDatabase"/>
• </Realm>
<Host name="localhost" appBase="webapps"
• unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
• prefix="localhost_access_log" suffix=".txt"
• pattern="%h %l %u %t "%r" %s %b" />
该目录主要用于存放jakarta扩展的接口表述 就是J2EE的文件,jakarta的目录结构如下: catalina 该目录用于存放Tomcat的核心文件,包括容器,组件。 coyote 该目录用户存放协议相关的内容 如HTTP协议 HTTP2协议 AJP等协议处理器。 el 该目录用于存放el表达式解析的相关内容 jasper 该目录用于存放JSP引擎的相关内容 juli 该目录存放JULI日志框架的源码 naming 该目录存放JNDI的内容 tomcat 该目录存放Tomcat工具类 例如jar包扫描组件 DBCP连接池源码 InstanceManager实例化管理器等组件
Server | |Service Service 因为Tomcat需要包含多个连接器 所以诞生了Service | |-----------------|Connector Connector Engine 因为Tomcat需要一个可以接收客户端以及可以处理协议相关的功能 这里引入了Connector连接器 | Host Host 因为Tomcat需要实现HTTP中指定服务主机的功能这里引入了Host组件 | Context Context 因为Tomcat需要使用WEB应用的功能 所以引入了Host组件 | Wrapper Wrapper 因为Tomcat需要表示Servlet功能 所以引入了Wrapper组件
Tomcat需要一个对象来代表整个Servlet容器 这时引入了Server组件。Tomcat需要一个组件来包含或多个连接器 这时引入了Service组件Tomcat需要一个用于接收客户端并处理协议相关的功能 这时引入了Connector连接器Tomcat需要一个用于实现HTTP中指定服务主机的功能 这是引入了Host组件Tomcat需要一个用于表示WEB应用的功能 这时引入了ContextTomcat需要一个用于表示Servlet的功能 这时引入了Wrapper。
1.每一个Servlet都有一个自己的生命周期 如init方法初始化 destory方法销毁 为了满足这些需求,引入了Wrapper组件。 2.一个Web应用将会有很多的Servlet实例 这时候就会有很多的Wrapper组件,根据Servlet规范,需要实现Web应用的Session机制 类加载机制,为了满足这些需求 需要一个管理对象代表一个应用,这时就引入了Context组件。 3.Http头部拥有一个Host字段来选择后端的主机 这时需要一个用于管理上下文组件的需求,这时引入了Host虚拟机主机 4.在拥有多个Host虚拟主机组件后 就需要一个管理他们的组件的功能,所以引入了Engine引擎组件 5.当我们需要对外提供服务时 肯定少不了TCP/IP协议 在第四层传输层可以选择UDP和TCP,为了保证可靠连接,这是实现Tomcat时必须选择TCP 这时需要考虑的只有应用层协议了 可以支持HTTP,HTTPS等协议,就需要一个组件来满足这个需求,这时引入了Connector连接器组件 6.有了连接器和引擎组件之后 需要一个能管理这些组件的功能 这时就引入了Service组件 7.Service组件的配置要求 管理要求 需要引入最上层的Server组件
1.树形结构中 每一个父组件需要管理子组件 就需要一个机制来管理这些组件的生命周期,因此我们引入了Lifecycle接口来完成此操作 2.有些组件包含管理功能 还需要包含容器功能(Container) 由于容器是一个特殊的组件,那么让Container接口继承Lifecyle接口来完成此操作 3.有时候需要对不同的组件进行相应的操作 例如:在初始化,启动,停止,销毁前后执行不同的操作,这时候就引入一个LifecycleListener接口来定义监听器的行为,容器是一个特殊的组件所以使用ContainerListener接口来定义容器的行为2.4 责任链模式 当一个请求需要进行一系列的处理后才能到达最终的处理逻辑,这时就会想到一个完成这种需求的设计模式,即责任链设计模式。 可以定义一个责任链来完成对请求的过滤和处理,这时引入了Pipeline接口,它可以管理Vavle接口和触发责任链,Vavle接口是用来执行过滤流程的。 FilterChain接口可以管理Filter接口和触发责任链,Filter接口是用来执行过滤流程的。 用他们来完成这些需求,前者主要是在Tomcat容器处理中起到责任链的效果,而后者则是用于实际调用Servlet前对这些请求进行过滤。 1.Pipeline接口在容器对象传递的时候起到了作用 2.FilterChain接口仅在调用实际的Servlet时起到了作用
1.在Lifecycle接口中定义的生命周期函数有很多,特别注意的是init组件初始化方法,start组件开始方法,stop组件停止的方法。2.初始化方法调用的时候,初始化所有需要的对象,并且会调用监听器 并吧INIT_EVENT事件传递过去 这里INIT_EVENT事件 也就是代表LifecycleState枚举类的INITIALIZING 初始化状态组件开始方法start方法调用的时候,表示组件已经开始运行,监听器发出BEFORE_START_EVENT事件(初始化状态) 这时组件的生命周期转换为组件开始前状态(STARTING_PREP) 当所有组件依赖的子组件完成启动后,将发出START_EVENT事件 这时候状态转换为STARTING 当方法返回前 将发出AFTER_START_EVENT事件 状态转换为STARTED3.组件停止方法,当方法调用后 内部所有依赖的组件也应该都关闭,将发出BEFORE_STOP_EVENT事件,随后状态转换为STOPPING_PREP,当所有组件都关闭之后 将发出STOP_EVENT事件 随后状态转换为STOPPING4.组件销毁方法 当方法调用后 内部所依赖的组件也应该被销毁
NEW(false, null), //组件新建的状态
INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT), //组件初始化状态
INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT), //组件初始化后的状态
STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT), //组件开始前的状态
STARTING(true, Lifecycle.START_EVENT), //开始中
STARTED(true, Lifecycle.AFTER_START_EVENT), //开始后
STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT), //停止前
STOPPING(false, Lifecycle.STOP_EVENT), //停止中
STOPPED(false, Lifecycle.AFTER_STOP_EVENT), //停止后
DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT), //销毁前
DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT), //销毁中
FAILED(false, null); //销毁后
3.2 1.流程过度状态均在方法内自动完成 不需要调用方法2.组件刚创建对象后处于NEW状态 此时可以调用init方法进行初始化流程 也可以调用start方法直接进入开始流程3.组件在开始后可以调用stop方法进入停止流程4.组件在停止后可以调用destroy方法进入销毁流程5.组件一旦进入销毁状态,将不能够改变状态6.组件可以在新建状态时调用destrory方法进入销毁状态或者调用stop方法进入停止状态7.组件可以在停止后调用start方法重新进入开始流程8.组件可以在失败后调用stop方法进入停止流程中的STOPPING阶段
Lifecycle接口中的方法有很多,通过接口可以添加监听器,查看组件的状态,但是并没有实现如何添加,如何查询,以及如何处罚事件并通知监听功能我们可以创建一个抽象类来实现Lifecycle接口中的那些部分方法 然后只需要子类专注于实现自己的生命周期函数,不需要考虑如何触发事件,通知监听器等。比如:
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycleListeners.add(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycleListeners.toArray(new LifecycleListener[0]);
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycleListeners.remove(listener);
}
@Override
public final synchronized void init() throws LifecycleException {
//当前状态如果不是NEW状态 则跑出无效状态异常
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
//否则设置组件状态未INITIALZHG
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal(); //回调完成初始化
完成初始化之后设置组件状态为INITIALIZED
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
实现开始流程
@Override
public final synchronized void start() throws LifecycleException {
//首先判断组件已经处于启动流程中或者已经启动完成时 打印输出
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
//如果状态为新建状态 执行初始化流程
if (state.equals(LifecycleState.NEW)) {
init();
//如果状态为失败状态 先执行停止流程
} else if (state.equals(LifecycleState.FAILED)) {
stop();
//如果状态不是初始化完成状态并且不是停止完成状态 则抛出异常
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
//将状态修改为STARTING_PREP
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal(); //子嘞完成内部组件开始流程
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
@Override
public final synchronized void stop() throws LifecycleException {
//如果已经处于停止流程中 或者已经停止 那么打印异常并返回
if (LifecycleState.STOPPING_PREP.equals(state) || LifecycleState.STOPPING.equals(state) ||
LifecycleState.STOPPED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStopped", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStopped", toString()));
}
return;
}
//如果当前为NWE状态 直接状态转变为STOPPED 因此此时内部组件都没有启动 所以不需要进入完整的停止周期
if (state.equals(LifecycleState.NEW)) {
state = LifecycleState.STOPPED;
return;
}
//只能从STOPPING和FAILED状态进入停止流程 否则抛出无效状态异常
if (!state.equals(LifecycleState.STARTED) && !state.equals(LifecycleState.FAILED)) {
invalidTransition(Lifecycle.BEFORE_STOP_EVENT);
}
try {
//如果状态为FAILED状态 那么触发BEFORE_STOP_EVENT异常通知监听器
if (state.equals(LifecycleState.FAILED)) {
// Don't transition to STOPPING_PREP as that would briefly mark the
// component as available but do ensure the BEFORE_STOP_EVENT is
// fired
fireLifecycleEvent(BEFORE_STOP_EVENT, null);
} else {
//否则开始转变状态为STOPPING_PREP
setStateInternal(LifecycleState.STOPPING_PREP, null, false);
}
stopInternal();
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
//子类负责转彼岸状态为STOPPING 或者进入FAILED状态 那么抛出无效状态转换异常
if (!state.equals(LifecycleState.STOPPING) && !state.equals(LifecycleState.FAILED)) {
invalidTransition(Lifecycle.AFTER_STOP_EVENT);
}
setStateInternal(LifecycleState.STOPPED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.stopFail", toString());
} finally {
//子类实现了SingleUse 表明对象只使用一次 那么在stop方法返回后 将自动调用destory方法 进入销毁流程
if (this instanceof Lifecycle.SingleUse) {
// Complete stop process first
setStateInternal(LifecycleState.STOPPED, null, false);
destroy();
}
}
}
@Override
public final synchronized void destroy() throws LifecycleException {
//如果状态为FAILED 先执行stop结束流程 因此此时可能有些组件已经开始运行 但是由于某些原因进入FAILED状态 所以有必要进入stop流程
if (LifecycleState.FAILED.equals(state)) {
try {
// Triggers clean-up
stop();
} catch (LifecycleException e) {
// Just log. Still want to destroy.
log.error(sm.getString("lifecycleBase.destroyStopFail", toString()), e);
}
}
//如果处于销毁或者处于销毁流程中 那么打印异常并退出
if (LifecycleState.DESTROYING.equals(state) || LifecycleState.DESTROYED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyDestroyed", toString()), e);
} else if (log.isInfoEnabled() && !(this instanceof Lifecycle.SingleUse)) {
// Rather than have every component that might need to call
// destroy() check for SingleUse, don't log an info message if
// multiple calls are made to destroy()
log.info(sm.getString("lifecycleBase.alreadyDestroyed", toString()));
}
return;
}
//如果当前状态不是STOPPED,FAILED,NEW,INITIALIZED 抛出无效状态转换异常 因为只有这4种状态可以直接进入销毁流程
if (!state.equals(LifecycleState.STOPPED) && !state.equals(LifecycleState.FAILED) &&
!state.equals(LifecycleState.NEW) && !state.equals(LifecycleState.INITIALIZED)) {
invalidTransition(Lifecycle.BEFORE_DESTROY_EVENT);
}
try {
//转换状态为DESTPORYING 子类执行以来组件的销毁流程 然后再将状态转换为DESTOYING
setStateInternal(LifecycleState.DESTROYING, null, false);
destroyInternal();
setStateInternal(LifecycleState.DESTROYED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.destroyFail", toString());
}
}
package com.nanchen.sec;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.util.LifecycleBase;
import javax.servlet.http.HttpServlet;
//模拟tomcat的生命周期
public class LifecycleDemo {
public static void main(String[] args) throws LifecycleException{
Loglistener loglistener = new Loglistener();
//创建a组件和b组件 并将b放入a中
ComponentB componentB = new ComponentB();
ComponentA componentA = new ComponentA(componentB);
componentB.addLifecycleListener(loglistener);
componentA.addLifecycleListener(loglistener);
//初始化a 然后启动a 停止a 最后销毁a
componentA.init();
componentA.start();
componentA.stop();
componentA.destroy();
}
}
class ComponentA extends LifecycleBase {
private ComponentB child;
public ComponentA(ComponentB child){
this.child = child;
}
@Override
protected void initInternal() throws LifecycleException {
System.out.println("initInternal");
System.out.println();
//父组件负责子组件的初始化
child.init();
}
@Override
protected void startInternal() throws LifecycleException {
System.out.println("startInternal");
System.out.println();
setState(LifecycleState.STARTING); //子组件启动前将状态设置为启动中
child.start(); //父组件负责子组件的启动
}
@Override
protected void stopInternal() throws LifecycleException {
System.out.println("stopInternal");
System.out.println();
setState(LifecycleState.STOPPING);
child.stop();
}
@Override
protected void destroyInternal() throws LifecycleException {
System.out.println("destroyInternal");
System.out.println();
child.destroy();
}
}
class ComponentB extends LifecycleBase{
@Override
protected void initInternal() throws LifecycleException {
System.out.println("initInternal");
System.out.println();
}
@Override
protected void startInternal() throws LifecycleException {
System.out.println("startInternal");
setState(LifecycleState.STARTING);
}
@Override
protected void stopInternal() throws LifecycleException {
System.out.println("stopInternal");
System.out.println();
setState(LifecycleState.STOPPING);
}
@Override
protected void destroyInternal() throws LifecycleException {
System.out.println("destroyInternal");
}
}
class Loglistener implements LifecycleListener{
@Override
public void lifecycleEvent(LifecycleEvent event) {
System.out.println("---------loglistener start-------");
// System.out.println("");
System.out.println();
}
}
JMX(java管理扩展)在java中定义了应用程序以及网络管理和监控的体系结构,设计模式,应用程序接口以及服务,通常使用JMX监控系统的运行状态或管理系统的某些方面 如清楚缓存 重新加载配置文件等。那么如果希望将Tomcat中的组件放入JMX进行管理,
就需要接入JMX的功能,同理,这些功能的实现对于所有组件都是一样的,所以也希望将这些方法进行封装实现。Tomcat实现了一个LifecycleMBeanBase,提供接入JMX的功能。通常都继承这个类 然后提供自己的实现方法,这样就可以通过JMX接口来管理和查询组件了。
@Override
protected void initInternal() throws LifecycleException {
// If oname is not null then registration has already happened via
// preRegister().
if (oname == null) {
mserver = Registry.getRegistry(null, null).getMBeanServer();
oname = register(this, getObjectNameKeyProperties());
}
}
@Override
protected void destroyInternal() throws LifecycleException {
unregister(oname);
}
@Override
public final String getDomain() {
if (domain == null) {
domain = getDomainInternal();
}
if (domain == null) {
domain = Globals.DEFAULT_MBEAN_DOMAIN;
}
return domain;
}
protected abstract String getDomainInternal();
@Override
public final ObjectName getObjectName() {
return oname;
}
protected abstract String getObjectNameKeyProperties();
protected final ObjectName register(Object obj,
String objectNameKeyProperties) {
// Construct an object name with the right domain
//构建对象名
StringBuilder name = new StringBuilder(getDomain());
name.append(':');
name.append(objectNameKeyProperties);
ObjectName on = null;
try {
//创建ObjectName 并将其注册到MBServer中
on = new ObjectName(name.toString());
Registry.getRegistry(null, null).registerComponent(obj, on, null);
} catch (Exception e) {
log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name), e);
}
return on;
}
protected final void unregister(ObjectName on) {
// If null ObjectName, just return without complaint
if (on == null) {
return;
}
// If the MBeanServer is null, log a warning & return
if (mserver == null) {
log.warn(sm.getString("lifecycleMBeanBase.unregisterNoServer", on));
return;
}
try {
//直接调用MBServer的移除方法
mserver.unregisterMBean(on);
} catch (MBeanRegistrationException e) {
log.warn(sm.getString("lifecycleMBeanBase.unregisterFail", on), e);
} catch (InstanceNotFoundException e) {
log.warn(sm.getString("lifecycleMBeanBase.unregisterFail", on), e);
}
}
@Override
public final void postDeregister() {
// NOOP
}
@Override
public final void postRegister(Boolean registrationDone) {
// NOOP
}
@Override
public final void preDeregister() throws Exception {
// NOOP
}
可以看到LifecycleMBeanBase类实现了在初始化时将组件注册到JMX中进行管理,在销毁的时候将组件从JMX中移除 但是使用LifecycleMBeanBase的子类时必须手动给调用super回调父类的函数。
1.Tomcat通过Lifecycle定义了生命周期函数和时间对象字符串
2.通过LifecycleState枚举类定义了组件的状态
3.通过LifecycleEvent类实现了生命周期中传递的事件对象
4.通过LifecycleListener接口定义了监听生命周期事件的监听器
5.通过LifecleBase类提供了完整的生命周期函数实现
6.子类只需要完成对应的初始化 开始 停止 销毁的标准流程即可,其中的状态改变创建事件通知监听器的操作则已经在LifecycleBase类中完成,且定义了所有的生命周期状态转换的校验。
最后由需要接入JMX来监控管理组件 所以定义了LifecycleMBeanBase类在初始化和销毁时,将组件从JMX中移除。
在Tomcat中,容器可以能够执行从客户端发送过来的请求,然后将这些请求的响应信息返回给客户端。
一个容器可以选择性的支持一组Valve组件组成的处理流水线,通过这个Pipeline组件处理客户端的请求。在Tomcat中,Engine,Host,Context,Wrapper (代表一个Servlet组件)均属于容器。
容器接口继承自Lifecycle,拥有Tomcat的生命周期,并且其中也定义了容器事件对象字符串,并且定义了容器监听器和监听属性变换的监听器。容器包含了一组容器内部的事件机制,所以使用容器时,涉及3个事件机制:生命周期事件,容器事件,监听容器属性变换的监听器,可以通过getParent方法获取父容器,即定义父子容器的关系,同时容器内部还可以定义Log组件用于记录日志。定义Realm用于授权访问,定义Cluster用于组成集群。我们可以在backgroundProcess方法中编写周期性执行的动作,例如监听某个文件变换后的reload容器,特别需要注意的优化点事,可以设置子容器并行启动或停止的线程数来实现并行执行加快性能。
public static final String ADD_CHILD_EVENT = "addChild";
.... 当容器添加Valve时触发事件 当容器移除子容器时触发事件 当容器移除Valve时触发事件 并通知监听器
public String getLogName();
public ObjectName getObjectName();
public void addContainerListener(ContainerListener listener);
public void addPropertyChangeListener(PropertyChangeListener listener);
public void fireContainerEvent(String type, Object data);
public void logAccess(Request request, Response response, long time,
boolean useDefault);
容器可以绑定一组监听器 而监听器用于监听容器的事件,同样也需要一个事件对象,而不是容器接口定义的事件字符串对象 因为我们还需要传递额外的信息 比如事件源 携带的数据。
容器事件对象ContainerEvent继承于EventObject 和 LifcycleEvent事件对象一样 都继承于EventObject EventObject定义了事件源 ContainerEvent在事件源的基础上有添加了携带数据对象data和容器事件字符串对象type
private final Object data;
private final String type;
public ContainerEvent(Container container, String type, Object data) {
super(container);
this.type = type;
this.data = data;
}
容器事件监听器只有一个方法的接口,可以实现这个接口容器中的事件。他和生命周期一样,只不过更换了名字和事件对象类型
public void containerEvent(ContainerEvent event);
和生命周期一样,对于监听器的调用,移除,添加,获取名字等一系列方法都没有实现,如果让每个容器都自己去实现这些方法是不现实的,方法太多并且都是通用的方法,所以需要合理运用模版方法设计模式定义基本实现和算法,而ContainerBase抽象类是容器接口的模版类,如果子类从ContainerBase类继承实现完整容器周期,则需要组合一个Pipeline组件 并且将当前容器需要处理的流程封装进一个Valve组件中 通常设置为null,而必须要使用的组件,如Pipeline组件,都是在变量声明时就直接创建了。在其中获取与容器关联的组件可以共用父类的组件。即如果自己没有定义慢慢获取父容器定义的组件,这就体现出父子容器之间的关系--------子容器可以获取父容器的组件,而父容器不能访问子容器的组件 这和spring和springmvc的父子容器想象。
独立启动器(BootStrap) 内嵌启动器(Tomcat)的原理
PRGDIR=`dirname "$PRG"` #目录项
EXECUTABLE=catalina.sh #需要执行的sh文件
exec "$PRGDIR"/"$EXECUTABLE" version "$@" #$@传递给脚本或函数的所有参数
# Only set CATALINA_HOME if not already se# Only set CATALINA_HOME if not already set
[ -z "$CATALINA_HOME" ] && CATALINA_HOME=`cd "$PRGDIR/.." >/dev/null; pwd` #CATALINA_HOME 目录
# Copy CATALINA_BASE from CATALINA_HOME if not already set
[ -z "$CATALINA_BASE" ] && CATALINA_BASE="$CATALINA_HOME" #将CATALINA_BASE修改为CATALINA_HOME
# Ensure that any user defined CLASSPATH variables are not used on startup,
# but allow them to be specified in setenv.sh, in rare case when it is needed.
CLASSPATH= #类路径信息
JAVA_OPTS="$JAVA_OPTS $JSSE_OPTS" #JVM启动参数
PID file."
rm -f "$CATALINA_PID" >/dev/null 2>&1
if [ $? != 0 ]; then
if [ -w "$CATALINA_PID" ]; then
cat /dev/null > "$CATALINA_PID"
else
echo "Unable to remove or clear stale PID file. Start aborted."
exit 1
fi
fi
fi
else
echo "Unable to read PID file. Start aborted."
exit 1
fi
else
rm -f "$CATALINA_PID" >/dev/null 2>&1
if [ $? != 0 ]; then
if [ ! -w "$CATALINA_PID" ]; then
echo "Unable to remove or write to empty PID file. Start aborted."
exit 1
fi
fi
fi
fi
fi
shift
if [ -z "$CATALINA_OUT_CMD" ] ; then
touch "$CATALINA_OUT"
else
if [ ! -e "$CATALINA_OUT" ]; then
if ! mkfifo "$CATALINA_OUT"; then
echo "cannot create named pipe $CATALINA_OUT. Start aborted."
exit 1
fi
elif [ ! -p "$CATALINA_OUT" ]; then
echo "$CATALINA_OUT exists and is not a named pipe. Start aborted."
exit 1
fi
$CATALINA_OUT_CMD <"$CATALINA_OUT" &
fi
if [ "$1" = "-security" ] ; then
if [ $have_tty -eq 1 ]; then
echo "Using Security Manager"
fi
shift
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Djava.security.manager \
-Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"
else
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"
fi
if [ ! -z "$CATALINA_PID" ]; then
echo $! > "$CATALINA_PID"
fi
echo "Tomcat started."w
Tomcat主类为:Bootstrap 其中定义了main方法函数和启动方法,通过它可以启动Tomcat的核心类Catalina。这部分内容大致了解,不需要研究细节。
Bootstrap启动类核心方法的原理
该方法首先创建了Bootstrap对象,然后调用init方法进行初始化,解析命令行传入的参数,最后分别调用Bootstrap对象对应的方法,由于主要研究启动流程,所以这里只需要了解init load start方法即可
public static void main(String args[]) {
synchronized (daemonLock) { //首先获取到锁
if (daemon == null) {
Bootstrap bootstrap = new Bootstrap(); //第一次启动创建Bootstrap对象
try {
bootstrap.init();//调用init方法进行初始化对象
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
//设置线程上下文类的加载器
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
//转换参数
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
//启动Tomcat
} else if (command.equals("start")) {
//主线程需要设置等待关闭 且先调用load方法加载资源,然后调用start方法启动Tomcat
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
//如果发生异常 打印异常信息并退出
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
该方法首先初始化了Tomcat的类加载器,然后加载核心类Catalina 创建Catalina实例 最后调用setParentClassLoader方法设置父类加载器为sharedLoader类加载器
public void init() throws Exception {
initClassLoaders(); //初始化类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader); //设置线程上下文类加载器
SecurityClassLoad.securityClassLoad(catalinaLoader); //加载Tomcat核心类Catalina
// Load our startup class and call its process() method
if (log.isDebugEnabled()) {
log.debug("Loading startup class");
}
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance(); //通过反射创建Catalina实例
// Set the shared extensions class loader
if (log.isDebugEnabled()) {
log.debug("Setting startup class properties");
}
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
//保存Catalina实例对象
catalinaDaemon = startupInstance;
}
private void load(String[] arguments) throws Exception {
// Call the load() method
//准备参数 然后调用Catalina对象的load方法
String methodName = "load";
Object param[];
Class<?> paramTypes[];
//如果没有传入参数 那么就是null
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
//如果传入了参数 那么构建参数对象
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
//调用Catalina的load方法
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}
public void start() throws Exception {
//如果没有初始化 那么先进行初始化 此时catalinaDaemon 如果没有经过init方法进行实例化 那么继续调用init方法
if (catalinaDaemon == null) {
init();
}
//如果已经实例化 那么调用Catalina的strart方法
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
可以看到bootstrap类只是一个门面类,其中完成了类加载器的初始化 但是对于真实的load和start方法均是Catalina类完成的
public void stop() throws Exception {
//调用Catalina的stop方法
Method method = catalinaDaemon.getClass().getMethod("stop", (Class []) null);
method.invoke(catalinaDaemon, (Object []) null);
}
Catalina类是一个用于包装Tomcat启动和关闭的核心类,其中包含对于Tomcat加载 启动 关闭的核心方法 启动Tomcat时首先调用load方法 然后调用start方法启动Tomcat 这里也按该调用顺序来进行分析。
load方法的实现原理分析流程如下:
1.初始化所需要的系统变量
2.创建server.xml的文件流
3.创建Digester对象
4.调用parse方法将server.xml中定义的层级结构对象全部初始化
5.重定向输入/输出流
6.调用Server对象的init方法进行初始化
此时 通过前面对于配置文件结构的了解可以看到 Server是Tomcat的顶层对象。这时根据声明周期原理分析得出,当调用Server对象的init方法后 将进入整个Server组件的初始化流程。
代码如下(Catalina类中的load方法):
public void load() {
//只加载一次
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
//设置额外的Tomcat需要的环境变量
initNaming();
//创建Digester对象 解析server.xml配置文件,并按照其中的层级结构生成对应的所有对象实例
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
//加载server.xml配置文件的文件流
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
//开始转换server.xml
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
//转换完关闭流对象
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
//此时 server.xml中的所有对象已经创建并且嵌套完毕,这些动作是由Digester库进行实现的 这个库专门用于解析xml并生成相关对象
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
//初始化输入/输出流 将system的输出流进行重定向
initStreams();
// Start the new server
try {
//初始化server对象
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
start方法的实现原理分析流程如下:
1.如果server还未初始化 那么调用load方法初始化server组件
2.初始化后 如果server对象为空 那么为异常状态 此时打印日志并退出
3.调用start方法启动server
4.注册JVM shutdown钩子 当JVM退出时,将毁掉run方法,在run方法中调用stop方法 停止server实例,以免意外退出时清理serve实例
5.如果指定await 那么让当前线程等待Tomcat结束 等待结束后关闭Server实例
public void start() {
//如果server未初始化 那么调用load方法进行初始化
if (getServer() == null) {
load();
}
//如果server为空
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
//启动server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
//注册JVM shutdown钩子
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
//创建钩子对象并将其注册到JVM退出时执行列表中
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
//如果指定await(该变量在使用bootstrap独立启动时 设置为true) 那么让当前线程等待Tomcat结束
if (await) {
await();
stop(); //等待结束后 关闭server实例
}
}
//直接调用server组件的await方法
public void await() {
getServer().await();
}
//当JVM钩子 退出时将回调run方法 注意:不是启动线程对象
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
//调用stop方法停止实例
Catalina.this.stop();
}
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString("catalina.shutdownHookFail"), ex);
} finally {
// If JULI is used, shut JULI down *after* the server shuts down
// so log messages aren't lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
stop方法的实现原理分析流程如下。
1.移除JVM钩子以免重复执行该方法。
2.判断server声明周期状态 如果已经调用stop方法 那么直接返回 否则调用server的stop destroy声明周期方法 停止并销毁Server及其子组件
public void stop() {
try {
//移除钩子
if (useShutdownHook) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
// If JULI is being used, re-enable JULI's shutdown to ensure
// log messages are not lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
true);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
// 关闭server
try {
Server s = getServer();
LifecycleState state = s.getState();
//如果stop方法已经调用 那么直接返回 什么也不做
if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
&& LifecycleState.DESTROYED.compareTo(state) >= 0) {
// Nothing to do. stop() was already called
} else {
//如果stop方法没有被调用 那么调用server的stop destory生命周期方法 停止并销毁server以及子组件
s.stop();
s.destroy();
}
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
}
}
在load方法调用的时候 调用了initStreams方法中创建了SystemLogHandler对象 包装了system.out 以及 system.err。
有时候想捕捉输入/输出流的内容 最好的办法就是装饰者模式 SystemLogHandler便是这么设计的,通过startCapture方法开启捕捉
stopCapture方法关闭捕捉,findStream方法获取捕捉流对象是什么(了解即可)
protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
System.setOut(new SystemLogHandler(System.out));
System.setErr(new SystemLogHandler(System.err));
}
在web环境中Servlet的规范要求每个web应用 使用自己的类加载器,目的是使得Web应用自己的类的优先级高于Web容器提供的类的优先级。
而对于Tomcat来说也是这样做的,但是Tomcat的类加载器还可以定义得较为复杂 从而实现类的隔离
当java应用启动时 会创建如下3个类加载器
BootstrapClassLoader、ExtensionClassLoader、ApplicationClassLoader
BootstrapClassLoader加载器在虚拟机中使用c++语言编写,在java虚拟机启动时初始化,它主要负责加载路径为:
%JAVA_HOME%/jre/lib 或者通过参数 -Xbooctclasspath 指定的路径以及%JAVA_HOME%/jre/classes中的类
BootstrapClassLoader创建ExtensionClassLoader类加载器 ExtensionClassLoader是用java语言编写的,具体来说就是sun.misc.Launcher$ExtClassLoader,ExtensionClassLoader继承自URLClassLoader,负责加载%JAVA_HOME%/jre/lib/ext路径下的扩展类库,以及java.ext.dirs系统变量指定的路径中的类
BootstrapClassLoader创建完ExtensionClassLoader 之后就会创建ApplicationClassLoader 并且将ApplicationClassLoader的父类加载器指定为ExtensionClassLoader 继承于URLClassLoader
在java类中加载器总是按照负责关系排列,通常,当类加载器被要求加载特定的类或资源时 它首先将请求委托给父类加载器,然后仅在父类加载器找不到请求的类或资源的情况下 才进行自己加载 这种加载机制也称之为双亲委派机制
public static void main(String[] args) {
ClassLoader c = Demo.class.getClassLoader(); //获取demo类的app类加载器
System.out.println(c.getClass().getSimpleName());
ClassLoader c1 = c.getParent();
System.out.println(c1.getClass().getSimpleName()); //获取c类加载器的父类加载器
ClassLoader c2 = c1.getParent();
System.out.println(c2); //获取c1类加载器的父类加载器
}
输出结果:
AppClassLoader
ExtClassLoader
null
可以看到以上demo类是由AppClassLoader类加载器进行加载的 而AppClassLoader的父类加载器是ExtClassLoader加载器 但是可以发现当我们去获取ExtClassLoader类加载器的父类加载器的时候 获取到的是null 这是因为BootstrapClassLoader类加载器是由c进行编写的 所以他不是一个实体
双亲委派就是某个类加载器在接到加载类的请求时 会先将加载类的人物委托给父类加载器 如果父类加载器无法完成加载任务时,才会自己加载。ExtClassLoader的继承体系如图,可以看到ExtClassLoader继承自URLClassLoader 而URLClassLoader继承自SecureClassLoader 跟类是ClassLoader
AppClassLoader的继承体系如下:AppClassLoader也继承自URLClassLoader 而URLClassLoader继承自SecureClassLoader 跟类是ClassLoader
通常使用loadclass方法加载需要的类 而该方法也实现了前面说到的双亲委派机制
//name为类的全限名,resolve指定是否加载类后完成解析
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//获取锁 避免多线程对同一个类进行加载
synchronized (getClassLoadingLock(name)) {
//使用findLoadedClass方法查看当前类是否被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果指定了父类加载器 那么尝试从父类加载器进行加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//否则尝试通过Bootstrap类加载器进行加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果类仍未找到 那么调用子类充血的findClass方法查找类
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
//如果指定类解析类 那么进行解析
if (resolve) {
resolveClass(c);
}
return c;
}
}
loadclass方法实现了查询已加载类的功能 同时如果指定了父类加载器 则会通过双亲委派机制来完成加载;如果没有指定父类加载器 那么在加载之前还是会通过Bootstrap加载器尝试加载 如果还是无法加载 那么调用findclass方法尝试自己加载 下面看findClass的默认代码。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
如果希望保持双亲委派机制 那么就应该实现该方法 完成自己的加载逻辑,当然也可以直接重写loadclass方法实现业务逻辑 这时将会打破双亲委派机制
Urlclassloader类加载器没有重写loadclass方法 只是扩展了findclass方法实现了自己的加载逻辑。
而对于SecureClassloader 它里面只定义了加载安全校验的逻辑,主要与java安全管理器交互。
public URLClassLoader(URL[] urls, ClassLoader parent) {
//调用父类的构造器设置父类加载器
super(parent);
//创建查找类的路径
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
ucp = new URLClassPath(urls, acc);
}
//实现自己的查找逻辑
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
//构建类查找路径 并通过URLCLassPath查找
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
//如果找到类 则调用defineClass将类加载到JVM中
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
} catch (ClassFormatError e2) {
if (res.getDataError() != null) {
e2.addSuppressed(res.getDataError());
}
throw e2;
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
可以看到URLCLassLoader就是定义了可以通过指定的URL链接寻找类的逻辑,通过扩展ClassLoader的findClass方法完成了类加载方法 最终调用ClassLoader的defineClass方法完成了类加载。
static class AppClassLoader extends URLClassLoader{
//直接传递urls信息
AppClassLoader(URL[] urls,ClassLoader parent){
super(urls,parent,factory)
}
}
AppClassLoader的实现更为简单 直接给URLClassLoader传递url信息 最终也是通过URLClassLoader重写的findclass方法在传递进去的url信息中查找类并定义类的信息。
如何打破双亲委派机制?双亲蚊拍机制的出现是为了防止用户定义了一个和Bootstrap类加载器一模一样的类名,然后在其中写入危害JVM的代码 如果没有双亲委派机制 就可以直接加载这个危险的类从而造成破坏 但是如果有双亲委派机制 由于这个类必须由BootStrap类加载器来加载。当传递父类加载器时就会返回正确的类实例,从而不会生成JVM的危害类
为什么要打破这种机制?
这是因为Java提供了服务提供者接口(service provider interface SPI) 它允许第三方为这些接口提供实现。
常见的SPI有JDBC JCE JNDI JAXP 和 JBI等 这些SPI由Java核心库来提供,如JAXP的spi接口定义在javax.xml.parsers包中 这些SPI的实现代码很可能作为Java应用锁机来的jar包被纳入进来,可以通过类路径CLASSPATH找到。
例如实现了JAXP SPI中的Apache Xerces所包含的jar包。SPI接口中的代码经常需要加载具体的实现类,如JAXP中的javax.xml.parsers.DocumentBuilderFactory类中的newInstance() 方法用来生成一个新的DocumentBuilderFactory的实例。
这个类继承自javax.xml.parsers.DocumentBuilderFactory 如果在Apache Xerces中 实现的类是org.apache.xerces.jaxp.DocumentBuilderFactoryImpl,而问题就在于SPI的接口是java核心库的一部分 是由引导类加载器(Bootstrap)来加载的 SPI提供商实现的java类是由系统类加载器进行加载的 如果按照双亲委派机制的实现,那么引导类加载器无法找到spi的实现类的 因为他只加载java的核心库,它也不能作为系统类加载器的代理。
如何打破双亲委派机制?
从源码的分析中也可以看到 可以通过继承ClassLoader类 然后将loadClass重写,就完全打破了双亲委派机制,因为方法都重写了,自然不存在任何双亲委派的逻辑 但是为了应用程序的安全,一般来说不推荐重写loadclass 而是通过继承classloader重写findclass方法 或者可以直接用urlclassloader传递加载路径来进行完成加载
Classloader的源码中有两个核心方法:loadClass和findClass,LoadClass方法实现了双亲委派机制,另外也可以看到在不指定父类加载器parent时 程序也会先到BootStrap中加载 如果没有加载成功 那么会调用classloader的findclass加载类,且findClass方法默认抛出ClassNotFoundException异常,同时在URLClassLoader类中对findClass方法进行了实现,可以通过指定URL(统一资源定位链接) 来进行加载类文件。
package com.nanchen.sec; //(注意编译的时候不需要吧package包名写上)
public class Common {
public void test() throws ClassNotFoundException{
System.out.println("need A class start contextClassLoader load");
ClassLoader contextClassloader = Thread.currentThread().getContextClassLoader();
contextClassloader.loadClass("A");
System.out.println("load class");
}
}
class A{
public void test(B b){
System.out.println(b);
}
}
class B{
}
package com.nanchen.sec;
import javax.servlet.ServletOutputStream;
import java.io.File;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader{
//定义类文件路径
public File classFileDirectory;
public MyClassLoader(File classFileDirectory){
if (classFileDirectory == null){
throw new RuntimeException("目录为null");
}
if (!classFileDirectory.isDirectory()){
throw new RuntimeException("必须是目录");
}
this.classFileDirectory = classFileDirectory;
}
//重写LoadClass方法
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = null;
//这段代码很重要 如果没有这段代码 将会导致加载失败,原因是找不到java/lang/Object类,因为此时重写loadclass相当于打破了
//双亲委派机制,在java中不指定显示继承的类都会默认继承Object类 由于Object类是Bootstrap类加载器进行加载的,所以无法获取它。
//此时必须调用父类的classloader的代码让其加载Object类
try{
//保证java/lang/object被加载
clazz = super.loadClass(name,false);
if (clazz !=null){
return clazz;
}
}catch (Exception e){
e.printStackTrace();
}
try{
//获取类文件的File对象 然后使用Fileinputstream加载后读入byte数组中,最后通过defineclass方法加载类
File classfile = findClassFile(name);
FileInputStream fileInputStream = new FileInputStream(classfile);
byte[] bytes = new byte[(int)classfile.length()];
fileInputStream.read(bytes);
defineClass(name,bytes,0,bytes.length);
}catch (Exception e){
throw new ClassNotFoundException();
}
return clazz;
}
public File findClassFile(String name){
//获取文件夹下所有文件 遍历找到对应名字的name的类文件 然后返回file对象
File[] files = classFileDirectory.listFiles();
for (int i = 0; i <files.length ; i++) {
File cur = files[i];
String fileName = cur.getName();
//只加载类文件
if (fileName.endsWith(".class") && fileName.substring(0,fileName.lastIndexOf(".")).equals(name)){
return cur;
}
}
return null;
}
public static void main(String[] args) throws Exception{
ClassLoader classLoader = new MyClassLoader(new File("./"));
Class<?> a = classLoader.loadClass("A.class");
System.out.println(a);
}
}
使用findClass进行自定义加载类
package com.nanchen.sec;
import java.io.File;
import java.io.FileInputStream;
public class MyClassLoader2 extends ClassLoader{
public File classFileDirectory;
public MyClassLoader2(File classFileDirectory){
if (classFileDirectory == null){
throw new RuntimeException("目录为null");
}
if (!classFileDirectory.isDirectory()){
throw new RuntimeException("必须是目录");
}
this.classFileDirectory = classFileDirectory;
}
//重写Classloader的findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//此时不需要手动调用父类的loadClass方法 因此双亲委派没有被打破
Class<?> clazz = null;
try{
File classfile = findClassFile(name);
FileInputStream fileInputStream = new FileInputStream(classfile);
byte[] bytes = new byte[(int)classfile.length()];
fileInputStream.read(bytes);
clazz = defineClass(name,bytes,0,bytes.length);
}catch (Exception e){
e.printStackTrace();
}
return clazz;
}
public File findClassFile(String name){
//获取文件夹下所有文件 遍历找到对应名字的name的类文件 然后返回file对象
File[] files = classFileDirectory.listFiles();
for (int i = 0; i <files.length ; i++) {
File cur = files[i];
String fileName = cur.getName();
//只加载类文件
if (fileName.endsWith(".class") && fileName.substring(0,fileName.lastIndexOf(".")).equals(name)){
return cur;
}
}
return null;
}
public static void main(String[] args) throws Exception{
ClassLoader classLoader = new MyClassLoader(new File("/Users/relay/Documents/apache-tomcat-8.5.83-src/test/com/nanchen/sec"));
Class<?> a = classLoader.loadClass("A");
System.out.println(a);
}
}
了解了如何自定义类加载之后 来看看以下代码发生的情况
package com.nanchen.sec;
import java.io.File;
import java.lang.reflect.Method;
public class Demo1 {
public static void main(String[] args) throws Exception{
ClassLoader classLoader1 = new MyClassLoader(new File("/Users/relay/Documents/apache-tomcat-8.5.83-src/test/com/nanchen/sec"));
ClassLoader classLoader2 = new MyClassLoader(new File("/Users/relay/Documents/apache-tomcat-8.5.83-src/test/com/nanchen/sec"));
//classLoader1类加载器加载A类
Class<?> a1Clazz = classLoader1.loadClass("A");
//classLoader1类加载器加载B类
Class<?> b1Clazz = classLoader1.loadClass("B");
//classLoader2类加载器加载B类
Class<?> b2Clazz = classLoader2.loadClass("B");
System.out.println("a1Clazz类加载器:" + a1Clazz.getClassLoader());
System.out.println("b1Class类加载器:" + b1Clazz.getClassLoader());
//创建A类和B类的对象
Object aobj1 = a1Clazz.newInstance();
Object bobj2 = b2Clazz.newInstance();
//获取A类的test方法
Method test = a1Clazz.getDeclaredMethod("test",b1Clazz);
//调用aobj1对象的test方法 传入的参数是类加载器classLoaderx2创建的对象bobj2
test.invoke(aobj1,bobj2);
}
}
运行结果如下:
a1Clazz类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
b1Class类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.IllegalAccessException: Class com.nanchen.sec.Demo1 can not access a member of class A with modifiers ""
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.Class.newInstance(Class.java:436)
at com.nanchen.sec.Demo1.main(Demo1.java:24)
我们创建的两个类加载器对象,即classloader1和classLoader2 然后分别用它们加载了a和b类 通过加载class对象创建了aobj1和aobj2对象,此时通过反射调用aobj1方法 传入的对象是classloader2的bobj2对象 ,而由于bobj2不是由classloader1加载的 即和aobj1对象不是同一个类加载器。我们通过定义的类加载器进行了类的隔离 因为不同类加载器之间的类是相互隔离的。
package com.nanchen.sec;
import java.io.*;
import java.lang.reflect.Method;
public class MyclassLoader3 extends ClassLoader {
public File classFileDirectory;
//标识当前类加载器是不是父类加载器
public boolean isParent;
public MyclassLoader3(File classFileDirectory, ClassLoader parent) throws Exception {
//调用ClassLoader的构造器将parent设置为当前类加载器的父类加载器
super(parent);
//如果parent为空 那么当前类加载器就是父类加载器
isParent = parent == null;
if (classFileDirectory == null) {
throw new RuntimeException("目录为null");
}
if (!classFileDirectory.isDirectory()) {
throw new RuntimeException("必须是目录");
}
//重写findClass方法
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//此时不需要手动调用父类的loadClass方法 因此双亲委派没有被打破
Class<?> clazz = null;
try {
File classfile = findClassFile(name);
FileInputStream fileInputStream = new FileInputStream(classfile);
byte[] bytes = new byte[(int) classfile.length()];
fileInputStream.read(bytes);
clazz = defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return clazz;
}
public File findClassFile(String name){
//获取文件夹下所有文件 遍历找到对应名字的name的类文件 然后返回file对象
File[] files = classFileDirectory.listFiles();
for (int i = 0; i <files.length ; i++) {
File cur = files[i];
String fileName = cur.getName();
//只加载类文件
if (fileName.endsWith(".class") && fileName.substring(0,fileName.lastIndexOf(".")).equals(name)){
return cur;
}
}
return null;
}
public static void main(String[] args) throws Exception{
ClassLoader parent = new MyclassLoader3(new File("/Users/relay/Documents/apache-tomcat-8.5.83-src/test/com/nanchen/sec"),null);
ClassLoader classLoader1 = new MyclassLoader3(new File("/Users/relay/Documents/apache-tomcat-8.5.83-src/test/com/nanchen/sec"),parent);
ClassLoader classLoader2 = new MyclassLoader3(new File("/Users/relay/Documents/apache-tomcat-8.5.83-src/test/com/nanchen/sec"),parent);
System.out.println("parent类加载器:" + parent);
System.out.println("classloader1类加载器:" + classLoader1);
System.out.println("classloader2类加载器:" + classLoader2);
//classLoader1加载Common类 并调用test方法
Class<?> common = classLoader1.loadClass("Common");
System.out.println("common类加载器:" + common.getClassLoader());
Object commonObj = common.newInstance();
Method test = common.getDeclaredMethod("test");
test.invoke(commonObj);
}
}
Common类的test方法需要加载类A 由于Common类中只能通过线程上下文类加载器加载,而线程上下文加载器默认是App类加载器,App类加载器自然不知道A类在哪里加载。
//将ClassLoader1或者classLoader2设置为线程的上下文类加载器
Thread.currentThread().setContextClassLoader(classLoader1);
此时就能够完美解决父类加载器需要通过子类加载器加载类的需求了 当然 这样也就打破了双亲委派机制
Tomcat是Servlet容器,Servlet规范中定义每个web应用程序都应该使用自己的Classloader类加载器,那么Tomcat也定义了一些加载器,以允许容器中的不同部分以及在容器上运行的web应用程序可以拥有类相互隔离和公用的功能,在Tomcat启动时 将会创建所示的类加载器层级结构
Bootstrap
|
System
|
Common
|
Webapp1 Webapp2 ...
其中Bootstrap 包括了之前介绍的顶层c++实现的类加载和ext扩展类加载器和system类加载器(App类加载器) 已经提及了 接下来分别讲解System Common和Webapp类加载器锁加载类的路径
该类加载器通常从CLASSPATH环境变量的路径中加载类和资源,所有这些类对Tomcat内部类和Web应用程序都是可见并且共享的,但是,标准的Tomcat启动脚本 ($CATALINA_HOME/bin/catalina.sh) 或者 %CATALINA_HOME%.bat 忽略了CLASSPATH环境变量中的设置 而是指定System类加载器从以下路径加载类和资源。
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"
该类加载器用于加载包含Tomcat内部类和所有Web应用程序都可见的附加类,这也就是为什么他被称为公共类加载器的原因。通常情况下,Web应用程序独立使用的类不应该通过Common类加载器加载,Common类加载类的位置由$CATALINA_BASE/conf/catalina.properites中的common.loader属性置顶 默认按照以下描述的位置顺序进行搜索
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
1.catalina.base/lib 路径下非jar包形式的类
2.catalina.base/lib/*.jar 路径下所有的jar包
3.catalina.home/lib 路径下非jar包形式的类
4.catalina.home/lib/* 路径下所有的jar包
Webapp类加载器
该类加载球加载部署在单个Tomcat实例中的每个web应用程序所以来的类库,即web应用程序的 WEB-INF/classes目录中非jar包形式的类和资源。以及/WEB-INF/lib目录下所有的jar包文件类和资源,这些类只有这个web应用程序可见 其他web应用程序均不可见web应用程序类加载器不同于默认的java双亲委派模型,而是根据Servlet规范优先加载从web应用程序中的类,在当前webapp类加载器无法加载时才会向上委托(当然对于JRE中 bootstrap加载的类必须优先加载) 当委托到Common类加载器时开始遵循双亲委派机制,因为web应用程序的角度来看,类加载的顺序为:1.jvm的引导类2.web应用程序的/WEB-INF/classes3.WEB应用程序的/WEB-INF/lib/*.jar4.系统类加载器5.Common类加载器默认情况下catalina.properties配置文件中并未定义Server和Shared类加载器 而是使用前面描述的简化层次结构,但可以通过置顶conf/catalina.properties中的server.loader属性(用于定义Server类加载器的路径) 和 shared.loader属性(用于定义Shared共享类加载器的路径) 当使用这种层级结构时 Server类加载器加载的类只有Tomcat内部可见 并且Web应用程序完全不可见。Shared加载器加载的类所有web应用程序可见 并且可以在所有web应用程序被用来共享类。任何shared加载器加载的类做了更新 都是需要热部署 需要重启整个Tomcatx