【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雏形的感觉了,但是需要注意的是,再复杂的场景下,清晰全面的配置还是很有必要的。配置和约定只是两种实现方式,具体选择哪种其实看业务实现。

相关文章
|
17天前
|
Java 开发者 Spring
深入理解 Spring Boot 中的 @EnableAutoConfiguration 注解:概念与实践
【4月更文挑战第21天】在 Spring Boot 项目中,@EnableAutoConfiguration 注解是实现自动配置的核心,它可以根据项目的依赖和配置,自动地配置 Spring 应用程序中的 Bean
39 3
|
17天前
|
Java API 网络架构
深入理解 Spring Boot 中的 @RestController 注解:概念与实践
【4月更文挑战第20天】在现代Web开发中,创建RESTful服务已成为常态。Spring Boot通过提供@RestController注解,极大简化了REST API的开发过程。本篇博客旨在详细介绍@RestController的概念、优势以及在Spring Boot项目中的具体应用方法。
43 8
|
17天前
|
Java 测试技术 开发者
Spring IoC容器通过依赖注入机制实现控制反转
【4月更文挑战第30天】Spring IoC容器通过依赖注入机制实现控制反转
22 0
|
6天前
|
Java API 网络架构
利用Java Spring Boot构建微服务架构的实践探索
随着业务复杂性的增长和互联网技术的飞速发展,微服务架构已成为现代软件开发中不可或缺的一部分。本文旨在探讨如何利用Java Spring Boot框架构建微服务架构,包括微服务的定义、优势,以及通过实际案例展示如何设计、开发和部署微服务。我们将关注服务拆分、服务间通信、数据一致性、服务治理等核心问题,并探讨如何结合Spring Cloud生态中的组件来实现高效、可靠的微服务架构。
|
8天前
|
Java Spring 容器
Spring依赖注入方式,自动装配及自动装配特征
Spring依赖注入方式,自动装配及自动装配特征
9 1
|
17天前
|
NoSQL Java MongoDB
【MongoDB 专栏】MongoDB 与 Spring Boot 的集成实践
【5月更文挑战第11天】本文介绍了如何将非关系型数据库MongoDB与Spring Boot框架集成,以实现高效灵活的数据管理。Spring Boot简化了Spring应用的构建和部署,MongoDB则以其对灵活数据结构的处理能力受到青睐。集成步骤包括:添加MongoDB依赖、配置连接信息、创建数据访问对象(DAO)以及进行数据操作。通过这种方式,开发者可以充分利用两者优势,应对各种数据需求。在实际应用中,结合微服务架构等技术,可以构建高性能、可扩展的系统。掌握MongoDB与Spring Boot集成对于提升开发效率和项目质量至关重要,未来有望在更多领域得到广泛应用。
【MongoDB 专栏】MongoDB 与 Spring Boot 的集成实践
|
17天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
18 1
|
17天前
|
缓存 NoSQL Java
17:缓存机制-Java Spring
17:缓存机制-Java Spring
43 5
|
17天前
|
消息中间件 安全 Java
探索|Spring并行初始化加速的思路和实践
作者通过看过的两篇文章发现实现Spring初始化加速的思路和方案有很多类似之处,通过本文记录一下当时的思考和实践。
|
17天前
|
安全 Java 测试技术
利用Java反射机制提高Spring Boot的代码质量:概念与实战
【4月更文挑战第29天】Java反射机制提供了一种强大的方法来在运行时检查或修改类和对象的行为。在Spring Boot应用中,合理利用反射可以提高代码的灵活性和可维护性。本篇博客将探讨Java反射的核心概念,并展示如何通过反射提高Spring Boot项目的代码质量。
34 0