这种作法通常在小规模的开发过程当中不会产生问题,仅仅要程序猿熟悉Java语言、了解JDBC技术和MySQL,可以很是快开发出对应的应用程序。
没有JNDI的作法存在的问题:
一、数据库server名称MyDBServer 、username和口令均可能需要改变,由此引起JDBC URL需要改动;
二、数据库可能改用别的产品,如改用DB2或者Oracle,引起JDBC驱动程序包和类名需要改动;
三、随着实际使用终端的添加,原配置的链接池參数可能需要调整;
四、......
解决的方法:
程序猿应该不需要关心“详细的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?訪问数据库的username和口令是什么?”等等这些问题。程序猿编写的程序应该没有对 JDBC 驱动程序的引用,没有server名称,没实username称或口令 —— 甚至没有数据库池或链接管理。php
而是把这些问题交给J2EE容器(比方weblogic)来配置和管理,程序猿仅仅需要对这些配置和管理进行引用就能够。
由此,就有了JNDI。java
//看的出来。是为了一个最最核心的问题:是为了解耦,是为了开发出更加可维护、可扩展//的系统
用了JNDI以后的作法:
首先。在在J2EE容器中配置JNDI參数,定义一个数据源。也就是JDBC引用參数,给这个数据源设置一个名称;而后,在程序中,经过数据源名称引用数据源从而訪问后台数据库。
//红色的字可以看出。JNDI是由j2ee容器提供的功能
详细操做例如如下(以JBoss为例):
一、配置数据源
在JBoss 的 D:\jboss420GA\docs\examples\jca 文件夹如下。有很是多不一样数据库引用的数据源定义模板。mysql
将当中的 mysql-ds.xml 文件Copy到你使用的server下,如 D:\jboss420GA\server\default\deploy。
改动 mysql-ds.xml 文件的内容,使之能经过JDBC正确訪问你的MySQL数据库。例如如下:
程序员
xml version="1.0" encoding="UTF-8"?> web
//解藕了。可扩展了
在系统部署后。假设数据库的相关參数变动。仅仅需要又一次配置 mysql-ds.xml 改动当中的JDBC參数,仅仅要保证数据源的名称不变,那么程序源码就无需改动。面试
因而可知。JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。spring
JNDI的扩展:
JNDI在知足了数据源配置的要求的基础上。还进一步扩充了做用:所有与系统外部的资源的引用,都可以经过JNDI定义和引用。sql
//注意什么叫资源
因此,在J2EE规范中,J2EE 中的资源并不局限于 JDBC 数据源。数据库
引用的类型有很是多,当中包含资源引用(已经讨论过)、环境实体和 EJB 引用。编程
特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另一项关键角色:查找其它应用程序组件。
EJB 的 JNDI 引用很是类似于 JDBC 资源的引用。在服务趋于转换的环境中,这是一种很是有效的方法。可以对应用程序架构中所获得的所有组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题。再到简单配置字符串或其它对象。这可以下降随时间的推移服务变动所产生的维护成本,同一时候还可以简化部署,下降集成工做。外部资源”。
总结:
J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。//sun 果真喜欢制定规范JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在执行时间接地查找其它组件、资源或服务的通用机制。在多数状况下,提供 JNDI 供应者的容器可以充当有限的数据存储。这样管理员就可以设置应用程序的执行属性,并让其它应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用做这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。
在 J2EE 中,JNDI 是把 J2EE 应用程序合在一块儿的粘合剂。JNDI 提供的间接寻址赞成跨企业交付可伸缩的、功能强大且很是灵活的应用程序。
这是 J2EE 的承诺,而且通过一些计划和预先考虑。这个承诺是全然可以实现的。
从上面的文章中可以看出:
一、JNDI 提出的目的是为了解藕,是为了开发更加easy维护,easy扩展。easy部署的应用。
二、JNDI 是一个sun提出的一个规范(类似于jdbc),详细的实现是各个j2ee容器提供商。sun 仅仅是要求,j2ee容器必须有JNDI这种功能。
三、JNDI 在j2ee系统中的角色是“交换机”,是J2EE组件在执行时间接地查找其它组件、资源或服务的通用机制。
四、JNDI 是经过资源的名字来查找的,资源的名字在整个j2ee应用中(j2ee容器中)是惟一的。
上文提到过双亲委派模型并非一个强制性的约束模型,而是 Java设计者推荐给开发者的类加载器实现方式。在Java 的世界中大部分的类加载器都遵循这个模型,但也有例外。
双亲委派模型的一次“被破坏”是由这个模型自身的缺陷所致使的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载) ,基础类之因此称为“基础”,是由于它们老是做为被用户代码调用的API ,但世事每每没有绝对的完美,若是基础类又要调用回用户的代码,那该怎么办?这并不是是不可能的事情,一个典型的例子即是JNDI 服务,JNDI如今已是Java的标准服务,它的代码由启动类加载器去加载(在 JDK 1.3时放进去的rt.jar),但JNDI 的目的就是对资源进行集中管理和查找,它须要调用由独立厂商实现并部署在应用程序的Class Path下的JNDI 接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识” 这些代码 ,由于启动类加载器的搜索范围中找不到用户应用程序类,那该怎么办?为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器能够经过java.lang.Thread类的setContextClassLoader()方法进行设置,若是建立线程时还未设置,它将会从父线程中继承一个,若是在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器(Application ClassLoader)。
有了线程上下文类加载器,就能够作一些“舞弊”的事情了,JNDI服务使用这个线程上下文类加载器去加载所须要的 SPI代码,也就是父类加载器请求子类加载器去完成类加载的动做,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器 ,实际上已经违背了双亲委派模型的通常性原则,但这也是迫不得已的事情。Java中全部涉及SPI的加载动做基本上都采用这种方式,例如JNDI 、JDBC、JCE、 JAXB 和JBI等。
目前,业内关于OSGI技术的学习资源或者技术文档仍是不多的。我在某宝网搜索了一下“OSGI”的书籍,结果却是有,可是种类少的可怜,并且几乎没有人购买。
由于工做的缘由我须要学习OSGI,因此我不得不想尽办法来主动学习OSGI。我将用文字记录学习OSGI的整个过程,经过整理书籍和视频教程,来让我更加了解这门技术,同时也让须要学习这门技术的同志们有一个清晰的学习路线。
咱们须要解决一下几问题:
1.如何正确的理解和认识OSGI技术?
咱们从外文资料上或者从翻译过来的资料上看到OSGi解释和定义,都是直译过来的,可是OSGI的真实意义未必是中文直译过来的意思。OSGI的解释就是Open Service Gateway Initiative,直译过来就是“开放的服务入口(网关)的初始化”,听起来很是费解,什么是服务入口初始化?
因此咱们不去直译这个OSGI,咱们换一种说法来描述OSGI技术。
咱们来回到咱们之前的某些开发场景中去,假设咱们使用SSH(struts+spring+hibernate)框架来开发咱们的Web项目,咱们作产品设计和开发的时候都是分模块的,咱们分模块的目的就是实现模块之间的“解耦”,更进一步的目的是方便对一个项目的控制和管理。
咱们对一个项目进行模块化分解以后,咱们就能够把不一样模块交给不一样的开发人员来完成开发,而后项目经理把你们完成的模块集中在一块儿,而后拼装成一个最终的产品。通常咱们开发都是这样的基本状况。
那么咱们开发的时候预计的是系统的功能,根据系统的功能来进行模块的划分,也就是说,这个产品的功能或客户的需求是划分的重要依据。
可是咱们在开发过程当中,咱们模块之间还要彼此保持联系,好比A模块要从B模块拿到一些数据,而B模块可能要调用C模块中的一些方法(除了公共底层的工具类以外)。因此这些模块只是一种逻辑意义上的划分。
最重要的一点是,咱们把最终的项目要去部署到tomcat或者jBoss的服务器中去部署。那么咱们启动服务器的时候,能不能关闭项目的某个模块或功能呢?很明显是作不到的,一旦服务器启动,全部模块就要一块儿启动,都要占用服务器资源,因此关闭不了模块,假设能强制拿掉,就会影响其它的功能。
以上就是咱们传统模块式开发的一些局限性。
咱们作软件开发一直在追求一个境界,就是模块之间的真正“解耦”、“分离”,这样咱们在软件的管理和开发上面就会更加的灵活,甚至包括给客户部署项目的时候均可以作到更加的灵活可控。可是咱们之前使用SSH框架等架构模式进行产品开发的时候咱们是达不到这种要求的。
因此咱们“架构师”或顶尖的技术高手都在为模块化开发努力的摸索和尝试,而后咱们的OSGI的技术规范就应运而生。
如今咱们的OSGI技术就能够知足咱们以前所说的境界:在不一样的模块中作到完全的分离,而不是逻辑意义上的分离,是物理上的分离,也就是说在运行部署以后均可以在不中止服务器的时候直接把某些模块拿下来,其余模块的功能也不受影响。
由此,OSGI技术未来会变得很是的重要,由于它在实现模块化解耦的路上,走得比如今你们常常所用的SSH框架走的更远。这个技术在将来大规模、高访问、高并发的Java模块化开发领域,或者是项目规范化管理中,会大大超过SSH等框架的地位。
如今主流的一些应用服务器,Oracle的weblogic服务器,IBM的WebSphere,JBoss,还有Sun公司的glassfish服务器,都对OSGI提供了强大的支持,都是在OSGI的技术基础上实现的。有那么多的大型厂商支持OSGI这门技术,咱们既能够看到OSGI技术的重要性。因此未来OSGI是未来很是重要的技术。
可是OSGI仍然脱离不了框架的支持,由于OSGI自己也使用了不少spring等框架的基本控件(由于要实现AOP依赖注入等功能),可是哪一个项目又不去依赖第三方jar呢?
双亲委派模型的另外一次“被破坏”是因为用户对程序动态性的追求而致使的,这里所说的“ 动态性”指的是当前一些很是“热门”的名词:代码热替换(HotSwap)、模块热部署(HotDeployment)等 ,说白了就是但愿应用程序能像咱们的计算机外设那样,接上鼠标、U盘,不用重启机器就能当即使用,鼠标有问题或要升级就换个鼠标,不用停机也不用重启。对于我的计算机来讲,重启一次其实没有什么大不了的,但对于一些生产系统来讲,关机重启一次可能就要被列为生产事故,这种状况下热部署就对软件开发者,尤为是企业级软件开发者具备很大的吸引力。Sun 公司所提出的JSR-29四、JSR-277规范在与 JCP组织的模块化规范之争中落败给JSR-291(即 OSGi R4.2),虽然Sun不甘失去Java 模块化的主导权,独立在发展 Jigsaw项目,但目前OSGi已经成为了业界“ 事实上” 的Java模块化标准,而OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每个程序模块( OSGi 中称为Bundle)都有一个本身的类加载器,当须要更换一个Bundle 时,就把Bundle连同类加载器一块儿换掉以实现代码的热替换。
在OSGi环境下,类加载器再也不是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi 将按照下面的顺序进行类搜索:
1)将以java.*开头的类委派给父类加载器加载。
2)不然,将委派列表名单内的类委派给父类加载器加载。
3)不然,将Import列表中的类委派给 Export这个类的Bundle的类加载器加载。
4)不然,查找当前Bundle的 Class Path,使用本身的类加载器加载。
5)不然,查找类是否在本身的Fragment Bundle中,若是在,则委派给 Fragment Bundle的类加载器加载。
6)不然,查找Dynamic Import列表的 Bundle,委派给对应Bundle的类加载器加载。
7)不然,类查找失败。
上面的查找顺序中只有开头两点仍然符合双亲委派规则,其他的类查找都是在平级的类加载器中进行的。
只要有足够意义和理由,突破已有的原则就可认为是一种创新。正如OSGi中的类加载器并不符合传统的双亲委派的类加载器,而且业界对其为了实现热部署而带来的额外的高复杂度还存在很多争议,但在Java 程序员中基本有一个共识:OSGi中对类加载器的使用是很值得学习的,弄懂了OSGi的实现,就能够算是掌握了类加载器的精髓。
Tomcat的用户必定都使用过其应用部署功能,不管是直接拷贝文件到webapps目录,仍是修改server.xml以目录的形式部署,或者是增长虚拟主机,指定新的appBase等等。
但部署应用时,不知道你是否曾注意过这几点:
若是在一个Tomcat内部署多个应用,甚至多个应用内使用了某个相似的几个不一样版本,但它们之间却互不影响。这是如何作到的。
若是多个应用都用到了某相似的相同版本,是否能够统一提供,不在各个应用内分别提供,占用内存呢。
还有时候,在开发Web应用时,在pom.xml中添加了servlet-api的依赖,那实际应用的class加载时,会加载你的servlet-api 这个jar吗
以上提到的这几点,在Tomcat以及各种的应用服务器中,都是经过类加载器(ClasssLoader)来实现的。经过本文,你能够了解到Tomcat内部提供的各类类加载器,Web应用的class和资源等加载的方式,以及其内部的实现原理。在遇到相似问题时,更成竹在胸。
类加载器
Java语言自己,以及如今其它的一些基于JVM之上的语言(Groovy,Jython, Scala...),都是在将代码编译生成class文件,以实现跨多平台,write once, run anywhere。最终的这些class文件,在应用中,又被加载到JVM虚拟机中,开始工做。而把class文件加载到JVM的组件,就是咱们所说的类加载器。而对于类加载器的抽象,能面对更多的class数据提供形式,例如网络、文件系统等。
Java中常见的那个ClassNotFoundException和NoClassDefFoundError就是类加载器告诉咱们的。
Servlet规范指出,容器用于加载Web应用内Servlet的class loader, 容许加载位于Web应用内的资源。但不容许重写java.*, javax.*以及容器实现的类。同时
每一个应用内使用Thread.currentThread.getContextClassLoader()得到的类加载器,都是该应用区别于其它应用的类加载器等等。
根据Servlet规范,各个应用服务器厂商自行实现。因此像其余的一些应用服务器同样, Tomcat也提供了多种的类加载器,以便应用服务器内的class以及部署的Web应用类文件运行在容器中时,可使用不一样的class repositories。
在Java中,类加载器是以一种父子关系树来组织的。除Bootstrap外,都会包含一个parent 类加载器。(这里写parent 类加载器,而不是父类加载器,不是为了装X,是为了不和Java里的父类混淆) 通常以类加载器须要加载一个class或者资源文件的时候,他会先委托给他的parent类加载器,让parent类加载器先来加载,若是没有,才再在本身的路径上加载。这就是人们常说的双亲委托,即把类加载的请求委托给parent。
可是...,这里须要注意一下
对于Web应用的类加载,和上面的双亲委托是有区别的。
主流的Java Web服务器(也就是Web容器) ,如Tomcat、Jetty、WebLogic、WebSphere 或其余笔者没有列举的服务器,都实现了本身定义的类加载器(通常都不止一个)。由于一个功能健全的 Web容器,要解决以下几个问题:
1)部署在同一个Web容器上 的两个Web应用程序所使用的Java类库能够实现相互隔离。这是最基本的需求,两个不一样的应用程序可能会依赖同一个第三方类库的不一样版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库能够互相独立使用。
2)部署在同一个Web容器上 的两个Web应用程序所使用的Java类库能够互相共享 。这个需求也很常见,例如,用户可能有10个使用spring 组织的应用程序部署在同一台服务器上,若是把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到Web容器的内存,若是类库不能共享,虚拟机的方法区就会很容易出现过分膨胀的风险。
3)Web容器须要尽量地保证自身的安全不受部署的Web应用程序影响。目前,有许多主流的Java Web容器自身也是使用Java语言来实现的。所以,Web容器自己也有类库依赖的问题,通常来讲,基于安全考虑,容器所使用的类库应该与应用程序的类库互相独立。
4)支持JSP应用的Web容器,大多数都须要支持 HotSwap功能。咱们知道,JSP文件最终要编译成Java Class才能由虚拟机执行,但JSP文件因为其纯文本存储的特性,运行时修改的几率远远大于第三方类库或程序自身的Class文件 。并且ASP、PHP 和JSP这些网页应用也把修改后无须重启做为一个很大的“优点”来看待 ,所以“主流”的Web容器都会支持JSP生成类的热替换 ,固然也有“非主流”的,如运行在生产模式(Production Mode)下的WebLogic服务器默认就不会处理JSP文件的变化。
因为存在上述问题,在部署Web应用时,单独的一个Class Path就没法知足需求了,因此各类 Web容都“不约而同”地提供了好几个Class Path路径供用户存放第三方类库,这些路径通常都以“lib”或“classes ”命名。被放置到不一样路径中的类库,具有不一样的访问范围和服务对象,一般,每个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库 。如今,就以Tomcat 容器为例,看一看Tomcat具体是如何规划用户类库结构和类加载器的。
在Tomcat目录结构中,有3组目录(“/common/*”、“/server/*”和“/shared/*”)能够存放Java类库,另外还能够加上Web 应用程序自身的目录“/WEB-INF/*” ,一共4组,把Java类库放置在这些目录中的含义分别以下:
①放置在/common目录中:类库可被Tomcat和全部的 Web应用程序共同使用。
②放置在/server目录中:类库可被Tomcat使用,对全部的Web应用程序都不可见。
③放置在/shared目录中:类库可被全部的Web应用程序共同使用,但对Tomcat本身不可见。
④放置在/WebApp/WEB-INF目录中:类库仅仅能够被此Web应用程序使用,对 Tomcat和其余Web应用程序都不可见。
为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,其关系以下图所示。
上图中灰色背景的3个类加载器是JDK默认提供的类加载器,这3个加载器的做用已经介绍过了。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat本身定义的类加载器,它们分别加载/common/*、/server/*、/shared/*和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器一般会存在多个实例,每个Web应用程序对应一个WebApp类加载器,每个JSP文件对应一个Jsp类加载器。
从图中的委派关系中能够看出,CommonClassLoader能加载的类均可以被Catalina ClassLoader和SharedClassLoader使用,而CatalinaClassLoader和Shared ClassLoader本身能加载的类则与对方相互隔离。WebAppClassLoader可使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并经过再创建一个新的Jsp类加载器来实现JSP文件的HotSwap功能。
对于Tomcat的6.x版本,只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader项后才会真正创建Catalina ClassLoader和Shared ClassLoader的实例,不然在用到这两个类加载器的地方都会用Common ClassLoader的实例代替,而默认的配置文件中没有设置这两个loader项,因此Tomcat 6.x瓜熟蒂落地把/common、/server和/shared三个目录默认合并到一块儿变成一个/lib目录,这个目录里的类库至关于之前/common目录中类库的做用。这是Tomcat设计团队为了简化大多数的部署场景所作的一项改进,若是默认设置不能知足须要,用户能够经过修改配置文件指定server.loader和share.loader的方式从新启用Tomcat 5.x的加载器架构。
Tomcat加载器的实现清晰易懂,而且采用了官方推荐的“正统”的使用类加载器的方式。若是读者阅读完上面的案例后,能彻底理解Tomcat设计团队这样布置加载器架构的用意,那说明已经大体掌握了类加载器“主流”的使用方式,那么笔者不妨再提一个问题让读者思考一下:前面曾经提到过一个场景,若是有10个Web应用程序都是用Spring来进行组织和管理的话,能够把Spring放到Common或Shared目录下让这些程序共享。Spring要对用户程序的类进行管理,天然要能访问到用户程序的类,而用户的程序显然是放在/WebApp/WEB-INF目录中的,那么被CommonClassLoader或SharedClassLoader加载的Spring如何访问并不在其加载范围内的用户程序呢?若是研究过虚拟机类加载器机制中的双亲委派模型,相信读者能够很容易地回答这个问题。
分析:若是按主流的双亲委派机制,显然没法作到让父类加载器加载的类 去访问子类加载器加载的类,上面在类加载器一节中提到过经过线程上下文方式传播类加载器。
答案是使用线程上下文类加载器来实现的,使用线程上下文加载器,可让父类加载器请求子类加载器去完成类加载的动做。看spring源码发现,spring加载类所用的Classloader是经过Thread.currentThread().getContextClassLoader()来获取的,而当线程建立时会默认setContextClassLoader(AppClassLoader),即线程上下文类加载器被设置为 AppClassLoader,spring中始终能够获取到这个AppClassLoader( 在 Tomcat里就是WebAppClassLoader)子类加载器来加载bean ,之后任何一个线程均可以经过 getContextClassLoader()获取到WebAppClassLoader来getbean 了 。
本篇博文内容取材自《深刻理解Java虚拟机:JVM高级特性与最佳实践》
微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。做者黄小斜,专一 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”Java“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源)