Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Lazy initialization #516

Closed
DaiYuANg opened this issue Mar 8, 2024 · 13 comments · Fixed by #518
Closed

Support Lazy initialization #516

DaiYuANg opened this issue Mar 8, 2024 · 13 comments · Fixed by #518
Assignees
Milestone

Comments

@DaiYuANg
Copy link

DaiYuANg commented Mar 8, 2024

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

@SentryMan
Copy link
Collaborator

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

@SentryMan
Copy link
Collaborator

@DaiYuANg
Copy link
Author

DaiYuANg commented Mar 8, 2024

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

@SentryMan
Copy link
Collaborator

Tried using the Provider Interface?

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

@DaiYuANg
Copy link
Author

DaiYuANg commented Mar 8, 2024

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

@SentryMan
Copy link
Collaborator

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

@rob-bygrave
Copy link
Contributor

rob-bygrave commented Mar 8, 2024

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

@DaiYuANg
Copy link
Author

DaiYuANg commented Mar 8, 2024

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
Copy link
Author

DaiYuANg commented Mar 8, 2024

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.

@SentryMan
Copy link
Collaborator

Of course, for example:

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

@rbygrave
Copy link
Contributor

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.

@SentryMan
Copy link
Collaborator

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

@DaiYuANg
Copy link
Author

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

Hey, thanks so much 😸

@rbygrave rbygrave added this to the 9.12 milestone Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants