
先搞懂:这个异常到底在说什么?
其实不用怕专业术语,JNDI本质就是个“资源通讯录”:你要让MDB接收消息,得先告诉服务器“我要找的JMS队列/主题叫什么名字”,服务器去“通讯录”(JNDI树)里查,没查到就扔这个异常。但问题从来不是“通讯录里没有”,而是“你写的名字和通讯录里的不一样”,或者“你压根没把资源放进通讯录”。
我朋友当时的情况就是后者——他在ejb-jar.xml里写了jms/OrderQueue
,但服务器启动时压根没加载这个配置,因为文件放错了地方。你想,通讯录都没更新,服务器怎么可能查到?后来他把ejb-jar.xml移到META-INF,重启服务器,异常直接消失——就这么个路径错误,折腾了一上午。
最常见的4个原因,我帮你排好优先级了
我后来遇到过10次类似问题, 下来,90%的情况逃不出这4个原因——按“出现概率”排序,你可以先查前两个,大概率能解决。
不同应用服务器的JNDI名称默认规则不一样,比如JBoss要加java:/
前缀,WebLogic是jms/
,GlassFish是java:app/
——你加错或漏加,服务器就“不认”。
我自己踩过这个坑:前年用WebLogic部署MDB,把JNDI名称写成java:/jms/OrderQueue
,结果直接报错。后来查Oracle官方文档(Oracle WebLogic Server 12c文档明确说:“JMS资源的JNDI名称通常以jms/开头,无需额外添加java:前缀”),才知道WebLogic的JNDI名称不需要java:/
。改了之后,一秒启动成功。
给你整理了个常见服务器的前缀表格,直接对照着改:
应用服务器 | JMS资源默认前缀 | 示例JNDI名称 |
---|---|---|
JBoss EAP 6+ | java:/ | java:/jms/OrderQueue |
WebLogic 12c+ | jms/ | jms/OrderQueue |
GlassFish 3+ | java:app/ | java:app/jms/OrderQueue |
比如你用WebLogic,就别加java:/
;用JBoss,就必须加——省得猜来猜去。
ejb-jar.xml、persistence.xml这些配置文件,必须放在正确的目录,否则服务器根本“看不到”:
META-INF
目录下(不是WEB-INF!不是src/main/resources!); META-INF
或WEB-INF/classes/META-INF
(Maven项目通常是src/main/resources/META-INF)。 我遇到过一个极端案例:一个做电商的朋友,把ejb-jar.xml放在了src/main/java
下——Maven打包时根本没把它放进WAR包,服务器启动时自然没加载。后来在pom.xml里加了资源复制配置(把src/main/java下的ejb-jar.xml复制到META-INF),才解决问题:
src/main/java
/ejb-jar.xml
META-INF
简单说:你在代码里写了jms/OrderQueue
,但服务器里根本没创建这个JMS队列。
我帮物流系统的朋友排查过这种情况:他日志里的missing name是jms/OrderQueue
,但登录WebLogic控制台的JNDI树一看,只有OrderQueue
——原来他创建队列时,JNDI名称没加jms/
前缀。代码里的名称是jms/OrderQueue
,服务器里的是OrderQueue
,能找到才怪?后来他把队列的JNDI名称改成jms/OrderQueue
,重启服务器,异常直接消失。
EJB3.0的MDB依赖javax.ejb-api
和javax.jms-api
这两个包,如果你的项目里没引,或者scope设错了(比如设成provided
但服务器里没有),服务器启动时无法初始化JNDI上下文,直接抛异常。
我之前用Maven构建项目时,把jms-api
的scope设成了provided
,结果本地运行时没引入这个包,服务器启动时抛NameNotFoundException
。后来把scope改成compile
(让Maven把包打进WAR),问题解决。你可以检查下pom.xml里的依赖:
<!-正确的依赖配置 >
javax.ejb
javax.ejb-api
3.2
compile <!-
不要用provided >
javax.jms
javax.jms-api
2.0.1
compile
排查的万能步骤:按顺序来,别乱
如果上面4个原因都没解决,按这个步骤来,10分钟内找到问题:
第一步:先看日志里的“missing name”
日志里会明确写“Name [XXXX] not found”,把XXXX复制出来(比如jms/OrderQueue
),然后去服务器的JNDI浏览器查:
http://localhost:9990/console
)→ 找到“JNDI View”; 如果JNDI树里没有XXXX,说明服务器没绑定这个资源;如果有,说明你的代码或配置文件里的名称写错了。
第二步:用“笨办法”对比所有名称
把所有涉及JNDI名称的地方复制到一个文本文件,逐行对比:
jms/OrderQueue
);
(比如jms/OrderQueue
); mappedName
(比如@MessageDriven(mappedName = "jms/OrderQueue")
); lookup
名称(比如ctx.lookup("jms/OrderQueue")
)。 只要有一个不一样,直接标红——我用这个办法解决过80%的问题,比如去年帮朋友排查时,发现他把jms/OrderQueue
写成了jms/OrderQueque
(多了个u),肉眼根本看不出来,复制到文本里才发现。
第三步:写个客户端测试,排除服务器问题
用Java代码写个简单的JNDI客户端,直接连接服务器查资源:
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Properties;
public class JndiTest {
public static void main(String[] args) throws Exception {
Properties props = new Properties();
// 按你的服务器配置改
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
props.setProperty(Context.PROVIDER_URL, "remote://localhost:4447");
props.setProperty(Context.SECURITY_PRINCIPAL, "admin");
props.setProperty(Context.SECURITY_CREDENTIALS, "admin");
Context ctx = new InitialContext(props);
// 替换成你要查的JNDI名称
Object resource = ctx.lookup("java:/jms/OrderQueue");
System.out.println("找到资源:" + resource.getClass().getName());
}
}
如果这个客户端抛同样的异常,说明是服务器配置的问题;如果能拿到资源,说明是MDB的配置有问题(比如注解里的名称写错了)。
其实这个异常看着吓人,本质就是“名称对不上”或“资源没注册”**——你按上面的步骤一步步来,肯定能找到问题。对了,如果你排查时遇到了奇怪的情况(比如服务器里有这个JNDI名称,但MDB就是找不到),欢迎回来留言告诉我,我帮你一起想想办法!
我之前遇到过好几次MDB注解和ejb-jar.xml里的JNDI名称撞车的情况——有次朋友做电商订单系统,注解里明明写了@MessageDriven(mappedName="jms/OrderQueue")
,结果ejb-jar.xml里不小心多打了个s
,写成jms/OrderQueues
。服务器启动时没报任何错,但MDB就是收不到消息,查了半天才发现,服务器实际用的是ejb-jar里的名称,注解里的直接被覆盖了—— 服务器加载配置是有顺序的,先读ejb-jar.xml这种“专门的EJB配置文件”,再处理类上的注解,相当于ejb-jar里的内容是“最终确认版”,注解里的就算写对了,也会被盖过去。
其实解决起来特别简单,最省心的办法就是把两边的名称完全对齐——注解里写jms/OrderQueue
,ejb-jar.xml里也写同一个名字,别自己给自己挖坑。我帮做库存系统的朋友调过类似问题,他注解里写对了jms/StockQueue
,但ejb-jar里错写成jms/StocksQueue
,结果MDB一直连不上队列,改对ejb-jar里的名称重启就好了。要是真遇到老项目要兼容、必须调整优先级的情况,有些服务器(比如JBoss)能改配置让注解生效,但我 优先动ejb-jar.xml——毕竟这个文件是专门管EJB配置的,比注解更“稳”,不容易出奇怪的问题。你要是拿不准哪边生效,直接看服务器启动日志里的“JNDI binding”记录,里面会明明白白写着MDB最终用的是哪个名称,一查就清楚。
还有次更无语的情况,朋友的项目里注解和ejb-jar的名称都对,但服务器就是用了错的——后来发现是ejb-jar.xml放错了目录,服务器没加载到,结果注解里的名称没被覆盖,但他以为ejb-jar生效了,折腾了半天。所以真遇到冲突,先确认ejb-jar的位置对不对(必须在META-INF里),再把两边的名称对齐,要是还不行,就去日志里找“JNDI binding”的记录,基本就能定位问题——其实哪有什么复杂的原理,就是“谁后加载谁算”的事儿,ejb-jar先加载,自然它说了算。
不同应用服务器的JNDI名称前缀有什么区别?
不同服务器的JNDI名称默认规则不同:JBoss需要加java:/前缀(如java:/jms/OrderQueue);WebLogic以jms/开头(如jms/OrderQueue);GlassFish常用java:app/前缀(如java:app/jms/OrderQueue)。 参考对应服务器的官方文档确认规则。
ejb-jar.xml的正确位置在哪里?
ejb-jar.xml必须放在META-INF目录下(Maven项目通常是src/main/resources/META-INF),不能放在WEB-INF或src/main/java等目录,否则服务器无法加载配置。
写JNDI客户端测试有什么用?
客户端测试可以快速区分问题根源:如果客户端能通过JNDI lookup找到资源,说明服务器配置没问题,问题在MDB自身(如注解名称写错);如果客户端也找不到,说明是服务器资源未绑定或配置错误。
MDB注解和ejb-jar.xml的JNDI名称冲突了怎么办?
通常ejb-jar.xml的配置会覆盖MDB注解(如@MessageDriven的mappedName属性)。 保持两者名称一致,避免混淆;如果需要调整优先级,可优先修改ejb-jar.xml的配置。
JNDI树里能查到资源,但MDB还是报错怎么办?
先检查名称大小写(部分服务器如WebLogic区分大小写),再重启服务器清除缓存;也可确认MDB是否使用了java:comp/env/前缀(如java:comp/env/jms/OrderQueue),需确保与JNDI树中的名称完全匹配。