我想为组件注入接口的代理实现,然后让spring根据运行时属性(以及实现类中的注释的值)选择正确的实现。所以我的组件不必关心选择正确的组件。
有点像范围。但我认为范围仅用于处理同一实现类的不同实例。我错了吗?
我希望这可以为任意接口运行,而无需为每个新服务创建服务定位器或其他构造。
这是一个例子。
假设我有一个定义服务的接口
package test;
public interface IService {
void doSomething();
}
和两个实现:
package test;
import javax.inject.Named;
@Named
@MyAnnotation("service1")
public class Service1 implements IService {
@Override
public void doSomething() {
System.out.println("this");
}
}
...
package test;
import javax.inject.Named;
@Named
@MyAnnotation("service2")
public class Service2 implements IService {
@Override
public void doSomething() {
System.out.println("that");
}
}
现在我想将一个IService注入另一个组件,让spring根据一些可查询的运行时属性和MyAnnotation的值选择正确的实现。
有没有办法在春天以一般方式做到这一点?
编辑:
我有一个拥有一些价值的上下文。在这种情况下,它是本地的线程。
package test;
public class MyValueHolder {
private static final ThreadLocal<String> value = new ThreadLocal<>();
public static void set(String newValue) {
value.set(newValue);
}
public static String get() {
return value.get();
}
public static void reset() {
value.remove();
}
}
我有一个使用IService
package test;
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class MyComponent {
@Inject
private IService service;
public void myImportantWorkflow(){
MyValueHolder.set("service1");
service.doSomething();
MyValueHolder.set("service2");
service.doSomething();
}
}
注入的服务应该只是一个代理。根据{{1}}中设置的值,对MyValueHolder
的调用应委托给service1或service2。因此,在此示例中,它应该在第一次调用中委托给service1上的doSomething,而在第二次调用中委托给service2。
我可以编写一个实现doSomething
接口的委托人,并将其用于这一项服务。但是我必须为其他所有服务重复这一点。我希望春天可以用代理几乎单独做这样的事情。当然,我必须提供一些方法来根据本地线程中的值hold来查找bean并将其注册到spring。但我不知道如果不修改弹簧框架,这是否可行。如果有可能如何实现这一点。
答案 0 :(得分:1)
您可以使用ProxyFactoryBean
创建代理,使用TargetSource
进行查找。
例如(未经测试)
public class AnnotatedBeanTargetSource implements TargetSource, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
private Class<? extends Annotation> annotationType;
private Class<?> implementedIterface;
private Map<String, Object> beans;
@Override
public Class<?> getTargetClass() {
return this.implementedIterface;
}
@Override
public boolean isStatic() {
return false;
}
@Override
public Object getTarget() throws Exception {
if (this.beans == null) {
this.beans = lookupTargets();
}
return this.beans.get(MyValueHolder.get());
}
protected Map<String, Object> lookupTargets() {
Map<String, Object> resolvedBeans = new HashMap<String, Object>();
String[] candidates = beanFactory.getBeanNamesForAnnotation(annotationType);
for (String beanName : candidates) {
Class<?> type = beanFactory.getType(beanName);
if (this.implementedIterface.isAssignableFrom(type)) {
Annotation ann = AnnotationUtils.getAnnotation(type, annotationType);
resolvedBeans.put((String) AnnotationUtils.getValue(ann), beanFactory.getBean(beanName));
}
}
return resolvedBeans;
}
@Override
public void releaseTarget(Object target) throws Exception {
// nothing to do
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
public Class<? extends Annotation> getAnnotationType() {
return annotationType;
}
public void setAnnotationType(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
public Class<?> getImplementedIterface() {
return implementedIterface;
}
public void setImplementedIterface(Class<?> implementedIterface) {
this.implementedIterface = implementedIterface;
}
}
答案 1 :(得分:1)
这就是我要做的事情:
@Named
public class MyComponent {
// introduce a marker interface for Injecting proxies
@InjectDynamic
IService service
...
public void useIService() {
service.doSomething();
...
service.doSomethingElse();
...
service.doFinally();
}
}
定义一个BeanPostProcessor,用@InjectDynamic
注释的字段扫描bean,然后创建并注入一个实现该字段所需类型的代理。
Proxy实现将在applicationContext中查找实现Supplier<T>
(Java 8或guava版本)的bean,其中<T>
是用@InjectDynamic
注释的字段的类型。
然后你可以定义
@Name
public IServiceSupplier implements Supplier<IService> {
@Override
public IService get() {
// here you implement the look-up logic for IService
}
}
通过这种方式,当前实现的活动查找与Proxy分离,并且可以按目标类型进行更改。