为什么EJB bean方法是包私有的时候无法被检测到?

5
这让我感到困惑。我创建了一个基于JavaEE7,在Wildfly 10.1上运行的极简JAX-RS应用程序。
@ApplicationPath("")
public class JAXRSConfiguration extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        return new HashSet<Class<?>>(Arrays.asList(Resource.class));
    }
}

该资源注入一个虚拟的无状态bean:
@Path("")
public class Resource {

    @Inject
    Manager manager;

    @GET
    @Path("/go")
    public void go() {
        manager.call();
    }
}

这是一个Bean:
@Stateless
public class Manager {

    @PostConstruct
    private void init() {
        System.out.println("POST CONSTRUCT ");
    }

    void call() {
        System.out.println("called ");
    }
}

使用浏览器执行GET会导致以下错误:
org.jboss.resteasy.spi.UnhandledException: org.jboss.weld.exceptions.WeldException: Class org.jboss.weld.util.reflection.Reflections can not access a member of class com.a.b.Manager with modifiers ""

可以发布完整的堆栈跟踪,但所有的 Caused By 消息都是相同的。

我搜索了这个错误,发现当注入的 bean 不是公共的时候会发生这种情况,但我的是公共的。我决定尝试去掉 public 看看会有什么问题,结果……它起作用了。Bean 被注入了,任何可能有的注入也被注入了,@PostConstruct 方法被调用并打印出所有信息。

这与 EJB stateless class has to be public 吗? 完全相反。这是怎么回事?

更新

Oliv37 提醒我进行一些测试,以下是结果:

  • 如果 callpackage,那么只有在 Managerpackage 时才有效。
  • 如果 callpublic,则无论如何都有效。
  • 如果 callfinal,则只有在 Managerpackage 时才会调用 @PostConstruct 方法。

现在的问题是:为什么方法需要是 public 才能让 CDI 检测到,以及为什么将其设置为 final 会导致如果类是 public,则不调用 @PostConstruct 方法?

更新2

完整的堆栈跟踪:

org.jboss.resteasy.spi.UnhandledException: org.jboss.weld.exceptions.WeldException: Class org.jboss.weld.util.reflection.Reflections can not access a member of class com.a.b.Manager with modifiers ""
    at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:77)
    at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:220)
    at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:175)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:418)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:209)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:221)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.undertow.servlet.api.LegacyThreadSetupActionWrapper$1.call(LegacyThreadSetupActionWrapper.java:44)
    at io.undertow.servlet.api.LegacyThreadSetupActionWrapper$1.call(LegacyThreadSetupActionWrapper.java:44)
    at io.undertow.servlet.api.LegacyThreadSetupActionWrapper$1.call(LegacyThreadSetupActionWrapper.java:44)
    at io.undertow.servlet.api.LegacyThreadSetupActionWrapper$1.call(LegacyThreadSetupActionWrapper.java:44)
    at io.undertow.servlet.api.LegacyThreadSetupActionWrapper$1.call(LegacyThreadSetupActionWrapper.java:44)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:202)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:805)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.jboss.weld.exceptions.WeldException: Class org.jboss.weld.util.reflection.Reflections can not access a member of class com.a.b.Manager with modifiers ""
    at org.jboss.weld.util.reflection.Reflections.invokeAndUnwrap(Reflections.java:437)
    at org.jboss.weld.bean.proxy.EnterpriseBeanProxyMethodHandler.invoke(EnterpriseBeanProxyMethodHandler.java:128)
    at org.jboss.weld.bean.proxy.EnterpriseTargetBeanInstance.invoke(EnterpriseTargetBeanInstance.java:56)
    at org.jboss.weld.bean.proxy.InjectionPointPropagatingEnterpriseTargetBeanInstance.invoke(InjectionPointPropagatingEnterpriseTargetBeanInstance.java:67)
    at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:100)
    at com.a.b.Manager$Proxy$_$$_Weld$EnterpriseProxy$.call(Unknown Source)
    at com.airhacks.boundary.Resource.go(Resource.java:16)
    at com.airhacks.boundary.Resource$Proxy$_$$_WeldClientProxy.go(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:295)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:249)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:236)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:402)
    ... 42 more
Caused by: java.lang.IllegalAccessException: Class org.jboss.weld.util.reflection.Reflections can not access a member of class com.a.b.Manager with modifiers ""
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Method.invoke(Method.java:491)
    at org.jboss.weld.util.reflection.Reflections.invokeAndUnwrap(Reflections.java:433)
    ... 58 more

1
你尝试在Manager类中的call方法上添加public修饰符了吗? - Olivier Boissé
1
方法的默认可见性是“包级私有”,那么ResourceManager类是否在同一个包中? - Olivier Boissé
@oliv37 我也尝试将该方法设为final,但只要该类是public的,就会导致post construct方法不被调用。我正在将我的发现更新到问题中。 - Mark
只是出于好奇,你能否在某个地方分享整个堆栈跟踪?Gist或其他什么东西... - Siliarus
@Siliarus 可以在这里发布。 - Mark
显示剩余4条评论
1个回答

10
简短的答案是 - 将你的call方法设为public 更长的答案是这样的 - 问题在于你的call方法是包保护的。根据EJB规范,第会话Bean的无接口视图章节(引用自3.1 EJB规范):

只有bean类(和任何超类)的公共方法可以通过无接口视图调用。通过无接口视图引用尝试调用具有其他访问修饰符的方法必须导致javax.ejb.EJBException

使用非公共修饰符违反了规范,并且它不抛出EJBException的事实可能是WFLY中EJB实现的一个忽略或特性。无论哪种方式,你都处于灰色未指定的领域。
现在说到 Weld - 它试图使用反射来访问该方法并调用它,故意在调用之前不使其可访问。这就是明显的问题所在,因为你无法访问那个方法而不绕过它(使用 Method.setAccessible())。
至于你关于 final 方法和 @PostConstruct 不被调用的问题 - 你正在违反另一个 EJB 规则。这个规则在 4.9.8 Session Bean’s No-Interface View 中,其中一个破折号说:

除了 java.lang.Object 之外,bean 类和任何超类的私有方法都可以声明为 final

我不能说明为什么没有例外,但这是你头疼的原因。至于为什么存在这个规则 - 嗯,正如我在评论中所述,Weld 需要创建代理,如果方法不能被重写,就不可能编织 @PostConstruct 调用并拦截它。
希望这回答了你的问题。

还创建了WFLY问题来检查这种行为 - 结果它会抛出正确的异常(调用非公共EJB方法 - 请参见我在该问题中的评论),但Weld更快并且在IllegalAccess反射问题上失败 - 这应该在Weld方面得到改进。 - Siliarus
最终修饰符会导致不调用后构造函数吗? - Mark
哎呀,说实话,这完全是一个不同的问题。我猜问题可能出在Weld创建bean的代理对象上 - 但根据CDI规范,它不能使用final方法。但是真的,这个问题应该有一个单独的“线程”,您可以在其中指定具体的部署场景,以确定它失败的原因。在这里,所有选项都变得混乱了。 - Siliarus
我也会调查这个问题,并稍后在此处报告,但正如我所说,那将是不同的问题(或者是误用,还不确定)。 - Siliarus
@Mark,你的第二个问题是因为你违反了另一个EJB规范4.9.8 Session Bean's No-Interface View - 除非是私有的,否则你不能在那里使用final方法(完整引用在我的答案中)。 - Siliarus
好的,谢谢。虽然如果类不是公共的,有一个final方法似乎没有问题,但是不知何故仍会创建代理。 - Mark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接