配置元数据描述了Spring容器在应用程序中是如何实例化、配置和组装对象的。配置的方式有XML配置、注解配置、Java配置。
一、Bean XML配置流程
Spring的配置至少需要一个或多个由容器管理的bean.基于XML的配置元数据,需要用<beans>元素内的<bean>元素来配置。
1、基于XML的配置元数据的基本结构(定义Bean)
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="..." class="...">
<!--放置这个bean的协作者和配置-->
</bean>
<bean id="..." class="...">
<!--放置这个bean的协作者和配置-->
</bean>
</beans>
id属性是用于标识单个bean定义的字符串,它的值指协作对象,class属性定义bean的类型,并使用完全限定的类名。
应用举例:
<bean id="customerBean" class="com.herry.demo.Customer">
<property name="name" value="herry"></property>
</bean>
bean的命名:在基于XML配置元数据中,使用id或name属性来指导bean标识符。bean的命名遵循一个小写字母开头的骆驼命名规则。
(1)作用域配置
Spring 中创建bean时,可以指定作用域,作用域有以下5种类型
- 单例(singleton) 默认作用域,一个spring容器中只有Bean的一个实例。
-
原型(prototype)每次获取Bean时生成一个新的实例。
-
请求(request)作用域是单个http请求,单个http请求只有Bean的一个实例。一旦请求完成,bean实例将被销毁。
-
会话(session)作用域是单个会话,单个会话只有Bean的一个实例。一旦会话结束,bean实例将被销毁。
-
全局会话(global-session)在Portlet应用程序中使用,每个全局会话只有Bean的一个实例。普通Servlet应用中与会话作用域无区别。
(2)设置初始化方法和销毁方法
Bean在创建时,需要执行一些资源(数据库、套接字、文件)申请等初始化工作,可以在Bean的初始化回调方法中处理,此方法由Spring容器调用。
同样Bean在销毁时,需要执行一些资源(数据库、套接字、文件)申请等销毁工作,可以在Bean的销毁回调方法中处理,此方法由Spring容器调用。
应用举例:
xml文件配置
<bean id="customerBean" class="com.herry.demo.Customer" init-method="init" destroy-method="close">
</bean>
创建初始化方法和销毁方法
public class Customer {
//....
public void init() {
System.out.println("初始化...");
}
public void close() {
System.out.println("销毁...");
}
}
运行效果:
2、实例化容器(创建Spring容器)
Spring IoC容器需要在应用启动时进行实例化。在实例化过程中,IoC容器会从各种外部资源加载配置元数据,提供给ApplicationConext构造函数。
有两种IoC容器:
- ApplicationContext
- BeanFactory
ApplicationContext容器是更高级更常用的容器,继承并扩展了BeanFactory的功能。同样ApplicationContext
本身是一个Java接口,常用的实现类是:
FileSystemXmlApplicationContext
: 通过文件路径加载bean的xml配置文件ClassPathXmlApplicationContext
: 通过类路径加载bean的xml配置文件WebXmlApplicationContext
: 通过web网址加载bean的xml配置文件
举例:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Hello
{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");// ApplicationContext容器加载application.xml
}
}
3、使用容器(通过spring容器获取bean)
ApplicationContext提供的getBean()方法可用于检索bean实例。
应用举例:
//创建并配置bean
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
//检索配置了的bean实例
Customer customerBean = (Customer) context.getBean("customerBean");
//使用bean实例
customerBean.displayInfo();
4、关闭容器
最后应用结束时需要关闭容器,释放资源,容器中的所有bean也将被销毁。
context.close();
应用举例:
((ClassPathXmlApplicationContext) context).close();
二、Spring管理Bean
1、Bean的命名
Bean通过名称进行区分,每个Bean至少有一个名称,通过Bean名称,可以引用其他Bean。命名方式有三种:
- id命名,只有一个,不能重复
- name命名
- 别名(aliase)
2、Bean的实例化方法
Bean的实例化方法有两种:
- 构造函数实例化:分为有参的构造函数和无参的构造函数
- 调用静态或实例工厂方法
(1)构造函数实例化
Customer.java类如下所示,该类含有有参构造函数和无参构造函数
package com.herry.demo;
public class Customer {
String name;
//无参构造函数
public Customer() {
}
//有参构造函数
public Customer(String name) {
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void displayInfo() {
System.out.println("Hello: "+ name);
}
public void init() {
System.out.println("初始化...");
}
public void close() {
System.out.println("销毁...");
}
}
XML配置调用无参构造函数实例化Bean
<bean id="customerBean" class="com.herry.demo.Customer" init-method="init" destroy-method="close">
</bean>
XML配置调用有参构造函数实例化Bean
<bean id="customerBean" class="com.herry.demo.Customer" init-method="init" destroy-method="close">
<constructor-arg value="herry"/>
</bean>
注意constructor-arg和property在参数传递的用法不同:
- constructor-arg:通过构造函数注入。
- property:通过setter对应的方法注入
(2) 调用静态或实例工厂方法
添加Store.java文件
package com.herry.demo;
public class Store {
//静态方法
public static Customer crateCustomer1()
{
Customer customer=new Customer();
customer.setName("herry");
return customer;
}
//工厂方法
public Customer crateCustomer2()
{
Customer customer=new Customer();
customer.setName("herry");
return customer;
}
}
调用静态工厂方法实例化Bean,通过调用类中的factory-method特性所指定的静态方法实现。
<bean id="customerBean" class="com.herry.demo.Store" factory-method="crateCustomer1">
</bean>
使用实例工厂方法实例化Bean,需要加入factory-bean特性,通过该特性引用包含实例工厂方法的工厂Bean.
<bean id="storeBean" class="com.herry.demo.Store" >
</bean>
<bean id="customerBean" factory-bean="storeBean" factory-method="crateCustomer2">
</bean>
3、Bean作用域
由Spring容器创建的Bean的生存期称为Bean作用域。作用域的设定使用<bean>元素的scope特性来指定一个Bean定义的作用域。
4、延迟初始化
默认的情况下,Spring容器在启动阶段创建Bean,优点是尽可能早的发现配置错误。Spring,XML配置中<bean>元素的lazy-init特性可以定Bean定义延迟,直到Bean需要使用时,才由容器创建。延迟初始化的优点是加快容器启动时间,并且占用较少的内存量。
5、生命周期回调
Bean定义回调方法,这些方法可以在Bean生命周期的任何特点时间点由容器调用。如基于XML配置的<bean>元素中的init-method和destory-method特性。
三、依赖注入
依赖注入的基本原则是应用程序对象不应该负责它们所依赖的资源或协作者,而是应该由IoC容器处理对象创建和依赖注入,从而导致资源查找的外部化。
关于依赖注入的优势参考以下知乎回答:spring的依赖注入到底有什么优势? - 牛岱的回答 - 知乎 https://www.zhihu.com/question/27053548/answer/575335901
xml配置文件中,在bean的定义中可配置该bean的依赖项,通常使用的配置方式有2种:
- 基于构造函数注入
- 基于Setter方法注入
有以下代码:
类A和组件B,A依赖于B,A的方法importantMethod用到了B。若B是接口,且有多个实现,则A的可重用性大大降低。依赖注入,即接管对象的创建工作,并将对象的引用注入到需要该对象的组件。依赖注入框架会分别创建对象A和对象B,并将对象B注入到对象A中。
1、基于构造函数注入
对于上述代码,利用构造函数注入的形式如下:
构造函数注入在组件创建期间被执行。依赖项被表示为构造函数的参数,容器通过检查Bean定义中指定的构造函数参数来确定调用哪个构造函数。
使用<constructor-arg>的ref特性注入依赖项
应用举例:
(1)编写Customer类
package com.herry.demo;
public class Customer {
String name;
//有参构造函数
public Customer(String name) {
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMessage()
{
return "Hello: "+ name;
}
}
(2)编写MessagePrinter类,该类依赖Customer类
package com.herry.demo;
import com.herry.demo.Customer;
public class MessagePrinter {
private Customer customer;
//定义有参构造函数
public MessagePrinter(Customer customer) {
this.customer=customer;
}
//使用注入的Customer的具体逻辑
public void displayInfo() {
System.out.println(this.customer.getMessage());
}
}
(3)编写应用主类Hello
package com.herry.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Hello
{
public static void main(String[] args) {
// @SuppressWarnings("resource")
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
MessagePrinter printer=context.getBean(MessagePrinter.class);
printer.displayInfo();
((ClassPathXmlApplicationContext) context).close();
}
}
(4)创建配置文件
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 定义bean -->
<bean id="customerBean" class="com.herry.demo.Customer" >
<constructor-arg name="name" value="herry"/>
</bean>
<!-- 依赖注入 -->
<bean id="messagePrinter" class="com.herry.demo.MessagePrinter">
<constructor-arg ref="customerBean"/>
</bean>
</beans>
测试结果:
2、基于Setter方法注入
对于举例中的代码使用Setter方法依赖注入形式如下:
A类中新增setB方法,该方法被spring框架调用,以注入B的一个实例。
Setter注入是在Bean实例化完成后执行。实例化后,通过调用bean的setter方法完成。
工程的目录结构如下:
(1) Customer类与上面一样不做任何修改
(2)MessagePrinter类修改为如下:
package com.herry.demo;
import com.herry.demo.Customer;
public class MessagePrinter {
private Customer customer;
public MessagePrinter()
{
}
public Customer getCustomer() {
return customer;
}
//定义setter函数,setter方法注入bean
public void setCustomer(Customer customer) {
this.customer=customer;
}
//使用注入的Customer的具体逻辑
public void displayInfo() {
System.out.println(this.customer.getMessage());
}
}
(3) 主类也不要修改,做了如下调整,也可不变
package com.herry.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Hello
{
public static void main(String[] args) {
// @SuppressWarnings("resource")
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
MessagePrinter printer=(MessagePrinter)context.getBean("messagePrinter");
printer.displayInfo();
((ClassPathXmlApplicationContext) context).close();
}
}
(4) 配置文件修改如下
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 定义bean -->
<bean id="customerBean" class="com.herry.demo.Customer" >
<constructor-arg name="name" value="herry"></constructor-arg>
</bean>
<!-- 依赖注入 -->
<bean id="messagePrinter" class="com.herry.demo.MessagePrinter">
<property name="customer" ref="customerBean"/>
</bean>
</beans>
配置文件中,Customer类通过构造函数方式实例化Bean;messagePrinter对象通过配置property元素来调用setter方法以设置值。
(5)运行结果如下: