{"id":16101665,"url":"https://github.com/casid/jusecase-inject","last_synced_at":"2025-03-18T07:31:33.763Z","repository":{"id":32659329,"uuid":"104933106","full_name":"casid/jusecase-inject","owner":"casid","description":"A fast and lightweight dependency injection framework for Java, with focus on simplicity, testability and ease of use.","archived":false,"fork":false,"pushed_at":"2022-01-06T16:29:45.000Z","size":60,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-28T08:17:34.994Z","etag":null,"topics":["aspectj","dependency-injection","java","tdd"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/casid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-26T20:14:28.000Z","updated_at":"2022-07-13T09:08:33.000Z","dependencies_parsed_at":"2022-08-07T17:47:46.773Z","dependency_job_id":null,"html_url":"https://github.com/casid/jusecase-inject","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casid%2Fjusecase-inject","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casid%2Fjusecase-inject/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casid%2Fjusecase-inject/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casid%2Fjusecase-inject/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/casid","download_url":"https://codeload.github.com/casid/jusecase-inject/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243910814,"owners_count":20367545,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspectj","dependency-injection","java","tdd"],"created_at":"2024-10-09T18:50:35.363Z","updated_at":"2025-03-18T07:31:32.968Z","avatar_url":"https://github.com/casid.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Inject\n\n[![Build Status](https://travis-ci.org/casid/jusecase-inject.svg?branch=master)](https://travis-ci.org/casid/jusecase)\n[![Coverage Status](https://coveralls.io/repos/github/casid/jusecase-inject/badge.svg?branch=master)](https://coveralls.io/github/casid/jusecase-inject?branch=master)\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/casid/jusecase-inject/master/LICENSE)\n[![Maven Central](https://img.shields.io/maven-central/v/org.jusecase/inject.svg)](http://mvnrepository.com/artifact/org.jusecase/inject)\n\nA fast and lightweight dependency injection framework for Java, with focus on simplicity, testability and ease of use. Requires Java 11, AspectJ and JUnit 5.\n\n## Motivation\nI've written this small lib to have faster TDD cycles in my personal \u003ca href=\"https://mazebert.com/\"\u003eJava backend project\u003c/a\u003e. I'm running it in production for over two years now, without looking back at Spring/Guice/Dagger or doing it all by hand.\n\nFirst off, you should **NOT** use this lib, if you:\n- Can't or don't want to use AspectJ\n- Have more than one application context in a process (enterprise java)\n- Want to have circular dependencies (well, this might actually be a benefit)\n\nHere is why you may want to check it out:\n- Create components naturally with `new Foo()`, and injection happens automatically\n- First class support for unit testing\n- Prepared for parallel unit test execution\n- No static, hard to test loggers\n- Small footprint, no dependencies except AspectJ (the JAR is about 14KB)\n\nBut see for yourself. Here's a small component:\n```java\nimport org.jusecase.inject.Component;\nimport javax.inject.Inject;\n\n@Component\npublic class CoffeeMachine {\n    @Inject\n    private BeansRepository beansRepository;\n    @Inject\n    private WaterRepository waterRepository;\n}\n```\nAt some place at startup, we init the dependencies:\n```java\nInjector.getInstance().add(new BeansRepository());\nInjector.getInstance().add(new WaterRepository());\n```\n\nHere is how you create the CoffeeMachine:\n```java\nCoffeeMachine coffeeMachine = new CoffeeMachine();\n```\nAll dependencies are injected. If dependencies are missing you will get an exception telling you what's exactly missing.\n\n## Getting started\n\nJUsecase Inject is available on maven central repository:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.jusecase\u003c/groupId\u003e\n    \u003cartifactId\u003einject\u003c/artifactId\u003e\n    \u003cversion\u003e0.3.1\u003c/version\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.jusecase\u003c/groupId\u003e\n    \u003cartifactId\u003einject\u003c/artifactId\u003e\n    \u003cversion\u003e0.3.1\u003c/version\u003e\n    \u003ctype\u003etest-jar\u003c/type\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nYou should add JUnit 5 testing dependencies if you haven't already.\n\nAnd AspectJ (for Java 11 we unfortunately can't use the official plugin):\n```xml\n\u003cplugin\u003e\n    \u003cgroupId\u003ecom.nickwongdev\u003c/groupId\u003e\n    \u003cartifactId\u003easpectj-maven-plugin\u003c/artifactId\u003e\n    \u003cversion\u003e1.12.6\u003c/version\u003e\n    \u003cconfiguration\u003e\n        \u003ccomplianceLevel\u003e${maven.compiler.release}\u003c/complianceLevel\u003e\n        \u003csource\u003e${maven.compiler.source}\u003c/source\u003e\n        \u003ctarget\u003e${maven.compiler.target}\u003c/target\u003e\n        \u003cencoding\u003eUTF-8 \u003c/encoding\u003e\n        \u003caspectLibraries\u003e\n            \u003caspectLibrary\u003e\n                \u003cgroupId\u003eorg.jusecase\u003c/groupId\u003e\n                \u003cartifactId\u003einject\u003c/artifactId\u003e\n            \u003c/aspectLibrary\u003e\n        \u003c/aspectLibraries\u003e\n    \u003c/configuration\u003e\n    \u003cexecutions\u003e\n        \u003cexecution\u003e\n            \u003cgoals\u003e\n                \u003cgoal\u003ecompile\u003c/goal\u003e\n                \u003cgoal\u003etest-compile\u003c/goal\u003e\n            \u003c/goals\u003e\n        \u003c/execution\u003e\n    \u003c/executions\u003e\n    \u003cdependencies\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.aspectj\u003c/groupId\u003e\n            \u003cartifactId\u003easpectjtools\u003c/artifactId\u003e\n            \u003cversion\u003e1.9.5\u003c/version\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n\u003c/plugin\u003e\n```\n\nTo see if everything works as expected, we can create a quick hello world class.\n\n\u003e You find the code for this example in the test source package [org.jusecase.inject.classes.example1](src/test/java/org/jusecase/inject/classes/example1)\n\n```java\nimport org.jusecase.inject.Component;\nimport javax.inject.Inject;\nimport javax.inject.Named;\n\n@Component\npublic class HelloWorld {\n    @Inject\n    @Named(\"hello\")\n    private String hello;\n    @Inject\n    @Named(\"world\")\n    private String world;\n\n    public HelloWorld() {\n        System.out.println(hello + \" \" + world);\n    }\n}\n```\n\nLet's create a unit test to see if everything is working:\n```java\nimport org.junit.jupiter.api.Test;\n\nclass HelloWorldTest {\n    @Test\n    void test() {\n        new HelloWorld();\n    }\n}\n```\n\nRun the test. It fails with this error message: \n`org.jusecase.inject.InjectorException: No implementation found. Failed to inject java.lang.String hello in org.jusecase.inject.classes.HelloWorld`\n\nWell, that makes sense. We haven't told Inject, what dependencies to use. There is a `ComponentTest` interface that helps with writing unit tests. By implementing it, we get some BDD style default methods, to setup test dependencies:\n```java\nimport org.junit.jupiter.api.Test;\nimport org.jusecase.inject.ComponentTest;\n\nclass HelloWorldTest implements ComponentTest {\n    @Test\n    void test() {\n        givenDependency(\"hello\", \"Hello\");\n        givenDependency(\"world\", \"World\");\n        new HelloWorld();\n    }\n}\n```\n\nYou should now see this output: `\"Hello World\"`\n\n## Trainers aka Custom Mocks\n\nLet's have a look at a more interesting case than hello world. We want to write a small registration service.\n\n\u003e You find the code for this example in the test source package [org.jusecase.inject.classes.example2](src/test/java/org/jusecase/inject/classes/example2)\n\n```java\n@Component\npublic class RegisterNewsletter {\n    @Inject\n    private NewsletterGateway newsletterGateway;\n    @Inject\n    private EmailValidator emailValidator;\n\n    public void register(String email) {\n        emailValidator.validate(email);\n        try {\n            newsletterGateway.addRecipient(email);\n        } catch (DuplicateKeyException e) {\n            throw new BadRequest(\"This email address is already registered.\");\n        }\n    }\n}\n```\n\nWe also have a entity gateway for newsletter recipients (only emails for the sake of this example).\n```java\npublic interface NewsletterGateway {\n    void addRecipient(String email) throws DuplicateKeyException;\n    boolean isRecipient(String email);\n}\n```\n\nIt's an interface, because in our unit test we don't want to use the real thing. Let's write a custom mock for it.\n```java\npublic class NewsletterGatewayTrainer implements NewsletterGateway {\n    private final Set\u003cString\u003e emails = new HashSet\u003c\u003e();\n    \n    @Override\n    public void addRecipient(String email) throws DuplicateKeyException {\n        if (isRecipient(email)) {\n            throw new DuplicateKeyException(\"E-Mail already registered.\");\n        }\n        emails.add(email);\n    }\n    \n    @Override\n    public boolean isRecipient(String email) {\n        return emails.contains(email);\n    }\n}\n```\n\nLet's have a look how we can test this class. We really would like to tell Inject that we want to use the `NewsletterGatewayTrainer` as implementation for `NewsletterGateway`. We can do this by hand, like we did it in the Hello World example:\n\n```java\nclass RegisterNewsletterTest implements ComponentTest {\n\n    RegisterNewsletter registerNewsletter;\n\n    @BeforeEach\n    void setUp() {\n        givenDependency(new NewsletterGatewayTrainer());\n        registerNewsletter = new RegisterNewsletter();\n    }\n}\n```\n\nUsually you want to do stuff with your custom mocks in your unit tests. So there is a shorthand, to inject custom mocks in unit tests, by using the `@Trainer` annotation:\n\n```java\nclass RegisterNewsletterTest implements ComponentTest {\n\n    @Trainer // ComponentTest will instantiate this field and provide it as a dependency.\n    NewsletterGatewayTrainer newsletterGatewayTrainer;\n\n    RegisterNewsletter registerNewsletter;\n\n    @BeforeEach\n    void setUp() {\n        registerNewsletter = new RegisterNewsletter();\n    }\n}\n```\n\nThere is another dependency in this class, the `EmailValidator`. This is something we don't want to mock, thus we can simply provide it to the test by saying `givenDependency(new EmailValidator());`. The final test then may look something like this:\n\n```java\nclass RegisterNewsletterTest implements ComponentTest {\n\n    @Trainer\n    NewsletterGatewayTrainer newsletterGatewayTrainer;\n\n    RegisterNewsletter registerNewsletter;\n\n    @BeforeEach\n    void setUp() {\n        givenDependency(new EmailValidator());\n        registerNewsletter = new RegisterNewsletter();\n    }\n\n    @Test\n    void success() {\n        whenEmailIsRegistered(\"test@example.com\");\n        assertThat(newsletterGatewayTrainer.isRecipient(\"test@example.com\")).isTrue();\n    }\n\n    @Test\n    void alreadyRegistered() {\n        newsletterGatewayTrainer.addRecipient(\"test@example.com\");\n        Throwable throwable = catchThrowable(() -\u003e whenEmailIsRegistered(\"test@example.com\"));\n        assertThat(throwable).isInstanceOf(BadRequest.class).hasMessage(\"This email address is already registered.\");\n    }\n\n    @Test\n    void emptyMail() {\n        Throwable throwable = catchThrowable(() -\u003e whenEmailIsRegistered(\"\"));\n        assertThat(throwable).isInstanceOf(BadRequest.class).hasMessage(\"Please enter an email address\");\n    }\n\n    @Test\n    void nullEmail() {\n        Throwable throwable = catchThrowable(() -\u003e whenEmailIsRegistered(null));\n        assertThat(throwable).isInstanceOf(BadRequest.class).hasMessage(\"Please enter an email address\");\n    }\n\n    @Test\n    void invalidEmail() {\n        Throwable throwable = catchThrowable(() -\u003e whenEmailIsRegistered(\"email\"));\n        assertThat(throwable).isInstanceOf(BadRequest.class).hasMessage(\"email is not a valid email address\");\n    }\n\n    private void whenEmailIsRegistered(String email) {\n        registerNewsletter.register(email);\n    }\n}\n```\n\n## Nicer logging\n\nIn order to obtain a logger one usually does something like this:\n```java\nprivate static final Logger LOGGER = Logger.getLogger(MyService.class.getName());\n```\n\nWith Inject, you can register per class providers. They will provide a new instance for every class they are injected into. This is exactly what we need to inject loggers.\n\n```java\npublic class LoggerProvider implements PerClassProvider\u003cLogger\u003e {\n    @Override\n    public Logger get(Class\u003c?\u003e classToInject) {\n        return Logger.getLogger(classToInject.getName());\n    }\n}\n```\n\nYou need to register the provider like this, at the place you configure your production dependencies:\n\n```java\ninjector.addProvider(new LoggerProvider());\n```\n\nIn all your components, you can now simply inject a logger that will generate logs for this class:\n\n```java\n@Component\npublic class MyService {\n    @Inject\n    private Logger logger;\n    \n    public MyService() {\n        logger.info(\"This service got created.\");\n    }\n}\n```\n\nFor your tests you can now write a `LoggerTrainer`, that does not log at all. This has the benefit, that your CI build logs look very clean and are not cluttered with exceptions. And finally, you can use it to verify that a certain important message was logged. For instance, your test could look like this:\n\n```java\npublic class MyServiceTest {\n    @Trainer\n    LoggerTrainer loggerTrainer;\n    \n    @Test\n    void logging() {\n        new MyService();\n        loggerTrainer.thenInfoWasLogged(\"This service got created.\");\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcasid%2Fjusecase-inject","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcasid%2Fjusecase-inject","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcasid%2Fjusecase-inject/lists"}