【Spring学习笔记 四】Spring自动装配机制实践

简介: 【Spring学习笔记 四】Spring自动装配机制实践

我们一般学习某个知识,一定会现有个较为复杂的配置让你理解其中的关系,这个配置清晰规整,但是可能会需要大量的配置,这个时候就会有约定大于配置的理论实现了,通过我们约定好的一致的名称,我可以少写很多对应关系,例如MyBatis中如果数据库表的字段名和模型Model的字段名一致,那么就不需要结果集映射了。同样的在Spring中如果我们约定好一些规则,也能减少XML的配置。本篇Blog我们依然基于XML配置进行介绍,基于注解配置和Java类配置的实现方式统一在后续Blog介绍。

自动装配概念

自动装配是使用spring满足bean依赖的一种方法,spring会在应用上下文中为某个bean寻找其依赖的bean。Spring的自动装配需要从两个角度来实现,或者说是两个操作:

  • 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean
  • 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI

组件扫描和自动装配组合发挥巨大威力,使得显式的配置降低到最少,换句话说:Spring 容器可以在不使用<constructor-arg><property> 元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量

自动装配模式

下列自动装配模式,它们可用于指示 Spring 容器为来使用自动装配进行依赖注入。可以使用<bean>元素的 autowire 属性为一个 bean 定义指定自动装配模式

事实上,我们一般不推荐使用constructor的形式,因为当bean存在多个构造器的时候,构造方式太复杂了,所以这里不做详细介绍,我们主要学习下byNamebyType的实现方式,当然事实上我们只是通过XML配置来入门理解,实际上用注解实现自动装配更便捷一些。

自动装配限制

当然方便带来的问题就是混乱,所以自动装配也有一些限制:

  • 使用byName进行自动装配时,名称必须一一对应的上,也就是当前bean的属性和其依赖的bean的名称定义相同
  • 使用byType进行自动装配时,类型必须唯一,如果依赖的bean有多个类型相同的,那么会抛异常
  • 使用constructor进行自动装配时,必须让构造器有参,同时如果有多个有参构造器,配置也会非常混乱复杂。

所以方便一般只适用于简单的依赖关系,如果80%的场景是简单关系,那么自动装配很适合使用,也就是说我们不能抛弃规整的XML配置,而是依据场景而定。

自动装配实现

接下来我们实践下这两种实现方式,看看自动装配如何发挥作用的,还拿之前的Person类来举例:

Person类

package com.example.Spring.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Perosn {
    private String name;
    private int age;
    private String[] emails;
    private List<String> interests;
    private Set<String> games;
    private Map<String,String> bankAccountMap;
    private String badHobby;
    private Address address;
    private Properties info;
    public void show(){
        System.out.println("name: "+name);
        System.out.println("age: "+age);
        System.out.print("emails: ");
        for (String email:emails){
            System.out.print(email+" ");
        }
        System.out.println(" ");
        System.out.println("interests: "+interests);
        System.out.println("games: "+games);
        System.out.println("bankAccountMap: "+bankAccountMap);
        System.out.println("badHobby: "+badHobby);
        System.out.println("address: "+address);
        System.out.println("info: "+info);
    }
}

这里的Address即是我们依赖的类,我们测试下对Address的自动装配

<bean id="person" class="com.example.Spring.model.Perosn" >
        <!--常量注入-->
        <property name="name" value="tml"/>
        <!--常量注入-->
        <property name="age" value="30"/>
        <!--array注入-->
        <property name="emails">
            <array>
                <value>12345668@qq.com</value>
                <value>12345668@163.com</value>
                <value>12345668@126.com</value>
            </array>
        </property>
        <!--list注入-->
        <property name="interests">
            <list>
                <value>听歌</value>
                <value>看电影</value>
                <value>爬山</value>
            </list>
        </property>
        <!--Set注入-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>王者荣耀</value>
                <value>使命召唤</value>
            </set>
        </property>
        <!--Map注入-->
        <property name="bankAccountMap">
            <map>
                <entry key="中国邮政" value="456456456465456"/>
                <entry key="建设" value="1456682255511"/>
            </map>
        </property>
        <!--Null注入-->
        <property name="badHobby"><null/></property>
        <!--Bean注入-->
        <property name="address" ref="address"/>
        <!--属性注入-->
        <property name="info">
            <props>
                <prop key="学号">20190604</prop>
                <prop key="性别">男</prop>
                <prop key="姓名">小明</prop>
            </props>
        </property>
    </bean>
    <bean id="address" class="com.example.Spring.model.Address" >
        <property name="cityCode" value="100031"/>
        <property name="cityName" value="北京"/>
    </bean>

非自动实现方式

首先我们来看下之前的非自动装配的实现方式,只截取Address部分的配置,原有的应用方式是这样的:

<bean id="person" class="com.example.Spring.model.Perosn" >
        <!--Bean注入-->
        <property name="address" ref="address"/>
</bean>
<bean id="address" class="com.example.Spring.model.Address" >
        <property name="cityCode" value="100031"/>
        <property name="cityName" value="北京"/>
</bean>

byName自动装配

我们再来看下通过byName自动装配的实现方式,只截取Address部分的配置,新的配置方式如下:

<bean id="person" class="com.example.Spring.model.Perosn" autowire="byName">
</bean>
<bean id="address" class="com.example.Spring.model.Address" >
        <property name="cityCode" value="100031"/>
        <property name="cityName" value="北京"/>
</bean>

当一个bean节点带有 autowire byName的属性时

  1. 装配时将查找其类中所有的set方法名,如果配置中有则使用配置中的
  2. 如果配置中没有则去spring容器中寻找是否有此字符串名称id的对象
  3. 如果有,就取出注入;如果没有,返回null

我们测试下实现:

public class PersonTest {
    @Test
    public void PersonTest(){
        //解析beans.xml文件,生成管理相应的bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Perosn person = (Perosn) context.getBean("person");
        person.show();
    }
}

可以发现地址还是能被正常打印出来:

这种方式要求,属性名必须和配置文件中对应对象的id一致,否则会找不到装配对象。

byType自动装配

我们再来看下通过byType自动装配的实现方式,只截取Address部分的配置,新的配置方式如下:

<bean id="person" class="com.example.Spring.model.Perosn" autowire="byType">
</bean>
<bean id="address2" class="com.example.Spring.model.Address" >  //即使属性名称不一致,只要类型匹配上了还是能自动装配
        <property name="cityCode" value="100031"/>
        <property name="cityName" value="北京"/>
</bean>

当一个bean节点带有 autowire byType的属性时

  1. 装配时将查找其类中所有的set方法名,如果配置中有则使用配置中的
  2. 如果配置中没有则去spring容器中寻找是否有此属性类型的对象
  3. 如果有且唯一,就取出注入;如果没有,返回null;如果有且不唯一抛异常

我们测试下实现:

public class PersonTest {
    @Test
    public void PersonTest(){
        //解析beans.xml文件,生成管理相应的bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Perosn person = (Perosn) context.getBean("person");
        person.show();
    }
}

可以发现地址还是能被正常打印出来:

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常,例如如果配置为:

<bean id="address" class="com.example.Spring.model.Address" >
        <property name="cityCode" value="100031"/>
        <property name="cityName" value="北京"/>
    </bean>
    <bean id="address2" class="com.example.Spring.model.Address" >
        <property name="cityCode" value="100031"/>
        <property name="cityName" value="北京"/>
    </bean>

就会报如下的异常:

警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'person' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'address'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.Spring.model.Address' available: expected single matching bean but found 2: address,address2
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'person' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'address'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.Spring.model.Address' available: expected single matching bean but found 2: address,address2
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1516)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1399)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
  at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
  at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.Spring.model.Address' available: expected single matching bean but found 2: address,address2
  at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1358)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1501)

总结一下

本篇Blog对自动装配有了一个大致了解,自动装配的本质实际上就是约定大于配置,通过命名一致的约定或者类型独一无二的约定来减少配置的量,这其实已经有了一些Spring Boot雏形的感觉了,但是需要注意的是,再复杂的场景下,清晰全面的配置还是很有必要的。配置和约定只是两种实现方式,具体选择哪种其实看业务实现。

相关文章
|
8天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
31 2
|
26天前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
58 2
|
28天前
|
SQL Java 数据库
Spring Boot与Flyway:数据库版本控制的自动化实践
【10月更文挑战第19天】 在软件开发中,数据库的版本控制是一个至关重要的环节,它确保了数据库结构的一致性和项目的顺利迭代。Spring Boot结合Flyway提供了一种自动化的数据库版本控制解决方案,极大地简化了数据库迁移管理。本文将详细介绍如何使用Spring Boot和Flyway实现数据库版本的自动化控制。
26 2
|
2月前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
45 2
|
3月前
|
XML Java 数据格式
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
这篇文章是Spring5框架的入门教程,详细讲解了IOC容器中Bean的自动装配机制,包括手动装配、`byName`和`byType`两种自动装配方式,并通过XML配置文件和Java代码示例展示了如何在Spring中实现自动装配。
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
|
3月前
|
Java 开发工具 Spring
Spring的Factories机制介绍
Spring的Factories机制介绍
71 1
|
3月前
|
Java 开发工具 Spring
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
|
3月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
|
3月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决