Skip to content

Instantly share code, notes, and snippets.

@efenderbosch
Created April 10, 2018 14:04
Show Gist options
  • Save efenderbosch/96eb3310b08494911b7c290a14ede2e2 to your computer and use it in GitHub Desktop.
Save efenderbosch/96eb3310b08494911b7c290a14ede2e2 to your computer and use it in GitHub Desktop.
Guice Dynamic Proxy Feature Toggles
@Target(PARAMETER)
@Retention(RUNTIME)
@BindingAnnotation
public @interface Disabled {}
@Target(PARAMETER)
@Retention(RUNTIME)
@BindingAnnotation
public @interface Enabled {}
@Target(TYPE)
@Retention(RUNTIME)
public @interface Feature {
String key();
}
public class FeatureFlipAnnotationTest {
private static final Injector INJECTOR = Guice.createInjector(new FeatureFlipTestModule());
private static final SomeFeature SOME_FEATURE = INJECTOR.getInstance(SomeFeature.class);
@Test
public void test_enabled() {
when(MOCK_FEATURE_FLIP.isFeatureEnabled("test_key")).thenReturn(true);
assertThat(SOME_FEATURE.foo(), is("enabled"));
}
@Test
public void test_disabled() {
when(MOCK_FEATURE_FLIP.isFeatureEnabled("test_key")).thenReturn(false);
assertThat(SOME_FEATURE.foo(), is("disabled"));
}
@Feature(key = "test_key")
public interface SomeFeature {
String foo();
}
public static class EnabledImplementation implements SomeFeature {
@Override
public String foo() {
return "enabled";
}
}
public static class DisabledImplementation implements SomeFeature {
@Override
public String foo() {
return "disabled";
}
}
public static class SomeFeatureProvider extends FeatureFlipProxyProvider<SomeFeature> {
@Inject
public SomeFeatureProvider(FeatureFlip featureFlip,
@Enabled SomeFeature enabledImplementation,
@Disabled SomeFeature disabledImplementation) {
super(SomeFeature.class, featureFlip, enabledImplementation, disabledImplementation);
}
}
public static class FeatureFlipTestModule extends AbstractModule {
public static final FeatureFlip MOCK_FEATURE_FLIP = mock(FeatureFlip.class);
@Override
protected void configure() {
bind(FeatureFlip.class).toInstance(MOCK_FEATURE_FLIP);
bind(SomeFeature.class).annotatedWith(Enabled.class).to(EnabledImplementation.class).in(SINGLETON);
bind(SomeFeature.class).annotatedWith(Disabled.class).to(DisabledImplementation.class).in(SINGLETON);
bind(SomeFeature.class).toProvider(SomeFeatureProvider.class);
}
}
}
public abstract class FeatureFlipProxyProvider<T> implements Provider<T> {
private final T proxy;
@SuppressFBWarnings("WEM_WEAK_EXCEPTION_MESSAGING")
protected FeatureFlipProxyProvider(Class<T> type,
FeatureFlip featureFlip,
T enabledImplementation,
T disabledImplementation) {
Feature feature = type.getAnnotation(Feature.class);
if (feature == null) {
throw new IllegalArgumentException(type.getName() + " must be annotated with @Feature");
}
String key = feature.key();
ClassLoader classLoader = FeatureFlipProxyProvider.class.getClassLoader();
Class<?>[] interfaces = {type};
proxy = (T) Proxy.newProxyInstance(classLoader, interfaces,
(proxy1, method, args) -> featureFlip.isFeatureEnabled(key) ?
method.invoke(enabledImplementation, args) :
method.invoke(disabledImplementation, args));
}
@Override
public T get() {
return proxy;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment