💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 装配Bean ## Spring 配置的可选方案 Spring 提供了三种主要的装配机制 - 在XML中进行显示配置 - 在java中进行显示配置 - 隐式的bean发现机制和自动装配 **配置的选择** Spring配置可以进行搭配使用,但是我们建议尽可能的使用自动配置的机制,显示配置越少越好,当你必须要显示配置bean的时候,我们推荐使用类型安全并且比XML更加强大的JavaConfig. 最后当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML. ## 自动化装配Bean Spring 从两个角度来实现自动化装配: - 组件扫描:Spring会自动发现应用上下文中所创建的bean. - 自动装配:Spring自动满足bean之间的依赖。 组件扫描和自动装配组合在一起就能发挥出强大的威力,他们能够将你的显示配置降低到最少。 ### 创建可被发现的bean 使用注解标明`@Component`组件类。这个注解会告知Spring要为这个类创建bean.没有必要显示配置bean,因为这个类使用了`@Component`注解 使用注解`@ComponentScan`启用组件扫描 如果没有其他配置的话、`@ComponentScan`默认扫描与配置类相同的包。 ### 为组件扫描的bean命名 Spring 应用上下文中所有的bean都给定一个ID.如果我们没有明确的为bean设置ID,那么Spring会根据类名为其指定一个ID.具体来讲就是将类名第一个字母变为小写。 - 如何为bean设置不同的ID ```java @Component("myTest") public class Test implements TestI{ .... } ``` - 如何设置组件扫描不同的包 - 通过value属性指明包的名称 ```java @ComponentScan("com.test") public class TestConfig {} ``` - 通过basePackages 属性进行配置 ```java /** 第一种设置方法 */ @ComponentScan(basePackages="com.test") public class TestConfig {} /** 第二种设置方法,设置扫描多个包 */ @ComponentScan(basePackages={"com.test","com.test2"}) public class TestConfig {} ``` 上面的列子中,所设置的基础包都是以String类型表示的,这种方法的缺点就是类型不安全,如果重构代码的话,那么所指定的基础包就可能会出错误。 ```java /** 第三种设置方法,将其指定为包中所包含的类或接口 */ @ComponentScan(basePackageClasses={Test1.class,Test2.class}) public class TestConfig {} ``` 为basePackageClasses属性所设置的数组包含了类,这些类所在的包将会作为组件扫描的基础包。 ### 通过为bean添加注解实现自动装配 简单来说,自动装配就是让Spring自动满足bean依赖的一种方法。在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean.为了声明要进行自动装配,我们可以借助Spring的`@Autowired`注解 不管是构造器,Setter方法还是其他的方法,Spring 都会尝试满足方法参数所声明的依赖。假如有且只有一个bean匹配,那么这个bean会被装配进来。 如果没有匹配的bean,那么应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现。我们可以把`@Audowired`的required属性设置为false: ```java @Audowired(required=false) public Test(Tests tests){ this.tests=tests; } ``` required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。需要注意,如果代码没有null检查的话,这个处于未装配状态的属性有可能出现空指针异常。 ## 通过Java配置bean Spring自动化配置是最为推荐的方式,但是有时候自动化配置的方案行不通,因此需要明确配置Spring. 在这种情况下,必须采用显示的装配方式 XML和Java配置。接下来我们先介绍下Java配置 **注意JavaConfig与其他Java代码的区别** JavaConfig是配置代码,这意味着它不应该包含任何业务逻辑,JavaConfig 也不应该侵入到业务逻辑代码之中。 ***尽管不是必须,通常会将JavaConfig放在单独的包中,使它和其他应用程序逻辑分离开来*** ### 创建配置类 创建JavaConfig类的关键在于为其添加`@Configuration`注解,`@Configuration`注解表明这个类是一个配置类,该应该包含Spring应用上下文中如何创建bean的细节。 ```java @Configuration public class TestConfig {} ``` - 如何声明一个简单bean ```java @Configuration public class TestConfig { @Bean public Test test(){ return new Test1(); } } ``` **注意** 带有`@Bean`注解的的方法可以采用任何必要的java功能产生bean实例 ## 通过XML装配bean XML配置是spring刚刚出现时的解决方案,现在它已经不是我们唯一的可选方案,而且Spring现在有强大的自动化配置和基于Java的配置,XML不应该再是我们的第一选择了,但是鉴于存在很多基于XML的Spring配置,理解如何在Spring中使用XML还是很重要的。理解XML配置用来帮助我们理解维护已有的XML配置,在完成新的Spring工作时,希望你能够使用自动化配置和JavaConfig. > 如何创建XML配置规范-如下 ```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" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans> ``` - 声明一个简单bean ```XML <bean class="com.yc.Test" /> ``` 这里声明了一个简单的bean,创建这个bean的类通过class属性来指定,并且要使用全限定的类名。 **如果没有明确给的ID**,这个bean将会根据全限定类名来进行命名。本例中bean的id将会是com.yc.Test#0, '#0'是一个计数的形式,用来区分相同类型的其他bean,如果声明了一个Test,并且没有明确的进行标识。那么它自动得到的ID是com.yc.Test#1。 > 减少繁琐 - 为了减少XML中繁琐的配置,只对那些需要按名字引用的bean通过id属性进行明确的命名 > XML的bean声明与JavaConfi对比 - 显然XML配置并没有JavaConfig那样强,主要表现如下 1. 在JavaConfig配置中,你可以通过任何可以想象到的方法创建bean实例 2. Spring的XML配置并不能从编译期的类型检查中受益,即便它所引用的是实际的类型。(如果重命名了类,会发生什么) ### 如何借助构造器注入初始化bean 在SpringXml配置中,声明DI时,会有多种可选配置方案和风格,具体到构造器注入有两种基本的配置方案: 1. `<constructor-arg>`元素 2. 使用Spring3.0所引入的c-命名空间 以上两种的区别很大程度是是否冗长繁琐。c-命名空间为了解决`<constructor-arg>`元素冗长难懂而设置,但是有些事情`<constructor-arg>`元素可以做到,c-命名空间却无法实现。 > 如何为构造器注入bean引用? - xml使用方法 ```java public class Test{ private CompatDisc compatDisc; public Test(CompatDisc compatDisc){ this.compatDisc=compatDisc; } } ``` ```XML <bean id="test" class="com.yc.test"> <constructor-arg ref="compactDisc"> </bean> ``` - c-命名空间使用方法 ```xml <bean id="test" class="com.yc.test" c:compatDisc-ref="compatDisc"> ``` **c-命名空间使用说明** 在这里,我们使用c-命名空间声明构造器参数,它作为<bean> 元素的一个属性,不过属性的名字有点诡异。图 c-命名空间描述了属性名如何组合而成。 ![c-命名空间属性名如何组合而成](image/spring实战/c-命名空间.png) - c-命名空间 诡异的用法 ```xml <bean id="test" class="com.yc.test" c:_0-ref="compatDisc"> ``` 这个c-命名空间属性看起来有点怪异,我将参数名替换成了"0",也就是参数的索引。因为在XML中不允许作为属性的第一个字符,因此必须要添加一个下划线作为前缀。 - 使用参数索引的优点 使用索引来识别构造器参数比使用名字更好一些:即使我们移除掉了调试标志,参数却会依然保持相同的顺序,如果有多个构造器参数的话,这当然是很有用处的。 只有一个构造器参数时,我们还有另外一个方案------根本不用去标示参数 ```xml <bean id="test" class="com.yc.test" c:_-ref="compatDisc"> ``` > 如何将字面量注入到构造器? ```JAVA public class TestTwo { private String title; private String name; public TestTwo(String title,String name){ this.title=title; this.name=name; } } ``` 1. `<constructor-arg>`元素 进行参数注入 ```xml <bean id="testTwo" class="com.yc.test.TestTwo"> <constructor-arg value="Title 标题"/> <constructor-arg value="name 姓名"/> </bean> ``` 2. 使用c-命名空间 ```xml <bean id="testTwo" class="com.yc.test.TestTwo" c:_tilte="Title 标题" c:_name="name 姓名" /> ``` 或者 ```xml <bean id="testTwo" class="com.yc.test.TestTwo" c:_0="Title 标题" c:_1="name 姓名" /> ``` > 如何装配集合 ? 如下数据: ```JAVA public class TestTwo { private String title; private String name; private List<String> lists; public TestTwo(String title,String name,List<String> lists){ this.title=title; this.name=name; this.lists=lists; } public void dlay(){ System.out.println(lists.toString); } } ``` 如上我们更新了我们TestTwo,变更后我们必须要提供一个集合列表,最简单的方法是为设置集合列表为null。因为他是构造器参数,所以必须要声明它。不过我们可以用如下方式传递null給它。 ```XML <bean id="testTwo" class="com.yc.test.TestTwo"> <constructor-arg value="Title 标题"/> <constructor-arg value="name 姓名"/> <constructor-arg><null/><constructor-arg/> </bean> ``` <null/> 元素所做的事情与我们期望是一样的:将null传递给构造器。但这并不是解决问题的好办法。虽然注入期也能正常执行。但是调用dlay()方法会遇到,空指针异常。更好的解决方法是提供一个集合列表,要达到这一点,我们有多个可选方案。 1. 首先我们可以使用<list>元素来声明一个列表 ```XML <bean id="testTwo" class="com.yc.test.TestTwo"> <constructor-arg value="Title 标题"/> <constructor-arg value="name 姓名"/> <constructor-arg> <list> <value>Test1<value/> <value>Test2<value/> <value>Test3<value/> <list/> <constructor-arg/> </bean> ``` > 如果集合泛型为自定义对象怎么办? ```JAVA public TestTwo(String title,String name,List<Test1> lists){...} ``` ```XML <bean id="testTwo" class="com.yc.test.TestTwo"> <constructor-arg value="Title 标题"/> <constructor-arg value="name 姓名"/> <constructor-arg> <list> <ref bean="test1"/> <ref bean="test2"/> <ref bean="test3"/> <list/> </constructor-arg> </bean> ``` > 当构造参数时List时,使用<list>是合理的,尽管如此,我们可以按照同样的方法使用<set>元素 ```XML <bean id="testTwo" class="com.yc.test.TestTwo"> <constructor-arg value="Title 标题"/> <constructor-arg value="name 姓名"/> <constructor-arg> <set> <ref bean="test1"/> <ref bean="test2"/> <ref bean="test3"/> <set/> </constructor-arg> </bean> ``` **总结** 在装配结合方面,<constructor-arg> 比c-命名空间更有优势。目前c-命名空间无法实现装配集合的功能 ### 如何设置属性 到目前为止,我们的类完成是通过构造器注入,没有使用属性的setter方法,接下来我们看下SpringXml是如何实现属性注入的。 ```JAVA public class TestThree { private TestTwo testTwo; public void setTestTwo(TestTwo testTwo){ this.testTwo=testTwo; } } ``` 现在TestThree 没有任何构造器(除了隐含的),他没有强依赖,因此我们可以采用如下方式声明Spring bean ```XML <bean id="testThree" class="com.yc.test.TestThree"> <property name="testTwo" ref="testTwo"/> </bean> ``` <property>元素为属性的Setter方法所提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的。在本列中,它引用ID为testTwo的bean(通过ref属性),并将其注入到属性testTwo中(通过setTestTwo()方法) - p-命名空间 ```xml <bean id="testThree" class="com.yc.test.TestThree" p:testTwo-ref="testTwo" /> ``` p-命名空间中属性遵循的命名与c-命名空间中属性类似。图p-命名空间属性名组成 阐述了它 ![p-命名空间属性名如何组合而成](image/spring实战/p-命名空间.png) > 如何将字面量注入到属性中 属性也可以注入字面量,这和构造器类似,下面请看例子 ```java public class TestThree { private TestTwo testTwo; private String title; private String name; private List<String> lists; public void setTestTwo(TestTwo testTwo){ this.testTwo=testTwo; } public void setTitle(String title){ this.title=title; } public void setName(String name){ this.name=name; } public void setLists(String lists){ this.lists=lists; } } ``` ```xml <bean id="testThree" class="com.yc.test.TestThree"> <property name="testTwo" ref="testTwo"></property> <property name="title" value="Title 标题"></property> <property name="name" value="Name 姓名"></property> <property name="lists"> <list> <value>测试</value> <value>测试</value> <value>测试</value> </list> </property> </bean> ``` ### util-命名空间及其模式 util-命名空间所提供的功能之一就是<util:list>元素,它会创建一个列表的bean,借助<util:list>,我们可以将集合列表转移到testThree bean之外,如下所示 ```xml <util:list id="lists"> <value>测试</value> <value>测试</value> <value>测试</value> </util:list> ``` 现在我们能够像使用其他bean那样,将集合列表bean注入到testThree bean的lists属性中: ```xml <bean class="soundsystem.TestTwo" p:title="Title 标题" p:lists-ref="lists" p:testTwo-ref="testTwo" p:name="Nmae 姓名" /> ``` - Spring util 命名空间中的元素 ![Spring util 命名空间中的元素](image/spring实战/spring-util.png) ## 导入和混合配置 在spring应用中,我们可能同时使用自动化和显示配置,即便我们更喜欢通过JavaConfig实现显示配置,但有时候XML却是最佳方案。 > 如何在JavaConfig中引用XML配置 答案是`@ImportResoure`注解 ```java @Configuration @Import(CDPlayerConfig.class) @ImportResource("classpath:cd-config.xml") public class SystemConfig{ } ``` > 如何在XML配置中引用JavaConfig 在JavaConfig 配置中,我们已经展现了如何使用`@Import`和`@ImportResource`来拆分JavaConfig类。 在XML中,我们可以使用<import>元素来拆分XML配置。对于JavaConfig类导入XML配置,我们只需要把JavaConfig配置声明bean就行了。