PicoContainer can add behaviors to components automatically during instantiation.
Behaviors are deleivered by passing a BehaviorFactory reference into the PicoContainer instance on construction. For the most part BehaviorFactories are completely stateless, and the Behavior instances they make are not.
See below for details on Caching and Singletons, Implementation Hiding and Hot Swapping, as well as Other behaviors
DefaultPicoContainer can take a behavior factory instance in its constructor:
pico = new DefaultPicoContainer(new BehaviorClass()); pico.addComponent(Foo.class); // other components added Foo foo = pico.getcomponent(Foo.class) // Foo instance will be affected by an additonal behavior.
Or it can take behaviours that wrap each other:
pico = new DefaultPicoContainer(new BehaviorClass().wrap(new AnotherBehaviorClass())); pico.addComponent(Foo.class); // other components added Foo foo = pico.getcomponent(Foo.class) // Foo instance will be affected by two additonal behaviors.
Behaviors can be signalled by properties per component:
pico = new DefaultPicoContainer(); pico.as(SOME_BEHAVIOR).addComponent(Foo.class); // the behavior has a property marking it, and the default component facory understands that property // other components added Foo foo = pico.getcomponent(Foo.class) // Foo instance will be affected by an additonal behavior.
You can build a container with the applicable property:
pico = new PicoBuilder().withXXXBehavior().build(); pico.addComponent(Foo.class); // other components added Foo foo = pico.getcomponent(Foo.class) // Foo instance will be affected by an additonal behavior.
Behaviors can be chained together by PicoBuilder:
import static org.picocontainer.behaviors.Behaviors.xxxxxxx; import static org.picocontainer.behaviors.Behaviors.yyyyyyy; ... pico = new PicoBuilder().withBehaviors(xxxxxxx(), yyyyyyy()).build(); pico.addComponent(Foo.class); // other components added Foo foo = pico.getcomponent(Foo.class) // Foo instance will be affected by two additonal behaviors.
This is where a component has a single instance in the container rather that a new one created each time the container is asked for that type. Guice calls this a Singleton. After guice came out we debated this, and decided that we should not call this a Singleton. Another PicoContainer instance could be manging another single instance of the same component, so it does not fit the definition of Singleton as defined in the Design Patterns book.
Choose your style for caching behavior:
pico = new DefaultPicoContainer(new Caching()); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
pico = new DefaultPicoContainer(); pico.as(CACHE).addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
pico = new PicoBuilder().withCaching().build(); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
import static org.picocontainer.behaviors.Behaviors.caching; ... pico = new PicoBuilder().withBehaviors(caching()).build(); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
This is caching as above, but only when that property is set.
pico = new DefaultPicoContainer(new OptinCaching()); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // different instances
pico = new DefaultPicoContainer(new OptinCaching()); pico.as(CACHE).addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
This is where a component has a single instance in the container rather that a new one created each time the container is asked for that type. The difference to 'Caching' is that there is one cache per thread using the container. For the most part you are not going to use this behavior unless you are 100% sure that the muti-threaded application you are writing maps threads to the notional session you are trying to model.
Choose your style for thread caching behavior:
pico = new DefaultPicoContainer(new ThreadCaching()); pico.addComponent(Apple.class); new Thread() { public void run() { Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); } }).start(); new Thread() { public void run() { Apple a3 = pico.getComponent(Apple.class); } }).start(); // a1 and a2 are the same instance // a1 and a3 are different instances
pico = new DefaultPicoContainer(); pico.as(THREAD_CACHE).addComponent(Apple.class);
pico = new PicoBuilder().withThreadCaching().build(); pico.addComponent(Apple.class);
This is where a component has a single instance in the container rather that a new one created each time the container is asked for that type. This is like 'ThreadCaching' as it has the one cache per thread behavior, but the whole store is can be extracted and set outside if you have a referene to the Storing instance. Again you are likely to use this behavior if you are trying to model 'session' behavior. With this one though you able to retrieve the cache from some place associated with the session and set it for the thread before use. After use, it can be extracted and stored back with the sesion.
Only one style is shown here as you will need to hang on to the reference to the BehaviorFactory to make use of it:
pico = new DefaultPicoContainer(new Storing()); pico.addComponent(Apple.class); Apple a1, a2, a3 new Thread() { public void run() { a1 = pico.getComponent(Apple.class); a2 = pico.getComponent(Apple.class); } }).start(); new Thread() { public void run() { a3 = pico.getComponent(Apple.class); } }).start(); // a1 and a2 are the same instance // a1 and a3 are different instances
The BehaviorFactory for Storing behavior is not stateless. It allows the cache to be set for the thread as well as extracted. Here's a code snippet to illustrate that:
storing = new Storing(); pico = new DefaultPicoContainer(sc); .... storing.putCacheForThread(httpRequest.getSession().getAttribute("sessionComponentStore"); // do other methods that process request in session concept and may do getComponent(..) from the sesion container. httpRequest.getSession().setAttribute("sessionComponentStore", sc.getCacheForThread());
See scopes for another example for a web framework
This is where the implementation of the component is hidden from other components using it. The instance cannot be cast back to the implementation. It only works if the type has an interface that it implements.
pico = new DefaultPicoContainer(new ImplementationHiding()); pico.addComponent(Apple.class, AppleImpl.class); Apple a1 = pico.getComponent(Apple.class); // cannot cast back to AppleImpl
pico = new DefaultPicoContainer(); pico.as(HIDE_IMPL).addComponent(Apple.class, AppleImpl.class); Apple a1 = pico.getComponent(Apple.class); // cannot cast back to AppleImpl
pico = new PicoBuilder().withImplementationHiding().build(); pico.addComponent(Apple.class, AppleImpl.class); Apple a1 = pico.getComponent(Apple.class); // cannot cast back to AppleImpl
import static org.picocontainer.behaviors.Behaviors.implementationHiding; ... pico = new PicoBuilder().withBehaviors(implementationHiding()).build(); pico.addComponent(Apple.class, AppleImpl.class); Apple a1 = pico.getComponent(Apple.class); // cannot cast back to AppleImpl
This behavior leverages Reflection's dynamic proxy capability.
There's another implementation hiding behavior in Pico Gems called AsmImplemenationHiding that leverages ASM to make 'more concrete' hidden implementations than is possible via reflection. It generates real classes using ASM
This builds on the ASMImplementationHiding behavior above, but also allows the hot swapping of component implementations during use. It has implicit caching behavior too.
DefaultPicoContainer pico = new DefaultPicoContainer(new HotSwapping()); pico.addComponent(Map.class, HashMap.class); Map firstMap = pico.getComponent(Map.class); firstMap.put("foo", "bar"); HotSwappable hs = (HotSwappable) pico.getComponentAdapter(Map.class); Object oldMap = hs.getSwappable().swap(new HashMap()); Map secondMap = pico.getComponent(Map.class); secondMap.put("apple", "orange"); // first map and second map are the same // 'foo' is not a key in the map, wereas 'apple' is
When components are created by two threads concurrently, with the intention of the instance being cached, it is possible in a small percentage of cases for the first instance into the cache to be replaced with a second instance. To prevent this, you may want to try one of two behaviors to make the operation thread safe.
Synchronizing wraps object creation in Java's classic synchronize feature:
pico = new DefaultPicoContainer(new Synchronizing().wrap(new Caching())); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
pico = new DefaultPicoContainer(); pico.as(SYNCHRONIZE, CACHE).addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
pico = new PicoBuilder().withSynchronizing().withCaching().build(); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
import static org.picocontainer.behaviors.Behaviors.synchronizing; import static org.picocontainer.behaviors.Behaviors.caching; ... pico = new PicoBuilder().withBehaviors(synchronizing(), caching()).build(); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
Locking wraps object creation in JDK 1.5's ReentrantLock facility. It is suggested that this is a more efficient alternative to the Synchronizing behaviour above:
pico = new DefaultPicoContainer(new Locking().wrap(new Caching())); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
pico = new DefaultPicoContainer(); pico.as(LOCK, CACHE).addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
pico = new PicoBuilder().withLocking().withCaching().build(); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
import static org.picocontainer.behaviors.Behaviors.locking; import static org.picocontainer.behaviors.Behaviors.caching; ... pico = new PicoBuilder().withBehaviors(synchronizing(), caching()).build(); pico.addComponent(Apple.class); Apple a1 = pico.getComponent(Apple.class); Apple a2 = pico.getComponent(Apple.class); // both the same instance
This is where there are a number of setters for a component that will be could be set after instantiation. A way of handing in some configuration if you like.
class Foo { String message; public void setMessage(String message) { this.message = message; } public String toString() { return message; } } ... pico = new DefaultPicoContainer(new PropertyApplying()); pico.addComponent(Foo.class); PropertyApplicator pa = (PropertyApplicator) pico.getComponentAdapter(Foo.class); pa.setProperty("message", "hello"); System.out.println(pico.getComponent(Foo.class)); // prints hello
pico = new DefaultPicoContainer(); pico.as(APPLY_PROPERTIES).addComponent(Foo.class); PropertyApplicator pa = (PropertyApplicator) pico.getComponentAdapter(Foo.class); pa.setProperty("message", "hello"); System.out.println(pico.getComponent(Foo.class)); // prints hello
pico = new PicoBuilder().withProperties().build(); pico.addComponent(Foo.class); PropertyApplicator pa = (PropertyApplicator) pico.getComponentAdapter(Foo.class); pa.setProperty("message", "hello"); System.out.println(pico.getComponent(Foo.class)); // prints hello
import static org.picocontainer.behaviors.Behaviors.propertyApplying; ... pico = new PicoBuilder().withBehaviors(propertyApplying()).build(); pico.addComponent(Foo.class); PropertyApplicator pa = (PropertyApplicator) pico.getComponentAdapter(Foo.class); pa.setProperty("message", "hello"); System.out.println(pico.getComponent(Foo.class)); // prints hello
import static org.picocontainer.behaviors.Behaviors.caching; import static org.picocontainer.behaviors.Behaviors.propertyApplying; ... pico = new PicoBuilder().withBehaviors(caching(), propertyApplying()).build(); pico.addComponent(Foo.class); Cached cached = (Cached) pico.getComponentAdapter(Foo.class); PropertyApplicator pa = (PropertyApplicator) getDelegate(PropertyApplicator.class); pa.setProperty("message", "hello"); System.out.println(pico.getComponent(Foo.class)); // prints hello
This is where a component is going to be instantiated regardless of whether:
It is most likely that you're doing this because the component in question is self contained and doing something once only. Alternatively, you're cutting a legacy codebase over from nest-of-singletons to dependency injection in stages.
class Foo { public Foo() { System.out.println("Foo was instantiated"); } } ... pico = new DefaultPicoContainer(new Automatic()); pico.addComponent(Foo.class); pico.addComponent("bar", String.class); pico.getComponent("bar"); // Foo instantiated too.
pico = new DefaultPicoContainer(); pico.as(AUTOMATIC).addComponent(Foo.class); pico.addComponent("bar", String.class); pico.getComponent("bar"); // Foo instantiated too.
pico = new PicoBuilder().withAutomatic().build(); pico.addComponent(Foo.class); pico.addComponent("bar", String.class); pico.getComponent("bar"); // Foo instantiated too.
import static org.picocontainer.behaviors.Behaviors.automatic; ... pico = new PicoBuilder().withBehaviors(automatic()).build(); pico.addComponent(Foo.class); pico.addComponent("bar", String.class); pico.getComponent("bar"); // Foo instantiated too.