jboss里如何将公共的jar从war里抽出来

工程结构

-ear
 -web1.war
   -WEB-INF
    -lib
      -common.jar // 公共的jar包
 -web2.war
     -WEB-INF
    -lib
      -common.jar // 公共的jar包

有1个ear包里有2个war,war都含有共同的common.jar

问题

这样部署到 jboss里没问题。但由于项目需要hotfix–即而当遇到测试测出不能进行下去的bug时候,就需要将fix好的class文件手工替换,假设现在遇到的class在common.jar 里,替换的时候就需要替换2个地方。为什么不重新部署一个,因为那样太耗时了。

解决过程记录

按之前EAR工程在jboss(wildfly)里的结构以及class loading关系
里的方法
把common作为一个jar后在application.xml里加上

<module>
	<ejb>common.jar</ejb>
</module> 

然后在web1 和web2里将common.jar去掉

[org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.ClassCastException: com.aci.ws.idealx.resolver.EmployeeValueResolver cannot be cast to org.springframework.web.method.support.HandlerMethodArgumentResolver
Caused by: java.lang.ClassCastException: com.xxx.resolver.EmployeeValueResolver cannot be cast to org.springframework.web.method.support.HandlerMethodArgumentResolver"}

由于web1.war里引用了commn.jar里的EmployeeValueResolver,

public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {	
     argumentResolvers.add(new EmployeeValueResolver());   
	}

EmployeeValueResolver的代码

public class EmployeeValueResolver 
   implements HandlerMethodArgumentResolver {...}

HandlerMethodArgumentResolver 是spring-web-5.1.2.release.jar里的类。
而spring-web-5.1.2.release.jar在web工程里也有引用

整个项目结构如下:

-ear
 -common.jar //这里EmployeeValueResolver 找到lib下HandlerMethodArgumentResolver 
 -web1.war
   -WEB-INF
    -lib
      -spring-web-5.1.2.release.jar
 -web2.war
     -WEB-INF
    -lib
      -spring-web-5.1.2.release.jar
  -3rdparty
    spring-web-5.1.3.release.jar    

为什么报class cast出错?

由于之前有经验,虽然类命一样,可是类加载器不一样,也会认定为不同的2个class。所以猜测是由于类加载器不一致引起的。

那就进行调用的地方断点跟踪

public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {	
     //这里断点
     argumentResolvers.add(new EmployeeValueResolver());   
	}

断点后,打印出
new EmployeeValueResolver().getClass().getInterfaces()[0].getClassLoader()
结果是

ModuleClassLoader for Module
“deployment.epj2ee.ear.3rdparty/spring-web-5.1.3.RELEASE.jar:main”
from Service Module Loader

再打印
HandlerMethodArgumentResolver.class.getClassLoader()
结果是

ModuleClassLoader for Module
“deployment.epj2ee.ear.web1.war:main” from Service Module
Loader

很明显2个类加载器不一致,这就是造成报class cast的原因。

为什么原来放在war的lib目录下没问题呢?

可以肯定是加载器一致。根据wildfly guide 文档里class加载的层次级别从高到低如下

  • System Dependencies
  • User Dependencies - These are dependencies that are added through jboss-deployment-structure.xml or through the Dependencies: manifest entry.
  • Local Resource - Class files packaged up inside the deployment itself, e.g. class files from WEB-INF/classes or WEB-INF/lib of a war.
  • Inter deployment dependencies - These are dependencies on other deployments in an ear deployment. This can include classes in an ear’s lib directory, or classes defined in other ejb jars

按上面的理论,原来的 common.jar放在war的lib 下,在第3级别,web1加载器(deployment.epj2ee.ear.web1.war:main) 在 加载EmployeeValueResolver(new EmployeeValueResolver())的时候发现在本地lib里找到,那么类加载器就是web1的,并且去加载HandlerMethodArgumentResolver 也可以从本地找到。

那现在问题是 为什么HandlerMethodArgumentResolver在2个地方都有啊?为什么能就是从web1.war里找到呢?
这得从jboss 的类加载器找出答案

public class ModuleClassLoader extends ConcurrentClassLoader {
  protected final Class<?> findClass(String className, boolean exportsOnly, final boolean resolve) throws ClassNotFoundException {
    // Check if we have already loaded it..
    //private native final Class<?> findLoadedClass0(String name);
    //现在通过native去检查是否这个className已经加载了。即使其他的module
    //的类加载器已经加载过了,但由于类加载器不同,这里也是返回null
    Class<?> loadedClass = findLoadedClass(className);
    if (loadedClass != null) {
        if (resolve) {
            resolveClass(loadedClass);
        }
        return loadedClass;
    } 
    ...
    final Module module = this.module;
    log.trace("Finding class %s from %s", className, module);
    //找不到的话就去当前module里找
    //也就是说当类找不到的时候,就从本地的module里先找
    //那找不到怎么办?找不到了自然就从上面的优先级去找了
    final Class<?> clazz = module.loadModuleClass(className, resolve);
    if (clazz != null) {
        return clazz;
    }
    log.trace("Class %s not found from %s", className, module);

    throw new ClassNotFoundException(getClassNotFoundExceptionMessage(className, module));
  }
}
Class<?> loadModuleClass(final String className, final boolean resolve) throws ClassNotFoundException {
    //先从当前的路径里去找
    for (String s : systemPackages) {
        if (className.startsWith(s)) {
           //这个moduleClassLoader是当前module的class loader
            return moduleClassLoader.loadClass(className, resolve);
        }
    }
    //如果找不到,就从其它地方去找,这里的其它地方就是从上面的加载优先等级找起
    final String path = pathOfClass(className);
    final Map<String, List<LocalLoader>> paths = getPathsUnchecked();
    final List<LocalLoader> loaders = paths.get(path);
    if (loaders != null) {
        Class<?> clazz;
        for (LocalLoader loader : loaders) {
            clazz = loader.loadClassLocal(className, resolve);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    final LocalLoader fallbackLoader = this.fallbackLoader;
    if (fallbackLoader != null) {
        return fallbackLoader.loadClassLocal(className, resolve);
    }
    return null;
}

现在将common.jar 不再放在第3级别,而是放到第2级别。 根据上面的分析就很容易知道本module找不到class后就只能去其它的module里找。那么类加载器自然就不同了。而都放在war下,那类加载器自始至终都相等。这也是文档里的
在这里插入图片描述

问题还没解决,怎么办?

从通过文档里看到有个参数是 ,具体见英文注释解释。就是把local的级别变低,如下

这样又会出错

‘org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration’:
BeanPostProcessor before instantiation of bean failed; nested
exception is java.lang.IllegalArgumentException: Class
‘com.xxx.aop.DesDecryptAspect’ is not an @AspectJ aspect

原因很好解释,会去判断class是否有 org.aspectj.lang.annotation.Aspect

public boolean isAspect() {
   return clazz.getAnnotation(Aspect.class) != null;
}

而工程下有多个jar
在这里插入图片描述

local-last value=“true” 即有同名的类在不同的路径戏啊,放弃从自己的路径去找,让自己的级别最低。这样肯定找到的是什么莫名其妙的jar里,肯定就出错了。

最终的解决方案

看下面的英文注释,

  1. 所有的在 deployment下使用的标签都能被sub-deployment使用上,也就是说
    resource-root也能用。

  2. resource-root的解释-相当于把resource-root里的东西合并到lib里。

那不就相当于在sub-deployment里如果加上resource-root,就相当于把my-library.jar放到了war的lib下,这样classloader就都统一了。

<deployment>
  <!-- These add additional classes to the module.
   In this case it is the same as including the jar
    in the EAR's lib directory -->
    <resources>
      <resource-root path="my-library.jar" />
    </resources>
  </deployment>
  <sub-deployment name="myapp.war">
    <!-- This corresponds to the module for a web deployment -->
    <!-- it can use all the same tags as the <deployment> entry above -->
  </sub-deployment>  

因此最后的解决方案:

 <sub-deployment name="web1.war">
     <resources>
        <resource-root path="common.jar" />
     </resources>
  </sub-deployment>  

总计

这篇啰啰嗦嗦的记录了一堆,而最后解决的方案其实很简单。但我认为更重要的解决问题的过程,遇到问题就去baidu,google也许很容易能找到答案,这样找的答案会失去一个重要的思考的过程。这里的记录其实就是我的一个探索的过程,总结来说就是 通过阅读官方文档,猜测,做实验印证,结合通过源码的阅读来印证的一个解决过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值