Why is the EJB bean method not detected if it is package private?
Asked Answered
L

1

5

This one is puzzling me. I created a minimal JAX-RS application based on JavaEE7 running on Wildfly 10.1.

@ApplicationPath("")
public class JAXRSConfiguration extends Application {

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

The resource injects a single dummy stateless bean:

@Path("")
public class Resource {

    @Inject
    Manager manager;

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

This is the bean:

@Stateless
public class Manager {

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

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

Using the browser to execute the GET causes the following error:

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 ""

Can post full stack trace but all the Caused By messages are the same.

I searched for this error and it happens when the injected bean is not public, but mine is. I decided to try to remove the public to see what it will complain about and... it works. The bean is injected, any injections it might have are injected, the post construct method is called and all the prints are printed.

This is in complete contrast to Does ejb stateless class has to be public?. What's going on here?

Update

Oliv37 prompted me to do some tests, here are the findings:

  • If call is package, then it only works when the Manager is package.
  • If call is public, it works regardless.
  • If call is final, the @PostConstruct method is called only if Manager is package.

Now the questions become: why does the method need to be public for CDI detection to work and why making it final causes the post construct method to not be called if the class is public?

Update2

Full stack trace:

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
Legitimatize answered 6/10, 2017 at 9:20 Comment(9)
Just one comment about the class being not public and it works: As one comment in your linked question said: It works in your environment with your chosen container, but it isn't guaranteed to work in every EE container, because it's not defined by the spec.Disclosure
@Disclosure OK, but why doesn't it work with public if that's the spec? I understand that if i do things not in the spec it might work and might not, but if i do things in the spec they must work...Legitimatize
hum did you try to add the public modifier for the method call inside the Manager class ?Silures
@oliv37 Tried now. If the method is public then it works regardless of the class visibility. Didn't see aynthign in the specs about method visibility rules.Legitimatize
The default visibility of a method is package, so are Resource and Manager classes in the same package ?Silures
@oliv37 I also tried making the method final and that causes the post construct method to not be called as long as the class is public. I'm updating my findings into the question.Legitimatize
@oliv37 Of course, otherwise I would get a compilation error when calling call.Legitimatize
Just out of curiosity, can you share whole stack trace somewhere? Gist or something...Ibidem
@Ibidem Can even post it hereLegitimatize
I
10

The short answer is - make your call method public

The longer one goes like this - problem here is that your call method is package protected. Accordingly to EJB spec, chapter Session Bean’s No-Interface View (quoting from 3.1 EJB spec):

Only public methods of the bean class (and any super-classes) may be invoked through the no-interface view. Attempted invocations of methods with any other access modifiers via the no-interface view reference must result in a javax.ejb.EJBException.

By using non-public modifier you are violating spec and the fact that in does not throw EJBException can be either overlook in EJB impl in WFLY or a feature. One way or the other, you are in grey unspecified area.

Now as for Weld - it is trying to use reflections to access that method and invoke it, deliberately not making it accessible prior to invocation. That's where it obviously blows as you do not have access to that other method without bypassing it (with Method.setAccessible()).

As for your side-question about final method and @PostConstruct not being invoked - you are breaking yet another EJB rule. This one is in 4.9.8 Session Bean’s No-Interface View and one of the dashes there says:

Only private methods of the bean class and any superclasses except java.lang.Object may be declared final.

I cannot speak for why there is no exception, but this is the cause of your headaches. As for why this rule exists - well as I stated in comments Weld needs to create proxy and if the method cannot be overriden, it's impossible to weave in @PostConstruct calls and intercept it.

Hope this answers your queries.

Ibidem answered 9/10, 2017 at 7:40 Comment(6)
Also created WFLY issue to inspect this behaviour - turns out it would throw the correct exception (calling non-public EJB method - see my comment in that issue) but Weld is faster and fails on IllegalAccess reflections problems - this could (should) be improved on Weld side.Ibidem
What about the final modifier causing not calling the post construct?Legitimatize
Uf, that's wholly different question compared to what we are sorting out here to be honest. I suppose the problem there will be with Weld creating proxy object of the bean - but according to CDI spec that doesn't work with final methods. But really, that issue would deserve a separate "thread" where you specify the concrete deployment scenario where it fails. Here it's getting tangled with all the options.Ibidem
I'll look into that as well and can report it here later on, but as I said, that's gonna be different issue (well, or misuse, dunno yet).Ibidem
@Legitimatize Expanded answer, your second problem is there because you are breaking another EJB spec rule under ` 4.9.8 Session Bean’s No-Interface View` - you cannot have final method there unless it's private (full quotation in my answer).Ibidem
Alright, thanks. Although it's weird that there is no problem with having a final method if the class is not public. Somehow a proxy is created anyway.Legitimatize

© 2022 - 2024 — McMap. All rights reserved.