1 概念
IoC(Inverse of Control)控制反转是Spring容器的内核,AOP、声明式事务都是在此基础上开花结果。
因为IoC不够开门见山,Martin Fowler 提出了DI(Dependency Injection 依赖注入)的概念来代替IoC,即调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
2 IOC类型
注入方法上看,主要划分为三种类型:
- 构造函数注入
- 属性注入
- 接口注入
Spring支持构造函数注入和属性注入!
2.1 构造函数注入
通过调用类的构造函数,将接口实现类通过构造函数变量传入。
借助《精通Spring4.x企业应用实战》中liudehua饰演革离攻城理解构造函数注入如下:
MoAttack不用care具体谁来饰演GeLi这个角色,MoAttack更像剧本操作类!Director通过实现direct()方法将选定的演员注入到MoAttack中,发起MoAttack需要执行的操作(比如攻城,cityGateAsk())。
public class MoAttack {
private GeLi geli;
// 构造函数注入
public MoAttack(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
this.geli.responseAsk("墨者革离");
}
}
public class Director {
public void direct() {
// 指定接口实现类
GeLi geli = new LiuDeHua();
// 注入对象
MoAttack moAttack = new MoAttack(geli);
// 执行操作
moAttack.cityGateAsk();
}
}
public interface GeLi {
void responseAsk(String str);
}
public class LiuDeHua implements GeLi {
@Override
public void responseAsk(String str) {
System.out.println(str);
}
}
2.2 属性注入
通过Setter方法完成调用类所需依赖的注入,更加灵活方便。
public class MoAttack {
private GeLi geli;
public void cityGateAsk() {
this.geli.responseAsk("墨者革离");
}
public void setter(GeLi geli) {
this.geli = geli;
}
}
public class Director {
public void direct() {
GeLi geli = new LiuDeHua();
MoAttack moAttack = new MoAttack();
moAttack.setter(geli);
moAttack.cityGateAsk();
}
}
2.3 接口注入
同使用Setter方法对属性注入无本质区别,并且会增加接口,不推荐使用!
3 BeanFactory 和 ApplicationContext
Spring 通过一个配置文件描述Bean和Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。
Spring IOC 容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、Bean实例代理、事件发布、资源装载等。
BeanFactory(Bean 工厂)是Spring框架最核心的接口,提供了高级IOC的配置机制,管理不同类型的Java对象。
ApplicationContext(应用上下文)建立在BeanFactory基础之上,提供了更多面向应用的功能。
我们一般称BeanFactory为IOC容器;ApplicationContext为应用上下文或者Spring容器。
4 装配Bean
Spring容器成功启动的三个必要条件
- Spring 框架的类包都已经放在应用程序的类路径下。
- 应用程序为Spring提供了完整的Bean配置信息
- Bean 的类都已经放在应用程序的类路径下。
Bean配置信息包括以下四部分:
- Bean 实现类
- Bean 属性信息
- Bean 依赖关系 Bean之间的依赖
- Bean 行为配置 生命周期范围、回调函数等
Spring 支持多种形式的Bean配置方式。基于XML的配置、基于注解配置、基于Java类配置、基于Groovy动态语言配置。
Bean 配置信息定义了Bean的实现和依赖关系,Spring容器根据各种形式的配置信息在容器内部建立Bean定义注册表;然后根据注册表加载实例化Bean,并建立Bean之间的依赖关系;最后将这些准备好的Bean放到Bean缓存池中,供外层应用程序调用。
4.1 基于XML的配置
4.1.1 XML文件概念
Spring2.0 以后采用Schema格式,让不同类型的配置拥有自己的命名空间,配置文件更具扩展性。
<?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标准命名空间,用于指定自定义命名空间的Schema文件
xmlns:aop="http://www.springframework.org/schema/aop" 自定义命名空间
xsi:schemaLocation=" 为每个命名空间指定具体的Schema文件
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
Schema在文档跟节点通过xmlns对文档所引用的命名空间进行声明。
默认命名空间:没有空间名,用于SpringBean的定义。
xsi标准命名空间:用于为每个文档中的命名空间指定相应的Schema样式文件,W3C定义的标准命名空间。
自定义命名空间:用于Spring配置的自己定义命名空间。如果自己定义命名空间别名为空,表示该命名空间为文档默认命名空间
指定命名空间的用途:
- XML解析器可以获取Schema文件并对文档进行格式合法性炎症;
- 开发环境下,IDE引用Schema文件对文档编辑提供自动补全功能。
4.1.2 装配Bean
<bean id = "foo" class = "com.smart.Foo" />
id是Bean的名称,通过容器的getBean(“foo”)即可获取对应的Bean,在容器中起到定位查找的作用;class属性指定了Bean对应的实现类。
配置规则:
- 指定id,id在IOC容器中必须是唯一的。
- 指定name,允许出现重复name,如果有多个相同name的Bean,那么通过getBean(beanName)获取Bean时,将返回后面生命的那个Bean,原因是后面的Bean覆盖了前面同名的Bean。因为bean仅配置name,默认ID=name。
- id、name均未指定,Spring自动将全限定类名作为Bean名称,用户可以通过getBean(“com.smart.Foo”)获取Bean。如果存在多个实现类相同的匿名Bean,全限定类名"com.smart.Foo"、“com.smart.Foo#1”、“com.smart.Foo#2”、······依次类推
4.1.3 基于XML配置的依赖注入
属性注入:
要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。
<bean id = "foo" class = "com.smart.Foo" >
<property name = "name"><value>"fooName"</value> </property>
</bean>
Public class Foo {
private String name;
public void setName(String name) {
this.name = name;
}
}
构造函数注入:
参照属性注入,在构造方法中传入参数,通过<constructor-arg>
标签配置。
工厂方法注入:
省略。。。
4.2 Bean作用域
类型 | 说明 |
---|---|
singleton | Spring IOC容器中只存在一个Bean实例,Bean以单例的方式存在,默认作用域 |
prototype | 每次从容器中调用Bean时,都会返回一个新的实例。每次调用getBean()时,等效new XXXBean()操作 |
request | 每次Http请求都会返回一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一Http Session 共享一个Bean,不同Http Session 使用不同Bean,该作用域仅适用于WebApplicationContext环境 |
globalSession | 同一全局Session 共享同一Bean,一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
singleton
默认情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存在容器中。单例模式优点:
- 对Bean提前进行实例化操作会及早发现一些潜在的配置问题
- Bean以缓存的方式保存,在运行时无需实例化,提高了运行效率
singleton Lazy 初始化
使用@Lazy在类中注解或者在XML配置Bean标签中添加lazy-init=“true”。
prototype
默认情况下,Spring容器启动时不实例化prototype的Bean,Spring容器将Bean交给调用者后不再管理它的生命周期。
4.3 基于注解的配置
不管是XML还是注解,都是表达Bean定义的载体,实质都是为Spring容器提供Bean定义的信息,在表现形式上都是将XML定义的内容通过类注解进行描述。
Spring容器成功启动的是三个必要条件:Bean定义信息、Bean实现类和Spring本身。
如果基于XML的配置,则Bean定义信息和Bean实现类本身是分离的;如果采用基于注解的配置文件,则Bean定义信息通过在Bean实现类上的标注注解实现。
package com.smart.anno;
import org.springframework.stereotype.Component;
@Component("userDao")
public class UserDao {
// ....
}
@Component注解在UserDao类声明处对类进行标注,它可以被Spring容器识别,Spring容器自动将POJO转换为容器管理的Bean。
等效XML配置如下:
<bean id = "userDao" class = "com.smart.anno.UserDao"/>
除@Component外,Spring还提供了3个基本功能和@Component等效的注解(@Repository、@Service、@Controller),分别用于对Dao、Service和Web层的Controller进行注解。
提供这3个注解是为了让标注类本身的用途清晰化,推荐使用特定的注解标注特定Bean。
1.@Autowired属性注入
Spring通过@Autowired注解实现Bean的依赖注入。
package com.smart.anno;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoginService {
@Autowired
private UserDao userDao;
}
@Service将LoginService标注为一个Bean,@Autowired注入UserDao的Bean,@Autowired默认按类型匹配的方式在容器中查找匹配的Bean,当且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。
2.使用@Autowired的required属性
如果容器中没有一个和标注变量类型匹配的Bean,Spring容器启动时报NoSuchBeanDefinitionException异常,如果希望Spring即使找不到匹配的Bean完成注入也不要抛异常,使用@Autowired(required = false)进行标注。
默认情况下required属性值为true。
3.使用@Qualifier指定注入Bean的名称
如果容器中有一个以上匹配的Bean时,通过@Qualifier限定Bean的名称。
假设有两个类型为UserDao的Bean,一个名为userDao,一个名为otherUserDao,@Qualifier限定名称解决类型匹配问题!
@Service
public class LoginService {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}
4.对类方法进行标注
@Autowired可以对类成员变量及方法的入参进行标注
@Service
public class LoginService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
Spring容器中大部分Bean都是单例的,所以一般无须通过@Repository@Service等注解的value属性为Bean指定名称,也无需使用@Qualifier注解按名称进行注入。
Tips:
项目开发中建议在方法上标注@Autowired注解。
5.对集合类进行标注
如果对类中集合类的变量或者和方法的入参进行@Autowired标注,那么Spring会将容器中类型匹配的Bean都自动注入进来。
@Component
public class MyComponent {
@Autowired
private List<Plugin> plugins;
@Autowired
private Map<String, Plugin> map;
public List<Plugin> getPlugins() {
return plugins;
}
public Map<String, Plugin> getMap() {
return map;
}
}
Plugin是一个接口,OnePlugin和TwoPlugin是Plugin接口的实现类。
注入时,plugins会将所有类型匹配的Bean都注入到list中;所有实现Plugin接口的Bean注入到map集合,key是Bean的名字,value是所有实现了Plugin的Bean。
OnePlugin和TwoPlugin的加载顺序是不确定的,可以通过@Order注解或实现Order接口来决定Bean加载顺序,值越小,优先被加载。
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value = 1)
public class OnePlugin implements Plugin {
//...
}
注意:
Autowired是类型匹配的,除对集合类进行标注外,注入类型为接口时!一个接口只能有一个实现,否则会报错。
@Component
public class MyComponent {
@Autowired
private Plugin plugin;
}
6.对延迟依赖注入支持
Spring4.0支持延迟依赖注入,即在Spring容器启动时,对于在Bean上标注@Lazy和@Autowired注解的属性,不会立即注入属性值,而是延迟到调用此属性的时候才会注入属性值。
注意:@Lazy的使用,需要在属性和目标Bean上同时标注,缺一不可!
比如:
@Lazy
@Component("userDao")
public class UserDao {
}
@Service
public class LoginService {
private UserDao userDao;
@Lazy
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
7.对标准注解的支持
Spring支持JSR-250中的@Resource和JSR-330中的@Inject注解,这两个注解和@Autowired注解的功能类似,都是对类变更及刚发入参提供自动注入功能。
@Resource需要提供一个Bean名称属性,按名称匹配注入Bean;@Inject和@Autowired同样是按类型匹配注入Bean,但没有required属性。
无论@Recourse还是@Inject都没有@Autowired功能强大!!!可以忽略这两个注解。
Bean作用范围
通过注解配置的Bean和通过配置的Bean一样,默认作用范围是singleton。Spring为注解配置提供了一个@Scope注解,可以通过它显示指定Bean的作用范围。
4.4 使用Java类提供Bean定义信息
普通POJO只要标注@Configuration注解,就可以为Spring容器提供Bean定义的信息,每个标注了@Bean的类方法都相当于提供了一个Bean的定义信息。
package com.smart.conf;
import com.smart.dao.LogDao;
import com.smart.dao.UserDao;
import com.smart.service.LogonService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConf {
@Bean
public UserDao userDao() {
return new UserDao();
}
@Bean
public LogDao logDao() {
return new LogDao();
}
@Bean
public LogonService logonService() {
LogonService logonService = new LogonService();
logonService.setUserDao(userDao());
logonService.setLogonDao(LogDao());
return logonService;
}
}
Bean类型由方法返回值的类型决定,名称默认和方法名相同,也可以通过入参显示指定Bean名称,如Bean(name = “userDao”)。@Bean所标注的方法提供了Bean的实例化逻辑。
以上配置和如下XML配置等效
<bean id="userDao" class="com.smart.dao.UserDao" />
<bean id="logDao" class="com.smart.dao.LogDao" />
<bean id="logonService" class="com.smart.service.logonService"
p:logDao-ref="userDao" p:userDao-ref="logDao" />
基于Java类的配置方式和基于XML或者基于注解的配置方式相比,前者通过代码编程的方式可以更加灵活的实现Bean的实例化和Bean之间的装配;后者都是通过配置声明的方式,在灵活性上要稍逊一些,但是配置上更简单一些。
如果Bean在多个@Configuration配置类中定义,@Configuration注解类本身已经标注了@Component注解,所以任何标注了@Configuration的类,本身也相当于标注了@Component,他们可以像普通Bean一样被注入到其他Bean中。可以通过@Autowired注入到其他配置类中。
4.5 基于Groovy DSL 配置
省略。。。
4.6 对比总结
基于XML配置适用场景:
1)Bean实现类来源于第三方类库,如DataSource、JdbcTemplate等,因为无法再类中标注注解,所以使用XML配置。
2)命名空间的配置,如aop、context等,只能使用XML配置
基于注解配置适用场景:
Bean的实现类是当前项目开发的,可以直接在Java类中适用基于注解的配置。
基于Java类配置和基于Groovy DSL配置适用场景:
实例化Bean的逻辑比较复杂。
项目开发一般采用:XML+基于注解的配置方式