-
Notifications
You must be signed in to change notification settings - Fork 25
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
Refactor scopes - Removes ApplicationScope & RequestScope in favour of "custom scopes" #134
Conversation
…hSpy() methods to there Provides a cleaner API for our main non-test use cases
…uires() and remove String based dependsOn() and provides() Will need users to migrate to Class<?> based provides() and requires(). Name also isn't strictly used (name only used for name of generated Module class).
Nice, from a quick scan through it feels like much more code was deleted than added, but without loss of expressive power. |
Splits generation of old BeanScopeFactory into Module + a helper BeanFactory. This allows the use of the generated Module via BeanScopeBuilder.withModules(). This does not support: - partial compile (need to reload meta data for custom scope modules on partial compile) - external/requires dependencies provided by other scopes / typically the main scope - parent/child relationship of BeanScopes (e.g. custom scope with parent of the global scope)
Yes, so far this is has the "good feeling" about it. I have just managed some initial "custom scope" support. I think I have about 3 more things to support for custom scopes. I'm hoping that we can define a custom scope via: import io.avaje.inject.InjectModule;
import jakarta.inject.Scope;
@Scope
@InjectModule(requires = ...)
public @interface MyCustomScope {
} Where the So at this point no extra annotation required for a custom scope. Each custom scope gets its own generated Module (and bean factory which got split out). We can then specify with BeanScopeBuilder which modules are used to create the BeanScope (looks like Guice in that regard). The "global" modules generated from We should be able to support custom scope only cases. There would be no service loading used, all BeanScopes would be built with explicitly specified modules in that case. Splits generation of old BeanScopeFactory into Module + a helper BeanFactory. This allows the use of the generated Module via BeanScopeBuilder.withModules(). This does not support:
|
Effectively this means we read the module meta data back for custom scopes at the beginning (first compile round). For partial compile it gets mutated but we still have full meta data per module to write correct ordering etc on the last compile round.
This isn't as tight/ideal as we could hope for but ok for now.
Next steps:
At this point we allow a custom scope to depend on anything provided by the "default" scope - effectively assuming that the custom scope will be created with the "default" scope as it's parent. At least, this is the initial plan as that allow us to get an initial release out for this. We ultimately could be tighter wrt externally provided dependencies of custom scopes (but this is probably ok). Just need the parent / child support and then we can take stock and look / play with it. |
… (for dependencies)
…te "BeanFactory") Removes SimpleFactoryWriter, write the module with all the build methods together on the same class again. We can do this because we get the JavaFileObject early - This means our Module classes are now visible/usable in src/main - We write the content on last round as we only have the full ordering at that point
This changes the existing BeanEntry to be an interface and makes it more consistent API wise.
@mikehearn I have released this as version 6.5-RC0 to maven central. I don't have anything outstanding to do on it, it is ready to try out. |
WRT the prior code example: @CreateScope
annotation class MyGlobal
@CreateScope(external = [Foo::class, Bar::class], parent = MyGlobal::class)
annotation class MyRequest
@MyGlobal class A
@MyRequest class B(val a: A)
fun main() {
val scope = MyGlobalScope.newBuilder().build()
val requestScope = MyRequestScope.newFrom(scope).build()
}
Default scopeThere is still this concept of the default scope (global scope). What this boils down to is that when we are not using custom scopes those beans/components goto a 'default' Module. When we build a BeanScope without specifying any explicit modules what happens is that is will use ServiceLoader to find and load all the 'default' Modules in the classpath/modulepath. So we can use avaje-inject in many jars each with a 'default' Module, when we create BeanScope without specifying any explicit modules it loads all of these and wires the whole lot. So Now this is generally what we do when we build reusuable components or modular monoliths - we are wiring things from multiple jars into a single BeanScope. Note that with 6.5.RC0 we don't need to use this 'default' scope. We can do everything as custom scopes. Custom scopePretty similar to your original example import jakarta.inject.Scope;
@Scope
public @interface MyGlobal {
} Add with a dependency on a 'parent' scope and some other external dependencies - these all go into package org.example;
import io.avaje.inject.InjectModule;
import jakarta.inject.Scope;
@Scope
@InjectModule(requires = {MyGlobal.class, Foo.class, Bar.class})
public @interface MyRequest {
} So components with fun main() {
val scope = MyGlobalScope.newBuilder().build()
val requestScope = MyRequestScope.newFrom(scope).build()
} Would be BeanScope globalScope = BeanScope.newBuilder()
.withModules(new MyGlobalModule())
.build()
BeanScope requestScope = BeanScope.newBuilder()
.withModules(new MyRequestModule())
.withParent(globalScope)
.withBean(Foo.class, foo)
.withBean(Bar.class, bar)
.build() We alternatively could create it as a single scope by specifying both modules like below. avaje-inject will determine the order to run the modules based on the requires/provides (kind of Guice style): BeanScope scope = BeanScope.newBuilder()
.withModules(new MyGlobalModule(), new MyRequestModule())
.withBean(Foo.class, foo)
.withBean(Bar.class, bar)
.build() |
Notes: The generated 'default' module implements The generated 'custom' modules implement |
withSpy()
withMock()
methods ofBeanScopeBuilder
to only be available afterbeanScopeBuilder.forTesting()
@InjectModule
to useClass<?>
for provides and requires (not strings)Module
and allow us to specify explicitly which modules to include in BeanScopeModule