Quarkus: An application scoped bean instantiated twice

Created on 29 Apr 2020  Â·  12Comments  Â·  Source: quarkusio/quarkus

Describe the bug
I want to create a bean only once during the application lifetime.
I have the next configuration

@ApplicationScoped
public class RedisConfiguration {

@Produces
@ApplicationScoped
    public JedisPool JedisPool() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(poolSize);
        poolConfig.setMaxIdle(poolSize);
        poolConfig.setJmxEnabled(false); // GraalVM doesn't support JMX

        return new JedisPool(poolConfig, host, port, timeout, null, database, ssl);
    }
}

and inject JedisPool somewhere else.

Expected behavior
JedisPool constructor is called only once. So, I can guarantee that nobody instantiates this class except this call. Weld (at least weld-se) provides exactly the same behavior.

Actual behavior
JedisPool constructor is called twice. The first call with default no-args constructor, and the second one initiated by the method above.

Environment (please complete the following information):

  • Output of uname -a or ver: Darwin localhost 19.0.0 Darwin Kernel Version 19.0.0: Thu Oct 17 16:17:15 PDT 2019; root:xnu-6153.41.3~29/RELEASE_X86_64 x86_64
  • Output of java -version: java version "1.8.0_231"
  • Quarkus version or git rev: 1.4.1.Final
  • Build tool (ie. output of mvnw --version or gradlew --version): Apache Maven 3.6.2

Additional context
Why it is important: in this particular case I call poolConfig.setJmxEnabled(false); to make it work in the native mode because GraalVM is having some troubles with JMX. By default, JMX flag is enabled and the application is failing.
@Singleton annotation solves this issue, but in this case, I can't mock this bean using QuarkusMock

kinbug triagwontfix

Most helpful comment

@geoand is right. For a normal scoped bean a client proxy is always created when obtaining an injectable reference. This behavior is defined by the CDI spec.

AFAIK in weld-se (or at least in some versions) the relaxed construction is enabled by default. And it's implemented using sun.misc.Unsafe#allocateInstance() which allows you to create an instance without calling the constructor. However, Unsafe is non-portable and definitely not a good way to go. In Quarkus, we generate a no-args constructor if missing and needed (normal scoped bean).

Do you control the JedisPool class? I suppose you don't because you're using a producer method. In that case I'm afraid we can't do much about it (except for using non-normal scope).

All 12 comments

I think this is expected due to the creation of a proxy - @mkouba or @manovotn can confirm or deny.

You can use @Singleton if you want to not create a proxy at all.

@geoand is right. For a normal scoped bean a client proxy is always created when obtaining an injectable reference. This behavior is defined by the CDI spec.

AFAIK in weld-se (or at least in some versions) the relaxed construction is enabled by default. And it's implemented using sun.misc.Unsafe#allocateInstance() which allows you to create an instance without calling the constructor. However, Unsafe is non-portable and definitely not a good way to go. In Quarkus, we generate a no-args constructor if missing and needed (normal scoped bean).

Do you control the JedisPool class? I suppose you don't because you're using a producer method. In that case I'm afraid we can't do much about it (except for using non-normal scope).

I agree. This is standard behavior and Weld w/o relaxed construction should give you the same result.

Some of the above suggestions should do the trick.

You can probably just use @Singleton instead of @ApplicationScoped.

You can probably just use @singleton instead of @ApplicationScoped.

@stuartwdouglas The problem is that @miron4dev would like to use QuarkusMock which only works with normal scopes and proxies ;-).

And there's absolutely no chance we could implement mocking for singletons?

And there's absolutely no chance we could implement mocking for singletons?

Not with the current test app lifecycle (one app for all tests).

You could do it via a bytecode transformer, basically just add that same client proxy mocking code into all @Singleton beans.

You could do it via a bytecode transformer, basically just add that same client proxy mocking code into all @singleton beans.

Like modifying all the methods of a bean class? And if a mock exists then delegate to this mock instead of executing the method body? That could work. On the other hand, such a change looks quite "invasive".

It's only for tests

On Mon, 4 May 2020, 7:29 pm Martin Kouba, notifications@github.com wrote:

You could do it via a bytecode transformer, basically just add that same
client proxy mocking code into all @singleton
https://github.com/singleton beans.

Like modifying all the methods of a bean class? And if a mock exists then
delegate to this mock instead of executing the method body? That could
work. On the other hand, such a change looks quite "invasive".

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/quarkusio/quarkus/issues/8960#issuecomment-623357754,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AACQG6YLGP7MIGKHBATDQ43RP2DHZANCNFSM4MTZD5EA
.

That should work. I am +1.

If we are worried it's to invasive, perhaps we would have a flag that the user needs to switch?

There is only a one case I can think of were mockito would still complain:
When the implementation of a bean is a dynamic proxy, we still won't be able to pass it to mockito. That shouldn't be too much of a problem.

It's only for tests

Yes, I know. My point is that the bigger the difference between the test and the prod environment is the bigger chance for unpredictable bugs. But that's a general problem with mocking ;-)

implementation of a bean is a dynamic proxy

Ehm, what is it?

Was this page helpful?
0 / 5 - 0 ratings