命令行运行jar包报Hibernate Search SearchFactory未初始化异常
使用Hibernate Search 5.9.3版本时,在Eclipse中运行代码可正常执行、索引创建流程无报错,但通过命令行运行打包生成的jar文件时抛出异常,异常截图如下:
涉及的代码片段:
factory = configuration.buildSessionFactory(serviceRegistry); Session session = factory.openSession(); try { Thread.sleep(10); } catch (InterruptedException e1) { e1.printStackTrace(); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { System.out.println("1:Search.getFullTextEntityManager(factory.openSession())"); fullTextEntityManager = Search.getFullTextEntityManager(session); session.getTransaction().begin(); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("2:fullTextEntityManager.createIndexer().startAndWait();"); try { fullTextEntityManager.createIndexer().startAndWait(); } catch (InterruptedException ex) { ex.printStackTrace(); } catch (Exception ex) { System.out.println("3:fullTextEntityManager.createIndexer().startAndWait();"); try { fullTextEntityManager.createIndexer().startAndWait(); } catch (InterruptedException e) { e.printStackTrace(); } ex.printStackTrace(); } try { session.getTransaction().commit(); } catch (Exception e) { session.getTransaction().rollback(); } } });
Eclipse运行项目时会自动将所有关联依赖加入运行时类路径,因此不会出现类找不到、服务加载失败的问题。但默认打包生成的jar仅包含项目自身的编译文件,不会引入Hibernate Search、Lucene等传递依赖,同时如果打包时未处理Java SPI配置文件,也会导致Hibernate Search无法加载内置组件,命令行运行时就会抛出异常。
另外现有代码存在线程使用隐患:在Swing事件调度线程(EDT)中执行startAndWait()这类阻塞式重IO操作,会直接导致UI卡顿,只是Eclipse环境下依赖齐全未触发类加载错误,该问题才没有显现。
- 先查看命令行抛出的完整异常栈,如果是
ClassNotFoundException、NoClassDefFoundError或者服务加载相关报错,可直接定位为类路径缺失/打包配置错误 - 解压打包生成的jar,检查内部是否包含Hibernate Search、Lucene、Hibernate Core相关的class文件,以及
META-INF/services目录下是否存在Hibernate Search对应的SPI配置文件 - 确认命令行运行时是否通过
-cp参数完整指定了所有依赖包的路径,没有遗漏必要依赖
方案1:打包时将所有依赖合并入可执行jar
如果使用Maven构建,推荐使用maven-shade-plugin打包,它会自动处理SPI配置文件的合并,避免服务加载失败,在pom.xml中添加如下配置:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>替换为你的主类全限定名</mainClass> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers> </configuration> </execution> </executions> </plugin>
配置完成后执行mvn package,生成的jar包可直接通过java -jar 包名.jar运行。
如果使用Gradle构建,在build.gradle中添加如下配置:
plugins { id 'java' } jar { manifest { attributes 'Main-Class': '替换为你的主类全限定名' } from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } // 合并SPI配置文件,排除签名文件避免报错 exclude 'META-INF/*.SF' exclude 'META-INF/*.DSA' exclude 'META-INF/*.RSA' mergeServiceFiles() }
方案2:运行时手动指定依赖类路径
如果不需要将所有依赖打入单个jar,可以把所有依赖包统一放在lib目录下,运行时通过-cp参数指定类路径:
Windows环境执行命令:
java -cp "你的项目jar包.jar;lib/*" 你的主类全限定名
Linux/macOS环境执行命令:
java -cp "你的项目jar包.jar:lib/*" 你的主类全限定名
代码优化建议
将索引构建逻辑从Swing EDT线程移到独立工作线程,避免UI阻塞,同时不要跨线程共享Session实例:
new Thread(() -> { // 工作线程单独打开Session和事务 FullTextEntityManager ftem = Search.getFullTextEntityManager(factory.openSession()); ftem.getTransaction().begin(); try { ftem.createIndexer().startAndWait(); ftem.getTransaction().commit(); } catch (Exception e) { if (ftem.getTransaction().isActive()) { ftem.getTransaction().rollback(); } e.printStackTrace(); } finally { ftem.close(); } // 索引构建完成后再切回EDT线程更新UI SwingUtilities.invokeLater(() -> { // 填写索引完成后的UI刷新逻辑 }); }).start();
内容的提问来源于stack exchange,提问作者svkvvenky

