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

Support Generic Auto Provides/Requires

Open SentryMan opened this issue 1 year ago • 9 comments

  • Changes the Module interface to use Type[] instead of Class<?>[]
  • Updates generator to allow generic types to be added to auto provides/requires via GenericType
  • updates the maven/gradle plugin to use Type[] instead of Class<?>[]

Now we can have a generic (factory) bean in Module A:

@Component
public class OtherComponentGeneric implements Supplier<String> {

  @Override
  public String get() {
    return "hello";
  }
}

The generated Avaje Module class will have:

public final class ModuleClass implements Module {
// ...rest of the module class
  @Override
  public Type[] autoProvides() {
    return autoProvides;
  }
  private static final Type[] autoProvides = {
    new GenericType<java.util.function.Supplier<java.lang.String>>(){},
    OtherComponentGeneric.class,
  };
// ...rest of the module class

and in Module B we can reference via:

@Component
public class HasExternalDependencies {

  public final Supplier<String> stringSupplier;

  public HasExternalDependencies(
      Supplier<String> stringSupplier) {
    this.stringSupplier = stringSupplier;
  }
}

SentryMan avatar Feb 11 '24 19:02 SentryMan

Did somebody hit a limitation here / was there a requirement for this?

The autoProvides is for wiring across modules. I tend to believe that cross-module dependencies should be interfaces and I tend to think they shouldn't use generic types. Has someone hit an issue here?

rbygrave avatar Feb 13 '24 19:02 rbygrave

Is it so inconceivable that a module would provide generic types? I've got a mongo common library that handles authentication and provides some common MongoCollection beans. Currently, I have to wrap the generic types in a non-generic wrapper class to wire across modules.

SentryMan avatar Feb 13 '24 19:02 SentryMan

Factory beans that return Maps are also not uncommon. Overall, wrapping generic types to get them to wire across modules is quite a hassle.

SentryMan avatar Feb 13 '24 20:02 SentryMan

Currently, I have to wrap the generic types in a non-generic wrapper class to wire across modules.

That sounds like a concrete class being used across a module boundary - so yeah I'm really surprised as that sounds like relatively tight coupling between modules. Can you share an example? [so that I can get a sense of the coupling, I'm also wondering if this is changing with the java module system in terms of tight coupling]

Noting that this is a breaking change - it would need a bump to the major version.

rob-bygrave avatar Feb 13 '24 23:02 rob-bygrave

Noting that this is a breaking change - it would need a bump to the major version

Surprisingly, it doesn't break anything except the plugins. Class<?> extends Type so modules generated before this change will load and work as expected.

SentryMan avatar Feb 13 '24 23:02 SentryMan

Can you share an example?

an example of wrapping? In module A:

@Singleton
public record WrapperClass(Map<String, String> map){}

Then you can inject WrapperClass into a bean in some module B.

SentryMan avatar Feb 14 '24 00:02 SentryMan

an example of wrapping?

I meant a real world example - as in, we are using tight coupling between 2 modules with a concrete class (and not an interface). What is real world example where this is a good idea (versus a bad idea because that is tight coupling between modules that uses an implementation - a non-abstract implementation class to coupling 2 modules together ... and I'm going "Really??").

rob-bygrave avatar Feb 14 '24 00:02 rob-bygrave

modules with a concrete class (and not an interface)

I don't follow, the point of this PR is that we can avoid concrete wrapper classes and use the generic interface/super classes directly

SentryMan avatar Feb 14 '24 00:02 SentryMan

modules with a concrete class (and not an interface)

I did think of an example though. Some time ago I was helping out a guy on discord with avaje and he was quite confused about the current behavior, he had a common library that would configure a Jackson ObjectMapper for his other applications.

The problem was that ObjectMapper is auto provided as Versioned, which nobody recognizes.

public final class ModuleClass implements Module {
// ...rest of the module class
  @Override
  public Class<?>[] autoProvides() {
    return autoProvides;
  }
  private static final Class<?>[] autoProvides = {
    Versioned.class,
  };

so naturally his compilation would fail. I ended up telling him he had to manually use @InjectModule(provides=ObjectMapper.class) to get it to work.

Honestly, any sort of avaje-based configuration library that configures third-party classes would appreciate #501

SentryMan avatar Feb 14 '24 00:02 SentryMan