Spring - 通过指定环境验证注射候选者的资格

发布于 2024-12-06 06:48:01 字数 1840 浏览 1 评论 0原文

编辑: 也许提出这个问题的更简洁的方法是:Spring 是否为我提供了一种通过提供自己的侦听器/工厂/决策逻辑来解决注入时不明确的候选者的方法?


事实上,可以说下面成员字段上的 @Environmental 限定符是不必要的:如果 @Inject-ion 不明确......让我帮忙?事实上, @ResolveWith(EnvironmentalResolver.class) 也可以。


当 Spring 尝试注入依赖项(使用注释)时,我知道如果我要拥有多个实现该接口的组件,我需要 @Qualifier 一个 @Inject 点。

我想做的是这样的:

class MyFoo implements Foo {
    @Inject
    @Environmental
    private Bar bar;
}

@Environmental(Environment.Production)
class ProductionBar implements Bar {
}

@Environmental({Environment.Dev, Environment.Test})
class DevAndTestBar implements Bar {
}

我希望我需要创建某种歧义解析器,它看起来(模糊地)像这样:

< code>

class EnvironmentalBeanAmbiguityResolver {

    // set from configuration, read as a system environment variable, etc.
    private Environment currentEnvironment;

    public boolean canResolve(Object beanDefinition) {
        // true if definition has the @Environmental annotation on it
    }

    public Object resolve(Collection<Object> beans) {
        for (Object bean : beans) {
            // return bean if bean @Environmental.values[] contains currentEnvironment
        }
        throw new RuntimeException(...);
    }
}

一个有用的例子是我们有一项联系最终用户的服务。现在我只有一个 AOP 方面,在方法调用“MailSender”之前,检查“生产”环境标志,如果未设置,它将向我们发送电子邮件而不是用户电子邮件。我'我不想将其包装在特定于邮件发送的 AOP 方面,而是能够根据当前环境区分服务,有时这只是“生产”或“非生产”的问题,正如我上面演示的那样,但每个环境的定义都有效 我认为这也

可以在区域中重用...例如@Regional和@Regional(Region.UnitedStates)等等,

如果你愿意的话,我想@Environmental实际上会是一个@Qualifier。直接依赖于您可以的环境(@Environmental(Production) bean 可能直接依赖于 @Environmental(Production) 协作者 - 因此对于较低级别的项目没有歧义 --- 相同@Regional(US) 项目将明确依赖于其他 @Regional(US) 项目,并且将绕过我尚未理解的 BeanAmbiguityResolver)

谢谢。

Edit:
Perhaps a more concise way to ask this question is: Does Spring provide a way for me to resolve ambiguous candidates at injection time by providing my own listener/factory/decision logic?


In fact, arguably the @Environmental qualifier on the member field below is unnecessary: if an @Inject-ion is ambiguous... let me help? In fact, @ResolveWith(EnvironmentalResolver.class) would be alright too..


When Spring attempts to inject a dependency (using annotations) I understand that I need to @Qualifier an @Inject point if I am to have multiple components that implement that interface.

What I'd like to do is something like this:

class MyFoo implements Foo {
    @Inject
    @Environmental
    private Bar bar;
}

@Environmental(Environment.Production)
class ProductionBar implements Bar {
}

@Environmental({Environment.Dev, Environment.Test})
class DevAndTestBar implements Bar {
}

I would expect that I need to create some kind of ambiguity resolver which would look something (vaguely) like this:

class EnvironmentalBeanAmbiguityResolver {

    // set from configuration, read as a system environment variable, etc.
    private Environment currentEnvironment;

    public boolean canResolve(Object beanDefinition) {
        // true if definition has the @Environmental annotation on it
    }

    public Object resolve(Collection<Object> beans) {
        for (Object bean : beans) {
            // return bean if bean @Environmental.values[] contains currentEnvironment
        }
        throw new RuntimeException(...);
    }
}

One example of where this would be useful is we have a service that contacts end-users. Right now I just have a hacked together AOP aspect that before the method call to the "MailSender', checks for a "Production" environment flag and if it is not set, it sends the email to us instead of the users email. I'd like to instead of wrapping this in an AOP aspect specific to mail sending, instead be able to differentiate services based on the current environment. Sometime's it is just a matter of "production" or "not production" as I've demonstrated above, but a per-environment definition works too.

I think this can be reused for region too... e.g. @Regional and @Regional(Region.UnitedStates) and so on and so forth.

I'd imagine @Environmental would actually be a @Qualifier that way if you wanted to depend directly on something environmental you could (an @Environmental(Production) bean would likely depend directly on an @Environmental(Production) collaborator - so no ambiguity for lower level items --- same a @Regional(US) item would depend on other @Regional(US) items expiclitly and would bypass my yet-to-be-understood BeanAmbiguityResolver)

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

吹泡泡o 2024-12-13 06:48:01

我想我解决了这个问题!

考虑以下情况:

public interface Ambiguity {
    public boolean isSatisfiedBy(BeanDefinitionHolder holder);
}

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
public @interface Ambiguous {
    Class<? extends Ambiguity> value();
}

@Target(TYPE)
@Retention(RUNTIME)
public @interface Environmental {
    public static enum Environment {
        Development, Testing, Production
    };
    Environment[] value() default {};
}

@Named
public class EnvironmentalAmbiguity implements Ambiguity {

    /* This can be set via a property in applicationContext.xml, which Spring
       can use place holder, environment variable, etc. */
    Environment env = Environment.Development;

    @Override
    public boolean isSatisfiedBy(BeanDefinitionHolder holder) {
        BeanDefinition bd = holder.getBeanDefinition();
        RootBeanDefinition rbd = (RootBeanDefinition) bd;

        Class<?> bc = rbd.getBeanClass();

        Environmental env = bc.getAnnotation(Environmental.class);

        return (env == null) ? false : hasCorrectValue(env);
    }

    private boolean hasCorrectValue(Environmental e) {
        for (Environment env : e.value()) {
            if (env.equals(this.env)) {
                return true;
            }
        }
        return false;
    }

}

@Named
public class MySuperDuperBeanFactoryPostProcessor implements
        BeanFactoryPostProcessor, AutowireCandidateResolver {

    private DefaultListableBeanFactory beanFactory;
    private AutowireCandidateResolver defaultResolver;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory arg)
            throws BeansException {
        if (arg instanceof DefaultListableBeanFactory) {
            beanFactory = (DefaultListableBeanFactory) arg;

            defaultResolver = beanFactory.getAutowireCandidateResolver();
            beanFactory.setAutowireCandidateResolver(this);

            return;
        }

        throw new FatalBeanException(
                "BeanFactory was not a DefaultListableBeanFactory");
    }

    @Override
    public Object getSuggestedValue(DependencyDescriptor descriptor) {
        return defaultResolver.getSuggestedValue(descriptor);
    }

    @Override
    public boolean isAutowireCandidate(BeanDefinitionHolder holder,
            DependencyDescriptor descriptor) {
        Ambiguity ambiguity = getAmbiguity(descriptor);

        if (ambiguity == null) {
            return defaultResolver.isAutowireCandidate(holder, descriptor);
        }
        return ambiguity.isSatisfiedBy(holder);
    }

    private Ambiguity getAmbiguity(DependencyDescriptor descriptor) {
        Ambiguous ambiguous = getAmbiguousAnnotation(descriptor);

        if (ambiguous == null) {
            return null;
        }

        Class<? extends Ambiguity> ambiguityClass = ambiguous.value();
        return beanFactory.getBean(ambiguityClass);
    }

    private Ambiguous getAmbiguousAnnotation(DependencyDescriptor descriptor) {
        Field field = descriptor.getField();

        if (field == null) {
            MethodParameter methodParameter = descriptor.getMethodParameter();

            if (methodParameter == null) {
                return null;
            }
            return methodParameter.getParameterAnnotation(Ambiguous.class);
        }
        return field.getAnnotation(Ambiguous.class);
    }

}

现在,如果我有一个接口 MyInterface 和两个实现它的类 MyFooInterface 和 MyBarInterface,如下所示:

public interface MyInterface {
    public String getMessage();
}

@Named
@Environmental({ Environment.Testing, Environment.Production })
public class MyTestProdInterface implements MyInterface {
    @Override
    public String getMessage() {
        return "I don't always test my code, but when I do, I do it in production!";
    }
}

@Named
@Environmental(Environment.Development)
public class DevelopmentMyInterface implements MyInterface {
    @Override
    public String getMessage() {
        return "Developers, developers, developers, developers!";
    }
}

如果我想 @Inject MyInterface 我会得到与预期相同的多个 bean 定义错误。但我可以添加@Ambigously(EnvironmentalAmbiguity.class),然后EnvironmentalAmbiguity会告诉它满足哪个bean定义。

另一种方法是使用 List 并遍历它们,看看它们是否满足给定的 bean 定义,这意味着依赖关系不需要 @Ambigously 注释。这可能更“IoC-ish”,但我也认为它可能表现不佳。我还没有测试过。

I think I solved this!

Consider the following:

public interface Ambiguity {
    public boolean isSatisfiedBy(BeanDefinitionHolder holder);
}

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
public @interface Ambiguous {
    Class<? extends Ambiguity> value();
}

@Target(TYPE)
@Retention(RUNTIME)
public @interface Environmental {
    public static enum Environment {
        Development, Testing, Production
    };
    Environment[] value() default {};
}

@Named
public class EnvironmentalAmbiguity implements Ambiguity {

    /* This can be set via a property in applicationContext.xml, which Spring
       can use place holder, environment variable, etc. */
    Environment env = Environment.Development;

    @Override
    public boolean isSatisfiedBy(BeanDefinitionHolder holder) {
        BeanDefinition bd = holder.getBeanDefinition();
        RootBeanDefinition rbd = (RootBeanDefinition) bd;

        Class<?> bc = rbd.getBeanClass();

        Environmental env = bc.getAnnotation(Environmental.class);

        return (env == null) ? false : hasCorrectValue(env);
    }

    private boolean hasCorrectValue(Environmental e) {
        for (Environment env : e.value()) {
            if (env.equals(this.env)) {
                return true;
            }
        }
        return false;
    }

}

@Named
public class MySuperDuperBeanFactoryPostProcessor implements
        BeanFactoryPostProcessor, AutowireCandidateResolver {

    private DefaultListableBeanFactory beanFactory;
    private AutowireCandidateResolver defaultResolver;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory arg)
            throws BeansException {
        if (arg instanceof DefaultListableBeanFactory) {
            beanFactory = (DefaultListableBeanFactory) arg;

            defaultResolver = beanFactory.getAutowireCandidateResolver();
            beanFactory.setAutowireCandidateResolver(this);

            return;
        }

        throw new FatalBeanException(
                "BeanFactory was not a DefaultListableBeanFactory");
    }

    @Override
    public Object getSuggestedValue(DependencyDescriptor descriptor) {
        return defaultResolver.getSuggestedValue(descriptor);
    }

    @Override
    public boolean isAutowireCandidate(BeanDefinitionHolder holder,
            DependencyDescriptor descriptor) {
        Ambiguity ambiguity = getAmbiguity(descriptor);

        if (ambiguity == null) {
            return defaultResolver.isAutowireCandidate(holder, descriptor);
        }
        return ambiguity.isSatisfiedBy(holder);
    }

    private Ambiguity getAmbiguity(DependencyDescriptor descriptor) {
        Ambiguous ambiguous = getAmbiguousAnnotation(descriptor);

        if (ambiguous == null) {
            return null;
        }

        Class<? extends Ambiguity> ambiguityClass = ambiguous.value();
        return beanFactory.getBean(ambiguityClass);
    }

    private Ambiguous getAmbiguousAnnotation(DependencyDescriptor descriptor) {
        Field field = descriptor.getField();

        if (field == null) {
            MethodParameter methodParameter = descriptor.getMethodParameter();

            if (methodParameter == null) {
                return null;
            }
            return methodParameter.getParameterAnnotation(Ambiguous.class);
        }
        return field.getAnnotation(Ambiguous.class);
    }

}

Now if I have an interface MyInterface and two classes that implement it MyFooInterface and MyBarInterface like this:

public interface MyInterface {
    public String getMessage();
}

@Named
@Environmental({ Environment.Testing, Environment.Production })
public class MyTestProdInterface implements MyInterface {
    @Override
    public String getMessage() {
        return "I don't always test my code, but when I do, I do it in production!";
    }
}

@Named
@Environmental(Environment.Development)
public class DevelopmentMyInterface implements MyInterface {
    @Override
    public String getMessage() {
        return "Developers, developers, developers, developers!";
    }
}

If I want to @Inject MyInterface I would get the same multiple bean definition error that one would expect. But I can add @Ambiguous(EnvironmentalAmbiguity.class) and then the EnvironmentalAmbiguity will tell which bean definition it is satisfied by.

Another approach would have been to use a List and go through them all seeing if they are satisfied by a given bean definition, this would mean that the dependnecy wouldn't need the @Ambiguous annotation. That might be more "IoC-ish" but I also thought it might perform poorly. I have not tested that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文