"Parameter based singleton scope" in AssistedInject factory?

1,149 views
Skip to first unread message

Sondre Bjornebekk

unread,
Oct 14, 2010, 4:25:10 AM10/14/10
to google-guice
Hi,

Trying to use assisted inject and find it pretty neat, the minor
detail is that it does not quite work for me yet :-)

I have created a Map wrapper to ease switching of caches (typically to
something like Coherence) for my application. I want the Map to be
returned "in a Singleton scope, based on the value of the (assisted)
parameter". I am doing:

bind(CacheFactory.class).toProvider( FactoryProvider.newFactory(CacheFactory.class,
NamedCacheLocal.class) );

And I have:
/**
* A factory for named caches, that later might hide its
implementation using a cluster wide storage.
*/

public interface CacheFactory {
//had to be named create to work with guice assisted inject, so
renamed from getNamedCache
public NamedCache create(String name);
}

public interface NamedCache<K,V> extends Map<K,V> {
public int getHitPercent();
public String getHitPercentString();
...
}

/**
* A named cache with a local map as implementation for storage.
*/

public class NamedCacheLocal<K,V> implements NamedCache<K,V> {
...

/**
* Constructs a NamedCacheLocal with its dependencies.
*/
@Inject
public NamedCacheLocal(@Assisted String cacheName) {
...
}

I have this (failing) unit test that checks that the same cache is
used for the same name:

System.out.println("doing put / get test");
NamedCache<String,String> namedCache =
namedCacheFactory.create("myCache");
namedCache.put("fisk","fesk");
for(int i=0;i<10; i++){
Object o = namedCache.get("non-hit");
namedCache.put("test"+i,"testval"+i);
}
Object hit = namedCache.get("fisk");
System.out.println("cache " + namedCache + " delivered value "
+ hit + " to a hit percent of " + namedCache.getHitPercentString());
assertTrue(namedCache.getHitPercent() > 8 &&
namedCache.getHitPercent() < 10);
NamedCache<String,String> namedCacheWithSameName =
namedCacheFactory.create("myCache");
for(int i=0;i<10; i++){
String val = namedCacheWithSameName.get("test"+i);
System.out.println("got val from cache: " + val);
}
//should have used the same reference, thus hit pct now higher
System.out.println("done with cache " +
namedCacheWithSameName);
assertTrue(namedCache.getHitPercent() > 10);
assertTrue(namedCache.equals(namedCacheWithSameName));


So is the thing I put in quotation marks in the intro ("in a Singleton
scope, based on the value of the (assisted) parameter") even possible
just by Guice config, or do I have to go two steps back and one
forward and just implement the CacheFactory myself with this logic?
The first thing I tried was

bind( CacheFactory.class ).
toProvider( FactoryProvider.newFactory(CacheFactory.class,
NamedCacheLocal.class) ) .
in ( Singleton.class );

But that (of course) just gives me a singleton CacheFactory, not the
NamedCacheLocal "singleton" based on the assisted factory parameter.

Cheers,

Sondre

Fred Faber

unread,
Oct 14, 2010, 12:27:32 PM10/14/10
to google...@googlegroups.com
Basically you have a factory that should return a singleton instance for each unique string value passed into it?

FactoryProvider won't do this for you out of the box, as it will create a new instance per invocation (as you've illustrated).

What I would do in this case is to decorate your factory with an implementation that controls singletonness:

interface CacheFactory {
  // This can be named anything you want, not just "create"
  NamedCache create(String name);
}

class CachingCacheFactory implements CacheFactory {
  
 private final CacheFactory underlyingFactory;

 private final Map<String, NamedCache> caches = new MapMaker()
      .makeComputingMap(new Function<String, NamedCache>(){
           @Override public NamedCache apply(String name) {
                return underlyingFactory.create(name);
          }
      });
          
  @Inject
  CachingCacheFactory {
      @AlwaysCreatesNewInstances CacheFactory cacheFactory) {
     this.underlyingFactory = cacheFactory;
   }

  @Override
  NamedCache create(String name) {
      return caches.get(name);
  }
}
 

* * *

public class YourModule extends AbstractModule {
 
   @Override protected void configure() {
       // as an alternative to binding this as a singleton, you could inject the map of caches
       // as a singleton instance.  6 or 1/2 dozen, unless you want to unittest your
       // implementation, in which case you'll want to inject the singleton map
       bind(CacheFactory.class)
           .to(CachingCacheFactory.class)
           .in(Singleton.class);
     
       // this binds a "CacheFactory" instance, annotated with @AlwaysCreatesNewInstances, to
       // a default factory implementation which will return instances of NamedCacheLocal when
       // its create() method is invoked
       install(new FactoryModuleBuilder()
           .implement(NamedCache, NamedCacheLocal)
           .build(Key.get(NamedCache.class, AlwaysCreatesNewInstances.class));
    }
}
  
That should do the trick.

-Fred  



--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To post to this group, send email to google...@googlegroups.com.
To unsubscribe from this group, send email to google-guice...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-guice?hl=en.


Dhanji R. Prasanna

unread,
Oct 14, 2010, 6:47:54 PM10/14/10
to google...@googlegroups.com
Interesting problem. I've heard this referred to as the "Multiton" pattern.

Dhanji.

Sondre Bjornebekk

unread,
Oct 15, 2010, 9:12:21 AM10/15/10
to google-guice
Neat. In fact the MapMaker[1] that takes a Function and provides
different eviction policies etc. that you used for the map of caches
will probably also save me from some manual work in the actual local
heap cache implementation around synchronization etc. Thanks!

Since I found it less than smooth to integrate Guice 3.0 in my maven
build at this point (Stuart McCulloch's snapshot build that was
discussed here seems to only have 2.1.8?), I replaced the
@AlwaysCreatesNewInstances with a simple @Named annotation to
differentiate the injection for the "actual cache factory" from the
Multiton giving me:

@Inject
public MultitonCacheFactory(@Named("underlyingFactory")
CacheFactory cacheFactory) {
this.underlyingFactory = cacheFactory;
}

And:
bind(CacheFactory.class).
to(MultitonCacheFactory.class).in(Singleton.class);
bind(CacheFactory.class).
annotatedWith(Names.named("underlyingFactory")).

toProvider( FactoryProvider.newFactory(CacheFactory.class,
NamedCacheLocal.class) );

This seems to work in the unit testing (and with some simple
concurrency testing). Any comments on that approach?

Will do a quick feasibility test, but cannot see why I should not be
able to wrap Coherence (or something similar) by just changing the
last of the two bindings.

-S-

[1] - This Google guava library was in fact all new to me and seems
really useful. Kudos goes to MV for releasing it under an Apache
license.

On Oct 14, 12:27 pm, Fred Faber <ffa...@faiser.com> wrote:
> Basically you have a factory that should return a singleton instance for
> each unique string value passed into it?
>
> FactoryProvider won't do this for you out of the box, as it will create a
> new instance per invocation (as you've illustrated).
>
> What I would do in this case is to decorate your factory with an
> implementation that controls singletonness:
>
> interface CacheFactory {
>   // This can be named anything you want, not just "create"
>   NamedCache create(String name);
>
> }
>
> class CachingCacheFactory implements CacheFactory {
>
>  private final CacheFactory underlyingFactory;
>
>  //http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/co...
>  private final Map<String, NamedCache> caches = new MapMaker()
>       .makeComputingMap(new Function<String, NamedCache>(){
>            @Override public NamedCache apply(String name) {
>                 return underlyingFactory.create(name);
>           }
>       });
>
>   @Inject
>   CachingCacheFactory {
>       @AlwaysCreatesNewInstances CacheFactory cacheFactory) {
>      this.underlyingFactory = cacheFactory;
>    }
>
>   @Override
>   NamedCache create(String name) {
>       return caches.get(name);
>   }
>
> }
>
> * * *
>
> public class YourModule extends AbstractModule {
>
>    @Override protected void configure() {
>        // as an alternative to binding this as a singleton, you could inject
> the map of caches
>        // as a singleton instance.  6 or 1/2 dozen, unless you want to
> unittest your
>        // implementation, in which case you'll want to inject the singleton
> map
>        bind(CacheFactory.class)
>            .to(CachingCacheFactory.class)
>            .in(Singleton.class);
>
>        //http://google-guice.googlecode.com/svn/trunk/javadoc/com/google/injec...
>        // this binds a "CacheFactory" instance, annotated with
> @AlwaysCreatesNewInstances, to
>        // a default factory implementation which will return instances of
> NamedCacheLocal when
>        // its create() method is invoked
>        install(new FactoryModuleBuilder()
>            .implement(NamedCache, NamedCacheLocal)
>            .build(Key.get(NamedCache.class,
> AlwaysCreatesNewInstances.class));
>     }
>
> }
>
> That should do the trick.
>
> -Fred
>
> On Thu, Oct 14, 2010 at 4:25 AM, Sondre Bjornebekk <
>
> > google-guice...@googlegroups.com<google-guice%2Bunsu...@googlegroups.com>
> > .

Fred Faber

unread,
Oct 15, 2010, 10:10:23 AM10/15/10
to google...@googlegroups.com
That all looks reasonable, sure.

Avoiding use of @Named is a matter of best practice, wherein a large code base becomes susceptible to global namespace collisions, and even a small code base is at risk for typos.  Hence use of a well typed @BindingAnnotation is superior.

Glad this does the trick.

-Fred

To unsubscribe from this group, send email to google-guice...@googlegroups.com.

Stuart McCulloch

unread,
Oct 17, 2010, 7:48:35 PM10/17/10
to google...@googlegroups.com
On 15 October 2010 14:12, Sondre Bjornebekk <sondre.b...@gmail.com> wrote:
Neat. In fact the MapMaker[1] that takes a Function and provides
different eviction policies etc. that you used for the map of caches
will probably also save me from some manual work in the actual local
heap cache implementation around synchronization etc. Thanks!

Since I found it less than smooth to integrate Guice 3.0 in my maven
build at this point (Stuart McCulloch's snapshot build that was
discussed here seems to only have 2.1.8?)

this snapshot is actually tracking trunk which was originally meant to be Guice 2.1
hence the 2.1.8-SNAPSHOT version (we made a number of stable point releases)

to avoid further confusion I've bumped the sisu-guice version to 3.0-SNAPSHOT,
and the latest CI build should now be available from the sonatype forge repository

HTH

To unsubscribe from this group, send email to google-guice...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages