【问题标题】:Spring proxy to choose implementation based on annotation and runtime valueSpring 代理根据注解和运行时值选择实现
【发布时间】:2014-08-13 16:02:10
【问题描述】:

我想将接口的代理实现注入到组件中,然后让 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();
    }
}

注入的服务应该只是一个代理。根据MyValueHolder 中设置的值,对doSomething 的调用应该委托给service1 或service2。所以在这个例子中,它应该在第一次调用中委托给 service1 上的 doSomething,在第二次调用中委托给 service2。

我可以编写这样一个实现IService 接口的委托器并将其用于这项服务。但随后我必须对其他所有服务重复此操作。我希望 spring 几乎可以自己用代理做这样的事情。当然,我必须提供一些方法来根据本地线程中的值查找bean并将其注册到spring。但我不知道在不修改弹簧框架的情况下这是否可能。如果有可能如何做到这一点。

【问题讨论】:

  • 这完全取决于您所说的“让 spring 根据一些可查询的运行时属性和 MyAnnotation 的值选择正确的实现”的意思。您可能应该编写一个单元测试来充实您期望的行为。
  • '让 spring 选择正确的实现'意味着 spring 使用接口类型和注释值进行某种查找以获得正确的实现,注入的代理然后调用这个查找的实现。
  • Spring 已经检查了类型并使用@Qualifier 注解来选择要注入的bean。
  • 是的,但是@Qualifier 按名称注入实现。但关键是我有两个实现,只有一个应该用于调用。应该通过检查运行时值(实际上是线程本地值)来确定哪一个。所以我在IService 上调用doSomeThing() 方法,现在应该检查本地线程的值。如果它包含“service1”,则调用被委托给Service1,如果它包含“service2”,它被委托给Service2。这种查找应该对使用IService的类透明地发生
  • @Finn 你能详细说明一下选择是如何发生的吗?多一点信息会有所帮助

标签: java spring proxy scope


【解决方案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;
    }
}

【讨论】:

  • 这为我指明了正确的方向。我使用BeanDefinitionRegistryPostProcessor 创建代理bean 定义并将实际实现标记为不是自动装配的候选对象。谢谢
【解决方案2】:

这就是我会做的:

@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,然后创建并注入一个实现该字段所需类型的代理。

代理实现将在 applicationContext 中查找实现 Supplier&lt;T&gt;(Java 8 或 guava 版本)的 bean,其中 &lt;T&gt; 是使用 @InjectDynamic 注释的字段的类型。

然后你可以定义

@Name
public IServiceSupplier implements Supplier<IService> {
     @Override
     public IService get() {
            // here you implement the look-up logic for IService
     }
}

通过这种方式,当前实现的活动查找与代理分离,并且可以通过目标类型进行更改。

【讨论】:

  • 为了清楚起见,我认为这通常是一个坏主意除非您可以保证代理不会在 MyComponent 方法的方法调用期间更改 MyComponent 的 IService 的实现。这意味着您也必须代理 MyComponent。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-09
  • 2018-09-23
  • 1970-01-01
  • 2018-04-17
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多