前面已经介绍了 Spring IoC 的理念和设计,这一篇文章将介绍的是如何将本身开发的 Bean 装配到 Spring IoC 容器中。html
大部分场景下,咱们都会使用 ApplicationContext 的具体实现类,由于对应的 Spring IoC 容器功能相对强大。java
而在 Spring 中提供了 3 种方法进行配置:git
在现实的工做中,这 3 种方式都会被用到,而且在学习和工做之中经常混合使用,因此这里给出一些关于这 3 种优先级的建议:github
1.最优先:经过隐式 Bean 的发现机制和自动装配的原则。
基于约定因为配置的原则,这种方式应该是最优先的web
2.其次:Java 接口和类中配置实现配置
在没有办法使用自动装配原则的状况下应该优先考虑此类方法正则表达式
3.最后:XML 方式配置
在上述方法都没法使用的状况下,那么也只能选择 XML 配置的方式。spring
使用 XML 装配 Bean 须要定义对应的 XML,这里须要引入对应的 XML 模式(XSD)文件,这些文件会定义配置 Spring Bean 的一些元素,当咱们在 IDEA 中建立 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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
这就只是一个格式文件,引入了一个 beans 的定义,引入了 xsd 文件,它是一个根元素,这样它所定义的元素将能够定义对应的 Spring Bean微信
先来一个最简单的装配:
<bean id="c" class="pojo.Category"> <property name="name" value="测试" /> </bean>
简单解释一下:
id
属性是 Spring 能找到当前 Bean 的一个依赖的编号,遵照 XML 语法的 ID 惟一性约束。必须以字母开头,可使用字母、数字、连字符、下划线、句号、冒号,不能以 /
开头。id
属性不是一个必需的属性,name
属性也能够定义 bean 元素的名称,能以逗号或空格隔开起多个别名,而且能够使用不少的特殊字符,好比在 Spring 和 Spring MVC 的整合中,就得使用 name
属性来定义 bean 的名称,而且使用 /
开头。id
属性也能够是 String 类型了,也就是说 id
属性也可使用 /
开头,而 bean 元素的 id 的惟一性由容器负责检查。id
和 name
属性都没有声明的话,那么 Spring 将会采用 “全限定名#{number}” 的格式生成编号。 例如这里,若是没有声明 “id="c"
” 的话,那么 Spring 为其生成的编号就是 “pojo.Category#0
”,当它第二次声明没有 id
属性的 Bean 时,编号就是 “pojo.Category#1
”,以此类推。class
属性显然就是一个类的全限定名property
元素是定义类的属性,其中的 name
属性定义的是属性的名称,而 value
是它的值。这样的定义很简单,可是有时候须要注入一些自定义的类,好比以前饮品店的例子,JuickMaker 须要用户提供原料信息才能完成 juice 的制做:
<!-- 配置 srouce 原料 --> <bean name="source" class="pojo.Source"> <property name="fruit" value="橙子"/> <property name="sugar" value="多糖"/> <property name="size" value="超大杯"/> </bean> <bean name="juickMaker" class="pojo.JuiceMaker"> <!-- 注入上面配置的id为srouce的Srouce对象 --> <property name="source" ref="source"/> </bean>
这里先定义了一个 name
为 source 的 Bean,而后再制造器中经过 ref
属性去引用对应的 Bean,而 source 正是以前定义的 Bean 的 name
,这样就能够相互引用了。
ref
属性有些时候咱们须要装配一些复杂的东西,好比 Set、Map、List、Array 和 Properties 等,为此咱们在 Packge【pojo】下新建一个 ComplexAssembly 类:
package pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class ComplexAssembly { private Long id; private List<String> list; private Map<String, String> map; private Properties properties; private Set<String> set; private String[] array; /* setter and getter */ }
这个 Bean 没有任何的实际意义,知识为了介绍如何装配这些经常使用的集合类:
<bean id="complexAssembly" class="pojo.ComplexAssembly"> <!-- 装配Long类型的id --> <property name="id" value="1"/> <!-- 装配List类型的list --> <property name="list"> <list> <value>value-list-1</value> <value>value-list-2</value> <value>value-list-3</value> </list> </property> <!-- 装配Map类型的map --> <property name="map"> <map> <entry key="key1" value="value-key-1"/> <entry key="key2" value="value-key-2"/> <entry key="key3" value="value-key-2"/> </map> </property> <!-- 装配Properties类型的properties --> <property name="properties"> <props> <prop key="prop1">value-prop-1</prop> <prop key="prop2">value-prop-2</prop> <prop key="prop3">value-prop-3</prop> </props> </property> <!-- 装配Set类型的set --> <property name="set"> <set> <value>value-set-1</value> <value>value-set-2</value> <value>value-set-3</value> </set> </property> <!-- 装配String[]类型的array --> <property name="array"> <array> <value>value-array-1</value> <value>value-array-2</value> <value>value-array-3</value> </array> </property> </bean>
<list>
元素进行装配,而后经过多个 <value>
元素设值<map>
元素进行装配,而后经过多个 <entry>
元素设值,只是 entry
包含一个键值对(key-value)的设置<properties>
元素进行装配,经过多个 <property>
元素设值,只是 properties
元素有一个必填属性 key
,而后能够设置值<set>
元素进行装配,而后经过多个 <value>
元素设值<array>
设置值,而后经过多个 <value>
元素设值。上面看到了对简单 String 类型的各个集合的装载,可是有些时候可能须要更为复杂的装载,好比一个 List 能够是一个系列类的对象,为此须要定义注入的相关信息,其实跟上面的配置没什么两样,只不过加入了 ref
这一个属性而已:
<list>
元素定义注入,使用多个 <ref>
元素的 Bean 属性去引用以前定义好的 Bean<property name="list"> <list> <ref bean="bean1"/> <ref bean="bean2"/> </list> </property>
<map>
元素定义注入,使用多个 <entry>
元素的 key-ref
属性去引用以前定义好的 Bean 做为键,而用 value-ref
属性引用以前定义好的 Bean 做为值<property name="map"> <map> <entry key-ref="keyBean" value-ref="valueBean"/> </map> </property>
<set>
元素定义注入,使用多个 <ref>
元素的 bean
去引用以前定义好的 Bean<property name="set"> <set> <ref bean="bean"/> </set> </property>
除了上述的配置以外, Spring 还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和 XML 模式(XSD)文件。
c-命名空间是在 Spring 3.0 中引入的,它是在 XML 中更为简洁地描述构造器参数的方式,要使用它的话,必需要在 XML 的顶部声明其模式:
如今假设咱们如今有这么一个类:
package pojo; public class Student { int id; String name; public Student(int id, String name) { this.id = id; this.name = name; } // setter and getter }
在 c-命名空间和模式声明以后,咱们就可使用它来声明构造器参数了:
<!-- 引入 c-命名空间以前 --> <bean name="student1" class="pojo.Student"> <constructor-arg name="id" value="1" /> <constructor-arg name="name" value="学生1"/> </bean> <!-- 引入 c-命名空间以后 --> <bean name="student2" class="pojo.Student" c:id="2" c:name="学生2"/>
c-命名空间属性名以 “c:
” 开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此以后若是须要注入对象的话则要跟上 -ref
(如c:card-ref="idCard1"
,则对 card 这个构造器参数注入以前配置的名为 idCard1 的 bean)
很显然,使用 c-命名空间属性要比使用 <constructor-arg>
元素精简,而且会直接引用构造器之中参数的名称,这有利于咱们使用的安全性。
咱们有另一种替代方式:
<bean name="student2" class="pojo.Student" c:_0="3" c:_1="学生3"/>
咱们将参数的名称替换成了 “0” 和 “1” ,也就是参数的索引。由于在 XML 中不容许数字做为属性的第一个字符,所以必需要添加一个下划线来做为前缀。
c-命名空间经过构造器注入的方式来配置 bean,p-命名空间则是用setter的注入方式来配置 bean ,一样的,咱们须要引入声明:
而后咱们就能够经过 p-命名空间来设置属性:
<!-- 引入p-命名空间以前 --> <bean name="student1" class="pojo.Student"> <property name="id" value="1" /> <property name="name" value="学生1"/> </bean> <!-- 引入p-命名空间以后 --> <bean name="student2" class="pojo.Student" p:id="2" p:name="学生2"/>
咱们须要先删掉 Student 类中的构造函数,否则 XML 约束会提示咱们配置 <constructor-arg>
元素。
一样的,若是属性须要注入其余 Bean 的话也能够在后面跟上 -ref
:
<bean name="student2" class="pojo.Student" p:id="2" p:name="学生2" p:cdCard-ref="cdCard1"/>
工具类的命名空间,能够简化集合类元素的配置,一样的咱们须要引入其声明(无需担忧怎么声明的问题,IDEA会有很友好的提示):
咱们来看看引入先后的变化:
<!-- 引入util-命名空间以前 --> <property name="list"> <list> <ref bean="bean1"/> <ref bean="bean2"/> </list> </property> <!-- 引入util-命名空间以后 --> <util:list id="list"> <ref bean="bean1"/> <ref bean="bean2"/> </util:list>
<util:list>
只是 util-命名空间中的多个元素之一,下表提供了 util-命名空间提供的全部元素:
元素 | 描述 |
---|---|
<util:constant> |
引用某个类型的 public static 域,并将其暴露为 bean |
<util:list> |
建立一个 java.util.List 类型的 bean,其中包含值或引用 |
<util:map> |
建立一个 java.util.map 类型的 bean,其中包含值或引用 |
<util:properties> |
建立一个 java.util.Properties 类型的 bean |
<util:property-path> |
引用一个 bean 的属性(或内嵌属性),并将其暴露为 bean |
<util:set> |
建立一个 java.util.Set 类型的 bean,其中包含值或引用 |
在实际开发中,随着应用程序规模的增长,系统中 <bean>
元素配置的数量也会大大增长,致使 applicationContext.xml 配置文件变得很是臃肿难以维护。
<import>
元素引入其余配置文件1.在【src】文件下新建一个 bean.xml 文件,写好基础的约束,把 applicationContext.xml 文件中配置的 <bean>
元素复制进去
2.在 applicationContext.xml 文件中写入:
<import resource="bean.xml" />
3.运行测试代码,仍然能正确获取到 bean:
上面,咱们已经了解了如何使用 XML 的方式去装配 Bean,可是更多的时候已经再也不推荐使用 XML 的方式去装配 Bean,更多的时候回考虑使用注解(annotation) 的方式去装配 Bean。
在 Spring 中,它提供了两种方式来让 Spring IoC 容器发现 bean:
咱们把以前建立的 Student 类改一下:
package pojo; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component(value = "student1") public class Student { @Value("1") int id; @Value("student_name_1") String name; // getter and setter }
解释一下:
value
属性表明这个类在 Spring 中的 id
,这就至关于在 XML 中定义的 Bean 的 id:<bean id="student1" class="pojo.Student" />
,也能够简写成 @Component("student1")
,甚至直接写成 @Component
,对于不写的,Spring IoC 容器就默认以类名来命名做为 id
,只不过首字母小写,配置到容器中。value
属性是同样的。这样咱们就声明好了咱们要建立的一个 Bean,就像在 XML 中写下了这样一句话:
<bean name="student1" class="pojo.Student"> <property name="id" value="1" /> <property name="name" value="student_name_1"/> </bean>
可是如今咱们声明了这个类,并不能进行任何的测试,由于 Spring IoC 并不知道这个 Bean 的存在,这个时候咱们可使用一个 StudentConfig 类去告诉 Spring IoC :
package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class StudentConfig { }
这个类十分简单,没有任何逻辑,可是须要说明两点:
@Component
注解的 POJO。这样一来,咱们就能够经过 Spring 定义好的 Spring IoC 容器的实现类——AnnotationConfigApplicationContext 去生成 IoC 容器了:
ApplicationContext context = new AnnotationConfigApplicationContext(StudentConfig.class); Student student = (Student) context.getBean("student1", Student.class); student.printInformation();
这里能够看到使用了 AnnotationConfigApplicationContext 类去初始化 Spring IoC 容器,它的配置项是 StudentConfig 类,这样 Spring IoC 就会根据注解的配置去解析对应的资源,来生成 IoC 容器了。
@ComponentScan
注解,它只是扫描所在包的 Java 类,可是更多的时候咱们但愿的是能够扫描咱们指定的类@Value
注解并不能注入对象@Component
注解存在着两个配置项:
咱们来试着重构以前写的 StudentConfig 类来验证上面两个配置项:
package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages = "pojo") public class StudentConfig { } // —————————————————— 【 宇宙超级无敌分割线】—————————————————— package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackageClasses = pojo.Student.class) public class StudentConfig { }
验证都能经过,bingo!
上面提到的两个弊端之一就是没有办法注入对象,经过自动装配咱们将解决这个问题。
所谓自动装配技术是一种由 Spring 本身发现对应的 Bean,自动完成装配工做的方式,它会应用到一个十分经常使用的注解 @Autowired
上,这个时候 Spring 会根据类型去寻找定义的 Bean 而后将其注入,听起来很神奇,让咱们实际来看一看:
1.先在 Package【service】下建立一个 StudentService 接口:
package service; public interface StudentService { public void printStudentInfo(); }
使用接口是 Spring 推荐的方式,这样能够更为灵活,能够将定义和实现分离
2.为上面的接口建立一个 StudentServiceImp 实现类:
package service; import org.springframework.beans.factory.annotation.Autowired; import pojo.Student; @Component("studentService") public class StudentServiceImp implements StudentService { @Autowired private Student student = null; // getter and setter public void printStudentInfo() { System.out.println("学生的 id 为:" + student.getName()); System.out.println("学生的 name 为:" + student.getName()); } }
该实现类实现了接口的 printStudentInfo() 方法,打印出成员对象 student 的相关信息,这里的 @Autowired
注解,表示在 Spring IoC 定位全部的 Bean 后,这个字段须要按类型注入,这样 IoC 容器就会寻找资源,而后将其注入。
3.编写测试类:
// 第一步:修改 StudentConfig 类,告诉 Spring IoC 在哪里去扫描它: package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages = {"pojo", "service"}) public class StudentConfig { } // 或者也能够在 XML 文件中声明去哪里作扫描 <context:component-scan base-package="pojo" /> <context:component-scan base-package="service" /> // 第二步:编写测试类: package test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import pojo.StudentConfig; import service.StudentService; import service.StudentServiceImp; public class TestSpring { public static void main(String[] args) { // 经过注解的方式初始化 Spring IoC 容器 ApplicationContext context = new AnnotationConfigApplicationContext(StudentConfig.class); StudentService studentService = context.getBean("studentService", StudentServiceImp.class); studentService.printStudentInfo(); } }
运行代码:
@Autowired
注解表示在 Spring IoC 定位全部的 Bean 后,再根据类型寻找资源,而后将其注入。required
来改变,好比 @Autowired(required = false)
@Autowired
注解不只仅能配置在属性之上,还容许方法配置,常见的 Bean 的 setter 方法也可使用它来完成注入,总之一切须要 Spring IoC 去寻找 Bean 资源的地方均可以用到,例如:
/* 包名和import */ public class JuiceMaker { ...... @Autowired public void setSource(Source source) { this.source = source; } }
在大部分的配置中都推荐使用这样的自动注入来完成,这是 Spring IoC 帮助咱们自动装配完成的,这样使得配置大幅度减小,知足约定优于配置的原则,加强程序的健壮性。
在上面的例子中咱们使用 @Autowired
注解来自动注入一个 Source 类型的 Bean 资源,但若是咱们如今有两个 Srouce 类型的资源,Spring IoC 就会不知所措,不知道究竟该引入哪个 Bean:
<bean name="source1" class="pojo.Source"> <property name="fruit" value="橙子"/> <property name="sugar" value="多糖"/> <property name="size" value="超大杯"/> </bean> <bean name="source2" class="pojo.Source"> <property name="fruit" value="橙子"/> <property name="sugar" value="少糖"/> <property name="size" value="小杯"/> </bean>
咱们能够会想到 Spring IoC 最底层的容器接口——BeanFactory 的定义,它存在一个按照类型获取 Bean 的方法,显然经过 Source.class 做为参数没法判断使用哪一个类实例进行返回,这就是自动装配的歧义性。
为了消除歧义性,Spring 提供了两个注解:
/* 包名和import */ public class JuiceMaker { ...... @Autowired @Qualifier("source1") public void setSource(Source source) { this.source = source; } }
@Component
注解来装配 Bean ,而且只能注解在类上,当你须要引用第三方包的(jar 文件),并且每每并无这些包的源码,这时候将没法为这些包的类加入 @Component
注解,让它们变成开发环境中的 Bean 资源。@Component
注解,但这样很 low@Bean
注解,注解到方法之上,使其成为 Spring 中返回对象为 Spring 的 Bean 资源。咱们在 Package【pojo】 下新建一个用来测试 @Bean
注解的类:
package pojo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BeanTester { @Bean(name = "testBean") public String test() { String str = "测试@Bean注解"; return str; } }
@Configuration
注解至关于 XML 文件的根元素,必需要,有了才能解析其中的 @Bean
注解而后咱们在测试类中编写代码,从 Spring IoC 容器中获取到这个 Bean :
// 在 pojo 包下扫描 ApplicationContext context = new AnnotationConfigApplicationContext("pojo"); // 由于这里获取到的 Bean 就是 String 类型因此直接输出 System.out.println(context.getBean("testBean"));
@Bean
的配置项中包含 4 个配置项:
使用 @Bean
注解的好处就是可以动态获取一个 Bean 对象,可以根据环境不一样获得不一样的 Bean 对象。或者说将 Spring 和其余组件分离(其余组件不依赖 Spring,可是又想 Spring 管理生成的 Bean)
在默认的状况下,Spring IoC 容器只会对一个 Bean 建立一个实例,但有时候,咱们但愿可以经过 Spring IoC 容器获取多个实例,咱们能够经过 @Scope
注解或者 <bean>
元素中的 scope
属性来设置,例如:
// XML 中设置做用域 <bean id="" class="" scope="prototype" /> // 使用注解设置做用域 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Spring 提供了 5 种做用域,它会根据状况来决定是否生成新的对象:
做用域类别 | 描述 |
---|---|
singleton(单例) | 在Spring IoC容器中仅存在一个Bean实例 (默认的scope) |
prototype(多例) | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时 ,至关于执行new XxxBean():不会在容器启动时建立对象 |
request(请求) | 用于web开发,将Bean放入request范围 ,request.setAttribute("xxx") , 在同一个request 得到同一个Bean |
session(会话) | 用于web开发,将Bean 放入Session范围,在同一个Session 得到同一个Bean |
globalSession(全局会话) | 通常用于 Porlet 应用环境 , 分布式系统存在全局 session 概念(单点登陆),若是不是 porlet 环境,globalSession 等同于 Session |
在开发中主要使用 scope="singleton"
、scope="prototype"
,对于MVC中的Action使用prototype类型,其余使用singleton,Spring容器会管理 Action 对象的建立,此时把 Action 的做用域设置为 prototype.
扩展阅读:@Profile 注解 、 条件化装配 Bean
Spring 还提供了更灵活的注入方式,那就是 Spring 表达式,实际上 Spring EL 远比以上注入方式都要强大,它拥有不少功能:
咱们来看一个简单的使用 Spring 表达式的例子:
package pojo; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component("elBean") public class ElBean { // 经过 beanName 获取 bean,而后注入 @Value("#{role}") private Role role; // 获取 bean 的属性 id @Value("#{role.id}") private Long id; // 调用 bean 的 getNote 方法 @Value("#{role.getNote().toString()}") private String note; /* getter and setter */ }
与属性文件中读取使用的 “$
” 不一样,在 Spring EL 中则使用 “#
”
扩展阅读: Spring 表达式语言
欢迎转载,转载请注明出处!
简书ID:@我没有三颗心脏
github:wmyskxz 欢迎关注公众微信号:wmyskxz_javaweb 分享本身的Java Web学习之路以及各类Java学习资料