Coder Social home page Coder Social logo

thorbenkuck / wiredi Goto Github PK

View Code? Open in Web Editor NEW
5.0 2.0 0.0 1009 KB

WireDI is a IOC-Framework, that bridges compile time and runtime dependency injection

Home Page: https://docs.thorbenkuck.de/wiredi

License: Other

Java 100.00%
aspects dependency-injection inversion-of-control ioc-container ioc-framework

wiredi's Introduction

WireDI

header

Links:

This project started out as a proof of concept (originally named SimpleDI), to show that dependency injection (or more precisely IOC) could be done at compile time, while keeping the feature set from reflections. This POC grew up to become WireDI, a compile time oriented framework, that still allows for runtime interactions.

It is full of all the features that you might know from existing IOC frameworks, but integrates the best of runtime reflection based operations into compile time, reducing the startup overhead. It does this and still allows for runtime adjustments.

But let's start at the important questions:

What does this framework do exactly?

In its core, this framework is an IOC container. You add one annotation to a class you want to be wired by this framework (namely @Wire) like this:

@Wire
class YourClass {
    // Fields, methods, constructors etc.
}

and then you can extract the class with all wired classes like this:

WireRepository wireRepository = WireRepository.open();
MyClass instance = wireRepository.get(MyClass.class);

All classes that you want to connect in this fashion have to be annotated with @Wire.

Yeah, I have seen this before... So, how does this framework differ from other frameworks?

Instead of retrieving annotated classes, constructing AST and proxies at startup, WireDI pre-compiles this information at compile time. Multiple annotation processors pick up different annotations and create certain other instances of suppliers.

This means, that as you call WireRepository.open(), all proxies already exist, all dependency requirements are analyzed and all qualifiers are correctly set.

Though precompiled, the data are not static. You can modify, change, remove or even manipulate the process. In total, it pre-analyzes the AST, but does not generate static IOC.

Ah, so it is one of those frameworks...

Yes and no. It does a lot of work at compile time, but as a state, not completely. Instead, it extracts the reflection heavy operations and does them at compile time. After compile time, it neatly snuggles into a more traditional framework. You can add custom instances, custom Aspects and other relevant information all at runtime.

It is not recommended, but you can certainly use it to do everything at runtime. And the combination of compile time and run time support allows us to enter a whole new dimension. We can integrate other IOC frameworks into this once with relative ease. Already existing frameworks, can benefit from the power of annotation processors and the already existing ecosystem of runtime DI and IOC.

We can go even as far as declaring aspects at runtime, even though the proxies are build at compile time. But let us start from the beginning:

Quick Start

To use WireDi, you need two things: The runtime environment and the processors.

1) Runtime Environment

Add the runtime environment like this:

<dependency>
    <groupId>com.wiredi</groupId>
    <artifactId>runtime-environment</artifactId>
    <version>${revision}</version>
</dependency>

This dependency will introduce the requirements to use the IOC container WireRepository, like this:

import com.wiredi.runtime.WireRepository;

public class Example {
    public static void main(String[] args) {
        WireRepository iocContainer = WireRepository.open();
    }
}

Additionally, to make classes available to the IOC container, this dependency also introduces the @Wire annotation, that you use like this:

import com.wiredi.annotations.Wire;

@Wire
public class MyDependency {
    // Members
}

2) Processors

The runtime environment itself will allow you to use the IOC container. To fill it, you will additionally need the processors. They need to be accessible to the compiler; There is no need to hold them in the class path for the runtime.

You can include them using the maven-compiler-plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <parameters>true</parameters>
        <annotationProcessorPaths>
            <annotationProcessorPath>
                <groupId>com.wiredi</groupId>
                <artifactId>processors</artifactId>
                <version>${wiredi.version}</version>
            </annotationProcessorPath>
        </annotationProcessorPaths>
    </configuration>
</plugin>

You require hooking up one annotation processor and the api, as well as the core api. You can do that simply, by adding this parent to your pom.xml

<parent>
    <groupId>com.wiredi</groupId>
    <artifactId>bootstrap</artifactId>
    <version>${revision}</version>
    <relativePath/> <!-- Always look up parent from repository -->
</parent>

This parent introduces the wire-di annotation processor and wire-di-runtime-environment, which allows you at run time to utilize the data generated at compile time.

For other forms of introducing this library to your application, see the usage section of the documentation.

Basic usage

The central annotation to mark classes as "I want to be able to be handled for di" is called Wire. This annotation marks a class as "Injection enabled," allowing the framework to inject it into other classes and other classes into it.

A basic example would look like this:

@Wire
class A {
    private final B b;
    
    public A(B b) {
        this.b = b;
    }
}

@Wire
class B {}

public class Main {
    public static void main(String[] args)  {
        WireRepository wireRepository = WireRepository.open();
        A a = wireRepository.get(A.class);
        // Do fancy stuff
    }
}

In this example right here, we have two classes, A and B and A has a dependency to B. Since both classes are marked with@Wire, the framework can identify these classes and inject them safely into each other.

As you run this code, a lot happens behind the curtains. The annotation processor generates instance of IdentifiableProvider classes, which you can even see in the compiled sources. These providers hold a sum of static information about this class, that are then used at runtime; Including the template as to how this class is created.

Properties

Order of Properties:

  • default properties (application.properties)
  • additional properties (key=load.additional-properties)
  • profile properties (application-.properties)
  • OS Environment Variables
  • System Properties

wiredi's People

Contributors

thorbenkuck avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

wiredi's Issues

[Feature]: Add "Compile Time" and "Runtime" lifecycle scopes

Type of feature request

A completely New Feature

What kind of feature would you like to see

The annotation processor can, in a lot of situations generate code that is kind of "pre-compiled".

For example: If you have any dependencies in a method annotated with @aspect, the annotation processor will generate an instance of the AspectHandler interface and declare these as constructor parameters, hence declaring them as required parameters.

In contrast to that, you might be tempted to not have them as a constructor parameter, but fetch them from the WireRepository lazily and on demand.

This can be used, if classes are commonly exchanged in the BeanContainer during runtime.

Code Examples

public class MyHandler {
    @Aspect(around = Example.class)
    @LifecycleScope(COMPILE)
    public Object handle(ExecutionContext<Example> context, MyService myService) {
        // ...
    }
}

Will compile to:

@Wire
@Generated
public final class MyHandler$handle$AspectHandler implements AspectHandler<Example> {
   private final MyHandler delegate;
   private final MyService myService;

   protected TransactionalHandler$handle$AspectHandler(
                MyHandler delegate,
                MyService myService
    ) {
      this.delegate = delegate;
      this.myService = myService;
   }

   @Override
   @Nullable
   public final Object process(@NotNull final ExecutionContext<Transactional> context) {
      return delegate.handle(
               context,
               myService
            );
   }
}

whilst

public class MyHandler {
    @Aspect(around = Example.class)
    @LifecycleScope(RUNTIME)
    public Object handle(ExecutionContext<Example> context, MyService myService) {
        // ...
    }
}

Will compile to:

@Wire
@Generated
public final class MyHandler$handle$AspectHandler implements AspectHandler<Example> {
   private final MyHandler delegate;
   private final WireRepository wireRepository;

   protected TransactionalHandler$handle$AspectHandler(
                MyHandler delegate,
                WireRepository wireRepository
    ) {
      this.delegate = delegate;
      this.wireRepository = wireRepository;
   }

   @Override
   @Nullable
   public final Object process(@NotNull final ExecutionContext<Transactional> context) {
      return delegate.handle(
               context,
               wireRepository.require(MyService.class) // fetch the dependency on demand
            );
   }
}

Feature Design

No response

[Feature]: Support Nullable injection points

Type of feature request

Extension of an existing feature

What kind of feature would you like to see

Currently all injection points are not nullable. This means that every injection point is required to be present.

There are some esoteric, or non-standard use cases where you'd like to support Nullable Injection Points. Meaning that the injection process does not fail if the IOC container cannot find a matching bean instance for the type.

For this feature, all injections points should be able to support @nullable. If @nullable is present on any injection point, instead of failing on wiring, it should inject null.

Code Examples

@Wire
public class MyExample {
    @Inject
    @Nullable
    private MyClass myClass;

    public MyExample(@Nullable MyClass myClass) {
    }

    @Inject
    public void inject(@Nullable MyClass myClass) {
    }
}

Feature Design

The annotation processor should provide Identifiable Provider instances, that injects null, if the injection point is not present.. Should look something like this in the IdentifiableProvider:

MyClass parameter0 = wireRepository.tryGet(MyClass.class).orElse(null)

Note: Qualifiers should still work with this.

[Feature]: Support for qualifiers and property injection for the @Aspect

Type of feature request

Extension of an existing feature

What kind of feature would you like to see

The @aspect Annotation allows for auto-creation of an AspectHandler during compile time. The advantage of this is, that you can inject any class dynamically into the method.

However, at the current point in time, this only supports singular parameters. It is missing:

  • List of features
  • Qualified features
  • Properties
  • Resolvables

Code Examples

public class MyHandler {
    @Aspect(around = Example.class)
    public Object handle(
                  ExecutionContext<Example> context,
                  @AwesomeQualifier MyService myService, 
                  List<MyService> allServices),
                  @Resolve("${remote.port:5005}") int remotePort,
                  @Property(name = "my.test.property") String testProperty
    {
        // ...
    }
}

Feature Design

No response

Add liniting

Quite simple. Add some linter to the pom to ensure code-style.

[Bug]: Test

What happened?

A bug happened!

Version

beta

Minimal, complete and verifiable example

asdasd

Root cause

asdasd

Relevant log output

asdasd

Contact Details

No response

[Feature]: Support for Annotation Processor Plugins

Type of feature request

A completely New Feature

What kind of feature would you like to see

It should be possible, to write plugins for the annotation processor. These plugins should configure the behavior the annotation processors and allow for customization of these. Concept work should be done in this ticket.

Code Examples

No response

Feature Design

No response

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.