Minimalistic Dependency Injection - Or: what I got wrong about dependency injection. dinistiq is a minimalistic approach to component based application setup done in Java. With only few external dependencies and a very small footprint it is based on the JSR 330 annotations for the Java SE using only one scope with mostly singletons.
This project is maintained by mgoellnitz

A small footprint approach to dependency injection with a framework or container implemented in Java.
Or: What I got wrong about DI
Minimalistic library to use dependency injection for the wire-up of software components. It thus mostly deals with singletons - some of them implementing interfaces - which should be injected as dependencies into one another taking into account their name, qualifier.
As the only other option besides the single scope managed by dinistiq it allows for the creation of fresh instances with all dependencies filled in from the scope of all beans collected.
The canonical home of the source code is the repository at Codeberg with mirrors at GitLab and GitHub. When possible, please prefer references to Codeberg.
Feel invited to use the issues section of this repository at Codeberg for any kind of feedback.
Dinistiq scans a given portion of the classpath for classes annotated with JSR330 Annotations. It does not introduce any custom annotations.
The missing bits can be configured by a set of properties files, describing
Firstly, the most important thing to use dinistiq is to annotate your dependencies with JSR330 @Inject so that dinistiq can find out which components are needed.
public class TestComponentB {
@Inject
public TestInterface test;
} // TestComponentB
The required dependency for you package will be javax:inject:1 for early
version os dinistiq, and jakarta.inject:jakarta.inject-api:2.0.1 and ulp
later.
In the next step, dinistiq resolves those components from the auto-scanned portion of the classpath where it instantiates all classes annotated with @Singleton. Optionally, these components may be named with @Named (with an optional name as the value parameter). Without a name given as a parameter, components are always named after their class name without the package name and a decapitalized first letter.
@Singleton
public class TestComponent implements TestInterface {
} // TestComponent
Thus, in this example, the instantiated bean of class TestComponent will be
available with the name testComponent. The term “name” is used in this
document, since it is used as the parameter name in the JSR330 annotations.
Since names must be unique within the scope they are in this case in fact
identifiers throughout the whole process.
If you are dealing with components of the same type, not only the beans may be named but also the injection point might indicate to require a bean with a certain name.
public class ConfigStuff {
@Inject
@Named
public String filename;
@Inject
@Named("prefix")
public String somePrefix;
} // ConfigStuff
In this case, filename is searched as a String component with the name “filename”, while somePrefix has a specific named annotation with value “prefix”.
This complete set-up is done without any configuration for dinistiq itself but only for the components to be used.
JSR330 specifies the concept of qualifiers to select which implementation of an interface is to be chosen. Unfortunately this means, that the injection point defines which implementation is chosen in the Java code. So we recommend not to use this in your code but add configuration files (see below) to control the selection of implementing classes outside the code.
You may define annotations describing qualifiers
@Qualifier
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestQualifier {
} // TestQualifier
and these qualifiers may be used to define injection criteria at the injection points.
public class QualifiedInjection {
@Inject
@TestQualifier
private TestInterface testInterface;
} // QualifiedInjection
Only implementations annotated with the given qualifier are taken into account when performing the injection.
@Singleton
@TestQualifier
public class QualifiedComponent implements TestInterface {
} // QualifiedComponent
If this is not enough, you can explicitly add some beans to be instantiated in properties files.
unannotatedComponent=dinistiq.test.components.UnannotatedComponent
Those files must simply be put in the folder dinistiq/ anywhere on your
classpath. This example will instantiate the class
dinistiq.test.components.UnannotatedComponent and store this bean with the
name unannotatedComponent in the set of available beans.
The properties files are scanned in alphabetical order, so you can override the
class for e.g. unannotatedComponent in a latter properties file, so classes
given for bean names used in mybeans.properties can be overridden in
override-mybeans.properties.
For any of the instantiated beans, you can provide more values to explicitly inject - again by the use of properties files.
After instantiation of the bean, a properties file with the bean’s name as its
base filename is searched - first in the dinistiq/defaults/ and then in the
dinistiq/beans/ folders on the classpath. Thus, you can deliver your components
with reasonable defaults and necessary overrides for the specific application.
file dinistiq/beans/example.properties:
activateCaching=true
This will call the property setter setActivateCaching() on the bean named
example. The grammar of the properties files describing the explicit injection
supports set and list type collections, boolean values, numeric value, strings,
and references to other beans.
file dinistiq/beans/example.properties
# numerics
intValue=42
longValue=123456789
floatValue=3.14159
doubleValue=2.7
# references
testInterface=${testComponent}
# strings and references in compound strings
replacement=a string
replacementTest=here comes ${replacement}
The bean named example is either a result of the automatic discovery of a class
named Example
@Named
@Singleton
public class Example {
} // Example
a name declaration from the scanned class
@Named("example")
@Singleton
public class ExampleComponent {
} // ExampleComponent
or taken from the naming in a configuration properties file
file dinistiq/demo.properties
example=some.package.ExampleComponent
At the top level, the types of the beans cannot be inferred, as the example
unannotatedComponent=dinistiq.test.components.UnannotatedComponent
showed. If you need some typical configuration types at this level - like e.g.
Strings, Booleans, Lists, and Maps, some extensions of this mere class based
syntax had to be introduced. Any simple type found in the java.lang package
can be instantiated with a value bound to it, since these values are immutable
and there are thus no modifiable fields or setters in these classes.
Some examples for this are:
booleanValue=java.lang.Boolean("false")
stringValue=java.lang.String("string value")
integerValue=java.lang.Integer(42)
While
mapTest=java.util.Map
creates an empty map instance. Like with any other bean, the contents of this map can be modified by a properties file. In this case, the contents of the properties file’s key / value pairs will be used as content for the whole map.
Lists of strings can be created by
listTest=java.util.List(first,second)
Extend your project with the dependency to the rather small dinistiq library
file. Dinistiq releases are available from Maven Central. The group id and
artifact id are both dinistiq.
Thus, for projects built with Gradle you will need to add the following lines to the repositories sections of the build file:
maven {
mavenCentral()
}
Older releases where provided through the discontinued JCenter facility and can still be obtain like with:
maven {
name = "JCenter Remains"
url = 'https://releases.jfrog.io/artifactory/oss-releases/'
}
And the dependencies section need the addition of the following dependency description line:
dependencies {
...
implementation "dinistiq:dinistiq:0.9"
...
}
Projects built with Apache Maven need the following steps:
module pom.xml
...
<dependencies>
...
<dependency>
<groupId>dinistiq</groupId>
<artifactId>dinistiq</artifactId>
</dependency>
...
</dependencies>
base pom.xml
...
<dependencyManagement>
...
<dependency>
<groupId>dinistiq</groupId>
<artifactId>dinistiq</artifactId>
<versions>0.9</version>
</dependency>
...
</dependencyManagement>
Dinistiq uses slf4j for logging and logback as an instance for testing.
Snapshot artifacts - currently for version 1.0-SNAPSHOT - are available from the respective GIT repository sites like e.g. Codeberg:
https://codeberg.org/api/packages/backendzeit/maven
Apart from optional configuration files to be placed somewhere on your classpath, you simply have to tell dinistiq which portion of the classpath to scan for annotations.
public class Test {
public static main(String[] args) {
Set<String> packages = new HashSet<String>();
packages.add(Test.class.getPackage().getName());
Dinistiq d = new Dinistiq(packages);
} // main()
} // Test
Make this portion of the classpath as small as ever possible, or point to some invented and thus empty package, if you want to avoid scanning.
After this step, you can ask dinistiq for instances of the components it created and injected.
public class Test {
public static main(String[] args) {
Set<String> packages = new HashSet<String>();
packages.add(Test.class.getPackage().getName());
Dinistiq d = null;
try {
d = new Dinistiq(packages);
} catch (Exception e) {
//
} // try/catch
TestInterface ti = d.findTypedBean(TestInterface.class);
TestInterface test = d.findBean(TestInterface.class, "test");
Set<TestInterface> tis = d.findTypedBeans(TestInterface.class);
} // main()
} // Test
Dinistiq comes with a very lean web integration. An ordered list of beans implementing the servlet interface will be registered directly with the servlet container.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>dinistiq.web</display-name>
<listener>
<listener-class>dinistiq.web.DinistiqContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>dinistiq.packages</param-name>
<param-value>com.example.components,org.example.components</param-value>
</context-param>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
The context loader listener tries to find the servlets from the dinistiq context by asking for RegisterableServlet instances.
/**
* Servlets which should handle requests fulfilling a certain regular expression for their uris.
*/
public interface RegisterableServlet extends Servlet, Comparable<RegisterableServlet> {
/**
* Returns a set of url patterns this servlet should be registered for.
*/
Set<String> getUrlPatterns();
/**
* Indicator if the implementing instance should be considered earlier or later
* in the servlet selection process.
*/
int getOrder();
} // RegisterableServlet
So a servlet has to tell which url patterns its requests should meet, to be able to handle them. Additionally, it tells an order number to sort all available servlets to provide a certain precedence rule for them.
It is perfectly possible that you will find our class resolving pretty dumb. So we provide the option to pass over a class resolver instance to dinistiq instead of the set of package names.
public class Test {
public static main(String[] args) {
Set<String> packages = new HashSet<String>();
packages.add(Test.class.getPackage().getName());
packages.add(Dinistiq.class.getPackage().getName());
Dinistiq d = null;
ClassResolver classResolver = new BetterClassResolver(packages);
try {
d = new Dinistiq(classResolver);
} catch (Exception e) {
//
} // try/catch
} // main()
} // Test
Be sure to add the package dinistiq in these cases, as shown above. Otherwise for obvious reasons, the properties files from the dinistiq path cannot be found as resources to be taken into consideration.
If you want to use custom class resolvers with the web integration, you need to implement a class resolver taking the set of package names as the single parameter to the constructor and put the name of this implementing class in the context loader listener configuration for dinistiq.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>dinistiq.web</display-name>
<listener>
<listener-class>dinistiq.web.DinistiqContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>dinistiq.packages</param-name>
<param-value>com.example.components,org.example.components</param-value>
</context-param>
<context-param>
<param-name>dinistiq.class.resolver</param-name>
<param-value>org.example.dinistiq.BetterClassResolver</param-value>
</context-param>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
Within the web application, all beans from the dinistiq scope are available in the application scope (servlet context) as attributes.
If your software needs to use some components which cannot be instantiated or obtained using all of the means presented here, you can pass over a named set of instances as a base set of beans for dinistiq to add the scanned and configured beans to.
We use this to e.g. put the servlet context in the set of beans for web integration (see below).
public class DinistiqContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent contextEnvironment) {
ServletContext context = contextEnvironment.getServletContext();
Set<String> packages = new HashSet<String>();
...
try {
Map<String, Object> externalBeans = new HashMap<String, Object>();
externalBeans.put("servletContext", context);
Dinistiq dinistiq = new Dinistiq(packages, externalBeans);
context.setAttribute(DINISTIQ_INSTANCE, dinistiq);
} catch (Exception ex) {
LOG.error("init()", ex);
} // try/catch
} // contextInitialized()
} // DinistiqContextLoaderListener
Dinistiq defines just one scope of beans you can grab beans from. If you need
fresh instances of beans where the members of this scope should be injected on
creation and optional post construct methods should be called just following the
same rules as the beans from the dinistiq scope, you will find a createBeans()
method besides all the options to find existing beans in the scope.
My myNewInstance = dinistiq.createBean(My.class, null);
If this is still no option, you can - like with external beans - provide instances externally and let dinistiq still handle their injections and post construct methods.
My myNewInstance = new My();
dinistiq.initBean(myNewInstance, null);
While dinistiq 0.4 happily works with Java 8, only dinistiq 0.5 and up can be compiled and tested with Java 8. The old PaaS Google App Engine is only supported with dinistiq 0.4.
Up to dinistiq 0.5 the code is supposed to be written in Java 7 with a subsequent switch to Java 8. This also results in the fact, that the classic version of Google App Engine is only supported up to version 0.4.
Due to the discontinuation of JCenter the main publication target was lost and the subsequent slow down in development makes faster changes for the intended environments desirable.
The code for dinistiq is prepared for building with Gradle. Since version 0.7, the Gradle wrapper is in use locally and in CI. Breaking changes in DSL are fixed synchronously on Gradle updates.
| dinistiq Version | Works with | Compiles with | Servlet API |
|---|---|---|---|
| 0.4 | Java 7 / 8 | Java 7 | 2.5 |
| 0.5 | Java 7 / 8 | Java 7 / 8 | 3.1 |
| 0.6 | Java 8 | Java 8 | 3.1 |
| 0.7 | Java 8 | Java 8 | 3.1 |
| 0.8.1 | Java 8 | Java 8 | 4.0 |
| 0.9 | Java 11 | Java 11 | 5.0 |
| 1.0 | Java 17 | Java 17 | 6.1 |
The developer of SilkDI presents an interesting comparison of some DI implementations done in Java and we want to add some values for dinistiq to this list:
| Library | dinistiq |
|---|---|
| Version | 0.9 |
| Archive size | <27kB |
| Further dependencies | 4 |
| API | |
| Methods in injector/context | 10 |
| Concept | |
| Container Model | flat instances |
| Configuration style | annotation, properties |
| Wiring style | automatic scan |
| Types | |
| Generics support | limited |
| Generic type safety | - |
| Wildcard generics | - |
| Primitive types handling | - |
| Bind to all (generic) supertypes | yes |
| Type link | - |
| Injection | |
| Annotation guidance | only JSR330 |
| Constructor injection | yes |
| Field injection | yes |
| Setter injection | yes |
| Factory methods | no |
| Static injection | yes |
| Method interception | no |
| Providers | (limited) |
| Optional injection | yes |
| Mixed injection | ? |
| Post construction hook | yes |
| Modularity | |
| Arrays | no |
| Collections | partly |
| Multibinds | yes |
| Sequence of declarations | undefined |
| Scopes | limited |
| Default scope | Singleton |
| Custom scopes | - |
| Available scopes | Singleton |
| Error behaviour | |
| Dependency cycles | illegal |
| Detection of a cyclic dependency error | runtime |
The closest competitor of dinistiq seems to be TinyDI - https://code.google.com/p/tinydi/. It recognises JSR330 Annotation but seems to lack the option of config files like the simple properties file mechanism of dinistiq. Additionally - unlike Spring and dinistiq - it depends on public setters for the injections. Private members with the @Inject annotation are not enough. Also, it is fairly unmaintained for some years now.
Another option I ran into is Feather described in this article. It lacks too many injection options to be usefull for the injection scenarios presented here like injecting after instantiation with a @PostConstruct method to complete initialization.
I rather apologise to introduce another Dependency Injection Container for the Java world - dinistiq - a very minimalistic approach to the topic. It turned out to be easier to implement another one, than to use others listed here. Limited in features, easy to use, and still more configurable than other options I could think of. After some months of use, I now can invite other users to take a look at it and try it in their own projects.
Also this text gives you a “why” on the use of the JSR330 annotations for Dependency Injection. It simply makes your code even more reusable in case your development or deployment environment changes.
Since tangram is much more about glueing together proven existing software components and frameworks than writing code, I felt the need to check if the existing code base was really fully dependent on the Spring Framework.
Despite the fact that spring more or less in many ways does what I need, it sometimes feels a bit bloated and does too much magic I don’t understand in detail (which I still had to learn when debugging things). So I tried to isolate the spring code during the tangram 0.9 work and present at least a second solution for all the things I did with spring so far.
For tangram spring does three things
So I took a look at other view frameworks like Vaadin, GWT, Apache Wicket, Play, Struts, JSF/JEE, Stripes. Right at the moment I think Vaading, GWT, Wicket, and Play are no really good fit for tangram, Struts in my eyes is a fading technology, and only JSF/JEE is an obvious option. With Java Server Faces I only had unsatisfying project experiences and the rest of JEE goes for plain Servlet. So tangram had to be provided with a plain servlet way of doing the view layer.
Since the modularity of tangram was achieved by the Spring way of plugging components together with Dependency Injection, the first thing to do was, to mark the generic components in a spring independent way and to look at the other options for the Dependency Injection part. Only then it would be possible to replace the spring view layer with a servlet view layer during the startup and wire-up of the application.
So the list of relevant DI frameworks gets shortened to those supporting the generic Dependency Injection annotations from JSR330 which are intended for JEE and can e.g. also be used with Google Guice and the Spring Framework alike.
From the reading Google Guice seemed to be a good alternative for the proof of concept phase, but it took me that much work to get something to run with it (not everything can be plugged together programmatically in my case), that I came out faster with my own Dependency Injection Container. Rather minimalistic and only suited for the setup of components.
Its advantage over Guice is that it’s smaller and easier to configure with properties files. Weeks later I discovered TinyDI as another option. While this container seems to be a lot cleverer about the search of annotated classes, it seems to lack the needed option of extending the configuration aspects from the annotations with properties files - defaults and overridden values and references.