Coder Social home page Coder Social logo

avaje / avaje-inject Goto Github PK

View Code? Open in Web Editor NEW
223.0 12.0 21.0 2.7 MB

Dependency injection via APT (source code generation) ala "Server-Side Dagger DI"

Home Page: https://avaje.io/inject

License: Apache License 2.0

Java 99.53% Shell 0.47%
java dependency-injection apt annotation-processing dagger inversion-of-control kotlin di-library dinject kapt

avaje-inject's People

Contributors

andrewazores avatar dependabot[bot] avatar github-actions[bot] avatar kevin70 avatar mechite avatar mohamed-abdelnour avatar norrisjeremy avatar rbygrave avatar rob-bygrave avatar sentryman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

avaje-inject's Issues

Roadmap

Interesting looking project, I like the idea of a APT based IOC basically taking the best bits of Spring without all the cruft. Whats plans for the future?

Swap/Change SystemContext objects with mocks

It would be awesome if dinject could swap objects for a test and reverert to the real object afterwards.

In my case Im trying to write Selenium usecase tests. The javalin server is allready started with all its controllers (dinject controlled singletons), but at some point I want that some controllers behave different.

@ExtendWith({SeleniumExtension.class})
public class InternalRestTest {

    private final Long expectedChecksum = 101010101L;

    @BeforeEach
    public void beforeEach(SeleniumHelper sh) throws Exception {
        sh.navigateTo("/");
    }

    @Test
    public void test_getBundleChecksum(SeleniumHelper sh) {

        InternalController internalRest = Mockito.spy(InternalController.class);
        doReturn(expectedChecksum).when(internalRest).getBundleChecksum();

        try (Context context = SystemContext.swapBean(internalRest)) { //<-- this would be nice
            //Not working with context here but Im expecting a different behavior from allready
            //started server

            Long response = sh.await("internalRest.getBundleChecksum()", Long.class);
            assertThat(response).isEqualTo(expectedChecksum);
        }
    }
}

This is the JUnit5 extension witch starts the server:

public class SeleniumExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver {

    private WebDriver driver;
    private final int port = 80;

    @Override
    public void beforeAll(ExtensionContext ec) throws Exception {
        Application.main(null);
        WebDriverManager.chromedriver().setup();
    }

    @Override
    public void beforeEach(ExtensionContext ec) throws Exception {
        System.setProperty("webdriver.chrome.silentOutput", "true");
        ChromeOptions options = new ChromeOptions();
        if (equalsIgnoreCase(System.getProperty("selenium.headless"), "true")) {
            options.addArguments("headless");
        }
        driver = new ChromeDriver(options);
        driver.manage().window().setSize(new Dimension(1_440, 1_050));
        driver.manage().timeouts().implicitlyWait(10, SECONDS);
        driver.manage().timeouts().setScriptTimeout(10, SECONDS);
    }

    @Override
    public void afterEach(ExtensionContext ec) throws Exception {
        driver.quit();
    }

    @Override
    public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) throws ParameterResolutionException {
        return pc.getParameter().getType() == SeleniumHelper.class;
    }

    @Override
    public Object resolveParameter(ParameterContext pc, ExtensionContext ec) throws ParameterResolutionException {
        return new SeleniumHelper(driver, port);
    }
}

And this is the main method called in the extension

public class Application {

    public static void main(String[] args) {

        InternalController internalController = getBean(InternalController .class);
        Security securityController = getBean(Security.class);

        Javalin javalin = Javalin.create()
                .disableStartupBanner()
                .enableCaseSensitiveUrls()
                .enableStaticFiles("/static")
                .accessManager(securityController)
                .start(80);

        javalin.post("/login", securityController::login, ANONYMOUS.asSet());
        javalin.get("/logout", securityController::logout, AUTHENTICATED.asSet());

        javalin.register(internalController);
    }
}

Add @Primary and @Secondary

These are used as tie breakers when multiple beans implement the same interface and a single bean is being injected.

@Primary makes it the preferred bean to inject
@Secondary makes it the least preferred bean to inject - will only be used if no other bean is available to inject. This makes it a kind of default bean used for injection when no other bean is available to inject.

ENH: Add SystemContext.getBeanContext() to return the underlying BeanContext

This is primarily added so that we can for example wire controllers for Javalin using BeanContext so that tests can wire test doubles / mocks for controllers and their dependencies during testing.

SystemContext.getBeanContext() provides the "normal" BeanContext when running the application but equally tests can provide an BeanContext with appropriate test doubles for the test.

Need Json Deserializers for collections

In my controller, I am posting List of a Pojo. The route that is created is of ctx.bodyAsClass(List.class). Hence, it doesn't deserialize the Pojos as the values are in LinkedHashMap.

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to <Pojo>

Currently I am accepting the data as Object and then converting it using Gson to the appropriate Collection of Pojos that the Api requires.

No injection of dependencies when using withSpy() and field injection

(Version 1.7)
Dependencies of spies are not injected:

        BootContext bootContext = new BootContext();
        bootContext.withSpy(AppleService.class);
        BeanContext beanContext = bootContext.load();

        AppleService appleServiceSpy = beanContext.getBean(AppleService.class);

        doNothing()
                .when(appleServiceSpy)
                .foo(anyString(), anyString(), anyString());

        System.out.println(appleServiceSpy);
        System.out.println(appleServiceSpy.bananaService); //<- is null but should not
        System.out.println(appleServiceSpy.peachService);  //<- is null but should not

I've made a simple test project to reproduce:
https://github.com/yaskor/dinject-fruit

Q: Feature: @Transactional

First of all, great library.
Only thing missing (for me) is @transactional.
One could define a DataSource and a TransactionManager and dinject would inject the transaction code into the annotated methods.

I have allready written something with an functional aproch like:

transactionManager.doInTransaction(()->{
    foo1();
    foo2();
})

But a annotation would be much nicer...

NotAMockException when using spy() on interface with a @Primary or @Secondary

  @Test
  public void withMockitoSpy_whenPrimary_expect_spyUsed() {

    try (BeanContext context = new BootContext()
      .withSpy(PEmailer.class) // has a primary
      .load()) {

      UserOfPEmailer user = context.getBean(UserOfPEmailer.class);
      PEmailer emailer = context.getBean(PEmailer.class);

      user.email();
      verify(emailer).email();
    }
  }

@Factory fails with types implementing interfaces

When using factory with beans implementing interfaces the generated class is missing imports for those interfaces.
In this example imports for Versioned and Serializable are missing.

I tried fixing it myself but didn't find a quick way how to debug apt processors. If you can provide me a hint about your development setup for dinject I would be happy fixing this myself.

package de.testung.example

import com.fasterxml.jackson.databind.ObjectMapper
import io.dinject.Bean
import io.dinject.Factory

@Factory
class ObjectMapperFactory {
    @Bean
    fun createObjectMapper(): ObjectMapper {
        return ObjectMapper()
    }
}

Generated class:

package de.testung.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.testung.example.ObjectMapperFactory;
import io.dinject.core.Builder;
import javax.annotation.Generated;

@Generated("io.dinject.generator")
public class ObjectMapperFactory$di  {

  public static void build(Builder builder) {
    if (builder.isAddBeanFor(ObjectMapperFactory.class)) {
      ObjectMapperFactory bean = new ObjectMapperFactory();
      builder.register(bean, null);
    }
  }

  public static void build_createObjectMapper(Builder builder) {
    if (builder.isAddBeanFor(ObjectMapper.class)) {
      ObjectMapperFactory factory = builder.get(ObjectMapperFactory.class);
      ObjectMapper bean = factory.createObjectMapper();
      builder.register(bean, null, Versioned.class, Serializable.class);
    }
  }
}

Add BootContext.withBean(Object bean) ... to supply test doubles into the dependency injection

So with the bootContext.withBean(bean) method we can supply beans that we want to use that override the otherwise normally injected bean.

For example, lets say we had a relatively expensive dependency (that talked to a database or something remote etc) and we want to build the context for testing purposes. We can provide a test double for that dependency and it would then be injected rather than the real thing.

  @Test
  public void withBean_expect_testDoublePumpUsed() {

    TDPump testDoublePump = new TDPump();

    try (BeanContext context = new BootContext()
       // Use/inject this instance rather than the normal one AND the normally created bean is skipped 
      .withBean(testDoublePump) 
      .load()) {

      String makeIt = context.getBean(CoffeeMaker.class).makeIt();
      assertThat(makeIt).isEqualTo("done");

      assertThat(testDoublePump.steam).isEqualTo(1);
      assertThat(testDoublePump.water).isEqualTo(1);
    }
  }

  /**
   * Our test double that we want to wire.
   */
  class TDPump implements Pump {

    int water;
    int steam;

    @Override
    public void pumpWater() {
      water++;
    }

    @Override
    public void pumpSteam() {
      steam++;
    }
  }
``

Support multiple @Factory @Named beans

@Factory
public class MyFactory {

  @Bean
  @Named("green")
  Otherthing greenOther() {
    return () -> "green";
  }

  @Bean
  @Named("yellow")
  Otherthing yellowOther() {
    return () -> "yellow";
  }
}
  private final Otherthing green;
  private final Otherthing yellow;

  MultipleOtherThings(@Named("green") Otherthing green, @Named("yellow") Otherthing yellow) {
    this.green = green;
    this.yellow = yellow;
  }

when using Lombok @Slf4j getting "Argument passed to when() is not a mock"

Hi @rbygrave,

when I execute this:

        BootContext bootContext = new BootContext();
        bootContext.withSpy(EmailService.class);
        beanContext = bootContext.load();

        EmailService emailService = beanContext.getBean(EmailService.class);
		
        System.out.println(emailService.getClass()); 
	// output--> class de.yaskor.autocontract.service.EmailService
		
        doNothing().when(emailService).sendEmail(anyString(), anyString(), anyString());

following exception is thrown:

	Argument passed to when() is not a mock!
	Example of correct stubbing:
	doThrow(new RuntimeException()).when(mock).someMethod();

this here works fine:

	BootContext bootContext = new BootContext();
        bootContext.withMock(EmailService.class);
        beanContext = bootContext.load();

        EmailService emailService = beanContext.getBean(EmailService.class);
        
        System.out.println(emailService.getClass()); 
	// output--> class de.yaskor.autocontract.service.EmailService$$EnhancerByMockitoWithCGLIB$$72a6db8a
		
        doNothing().when(emailService).sendEmail(anyString(), anyString(), anyString());

Please have a look at the sout's. The "withSpy" method doesn't replace the bean with the a spy, the "withMock" method works fine.

Throw useful error when Gradle + IDEA setup issue detected - java.lang.IllegalStateException: No modules found suggests using Gradle and IDEA but with a setup issue? Review IntelliJ Settings / Build / Build tools / Gradle - 'Build and run using' value and set that to 'Gradle'. Refer to https://dinject.io/docs/gradle#idea

java.lang.IllegalStateException: No modules found suggests using Gradle and IDEA but with a setup issue? Review IntelliJ Settings / Build / Build tools / Gradle - 'Build and run using' value and set that to 'Gradle'.  Refer to https://dinject.io/docs/gradle#idea
	at io.dinject.BootContext.load(BootContext.java:360)
	at io.dinject.SystemContext.init(SystemContext.java:48)
	at io.dinject.SystemContext.<clinit>(SystemContext.java:45)
	... 23 more

Will there be JPMS support?

While there is some overlap, there also seems to be places where DI could make JPMS easier to live with. Is there a plan to add JPMS support? Here's an example of the Dagger Coffee App using modules. It works, but DI would definitely make it more elegant.

Add support for @Factory and @Bean

@Factory
public class Configuration {

  private final StartConfig startConfig;

  @Inject // ... factory can have dependencies ...
  public Configuration(StartConfig startConfig) {
    this.startConfig = startConfig;
  }

  @Bean  // a simple factory method
  public AFact buildA() {
    ...
  }

  @Bean  // a factory method with a dependency
  public BFact buildB(Other other) {
    ...
  }

Fix for NPE injecting bean

ava.lang.NullPointerException
at io.kanuka.core.DBeanMap.getBean(DBeanMap.java:94)
at io.kanuka.core.DBuilder.getMaybe(DBuilder.java:126)
at io.kanuka.core.DBuilder.getMaybe(DBuilder.java:148)
at io.kanuka.core.DBuilder.get(DBuilder.java:197)
at io.kanuka.core.DBuilder.get(DBuilder.java:192)
at org.junk.Bar$di.build(Bar$di.java:10)
at org.junk._di$Factory.buildBar(_di$Factory.java:39)
at org.junk._di$Factory.createContext(_di$Factory.java:32)
at io.kanuka.BootContext.load(BootContext.java:80)

Detect circular dependency and fail compile with nice error - Was BeanContextFactory.createContext calls builders in the wrong order

Hi @rbygrave,

It's not the first time I'm facing this issue. Even if @DependecyMeta annotation contains the correct classes within the dependsOn list, the createContext method calls the builders in the wrong order causing:

Injecting null for com.example.MyClass when creating class com.example.MyClass2 - potential beans to inject: []

The generated createContext methods is like:

  @Override
  public BeanContext createContext(Builder parent) {
    builder.setParent(parent);
    // other builders
    build_MyClass2();
    // other builders
    build_MyClass();
    return builder.build();
  }

Any workarounds I can implement to force the generator to call the builders respecting the dependencies?

Thanks a lot ๐Ÿ™

More intuitive multi-module ordering for 'only supplies' modules

So for the case where we have multi-module dependencies BUT ... on the modules there is only supplies specified.

e.g. Have 2 modules where one is defined with
@ContextModule(name = "javalin-validator", provides = "validator") ... and the other module has NO @ContextModule or nothing defined for provides and dependsOn.

In this scenario the current 1.9 behaviour will wire these modules in an undefined order (we don't define which is wired first).

With this change dinject with ALWAYS include the modules that have a provides first and then follow that by modules that have nothing defined (and then followed by the normal ordering based on provides and dependsOn).

Q: Dagger2 for injection, dinject for route generation

Hi,

Love this library! But there are some dagger features that I miss such as multi-bindings etc.

Wouldn't it be an idea to leverage dagger for injection of dependencies and use dinject for code generation of routes? That way we get all the features of dagger2, your library becomes way less complex and could focus more on route/API generation than competing with dagger for DI?

Just a thought :)

Add support for @Qualifier annotations (as alternative to @Named)

Create a qualifier annotation. e.g.

@Qualifier
@Retention(RUNTIME)
public @interface Blue {
}

Specify the qualifier annotation rather than @Named on beans and injection targets (constructor parameters, field injection).

@Blue
@Singleton
public class BlueStore implements SomeStore {
...

Constructor injection:

@Singleton
public class StoreManagerWithQualifier {

  private final SomeStore store;

  public StoreManagerWithQualifier(@Blue SomeStore store) {
    this.store = store;
  }

Field injection:

@Singleton
public class StoreManagerWithFieldQualifier {

  @Inject
  @Blue
  SomeStore store;

Support generic interfaces like Repository<T,I>

For example:

public interface Repository<T,I> {
  T findById(I id);
}
@Singleton
public class HazRepo implements Repository<Haz,Long> {

  @Override
  public Haz findById(Long id) {
    ...
  }
}
@Singleton
public class HazManager  {

  private final Repository<Haz,Long> hazRepo;

  @Inject
  public HazManager(Repository<Haz, Long> hazRepo) {
    this.hazRepo = hazRepo;
  }

  public Haz find(Long id) {
    return hazRepo.findById(id);
  }
}

Support @Factory methods returning void

@Factory
public class MyFactory {

  @Bean
  SomeBean buildSomeBean() {
    return ...;
  }

  /**
   * A factory method that has dependencies and returns void.
   */
  @Bean
  void useSomeBean(SomeBean someBean) {

  }

  @Bean
  void another(SomeBean someBean) {

  }
}

Compile warnings when using withSpy, withMock

Im getting compile warnings when Im using follwing construct....

private final List<Class> withMocks;
private final List<Class> withSpies;

private ContextExtension(List<Class> withMocks, List<Class> withSpies) {
	this.withMocks = withMocks;
	this.withSpies = withSpies;
}
		
...
		
BootContext bootContext = new BootContext();
withMocks.forEach(mock -> bootContext.withMock(mock));
withSpies.forEach(spy -> bootContext.withSpy(spy));
beanContext = bootContext.load();
Changes detected - recompiling the module!
Compiling 26 source files to C:\Users\saka\Documents\NetBeansProjects\private\javalin-sample\target\test-classes
de/yaskor/autocontract/test/ContextExtension.java:[68,55] unchecked method invocation: method withMock in class io.dinject.BootContext is applied to given types
  required: java.lang.Class<D>
  found: java.lang.Class
de/yaskor/autocontract/test/ContextExtension.java:[68,56] unchecked conversion
  required: java.lang.Class<D>
  found:    java.lang.Class
de/yaskor/autocontract/test/ContextExtension.java:[68,26] unchecked method invocation: method forEach in class java.util.ArrayList is applied to given types
  required: java.util.function.Consumer<? super E>
  found: java.util.function.Consumer<java.lang.Class>
de/yaskor/autocontract/test/ContextExtension.java:[69,53] unchecked method invocation: method withSpy in class io.dinject.BootContext is applied to given types
  required: java.lang.Class<D>
  found: java.lang.Class
de/yaskor/autocontract/test/ContextExtension.java:[69,54] unchecked conversion
  required: java.lang.Class<D>
  found:    java.lang.Class
de/yaskor/autocontract/test/ContextExtension.java:[69,26] unchecked method invocation: method forEach in class java.util.ArrayList is applied to given types
  required: java.util.function.Consumer<? super E>
  found: java.util.function.Consumer<java.lang.Class>

How can I satisfy Class<D>?

ContextModule issue - unsatisfied dependencies when using maven shade plugin (appending META-INF/services/io.dinject.core.BeanContextFactory resources)

Hello Rob,

First of all, I really like this project and the simplicity of using a library vs an entire framework like quarkus.io.

But I also have a question/issue (not sure if this is the right place but could not find a better one :) ).
I have created a CoffeeApp like application with modules. Now I can run it inside IntelliJ and one module will load the other module with the ContextModule and resolve the dependencies.

But I also tried making a Jar file and then it failed, I get the following error:
Module [<applicationmodule>] has unsatisfied dependencies on modules: [<library module>]. Modules that were loaded ok are:[]. Consider using BootContext.withIgnoreMissingModuleDependencies() or BootContext.withSuppliedBeans(...)

After some searching I found that the issue is that this file is overwritten in the META-INF/services folder:
io.dinject.core.BeanContextFactory
When I add both _di$Factory lines manually to the 's version it will load.

So my question is: Is it possible to create a complete file with the modules in the correct order when building the project? Or am I creating the jar file incorrectly?

Best regards,
Tijs

ENH: Add support for request scoped @Inject for Javalin Context and Helidon ServerRequest and ServerResponse.

"Request scoped injection" translates to generating BeanFactory/BeanFactory2 and utlimately using these as dependencies (in generated web routes).

@Controller
@Path("/foo")
public class AController {

  @Inject
  SomeService service;  // a normal dependency

  @Inject
  Context context;   // controller that injects the javalin context

  @Get
  public String get() {
    return "hi " + context.toString() + service.hi();
  }

Add support for Provider<T>

import javax.inject.Provider;
import javax.inject.Singleton;

@Singleton
public class FooProvider implements Provider<Foo> {

  private final SomeDependency bar;

  FooProvider(SomeDependency bar) {
    this.bar = bar;
  }

  @Override
  public Foo get() {
    ...
    return new Foo(...);
  }
}

And then Foo is a bean that can be injected into other beans.

Fix to allow spy() with implementations (not interfaces) - IllegalStateException: Injecting null

Where Grinder is an implementation class and not an interface

  @Test
  public void withMockitoSpy_postLoadSetup_expect_spyUsed() {

    try (BeanContext context = new BootContext()
      .withSpy(Pump.class)
      .withSpy(Grinder.class) // HERE
      .load()) {

      ...

      CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
      coffeeMaker.makeIt();

      ...

      Grinder grinder = context.getBean(Grinder.class);
      verify(grinder).grindBeans(); // throws IllegalStateException
    }
  }
java.lang.IllegalStateException: Injecting null for org.example.coffee.grind.Grinder when creating class org.example.coffee.CoffeeMaker - potential beans to inject: []

	at io.dinject.core.DBuilder.get(DBuilder.java:223)
	at io.dinject.core.DBuilder.get(DBuilder.java:207)
	at org.example.coffee.CoffeeMaker$di.build(CoffeeMaker$di.java:14)
	at org.example.coffee._di$Factory.build_CoffeeMaker(_di$Factory.java:228)
	at org.example.coffee._di$Factory.createContext(_di$Factory.java:92)
	at io.dinject.BootContext.load(BootContext.java:364)
	at org.example.coffee.BootContext_mockitoSpyTest.withMockitoSpy_postLoadSetup_expect_spyUsed(BootContext_mockitoSpyTest.java:64)
	...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.