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

Add DependencyInjector to inject dependencies in annotations #4202

Merged
merged 16 commits into from
Jul 5, 2022

Conversation

minwoox
Copy link
Contributor

@minwoox minwoox commented Apr 11, 2022

Motivation:
Users cannot inject an instance, which has constructor parameters, through @Decorator and other annotations.
It would be nice if we provide a way to inject dependencies.

Modifications:

  • Add DependencyInjector to inject the instances that are specified in an annotated service.
    • If a DependencyInjector is not set, ReflectiveDependencyInjector is used to keep the backward-compatibility
    • If a DependencyInjector is set, BuiltInDependencyInjector is applied at first to support built-in classes.
  • Add SpringDependencyInjector that injects using BeanFactory.
    • You can set armeria.enable-auto-injection to true to apply SpringDependencyInjector.
    • You can also create a bean for DependencyInjector.

Result:

  • Close Provide a way to inject an instance for @Decorator #4006
  • You can now inject dependencies using DependencyInjector to annotated services.
    // Inject authClient that is needed to create the AuthDecorator.
    WebClient authClient = ...
    DependencyInjector injector = DependencyInjector.ofSingletons(new AuthDecorator(authClient));
    serverBuilder.dependencyInjector(dependencyInjector, true);
    
    // An annotated service that uses AuthDecorator.
    @Get("/foo")
    @Decorator(AuthDecorator.class)
    public FooResponse foo(FooRequest req) {
        // Authrorized request.
        ...
    }
    
    // authClient is injected.
    class AuthDecorator implements DecoratingHttpServiceFunction {
        AuthDecorator(WebClient authClient) { ... }
    
        @Override
        public HttpResponse serve(HttpService delegate, ServiceRequestContext ctx, HttpRequest req)
                throws Exception {
            // Authorize the request.
            ...
        }
    }

Motivation:
Users cannot inject an instance, which has constructor parameters, through `@Decorator` and other anntations.
It would be nice if we provide a way to inject dependencies.

Modifications:
- Add `DependencyInjector` to inject the instances that are specified in an annotated service.
  - Spring integration module automatically adds `SpringDependencyInjector` to inject the depdendencies defined via bean definition.

Result:
- Close line#4006
- You can now inject dependencies that have constructor parameters to an annotated service.
@minwoox minwoox added this to the 1.16.0 milestone Apr 11, 2022
@codecov
Copy link

codecov bot commented Apr 11, 2022

Codecov Report

Merging #4202 (a35ca48) into master (554d596) will increase coverage by 0.00%.
The diff coverage is 74.58%.

@@            Coverage Diff             @@
##             master    #4202    +/-   ##
==========================================
  Coverage     73.44%   73.44%            
- Complexity    17395    17443    +48     
==========================================
  Files          1482     1488     +6     
  Lines         65296    65448   +152     
  Branches       8207     8221    +14     
==========================================
+ Hits          47954    48067   +113     
- Misses        13169    13201    +32     
- Partials       4173     4180     +7     
Impacted Files Coverage Δ
...ria/internal/server/annotation/AnnotationUtil.java 89.05% <ø> (ø)
...java/com/linecorp/armeria/server/ServerConfig.java 0.00% <ø> (ø)
...ver/VirtualHostAnnotatedServiceBindingBuilder.java 53.01% <ø> (ø)
...ia/server/annotation/ExceptionHandlerFunction.java 100.00% <ø> (ø)
...ia/server/annotation/RequestConverterFunction.java 100.00% <ø> (ø)
...a/server/annotation/ResponseConverterFunction.java 100.00% <ø> (ø)
...rnal/server/annotation/AnnotatedObjectFactory.java 33.33% <33.33%> (-18.85%) ⬇️
...ecorp/armeria/common/OrElseDependencyInjector.java 41.17% <41.17%> (ø)
...ria/internal/common/BuiltInDependencyInjector.java 41.66% <41.66%> (ø)
...corp/armeria/common/DefaultDependencyInjector.java 50.00% <50.00%> (ø)
... and 37 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 554d596...a35ca48. Read the comment docs.

@Override
public <T> T getInstance(Class<T> type) {
try {
return beanFactory.getBean(type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be useful to inject a decorator with a name?

@Decorator(FooDecorator.class, "foo1Decorator")
public String foo();

// Will be chosen
@Bean(name = "foo1Decorator")
FooDecorator foo1() {
     ...
}

@Bean(name = "foo2Decorator")
FooDecorator foo2() {
     ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that's a good suggestion. 👍
However, can I just leave it as a TODO and implement it later because:

  • We haven't had such demands so far
  • It increases the complexity of the implementation
  • Users can easily apply the different instances using @DecoratorFactory

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. We can add the feature later without hurting the original API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would naming a bean work for other DI frameworks by the way? (Guice, Dagger, etc)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guice and Dagger supports @Named bean injection as well.
https://github.com/google/guice/wiki/BindingAnnotations#named

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looking good. Left minor comments.

@Override
public <T> T getInstance(Class<T> type) {
try {
return beanFactory.getBean(type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. We can add the feature later without hurting the original API.

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a nit comment, but happy with the changes once @ikhoon , @trustin 's comments are addressed 👍 Thanks @minwoox ! 🙇 👍 🙇

@ikhoon ikhoon modified the milestones: 1.16.0, 1.17.0 Apr 18, 2022
minwoox added a commit to minwoox/armeria that referenced this pull request Apr 26, 2022
Motivation
Currently, depedencies for decorators are injected when the class is set to the `GrpcServiceBuilder`.
However, we need to use the `DependencyInjector` that will be introduced in line#4202.
The `DependencyInjector` is set to the `Server` so we need to delay to inject dependencies
until the `serviceAdded` method is called which means we can get the injector.

Modifications:
- Refactor `GrpcServiceBuilder` for adding decorators
- Use `lookupMethodFromAttribute` where applicable
- Fix a bug that decorator is not applied when `grpcBuilder.addService(path, service, method)` is called.
- Inject dependencies in `GrpcDecoratingService.serviceAdded()`

Result:
- Ready to apply line#4202
- Decorator is correctly applied when a method is added via `GrpcServiceBuilder.addService(String,BindableService,MethodDescriptor)`.
minwoox added a commit to minwoox/armeria that referenced this pull request Apr 26, 2022
Motivation
Currently, depedencies for decorators are injected when the class is set to the `GrpcServiceBuilder`.
However, we need to use the `DependencyInjector` that will be introduced in line#4202.
The `DependencyInjector` is set to the `Server` so we need to delay to inject dependencies
until the `serviceAdded` method is called which means we can get the injector.

Modifications:
- Refactor `GrpcServiceBuilder` for adding decorators
- Use `lookupMethodFromAttribute` where applicable
- Fix a bug that decorator is not applied when `grpcBuilder.addService(path, service, method)` is called.
- Inject dependencies in `GrpcDecoratingService.serviceAdded()`

Result:
- Ready to apply line#4202
- Decorator is correctly applied when a method is added via `GrpcServiceBuilder.addService(String,BindableService,MethodDescriptor)`.
minwoox added a commit that referenced this pull request May 24, 2022
…ratingService (#4228)

Motivation
Currently, dependencies for decorators are injected when the class is set to the `GrpcServiceBuilder`.
However, we need to use the `DependencyInjector` that will be introduced in #4202.
The `DependencyInjector` is set to the `Server` so we need to delay injecting dependencies
until the `serviceAdded` method is called which means we can get the injector.

Modifications:
- Refactor `GrpcServiceBuilder` for adding decorators
- Fix a bug that decorator is not applied when `grpcBuilder.addService(path, service, method)` is called.
- The path, which is used to set a service, is now correctly normalized.
- Inject dependencies in `GrpcDecoratingService.serviceAdded()`

Result:
- Ready to apply #4202
- Decorator is correctly applied when a method is added via `GrpcServiceBuilder.addService(String,BindableService,MethodDescriptor)`.
@minwoox
Copy link
Contributor Author

minwoox commented Jun 16, 2022

Updated, PTAL. 🙇‍♂️

@jrhee17 jrhee17 self-requested a review June 30, 2022 01:26
Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Only left nits and a question 🙇

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!! 🍃🎉
Spring users will love this feature.

@@ -289,4 +289,9 @@ default boolean shutdownBlockingTaskExecutorOnStop() {
* supports case sensitive HTTP/1 headers.
*/
Http1HeaderNaming http1HeaderNaming();

/**
* Returns the {@link DependencyInjector} that injects dependencies in annotated services.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the DependencyInjector be used in a gRPC service?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. 😉 We might support adding annotations to a Thrift service later so let me change the sentence to injects dependencies in annotations

@minwoox minwoox changed the title Add DependencyInjector to inject dependencies for an annotated service Add DependencyInjector to inject dependencies in annotations Jul 1, 2022
* constructor, which does not have a parameter, of the classes.
*/
@UnstableApi
public interface DependencyInjector extends SafeCloseable {
Copy link
Member

@trustin trustin Jul 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move this interface to common so we can use this for both client and server side? For example, in the future, we could introduce a client-side (or common) @Decorator annotation and let our users use it with Retrofit or our future Retrofit alternative.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good suggestion. 👍 Moved the package. 😉

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a really useful feature! Thanks @minwoox 🙇 👍 🙇

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM with new changes.

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this awesome feature, @minwoox! 👍

@minwoox minwoox merged commit 91c787f into line:master Jul 5, 2022
@minwoox minwoox deleted the DI branch July 5, 2022 06:04
@minwoox
Copy link
Contributor Author

minwoox commented Jul 5, 2022

Thanks, reviewers! 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide a way to inject an instance for @Decorator
4 participants