primary能解决什么问题?

2022/01/10 spring专题 共 6358 字,约 19 分钟
闷骚的程序员

1. spring容器查找bean存在的问题?

spring容器中对外暴露一个 getBean(Class var1) throws BeansException;方法,意味着根据传入的指定类型,在IOC容器中获取该指定类型的bean对象。
实际上这个获取过程要求,当前传入的类型和被获取的对象必须满足isAssignableFrom关系,即传入的类型必须是目标对象的类型或者是其父类型。
因此,当我们传入的类型在整个Spring容器中存在多个子对象时,此时通过该方式获取对象将失败。

观察如下案例.

package test;

// 定义一个接口,同时定义两个实现类
public class NormalBean {
    public interface IService{}
    public static class ServiceA implements IService{}
    public static class ServiceB implements IService{}
}

spring的相关配置文件如下:

<bean id="serviceA" class="test.NormalBean.ServiceA"/>
<bean id="serviceB" class="test.NormalBean.ServiceB"/>

来个用例获取上面定义的bean对象:

package test;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
    @Test
    public void testDependOn() {
        String beanXml = "classpath:/test/applicationContext.xml";
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
        // 获取容器中符合 NormalBean.IService.class.isAssignableFrom(xxx);关系的目标bean xxx。
        NormalBean.IService service = context.getBean(NormalBean.IService.class);
        System.out.println(service);
    }
}

上面代码是从IOC容器中获取类型为NormalBean.IService类型的bean对象。

测试结果如下:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [test.NormalBean$IService] is defined: expected single matching bean but found 2: serviceB,serviceA
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:366)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1066)
at test.Main.testDependOn(Main.java:13)

从上面异常信息可以知道,spring容器中定义了2个bean,分别是serviceA和serviceB,这两个bean对象都实现了IService接口,而用例中我们要获取IService接口对应的bean,此时容器中有2个候选者,serviceA和serviceB都满要求。
因此,spring此时不知道该如何选择,到底是该返回serviceA实例还是返回serviceB实例呢?spring也一脸懵逼,所以就抛出异常了。

我们再看一个通过setter方法自动注入属性的案例:

package test;
public class NormalBean {
    public interface IService{}
    public static class ServiceA implements IService{}
    public static class ServiceB implements IService{}
    private IService service;
    public void setService(IService service){
        this.service = service;
    }
}

xml如下:

<bean id="serviceA" class="test.NormalBean.ServiceA"/>
<bean id="serviceB" class="test.NormalBean.ServiceB"/>
<bean id="setBean" class="test.NormalBean" autowire="byType"/>

上面配置文件中,setBean对象设置了byType,即按照类型自动注入依赖,spring会调用NormalBean的setService方法,在容器中查找和该方法参数满足isAssignableFrom关系的bean对象,然后将该bean对象通过setService方法注入进去。

执行测试用例,结果如下:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'setBean' defined in class path resource [test/applicationContext.xml]: Unsatisfied dependency expressed through bean property 'service': No qualifying bean of type [test.NormalBean$IService] is defined: expected single matching bean but found 2: serviceA,serviceB; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [test.NormalBean$IService] is defined: expected single matching bean but found 2: serviceA,serviceB
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1307)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1199)

按照需要的类型去容器中查找匹配的bean对象,期望只有一个匹配的,实际上却找到了2个匹配的,不知道如何选择,所以报错了。

当我们希望从容器中获取一个bean对象时,容器中却找到了多个符合条件的bean,此时spring就开始懵逼,不知道如何选择了,就会报上述异常。

再详细扩展一下知识点。
spring容器中根据类型获取bean实例,它根据的是接口的类型还是具体实现类的类型呢?
在 Spring 容器中,根据类型获取 Bean 实例时,可以根据接口类型或具体实现类类型来获取。以下是一些具体的场景和示例,说明如何通过不同类型获取 Bean 实例。

根据接口类型获取 Bean

如果一个 Bean 实现了某个接口,可以通过接口类型获取该 Bean 实例。这种方式在解耦和面向接口编程中非常常见。

示例: 假设有一个接口 MyService 和它的实现类 MyServiceImpl

public interface MyService {
    void doSomething();
}

@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

通过接口类型获取 Bean:

@Autowired
private MyService myService;  // 注入的是 MyServiceImpl 的实例

根据具体实现类类型获取 Bean

在某些情况下,您可能需要获取某个具体实现类的 Bean 实例。这种方式更具体,有时用于测试或在需要具体实现行为的情况下。

示例: 继续使用上面的 MyServiceMyServiceImpl

通过具体实现类类型获取 Bean:

@Autowired
private MyServiceImpl myServiceImpl;  // 注入的是 MyServiceImpl 的实例

使用 ApplicationContext 动态获取 Bean

有时,您需要在运行时根据类型动态获取 Bean 实例,这可以通过 ApplicationContext 来实现:

示例:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        
        // 根据接口类型获取 Bean
        MyService myService = context.getBean(MyService.class);
        myService.doSomething();

        // 根据具体实现类类型获取 Bean
        MyServiceImpl myServiceImpl = context.getBean(MyServiceImpl.class);
        myServiceImpl.doSomething();
    }
}

在以上示例中,无论是通过接口类型还是具体实现类类型,都可以获取到 MyServiceImpl 的实例并调用其方法。

总结

  • 接口类型:通过接口类型获取 Bean 更加通用,适用于面向接口编程。
  • 具体实现类类型:通过具体实现类类型获取 Bean 适用于需要具体实现的行为场景。

Spring 容器能够处理这两种情况,您可以根据实际需求选择合适的方式获取 Bean 实例。

@ConditionalOnBean中的class,指的是接口的类型,还是具体实现类的类型呢?
@ConditionalOnBean 注解可以用来在 Spring 应用上下文中条件性地加载一个 Bean。当使用 @ConditionalOnBean 注解时,您可以通过 value 属性指定一个或多个类型,这些类型可以是接口或具体的实现类。

具体来说,@ConditionalOnBean 注解的 value 属性可以是接口的类型或具体实现类的类型。只要 Spring 应用上下文中存在该类型的 Bean,该条件就会满足,标注了 @ConditionalOnBean 的配置类或 Bean 就会被加载。

下面是一些示例:

使用接口类型

@Configuration
@ConditionalOnBean(MyService.class)
public class MyServiceConfig {
    // 只有当 MyService 的 Bean 存在时,这个配置类才会被加载
}

使用具体实现类类型

@Configuration
@ConditionalOnBean(MyServiceImpl.class)
public class MyServiceConfig {
    // 只有当 MyServiceImpl 的 Bean 存在时,这个配置类才会被加载
}

在上述两个示例中,只要应用上下文中存在 MyServiceMyServiceImpl 类型的 Bean,MyServiceConfig 配置类就会被加载。

如果您想确保某个接口的任意实现类存在时加载配置,通常使用接口类型更加灵活和通用。如果您只想在特定实现类存在时加载配置,则可以使用具体实现类的类型。

2. 解决容器中匹配多个bean对象的问题

如何解决上述异常呢?

spring中可以通过bean元素的primary属性来解决这个问题,使用这个属性来指定当前bean为主要候选者,当容器查询一个bean的时候,如果容器中有多个候选者匹配的话,此时spring会返回主要候选者。

我们来使用primary来解决上述问题:

package test;
public class NormalBean {
    public interface IService{}
    public static class ServiceA implements IService{}
    public static class ServiceB implements IService{}
    private IService service;
    public void setService(IService service){
        this.service = service;
    }
}

spring配置文件如下:

<bean id="serviceA" class="test.NormalBean.ServiceA" primary="true"/>
<bean id="serviceB" class="test.NormalBean.ServiceB"/>
<bean id="setBean" class="test.NormalBean" autowire="byType"/>

测试结果如下:

test.NormalBean$ServiceA@5113ff65

分析上述过程:
从容器中查找IService类型匹配的bean,这个接口存在两个实现类,ServiceA和ServiceB,这两个实现类都交给spring容器进行管理了,但是serviceA被设置作为主要的候选者bean,所以从容器中获取IService类型的bean对象会返回serviceA。

3. 总结

  1. 当从容器中查找一个bean对象时,如果容器中出现多个bean候选者时,可以通过primary=”true”将当前bean设置为首选者,这样在查找时就会返回该主要候选者,否则将抛出异常。
  2. 如果候选者bean中有多个bean都被指定为primary的话,那么查找这些bean对象时同样会报错。

文档信息

Search

    Table of Contents