文章目录
一、定义
控制反转(Inversion of Control ,Ioc)
也称为依赖注入(Dependency Injection,DI)
是面向对象编程中的一种设计理念,用来降低程序代码之间的耦合度。
依赖一般指通过局部变量、方法参数、返回值等简历的对于其他对象的调用关系。
例如在A类方法中,实例化了B类的对象并调用其方法来完成特定的功能,那么我们就说A类依赖于B类
几乎所有的应用都由两个或更多的类通过合作来实现完整的功能。类与类之间的依赖关系增加了程序开发的复杂程度,我们在开发一个类的时候,还要考虑对正在使用该类的其他类的影响。例如,常见的业务调用数据访问层以实现持久化操作
二、使用场景
定义一种常见的业务层低矮用数据访问层以实现持久化操作:
/* 用户DAO接口 定义了所需的持久化方法*/
public interface UserDao{
public void save(User user);
}
/* 用户DAO实现类,实现对User类的持久化操作*/
public class UserDaoImpl implements UserDao{
public void save(User user){
// 保存信息到数据库
}
}
/* 用户业务类 实现对User功能的业务管理 */
public class UserServiceImpl implements UserService{
// 实例化所依赖的UserDao对象
private UserDao dao = new UserDaoImpl();
public void addNewUser(User user);
//调用UserDao的方法保存用户信息
dao.save(user);
}
如上代码所示,UserServiceImp对UserDaoImpl存在依赖关系。
这样的代码很常见,但是存在一个严重的问题,即UserServiceImpl和UserDaoImpl高度耦合,如果因为需求变化需要替换UserDao的实现类,将导致UserServiceImpl中的代码随之发生修改。如此,程序将不具备优良的可扩展性和可维护性,甚至在开发中难以测试。
三、使用工厂模式简单体现控制反转
/* 增加用户DAO工厂类。负责用户DAO实例的创建工作 */
public class UserDaoFactory{
public static UserDao getInstance(){
// 具体实现过程略
}
}
/* 用户业务类,实现对User功能的业务管理 */
public class UserServiceImpl implements UserService{
// 通过工厂类获取所依赖的用户DAO对象
private UserDao dao = UserDaoFactory.getInstance();
public void addNewUser(User user){
// 调用用户DAO的方法保存用户信息
dao.save(user);
}
}
此处示例代码中的用户DAO工厂类UserDaoFactory体现了“控制反转”的思想。
UserServiceImpl不再依赖自身的代码去获得所依赖的具体DAO对象,而是把这一工作转交给“第三方”UserDaoFactory,从而避免了和具体UserDao实现类之间的耦合。由此可见,在如何获取所依赖的对象上,“控制权”发生了“反转”,即从UserServiceImpl转移到了UserDaoFactory,这就是“控制反转”。
四、使用Spring实现控制反转
在Spring配置文件中,使用元素来定义Bean(也可以成为组件)的实例。
Bean元素有两个常用属性:一个是id,表示定义的Bean实例的名称;另一个是class,表示定义的Bean实例的类型
IOC原理
BeanFactory源码接口:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
public interface BeanFactory {
// 前缀
String FACTORY_BEAN_PREFIX = "&";
// 多个getBean方法
Object getBean(String name) throws BeansException;
Object <T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
// 是否包含Bean
boolean containsBean(String name);
// Bean是否单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// Bean是否原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 是否类型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// 获取Bean的类型
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
// 获取Bean的别名
String[] getAliases(String name);
}
首先我们看到了多个getBean方法,这也是IoC容器最重要的方法之一,它的意义是从IoC容器中获取Bean。而从多个getBean方法中可以看到有按类型(by type)获取Bean的,也有按名称(by name)获取Bean的,这就意味着在Spring IoC容器中,允许我们按类型或者名称获取Bean
isSingleton方法则判断Bean是否在Spring IoC中为单例。这里需要记住的是在Spring IoC容器中,默认的情况下,Bean都是以单例存在的,也就是使用getBean方法返回的都是同一个对象。与isSingleton方法相反的是isPrototype方法,如果它返回的是true,那么当我们使用getBean方法获取Bean的时候,Spring IoC容器就会创建一个新的Bean返回给调用者
由于BeanFactory的功能还不够强大,因此Spring在BeanFactory的基础上,还设计了一个更为高级的接口ApplicationContext。它是BeanFactory的子接口之一,在Spring的体系中BeanFactory和ApplicationContext是最为重要的接口设计
在现实中我们使用的大部分Spring IoC容器是ApplicationContext接口的实现类,它们的关系如图所示。
在图中可以看到,ApplicationContext接口通过继承上级接口,进而继承BeanFactory接口,但是在BeanFactory的基础上,扩展了消息国际化接口(MessageSource)、环境可配置接口(EnvironmentCapable)、应用事件发布接口(ApplicationEventPublisher)和资源模式解析接口(ResourcePatternResolver),所以它的功能会更为强大。
Java类-HelloSpring类
/* 第一个Spring,输出Hello,Spring */
public class HelloSpring{
// 定义who属性,该属性的值将通过Spring框架进行设置
private String who = null;
public void pring(){
System.out.println("Hello,"+this.getWho()+"!");
}
// 设置who
public void setWho(String who){
this.who = who ;
}
}
Spring配置文件-application.xml
<!-- 省略 -->
<!-- 通过bean元素声明需要Spring创建的实例。该实例的类型通过class属性指定 -->
<bean class="cn.springdemo.HelloSpring" id="helloSpring">
<!-- properties元素用来为实例的属性赋值 -->
<!-- 此处调用了setWho()-->
<property name="who">
<!-- 此处将字符串“Spring” 赋值给who属性 -->
<value>String</value>
</property>
</bean>
<!-- 省略 -->
经验:
1.使用<bean>元素定义一个组件时,通常需要使用id属性为其指定一个用来访问的唯一名称。如果想为Bean指定更多的别名,可以通过name属性指定,名称之间使用逗号、分号或空格进行分隔。
2.在本例中,Spring为Bean的属性赋值是通过调用属性的setter方法实现的,这种做法称为“设值注入”,而非直接为属性赋值。若属性名为who.setter方法名为setSomebody(),Spring配置文件中应写name="somebody"
使用Spring Ioc发现
实例的属性值将不再由程序中的代码来主动创建和管理,改为被动接受Spring的注入,使得组件之间以配置文件而不是硬编码的方式组织在一起。
五、参考资料
书籍:SSM轻量级框架应用实战
书籍:SpringBoot2.X