avaje-inject icon indicating copy to clipboard operation
avaje-inject copied to clipboard

Support Lazy initialization

Open DaiYuANg opened this issue 1 year ago • 10 comments

How to make Bean is lazy, Maybe we can be using Supplier and at runtime Supplier.get() to inject instance

DaiYuANg avatar Mar 08 '24 02:03 DaiYuANg

Inject is already pretty fast for wiring compared to other DI frameworks, for what purpose do you need lazy beans?

SentryMan avatar Mar 08 '24 03:03 SentryMan

I am writing a desktop application using javafx, I have some UI component bean for example: click a button and popup a dialog and the dialog is a bean. Suppose there is a situation: user open application and do not click the button, the dialog bean still in memory, lazy initialize there is a need. For optimize memory usage

DaiYuANg avatar Mar 08 '24 03:03 DaiYuANg

Tried using the Provider Interface?

you can wire a bean provider for cases where you don't want to immediately instantiate.

SentryMan avatar Mar 08 '24 03:03 SentryMan

Yeah, I had tried, I think this will make a lot of template code in my application cause there are many UI component need as a bean, if the DI framework support lazy initialization option that will be great. If there is no way to implement lazy initialization, I only have provider interface

DaiYuANg avatar Mar 08 '24 04:03 DaiYuANg

you could make a utility method to convert a supplier to a cached value. (btw you can also use Supplier if you like instead of Provider)


  public static <T> Supplier<T> memoize(Supplier<T> original) {
    return new Supplier<T>() {
      Supplier<T> delegate = this::firstTime;
      boolean initialized;

      public T get() {
        return delegate.get();
      }

      private synchronized T firstTime() {
        if (!initialized) {
          T value = original.get();
          delegate = () -> value;
          initialized = true;
        }
        return delegate.get();
      }
    };
  }

Then you can do:

@Singleton
class MyFXService {
  Supplier<MyObject> supplier;

  MyFXService(Supplier<MyObject> supplier) {
    this.supplier = memoize(supplier);
  }

  void someMethod() {
    var value = supplier.get();
    // idk do something
  }
}

SentryMan avatar Mar 08 '24 05:03 SentryMan

click a button and popup a dialog and the dialog is a bean.

@DaiYuANg can you show us what your code looks like? I don't know much about JavaFX (nor swing) these days. So the idea of using DI to inject a dialog is something I can't easily visualise in terms of what that code actually looks like.

To be clear I believe we are talking about "Lazy Singleton" (only 1 instance initialised lazily on demand - so a Provider<T> method with memoize to ensure the single instance).

Now currently we treat a @Factory @Bean @Secondary method as a Provider<T> ... and this is around only using secondary dependencies if another dependency doesn't exist (the secondary is only initialised if there isn't a higher priority dependency provided).

So as a following thought, is that maybe we could have a @Factory @Bean @Lazy method to similarly register the method as a Provider<T> (but this provider includes the memoize type logic to ensure only 1 instance). So, if we had that and that method was used to create the Dialog then the remaining question is how is that Provider<Dialog> actually used / wired ... what does the onButtonPress code look like / how does it use the Provider<Dialog> ?

rob-bygrave avatar Mar 08 '24 05:03 rob-bygrave

you could make a utility method to convert a supplier to a cached value. (btw you can also use Supplier if you like instead of Provider)

  public static <T> Supplier<T> memoize(Supplier<T> original) {
    return new Supplier<T>() {
      Supplier<T> delegate = this::firstTime;
      boolean initialized;

      public T get() {
        return delegate.get();
      }

      private synchronized T firstTime() {
        if (!initialized) {
          T value = original.get();
          delegate = () -> value;
          initialized = true;
        }
        return delegate.get();
      }
    };
  }

Then you can do:

@Singleton
class MyFXService {
  Supplier<MyObject> supplier;

  MyFXService(Supplier<MyObject> supplier) {
    this.supplier = memoize(supplier);
  }

  void someMethod() {
    var value = supplier.get();
    // idk do something
  }
}

Yeah, I tried the Supplier way I think this will make a lot of template code with the same reason, I had to xxx.get() many times

DaiYuANg avatar Mar 08 '24 16:03 DaiYuANg

click a button and popup a dialog and the dialog is a bean.

@DaiYuANg can you show us what your code looks like? I don't know much about JavaFX (nor swing) these days. So the idea of using DI to inject a dialog is something I can't easily visualise in terms of what that code actually looks like.

To be clear I believe we are talking about "Lazy Singleton" (only 1 instance initialised lazily on demand - so a Provider<T> method with memoize to ensure the single instance).

Now currently we treat a @Factory @Bean @Secondary method as a Provider<T> ... and this is around only using secondary dependencies if another dependency doesn't exist (the secondary is only initialised if there isn't a higher priority dependency provided).

So as a following thought, is that maybe we could have a @Factory @Bean @Lazy method to similarly register the method as a Provider<T> (but this provider includes the memoize type logic to ensure only 1 instance). So, if we had that and that method was used to create the Dialog then the remaining question is how is that Provider<Dialog> actually used / wired ... what does the onButtonPress code look like / how does it use the Provider<Dialog> ?

Of course, for example: I have preferences instance for my setting view(PreferencesFx from com.dlsc.preferencesfx:preferencesfx-core)

@Factory
public class RootFactory {

  @Bean
  PreferencesFx preferencesFx() {
    return PreferencesFx.of(
        SaveClass.class,
        Category.of(
            "Category Title",
            Group.of("Group Title", Setting.of("Setting Title", new SimpleStringProperty()))));
  }

  @Bean
  Preferences preferences() {
    return Preferences.systemRoot();
  }
}

And then there is a controller

@Singleton
@Slf4j
public class GlobalMenuBarController implements Initializable {

  //this is click event handle
  public void openPreferences(ActionEvent actionEvent) {
	//DIContext is a BeanScope wrapper
    val preferencesFx = DIContext.get(PreferencesFx.class);
    preferencesFx.show(true);
  }
}

In my case, maybe openPreferences never executed but now the PreferencesFx always in memory, In desktop environment, I want decrease memory usage, so I'm thinking about the Lazy initialization is necessary.

DaiYuANg avatar Mar 08 '24 16:03 DaiYuANg

Of course, for example:

try adding @Secondary to your factory beans. If I'm reading this right it should lazy load.

SentryMan avatar Mar 09 '24 05:03 SentryMan

Yes, @Secondary should get the lazy initialisation for now, and yes I think we will be able to support this properly by adding a @Lazy to be used like:

@Lazy @Bean
PreferencesFx preferencesFx() { 
 ...

Given this is used via val preferencesFx = DIContext.get(PreferencesFx.class); ... then yes that will use beanScope to obtain the instance - this part will work unchanged, all good here. Just to say, I think the alternative to the programmatic access would be to inject a Provider<PreferencesFx>.

So yes, I think we can add some proper support for this.

rbygrave avatar Mar 10 '24 19:03 rbygrave

@DaiYuANg just saying, 9.12-RC1 is in maven central so you can give this a try

SentryMan avatar Mar 11 '24 17:03 SentryMan

@DaiYuANg just saying, 9.12-RC1 is in maven central so you can give this a try

Hey, thanks so much 😸

DaiYuANg avatar Mar 12 '24 06:03 DaiYuANg