Spring框架的自动装配解析Autowiring Collaborators

前言

在SpringIOC的开发框架中,想要完成依赖注入需要把依赖关系描述清楚。第一个地方是要我们在类的定义,就是我们所写的代码里面体现出来依赖关系。第二个是在spring的配置中去描述我们定义好的依赖关系。如果按照这样两部操作,实际开发过程中就有了一个很麻烦的事情:维护依赖关系。比如一个类依赖了其他100个类,那就需要码农们手动在xml文件里面维护至少100个<bean>标签,而且不能出错,可以说非常的麻烦和繁琐。所以Spring框架为了解决这个问题就提出了自动装配这个概念。自动装配就是把维护的部分给省略去了,所以现在我们使用Spring框架的时候仅仅需要在类中提供依赖,继而把对象交给容器管理即可完成注入。可以说自动装配是Spring的精髓中的精髓,那么本篇我们就探究一下官方文档中是怎么介绍这个东西的。更多Spring内容进入【Spring解读系列目录】

Autowiring Collaborators

自动装配的Spring官方名字叫做Autowiring Collaborators。首先看下官方文档是怎么解释这个自动装配的,以下摘自官方文档:

The Spring container can autowire relationships between collaborating beans. You can let Spring resolve collaborators (other beans) automatically for your bean by inspecting the contents of the ApplicationContext.

这段话的大意就是:Spring容器可以自动装配合作的bean的关系。通过检查ApplicationContext这个东西,使得你能够让spring自动的为你的bean去解析合作者(也就是其他的bean)。

官方文档说的很绕,一句话总结就是:自动装配可以通过检查ApplicationContext,然后自动帮你维护你自己的bean之间的关系。

自动装配优点

有两大块,以下摘自官方文档:

Autowiring can significantly reduce the need to specify properties or constructor arguments. (Other mechanisms such as a bean template discussed elsewhere in this chapter are also valuable in this regard.)

大意:自动装配可以显著的减少属性配置和构造方法

Autowiring can update a configuration as your objects evolve. For example, if you need to add a dependency to a class, that dependency can be satisfied automatically without you needing to modify the configuration. Thus autowiring can be especially useful during development, without negating the option of switching to explicit wiring when the code base becomes more stable.

大意:当对象变更的时候,自动装配可以自动更新配置。比如添加依赖,如果需要给一个类添加一个依赖,这个过程使用自动装配可以自动进行,而不需要修改配置。所以官方说Autowiring在开发周期中非常有用,因为我们修改依赖也不需要显式的写出来,所以可以使代码更稳定。

自动装配的模式

官方文档也有写,一共有四种。本篇博文将会用byName和byType两种来解析Spring自动装配的操作。

Mode Explanation
no (Default) No autowiring. 不使用自动装配,配置no和default是一样的
byName 通过指定Name来装配,其实这个也是通过setter方法来装配, 因为这个Name就是来自于setter方法。
byType 通过指定Type(类型)来装配
constructor 通过使用构造方法装配

但是官方文档上并没有详细的给出怎么装配的,所以,我们还是写几个例子。

全局指定

byType

第一种实现就是全局指定,为了更好的解释,我们使用xml来作为一个例子: Resource文件夹下新建一个spring-config.xml的spring配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dao" class="com.example.demo.DemoDaoImpl"></bean>
    <bean id="service" class="com.example.demo.DemoService"></bean>
</beans>

举个简单的例子:我们现在有一个接口DemoDao,一个实现类DemoDaoImpl,一个业务类DemoService。

public interface DemoDao {
    
      //接口
    void test();
}
public class DemoDaoImpl implements DemoDao{
    
      //实现类
    @Override
    public void test() {
    
    
        System.out.println("test");
    }
}
public c
public class DemoService {
    
      //业务类
    private DemoDao dao;  //依赖
    public void doTest(){
    
    
        dao.test();
    }
    public void setDao(DemoDao dao) {
    
     //setter方法
        this.dao = dao;
    }
}

然后我们还要有一个测试类DemoTest和一个配置类Spring。说明一下:尽管笔者这里要讲解的是基于xml配置的,但是由于基于java的配置比较好编码,这里笔者用的混合型的配置,如果不明白请移步【Spring框架容器开启注解的方式】,可以了解更多有关配置的内容。

@Configuration
@ComponentScan("com.example")
@ImportResource("classpath:spring.xml")
public class Spring {
    
      //Spring配置类
}
public c
public class DemoTest {
    
      //测试类
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext acac=new AnnotationConfigApplicationContext(Spring.class);
        DemoService service= (DemoService) acac.getBean("service");
        service.doTest();
    }
}

可以看出DemoService依赖了DemoDao,我们在代码里提供了一个依赖关系名字叫dao。而且还有一个setter方法作为未来注入的地方。如果按照这个例子不进行自动注入的话,那么我们就要在<bean></bean>里再维护一个依赖,用来描述DemoService和DemoDao的关系。我都已经在程序里描述清楚了,为什么我还要再给你配置一遍呢?开发起来就会很麻烦,如果依赖成千上百,那有的是时间去配置维护了。自动装配就是解决这个问题的。

如果要添加全局指定的自动装配的配置,只需要添加一个属性default-autowire="byType",修改xml配置文件为如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd"
        default-autowire="byType"> <!-- byName | no | default | constructor-->
    <bean id="dao" class="com.example.demo.DemoDaoImpl"> </bean>
    <bean id="service" class="com.example.demo.DemoService"></bean>
</beans>

直接运行就可以打出test,这里我们使用的是" byType"方式。如果我们把default-autowire=" byType"这句话删掉,就会报一个空指针异常,这是为什么呢?那就要讲到自动装配的原理了。

Exception in thread "main" java.lang.NullPointerException
	at com.example.demo.DemoService.doTest(DemoService.java:12)
	at com.example.demo.DemoTest.main(DemoTest.java:13)
自动装配的原理

我们先解释什么是byType注入。比如程序扫描到了service这个id,发现它对应的类DemoService有一个依赖,而且发现这个依赖的类型(Type)是DemoDao,然后就会再去循环Spring整个容器当中去检索有没有一个类的接口或者和父类的类型是等于DemoDao,如果发现了就把这对象new出来然后赋给dao。笔者的例子中DemoDaoImpl就是实现的DemoDao,所以Spring就new了DemoDaoImpl给了它。到这里就很明白了是吧,如果我们删了default-autowire这个配置,那么Spring就不会new一个对象给dao。自然遇到dao.test();这行代码的时候,肯定会报空指针异常的。

冲突

那么其实这里还有一个问题。如果说我的程序里面有两个DemoDao怎么办?比如我再创建一个类DemoDaoImpl2也实现DemoDao。

public class DemoDaoImpl2 implements DemoDao{
    
    
    @Override
    public void test() {
    
    
        System.out.println("test 2");
    }
}

然后把DemoDaoImpl2也维护到配置文件中。其实如果用的是idea这里就已经报错了。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd"
        default-autowire="byType">
    <bean id="dao" class="com.example.demo.DemoDaoImpl"></bean>
    <bean id="dao1" class="com.example.demo.DemoDaoImpl2"></bean>
    <bean id="service" class="com.example.demo.DemoService"></bean>
</beans>

直接运行肯定报错。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'service' defined in class path resource [spring.xml]: Unsatisfied dependency expressed through bean property 'dao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.DemoDao' available: expected single matching bean but found 2: dao,dao1
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1524)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1404)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
	at com.example.demo.DemoTest.main(DemoTest.java:8)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.DemoDao' available: expected single matching bean but found 2: dao,dao1
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1285)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1509)
	... 12 more

大家看报的错误是expected single matching bean but found 2,Spring只要一个但是匹配了俩,冲突了。这个要怎么解决呢?其实我们可以使用byName的方式搞定

ByName

其实我们的<bean>里面还有一个name属性,如果说name没有指定,那么name默认和id是一样的。而这个name其实就是和里面的setter方法的名字有关系,注意并不是和声明的变量的名字有关。我们看DemoService里面的setter方法叫做setDao。Spring就会自动把set去掉然后首字母小写,变为dao。然后就去找dao这个id或者name。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd"
        default-autowire="byName">
    <bean id="dao" class="com.example.demo.DemoDaoImpl"></bean>
    <bean id="dao1" class="com.example.demo.DemoDaoImpl2"></bean>
    <bean id="service" class="com.example.demo.DemoService"></bean>
</beans>

如果说改成byName那么直接运行,就会打印test。那么如果我想打印test 2怎么改呢?如上所说,我们要改的就是DemoService里面的setter方法:

public class DemoService {
    
    
    private DemoDao dao;
    public void doTest(){
    
    
        dao.test();
    }
    public void setDao1(DemoDao dao) {
    
     //从setDao修改为setDao1,这样打印的就是test 2了
        this.dao = dao;
    }
}

如果大家还有疑问,或者我们解释的更彻底一些。我们把xml里的dao1改了成computer,然后我们修改setDao1为setComputer。再运行依然是test 2。
spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd"
        default-autowire="byName">
    <bean id="dao" class="com.example.demo.DemoDaoImpl"> </bean>
    <bean id="computer" class="com.example.demo.DemoDaoImpl2"> </bean>
    <bean id="service" class="com.example.demo.DemoService"> </bean>
</beans>

DemoService:

public class DemoService {
    
    
    private DemoDao dao; //byName的扫描和声明的变量名字没有关系
    public void doTest(){
    
    
        dao.test();
    }
    public void setComputer(DemoDao dao) {
    
     //修改有关的地方,在setter方法上
        this.dao = dao;
    }
}

public class DemoService {
    
    
    private DemoDao computer; //修改变量名
    public void doTest(){
    
    
        dao.test();
    }
    public void setDao(DemoDao dao) {
    
     //这里不改,依然打印的是test,而不是test2
        this.dao = dao;
    }
}

这就是byName的作用。

单独指定

如果说我们配置了全局指定,但是发现了冲突,而又不免不了这个冲突必须让某一个类去使用怎么办呢?这时候就可以用单独指定的办法,修改配置文件spring.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd"
        default-autowire="byType">
    <bean id="dao" class="com.example.demo.DemoDaoImpl"> </bean>
    <bean id="computer" class="com.example.demo.DemoDaoImpl2" > </bean>
    <bean id="service" class="com.example.demo.DemoService"  autowire="byName"> </bean>
</beans>

结语

那么这就是Spring的自动装配的原理和操作方法,也是Spring的最最核心的功能。但是我们并不常用这些内容,这里的讲解只是告诉大家他们的原理,那么下一篇【什么是@Autowired和@Reource以及其机制】就是真正和我们日常码代码有关的了。

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/107908488