浅谈在Jar中同名类冲突问题及覆写第三方Jar中的类

正文 一、同名类产生的原因 当同名类(完全限定名)在自己编写的工程中肯定不会出现,但是这无法保证在服务所依赖的JAR中不会出现。在多人协作时,如果每个人在自己负责的服务中都需要引用一段公共代码,同时又把它抽象到一个类的一个方法中,并且大家还采用了代码拷贝的方式进行维护。此时如果有1个人对这段代码做了定制化的修改,但没有修改方法或类的名称。这个时候如果有场景需要将多个服务合并打包发布的时候,这个定制化的类就变成了一颗定时炸弹。由于同一个类加载器对于同名类只会加载一次,那么一旦JVM的选取策略发生了变化,那么就会爆发不可控的风险。 二、JAR包加载的顺序 问题产生的原因其实是简单的,但是又是什么原因造成同名类在不同的环境下被选择了不同的版本? 由于同名类是在不同JAR出现的,所以这个问题就转化成了JAR的加载顺序是由什么因素导致的 类加载器级别 JVM的类加载其实是一个树形结构,而JVM在加载类的时候采用的是双亲代理模式,层级越高的类加载器会越早加载其路径下的类。下面是JVM类加载器的简单说明。 由于上面出问题的类都属于系统类加载器管理,所以我们可以排除类加载器级别导致的问题。 系统的文件加载顺序 当JAR包都属于同一个类加载器是,它们的加载顺序就是由系统的文件加载顺序来决定的。这往往就是因为环境的不同导致诡异的类冲突问题的元凶。由于大多数的容器的ClassLoader在获取对应录下的文件列表时是不会自己排序的,所以加载的顺序就依赖于底层文件系统返回的顺序。当系统的文件排序规则不一致时,就会发生上面的现象。在Linux系统中文件的顺序是由iNode的顺序来决定的。这时让我们来看一下两个不同环境下对相同JAR的排序是什么样的。 a. 公司环境 [root@home lib]# ls -i | sort2359300 A.jar — 该JAR包中的类是希望调用的2359301 B.jar b. 客户环境 [root@partner lib]# ls -i | sort1235909 B.jar1236002 A.jar — 该JAR包中的类是希望调用的 此时很明显可以看到上面的现象是由文件系统的排序不同导致的。 三、如何检测同名类冲突 在构建工具中使用插件 当使用maven时可以采用maven-enforcer-plugin,这个强大的maven插件,配合extra-enforcer-rules工具,能自动扫描Jar包将冲突检测并打印出来。 遗憾的是gradle中没有相应的插件,若日常使用的构建工具是gradle,就要💭别的方式 使用Linux命令 为了弥补gralde中没有现成插件的遗憾,当我们知道冲突的类名以后,我们可以在Linux中执行以下命令来检索冲突类出现在那些jar包中 #checkEnforce.sh​#/bin/sh​jarlist=`ls ./|grep jar`for jarname in $jarlist;doecho “检索 $jarname”jar -tvf $jarname|grep class|grep $1done 扩展:如何覆盖第三方jar的类 […]

红黑树

前言:耗费了五个多小时,算是差不多了 红黑树是一种结点带有颜色属性的二叉查找树,但它在二叉查找树之外,还有以下要求: 下图就是一个典型的红黑树: 但实现上我省略了其中的 Nil 结点,一般如下图,大家理解时也可以忽略它们。 优势和用途 我们知道二叉查找树在不停地添加或删除结点后,可能会导致结点情况如下: 这种情况下,二叉查找树的查找效率最坏会降低为 O(n)。 而红黑树由于在插入和删除结点时都会进行变色旋转等操作,在符合红黑树条件的情况下,即使一边子树全是黑色结点,另一边子树全是红黑相间,两子树的高度差也不会超过一半。一棵有 n 个结点的红黑树高度至多为 2log(n+1),查找效率最坏为 O(log(n))。 所以红黑树常被用于需求查找效率稳定的场景,如 Linux 中内核使用它管理内存区域对象、Java8 中 HashMap 的实现等,所以了解红黑树也很有意义。 下面介绍一下红黑树的等同 2-3-4树。 2-3-4树 2-3-4树是四阶的 B树(Balance Tree),它的结构有以下限制: 下图是一个典型的 2-3-4树(来自维基百科): 2-3-4树的查询操作像普通的二叉搜索树一样,非常简单,但由于其结点元素数不确定,在一些编程语言中实现起来并不方便,实现一般使用它的等同——红黑树。 红黑树维持平衡的方式 对应红黑树 至于为什么说红黑树是 2-3-4树的一种等同呢,这是因为 2-3-4树的每一个结点都对应红黑树的一种结构,所以每一棵 2-3-4树也都对应一棵红黑树,下图是 2-3-4树不同结点与红黑树子树的对应。 而上文中的 2-3-4树也可以转换成一棵红黑树: 由红黑树的性质5,和 2-3-4树的性质1,为了便于理解红黑树和 2-3-4树的对应关系,我们可以把红黑树从根结点到叶子结点的黑色结点个数定义为高度。 红黑树和 2-3-4树的结点添加和删除都有一个基本规则:避免子树高度变化,因为无论是 2-3-4树还是红黑树,一旦子树高度有变动,势必会影响其他子树进行调整,所以我们在插入和删除结点时尽量通过子树内部调整来达到平衡,2-3-4树实现平衡是通过结点的旋转和结点元素数变化,红黑树是通过结点旋转和变色。 下面来对照着 2-3-4树说一下红黑树结点的添加和删除: 结点插入 2-3-4树中结点添加需要遵守以下规则: 而将这些规则对应到红黑树里,就是: 如上图所示,虽然向红黑树中插入了一个新结点,但由于旋转和变色,子树的高度保持不变。 删除结点 红黑树的删除要比插入要复杂一些,我们还是类比 2-3-4树来讲: 将这些规则对应到红黑树中即: […]

Spring循环依赖(转)

前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题。 其实笔者本人对这类框架源码题还是持一定的怀疑态度的。 如果笔者作为面试官,可能会问一些诸如“如果注入的属性为null,你会从哪几个方向去排查”这些场景题。 那么既然写了这篇文章,闲话少说,发车看看Spring是如何解决的循环依赖,以及带大家看清循环依赖的本质是什么。 细看的话,会发现两个Map就可以了,一个存最终的,一个存中间的(甚至极端点一个都行),至于为何用三个,看网上有说法称第二级的Map是在2.5.4版本才引入的(也就是之前只有第一和第三层,待确认),原因就是下面讲的 IOC只使用一级缓存有什么问题? 一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。 如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。 所以,就要加一个map,这个map,用来存放那种不完整的bean。 那为什么Sping不选择二级缓存方式,而是要额外加一层缓存? 因为当使用aop时,注入的bean是代理对象,所以需要使用singletonFactories对象; 当产生循环依赖时需要注入earlySingletonObjects中的对象,但不产生循环依赖的场景是不需要的,因为循环依赖只是部分场景,提前生成early对象没有必要(有个getxxx方法,用于生成early对象),所以引入了第三层。这部分讲述了earlySingletonObjects的用途,以及为啥不能不要。 下面这点,讲的是为啥不直接创建代理,这样不要singletonFactories的用途,以及为啥不能不要。 如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。 Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。 如果出现了循环依赖,那没有办法,只有给Bean先创建代理(earlySingletonObjects),但是没有出现循环依赖的情况下,是不需要创建early对象的,这样就造成了浪费,所以设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。(这段有点说的不明白) 正文 通常来说,如果问Spring内部如何解决循环依赖,一定是默认的单例Bean中,属性互相引用的场景。 比如几个Bean之间的互相引用: img 甚至自己“循环”依赖自己: img 先说明前提:原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。 原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A… 这就套娃了, 你猜是先StackOverflow还是OutOfMemory? Spring怕你不好猜,就先抛出了BeanCurrentlyInCreationException! 基于构造器的循环依赖,就更不用说了,官方文档都摊牌了,你想让构造器注入支持循环依赖,是不存在的,不如把代码改了。 那么默认单例的属性注入场景,Spring是如何支持循环依赖的? Spring解决循环依赖 首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。 下面代码可以说是很清晰了 笔者翻阅Spring文档倒是没有找到三级缓存的概念,可能也是本土为了方便理解的词汇。 在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map: singletonObjects 它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。 singletonFactories 映射创建Bean的原始工厂 earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance. 后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。 所以笔者前文对“三级缓存”这个词有些迷惑,可能是因为注释都是以Cache of开头吧。 为什么成为后两个Map为垫脚石,假设最终放在singletonObjects的Bean是你想要的一杯“凉白开”。 那么Spring准备了两个杯子,即singletonFactories和earlySingletonObjects来回“倒腾”几番,把热水晾成“凉白开”放到singletonObjects中。 闲话不说,都浓缩在图里。 上面的是一张GIF,如果你没看到可能还没加载出来。三秒一帧,不是你电脑卡。 笔者画了17张图简化表述了Spring的主要步骤,GIF上方即是刚才提到的三级缓存,下方展示是主要的几个方法。 当然了,这个地步你肯定要结合Spring源码来看,要不肯定看不懂。 如果你只是想大概了解,或者面试,可以先记住笔者上文提到的“三级缓存”,以及下文即将要说的本质。 循环依赖的本质 上文了解完Spring如何处理循环依赖之后,让我们跳出“阅读源码”的思维,假设让你实现一个有以下特点的功能,你会怎么做? 将指定的一些类实例为单例 类中的字段也都实例为单例 […]

MyBatis 源码解析:配置文件的加载与解析(转)

zhenchao OSC开源社区 1周前 我们约定 mybatis-config.xml 文件为配置文件,SQL 语句配置文件为映射文件,本文我们将沿用上一篇中的示例程序,一起探究一下 MyBatis 加载和解析配置文件(即 mybatis-config.xml)的过程。 配置文件的加载过程 在示例程序中,执行配置文件(包括后面要介绍的映射文件)加载与解析的过程位于第一行代码中(如下)。其中,Resources 是一个简单的基于类路径或其它位置获取数据流的工具类,借助该工具类可以获取配置文件的 InputStream 流对象,然后将其传递给 SqlSessionFactoryBuilder#build 方法以构造 SqlSessionFactory 对象。 SqlSessionFactoryBuilder 由名字可知它是一个构造器,用于构造 SqlSessionFactory 对象。按照 MyBatis 的官方文档来说,SqlSessionFactoryBuilder 一旦构造完 SqlSessionFactory 对象便完成了其使命。其实现也比较简单,只定义了 SqlSessionFactoryBuilder#build 这一个方法及其重载版本,如下: 上述实现的核心在于如下两行: 第一行用来构造 XMLConfigBuilder 对象,XMLConfigBuilder 可以看作是 mybatis-config.xml 配置文件的解析器;第二行则调用该对象的 XMLConfigBuilder#parse 方法对配置文件进行解析,并记录相关配置项到 Configuration 对象中,然后基于该配置对象创建 SqlSessionFactory 对象返回。Configuration 可以看作是 MyBatis 框架内部全局唯一的配置类,用于记录几乎所有的配置和映射,以及运行过程中的中间值。后面我们会经常遇到这个类,现在可以将其理解为 MyBatis 框架的配置中心。 我们来看一下 XMLConfigBuilder 对象的构造过程: 构造方法各参数的释义见代码注释。这里针对一些比较不太直观的参数作进一步说明,首先看一下 XPathParser 类型的构造参数。我们需要知道的一点是,MyBatis 基于 […]

URL匹配RequestMatcher接口详解

我们知道spring secuity是控制URL的访问权限的,那么spring secuity是怎样拦截匹配URL,我们先看一个接口RequestMatcher 匹配HttpServletRequest的简单策略接口RequestMatcher,其下定义了matches方法,如果返回是true表示提供的请求与提供的匹配规则匹配,如果返回的是false则不匹配。 匹配HttpServletRequest的简单策略接口 RequestMatcher其实现类: AntPathRequestMatcher:重点 MvcRequestMatcher:重点 RegexRequestMatcher: 根据正则模式进行匹配 AnyRequestMatcher AntPathRequestMatcher 其javadoc描述如下: Matcher which compares a pre-defined ant-style pattern against the URL (servletPath + pathInfo) of an HttpServletRequest. The query string of the URL is ignored and matching is case-insensitive or case-sensitive depending on the arguments passed into the constructor.Matcher将预先定义的ant风格与URL进行比较(一个HttpServletRequest的servletPath + pathInfo)。 查询字符串网址被忽略,匹配是否区分大小写具体取决于传递给构造函数的参数(详细可查看AntPathRequestMatcher的三个入参的构造函数) Using […]

Spring Security 基于注解的接口角色访问控制

转自 1. 前言 虽然可以通过 javaConfig 的方式配置接口的角色访问控制。其实还有一种更加灵活的配置方式 基于注解 。 2. Spring Security 方法安全 Spring Security 基于注解的安全认证是通过在相关的方法上进行安全注解标记来实现的。 2.1 开启全局方法安全 我们可以在任何 @Configuration实例上使用 @EnableGlobalMethodSecurity 注解来启用全局方法安全注解功能。该注解提供了三种不同的机制来实现同一种功能,所以我们单独开一章进行探讨。 3. @EnableGlobalMethodSecurity 注解 @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ GlobalMethodSecuritySelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableGlobalMethodSecurity {​     /**     * 基于表达式进行方法访问控制     */     boolean […]

Spring Security内置Filter

1. 前言 转自 我们可以使用 Spring Security 实现了各种登录聚合的场面。其中我们是通过在 UsernamePasswordAuthenticationFilter 之前一个自定义的过滤器实现的。我怎么知道自定义过滤器要加在 UsernamePasswordAuthenticationFilter 之前。我在这个系列开篇说了 Spring Security 权限控制的一个核心关键就是 过滤器链 ,这些过滤器如下图进行过滤传递,甚至比这个更复杂!这只是一个最小单元。 Spring Security 内置了一些过滤器,他们各有各的本事。如果你掌握了这些过滤器,很多实际开发中的需求和问题都很容易解决。今天我们来见识一下这些内置的过滤器。 2. 内置过滤器初始化 在 Spring Security 初始化核心过滤器时 HttpSecurity 会通过将 Spring Security 内置的一些过滤器以 FilterComparator 提供的规则进行比较按照比较结果进行排序注册。 2.1 排序规则 FilterComparator 维护了一个顺序的注册表 filterToOrder 。 这些就是所有内置的过滤器。 他们是通过下面的方法获取自己的序号: 通过过滤器的类全限定名从注册表 filterToOrder 中获取自己的序号,如果没有直接获取到序号通过递归获取父类在注册表中的序号作为自己的序号,序号越小优先级越高。上面的过滤器并非全部会被初始化。有的需要额外引入一些功能包,有的看 HttpSecurity 的配置情况。 我们一般禁用 CSRF 功能,就意味着 CsrfFilter 不会被注册。 3. 内置过滤器讲解 接下来我们就对这些内置过滤器进行一个系统的认识。我们将按照默认顺序进行讲解。 3.1 ChannelProcessingFilter […]

Spring MVC 路径Uri中的 Ant 风格

1. 前言 我们经常在读到一些文章会遇到uri 支持 Ant 风格 ,而且这个东西在 Spring MVC 和 Spring Security 中经常被提及。这到底是什么呢?今天我们来学习了解一下。这对我们学习 Spring MVC 和 Spring Security 十分必要。 2. Ant 风格 说白了 Ant 风格就是一种路径匹配表达式。主要用来对uri的匹配。其实跟正则表达式作用是一样的,只不过正则表达式适用面更加宽泛,Ant仅仅用于路径匹配。 3. Ant 通配符 Ant 中的通配符有三种: ? 匹配任何单字符(除过操作系统默认的文件分隔符) * 匹配0或者任意数量的 字符 ** 匹配0或者更多的 目录这里注意了单个* 是在一个目录内进行匹配。 而** 是可以匹配多个目录。 {spring:[a-z]+} 将正则表达式[a-z]+匹配到的值,赋值给名为 spring 的路径变量. (PS:必须是完全匹配才行,在SpringMVC中只有完全匹配才会进入controller层的方法) 3.1 Ant 通配符示例 通配符 示例 说明 ? /ant/p?ttern 匹配项目根路径下 […]

lWoHvYe 无悔,专一