OD
is a simple Service Locator library for Java 6+.
While the Service Locator pattern is widely discredited, it may still have merits in some situations; see article In Defense of Service Locator. We created this library for people who may consider using Service Locator in their applications.
[ od-1.0.0.jar ] [ javadoc ] [ github repo ] [ google group ]
Contents
To lookup an object of a certain type, call OD.get(type)
Foo foo = OD.get( Foo.class );
Prior to the lookup, typically during the startup phase, the application needs to bind the type to a supplier. For example
OD.bind( Foo.class ).to( ()->new FooImpl() ); // or, `to(FooImpl::new)` OD.bind( Foo.class ).to( FooImpl.class ); // if lambda is not available
The supplier will be invoked for every call of OD.get(Foo.class)
.
To bind Foo
to a singleton
OD.bind( Foo.class ).to( new FooImpl() );
If type
alone is not sufficient as lookup key, tags
can be added as qualifiers. Tags can be an arbitrary array of Object
s, for example
OD.bind(Integer.class).tags("bar", 1).to(42); ... OD.get(Integer.class, "bar", 1); // returns `42`
A local binding is only visible to the current thread
OD.Local.bind( Foo.class ).to( someFoo ); // later in the same thread OD.get(Foo.class); // someFoo
Local bindings can be saved and restored
List<OD.Binding> prev = OD.Local.getBindings(); ... OD.Local.setBindings(prev); // restore bindings
Generic class or interface types can be used in lookup/binding; these types are represented by ClassType
.
For example, to bind and look up on a generic type List<Integer>
OD.bind( new ClassType< List<Integer> >(){} ).to( Arrays.asList(7,8,9) ); // type literal ------------- OD.get( ClassType.of(List.class, Integer.class) ); // type factory ---- -------
See package bayou.jtype
for type representations in our library.
To avoid confusion, we do not automatically extend bindings to super classes/interfaces. For example, a binding of Integer
does not imply a binding of Number
. If you do need that, you can manually add multiple bindings.
On the other hand, we do extend bindings to super types through wildcards; therefore, a binding of List<Integer>
can be looked up by List<? extends Number>
.
OD.get( new ClassType< List<? extends Number> >(){} ); ----------------------
Now, let's give a formal description of how OD
works.
A Binding
is an arbitrary mapping of (type,tags)->supplier
. A binding is applicable to a particular (type,tags)
if it's mapped to a non-null supplier.
There is a global binding list, populated by OD.bind()
.
There is a local binding list per thread, managed by OD.Local
.
For each lookup on (type,tags)
, the local binding list is checked first; if no applicable local binding is found, the global binding list is checked.
A binding list is ordered; if there are multiple bindings applicable to a (type,tags)
, the latest one is used.
You can create a custom Binding
that implements arbitrary binding strategy. For example,
// bind every class to its no-arg constructor, if there's one. OD.Binding defaultBinding = new OD.Binding() { @Override public <T> OD.Supplier<? extends T> map(ClassType<T> type, Object... tags) { Constructor<T> constructor; try { constructor = type.getTheClass().getConstructor(); } catch (Exception e) { return null; } return ()-> { try { return constructor.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } }; } @Override public Set<? extends Class> getApplicableClasses() { return null; // `null` means set of all classes } }; OD.bind(defaultBinding);
Tags are not intended to be arbitrary construction parameters, even though you could do that in a custom binding.
Tags are intended to be a limited set of lookup keys; typically, they should be compile time constants.
For example, instead of doing something like
OD.get(Shoe.class, size, color) // get a Shoe of any size and color
it's better to redesign it with a factory that takes construction parameters
OD.get(ShoeMaker.class).make(size, color);
When we do a binding like
OD.bind(Dao.class).to(MyDao.class);
where Dao
and MyDao
are generic
public class MyDao<T> implements Dao<T> { ...
the binding is actually equivalent to multiple bindings of Dao<t>
to MyDao<t>
, for every type t
. Therefore, if we do a lookup on Dao<Cat>
, we'll get a new MyDao<Cat>()
.
The constructor of MyDao
can accept parameters that represent the type arguments of the generic class
public class MyDao<T> implements Dao<T> { public MyDao(Class<T> classT) { //System.out.println(classT);
When we do a lookup on Dao<Cat>
, we'll get a new MyDao<Cat>(Cat.class)
.
For more details, see javadoc of bind(type).to(implClass)
.
See also source code of TypeArgConstructor
and ImplClassBinding
.
OD