Gimple is a small Dependency Injection Container for Groovy, Java and more generally for JVM. Consists of just one class ( ... maybe two ) and it's inspired (and in many points carbon-copied) from the PHP Pimple project
Homepage: https://fbn.github.io/gimple/
- Simple: one (... maybe two) class.
- Modern: strong use of closures.
- Thread safety & efficiency: powered by Guava Stripe. Tested with thread-safe.org.
- Nice API: the container is an
LinkedHashMap
extension. In groovy you can use it like a Map, [:]. - Prototype / Singleton: define singleton or prototype scoped services or parameters.
Java, and more generally JVM, already got many dependency injection containers (spring framework, google guice, picocontainer) but all of them are heavy libraries composed of dozens of classes.
It's just a single class!
Gimple is published via Maven:
- Maven repository: https://github.com/FbN/mvn/raw/master/
- Maven artifact: com.github.gimple Gimple 0.1.2
If you use gradle simply add the following lines to build.gradle file:
repositories {
maven {
url 'https://github.com/FbN/mvn/raw/master/'
}
}
dependencies {
compile 'com.github.gimple:Gimple:0.1.2'
}
def container = new gimple.Container()
A service is an object that does something as part of a larger system. Examples of services are:
- a database connection
- a templating engine
- a mailer
Almost any global object can be a service. Services are defined by a closure that returns an instance of an object:
container['sessionStorage'] = { c ->
new SessionStorage('SESSION_ID')
}
container['session'] = { c ->
new Session(c['sessionStorage'])
}
Notice that closure has access to the current container instance, allowing references to other services or parameters.
As objects are only created when you get them, the order of the definitions does not matter.
Using the defined services is also very easy:
def session = container['session']
By default, each time you get a service, Gimple returns the same instance of it.
If you want a different instance to be returned for all calls, wrap your closure with the factory()
method
container['session'] = container.factory( { c ->
new Session(c['sessionStorage'])
})
Now, each call to container['session']
returns a new instance of the session.
Defining a parameter allows to ease the configuration of your container from the outside and to store global values:
container['cookieName'] = 'SESSION_ID'
container['sessionStorageClass'] = 'SessionStorage'
If not a closure it is a parameter.
If you change the sessionStorage
service definition like below:
container['sessionStorage'] = { c ->
new "${c['sessionStorageClass']}"(c['cookieName'])
}
Because Gimple sees closures as a service definitions, you need to wrap closure with the protect()
method to store them as parameters:
container['randomFunc'] = container.protect({ new Random().nextInt() })
You can replace a service simple reassigning a new value to id (the service must not be already used).
In some cases you may want to modify a service definition after it has been defined without replacing it. You can see it as a filter applied after the original service.
You can use the extend()
method to define additional code to be run on your service just after it is created:
container['sessionStorage'] = { c ->
new "${c['sessionStorageClass']}"(c['cookieName'])
};
container.extend('sessionStorage', {storage, c ->
...
do anything with storage and c
...
storage
})
The first argument is the name of the service to extend, the second a function that gets access to the object instance and the container.
If you use the same libraries over and over, you might want to reuse some services from one project in another one. Package your services into a provider by implementing gimple.ServiceProviderInterface
:
class FooProvider implements gimple.ServiceProviderInterface {
def register(Container gimple) {
// register some services and parameters
// on gimple
}
}
Then, register the provider on a Container:
gimple.register(new FooProvider())
When you access an object, Gimple automatically calls the closure that you defined, which creates the service object for you. If you want to get raw access to the closure, you can use the raw() method:
container['session'] = { c ->
new Session(c['sessionStorage'])
};
def sessionFunction = container.raw('session')