概述
上一篇spring概述我们搭建完基于 Spring 框架的环境, 这篇我们开始真正的阅读 Spring 的源码,分析 Spring 的源码之前我们先来简单回顾下 Spring 核心功能的简单使用。
为什么需要 IoC
假如有这么一个业务场景:dao 层从不同的地方获取用户数据,service 层用来调用获取用户的方法,如何控制从想要的地方获取用户数据?
1、先写一个 User 类
public class User { private String name; public User() { } public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
2、写一个 UserDao 接口
public interface UserDao { public void getUser();}
3、再去写 Dao 的实现类
public class UserDaoImpl implements UserDao { public void getUser() { User user = new User("hresh"); System.out.println("从bean中获取到的用户数据为"+user); }}
4、写 UserService 的接口
public interface UserService { public void getUser();}
5、最后写 UserService 的实现类
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); public void getUser() { userDao.getUser(); }}
6、测试一下
public class UserGetTest { @Test public void getUser(){ UserService userService = new UserServiceImpl(); userService.getUser(); }}
这样就实现了一种读取用户信息的方式,接下来我们再增加一种从 Mysql 数据库中读取用户信息的方法。
再增加 UserDao 的实现类
public class UserDaoMysqlImpl implements UserDao { public void getUser() { User user = new User("acorn"); System.out.println("从MySQL数据库中获取到的用户数据为"+user); }}
紧接着我们要去使用 MySql 的话 , 我们就需要去 service 实现类里面修改对应的实现。
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoMySqlImpl(); @Override public void getUser() { userDao.getUser(); }}
同样如果我们需要从 Oracle 数据库中读取数据,还需要构建一个 UserDao 的实现类,然后修改 UserServiceImpl 类。 假设我们的这种需求非常大 , 这种方式就根本不适用了,每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 。
那我们如何去解决?
我们可以在调用 UserDao 实现类的地方,不去实例化该对象,而是留出一个接口 ,利用 set 方法,代码如下:
public class UserServiceImpl implements UserService { private UserDao userDao; // 利用set实现 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void getUser() { userDao.getUser(); }}
现在在测试类里,进行测试:
public class UserGetTest { @Test public void getUser(){ UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(new UserDaoImpl()); userService.getUser(); userService.setUserDao(new UserDaoMysqlImpl()); userService.getUser(); }}
执行结果为:
从bean中获取到的用户数据为User{name='hresh'}从MySQL数据库中获取到的用户数据为User{name='acorn'}
虽然只是 UserServiceImpl 类中的代码做了修改,看起来变动不大,甚至你可能会说测试类中还变复杂了。但是仔细想一下,之前所有的 Dao 实现类都是在 UserServiceImpl 中控制创建,而现在由更接近用户的测试类中控制创建对象,把主动权交给了调用者,程序不用去管怎么创建,怎么实现了,它只负责提供一个接口即可。
这种思想 ,从本质上解决了问题 , 我们程序员不再去管理对象的创建了,更多的去关注业务的实现 ,耦合性大大降低 。这也就是 IoC 的原型 !
IoC本质
IoC( Inverse of Control:控制反转 )是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体,IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
要了解控制反转,有必要先了解软件设计的一个重要思想:依赖倒置原则( Dependency Inversion Principle )。
- 高层模块不应该依赖于底层模块,两者应该依赖于其抽象。
- 抽象不应该依赖具体实现,具体实现应该依赖抽象。
上面2点是依赖倒置原则的概念,也是核心。主要是说模块之间不要依赖具体实现,依赖接口或抽象。
其实依赖倒置原则的核心思想是面向接口编程。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清楚这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
IoC 在 Spring 中有多种实现方式,可以使用 XML 配置,也可以使用注解,新版本的 Spring 也可以零配置实现 IoC。
Spring 容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从 IoC 容器中取出需要的对象。
采用 XML 方式配置 Bean 的时候,Bean 的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
实战分析
编写代码
定义一个 bean 类:
public class User { private String name; public User() { } public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
源码很简单,bean 没有特别之处,Spring 的目的就是让我们的 bean 成为一个纯粹的 POJO,这就是 Spring 追求的,接下来就是在配置文件中定义这个 bean,配置文件如下:
复制代码
在上面的配置中我们可以看到bean的声明方式,在spring中的bean定义有N种属性,但是我们只要像上面这样简单的声明就可以使用了。
具体测试代码如下:
public class MyBeanTest { @Test public void MyBean(){ //解析application_context.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); //getBean : 参数即为spring配置文件中bean的id . User user = (User) context.getBean("user"); System.out.println(user); }}
执行结果为:
User{name='hresh'}
思考
User 对象是谁创建的?【user 对象是由 Spring 创建的】
-
User 对象的属性是怎么设置的?【user 对象的属性是由 Spring 容器设置的】
这个过程就叫做控制反转:
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用 Spring 后,对象是由 Spring 来创建的。
-
反转:程序本身不创建对象,而变成被动地接收对象。
依赖注入:利用 set 方法来进行注入的。
IOC是一种编程思想,由主动的编程变成被动的接收 。
关于 ClassPathXmlApplicationContext 的学习后续会单独介绍,有兴趣的朋友可以去看一下。
按照上述的方式我们对之前提到的业务场景进行修改。首先新增 一个 Spring 配置文件 application_context.xml
复制代码
测试代码如下:
@Testpublic void MyBean(){ ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl"); serviceImpl.getUser();}
之后我们不需要再去程序中改动了,要实现不同的操作,只需要在 XML 配置文件中进行修改。所谓的 IoC 就是对象由 Spring 来创建、管理和装配。
IoC涉及到的组件
在上文测试代码中我们用到的是 ApplicationContext,具体实现是 ClassPathXmlApplicationContext。所以接下来我们简单分析一下在此过程中涉及到的组件。
首先是 ClassPathXmlApplicationContext 类的继承关系图。
基本上包含了 IOC 体系中大部分的核心类和接口。 下面我们就针对这个图进行简单的拆分和补充说明。
Resource 主要负责对资源的抽象,它的每一个实现类都代表了一种资源的访问策略,如 ClasspathResource 、 URLResource ,FileSystemResource 等。
有了资源,就需要有资源加载模块,Spring 利用 ResourceLoader 来进行统一资源加载,关系图如下:
资源加载完毕之后就需要 BeanFactory 来进行加载解析,它是一个 bean 容器,其中 BeanDefinition 是它的基本结构,它内部维护着一 个 BeanDefinition map ,并可根据 BeanDefinition 的描述进行 bean 的创建和管理。
BeanFacoty 有三个直接子类 ListableBeanFactory
、HierarchicalBeanFactory
和 AutowireCapableBeanFactory
,DefaultListableBeanFactory
为最终默认实现,它实现了所有接口。
BeanDefinition 用来描述 Spring 中的 Bean 对象。
BeanDefinitionReader 的作用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。
ApplicationContext 是个 Spring 容器,也叫做应用上下文。它继承 BeanFactory,同时也是 BeanFactory 的扩展升级版。由于 ApplicationContext 的结构就决定了它与 BeanFactory 的不同,其主要区别有:
- 继承 MessageSource ,提供国际化的标准访问策略;
- 继承 ApplicationEventPublisher,提供强大的事件机制;
- 扩展 ResourceLoader,可以用来加载多个 Resource,可以灵活访问不同的资源;
- 对 Web 应用的支持。
上述提到的六个重要知识点是 Spring IoC 中最核心的部分,后续的学习也是针对这些内容进行详细解读。
IoC创建对象
无参构造器
当对象由无参构造器创建时,属性是由该类的 set 方法写入的。
User 类
public class User { private String name; public User() { System.out.println("user无参构造方法"); } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
application_context.xml
测试代码:
public class MyBeanTest { @Test public void MyBean(){ //解析application_context.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); //在执行getBean的时候, user已经创建好了,属性是通过set方法写入的 User user = (User) context.getBean("user"); System.out.println(user); }}
执行结果为:
user无参构造方法User{name='hresh'}
如果将 User 类中的 set 方法注释掉,再次调用测试代码,会报错,说明对象是由无参构造器创建成功后,会调用 set 方法完成实例的初始化。
有参构造器
User 类
public class User { private String name; public User() { System.out.println("user无参构造方法"); } public User(String name) { this.name = name; System.out.println("user有参构造方法"); } public String getName() { return name; }// public void setName(String name) {// this.name = name;// } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
application_context.xml
测试代码:
public class MyBeanTest { @Test public void MyBean(){ //解析application_context.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); User user = (User) context.getBean("user"); System.out.println(user); }}
执行结果为:
user有参构造方法User{name='hresh'}
结论:Spring 容器根据 XML 文件中的配置,调用 bean 类的有参构造器来创建对象。
Spring中XML配置
别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名 。
Bean的配置
import
团队的合作通过import来实现 ,当有多个关于 bean 定义的文件,最后可以集中在一个文件中。
参考:
Spring之IoC理论