当前位置: 首页 > news >正文

网站后台注入如何建立免费的个人企业网站

网站后台注入,如何建立免费的个人企业网站,设计招聘信息,做网站需要学会做哪些东西转载自 真正理解线程上下文类加载器#xff08;多案例分析#xff09; 前置知识#xff1a; java类加载器不完整分析 前言 此前我对线程上下文类加载器#xff08;ThreadContextClassLoader#xff0c;下文使用TCCL表示#xff09;的理解仅仅局限于下面这段话#x…转载自  真正理解线程上下文类加载器多案例分析 前置知识 java类加载器不完整分析 前言 此前我对线程上下文类加载器ThreadContextClassLoader下文使用TCCL表示的理解仅仅局限于下面这段话 Java 提供了很多服务提供者接口Service Provider InterfaceSPI允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。 这些 SPI 的接口由 Java 核心库来提供而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径CLASSPATH里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了SPI的接口是Java核心库的一部分是由启动类加载器(Bootstrap Classloader)来加载的SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的因为依照双亲委派模型BootstrapClassloader无法委派AppClassLoader来加载类。 而线程上下文类加载器破坏了“双亲委派模型”可以在执行线程中抛弃双亲委派加载链模式使程序可以逆向使用类加载器。 一直困恼我的问题就是它是如何打破了双亲委派模型又是如何逆向使用类加载器了直到今天看了jdbc的驱动加载过程才茅塞顿开其实并不复杂只是一直没去看代码导致理解不够到位。 JDBC案例分析 我们先来看平时是如何使用mysql获取数据库连接的 // 加载Class到AppClassLoader系统类加载器然后注册驱动类 // Class.forName(com.mysql.jdbc.Driver).newInstance(); String url jdbc:mysql://localhost:3306/testdb; // 通过java库获取数据库连接 Connection conn java.sql.DriverManager.getConnection(url, name, password); 以上就是mysql注册驱动及获取connection的过程各位可以发现经常写的Class.forName被注释掉了但依然可以正常运行这是为什么呢这是因为从Java1.6开始自带的jdbc4.0版本已支持SPI服务加载机制只要mysql的jar包在类路径中就可以注册mysql驱动。 那到底是在哪一步自动注册了mysql driver的呢重点就在DriverManager.getConnection()中。我们都是知道调用类的静态方法会初始化该类进而执行其静态代码块DriverManager的静态代码块就是 static {loadInitialDrivers();println(JDBC DriverManager initialized); } 初始化方法loadInitialDrivers()的代码如下 private static void loadInitialDrivers() {String drivers;try {// 先读取系统属性drivers AccessController.doPrivileged(new PrivilegedActionString() {public String run() {return System.getProperty(jdbc.drivers);}});} catch (Exception ex) {drivers null;}// 通过SPI加载驱动类AccessController.doPrivileged(new PrivilegedActionVoid() {public Void run() {ServiceLoaderDriver loadedDrivers ServiceLoader.load(Driver.class);IteratorDriver driversIterator loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});// 继续加载系统属性中的驱动类if (drivers null || drivers.equals()) {return;}String[] driversList drivers.split(:);println(number of Drivers: driversList.length);for (String aDriver : driversList) {try {println(DriverManager.Initialize: loading aDriver);// 使用AppClassloader加载Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println(DriverManager.Initialize: load failed: ex);}} } 从上面可以看出JDBC中的DriverManager的加载Driver的步骤顺序依次是  1. 通过SPI方式读取 META-INF/services 下文件中的类名使用TCCL加载  2. 通过System.getProperty(jdbc.drivers)获取设置然后通过系统类加载器加载。  下面详细分析SPI加载的那段代码。 JDBC中的SPI 先来看看什么是SP机制引用一段博文中的介绍 SPI机制简介  SPI的全名为Service Provider Interface主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想我们系统里抽象的各个模块往往有很多不同的实现方案比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里我们一般推荐模块之间基于接口编程模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类就违反了可拔插的原则如果需要替换一种实现就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制为某个接口寻找服务实现的机制。有点类似IOC的思想就是将装配的控制权移到程序之外在模块化设计中这个机制尤其重要。 SPI具体约定  Java SPI的具体约定为当服务的提供者提供了服务接口的一种实现之后在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名并装载实例化完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类而不需要再代码里制定。jdk提供服务实现查找的一个工具类java.util.ServiceLoader。 知道SPI的机制后我们来看刚才的代码 ServiceLoaderDriver loadedDrivers ServiceLoader.load(Driver.class); IteratorDriver driversIterator loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();} } catch(Throwable t) { // Do nothing } 注意driversIterator.next()最终就是调用Class.forName(DriverName, false, loader)方法也就是最开始我们注释掉的那一句代码。好那句因SPI而省略的代码现在解释清楚了那我们继续看给这个方法传的loader是怎么来的。 因为这句Class.forName(DriverName, false, loader)代码所在的类在java.util.ServiceLoader类中而ServiceLoader.class又加载在BootrapLoader中因此传给 forName 的 loader 必然不能是BootrapLoader复习双亲委派加载机制请看java类加载器不完整分析 。这时候只能使用TCCL了也就是说把自己加载不了的类加载到TCCL中通过Thread.currentThread()获取简直作弊啊。上面那篇文章末尾也讲到了TCCL默认使用当前执行的是代码所在应用的系统类加载器AppClassLoader。 再看下看ServiceLoader.load(Class)的代码的确如此 public static S ServiceLoaderS load(ClassS service) {ClassLoader cl Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl); } ContextClassLoader默认存放了AppClassLoader的引用由于它是在运行时被放在了线程中所以不管当前程序处于何处BootstrapClassLoader或是ExtClassLoader等在任何需要的时候都可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成需要的操作。 到这儿差不多把SPI机制解释清楚了。直白一点说就是我JDK提供了一种帮你第三方实现者加载服务如数据库驱动、日志库的便捷方式只要你遵循约定把类名写在/META-INF里那当我启动时我会去扫描所有jar包里符合约定的类名再调用forName加载但我的ClassLoader是没法加载的那就把它加载到当前执行线程的TCCL里后续你想怎么操作驱动实现类的static代码块就是你的事了。 好刚才说的驱动实现类就是com.mysql.jdbc.Driver.Class它的静态代码块里头又写了什么呢是否又用到了TCCL呢我们继续看下一个例子。 使用TCCL校验实例的归属 com.mysql.jdbc.Driver加载后运行的静态代码块: static {try {// Driver已经加载到TCCL中了此时可以直接实例化java.sql.DriverManager.registerDriver(new com.mysql.jdbc.Driver());} catch (SQLException E) {throw new RuntimeException(Cant register driver!);} } registerDriver方法将driver实例注册到系统的java.sql.DriverManager类中其实就是add到它的一个名为registeredDrivers的静态成员CopyOnWriteArrayList中 。 到此驱动注册基本完成接下来我们回到最开始的那段样例代码java.sql.DriverManager.getConnection()。它最终调用了以下方法 private static Connection getConnection(String url, java.util.Properties info, Class? caller) throws SQLException {/* 传入的caller由Reflection.getCallerClass()得到该方法* 可获取到调用本方法的Class类这儿调用者是java.sql.DriverManager位于/lib/rt.jar中* 也就是说caller.getClassLoader()本应得到Bootstrap启动类加载器* 但是在上篇文章[java类加载器不完整分析]中讲到过启动类加载器无法被程序获取所以只会得到null*/ClassLoader callerCL caller ! null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// 此处再次获取线程上下文类加载器用于后续校验if (callerCL null) {callerCL Thread.currentThread().getContextClassLoader();}}if(url null) {throw new SQLException(The url cannot be null, 08001);}SQLException reason null;// 遍历注册到registeredDrivers里的Driver类for(DriverInfo aDriver : registeredDrivers) {// 使用线程上下文类加载器检查Driver类有效性重点在isDriverAllowed中方法内容在后面if(isDriverAllowed(aDriver.driver, callerCL)) {try {println( trying aDriver.driver.getClass().getName());// 调用com.mysql.jdbc.Driver.connect方法获取连接Connection con aDriver.driver.connect(url, info);if (con ! null) {// Success!return (con);}} catch (SQLException ex) {if (reason null) {reason ex;}}} else {println( skipping: aDriver.getClass().getName());}}throw new SQLException(No suitable driver found for url, 08001);} private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {boolean result false;if(driver ! null) {Class? aClass null;try {// 传入的classLoader为调用getConnetction的线程上下文类加载器从中寻找driver的class对象aClass Class.forName(driver.getClass().getName(), true, classLoader);} catch (Exception ex) {result false;}// 注意只有同一个类加载器中的Class使用比较时才会相等此处就是校验用户注册Driver时该Driver所属的类加载器与调用时的是否同一个// driver.getClass()拿到就是当初执行Class.forName(com.mysql.jdbc.Driver)时的应用AppClassLoaderresult ( aClass driver.getClass() ) ? true : false;}return result; } 可以看到这儿TCCL的作用主要用于校验存放的driver是否属于调用线程的Classloader。例如在下文中的tomcat里多个webapp都有自己的Classloader如果它们都自带 mysql-connect.jar包那底层Classloader的DriverManager里将注册多个不同类加载器的Driver实例想要区分只能靠TCCL了。 Tomcat与spring的类加载器案例 接下来将介绍《深入理解java虚拟机》一书中的案例并解答它所提出的问题。部分类容来自于书中原文 Tomcat中的类加载器 在Tomcat目录结构中有三组目录“/common/*”,“/server/*”和“shared/*”可以存放公用Java类库此外还有第四组Web应用程序自身的目录“/WEB-INF/*”把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 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类但各个 WebAppClassLoader 实例之间相互隔离。而 JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class它出现的目的就是为了被丢弃当服务器检测到 JSP 文件被修改时会替换掉目前的 JasperLoader 的实例并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。 Spring加载问题 Tomcat 加载器的实现清晰易懂并且采用了官方推荐的“正统”的使用类加载器的方式。这时作者提一个问题如果有 10 个 Web 应用程序都用到了spring的话可以把Spring的jar包放到 common 或 shared 目录下让这些程序共享。Spring 的作用是管理每个web应用程序的beangetBean时自然要能访问到应用程序的类而用户的程序显然是放在 /WebApp/WEB-INF 目录中的由 WebAppClassLoader 加载那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加载并不在其加载范围的用户程序/WebApp/WEB-INF/中的Class呢 解答 答案呼之欲出spring根本不会去管自己被放在哪里它统统使用TCCL来加载类而TCCL默认设置为了WebAppClassLoader也就是说哪个WebApp应用调用了springspring就去取该应用自己的WebAppClassLoader来加载bean简直完美~ 源码分析 有兴趣的可以接着看看具体实现。在web.xml中定义的listener为org.springframework.web.context.ContextLoaderListener它最终调用了org.springframework.web.context.ContextLoader类来装载bean具体方法如下删去了部分不相关内容 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {try {// 创建WebApplicationContextif (this.context null) {this.context createWebApplicationContext(servletContext);}// 将其保存到该webapp的servletContext中 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);// 获取线程上下文类加载器默认为WebAppClassLoaderClassLoader ccl Thread.currentThread().getContextClassLoader();// 如果spring的jar包放在每个webapp自己的目录中// 此时线程上下文类加载器会与本类的类加载器加载spring的相同都是WebAppClassLoaderif (ccl ContextLoader.class.getClassLoader()) {currentContext this.context;}else if (ccl ! null) {// 如果不同也就是上面说的那个问题的情况那么用一个map把刚才创建的WebApplicationContext及对应的WebAppClassLoader存下来// 一个webapp对应一个记录后续调用时直接根据WebAppClassLoader来取出currentContextPerThread.put(ccl, this.context);}return this.context;}catch (RuntimeException ex) {logger.error(Context initialization failed, ex);throw ex;}catch (Error err) {logger.error(Context initialization failed, err);throw err;} } 具体说明都在注释中spring考虑到了自己可能被放到其他位置所以直接用TCCL来解决所有可能面临的情况。 总结 通过上面的两个案例分析我们可以总结出线程上下文类加载器的适用场景  1. 当高层提供了统一接口让低层去实现同时又要是在高层加载或实例化低层的类时必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。  2. 当使用本类托管类加载然而加载本类的ClassLoader未知时为了隔离不同的调用者可以取调用者各自的线程上下文类加载器代为托管。
http://mrfarshtey.net/news/11828/

相关文章:

  • 怎么做网站可手机看狗和女人做的网站
  • 专门找人做软件的网站央视优购物官方网站
  • 网站开发有哪些服务wordpress 公告栏
  • 网站根目录文件 seo销售网
  • 小视频网站如何建设网站 项目 需求
  • 中山网站建设seo135如何建立一个app
  • 沈阳市建设局网站磁力天堂torrentkitty
  • 广西网站怎么制作电子政务网站建设方案
  • 做自媒体都有什么网站长沙网站优化厂家
  • 北京市轨道交通建设管理有限公司网站iis搭建网站时 属于默认文档的是
  • 官方网站拼多多网站优化竞争对手分析
  • 南通住房和城乡建设局网站学校网站的建设需求
  • 星锐网站建设gif放网站有锯齿
  • 在线做漫画的网站好文章转载到wordpress
  • 做淘宝素材网站哪个好用万网域名注册商
  • 成都网站推广营销设计淮南网站推广
  • 网站seo快速排名阿里指数官网最新版本
  • 建设房屋出租网站东莞网站优化方法
  • 芜湖网站建设 文库网站关键词的确定
  • 企业网站空间多大源码库官网
  • 市场体系建设司在官方网站网站建设前景怎么样
  • 做网站需要哪些人做网站关于我们
  • 无锡新区企业网站推广wordpress怎样添加左侧菜单的功能
  • 上海松江区建设局官方网站制作表情包的微信小程序
  • 网站引导图wordpress怎么写接口
  • 外贸网站建设 东莞上海sem
  • PHP网站建设的基本流程网站设计设计目的
  • 企业门户网站的建设方法怎么登陆公司网站的后台
  • 建筑网站大全豆丁网wordpress+纯静态插件
  • 南京成旭通网站建设公司怎么样用asp做网站