The classes defined within this repository have been part of the bonaparte java implementation, but in fact do not relate to the DSL and/or serialization / deserialization functionality and therefore have been separated into this repository for better modularity.
Classes which might be useful in other projects are
- ByteBuilder, a corresponding class to StringBuilder, but for byte arrays
- ByteArray, a corresponding class to java's String class as implemented in Java 5
- xenums, enum based classes which allow some kind of inheritance
- enum sets, with access to the bitmap representation
- enum sets which are treated as character arrays / strings
- ApplicationException, some kind of Exception which supports an error number, which can be used to index the messages and categorize the exceptions by core reason.
In addition, this repository serves as a parent pom / bom, defining some revisions of libraries used in other projects.
This project uses maven3 as a build tool. For security reasons, an up to date maven version is enforced (3.8.5 upwards).
The library requires Java 8 minimum, and works with Java 11 and Java 17 as well.
JDP intends to be used as a lean alternative to classic dependency injection frameworks such as guice or CDI. It does not attempt to implement a full JSR 330 or 299 standard.
JDP provides the following annotations:
-
Inject (only for xtend) - transparently invokes the provider. @Inject @Any and @Inject @Optional are supported as well.
-
Singleton (scope) - the provider returns the same instance every time
-
Dependent (scope) - the provider returns a new instance every time
-
PerThread (scope) - the provider returns a different instance per thread
-
ScopeWithCustomProvider (scope) - the provider class is specified by the application as an annotation parameter
-
Named (qualifier) - assigns the class to a specific category of implementations
-
Specialized (qualifier) - overrides the implementation of any inherited subclass
-
Alternative (qualifier) - prevents the annotated class to be used unless it is the only one for the category
-
Startup - specifies methods to be invoked at initialization time, in a defined order
JDP offers the following features:
-
Autodetection of classes, as with CDI. No need to code modules as in guice.
-
No need for a no-args constructor. In fact you can create classes with a regular new().
-
No use of proxies as in CDI. This allows you to have final methods and classes.
-
Modifying object resolution at runtime. Fields injected before will continue to have their original value, though.
JDP does not work with cyclic dependencies. I consider that bad design anyway.
JDP is intended to be used on Xtend source code. @Inject is an active annotation which provides the annotated field with an initializer, which is just a library call to obtain the desired value. That simple, but sufficient for some of my applications. The xtend portion will be split into a separate project and therefore optional in the next revision.
It offers enough features to replace CDI in some multi-million code line projects. The class repository is kept in a static class however and therefore Jdp should only be used in projects where you provide the main() entry point as well.
Let's look at some example code. The code is in xtend, which is very similar to Java, and everything except the @Inject will look exactly the same in Java.
package de.jpaw.dp.alternatives
import de.jpaw.dp.Alternative
import de.jpaw.dp.Inject
import de.jpaw.dp.Jdp
import de.jpaw.dp.Singleton
import de.jpaw.dp.Specializes
// definition of an interface. Note that the interface itself is not annotated, same as in guice, CDI etc.
interface Car {
def void drive();
}
@Alternative
@Singleton
class DumboBuggyShouldNotBeSelected implements Car {
override drive() {
println("no, I am an alternative!")
}
}
@Singleton
class BaseCarShouldNotBeSelected implements Car {
override drive() {
println("no, I was overridden!")
}
}
@Specializes
@Singleton
class MegaCarShouldBeSelected extends BaseCarShouldNotBeSelected {
override drive() {
println("I'm driving mega car!")
}
}
@Alternative
@Singleton
class AnotherDumboBuggyShouldAlsoNotBeSelected implements Car {
override drive() {
println("no, I am an alternative!")
}
}
@Alternative
@Singleton
class SomeCarWhichIsDrivenLater implements Car {
override drive() {
println("I'm doing the last round")
}
}
class MainTestMain {
def static void main(String [] args) {
Jdp.init("de.jpaw.dp.alternatives");
// access from Java code
Jdp.getRequired(Car).drive();
// access from xtend
new SomeOtherClass().driveOtherCar
// modify the preference
Jdp.bindClassWithoutQualifier(SomeCarWhichIsDrivenLater, Car)
// should result in a different one used from now on...
new SomeOtherClass().driveOtherCar
}
}
class SomeOtherClass {
@Inject
Car myCar
def void driveOtherCar() {
myCar.drive
}
}
Running the code will produce
I'm driving mega car!
I'm driving mega car!
I'm doing the last round
This is a simple example of how a custom scope could be defined.
package de.jpaw.dp.customprovider
import de.jpaw.dp.CustomScope
import de.jpaw.dp.Jdp
import de.jpaw.dp.ScopeWithCustomProvider
interface Activity {
def void doSomething();
}
// a scope which returns the same instance per day, but a new one the next day.
// for demonstration purposes, it does this for a second instead of a day
// This is for demonstration purposes, the shown class is not thread safe!
class DailyScope implements CustomScope<Activity> {
private Activity currentActivity = null;
private long whenSet = -1L;
// defines the returned activity to be a specific one, from now on...
override set(Activity instance) {
currentActivity = instance;
whenSet = System.currentTimeMillis / 1000
}
override close() {
// hands over resources to GC
currentActivity = null;
whenSet = -1;
}
override get() {
val now = System.currentTimeMillis / 1000
if (currentActivity !== null && whenSet == now) {
// there is a defined activity, and it was set within this second. Return it.
return currentActivity
}
// create a new activity. Call some factory, or just create a new instance for this simple demo
whenSet = now
currentActivity = new Reading();
return currentActivity
}
}
@ScopeWithCustomProvider(DailyScope)
class Reading implements Activity {
private int howOften = 0
override doSomething() {
howOften += 1
println('''I'm reading page «howOften»''')
}
}
class SimpleCustomScopeDemo {
def static void main(String [] args) {
Jdp.init("de.jpaw.dp.customprovider")
for (var int i = 0; i < 10; i += 1) {
Jdp.getRequired(Activity).doSomething
Thread.sleep(300) // pass some time...
}
}
}
Running the code will produce something similar to
I'm reading page 1
I'm reading page 1
I'm reading page 2
I'm reading page 3
I'm reading page 1
I'm reading page 2
I'm reading page 3
I'm reading page 1
I'm reading page 2
I'm reading page 3
Every time page 1 is read, a new instance of the object has been created.
The following additions are planned:
-
Some file format to allow selection of @Alternatives via configuration file (interface[:qualifier]=implementation)
-
Startup also allowing for instance methods instead of static methods only
-
Shutdown operations to be scheduled by the Startup beans, and executed in reverse order of registration
It is not planned to achieve compatibility to any JSR. There are good existing implementations if you want that, like guice or weld. Therefore all annotations used by Jdp are in a different package on purpose and Jdp does not understand javax.inject.* annotations.
It is also not planned to support injecting objects of a shorter lived scope into a longer one, like a PerThread object into a Singleton. This can only be achieved via proxies, which is exactly one of the key goals of this project to to have any. In such a case, what you really want is to inject a provider, and that is what you should code.