概述为何须要 IoCIoC本质实战分析编写代码思考IoC涉及到的组件IoC建立对象无参构造器有参构造器Spring中XML配置参考文献php
上一篇spring概述咱们搭建完基于 Spring 框架的环境, 这篇咱们开始真正的阅读 Spring 的源码,分析 Spring 的源码以前咱们先来简单回顾下 Spring 核心功能的简单使用。java
假若有这么一个业务场景:dao 层从不一样的地方获取用户数据,service 层用来调用获取用户的方法,如何控制从想要的地方获取用户数据?mysql
一、先写一个 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 + '\'' +
'}';
}
}
复制代码
二、写一个 UserDao 接口web
public interface UserDao {
public void getUser();
}
复制代码
三、再去写 Dao 的实现类spring
public class UserDaoImpl implements UserDao {
public void getUser() {
User user = new User("hresh");
System.out.println("从bean中获取到的用户数据为"+user);
}
}
复制代码
四、写 UserService 的接口sql
public interface UserService {
public void getUser();
}
复制代码
五、最后写 UserService 的实现类数据库
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public void getUser() {
userDao.getUser();
}
}
复制代码
六、测试一下编程
public class UserGetTest {
@Test
public void getUser(){
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
复制代码
这样就实现了一种读取用户信息的方式,接下来咱们再增长一种从 Mysql 数据库中读取用户信息的方法。bash
再增长 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( 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,配置文件以下:
<?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">
<bean id="user" class="com.msdn.bean.User">
<property name="name" value="hresh" />
</bean>
</beans>
复制代码
在上面的配置中咱们能够看到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
<?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">
<bean id="mysqlImpl" class="com.msdn.dao.UserDaoMysqlImpl" />
<bean id="oracleImpl" class="com.msdn.dao.UserDaoOracleImpl" />
<bean id="serviceImpl" class="com.msdn.service.UserServiceImpl">
<!--注意: 这里的name并非属性 , 而是set方法后面的那部分 , 首字母小写-->
<!--引用另一个bean , 不是用value 而是用 ref-->
<property name="userDao" ref="oracleImpl" />
</bean>
</beans>
复制代码
测试代码以下:
@Test
public void MyBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl");
serviceImpl.getUser();
}
复制代码
以后咱们不须要再去程序中改动了,要实现不一样的操做,只须要在 XML 配置文件中进行修改。所谓的 IoC 就是对象由 Spring 来建立、管理和装配。
在上文测试代码中咱们用到的是 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 的不一样,其主要区别有:
上述提到的六个重要知识点是 Spring 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
<?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">
<bean id="user" class="com.msdn.bean.User">
<property name="name" value="hresh" />
</bean>
</beans>
复制代码
测试代码:
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
<?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">
<bean id="user" class="com.msdn.bean.User">
<constructor-arg name="name" value="hresh" />
</bean>
</beans>
复制代码
测试代码:
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 类的有参构造器来建立对象。
别名
alias 设置别名 , 为bean设置别名 , 能够设置多个别名 。
<!--设置别名:在获取Bean的时候可使用别名获取-->
<alias name="userT" alias="userNew"/>
复制代码
Bean的配置
<!--bean就是java对象,由Spring建立和管理-->
<!--
id 是bean的标识符,要惟一,若是没有配置id,name就是默认标识符
若是配置id,又配置了name,那么name是别名
name能够设置多个别名,能够用逗号,分号,空格隔开
若是不配置id和name,能够根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.msdn.bean.Hello">
<property name="name" value="Spring"/>
</bean>
复制代码
import
团队的合做经过import来实现 ,当有多个关于 bean 定义的文件,最后能够集中在一个文件中。
<import resource="{path}/beans.xml"/>
复制代码
https://blog.kuangstudy.com/index.php/archives/518/