使用动态代理进行要素切换

时间:2018-04-10 13:36:57

标签: java spring dynamic-proxy featuretoggle

我们一直在AWS Lambdas中使用Guice for DI,但现在正在转向Spring Boot和长期运行的服务。

我们在Guice中有功能切换作为动态代理,但需要在Spring中实现。

假设我们有一个SomeFeature界面和两个实施DisabledImplementationEnabledImplementation

我可以通过DisabledImplementation@Component("some.feature.disabled") EnabledImplementation标记@Component("some.feature.enabled")然后编写一个类似的实现来完全接近:

@Primary
@Component
public class FlippingFeature implements SomeFeature {

    private final SomeFeature enabled;
    private final SomeFeature disabled;
    private final FeatureFlip featureFlip;

    @Inject
    public FlippingFeature(@Named("some.feature.enabled") SomeFeature enabled,
                           @Named("some.feature.disabled") SomeFeature disabled,
                           FeatureFlip featureFlip) {
        this.enabled = enabled;
        this.disabled = disabled;
        this.featureFlip = featureFlip;
    }

    @Override
    public String foo() {
        return featureFlip.isEnabled("some.feature") ? enabled.foo() : disabled.foo();
    }
}

但是我宁愿不写FlippingFeature类,而是用隐藏的动态代理来做。我可以使用自定义BeanFactoryPostProcessor或其他内容执行此操作吗?

1 个答案:

答案 0 :(得分:-1)

我现在有一个相当不错的解决方案。

@Qualifier
@Retention(RUNTIME)
// tag the disabled feature implementation w/ this annotation
public @interface Disabled {}

@Qualifier
@Retention(RUNTIME)
// tag the enabled feature implementation w/ this annotation
public @interface Enabled {}

@Target(TYPE)
@Retention(RUNTIME)
// tag the feature interface w/ this annotation
public @interface Feature {
    String value();
}

// create a concrete implementation of this class for each feature interface and annotate w/ @Primary
// note the use of @Enabled and @Disabled injection qualifiers
public abstract class FeatureProxyFactoryBean<T> implements FactoryBean<T> {

    private final Class<T> type;
    private FeatureFlag featureFlag;
    protected T enabled;
    protected T disabled;

    protected FeatureProxyFactoryBean(Class<T> type) {
        this.type = type;
    }

    @Autowired
    public void setFeatureFlag(FeatureFlag featureFlag) {
        this.featureFlag = featureFlag;
    }

    @Autowired
    public void setEnabled(@Enabled T enabled) {
        this.enabled = enabled;
    }

    @Autowired
    public void setDisabled(@Disabled T disabled) {
        this.disabled = disabled;
    }

    @Override
    public T getObject() {
        Feature feature = type.getAnnotation(Feature.class);
        if (feature == null) {
            throw new IllegalArgumentException(type.getName() + " must be annotated with @Feature");
        }
        String key = feature.value();

        ClassLoader classLoader = FeatureProxyFactoryBean.class.getClassLoader();
        Class<?>[] interfaces = {type};
        return (T) Proxy.newProxyInstance(classLoader, interfaces,
                (proxy1, method, args) -> featureFlag.isEnabled(key) ?
                        method.invoke(enabled, args) :
                        method.invoke(disabled, args));
    }

    @Override
    public Class<T> getObjectType() {
        return type;
    }
}

// test classes

@Feature("test_key")
public interface SomeFeature {
    String foo();
}

@Disabled
@Component
public class DisabledFeature implements SomeFeature {
    @Override
    public String foo() {
        return "disabled";
    }
}

@Enabled
@Component
public class EnabledFeature implements SomeFeature {
    @Override
    public String foo() {
        return "enabled";
    }
}

@Primary
@Component
public class SomeFeatureProxyFactoryBean extends FeatureProxyFactoryBean<SomeFeature> {
    public SomeFeatureProxyFactoryBean() {
        super(SomeFeature.class);
    }
}

然后在需要的地方注入@Inject SomeFeature someFeature,由于@Primary注释,它将获得代理实例。

现在我们可以在Launchdarkly中打开和关闭该功能,它(几乎)立即反映在所有正在运行的实例中,而无需重启或重新初始化Spring上下文。