【问题标题】:Spring bean resolution based on unique constructors基于唯一构造函数的 Spring bean 解析
【发布时间】:2018-02-09 13:55:00
【问题描述】:

如果您能帮助我解决以下问题,我们将不胜感激。 假设我有以下类和接口:

public interface BaseType {
     public void method();
}

@Component
@Scope(SCOPE_PROTOTYPE)
public class BaseTypeImpl implements BaseType {
    private int num;

    public BaseTypeImpl(int num) {
        this.num = num;
    }

    @Override
    public void method() {
        System.out.println(num);
    }
}

@Component
@Scope(SCOPE_PROTOTYPE)
public class ChildBaseTypeImpl extends BaseTypeImpl {

    String mes;

    public ChildBaseTypeImpl(int num, String mes) {
        super(num);
        this.mes = mes;
    }

    @Override
    public void method() {
        super.method();
        System.out.println(mes);
    }
}

@Component
@Scope(SCOPE_PROTOTYPE)
public class SecondaryTypeImpl implements BaseType {

    private String str;

    public SecondaryTypeImpl(String str) {
        this.str = str;
    }

    @Override
    public void method() {
        System.out.println(str);
    }
}

结果,我有 3 个类实现了 1 个接口。 所有类都有具有不同参数的非默认构造函数。 有没有办法让Spring根据构造函数参数通过BaseType接口查找正确的bean? 我想这样做:

ApplicationContext ctx = new FileSystemXmlApplicationContext("beans.xml");
ctx.getBean(BaseType.class, 11); //Should return instance of BaseTypeImpl class object, since it has constructor taking int

或者像这样:

ctx.getBean(BaseType.class, 11, "hello!"); //Should return instance of ChildBaseTypeImpl

尝试这样做会导致异常:

线程“main”中的异常 org.springframework.beans.factory.NoUniqueBeanDefinitionException: 否 “springtest.BaseType”类型的合格 bean 可用:预期 单个匹配的 bean,但找到了 3 个: baseTypeImpl,childBaseTypeImpl,secondaryTypeImpl

似乎没有直接的方法可以做到这一点。

但也许可以获取所有可从BaseType 分配的类,在其中找到合适的构造函数,然后以*Impl 类作为第一个参数调用getBean 方法?

更新

感谢大家的回答! 正如 pvpkiran 提到的,有一种方法可以通过 bean 名称获取 bean 类。 这为反射打开了大门,从而解决了问题,这是示例代码:

public class App {

    static ApplicationContext ctx = new FileSystemXmlApplicationContext("beans.xml");

    public static void main(String[] args) {
        BaseType beanByInterface = getBeanByInterface(BaseType.class, "Hello!");
        System.out.println(beanByInterface.getClass());
    }

    public static <T> T getBeanByInterface(Class<T> interf, Object... params) {

        BeanDefinitionRegistry bdr = new SimpleBeanDefinitionRegistry();
        ClassPathBeanDefinitionScanner s = new ClassPathBeanDefinitionScanner(bdr);

        TypeFilter tf = new AssignableTypeFilter(interf);
        s.addIncludeFilter(tf);
        s.scan("springtest"); //Sample project package name
        String[] beans = bdr.getBeanDefinitionNames();

        for(String b : beans) {
            Class<?> type = ctx.getType(b);
            MAIN: for(Constructor cons : type.getConstructors()) {
                if (cons.getParameterCount() == params.length) {
                    for (int i = 0; i < cons.getParameterCount(); i++) {
                        if (!params[i].getClass().equals(cons.getParameterTypes()[i])) { //Will fail comparing primitive and boxed types, just leaving like this for simplicity
                            continue MAIN;
                        }
                    }
                    return (T) ctx.getBean(type, params);
                }
            }
        }

        return null;
    }

}

代码有问题,但这只是为了展示概念。 有了 bean 类,我可以获得正确的 bean,它具有所需的构造函数。

此示例输出“class springtest.SecondaryTypeImpl”

但我相信这应该是一些实用程序中涵盖的常见问题。 我不想发明自行车,但还是找不到解决办法。

更新 2

似乎现有库中没有这样的解决方案,因为这不是最佳实践。 无论如何,这是更新的方法,也许有人会发现它有用。

public static <T> T getBeanByInterface(Class<T> interf, Object... params) {

    String[] beans = ctx.getBeanNamesForType(interf);

    for(String beanName : ctx.getBeanNamesForType(interf)) {
        Class<?> type = ctx.getType(beanName);
        Class<?>[] paramTypes = new Class[params.length];
        //Getting params types
        for (int i = 0; i < params.length; i++) {
            paramTypes[i] = params[i].getClass();
        }

        if (ConstructorUtils.getMatchingAccessibleConstructor(type, paramTypes) != null) {
            return (T) ctx.getBean(type, params);
        }

    }
    return null;
}

【问题讨论】:

  • 我不认为你可以通过 Type 和 arguments 得到它,你可以从 bean name 和 arguments 得到它。你会这样吗?
  • 嗨 pvpkiran!我做对了吗 - 我可以通过它的名字来获取 bean 类?是的,我认为这应该对我有用。谢谢。

标签: java spring dependency-injection constructor


【解决方案1】:

BeanFactory 有以下getBean() 声明

Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException

正如您在此处看到的,没有声明类类型、bean 名称和参数(这是您要查找的)。

但是您可以使用 bean 名称和参数来获取 bean。

 ApplicationContext ctx = new FileSystemXmlApplicationContext("beans.xml");

 ChildBaseTypeImpl childBaseTypeImpl = (ChildBaseTypeImpl) ctx.getBean("childBaseTypeImpl", 11, "hello");
 BaseTypeImpl basetypeImpl = (BaseTypeImpl)  ctx.getBean("baseTypeImpl", 11, );  

【讨论】:

    【解决方案2】:

    好吧,正如@alexlys 所指出的,您不能使用构造函数参数来区分您的 bean。但是你可以用不同的方法得到相同的结果。这两种方法都假定您可以在运行时定义不需要的 bean,而是在编写代码时定义。

    • 您可以创建具有唯一名称的 bean,然后通过 名称,而不是接口

    • 您可以添加一些标记接口,例如 interface SecondaryType extends BaseType {//empty}。每个 bean 将只实现一个特定的接口,因此您可以使用此类 iterfaces 实例化 bean。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-03
      • 1970-01-01
      • 2014-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-14
      相关资源
      最近更新 更多