Permalink
Browse files

adding LazySingleton support

  • Loading branch information...
axelhzf committed Feb 4, 2012
1 parent 13860cf commit 30af78bb96c7561a529ac578ff93d2b49a1e5492
@@ -1,10 +1,16 @@
package play.modules.guice;

import play.Play;

import com.google.inject.Injector;
import com.google.inject.Stage;

/**
* Implemented if a custom injector is desired
*/
public abstract class GuiceSupport{

protected Stage stage = Play.mode.isDev() ? Stage.DEVELOPMENT : Stage.PRODUCTION;

protected abstract Injector configure();
}
@@ -0,0 +1,111 @@
package play.modules.guice;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.atomic.AtomicReference;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.spi.BindingScopingVisitor;

/**
* https://github.com/wiregit/wirecode/blob/master/components/common/src/main/java/org/limewire/inject/
*
* A factory for creating Providers that will enforce extreme laziness of
* creating the implementation class. The implementation class can be
* injected directly, and it will not be constructed until at least
* one method is called on the implementation.
* <p>
* This enforces that all bindings are done on implementations scoped
* to a {@code @}{@link Singleton} or {@code @}{@link LazySingleton},
* to ensure that methods are called on the same underlying instance
* of the implementation through repeated calls.
*/
public class LazyBinder<T> implements Provider<T> {

private final AtomicReference<T> providee = new AtomicReference<T>();
private final Class<T> expected;
private final Class<? extends T> implClass;
private Injector injector;

/**
* Constructs a new Provider that will ensure the implementation class is
* created only after at least one method is called.
*/
public static <T> Provider<T> newLazyProvider(Class<T> expected, Class<? extends T> implClass) {
if (!expected.isInterface()) {
throw new RuntimeException("Expected class must be an interface");
}
return new LazyBinder<T>(expected, implClass);
}

private LazyBinder(Class<T> expected, Class<? extends T> implClass) {
this.expected = expected;
this.implClass = implClass;
}

@SuppressWarnings("unused")
@Inject
private void registerAndCheckTypes(Injector injector) {

this.injector = injector;
injector.getBinding(implClass).acceptScopingVisitor(new BindingScopingVisitor<Void>() {
public Void visitEagerSingleton() {
return null;
}

public Void visitNoScoping() {
throw new RuntimeException("Class: " + implClass + " must be in scope @Singleton or @LazySingleton or @EagerSingleton");
};


public Void visitScope(Scope scope) {
if(scope != Scopes.SINGLETON && scope != MoreScopes.LAZY_SINGLETON && scope != MoreScopes.EAGER_SINGLETON) {
throw new RuntimeException("Class: " + implClass + " must be in scope @Singleton or @LazySingleton or @EagerSingleton");
}
return null;
};

public Void visitScopeAnnotation(
Class<? extends Annotation> scopeAnnotation) {
throw new RuntimeException("Wasn't expecting this");
};
});
}

@Override
public T get() {
// Keep only one LazyT.
T got = providee.get();
if(got != null) {
return got;
} else {
providee.compareAndSet(null, createProxy(expected, injector.getProvider(implClass)));
return providee.get();
}
}

private static <T> T createProxy(Class<T> expected, final Provider<? extends T> provider) {
ClassLoader classLoader = expected.getClassLoader();
return expected.cast(Proxy.newProxyInstance(classLoader, new Class[] { expected },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
return method.invoke(provider.get(), args);
} catch(InvocationTargetException ite) {
throw ite.getTargetException();
}
}
}));
}

}
@@ -0,0 +1,25 @@
package play.modules.guice;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

import com.google.inject.Injector;
import com.google.inject.ScopeAnnotation;

/**
* https://github.com/wiregit/wirecode/blob/master/components/common/src/main/java/org/limewire/inject/
*
* Apply this to implementation classes when you want only one instance
* (per {@link Injector}) to be reused for all injections for that binding.
*
* The singleton is guaranteed to be constructed lazily.
*/
@Target( { TYPE, METHOD })
@Retention(RUNTIME)
@ScopeAnnotation
public @interface LazySingleton {

}
@@ -0,0 +1,93 @@
package play.modules.guice;

import java.lang.annotation.Annotation;

import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.internal.LinkedBindingImpl;
import com.google.inject.spi.BindingScopingVisitor;

/**
* https://github.com/wiregit/wirecode/blob/master/components/common/src/main/java/org/limewire/inject/
*
* Extensions to the default Guice Scoping.
*/
public class MoreScopes {

/**
* Returns the scope of the binding, or the scope of the linked binding if
* it was linked.
*/
public static Scope getLinkedScope(Binding<?> binding) {
BindingScopingVisitor<Scope> scoper = new BindingScopingVisitor<Scope>() {
public Scope visitNoScoping() {
return Scopes.NO_SCOPE;
}

public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
throw new IllegalStateException("no annotations allowed here");
}

public Scope visitScope(Scope scope) {
return scope;
}

public Scope visitEagerSingleton() {
return EAGER_SINGLETON;
}
};

do {
Scope scope = binding.acceptScopingVisitor(scoper);

if (scope != Scopes.NO_SCOPE) {
return scope;
}

if (binding instanceof LinkedBindingImpl) {
LinkedBindingImpl<?> linkedBinding = (LinkedBindingImpl) binding;
Injector injector = linkedBinding.getInjector();
if (injector != null) {
binding = injector.getBinding(linkedBinding.getLinkedKey());
continue;
}
}

return Scopes.NO_SCOPE;
} while (true);
}

/**
* A singleton that will never be eager, in contrast to
* {@link Scopes#SINGLETON}, which Guice eagerly creates sometimes.
*/
public static final Scope LAZY_SINGLETON = new Scope() {
public <T> Provider<T> scope(Key<T> key, Provider<T> creator) {
return Scopes.SINGLETON.scope(key, creator);
}

@Override public String toString() {
return "MoreScopes.LAZY_SINGLETON";
}
};

/**
* A singleton that will be eagerly loaded. A class with
* an EagerSingleton annotation will be created at startup.
*/
public static final Scope EAGER_SINGLETON = new Scope() {
@Override
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
return Scopes.SINGLETON.scope(key, unscoped);
}

@Override public String toString() {
return "MoreScopes.EAGER_SINGLETON";
}
};

}
@@ -0,0 +1,29 @@
package play.modules.guice;

import play.Play;

import com.google.inject.AbstractModule;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.Stage;

public abstract class PlayAbstractModule extends AbstractModule {

private boolean lazySingletonScopeBinded = false;

protected <T> void bindLazySingletonOnDev(Class<T> expected, Class<? extends T> implClass){
if(!lazySingletonScopeBinded){
bindScope(LazySingleton.class, MoreScopes.LAZY_SINGLETON);
lazySingletonScopeBinded = true;
}

if(Play.mode.isDev()){
bind(implClass).in(MoreScopes.LAZY_SINGLETON);
Provider<T> provider = LazyBinder.newLazyProvider(expected, implClass);
bind(expected).toProvider(provider);
}else{
bind(expected).to(implClass).in(Scopes.SINGLETON);
}
}

}

0 comments on commit 30af78b

Please sign in to comment.