文章目录
Spring 中的IOC 和 DI
1. IOC 的概念和作用
1.1 什么是 ioc
首先,看一段代码:
private IAccountDao accountDao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao)BeanFactory.getBean("accountDao");
这两段代码代表了两种截然不同的创建对象的方式。
第一种方式,是通过 new 的方式来创建对象,即我们在找对象的时候是主动的。我们的应用 app 主动地在找,直接和资源进行联系。这个时候,应用和资源之间是有必然的联系的,并且这联系不能消除。这样,对于我们的应用的独立就变得很难。
而通过第二种方式我们会发现,应用App和资源之间已经没有直接地联系。App为了获得资源是找工厂获得资源,由工厂来和资源进行联系,并将资源转给应用。从而实现了消除应用和资源之间必然的直接的依赖关系。这种思想,就是我们所说的 ioc。
1.1.1 ioc 的概念:
ioc ( Inversion of Control),控制反转。把创建对象的权利交给框架。是框架的重要特征,并非面向对象编程的专业术语。它包括依赖注入( Dependency Injection, DI)和依赖查找(Dependency Lookup)。
1.1.2 ioc 为什么叫控制反转
ioc 的主要作用就是解耦,降低程序和类之间的相互的依赖性。那么为什么 ioc 不叫做降低依赖,而叫控制反转呢?
这是由于,一个程序,如果想要获的一个类的实例化对象,可以通过 new 关键字,来直接获取。这个过程取决于程序。即程序控制着对象的创建。
而当我们通过工厂类来获取类的实例化对象的时候,创建的对象是怎么的,到底能不能够使用,就不取决于程序,而是取决于工厂,工厂将类实例化之后,传给程序。程序无法独立自主的控制类的实例化。
这样,控制权从程序(本类)转交给其他类。这就是成为控制反转的原因。
1.1.3 ioc 的作用
ioc 不能实现数据库的增删改查,也不能实现表现层的请求参数的封装,甚至也无法接受请求。它的作用其实非常的简单:
消减计算机程序的耦合(解除我们代码中的依赖关系)。而不能消除代码中的依赖关系。
2. 使用 Spring 的 ioc 解决程序耦合
2.1 准备 Spring 的开发包
官网:https://spring.io/
下载地址:https://repo.spring.io/libs-release-local/org/springframework/spring/
解压:(spring 目录结构)
- docs:API 和开发规范
- libs:jar 包和源码
- schema:约束
这里需要注意,由于 spring5 使用的 jdk8 编写的,所以要求我们的 jdk 版本是 8 及以上。同时 tomcat 的版本要求 8.5 以上 。
2.2 spring 基于 xml 的 ioc 环境搭建和入门
-
创建一个不基于任何骨架的maven项目:
-
在 pom.xml 中导入以下依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.selflearning.spring</groupId> <artifactId>maven_spring_ioc_di</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.1.9.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.1.9.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> </dependencies> </project>
-
以下是导入的包:
-
以下是项目和包之间的依赖关系:
-
在 spring 中,由于 spring-jcl 中已集成了 apache 的commons-logging,所以,为了使用 spring 的最基础最核心的功能必须导入 common-logging 这个日志 jar 包。spring-aop 面向切面的编程依赖于 aspectjrt 和 aspectjweaver 这两个 jar。虽然暂时用不到,不过还是导入一下好了。
-
-
编写持久层 dao 和其实现类:
package com.selflearning.spring.dao; /** * 账户的持久层接口 */ public interface IAccountDao { /** * 模拟保存账户 */ void saveAccount(); }
实现类:
package com.selflearning.spring.dao.impl; import com.selflearning.spring.dao.IAccountDao; /** * 账户的持久层实现类 */ public class AccountDaoImpl implements IAccountDao { @Override public void saveAccount() { System.out.println("保存了账户"); } }
-
编写业务层 service 和其实现类:
package com.selflearning.spring.service; /** * 业务层的接口 - 操作账户 */ public interface IAccountService { /** * 模拟保存账户 */ void saveAccount(); }
实现类:
package com.selflearning.spring.service.impl; import com.selflearning.spring.dao.IAccountDao; import com.selflearning.spring.dao.impl.AccountDaoImpl; import com.selflearning.spring.service.IAccountService; /** * 账户的业务层实现类 */ public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao = new AccountDaoImpl(); @Override public void saveAccount() { accountDao.saveAccount(); } }
-
为了使用 spring,我们需要在 resources 文件夹下创建一个 xml 配置文件,用来配置可重用组件的id 和 可用于反射来创建类的全限定类名。
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 把对象交给 Spring 来管理 --> <bean id="accountService" class="com.selflearning.spring.service.impl.AccountServiceImpl"></bean> <bean id="accountDao" class="com.selflearning.spring.dao.impl.AccountDaoImpl"></bean> </beans>
这里的约束可以在下载的 spring 发行版本的 doc 文件夹下的 spring-framework-reference/core.htm 找到
-
然后,就可以在模拟的视图层来进行测试了:
package com.selflearning.spring.ui; import com.selflearning.spring.dao.IAccountDao; import com.selflearning.spring.service.IAccountService; import com.selflearning.spring.service.impl.AccountServiceImpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * 模拟一个表现层,用于调用业务层 */ public class Client { /** * 获取 spring 的 ioc 核心容器,并根据 id 获取对象 * @param args */ public static void main(String[] args) { // 1. 获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); // 2. 根据 id 获取 bean 对象 IAccountService as = (IAccountService) ac.getBean("accountService"); IAccountDao aDao = ac.getBean("accountDao", IAccountDao.class); System.out.println(as); System.out.println(aDao); // as.saveAccount(); } }
-
以下是输出:
com.selflearning.spring.service.impl.AccountServiceImpl@5442a311 com.selflearning.spring.dao.impl.AccountDaoImpl@548e7350
-
至此,基于 xml 的 ioc 的入门案例已经完成了。
3. ApplicationContext 的三个常用实现类
在上面的例子中,我们使用的 ClassPathXmlApplicationContext 这个 ApplicationContext 的实现类来实现对 bean.xml 的读取的。其实,ApplicationContext 常用的实现类一共有三个:
- FileSystemXmlApplicationContext - 可以加载磁盘任意路径下的配置文件(必须有访问权限)。
- ClassPathXmlApplicationContext - 可以加载类路径下的配置文件。
- AnnotationConfigApplicationContext - 它是用于读取注释创建容器的。
而 Application 的实现类非常多:
3.1 FileSystemXmlApplicationContext
FileSystemXmlApplicationContext 可以加载任意路径下的配置文件:
ApplicationContext ac = new FileSystemXmlApplicationContext("D:\Document\java\self_learning\spring\day01\maven_spring_ioc_di\src\main\resources\bean.xml");
3.2 ClassPathXmlApplicationContext
ClassPathXmlApplicationContext 可以加载类路径下的配置文件。
对于 maven 项目来讲, 当 maven 项目部署之后,resources 文件夹消失,配置文件会相应地放在类路径的跟目录。
对于普通的 Java 项目,放在源包下即可。
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
在实际的开发中,和 FileSystemXmlApplicationContext 相比, ClassPathXmlApplicationContext更加常用。
3.3 AnnotationConfigApplicationContext
AnnotationConfigApplicationContext 它是用于读取注释创建容器的。
4. BeanFactory 和 ApplicationContext 的区别
-
ApplicationContext:
- 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也即是说,只要一读取完配置文件马上就创建配置文件中的配置对象。
- 单例对象适用
- 实际开发中,采用此接口,此接口可以根据配置文件的不同来决定是采取立即加载还是延迟加载。
-
BeanFactory:
- 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据 id 获取对象了,什么时候才真正创建对象。
- 多例对象适用。
-
由于 Spring 是一个非常强大的框架,它能够根据读取的配置文件中的配置信息来决定到底适用立即加载还是延时加载。
5. Spring 中 bean 的细节
5.1 三种创建 bean 对象的方式
-
使用默认构造函数创建
-
在 Spring 配置文件中使用 bean 标签,配以 id 和 class 属性之后,且没有其他属性和标签时。采用的就是默认构造函数创建 bean 对象,此时,如果如果类中没有默认构造函数,则无法创建对象 。
<bean id="accountService" class="com.selflearning.spring.service.impl.AccountServiceImpl"></bean>
-
此时,该实现类中:
package com.selflearning.spring.service.impl; import com.selflearning.spring.service.IAccountService; public class AccountServiceImpl implements IAccountService { public AccountServiceImpl(int i) { } @Override public void saveAccount(){ System.out.println("Serivce 执行"); } }
没有默认构造函数,所以:会报错。
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.selflearning.spring.service.impl.AccountServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.selflearning.spring.service.impl.AccountServiceImpl.<init>()
没有默认构造函数,无法初始化对象。
-
-
使用其他 jar 包中的类来创建 bean 对象
-
有些时候,我们会引用其他框架已经写好的 jar 包(比如 Mybatis 框架在获取 SqlSession 的实例化对象来创建代理类的时候,需要首先使用 SqlSessionFactory 这个工厂类创建 SqlSession s实例化对象),这个时候,应该怎么办呢?
-
首先,这里先创建一个工厂类,来模拟导入的 jar 包中的类:
package com.selflearning.spring.factory; import com.selflearning.spring.service.IAccountService; import com.selflearning.spring.service.impl.AccountServiceImpl; /** * 模拟一个工厂类,该类可能是存在于 jar 包中,我们无法通过 * 修改源码的方式来提供默认构造函数 */ public class InstanceFactory { public IAccountService getAccountService() { return new AccountServiceImpl(); } }
-
然后,为了使用这个工厂类中的 getAccountService 方法,我们要在 xml 文件中创建:
<!-- 第二种方式:使用普通工厂类中的方法创建对象(使用某个类中的方法 创建对象,并存入 Spring 容器中--> <bean id="instanceFactory" class="com.selflearning.spring.factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
-
这里的配置文件的读取过程为:
首先 Spring 框架读取配置文件。当读到这两行时,Spring 会进入 id 为 “instanceFactory” 的标签中,并且当传入的参数为 accountService 时,会调用该工厂类中的 getAccountService 进行 Bean 的创建。
-
-
使用外部类中的静态方法来创建 bean:
-
有些时候,其他是通过其他 jar 包中类,依靠其静态方法来创建对象(比如我们手写的数据库连接池,返回连接时,一般使用的就是静态方法)。
-
使用静态方法时,只需要在标签中添加属性
factory-method="staticMethod"
即可。<!-- 第三种方式:使用工厂类中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入 Spring 容器 --> <bean id="accountService" class="com.selflearning.spring.factory.StaticFactory" factory-method="getAccountService"></bean>
-
6. bean 作用范围
在 Spring 中,通过其默认创建的对象是单例对象。但是我们可以在配置文件中,通过 scope 属性来修改bean的作用范围。
scope有以下几个属性值:
- singleton:单例的(默认)
- prototype:多例的
- request:作用于 web 应用的请求范围
- session:作用于 web 应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群时,它就是 session。
7. bean生命周期
- 单例对象
- 出生:当容器创建时,对象出生
- 活着:只要容器还在,对象一直存活
- 死亡:容器销毁,对象消亡
- 总结:单例对象的声明周期和容器相同
- 多例对象
- 出生:当我们使用对象时,Spring 框架为我们创建。
- 活着:对象只要是在使用的过程,就一直或者。
- 死亡:当对象长时间不用且没有别的对象引用时,由 Java 的垃圾回收期回收。