{{ v.name }}
{{ v.cls }}类
{{ v.price }} ¥{{ v.price }}
java本身是一种设计的非常简单,非常精巧的语言,所以java背后的原理也很简单,归结起来就是两点:1、jvm的内存管理理解了这一点,所有和对象相关的问题统统都能解决2、jvmclassloader理解了这一点,所有和java相关的配置问题,包括各种appserver的配置,应用的发布问题统统都能解决appclassloader|-----ejbclassloader|-----webappclassloader如果在appclassloader级别配置,是全局可见的。如果打包在ejb里面,那么就不会影响到webapplication,反之亦然,如果你在web-inf下面放置hibernate,也不会影响到ejb。放在ejbclassloader或者放在webappclassloader级别主要就是在局部范围内有效,不影响到其它的应用。试想,如果在一个weblogic上面配置多个虚拟域,你使用www.bruce.com域名,开发你的网站,我使用www.fankai.com开发我的网站,那么当然不希望我们的hibernate相互干扰,所以就可以放在ejbclassloader级别来配置hibernate。进一步阐述一下ejbclassloader的问题:先再次强调一下,hibernate和ejb,和appserver不存在兼容性问题,他们本来就是不相关的东西,就好像jdbc,相信没有人会认为jdbc和ejb不兼容吧,hibernate也是一样,它只和jdbc驱动,和数据库有兼容性问题,而和ejb,和appserver完全是不搭界的两回事。凡是认为hibernate和ejb不兼容的人,其实是都是因为对ejb学习的不到家,把责任推到hibernate身上了。我前面的帖子提到过classloader的层次,这里不重复了,总之我们先来看看classloader的作用范围:bootstrapclassloader:loadjrelibrt.jar,sunrsasign.jar,charsets.jar,jce.jar,jsse.jar,plugin.jarextclassloader:loadjrelibext目录下的库文件,loadjreclasses目录下的类appclassloader:loadclasspath变量指定路径下的类以上的load路径都是写死在jvm的c++源代码里面的,不能改变,详细请见王森的《java深度历险》在一个特定的appserver上,classloader会继续向下继承,继承的层次会根据不同的appserver有所不同,但是肯定不会变的就是:ejbclassloader:继承自appclassloader,继承层次根据appserver有所不同,一个ejbclassloader它的loadclass的范围仅限于jar或者ear范围之内。webappclassloader:继承自appclassloader,继承层次根据appserver有所不同,一个webappclassloader:它的loadclass的范围在web-inflib下的库文件和web-infclasses目录下的class文件。webappclassloader很好理解,大家毕竟用的很多,appserver上的一个webapplication会创建一个webappclassloader的实例去负责loadclass,所以如果你想让hibernate只在这个webapplication内生效,把它放到web-inflib下去就好了。如果你把hibernate放到了classpath变量指定的路径下,而你在web-inflib也放了一份,那么webappclassloader由于load范围所限,它会首先找到web-inflib下的那份hibernate,按照它的配置来初始化hibernate。如果你把hibernate放到了classpath变量指定的路径下,但你在web-inflib什么都没有放,那么webappclassloader由于load范围所限,它根本什么都找不到,于是它把loadhibernate的责任交给上一级的classloader,这样直到appclassloader,它找到了hibernate,按照它的配置来初始化hibernate。ejbclassloader稍微复杂一点,不那么容易理解。appserver会针对每一个ejb包文件创建一个ejbclassloader的实例,例如:hellorobbin.jarhellobruce.jar当你把这两个jar发布到appserver上以后,会创建两个ejbclassloader的实例,分别去load这两个ejb包,比如说:clejb_robbin是loadhellorobbin.jar的clejb_bruce是loadhellobruce.jar的那么clejb_robbin的load范围就仅仅限于hellorobbin.jar之内,它load不到hellorobbin.jar之外的任何文件,当然它也load不到hellobruce.jar。说到这里,我相信大家应该已经明白为什么ejb规范不允许ejb有io操作了吧?因为ejbclassloader根本找不到jar包之外的文件!!!如果现在你想实现hellorobbin.jar和hellobruce.jar的互相调用,那么该怎么办?他们使用了不同的ejbclassloader,相互之间是找不到对方的。解决办法就是使用ear。现在假设hellorobbin.jar和hellobruce.jar都使用了hibernate,看看该怎么打包和发布:helloejb.ear|------hellorobbin.jar|------hellobruce.jar|------hibernate2.jar|------pojo.jar(定义所有的持久对象和hbm文件的jar包)|------cglib-asm.jar|------commons-beanutils.jar|------commons-collections.jar|------commons-lang.jar|------commons-logging.jar|------dom4j.jar|------odmg.jar|------log4j.jar|------jcs.jar|------hibernate.properties|------log4j.properties|------cache.ccf|------meta-infapplication.xml(j2ee规范的要求,定义ear包里面包括了哪几个ejb)除此之外,按照ejb规范要求,hellorobbin.jar和hellobruce.jar还必须指出调用jar包之外的类库的名称,这需要在jar包的manifest文件中定义:hellorobbin.jar|------meta-infmanifest.mfmanifest.mf中必须包括如下一行:class-path:log4j.jarhibernate2.jarcglib-asm.jarcommons-beanutils.jarcommons-collections.jarcommons-lang.jarcommons-logging.jardom4j.jarjcs.jarodmg.jarjcs.jarpojo.jar这样就ok了,当把helloejb.ear发布到appserver上以后,appserver创建一个ejbclassloader实例loadear包里面的ejb,再根据ejb的jar包里面的manifest.mf指出的class-path去寻找相应的jar包之外的类库。所以一个ear包有点类似一个webapplication,ejbclassloader的load范围也就是ear范围之内,它load不到ear之外的文件。除非把hibernate定义到classpath指定的路径下,在这种情况下,ejbclassloader找不到hibernate,只能交给上一级的classloader,最后由appclassloader找到hibernate,进行初始化。没有写完,继续说...由于ear这样loadclass规则,假设robbin和bruce都在同一个weblogic上运行自己的网站,而我们都不希望自己的程序里面的hibernate配置被对方的搞乱掉,那么我们就可以这样来做:robbin'swebsite:robbin.ear|--------robbin.war(把webapplication打包)|--------robbin.jar(把开发的ejb打包)|--------hibernate2.jar..........................|--------meta-infapplication.xmlbruce'swebsite:bruce.ear|--------bruce.war(把webapplication打包)|--------bruce.jar(把开发的ejb打包)|--------hibernate2.jar..........................|--------meta-infapplication.xml这样在同一个appserver上运行,就可以互相不干扰。
-----------------------------------------------------------------------------
classloader主要对类的请求提供服务,当jvm需要某类时,它根据名称向classloader要求这个类,然后由classloader返回这个类的class对象。1.1几个相关概念classloader负责载入系统的所有resources(class,文件,来自网络的字节流等),通过classloader从而将资源载入jvm每个class都有一个reference,指向自己的classloader。class.getclassloader()array的classloader就是其元素的classloader,若是基本数据类型,则这个array没有classloader1.2主要方法和工作过程java1.1及从前版本中,classloader主要方法:classloadclass(stringname,booleanresolve);classloader.loadclass()是classloader的入口点defineclass方法是classloader的主要诀窍。该方法接受由原始字节组成的数组并把它转换成class对象。原始数组包含如从文件系统或网络装入的数据。findsystemclass方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用defineclass将原始字节转换成class对象,以将该文件转换成类。当运行java应用程序时,这是jvm正常装入类的缺省机制。resolveclass可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的loadclass时,可以调用resolveclass,这取决于loadclass的resolve参数的值findloadedclass充当一个缓存:当请求loadclass装入类时,它调用该方法来查看classloader是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法一般load方法过程如下:调用findloadedclass来查看是否存在已装入的类。如果没有,那么采用某种特殊的神奇方式来获取原始字节。(通过io从文件系统,来自网络的字节流等)如果已有原始字节,调用defineclass将它们转换成class对象。如果没有原始字节,然后调用findsystemclass查看是否从本地文件系统获取类。如果resolve参数是true,那么调用resolveclass解析class对象。如果还没有类,返回classnotfoundexception。否则,将类返回给调用程序。1.3委托模型自从jdk1.2以后,classloader做了改进,使用了委托模型,所有系统中的classloader组成一棵树,classloader在载入类库时先让parent寻找,parent找不到才自己找。jvm在运行时会产生三个classloader,bootstrapclassloader、extensionclassloader和appclassloader。其中,bootstrapclassloader是用c++编写的,在java中看不到它,是null。它用来加载核心类库,就是在lib下的类库,extensionclassloader加载lib/ext下的类库,appclassloader加载classpath里的类库,三者的关系为:appclassloader的parent是extensionclassloader,而extensionclassloader的parent为bootstrapclassloader。加载一个类时,首先bootstrap进行寻找,找不到再由extensionclassloader寻找,最后才是appclassloader。将classloader设计成委托模型的一个重要原因是出于安全考虑,比如在applet中,如果编写了一个java.lang.string类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的string加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载java.lang.string类时,系统最终会由bootstrap进行加载,这个具有破坏性的string永远没有机会加载。委托模型还带来了一些问题,在某些情况下会产生混淆,如下是tomcat的classloader结构图:bootstrap|system|common/catalinashared/webapp1webapp2...由common类装入器装入的类决不能(根据名称)直接访问由web应用程序装入的类。使这些类联系在一起的唯一方法是通过使用这两个类集都可见的接口。在这个例子中,就是包含由javaservlet实现的javax.servlet.servlet。如果在lib或者lib/ext等类库有与应用中同样的类,那么应用中的类将无法被载入。通常在jdk新版本出现有类库移动时会出现问题,例如最初我们使用自己的xml解析器,而在jdk1.4中xml解析器变成标准类库,load的优先级也高于我们自己的xml解析器,我们自己的xml解析器永远无法找到,将可能导致我们的应用无法运行。相同的类,不同的classloader,将导致classcastexception异常1.4线程中的classloader每个运行中的线程都有一个成员contextclassloader,用来在运行时动态地载入其它类,可以使用方法thread.currentthread().setcontextclassloader(...);更改当前线程的contextclassloader,来改变其载入类的行为;也可以通过方法thread.currentthread().getcontextclassloader()来获得当前线程的classloader。实际上,在java应用中所有程序都运行在线程里,如果在程序中没有手工设置过classloader,对于一般的java类如下两种方法获得的classloader通常都是同一个this.getclass.getclassloader();thread.currentthread().getcontextclassloader();方法一得到的classloader是静态的,表明类的载入者是谁;方法二得到的classloader是动态的,谁执行(某个线程),就是那个执行者的classloader。对于单例模式的类,静态类等,载入一次后,这个实例会被很多程序(线程)调用,对于这些类,载入的classloader和执行线程的classloader通常都不同。1.5web应用中的classloader回到上面的例子,在tomcat里,webapp的classloader的工作原理有点不同,它先试图自己载入类(在contextpath/web-inf/...中载入类),如果无法载入,再请求父classloader完成。由此可得:对于webapp线程,它的contextclassloader是webappclassloader对于tomcatserver线程,它的contextclassloader是catalinaclassloader1.6获得classloader的几种方法可以通过如下3种方法得到classloaderthis.getclass.getclassloader();//使用当前类的classloaderthread.currentthread().getcontextclassloader();//使用当前线程的classloaderclassloader.getsystemclassloader();//使用系统classloader,即系统的入口点所使用的classloader。(注意,systemclassloader与根classloader并不一样。jvm下systemclassloader通常为appclassloader)1.7几种扩展应用用户定制自己的classloader可以实现以下的一些应用安全性。类进入jvm之前先经过classloader,所以可以在这边检查是否有正确的数字签名等加密。java字节码很容易被反编译,通过定制classloader使得字节码先加密防止别人下载后反编译,这里的classloader相当于一个动态的解码器归档。可能为了节省网络资源,对自己的代码做一些特殊的归档,然后用定制的classloader来解档自展开程序。把java应用程序编译成单个可执行类文件,这个文件包含压缩的和加密的类文件数据,同时有一个固定的classloader,当程序运行时它在内存中完全自行解开,无需先安装动态生成。可以生成应用其他还未生成类的类,实时创建整个类并可在任何时刻引入jvm2.0资源载入所有资源都通过classloader载入到jvm里,那么在载入资源时当然可以使用classloader,只是对于不同的资源还可以使用一些别的方式载入,例如对于类可以直接new,对于文件可以直接做io等。2.1载入类的几种方法假设有类a和类b,a在方法amethod里需要实例化b,可能的方法有3种。对于载入类的情况,用户需要知道b类的完整名字(包括包名,例如"com.rain.b")1.使用class静态方法class.fornameclasscls=class.forname("com.rain.b");bb=(b)cls.newinstance();2.使用classloader/*step1.getclassloader*/classloadercl;//如何获得classloader参考1.6/*step2.loadtheclass*/classcls=cl.loadclass("com.rain.b");//使用第一步得到的classloader来载入b/*step3.newinstance*/bb=(b)cls.newinstance();//有b的类得到一个b的实例3.直接newbb=newb();2.2文件载入(例如配置文件等)假设在com.rain.a类里想读取文件夹/com/rain/config里的文件sys.properties,读取文件可以通过绝对路径或相对路径,绝对路径很简单,在windows下以盘号开始,在unix下以"/"开始对于相对路径,其相对值是相对于classloader的,因为classloader是一棵树,所以这个相对路径和classloader树上的任何一个classloader相对比较后可以找到文件,那么文件就可以找到,当然,读取文件也使用委托模型1.直接io/***假设当前位置是"c:/test",通过执行如下命令来运行a"javacom.rain.a"*1.在程序里可以使用绝对路径,windows下的绝对路径以盘号开始,unix下以"/"开始*2.也可以使用相对路径,相对路径前面没有"/"*因为我们在"c:/test"目录下执行程序,程序入口点是"c:/test",相对路径就*是"com/rain/config/sys.properties"*(例子中,当前程序的classloader是appclassloader,systemclassloader=当前的*程序的classloader,入口点是"c:/test")*对于classloader树,如果文件在jdklib下,如果文件在jdklib/ext下,如果文件在环境变量里,*都可以通过相对路径"sys.properties"找到,lib下的文件最先被找到*/filef=newfile("c:/test/com/rain/config/sys.properties");//使用绝对路径//filef=newfile("com/rain/config/sys.properties");//使用相对路径inputstreamis=newfileinputstream(f);如果是配置文件,可以通过java.util.properties.load(is)将内容读到properties里,properties默认认为is的编码是iso-8859-1,如果配置文件是非英文的,可能出现乱码问题。2.使用classloader/***因为有3种方法得到classloader,对应有如下3种方法读取文件*使用的路径是相对于这个classloader的那个点的相对路径,此处只能使用相对路径*/inputstreamis=null;is=this.getclass().getclassloader().getresourceasstream("com/rain/config/sys.properties");//方法1//is=thread.currentthread().getcontextclassloader().getresourceasstream("com/rain/config/sys.properties");//方法2//is=classloader.getsystemresourceasstream("com/rain/config/sys.properties");//方法3如果是配置文件,可以通过java.util.properties.load(is)将内容读到properties里,这里要注意编码问题。3.使用resourcebundleresourcebundlebundle=resourcebundle.getboundle("com.rain.config.sys");这种用法通常用来载入用户的配置文件,关于resourcebunlde更详细的用法请参考其他文档总结:有如下3种途径来载入文件1.绝对路径--->io2.相对路径--->io--->classloader3.资源文件--->resourcebundle2.3如何在web应用里载入资源在web应用里当然也可以使用classloader来载入资源,但更常用的情况是使用servletcontext,如下是web目录结构contextroot|-jsp、html、image等各种文件|-[web-inf]|-web.xml|-[lib]web用到的jar文件|-[classes]类文件用户程序通常在classes目录下,如果想读取classes目录里的文件,可以使用classloader,如果想读取其他的文件,一般使用servletcontext.getresource()如果使用servletcontext.getresource(path)方法,路径必须以"/"开始,路径被解释成相对于contextroot的路径,此处载入文件的方法和classloader不同,举例"/web-inf/web.xml","/download/webexagent.rar"