设为首页 收藏本站
查看: 418|回复: 0

[经验分享] 13.tomcat加载器

[复制链接]

尚未签到

发表于 2017-1-25 10:59:59 | 显示全部楼层 |阅读模式
  库(repository)和源(resources)。库表示加载器查找的地方,源表示加载器中的DirContext对象,它的文档基(document base)指向了上下文的文档基。
  一个servlet容器需要一个定制的容器,而不是简单的使用系统的加载器
如果使用系统的加载器来加载servlet和其他需要的类,这样servlet就可以进入Java虚拟机CLASSPATH环境下面的任何类和类库,这会带来安全隐患
在Catalina中,加载器使用org.apache.catalina.Loader接口表示
Tomcat需要一个自己的加载器的另一个原因是它需要支持在WEB-INF/classes或者是WEB-INF/lib目录被改变的时候会重新加载
要支持类的自动加载功能,一个加载器类必须实现org.apache.catalina.loader.Reloader接口
  Java的类加载机制
在每次创建一个Java类的实例时候,必须先将该类加载到内存中
Java虚拟机(JVM)使用类加载器来加载类。Java加载器在Java核心类库和CLASSPATH环境下面的所有类中查找类。
如果需要的类找不到,会抛出java.lang.ClassNotFoundException异常。
JVM使用了三种类加载器:bootstrap类加载器、extension类加载器和systen类加载器。
这三个加载器是父子关系,其中bootstrap类加载器在顶端,而system加载器在结构的最底层。
  其中bootstrap类加载器用于引导JVM,一旦调用java.exe程序,bootstrap类加载器就开始工作。因此,它必须使用本地代码实现,然后加载JVM需要的类到函数中。
另外,它还负责加载所有的Java核心类,例如java.lang和java.io包。另外bootstrap类加载器还会查找核心类库如rt.jar、i18n.jar等,这些类库根据JVM和操作系统来查找。
  extension类加载器负责加载标准扩展目录下面的类。这样就可以使得编写程序变得简单,只需把JAR文件拷贝到扩展目录下面即可,类加载器会自动的在下面查找。
不同的供应商提供的扩展类库是不同的,Sun公司的JVM的标准扩展目录是/jdk/jre/lib/ext。
  system加载器是默认的加载器,它在环境变量CLASSPATH目录下面查找相应的类。
  JVM使用哪个类加载器?答案在于委派模型(delegation model),这是出于安全原因
每次一类需要加载,system类加载器首先调用。但是,它不会马上加载类。相反,它委派该任务给它的父类-extension类加载器。extension类加载器也把任务委派给它的父类bootstrap类加载器
因此,bootstrap类加载器总是首先加载类。如果bootstrap类加载器不能找到所需要的类的extension类加载器会尝试加载类。
如果扩展类加载器也失败,system类加载器将执行任务。如果系统类加载器找不到类,一个java.lang.ClassNotFoundException异常。
为什么需要这样的往返模式?
委派模型对于安全性是非常重要的。如你所知,可以使用安全管理器来限制访问某个目录。
现在,恶意的意图有人能写出一类叫做java.lang.Object,可用于访问任何在硬盘上的目录。
因为JVM的信任java.lang.Object类,它不会关注这方面的活动。因此,如果自定义java.lang.Object被允许加载的安全管理器将很容易瘫痪。
  工作原理。
  当自定义java.lang.Object类在程序中被调用的时候,system类加载器将该请求委派给extension类加载器,然后委派给bootstrap类加载器。
这样bootstrap类加载器先搜索的核心库,找到标准java.lang.Object并实例化它。
这样,自定义java.lang.Object类永远不会被加载
  
java.lang.ClassLoader抽象类来扩展自己的类加载器。
  
-------------------------------------------
  Tomcat的需求自定义自己的类加载器原因包括以下内容
· 要制定类加载器的某些特定规则
 · 缓存以前加载的类
· 事先加载类以预备使用
  Loader接口
一个Tomcat类加载器表示一个Web应用程序加载器,而不是一个类加载器。
一个加载器必须实现org.apache.catalina.Loader接口。
加载器的实现使用定制的类加载器org.apache.catalina.loader.WebappClassLoader。
可以使用Loader接口的getClassLoader方法获取一个网络加载器ClassLoader。
  Loader接口的addReposity方法用于添加一个库,findRepositories方法用于返回一个所有库的队列。
一个Tomcat的加载器通常跟一个上下文相关联,Loader接口的和getContainer及setContainer方法是建立此关联。
一个加载器还可以支持重新加载,如果在上下文中的一个或多个类已被修改。这样,一个servlet程序员可以重新编译servlet或辅助类,新类将被重新加载而不需要不重新启动Tomcat加载。
为了达到重新加载的目的,Loader接口有修改方法。在加载器的实现中,如果在其库中一个或多个类别已被修改,modeify方法必须返回true,因此需要重新加载。
setReloadable和getReloadable,用于确定加载器中是否可以使用重加载。
默认情况下,在标准的上下文实现中(org.apache.catalina.core.StandardContext类)重载机制并未启用。
因此,要使得上下文启动重载机制,需要在server.xml文件添加一些元素如下: <Context path="/myApp" docBase="myApp" debug="0" reloadable="true"/>
另外,一个加载器的实现可以确定是否委派给父加载器类。为了实现这一点,Loader接口提供了getDelegate和setDelegate方法。

public interface Loader {
public ClassLoader getClassLoader();
public Container getContainer();
public void setContainer(Container container);
public DefaultContext getDefaultContext();
public void setDefaultContext(DefaultContext defaultContext);
public boolean getDelegate(); public void setDelegate(boolean delegate);
public String getInfo(); public boolean getReloadable();
public void setReloadable(boolean reloadable);
public void addPropertyChangeListener(PropertyChangeListenerlistener);
public void addRepository(String repository);
public String[] findRepositories();
public boolean modified();
public void removePropertyChangeListener(PropertyChangeListenerlistener);
}
  
Catalina提供了org.apache.catalina.loader.WebappLoader作为Load接口的实现。
WebappLoader对象包含一个org.apache.catalina.loader.WebappClassLoader类的实例,该类扩展了Java.netURLClassLoader类。
Loader接口和它的实现类的结构图
  
DSC0000.png
 
Reloader接口
要支持自动重新加载,一个加载器的实现必须实现org.apache.catalina.loader.Reloader接口

public interface Reloader {
public void addRepository(String repository);
public String[] findRepositories ();
public boolean modified();
}

  Reloader接口里最重要的方法是modified方法,如果在web应用程序中的servlet任何支持类被修改的时候该方法返回true。
addRepository方法用于添加一个库而findRepositories方法用于返回实现了Reloader接口的加载器的所有的库。
  
WebappLoader类
org.apache.catalina.loader.WebappLoader类是Loader接口的实现,它表示一个web应用程序的加载器,负责给web应用程序加载类。
WebappLoader创建一个org.apache.catalina.loader.WebappClassLoader类的实例作为它的类加载器。
像其他的Catalina组件一样,WebappLoader实现了org.apache.catalina.Lifecycle接口,可有由关联容器启动和停止。
WebappLoader类还实现了java.lang.Runnable接口,所以可以通过一个线程来重复的调用modified方法,如果modified方法返回true,WebappLoader实例同志它的关联容器。
  WebappLoader类的start方法被调用的时候,将会完成下面几项重要任务:
创建一个类加载器
设置库
设置类路径
设置访问权限
开启一个新线程用来进行自动重载
  
创建类加载器
WebappLoader使用一个内部类加载器来加载类。可以回头看Loader接口,该接口提供了getClassLoader方法但是并没有setClassLoader方法。
因此,不能通过传递一个WebappLoader来初始化它

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;
}
  设置库
WebappLoader的start方法会调用setRepositories方法来给类加载器添加一个库。
WEB-INF/classes目录传递给加载器addRepository方法,而WEB-INF/lib传递给加载器的setJarPath方法。
这样,类加载器能能从WEB-INF/classes 目录下面和WEB-INF/lib目录下面部署的类库里加载类。
设置类路径
该任务由start方法调用setClassPath方法完成,setClassPath方法会给servlet上下文分配一个String类型属性保存Jasper JSP编译的类路径,
设置访问权限
如果Tomcat使用了安全管理器,setPermissions给类加载器给必要的目录添加访问权限,例如WEB-INF/classes和WEB-INF/lib。如果不使用管理器,该方法马上返回
开启自动重载线程
WebappLoader支持自动重载,如果WEB-INF/classes或者WEB-INF/lib目录被重新编译过,在不重启Tomcat的情况下必须自动重新载入这些类。
为了实现这个目的,WebappLoader有一个单独的线程每个x秒会检查源的时间戳。
x的值由checkInterval变量定义,它的默认值是15,也就是每隔15秒会进行一次检查是否需要自动重载。
该类还提供了两个方法getCheckInterval和setCheckInterval方法来访问或者设置checkInterval的值。
  WebappClassLoader类
WebappClassLoader表示在一个web应用程序中使用的加载器。WebappClassLoader类继承了java.net.URLClassLoader类
缓存
缓存由WebappClassLoader类实例自己管理。另外,java.lang.ClassLoader维护了一个Vector,可以避免前面加载过的类被当做垃圾回收掉。
每一个可以被加载的类(放在 WEB-INF/classes目录下的类文件或者 JAR 文件)都被当做一个源。
一个源被org.apache.catalina.loader.ResourceEntry类表示。一个ResourceEntry实例保存一个byte类型的数组表示该类、最后修改的数据或者副本等等。

public class ResourceEntry {
public long lastModifled = -1;
public byte[] binaryContent = null;
public Class loadedClass = null;
public URL source = null;
public URL CodeBase = null;
public Manifest manifest = null;
public Certificate[] certificates = null;
}
  所有缓存的源被存放在一个叫做resourceEntries的HashMap中,键值为源名,所有找不到的源都被放在一个名为notFoundResources的HashMap中。
  加载类
当加载一个类的时候,WebappClassLoader类遵循以下规则:
· 所有加载过的类都要进行缓存,所以首先需要检查本地缓存。
· 如果无法再本地缓存找到类,使用java.langClassLoader类的findLoaderClass方法在缓存查找类、
· 如果在两个缓存中都无法找到该类,使用系统的类加载器避免从J2EE类中覆盖来的web应用程序。
· 如果使用了安全管理器,检查该类是否允许加载,如果该类不允许加载,则抛出ClassNotFoundException异常。
· 如果要加载的类使用了委派标志或者该类属于trigger包中,使用父加载器来加载类,如果父加载器为null,使用系统加载器加载。
· 从当前的源中加载类
· 如果在当前的源中找不到该类并且没有使用委派标志,使用父类加载器。如果父类加载器为null,使用系统加载器
· 如果该类仍然找不到,抛出ClassNotFoundException异常
  例子

public static void main(String[] args) {

System.setProperty("catalina.base", System.getProperty("user.dir"));
Connector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("test1");
wrapper1.setServletClass("test1Servlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("test2");
wrapper2.setServletClass("test2Servlet");
Context context = new StandardContext();
context.setPath("/myapp");
context.setDocBase("myapp");
context.addChild(wrapper1);
context.addChild(wrapper2);
context.addServletMapping("/test1", "test1");
context.addServletMapping("/test2", "test2");
LifecycleListener listener = new SimpleContextConfig();
((Lifecycle)context).addLifecycleListener(listener);
Loader loader = new WebappLoader();
context.setLoader(loader);
connector.setContainer(context);
try{
connector.initialize();
((Lifecycle)connector).start();
((Lifecycle)context).start();
WebappClassLoader classLoader= (WebappClassLoader)loader.getClassLoader();
String[] repositories = classLoader.findRepositories();
for(int i=0;i<repositories.length;i++){
System.out.println("--------------------");
}
System.in.read();
((Lifecycle)context).stop();
}catch(Exception e){
e.printStackTrace();
}
}


public class SimpleContextConfig implements LifecycleListener {
public void lifecycleEvent(LifecycleEvent event) {
if(Lifecycle.START_EVENT.equals(event.getType())){
System.out.println("---------------------");
}
}
}


public class SimplePipeline implements Pipeline, Lifecycle {
protected Valve basic = null;
protected Container container = null;
protected Valve[] valves = new Valve[0];
public void addValve(Valve valve) {
Valve results[] = new Valve[valves.length + 1];
System.arraycopy(valves, 0, results, 0, valves.length);
results[valves.length] = valve;
valves = results;
}
protected class StandardPipelineValveContext implements ValveContext {
protected int stage = 0;
public String getInfo() {
return null;
}
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
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 valve");
}
}
} // end of inner class

public Valve getBasic() {
return null;
}
public Valve getFirst() {
// TODO Auto-generated method stub
return null;
}
public Valve[] getValves() {
// TODO Auto-generated method stub
return null;
}
public void removeValve(Valve arg0) {
// TODO Auto-generated method stub
}
public void setBasic(Valve arg0) {
// TODO Auto-generated method stub
}
public void addLifecycleListener(LifecycleListener arg0) {
// TODO Auto-generated method stub
}
public LifecycleListener[] findLifecycleListeners() {
// TODO Auto-generated method stub
return null;
}
public void removeLifecycleListener(LifecycleListener arg0) {
// TODO Auto-generated method stub
}
public void start() throws LifecycleException {
// TODO Auto-generated method stub
}
public void stop() throws LifecycleException {
// TODO Auto-generated method stub
}
}

public class SimpleWrapper implements Wrapper, Pipeline, Lifecycle {
private Servlet instance = null;
private String servletClass;
private Loader loader;
private String name;
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
private SimplePipeline pipeline = new SimplePipeline();
protected Container parent = null;
protected boolean started = false;
public SimpleWrapper(){}


}

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-333239-1-1.html 上篇帖子: Tomcat源码系列4--Tomcat中Servlet处理流程 下篇帖子: tomcat jvm 参数说明
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表