一.前言
lz最近打算再过一遍Spring,温故而知新,想当年初入IT时不知从何入手,甚是捉急,IOC、AOP看了无数遍也不知其解,随着工作经验的累积,渐渐悟到了学习路线,整理出来,供大家研讨。
二. 本编讲点
无论你是小白还是老司机,Spring三大特性一定背吐了:
1.IOC(控制反转) 2.DI(依赖注入) 3.AOP(面向切面);
其中,IOC和DI是紧密相连的,所以本篇文章主要讲一讲什么是IOC/DI,到底有什么用!
三.什么是IOC,有什么用?
首先IOC是个容器,官方一点的话来说,最主要是完成了完成对象的创建和依赖的管理注入等等,这句话大家应该都知道,个人理解就是简化了开发,直接上代码感受一下,我们模拟一个购物的场景
/**
* 〈商场〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
public class Shop {
public Person person;
public String name;
public Shop(Person person) {
this.person = person;
setName("打开淘宝");
}
public void open() {
System.out.println(getName());
person.say();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Person {
public Pick pick;
private String name;
public Person(Pick pick) {
this.pick = pick;
setName("Jack MA");
}
public void say() {
System.out.println("我是"+getName()+",我要开始买东西了!");
pick.pick();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 〈挑选物品〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
public class Pick {
public Pay pay;
private String commodity;
public Pick(Pay pay) {
this.pay = pay;
setCommodity("防脱发洗发液");
}
public void pick() {
System.out.println("我挑选了" + getCommodity());
pay.pay();
}
public String getCommodity() {
return commodity;
}
public void setCommodity(String commodity) {
this.commodity = commodity;
}
}
/**
* 〈挑选物品〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
public class Pick {
public Pay pay;
private String commodity;
public Pick(Pay pay) {
this.pay = pay;
setCommodity("防脱发洗发液");
}
public void pick() {
System.out.println("我挑选了" + getCommodity());
pay.pay();
}
public String getCommodity() {
return commodity;
}
public void setCommodity(String commodity) {
this.commodity = commodity;
}
}
ok了,这一个简单的场景就模拟出来了,为了能更好的体会IOC的好处,Lz特意写的麻烦些,再写个测试接口跑一下
/**
* 〈一句话功能简述〉<br>
* 〈测试接口〉
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@RestController
@RequestMapping(value = "/test")
public class TestController {
@RequestMapping(value = "/demo")
@ResponseBody
public String demo() {
Pay pay = new Pay();
Pick pick = new Pick(pay);
Person person = new Person(pick);
Shop shop = new Shop(person);
shop.open();
return "ok";
}
}
好了,用原始的方法,我们通过对象的传递来维护依赖关系,那么我们用spring的情况下,该怎么写呢?
/**
* 〈商场〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringShop")
public class Shop {
@Autowired
private Person person;
public String name;
public Shop() {
setName("打开淘宝");
}
public void open() {
System.out.println(getName());
person.say();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 〈顾客〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringPerson")
public class Person {
@Autowired
private Pick pick;
private String name;
public void say() {
System.out.println("我是" + getName() + ",我要开始买东西了!");
pick.pick();
}
public Person() {
setName("Jack MA");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 〈挑选物品〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringPick")
public class Pick {
@Autowired
private Pay pay;
private String commodity;
public Pick() {
setCommodity("防脱发洗发液");
}
public void pick() {
System.out.println("我挑选了" + getCommodity());
pay.pay();
}
public String getCommodity() {
return commodity;
}
public void setCommodity(String commodity) {
this.commodity = commodity;
}
}
/**
* 〈付款〉<br>
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringPay")
public class Pay {
private String payWay;
public Pay(){
setPayWay("支付宝");
}
public String getPayWay() {
return payWay;
}
public void setPayWay(String payWay) {
this.payWay = payWay;
}
public void pay() {
System.out.println("我用" + getPayWay() + "付款");
}
}
/**
* 〈一句话功能简述〉<br>
* 〈测试接口〉
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@RestController(value = "SpringTestController")
@RequestMapping(value = "/test2")
public class TestController {
@Autowired
private com.koolearn.donut.practise.spring.Shop shop;
@RequestMapping(value = "/demo")
@ResponseBody
public String demo() {
shop.open();
return "ok";
}
}
怎么样,用了注解之后是不是感觉一身轻松,在实际项目中,一个对象不可能只依赖一个对象,大多数是依赖多个甚至十几个,并且依赖关系是n层,维护起来让人崩溃,有了Spring,你不用关心对象的实例化,更不用维护依赖关系,真正的实现了解耦!这就是程序员的春天,哈哈。
By the way,spring注入的另一种方式是xml配置,这里就不说了
四.理解IOC思想
lz觉得编程难点,是难在理解代码的思想,因为它是抽象的,和数学物理一样,看不见摸不着,正确理解并掌握了设计思想,才会从码农进阶为互联网精英,如果你是一个善于思考的人,可能会发现我们所学习到的知识似乎都遵循着某种规律,即使再抽象的东西,其本质思想,也会跟现实生活中有点联系,就拿IOC来说,就是把new对象的权利交给了Spring容器来处理,在现实中,不就是把一些自己嫌麻烦的事情,交给第三方来处理嘛;举个栗子,n多年前我们打车去一个地方,只能出门拦空车,节假日和大冬天一等就是好久,有时还要拼车,你需要告诉司机你去哪,司机看顺不顺路,再考虑是否带上你,这就可以看成是自己new 一个对象(出租车),乘客的目的地就像是一种依赖关系;现在好了,下一个app,输入起点终点,交给平台(容器)就什么也不用管了,美滋滋。
好了,有点扯远了,知道了IOC的作用,我们接下来看看源码,研究一下是怎么实现的吧!
五.Spring IOC的结构体系
(1)BeanFactory
大名鼎鼎的Bean工厂,Spring中Bean的创建是典型的工厂模式,看一下代码:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
boolean containsBean(String var1);
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
第一次看源码的时候一脸蒙蔽,包那么多,那么复杂,不过研究东西就要静下心来,这个就是Spring核心的Bean工厂定义,好像是2001年写的,这个类是Spring中所有的bean工厂,也就是俗称IOC容器的祖宗(膜拜3秒钟),各种IOC容器都只是它的实现或者为了满足特别需求的扩展实现,包括我们平时用的ApplicationContext。从接口中的方法名字不难看出,这些工厂的实现最大的作用就是根据bean的名称或者类型等等,返回一个bean的实例。
来看一下他的关系图
BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。但是从图中能发现最终的默认实现类是 DefaultListableBeanFactory,他实现了所有的接口。那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口都有他使用的场合,它主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。例如 ListableBeanFactory 接口表示这些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。这四个接口共同定义了 Bean 的集合、Bean 之间的关系、以及 Bean 行为。
现在我们来思考一件事情:一个工厂想要拥有这个功能,需要具备几个因素呢?
1.需要持有各种Bean的定义,否则无法正确的完成bean的实例化;
2.需要持有bean之间的依赖关系,否则在实例化过程中也会出现问题。例如上例,我们只是各自持有person和shop,却不知道他们的依赖关系,那么在shop初始化后,调用open方法时,就会报空指针。这是因为shop其实没有真正的被正确的实例化。
3.以上两种都要依赖我们写依赖关系的定义,我们暂且认为是xml文件,那么我们需要一个工具来读取它
在我的理解中,只要满足上述三种条件,便可以创建一个bean工厂,当然,Spring还有更高级的做法,以上只是我直观的去想如何实现IOC。
接下来接着思考,第一步,如何持有Bean的定义?我们知道在Spring的Xml配置文件中,有个lazy-init的属性,这就说明我们是可以控制bean在何时实例化的。这个属性默认是false的,也就是说Spring容器初始化后,配置了延迟加载的bean都还未产生,只是存储了bean的定义,而非实例,在需要的时候,它才会出现,接下来,再介绍一个祖宗级别的接口:BeanDefinition
package org.springframework.beans.factory.config;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.AttributeAccessor;
import org.springframework.lang.Nullable;
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
void setParentName(@Nullable String var1);
@Nullable
String getParentName();
void setBeanClassName(@Nullable String var1);
@Nullable
String getBeanClassName();
void setScope(@Nullable String var1);
@Nullable
String getScope();
void setLazyInit(boolean var1);
boolean isLazyInit();
void setDependsOn(@Nullable String... var1);
@Nullable
String[] getDependsOn();
void setAutowireCandidate(boolean var1);
boolean isAutowireCandidate();
void setPrimary(boolean var1);
boolean isPrimary();
void setFactoryBeanName(@Nullable String var1);
@Nullable
String getFactoryBeanName();
void setFactoryMethodName(@Nullable String var1);
@Nullable
String getFactoryMethodName();
ConstructorArgumentValues getConstructorArgumentValues();
default boolean hasConstructorArgumentValues() {
return !this.getConstructorArgumentValues().isEmpty();
}
MutablePropertyValues getPropertyValues();
default boolean hasPropertyValues() {
return !this.getPropertyValues().isEmpty();
}
void setInitMethodName(@Nullable String var1);
@Nullable
String getInitMethodName();
void setDestroyMethodName(@Nullable String var1);
@Nullable
String getDestroyMethodName();
void setRole(int var1);
int getRole();
void setDescription(@Nullable String var1);
@Nullable
String getDescription();
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
@Nullable
String getResourceDescription();
@Nullable
BeanDefinition getOriginatingBeanDefinition();
}
SpringIOC容器管理了我们定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的,其继承体系如下:
Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过下图中的类完成:
再看看源码,这个便是spring中的bean定义接口,所以其实我们工厂里持有的bean定义,就是一堆这个玩意,或者是他的实现类和子接口。这个接口并非直接的祖宗接口,他所继承的两个接口一个是core下面的AttributeAccessor,继承这个接口就以为这我们的bean定义接口同样具有处理属性的能力,而另外一个是beans下面的BeanMetadataElement,字面翻译这个接口就是bean的元数据元素,它可以获得bean的配置定义的一个元素。在XML文件中来说,就是会持有一个bean标签。
仔细观看,能发现beanDefinition中有两个方法分别是String[] getDependsOn()和void setDependsOn(String[] dependsOn),这两个方法就是获取依赖的beanName和设置依赖的beanName,这样就好办了,只要我们有一个BeanDefinition,就可以完全的产生一个完整的bean实例。
今天就先到这里吧,lz菜的抠脚,也是边看边写的,费了很大劲才梳理出一点东西,更多的是看到哪写到哪,IOC的实现原理用到了反射,我接下来想想怎么写进去。
文中若有欠妥的地方,希望大家指出。