我们一直在AWS Lambdas中使用Guice进行DI,但现在正在转向Spring Boot和长时间运行的服务。
我们在 Guice 中将功能切换作为动态代理工作,但需要在 Spring 中实现。
假设我们有一个SomeFeature
接口和两个实现DisabledImplementation
和EnabledImplementation
。
我可以通过用@Component("some.feature.disabled")
标记DisabledImplementation
,用@Component("some.feature.enabled")
标记EnabledImplementation
,然后编写这样的实现来非常接近:
@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
或其他方式执行此操作吗?
我现在有一个相当不错的解决方案。
@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 上下文。