一、简介
作为一个Java开发人员,相信对Spring框架不会陌生,本系列将从应用到源码全面讲解Spring框架,无论是想要提高自己的工作能力,亦或是想要在面试时增加更多有把握的谈资,那么可以与笔者一起学习本系列,有任何问题欢迎探讨。
- 阅读本系列文章需要读者对Spring框架有基础的使用经验。
二、什么是IOC
笔者发现一个有趣的现象,很多人在被问到"什么是Spring"或者"描述一下你理解的Spring"诸如此类的问题,张口就是"Spring就是IOC+AOP",这个答案错的有多离谱,笔者说了不算,直接看Spring官网:
首先,可以看到,Spring是一个项目总称,我们常说常用的Spring框架只是Spring众多项目的其中之一的Spring Framework,Spring下还有诸如Spring Boot、Spring Cloud、Spring Security、Spring Web Flow等等优秀项目。
那么IOC在哪呢,我们点开Spring Framework项目:
Dependency Injection(缩写DI),即依赖注入,就是IOC的其中一种具体实现手段,它就是Spring框架中的IOC,可以看出,IOC和AOP仅仅只是Spring众多项目之一Spring Framework的众多特性之核心技术的其中之二,现在回过头看看,应该明白"Spring=IOC+AOP"这个说法有多么离谱了吧。
如果再有人问你Spring是什么,你可以大胆得告诉他:Spring是一个项目总称,我们常说的Spring只是其众多项目之一Spring FrameWork的简称,而Spring FrameWork是一个项目管理框架,这个框架有非常多的特性(你可以根据官网去列举有哪些特性),我们经常提到的IOC和AOP都是其中的核心技术之一。
从Spring到Spring FrameWork再到核心技术,铺垫了这么多,那么IOC到底是什么呢?
IOC(Inversion of Control),意即控制反转,是面向对象编程中的一种设计原则。
在Spring中的IOC讲白话就是我们的对象不再使用new关键字来创建,而是交给Spring容器来管理,从而达到降低耦合度的目的。
- 什么是Spring容器:Spring容器也可以叫IOC容器,实质是一个bean工厂,bean工厂再实质可能只是一个用来存储所有bean的HashMap,所以容器一词本就比较模糊,只是IOC帮我们管理了对象,像一个存放对象的器具,我们便用容器这个词称呼它而已,大可不必深究容器是什么,这个问题本身意义不大。
- 什么是bean:bean就是IOC容器帮我们管理的对象,含糊一点来说,因为IOC容器帮我们管理对象,使对象有更多额外的一些属性,比如延迟加载(lazy)、是否单例(scope)、是否主类(primary)等等,只使用原先的Class是无法描述这么多属性的,因此,IOC容器自己定义了一个对象叫BeanDefinition(实际上它是一个接口,由实际具有不同业务逻辑的bean来实现它),它包含了我们的对象,也包含了其他属性,如果仍然不好理解,暂时先把bean就当成对象即可,在我们讲到源码时,笔者会带大家看到bean的真面目,这里先贴一个IOC容器里bean的基础定义源码,感兴趣的读者可以先看看有个映像。
package org.springframework.beans.factory.config;
/*
*bean定义类,相当于jdk中用于描述类的Class/Clazz类
*/
public interface BeanDefinition extends org.springframework.core.AttributeAccessor, org.springframework.beans.BeanMetadataElement {
java.lang.String SCOPE_SINGLETON = "singleton";
java.lang.String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
void setParentName(@org.springframework.lang.Nullable java.lang.String s);
//父类全限定名
@org.springframework.lang.Nullable
java.lang.String getParentName();
void setBeanClassName(@org.springframework.lang.Nullable java.lang.String s);
//原始对象的全限定类名
@org.springframework.lang.Nullable
java.lang.String getBeanClassName();
void setScope(@org.springframework.lang.Nullable java.lang.String s);
//是否单例
@org.springframework.lang.Nullable
java.lang.String getScope();
//是否懒加载
void setLazyInit(boolean b);
boolean isLazyInit();
//是否有依赖对象
void setDependsOn(@org.springframework.lang.Nullable java.lang.String... strings);
.....
Spring框架即便发展至今已经具备相当多优秀的功能和特性,但它在诞生之初就是一个帮我们管理对象的框架,这也是为什么讲解Spring框架要从IOC讲起。
IOC说到底只是一种抽象的设计原则,由具体使用它的框架来实现,上面已经提到过,在Spring框架中,依赖注入(DI)就是对IOC的主要实现方式,也就是说DI之于IOC的关系就像MyBatis之于ORM,永久代之于方法区。DI只是作为大多数时候IOC的主要实现方式,但IOC也有其他实现方式:依赖查找(Dependency Lookup,缩写DL)以及依赖拖拽(Dependency Pull,缩写DP),就像ORM的实现方式还有Hibernate,方法区的实现方式还有元空间一样。DL和DP这两种方式用到的地方比较少,了解即可,有兴趣的读者可以自行查阅。
三、为什么要用IOC
我们已经明白IOC是一种控制反转的设计原则,那么为什么需要控制反转呢,直接new对象又有哪些不好呢。带着这些问题我们来一起探讨为什么要用IOC以及它带来的好处。
话不多说,上代码。
首先,我们定义一个People类,当中有一个属性Car:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:31
* @Description: 拥有car的people,且有drive方法
*/
public class People {
private Car car;
public People(){ }
public void drive(){
System.out.println("今天开"+car.getCarName());
}
public void setCar(Car car) {
this.car = car;
}
}
根据面向抽象设计原则,我们定义一个Car接口,使代码更灵活,也方便扩展:
(这里额外补充一下,如果上述drive方法写成这样:
public void drive(){
BenzCar car = new BenzCar();
System.out.println("今天开"+car.getCarName());
}
那么People类和BenzCar类便产生了严重耦合度,面向抽象便是要把依赖具体类改为依赖接口类从而降低耦合,方便扩展,它与IOC没有直接关系,且莫混为一谈。
IOC的原则同样是为了降低耦合,方便扩展,往往为了实现这样的目标,就需要写更多额外的代码,例如使用面向抽象就需要写抽象出来的接口,使用IOC则需要写一个功能完善的对象生产工厂类,好在这样的工厂类Spring框架为我们提供了,直接用就好,当然,如果你够强也可以自己写一个,在本系列中,笔者也会带大家模拟一个实现IOC的对象工厂。
在整个Spring框架源码中,随处可见面向抽象原则的应用,这也是Java程序员进阶的基础,如果对面向抽象编程还不熟悉的读者可以先阅读其他相关文章学习后再阅读本系列文章。)
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:27
* @Description: Car接口
*/
public interface Car {
String getCarName();
}
接着,两个实现Car接口的具体实现类,并实现接口唯一的抽象方法,返回各自的车名:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:27
* @Description: 奔驰车
*/
public class BenzCar implements Car{
@Override
public String getCarName() {
return "奔驰";
}
}
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:31
* @Description: 宝马车
*/
public class BMWCar implements Car{
@Override
public String getCarName() {
return "宝马";
}
}
主类测试运行:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
People people =new People();
people.setCar(new BenzCar());
people.drive();
}
}
运行结果:今天开奔驰
可以看出,主类直接依赖了People类和BenzCar类,且如果People今天不想开奔驰了,要开宝马怎么办,那么需要直接修改主类中的代码:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
People people =new People();
people.setCar(new BMWCar());
people.drive();
}
}
运行结果:今天开宝马
这样代码间的耦合度高,且不易维护,可能这么一个People类和它的依赖类Car不太能够让你体会到这些缺点,那么试想一下,不止这一个测试类中有People和Car,还有更多业务类也有呢,再试想一下,你的项目中不可能只有一个People类吧,那么各个业务类中的对象创建和依赖关系的维护都如同上述案例中一样,是否会让你感到崩溃,它们互相交织,就如同一个杂乱无章的毛线团。
那么假如我们把这个"毛线团"全部丢到一个地方呢?这个地方我们现在给他起个名叫对象工厂,对象工厂帮我们创建项目中的所有对象,且帮我们维护它们的依赖关系,那么在项目中的所有业务逻辑中,我们都不再关心对象如何创建,依赖关系如何维护,所有地方都统一依赖对象工厂,然后向工厂取出我们要的对象。
/**
* @Author: Richard Lv
* @Date: 2021/2/2 10:55
* @Description: 对象生产工厂(极简陋版)
*/
public class ObjectFactory {
public static People getPeople(){
People people =new People();
people.setCar(new BMWCar());
return people;
}
}
依赖工厂的测试类:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
People people = ObjectFactory.getPeople();
people.drive();
}
}
可以看到,此时的测试类不再对People和Car有耦合度,只依赖了工厂,也不关心People的创建和依赖维护过程,只管得到对象和执行方法。
此时,你或许有疑问,只是把刚刚的代码移了一个地方封装了一个方法而已,而且还多了一个工厂类,但不要忘了我在写这个工厂类之前的所说的“毛线团”,因此,不要局限于我这个工厂类只写了这一个方法和它唯一一个地方的调用,这一个方法可以在很多地方调用,并且,这个工厂也不止生产这一个对象。
那么,以此类推,整个项目中所有业务类都只依赖对象工厂,那么项目的整体耦合度自然就下降了,易维护性就上升了。
这还不够,仅有这个对象工厂并不是IOC的完全体,可以看出,我的这个简陋工厂中,只管理了对象,没管理对象的依赖,依赖被我写死了,回到之前的问题,People今天又要换Car开了,虽然业务类的逻辑不用再修改,可是还是要改工厂类中的逻辑啊,Spring的IOC当然不会像我的demo一样low,它提供了xml和注解的方式让我们提供类之间的依赖关系,然后完成属性注入,这才是Spring IOC的精髓所在,也是依赖注入名称的由来。
我们先来看看Spring IOC的对象管理以及依赖维护方式,同时可以比较我上面写的简陋版,进一步感受Spring IOC的魅力。
首先看Spring IOC的XML方式,定义一个spring.xml文件:
修改之前的测试类:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
BeanFactory beanFactory=new ClassPathXmlApplicationContext("classpath:spring.xml");
People people = (People) beanFactory.getBean("people");
people.drive();
}
}
运行结果:今天开奔驰
可以看到,Spring也有一个工厂叫做BeanFactory,类似我上面的ObjectFactory,只不过人家的功能比我的强大也复杂得多,在XML方式中,我们在spring.xml中配置需要BeanFactory管理的对象,以及显示定义类之间的依赖关系,然后在测试类中只管取出用即可,不管People要开什么Car,都不用修改任何业务代码,改改配置文件即可。
另外,Spring还提供了基于注解(annotation)风格的IOC(以下是annotation+javaConfig技术的纯注解风格,你也可以使用annotation+xml的风格,详细的请看后续文章,这里暂不做介绍),首先,定义一个配置类:
/*
* 此注解表示这是一个配置类,spring容器在扫描到这个类时会按照配置类的方式解析这个类
*/
@Configuration
/*
此注解表示开启扫描功能,并扫描com.richard包下的类,在xml中我们显示定义了bean,并告知spring容器这个类的全路径,
那么spring容器就可以通过类的全路径反射得到Class类并实例化对象
而基于注解的方式没有这样的配置,因此,spring容器必须自己去扫描我们给定的路径下的所有类,并根据是否加了注解来决定该类
是否要放到spring容器当中
*/
@ComponentScan("com.richard")
public class SpringConfig { }
然后在需要Spring容器管理的对象上加上注解:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:27
* @Description: 奔驰车
*/
/*
Spring在扫描包时发现该类加了此注解,便会把这个类放进容器进行管理
*/
@Component
public class BenzCar implements Car{
@Override
public String getCarName() {
return "奔驰";
}
}
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:31
* @Description: 宝马车
*/
@Component
public class BMWCar implements Car{
@Override
public String getCarName() {
return "宝马";
}
}
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:31
* @Description: 拥有car的people,且有drive方法
*/
@Component
public class People {
/*
这个注解的参数就是需要注入的类的类名(首字母小写)
这里使用@Resource而没使用@Autowired是为了更直观一点表达我要注入哪个对象。
不明白@Autowired和@Resource以及自动注入的概念也没关系,后续在IOC应用文章中
会详细介绍
*/
@Resource(name = "benzCar")
private Car car;
public People(){ }
public void drive(){
System.out.println("今天开"+car.getCarName());
}
public void setCar(Car car) {
this.car = car;
}
}
(这里面有一些注解的使用和关于自动注入的概念如果不明白的没关系,这一篇文章本来就是简单介绍IOC,让你感受IOC的魅力,在接下来的文章会依次介绍IOC的应用,Spring的源码,我们一步一步来。)
最后,修改测试类:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
/*
* 注意,这里的实现类换了,参数是上面的Spring配置类
* 之前用XML的实现类是ClassPathXmlApplicationContext
* 他们扫描和解析对象的方式不同,但都是BeanFactory实现类,最终在管理对象时殊途同归
*/
BeanFactory beanFactory=new AnnotationConfigApplicationContext(SpringConfig.class);
People people = (People) beanFactory.getBean("people");
people.drive();
}
}
运行结果:今天开奔驰
那么,至此,你是否对什么是IOC以及为什么要用IOC有了更进一步的答案呢?其实这两个问题本身就应该结合来答:什么是IOC,简单来说,它能够帮我们管理项目中所有业务对象(注意,是具有业务逻辑的业务对象,类似Entity这样的数据对象本身也无需管理),并且帮我们维护其中的依赖关系,让我们告别通篇的new关键字;为什么要用IOC,它能够极大降低项目中类与类之间的耦合度。
四、IOC还能做什么
这个问题其实很简单,稍微延伸一下就可以想到,既然我们业务逻辑中再也不关心对象的创建过程,那么工厂在生产对象时就可以做很多事情。
比如,我不想得到一个普通的People了, 我想得到一个增加功能版本的People,怎么办?类的增强在Java设计模式中其实就是代理,也就是我想得到一个代理对象。
先看结果:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 10:44
* @Description: 测试类
*/
public class Test {
public static void main(String[] args) {
BeanFactory beanFactory=new AnnotationConfigApplicationContext(SpringConfig.class);
People people = (People) beanFactory.getBean("people");
people.drive();
}
}
运行结果:
我被增强啦,我开车很快
今天开奔驰
可以看见,我这个测试类与上面最后一次修改时一模一样,未做任何改动,但运行结果改变了。我们打个断点来看看得到的People是什么:
从图中可以看到,People不是原来的普通People了,而是一个被CGLib代理出来的代理对象,而完成这样的事,我只需要关注怎么写增强逻辑,根本不需要改动原来代码中所有People产生的地方,这就是把对象交给工厂来管理的又一大用处。
上面怎么完成这个增强的,相信熟悉Spring框架的读者已经猜到,没错,就是用了AOP,在最后我会贴出这个增强逻辑的AOP相关代码,不熟悉AOP的可以跳过。
那么,到这里,什么是IOC,为什么需要控制反转(对象工厂),IOC有什么好处,能用来做什么,相信读者都感受到了,也有了自己的答案。在上面的讲解中,或多或少用到了一些Spring IOC的API,不熟悉的读者可以关注接下来笔者对IOC应用的讲解。
本篇内容如有任何问题欢迎指出讨论,一起进步。
上面案例中涉及到的AOP代码(这里仅列出,不对其中任何AOP知识进行讲解,可关注笔者后续讲解AOP的文章):
首先,在Spring配置类中开启AspectJ语法支持的注解:
@Configuration
@ComponentScan("com.richard")
//开启AspectJ语法支持
@EnableAspectJAutoProxy
public class SpringConfig { }
定义一个注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface SuperMan {
}
定义一个切面,切点,连接点,通知以及织入逻辑:
/**
* @Author: Richard Lv
* @Date: 2021/2/3 11:34
* @Description: AOP切面
*/
@Aspect
@Component
public class MyAspect {
/*
切点就是所有加了我定义的@SuperMan注解的方法
*/
@Pointcut("@annotation(com.richard.annotation.SuperMan)")
public void methodsToBeAnnotated(){}
@Before("methodsToBeAnnotated()")
public void doSuper() {
System.out.println("我被增强啦,我开车很快");
}
}
在对应方法上加上我的注解:
/**
* @Author: Richard Lv
* @Date: 2021/1/29 16:31
* @Description: 拥有car的people,且有drive方法
*/
@Component
public class People {
@Resource(name = "benzCar")
private Car car;
public People(){ }
/*
加上这个注解,BeanFactory在创建这个对象实例时就会根据我写
的AOP逻辑产生一个代理对象
准确来说,应该是AOP通过BeanFactory提供的API干预了Bean的生命周期,
在实例对象生成过程中用代理对象替换了原来的普通对象,这需要对Spring源码有一定
的理解才能体会到这一步,如果基础比较薄弱,按上面的方式可能更好理解一点。
*/
@SuperMan
public void drive(){
System.out.println("今天开"+car.getCarName());
}
public void setCar(Car car) {
this.car = car;
}
}
至此,案例中的增强功能就大功告成了。
《走进Spring框架系列—初识IOC》
《走进Spring框架系列—IOC应用》
《走进Spring框架系列—IOC应用进阶篇》
《走进Spring框架系列—IOC应用进阶实战》
本系列文章参考-------Spring官网以及Spring框架源码