是什么
IOC全称: Inverse of Control, 控制反转,所以重点就是,什么是控制,怎样反转
* 谁控制谁,控制了什么
举个例子,我们先来看传统的编程
client类中代码:
public static void main(String[] args) {
UserManager userManager=new UserManagerImpl();//依赖写死在这里
userManager.addUser("张飒","123");
}
UserManager
public interface UserManager {
public void addUser(String username,String password);
}
UserManagerImpl
public class UserManagerImpl implements UserManager{
@Override
public void addUser(String username,String password){
//由我们的应用程序负责服务定位
UserDao userDao=new UserDaoMySqlImpl();
userDao.addUser(username,password);
}
}
很明显,UserDao接口有很多不同的实现,有mysql的,也有oracle的.如果我们想读取不同数据源的数据,那么就得有不同的UserManagerImpl类,而UserManagerImpl,即依赖于接口UserDao,有同时依赖实现,所以它需要在编译阶段,就确定使用哪种方式,这样就缺少灵活性了.而且代码量也增多.如果把现在调用哪种类型的userDao,交给client决定呢?
上面代码也写了,是通过应用程序直接负责服务的定位的,就是去找我想要的对象.这个是正转, 反转是什么, 是容器来帮忙创建及注入依赖对象.容器找到之后,就把你要的对象,给你,所以对象就事被动的接收依赖对象,就是所谓的反转了.
上面是不是有点像: 是自己直接找房子, 还是通过中介找房子?
现在我们把上面的代码改造,改造成spring管理的项目
这段代码很熟悉吧.
public static void main(String[] args) {
/*IOC容器中拿接口*/
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager=(UserManager)factory.getBean("userManager");
userManager.addUser("张飒","123");
}
来分析一下它.首先是applicationContext.xml
java自带了多种xml命名空间,通过这些命名空间可以配置Spring容器
beans: 支持声明Bean和装配Bean,是spring最核心也是最原始的命名空间
元素是spring最基本的配置单元,通过该元素spring将创建一个对象,这里创建了一个由spring容器管理的名字叫userDaoMySql的bean.id属性定义了Bean的名字,也作为该Bean在Spring容器中的引用.
当Spring容器加载该Bean时,Spring将使用默认的构造器来实例化userDaoMySql Bean;
<bean id="userDaoMySql" class="com.tgb.kwy.dao.UserDaoMySqlImpl"/>
<bean id="userDaoOracle" class="com.tgb.kwy.dao.UserDaoOracleImpl"/>
<bean id="userManager" class="com.tgb.kwy.manager.UserManagerImpl">
<!--<constructor-arg ref="userDaoMySql"/><!–构造函数方式–>-->
<!--通常javaBean是私有的,同时拥有一组存取器方法,以setXXX()和getXXX()形式存在.-->
<property name="userDao" ref="userDaoMySql"></property>
</bean>
上面看到了一个注释, Spring中,我们可以使用元素配置bean的属性,在许多方面都与类似,只不过一个是通过构造器参数来注入值,另一个是通过调用属性的setter方法来注入值
一旦UserManagerImpl被实例化,Spring就会调用元素所指定属性的setter方法为该属性注入值.
UserManagerImpl
public class UserManagerImpl implements UserManager {
private UserDao userDao;
/*构造函数方式*/
/*public UserManagerImpl(UserDao userDao) {
this.userDao = userDao;
}
*/
/*set方式*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String username,String password){
//由我们的应用程序负责服务定位
// UserDao userDao=new UserDaoMySqlImpl();
userDao.addUser(username,password);
}
}
在最上面的配置文件applicationContext.xml中,我们可以看到,如果想变换userDao的实现类,那么就我就改userManager中的ref=”userDaoOracle”即可.
所以控制反转: 对象的创建权,销毁都交给Spring来完成.
依赖注入:Spring主动创建被调用类的对象,然后把这个对象注入到我们自己的类中,使我们能使用它.依赖注入是通过反射实现注入的
附:上面的例子,是把配置全写在xml里面了,但是在实际项目中,更多的是扫描包,用注解的方式注入bean的实现.当需要修改的时候, 就把bean实现路径改了就成.
关于IOC是什么,我觉得这篇博客写的很好:
http://jinnianshilongnian.iteye.com/blog/1413846?page=2
源码分析
下面来了解一下Spring在这个过程中,是怎么做的?(进入源码阅读模式)
SpringIoC两个主要的容器系列,一个是BeanFactory接口的简单容器,实现了基本的功能.另一个就是ApplicationContext应用上下文,是容器的高级形态存在
BeanFactory是一个接口类,
DefaultListTableBeanFactory.XmlBeanFactory.ApplicationContext等都是具体的实现
public interface BeanFactory {
//工厂Bean的转义定义,因为如果使用bean的名字检索IOC容器得到的对象是工厂Bean生成的对象,
//如果需要得到工厂Bean本身,需要使用转义的名字来向IOC容器检索
String FACTORY_BEAN_PREFIX = "&";
//根据bean的名字,在IOC容器中得到bean实例,这个IOC容器就象一个大的抽象工厂,用户可以根据名字得到需要的bean
//在Spring中,Bean和普通的JAVA对象不同在于:
//Bean已经包含了我们在Bean定义信息中的依赖关系的处理,同时Bean是已经被放到IOC容器中进行管理了,有它自己的生命周期
Object getBean(String name) throws BeansException;
//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制
Object getBean(String name, Class requiredType) throws BeansException;
//提供对bean的检索,看看是否在IOC容器有这个名字的bean
boolean containsBean(String name);
//根据bean名字得到bean实例,并同时判断这个bean是不是单例,在配置的时候,默认的Bean被配置成单例形式,如果不需要单例形式,需要用户在Bean定义信息中标注出来,这样IOC容器在每次接受到用户的getBean要求的时候,会生成一个新的Bean返回给客户使用 - 这就是Prototype形式
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//得到bean实例的Class类型
Class getType(String name) throws NoSuchBeanDefinitionException;
//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
String[] getAliases(String name);
}
我们用的最多的就是getBean方法了. 在BeanFactory中,只对IOC容器的基本行为做了定义,具体的实现,工厂怎么生产对象的,我们得看具体的实现类.如果读取xml,可以使用XmlBeanFactory.使用方式:
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource res = resolver.getResource("classpath:applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(res);
xmlBeanFactory先通过Resource装载了Spring配置信息,然后启动IOC容器,就能通过getBean方法从IOC容器获取Bean了.但是通过BeanFactory启动IOC容器时,不会初始化配置文件中定义的Bean,而是在首次调用时
不过上面我们也提到了ApplicationContext.我们在上面的例子可以看到. 读取xml的时候, 采用的ClassPathXmlApplicationContext,而这个类就是继承了ApplicationContext
ClassPathXmlApplicationContext:默认从类路径加载配置文件
FileSystemXmlApplicationContext:默认从文件系统中装载配置文件
如果配置文件放置在类路径下,用户可以优先使用 ClassPathXmlApplicationContext 实现类:就像我们上面的例子:
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
如果配置文件放置在文件系统的路径下,则可以优先考虑使用 FileSystemXmlApplicationContext 实现类;
小结
- BeanFactory是Spring框架的基础设施,面向Spring本身
- ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合我们都直接使用ApplicationContext,而非底层的BeanFatory
- BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean
- 而ApplicationContext则在初始化应用上下文时就实例化所有单实例的Bean。
IoC初始化过程
Resource定位
- 在看源码的时候,我们可以看到ClassPathXmlApplicationContext通过继承AbstractApplicationContext,具备了ResourceLoader读入以Resource定义的BeanDefinition的能力,因为AbstractApplicationContext继承了DefaultResourceLoader;
- resourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源.
- 常见的resource资源类型如下:
FileSystemResource:以文件的绝对路径方式进行访问资源,效果类似于Java中的File;
ClassPathResourcee:以类路径的方式访问资源,效果类似于this.getClass().getResource(“/”).getPath();
ServletContextResource:web应用根目录的方式访问资源,效果类似于request.getServletContext().getRealPath(“”);
UrlResource:访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;
ByteArrayResource: 访问字节数组资源的实现类。
定位好的资源载入BeanDefinition
- IOC容器对于bean的管理和依赖注入功能实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的.
- BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析这个配置文件,配置文件中每一个就被解析成一个BeanDefinition对象(至于怎么解析的, 我感觉自己理解的不太好, 就不写了)
- 在这个时刻,依赖注入还没发生呢
将BeanDefinition注册到容器
- 上面提到了,获取所有的BeanDefinition, 那么这些BeanDefinition就存在BeanDefinitionRegistry中, 它通过hashMap来保存所有的BeanDefinition.key就是bean的名字.所以在配置文件中bean id 是不允许重复的.
- 完成了注册,就完成IOC容器的初始化过程了.这些BeanDefinition就能被容器使用了.容器作用就是对这些信息进行处理和维护.
依赖注入
上面我们有提到初始化的时候, 依赖注入还没发生. 真正发生的时刻,是用户第一次向IOC容器要Bean时. 这个是默认的配置,但是如果你在配置文件中通过lazy-init属性,让容器完成对Bean的实例化,就是相当于在初始化过程中完成的了.
- 依赖注入的入口,就是getBean()方法, 调用者通过getBean(beanName)向容器请求一个Bean时,容器将根据配置情况调用InstantiationStrategy进行bean实例化,使用BeanWrapper完成bean属性设置工作
- 简单理解:通过反射,实例化一个类时,通过反射调用set方法把hashmap中类属性注入到类中
IoC装配bean
* 关于bean作用域,这里多说一下: spring关于单例,仅限于Spring上下文的范围内,不像真正的单例, 在每个类加载器中保证只有一个实例. Spring的单例Bean只能保证在每个应用上下文中只有一个Bean的实例.还是可以通过传统的方式实例化同一个Bean. 甚至可以定义几个声明来实例化同一个bean
* 关于bean装配, spring容器默认是禁用注解装配的.所以在使用基于注解的自动装配前,我们需要在spring配置中启用它.最简单的启用方式: 这个元素告诉Spring我们打算使用基于注解的自动装配, 但是这种方式,还是需要我们使用元素显示定义Bean. 另一种更常用的是. 这个除了完成一样的工作.还运行spring自动检测Bean和定义Bean. 不再使用元素,Spring应用中的大多数Bean都能够实现定义和装配
* 怎么知道哪些类需要注册为Spring Bean?
* 就是上图中组件注解的几种方式了.Spring扫描到标注了注解的包时,会自动注册为SpringBean
bean生命周期
- Spring对Bean进行实例化
- Spring将值和Bean的引用注入进Bean对应的属性中
- 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给set-BeanName()接口方法
- 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()接口方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()接口方法,将应用上下文的引用传入
- 如果Bean实现了BeanPostProcessor接口,Spring将调用他们的post-ProcessBeforeInitialization()接口方法
- 如果Bean实现了InitaialzingBean接口,Spring将调用它们的after-PropertiesSet()接口方法.类似地,如果Bean使用intitle-method声明了初始化方法,该方法也会被调用
- 如果Bean实现了BeanPostProcessor接口,Spring将调用他们的post-ProcessAfterInitialization()方法
- 此时此刻,bean已经准备就绪,可以被应用程序使用了.它们将一直驻留在应用上下文中,直到该应用上下文被销毁
- 如果Bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法,同样,如果Bean使用destroy-method声明了销毁方法,该接口也会被调用.
参考: 众多博客,spring技术内幕书