项目作者: fabioCollini

项目描述 :
Retrieve easily all the implementations of an interface
高级语言: Kotlin
项目地址: git://github.com/fabioCollini/Inversion.git
创建时间: 2019-07-17T15:06:41Z
项目社区:https://github.com/fabioCollini/Inversion

开源协议:Apache License 2.0

下载


Inversion CircleCI codecov

Inversion simplifies the ServiceLoader usage to retrieve all the implementations of a certain interface.
Using Inversion it’s easy to use the dependency inversion in a multi module project.

Basic example

A first module defines an interface and a create field (annotated with InversionDef) to create the real implementation of the interface:

  1. interface MyInterface {
  2. fun doSomething(): String
  3. companion object {
  4. @get:InversionDef
  5. val create by Inversion.of(MyInterface::class)
  6. }
  7. }

The create field is a () -> MyInterface lambda, it can be used to create a new instance. The first module
doesn’t contain any real implementation of MyInterface.

A second module defines the real implementation annotated with InversionImpl:

  1. @InversionImpl
  2. class MyImpl : MyInterface {
  3. override fun doSomething() = "Hello world!"
  4. }

And that’s all! Now the real implementation can be retrieved invoking create:

  1. @InversionValidate
  2. class MainActivity : AppCompatActivity() {
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. setContentView(R.layout.activity_main)
  6. val impl = MyInterface.create()
  7. findViewById<TextView>(R.id.text).text = impl.doSomething()
  8. }
  9. }

Custom implementation creation

In case an extra logic is needed to create the real implementation, an InversionProvider annotated method can be used:

  1. @InversionProvider
  2. fun provideImpl(): MyInterface = MyImpl()

If a parameter is needed the InversionDef annotated property can be defined as an extension property:

  1. interface MyInterface {
  2. fun doSomething()
  3. }
  4. @get:InversionDef
  5. val Application.factory by Inversion.of(MyInterface::class)

In this example the Application instance can be used to create the implementation in the InversionProvider
annotated method:

  1. class MyImpl(val app: Application) : MyInterface {
  2. override fun doSomething() {
  3. //...
  4. }
  5. }
  6. @InversionProvider
  7. fun Application.provideImpl(): MyInterface = MyImpl(this)

Multi binding

Multiple implementations can be managed using the mapOf method instead of of:

  1. @get:InversionDef
  2. val allValues by Inversion.mapOf(MyInterface::class)

In this way the allValues field is a () -> Map<String, MyInterface> that can be used to retrieve a map with all the implementations.

Multiple implementations can be defined specifying in the annotation a String that will be used as the key in the map:

  1. @InversionProvider("A")
  2. fun MyClass.provideImplA(): MyInterface = MyImplA()
  3. @InversionProvider("B")
  4. fun MyClass.provideImplB(): MyInterface = MyImplB()

A multi binding example is available in this plaid fork, here the commits
that introduces Inversion and removes some reflection calls.

Internal implementation

Under the hood Inversion is an annotation processor that generates the configuration to simplify the ServiceLoader usage. ServiceLoader is
a standard Java class that can be used to discover the implementations of an interface based on some config file. An explanation on how to
use a ServiceLoader on Android is available in the post
Patterns for accessing code from Dynamic Feature Modules,
here there are the commits to introduce
Inversion in the demo project.

ServiceLoader reads some resource files to retrieve the implementations to use, on Android the disk read can be avoided using R8.
R8 removes the ServiceLoader calls and replaces it with the direct instantiations. Looking at the code of the last example
in the apk optimized with R8 we can find the following code instead of the ServiceLoader invocation:

  1. Iterator it = Arrays.asList(new MultiInstanceInterface_Factory[] {
  2. new MultiInstanceInterface_FactoryImpl_B(),
  3. new MultiInstanceInterface_FactoryImpl_A()
  4. }).iterator();

MultiInstanceInterface_FactoryImpl_A and MultiInstanceInterface_FactoryImpl_B are two classes generated by Inversion.

Validation

The Inversion annotation processor executes some basic validations to raise a compilation error in case of any misconfiguration.
A validate method in the Inversion object can be invoked to verify that there is an implementation defined for each definition.
This method uses reflection calls so it’s better to invoke it in a test or in the application startup only in the debug build.

JitPack configuration

Inversion is available on JitPack,
you can use it adding the JitPack repository in your build.gradle (in top level dir):

  1. repositories {
  2. maven { url "https://jitpack.io" }
  3. }

and the dependencies in the build.gradle of the modules:

  1. dependencies {
  2. kapt 'com.github.fabioCollini.inversion:inversionCodGen:0.3.1'
  3. implementation 'com.github.fabioCollini.inversion:inversionLib:0.3.1'
  4. }

License

  1. Copyright 2019 Fabio Collini
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.