二、IoC的介绍和使用

本文详细介绍了程序的耦合与解耦的概念,重点讲解了Spring框架中的IoC(控制反转)原理和使用,包括XML配置、Bean的作用范围、生命周期、依赖注入等。此外,还探讨了Spring的工厂类结构、不同类型的耦合度,以及如何通过工厂模式和IoC解耦。文章最后提到了基于注解的IoC配置,包括常用注解如@Component、@Autowired等,并对比了XML配置与注解配置的优劣。
摘要由CSDN通过智能技术生成

1 程序的耦合和解耦【理解】

1.1 什么是程序的耦合

  1. 耦合性(Coupling),也叫耦合度:是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

    在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。它有如下分类:

    1. 内容耦合:当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
    2. 公共耦合:两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
    3. 外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
    4. 控制耦合:一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
    5. 标记耦合:若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和C之间存在一个标记耦合。
    6. 数据耦合:模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
    7. 非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

    总结:耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

  2. 内聚:标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。

内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合,就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。

我们在开发中,有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。请看下面的示例代码:

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService {
   
    private IAccountDao accountDao = new AccountDaoImpl();
}

上面的代码表示:业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种编译期依赖关系,应该在我们开发中杜绝。我们需要优化代码。

再比如:早期我们的JDBC操作注册驱动时,我们为什么不使用DriverManager的register方法,而是采用Class.forName的方式?原因就是我们的类依赖了数据库的具体驱动类(MySQL),如果这时候更换了数据库品牌(比如Oracle),需要修改源码来重新数据库驱动。这显然不是我们想要的。

1.2 解决程序耦合的思路

当时我们讲解jdbc时,是通过反射来注册驱动的,代码如下:

// 此处只是一个字符串
Class.forName("com.mysql.jdbc.Driver");

此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除MySQL的驱动jar包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。同时,也产生了一个新的问题,MySQL驱动的全限定类名字符串是在java类中写死的,一旦要改还是要修改源码。解决这个问题也很简单,使用配置文件配置。

1.3 工厂模式解耦

在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件,创建和获取三层对象的类就是工厂。

1.4 IoC(Inversion Of Control, 控制反转)

上一小节解耦的思路有2个问题:

  1. 存哪去?由于我们是很多对象,肯定要找个集合来存。这时候有Map和List供选择。有查找需求,选Map。所以在应用加载时,创建一个Map,用于存放三层对象。我们把这个Map称之为容器
  2. 工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。原来:我们在获取对象时,都是采用new的方式。是主动的
    在这里插入图片描述
    现在我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的
    在这里插入图片描述
    这种被动接收的方式获取对象的思想就是控制反转,它是spring框架的核心之一。
    在这里插入图片描述
    明确IoC的作用:削减计算机程序的耦合(解除我们代码中的依赖关系)。

2 使用Spring的IoC解决程序耦合

2.1 案例的前期准备【会用】

本章我们使用的案例是,账户的业务层和持久层的依赖关系解决。在开始spring的配置之前,我们要先准备一下环境。由于我们是使用spring解决依赖关系,并不是真正的要做增删改查操作,所以此时我们没必要写实体类。并且我们在此处使用的是java工程,不是java web工程。

2.1.1. 准备Spring的开发包

  1. 官网:http://spring.io/
  2. 下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring/
    在这里插入图片描述
  3. 下载的Spring开发包解压:
    在这里插入图片描述
  4. Spring目录结构:
    • docs:API和开发规范;
    • libs:jar包和源码;
    • schema:约束。

注意:这里使用的版本是spring5.0.2。spring5版本是用jdk8编写的,所以要求我们的jdk版本是8及以上。同时tomcat的版本要求8.5及以上。

2.1.2. 创建业务层接口和实现类

/**
 * 账户的业务层接口
 */
public interface IAccountService {
   
    /**
     * 保存账户(此处只是模拟,并不是真的要保存)
     */
    void saveAccount();
}
/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService {
   
    private IAccountDao accountDao = new AccountDaoImpl();//此处的依赖关系有待解决

    @Override
    public void saveAccount() {
   
        accountDao.saveAccount();
    }
}

2.1.3. 创建持久层接口和实现类

/**
 * 账户的持久层接口
 */
public interface IAccountDao {
   
    /**
     * 保存账户
     */
    void saveAccount();
}
/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {
   
    @Override
    public void saveAccount() {
   
        System.out.println("保存了账户");
    }
}

2.2 基于XML的配置(入门案例)【掌握】

2.2.1 第一步:拷贝必备的jar包到工程的lib目录中

在这里插入图片描述

2.2.2 第二步:在类的根路径下创建一个任意名称的xml文件(不能是中文)

在这里插入图片描述
给配置文件导入约束:/spring-framework-5.0.2.RELEASE/docs/spring-framework-reference/html5/core.html
在这里插入图片描述

<?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">
</beans>

2.2.3 第三步:让Spring管理资源,在配置文件中配置service和dao

<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
  1. bean 标签:用于配置让spring创建对象,并且存入ioc容器之中;
  2. id 属性:对象的唯一标识;
  3. class 属性:指定要创建对象的全限定类名。

2.2.4 测试配置是否成功

/**
 * 模拟一个表现层
 */
public class Client {
   
	/**
     * 使用main方法获取容器测试执行
     */
	public static void main(String[] args) {
   
		//1.使用ApplicationContext接口,就是在获取spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据bean的id获取对象
		IAccountService aService = (IAccountService) ac.getBean("accountService");
		System.out.println(aService);
		IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
		System.out.println(aDao);
	}
}

运行结果:
在这里插入图片描述

2.3 Spring基于XML的IoC细节【掌握】

2.3.1 Spring中工厂的类结构图

在这里插入图片描述
在这里插入图片描述

2.3.1.1 BeanFactory和ApplicationContext 的区别
  1. BeanFactory才是Spring容器中的顶层接口,ApplicationContext是它的子接口。
  2. 创建对象的时间点不一样。
    1. ApplicationContext :只要一读取配置文件,默认情况下就会创建对象;
    2. BeanFactory :什么使用什么时候创建对象。
2.3.1.2 ApplicationContext接口的实现类
  1. ClassPathXmlApplicationContext :它是从类的根路径下加载配置文件,推荐使用这种
  2. FileSystemXmlApplicationContext :它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置;
  3. AnnotationConfigApplicationContext :当我们使用注解配置容器对象时,需要使用此类来创建spring容器。它用来读取注解。

2.3.2 IoC中<bean>标签和管理对象细节

2.3.2.1 <bean>标签
  1. 作用:用于配置让spring来创建的对象。默认情况下它调用类中的无参构造函数来创建类对象。如果没有无参构造函数则创建失败。
  2. 属性:
    1. id :给对象在容器中提供一个唯一标识。用于获取对象;
    2. class :指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数;
    3. scope :指定对象的作用范围。
      • singleton :默认值,单例的;
      • prototype :多例的;
      • request :WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中;
      • session :WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中;
      • global session :WEB项目中,应用在Portlet环境。如果没有Portlet环境那么globalSession相当于session。
    4. init-method :指定类中的初始化方法名称;
    5. destroy-method :指定类中销毁方法名称。
2.3.2.2 Bean的作用范围和生命周期
  1. 单例对象 scope="singleton"一个应用只有一个对象的实例。它的作用范围就是整个引用。
    生命周期:
    1. 对象出生:当应用加载,创建容器时,对象就被创建了;
    2. 对象活着:只要容器在,对象一直活着;
    3. 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
  2. 多例对象 scope="prototype"每次访问对象时,都会重新创建对象实例。
    生命周期:
    1. 对象出生:当使用对象时,创建新的对象实例;
    2. 对象活着:只要对象在使用中,就一直活着;
    3. 对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
2.3.2.3 实例化Bean的三种方式
  1. 第一种方式:使用默认无参构造函数。默认情况下它调用类中的无参构造函数来创建类对象。如果没有无参构造函数则创建失败。

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
    
  2. 第二种方式:spring管理静态工厂,使用静态工厂的方法创建对象。

    /**
     * 模拟一个静态工厂,创建业务层实现类
     */
    public class StaticFactory {
         
        public static IAccountService createAccountService() {
         
            return new AccountServiceImpl();
        }
    }
    
    <bean id="accountService"
    	class="com.itheima.factory.StaticFactory"
    	factory-method="createAccountService">
    </bean>
    

    此种方式是使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入spring容器:
    1. id 属性:指定bean的id,用于从容器中获取;
    2. class 属性:指定静态工厂的全限定类名;
    3. factory-method 属性:指定生产对象的静态方法。

  3. 第三种方式:spring管理实例工厂,使用实例工厂的方法创建对象。

    /**
     * 模拟一个实例工厂,创建业务层实现类
     * 此工厂创建对象,必须现有工厂实例对象,再调用方法
     */
    public class InstanceFactory {
         
        public IAccountService createAccountService() {
         
            return new AccountServiceImpl();
        }
    }
    
    <bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>
    <bean id="accountService"
    	factory-bean="instancFactory"
    	factory-method="createAccountService">
    </bean>
    

    此种方式是先把工厂的创建交给spring来管理。然后在使用工厂的bean来调用里面的方法:

    1. factory-bean 属性:用于指定实例工厂bean的id;
    2. factory-method 属性:用于指定实例工厂中创建对象的方法。

2.3.3 Spring的依赖注入

2.3.3.1 依赖注入的概念

依赖注入(Dependency Injection)是spring框架核心ioc的具体实现。
我们的程序在编写时,通过控制反转,把对象的创建交给了spring,但是代码中不可能出现没有依赖的情况。ioc解耦只是降低他们的依赖关系,但不会消除。
例如,我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用spring之后,就让spring来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

2.3.3.2 构造函数注入

顾名思义,就是使用类中的构造函数给成员变量赋值,类中需要提供一个对应参数列表的构造函数。
赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。

  1. 涉及的标签:constructor-arg
  2. 属性:
    1. index :指定参数在构造函数参数列表的索引位置;

    2. type :指定参数在构造函数中的数据类型;

    3. name :指定参数在构造函数中的名称,用这个找给谁赋值;

      上面三个都是找给谁赋值,下面两个指的是赋什么值的。

    4. value :它能赋的值是基本数据类型和String类型;

    5. ref :它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean。

具体代码如下:

public class AccountServiceImpl implements IAccountService {
   
	private String name;
	private Integer age;
	private Date birthday;
	
	public AccountServiceImpl(String name, Integer age, Date birthday) {
   
		this.name = name;
		this.age = age;
		this.birthday = birthday;
	}
	
	@Override
    public void saveAccount() {
   
		System.out.println(name + "," + age + "," + birthday);
	}
}

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值