关于spring 扫描不到jar中class文件的原因分析及解决

 更新时间:2021年08月23日 11:00:07   作者:xlxxcc  
这篇文章主要介绍了关于spring 扫描不到jar中class文件的原因分析及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

spring 扫描不到jar中class文件的原因及解决

背景

公司一web项目使用的是spring mvc开发的,老员工们写了一个缓存service,即EhcacheService , 该缓存service在web中使用了spring 的@Scheduled 启动加载缓存,代码如下:

applicationContext.xml

<context:component-scan base-package="cn.com.service" />

EhcacheService .java

// 启动加载缓存, 以上一次执行完为准
@Scheduled(fixedDelay = 365 * 24 * 60 * 60 * 1000)   
public void initEhcache() {
    logger.debug("++++++++++++++++++++缓存加载开始++++++++++++++++++++");
    long start = System.currentTimeMillis();
    try {
        this.ehcacheService.loadCache();
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    long end = System.currentTimeMillis();
    logger.debug("++++++++++++++++++++缓存加载结束,耗时:" + (end - start) + "++++++++++++++++++++");
}

然而最近同步数据,需要用到EhcacheService , 本人也懒得重写里面的方法,便想着使用ClassPathXmlApplicationContext 或者FileSystemXmlApplicationContext 或者GenericXmlApplicationContext来加载spring配置,然后打包成jar包,丢到linux上,使用java -jar my.jar。代码如下:

test.java

// 程序入口
public static void main(String[] args) throws Exception {
    // 加载spring配置
    GenericXmlApplicationContext context = new GenericXmlApplicationContext();
    context.setValidating(false);
    context.load("classpath*:spring-*.xml");
    context.refresh();
    // ApplicationContext ctx = new FileSystemXmlApplicationContext("spring-*.xml");
    // ApplicationContext context = new ClassPathXmlApplicationContext("spring-*.xml");
    System.out.println("bean的数据量" + context.getBeanDefinitionNames().length);
    EhcacheService ehcacheService = (EhcacheService) context.getBean("ehcacheService");
}

我们先看一下在Eclipse中运行情况

这里写图片描述

我们再看看打包成Runable jar File后,使用 java -jar my.jar的运行情况

这里写图片描述

根据图片中的错误,我们可以看到,spring-*.xml是成功被加载了,然而找不到bean, 很明显,它存在一种可能,那就是bean的class文件没有被spring扫描到。

那么为什么会出现这种情况呢?经过我多方面的查证,spring 扫描bean文件是通过Thread.currentThread().getContextClassLoader().getResource(packageName)加载的。那么我们分析一下ContextClassLoader资源加载机制。

举例说明:我们有这样的一个: cn.com.Test, 类加载器首先会把这个包名转化成文件夹的形式 cn/com, 然后到这个文件夹里去加载Test.class。

然后,当你打包成Runable jar File时,jar的包和文件系统中的包便不是一个概念了,它不能将cn.com转换成cn/com文件夹方式去解读, 类加载转换成cn/com去加载类的时候,便会报出classNotFoundException异常

下面我们使用如下代码验证一下这个过程:

// 项目中jar包所在物理路径  
String jarName = "C:\\Users\\Administrator\\Desktop\\my.jar";  
// 项目中war包所在物理路径
//String jarName = "C:\\Users\\Administrator\\Desktop\\my.war";  
 JarFile jarFile = new JarFile(jarName);  
 Enumeration<JarEntry> entrys = jarFile.entries();  
 while (entrys.hasMoreElements()) {  
     JarEntry jarEntry = entrys.nextElement();  
     System.out.println(jarEntry.getName());  
 }     

打印结果如下:

META-INF/MANIFEST.MF cn/com/server/action/JobAction.class cn/com/server/annotation/DataDigestAnnotation.class cn/com/server/dao/EhcacheDao.class

然后我们打包成war包再看看他的war包物理路径,我们可以看到打印结果如下:

META-INF/MANIFEST.MF META-INF/ WEB-INF/classes/ WEB-INF/classes/cn/ WEB-INF/classes/cn/com/ WEB-INF/classes/cn/com/ WEB-INF/classes/cn/com/server/ WEB-INF/classes/cn/com/server/action/ WEB-INF/classes/cn/com/server/action/JobAction.class WEB-INF/classes/cn/com/server/addrsrv/ WEB-INF/classes/cn/com/server/addrsrv/GeoAddrSrv.class …..

我们可以看到war类的文件目录和jar的文件目录明显不同,这样就能解释上面我所描述的问题。

Q: 那么我们怎么解决spring 扫描不到jar中class这个问题呢?

A: 有一种做法,就是打jar包的时候,打成JAR file, 然后选择 add directory entries, 如图:

这里写图片描述

然后这种打包方式,虽然能解决spring 扫描不到jar中class文件问题,但是打并不是我们想要的,我们想要的是一个可以执行jar,也就是Runable JAR FILE。

Q: 那么我们怎么打包成Runable JAR FILE,并且解决spring 扫描不到jar中class的问题?

A:

1、首先使用Eclipse打包,打包成JAR file。

2、上传到Linux, 解压my.jar

unzip my.jar -d myapp

3、进入 myapp文件夹, 使用以下命令:

java -Djava.ext.dirs=WebContent/WEB-INF/lib cn.com.test

大功告成

其他技巧:除了上诉使用代码方式查看jar包物理路径,我们还可以是 jar tr my.jar来查看。如图:

这里写图片描述

@ComponentScan注解进行扫描的几种方式

方式一:扫描包

返回是String的数组,所以可是多个包路径!也可是一个包路径!完整写法是

  • 单个:@ComponentScan(basePackages = “xxx”)
  • 多个:@ComponentScan(basePackages = {“xxx”,“aaa”,“…”})

注意:可以省略“basePackages =”

方式二:扫描类

同样返回是String的数组,所以可以是有多个类名! 也可是一个类名!

  • 单个:@ComponentScan(basePackageClasses = “”)
  • 多个:@ComponentScan(basePackageClasses = {“xxx”,“aaa”,“…”})

注意:不可以省略“basePackageClasses =”

测试:

方式三:扫描包(通配式:开发常用)

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java中将 int[] 数组 转换为 List分享

    Java中将 int[] 数组 转换为 List分享

    这篇文章主要介绍了Java中将 int[] 数组 转换为 List分享的相关资料,需要的朋友可以参考下
    2022-12-12
  • rabbitmq学习系列教程之消息应答(autoAck)、队列持久化(durable)及消息持久化

    rabbitmq学习系列教程之消息应答(autoAck)、队列持久化(durable)及消息持久化

    这篇文章主要介绍了rabbitmq学习系列教程之消息应答(autoAck)、队列持久化(durable)及消息持久化,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • Spring Boot 日志配置方法(超详细)

    Spring Boot 日志配置方法(超详细)

    默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。下面通过本文给大家介绍Spring Boot 日志配置方法详解,感兴趣的朋友参考下吧
    2017-07-07
  • SpringBoot读取资源目录中JSON文件的方法实例

    SpringBoot读取资源目录中JSON文件的方法实例

    最近做项目遇到需要将json类型的配置文件引用到项目中,已经将读取json文件的方法封装成工具类,下面这篇文章主要给大家介绍了关于SpringBoot读取资源目录中JSON文件的相关资料,需要的朋友可以参考下
    2023-04-04
  • Java中的MapStruct用法详解

    Java中的MapStruct用法详解

    这篇文章主要介绍了Java中的MapStruct用法详解,MapStuct的使用非常简单,把对应的jar包引入即可,本文通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • java this 用法详解及简单实例

    java this 用法详解及简单实例

    这篇文章主要介绍了java this 用法详解及简单实例的相关资料,需要的朋友可以参考下
    2017-03-03
  • Spring Boot 集成Dubbo框架实例

    Spring Boot 集成Dubbo框架实例

    本篇文章主要介绍了Spring Boot 集成Dubbo框架实例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • Kafka 日志存储实现过程

    Kafka 日志存储实现过程

    这篇文章主要为大家介绍了Kafka 日志存储的实现过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Maven指定JDK版本的实现

    Maven指定JDK版本的实现

    本文主要介绍了Maven指定JDK版本的实现,主要有两种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-09-09
  • Ireport的安装与使用教程

    Ireport的安装与使用教程

    这篇文章主要介绍了Ireport的安装与使用教程,需要的朋友可以参考下
    2021-10-10

最新评论