Genie provides support on injecting Collection and Map type data. Unlike other type of Java bean injection, Collection/Map type injection require an additional step after the bean has been constructed: loading element into the container. Take an example below:
/**
* Print out a Fibonacci number series injected
*/
public class FibonacciDemo {
@Inject
private List<Integer> series;
public void print(PrintStream ps) {
for (int n: series) {
ps.println(n);
}
}
}
We want to create a type binding module:
public FibonacciModule extends Module {
public void configure() {
bind(List.class).to(ArrayList.class);
}
}
Tips You don't have to do type binding for List
and Map
in your application because Genie has already done that for you.
Now if we load the bean with Genie:
FibonacciDemo demo = genie.get(FibonacciDemo.class);
We will get a FibonacciDemo
with an empty ArrayList
. One possible way is to use @Provides
method:
public FibonacciModule {
@Provides
FibonacciDemo createDemo(ArrayList list) {
// do logic to populate the list
}
}
Now a problem is how many numbers should we populate into the list. We can either hard code a number, or anyway inject a number into the @Provides
method. None of them satisfied our needs.
Genie comes up with an nice solution that allows application developer to define their data loading logic:
First, define an ElementLoader
that loads the fibonacci series:
class FibonacciSeriesLoader extends ElementLoaderBase<Integer> {
@Override
public List<Integer> load(Map<String, Object> options, BeanSpec container, Genie genie) {
int max = toInt(options.get("max")); // here "max" is provided by annotation class as shown below
int n1 = 1, n2 = 1, f;
List<Integer> list = C.newList();
list.add(n1);
list.add(n2);
for (;;) {
f = n1 + n2;
n1 = n2;
n2 = f;
if (f < max) {
list.add(f);
} else {
break;
}
}
return list;
}
...
}
Note: the full code of the FibonacciSeriesLoader.java
can be found at https://github.com/osglworks/java-di/blob/master/src/test/java/org/osgl/genie/FibonacciSeriesLoader.java
Now define the Loader
annotation:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Loader(FibonacciSeriesLoader.class)
public @interface FibonacciSeries {
/**
* The max value in the series
* @return the max value
*/
int max() default 100;
}
Finally modify our demo app by adding the @FibonacciSeries
annotation:
/**
* Print out a Fibonacci number series injected
*/
public class FibonacciDemo {
@FibonacciSeries(max = 1000)
private List<Integer> series;
public void print(PrintStream ps) {
for (int n: series) {
ps.println(n);
}
}
}
Then you can use Genie to get the demo app and the series will get populated:
FibonacciDemo demo = genie.get(FibonacciDemo.class);
demo.print(System.out);
Genie support filtering when loading container elements. Take the FibonacciDemo
above as an example, suppose you need only the even number in the Fibonacci series. You need to define a ElementFilter
class and a Filter
annotation:
The ElementFilter
logic
class EvenNumberFilter implements ElementFilter<Integer> {
@Override
public Osgl.Function<Integer, Boolean> filter(Map<String, Object> options, BeanSpec container) {
return N.F.IS_EVEN;
}
}
The Filter
annotation that specify the ElementFilter
implementation:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Filter(EvenNumberFilter.class)
public @interface EvenNumber {
}
Now update your FibonacciDemo
app by adding the EvenNumber
annotation:
/**
* Print out a Fibonacci number series injected
*/
public class FibonacciDemo {
@FibonacciSeries(max = 1000)
@EvenNumber
private List<Integer> series;
public void print(PrintStream ps) {
for (int n: series) {
ps.println(n);
}
}
}
Genie will ensure the data loaded in the series even Fibonacci numbers.
You can have many ElementFilter
s and only element pass all filters will be loaded. Actually every ElementLoader
implementation is also ElementFilter
, thus you can have multiple ElementLoader
s and only the first one will be served as loader and the rest will be treated as filter.
Things is a little bit different if we need to populate a Map
type bean. In addition to ElementLoader
and Loader
annotation, we need another annotation MapKey
to specify how to get the key
from the element so we can put
the element along with it's key
into the map.
/**
* Dispatch error to proper handlers
*/
class ErrorDispatcher {
@TypeOf
@MapKey("errorCode")
Map<Integer, ErrorHandler> registry;
String handle(int error) {
ErrorHandler handler = registry.get(error);
return null == handler ? "unknown" : handler.toString();
}
}
In the above code the Map
type bean registry
has been tagged with @MapKey("errorCode")
and @TypeOf
annotation. Where @TypeOf
is a built-in element Loader annotation which tells Genie to load element who close is sub class or implementation of ErrorHandler
. And @MapKey("errorCode")
is the annotation to tell Genie how to retrieve the key
: get the key from bean's "errorCode" property. And here is the code of ErrorHandler
:
/**
* A test class: define a abstract class for error handlers
*/
public abstract class ErrorHandler {
/**
* Returns the error code the implementation is looking for
* @return the interested error code
*/
public abstract int getErrorCode();
@Override
public String toString() {
return getClass().getSimpleName();
}
}
Genie provides three built-in Element loader and their corresponding annotation
Loader | Annotation | Purpose |
---|---|---|
AnnotatedElementLoader |
AnnotatedWith |
Load element whose class has been annotated with specified annotation class |
TypedElementLoader |
TypeOf |
Load element whose class is sub type or implementation of the specified class |
Note It requires Genie to be setup property with certain IoC container to use the @AnnotatedWith
and @TypeOf
element loader.
Please refer the following section: integration