我们一般学习某个知识,一定会现有个较为复杂的配置让你理解其中的关系,这个配置清晰规整,但是可能会需要大量的配置,这个时候就会有约定大于配置的理论实现了,通过我们约定好的一致的名称,我可以少写很多对应关系,例如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存在多个构造器的时候,构造方式太复杂了,所以这里不做详细介绍,我们主要学习下byName和byType的实现方式,当然事实上我们只是通过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的属性时
- 装配时将查找其类中所有的set方法名,如果配置中有则使用配置中的
- 如果配置中没有则去spring容器中寻找是否有此字符串名称id的对象
- 如果有,就取出注入;如果没有,返回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的属性时
- 装配时将查找其类中所有的set方法名,如果配置中有则使用配置中的
- 如果配置中没有则去spring容器中寻找是否有此属性类型的对象
- 如果有且唯一,就取出注入;如果没有,返回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雏形的感觉了,但是需要注意的是,再复杂的场景下,清晰全面的配置还是很有必要的。配置和约定只是两种实现方式,具体选择哪种其实看业务实现。