Spring源码详解
什么是Spring?
Spring是一个框架,封装了许多代码可以简化我们的开发,有两个核心,如果搞懂了就相当于把Spring框架给搞明白了,一个是IoC、另一个是AOP。
IoC
什么是IoC?
IoC(Inversion of Control),即控制反转,这是一种设计思想,在Spring指将对象的控制权交给Spring容器,由容器来实现对象的创建、管理,程序员只需要从容器获取想要的对象就可以了。这里引申一个词叫DI(Dependency Injection),即依赖注入,他是IoC的一种具体实现方式,类似于Map和HashMap的关系,指对象创建时的属性赋值交给Spring容器来做。
还有一种理解是把IoC理解为Spring容器,底层是一个Map,key是beanName,value是bean。
为什么需要IoC?
- 不用手动new对象,将对象创建和业务解耦
- 无需关注对象创建的过程,只管使用就行
IoC怎么使用?
声明bean
xml文件
@Component及其衍生注解
@Configuration和@Bean
获取bean
beanFactory.getBean(String beanName)
applicationContext.getBean(String beanName)
属性注入注解方式
@Autowired、@Value
IoC底层实现?
IoC把对象控制权反转给Spring框架,那么我们就来关注一下Spring怎么实现bean的创建、管理等,再详细一些,bean从实例化 -> 属性赋值 -> 初始化 -> 使用 -> 销毁,这叫做bean的生命周期,Spring管理的bean主要走的就是这么个流程。
什么是属性赋值前面已经解释过了,实例化和初始化是怎么回事呢?
- 实例化实际上就是bean的创建,给bean在内存中分配空间;
- 初始化回调各种Aware接口、回调各种初始化方法、生成AOP代理对象也在该阶段进行,该阶段主要是完成初始化回调。
基本概念
接下来会介绍底层源码,在此之前会给出一些基本概念,以便更加清晰地了解源码。
- BeanFactory:bean工厂,用来生成bean,故bean的实例化、属性赋值、初始化都在此进行
- BeanDefinition:bean的定义信息,你想让BeanFactory生产bean,那么自然要有张图纸,这张图纸就是BeanDefinition
- BeanPostProcessor:bean的增强器,用来执行一些增强方法
- BeanFactoryPostProcessor:beanFactory的增强器,用来执行一些增强方法
bean生命周期详解
refresh ()的第11步finishBeanFactoryInitialization(beanFactory);
这里会进行bean的生命周期
通过getBean(beanName);
进行单例bean的初始化,这里有对工厂bean的特殊处理TODO
getBean顾名思义就是获取bean,引出两个问题,去哪里获取?获取不到怎么办?
- Spring里面大量使用缓存,这里存储bean的缓存特指Map,通过beanName为key可以获取到对应的bean;
- 如果本身缓存不存在bean,那么需要去创建bean并将其放到缓存中去。
走从缓存中拿的逻辑
getSingleton(String beanName)
会尝试从缓存中获取bean,值得一提的是这里有三个缓存,俗称三级缓存,为什么要使用三级缓存,等到后面再说。
走bean创建的逻辑doCreateBean
getSingleton, ObjectFactory<?> singletonFactory)
内部会调用createBean()
去创建bean,并且创建后把bean放到缓存中去
接下来就是bean的生命周期了:
- 实例化:一般是反射调用无参构造器创建对象,instanceWrapper = createBeanInstance(beanName, mbd, args);
- 属性赋值:populateBean(beanName, mbd, instanceWrapper);
- 初始化:对bean进行一些额外操作,exposedObject = initializeBean(beanName, exposedObject, mbd);
- 销毁:
实例化详解
前面提到实例化时通过构造器实现,不通过直接new对象的方式,我们该如何创建对象呢?Spring用到了反射去实例化bean,要用反射自然少不了bean对应的Class对象,通过内存中的Class对象我们才能够获取到类的信息(一个对象的模板)从而创建对象。
- 获取bean的Class对象
Class<?> beanClass = resolveBeanClass(mbd, beanName);
- 还可能用工厂去创建bean
- 可能这里会用有参构造器,如果你有编写有参构造器的话
- 然后到是无参构造器构造
return instantiateBean(beanName, mbd);
constructorToUse = clazz.getDeclaredConstructor();
3种实例化方式
除了构造器之外可以使用工厂方法去进行bean的实例化,以下列出他们各自的优缺点
- 构造器:反射使用类构造器
- 优点:使用方便,简单明了
- 缺点:如果创建逻辑复杂,无法定制化配置
- 静态工厂:调用一个静态的方法
- 优点:可以在创建bean的时候获取到一些静态资源
- 缺点:需要提前写好,无法拓展
- 普通工厂:调用一个普通的方法
- 优点:适用于逻辑复杂的创建,如依赖注入或状态管理,如某个依赖需要通过入参判断注入哪个类
- 缺点:相比构造器方式需要多写一个工厂类
总的来说,如果没有特别复杂的创建逻辑需求,直接用构造器创建就好。
策略模式
值得一提的是这里用到了策略模式
Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
下面这张图是继承关系图:顶级策略接口InstantiationStrategy
默认JDK的无参构造器就是在SimpleInstantiationStrategy
类中实现,如果实现CGLIB的方式实例化,在CglibSubclassingInstantiationStrategy
有内部类CglibSubclassCreator
通过instantiate(@Nullable Constructor<?> ctor, Object... args)
实现CGLIB的一个实例化操作
属性赋值详解
对象已经在内存中存在了,但是它的属性还是空的,我们需要对其进行属性赋值,populateBean(beanName, mbd, instanceWrapper);
,可以思考几个问题,值存放在哪里?如何赋值?
这些值是存放在**BeanDefinition的成员变量里面的MutablePropertyValues propertyValues
**,通过这个就可以获取到bean的属性,在xml中是以<property>
标签表示,如果你需要用注解的方式实现,可以自定义注解,搭配自定义BeanFactoryPostProcessor
1 |
|
如何赋值?
setPropertyValues(PropertyValues pvs)
,setPropertyValue(PropertyValue pv)
1 | protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { |
对于@Autowired的注解,通过InstantiationAwareBeanPostProcessor的postProcessProperties()方法进行赋值
1 | if (hasInstantiationAwareBeanPostProcessors()) { |
初始化详解
属性赋值之后,我们还需要对其进行初始化,可以调用初始化方法实现一些逻辑,例如资源分配或者初始化,如Spring Boot的自动配置类,exposedObject = initializeBean(beanName, exposedObject, mbd);
调用用户自己实现的初始化方法invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
- 先调用 @PostConstructor 注解,在BeanPostProcessor前置增强执行
- 再调用实现 InitializingBean 接口的回调方法 afterPropertiesSet()
- 最后调用xml文件的 init-method 方法
初始化增强
在初始化前后,会对bean进行增强
前置增强
一般是一些Aware回调方法执行,如ApplicationContextAware的回调方法的执行,调用BeanPostProcessor增强processor.postProcessBeforeInitialization(result, beanName);
后置增强
这里会和AOP有关了,调用BeanPostProcessor增强,processor.postProcessAfterInitialization(result, beanName)
为什么使用三级缓存?
循环依赖
这个三级缓存其实和属性赋值那一步相关,缓存的是bean,那我们属性赋值的时候有可能是给基本属性赋值、也有可能是给引用属性赋值,在给引用属性赋值的时候我们会调用getBean(),是不是很熟悉,即创建bean的时候会调用的,从某种角度来看很像递归,肯会出现一直调用导致SOF,在Spring表现的就是会出现循环依赖问题,那我们就需要一个终止条件,即用缓存代替创建bean
具体是哪三级缓存?
1 | /** Cache of singleton objects: bean name to bean instance. */ |
为什么用了三级缓存就没有循环依赖?
先看循环依赖的原因:假设有A,B两个类,他们互相依赖
1.实例化A
2.给A的b属性赋值
3.实例化B
4.给B的a属性赋值
5.重复第一步
那用二级缓存能否解决循环依赖呢?
1.实例化A
2.给A的b属性赋值
3.实例化B
4.给B的a属性赋值(注意,此时的a就不会再走到第五步去实例化了,而是从二级缓存中获取完成品A)
5.B的属性赋值完成,接下来回到第二步,即将A的属性赋值完成,自此循环依赖解决
那为什么是三级缓存呢?
想一想如果只有二级缓存,再加上AOP代理,假设A是个代理对象,B是普通的bean,此时B.a是半成品A,而A是个代理对象,这两个不相同,即b.getA() != A a(一级 + 二级)
而A是个单例对象,这就很奇怪了吧。
那你可能又会想那实例化的时候我直接先把代理对象创建出来不久好了吗,不就一样了吗,但是objectFactory每次调用getObject()返回的都是新的代理对象,所以如果某个代理类C被当作很多个类的属性,那么这些类的这个引用属性C还是不相同的(一级 + 三级),所以我们需要三级缓存,有了二级就不用找三级,即代理对象只会被生产一次
以下是三级缓存的流程:
1.实例化A
2.给A的b属性赋值
3.实例化B
4.给B的a属性赋值(注意,此时的a就不会再走到第五步去实例化了,而是从三级缓存中获取工厂A,工厂A再去调用getObject()生成代理bean,把这个代理bean放到二级缓存中去,接下来把这个二级缓存中的代理A赋值给b.a)
5.B的属性赋值完成,接下来回到第二步,即将A的属性赋值完成,自此有代理对象的循环依赖解决
6.最后会把二级缓存的代理A放到一级缓存,即以后beanFactory.getBean(A.class)都是拿到代理A
AOP
什么是AOP?
AOP(Aspect Oriented Programming),即面向切面编程,是OOP(面向对象编程)的补充,OOP是纵向的,就拿一般的请求调用来说,是Controller调Service,Service再调Mapper对吧,这就是纵向的,而AOP是横向的,它针对某一层的方法,比如可以增强Service的方法等等。
为什么需要AOP?
- 能将核心业务逻辑和其它补充功能区分开,利于核心业务逻辑的复用,例如某个核心逻辑和日志,你肯定不希望在核心逻辑内部出现一些无关的东西如一些系统的日志吧
- 能降低模块间的耦合,模块间的依赖关系不用直接参杂在某个模块内部,例如Mapper模块你要操作数据库,为了保证事务的原子性,你要引入事务侵入源代码,那现在我们可以用@Transactional注解代替
AOP怎么用?
我们一般用于日志、事务、安全校验等。
核心概念
首先介绍几个AOP的核心概念:
- JoinPoint连接点:Spring把所有方法都看作连接点
- PonitCut切点:需要被增强的方法
- Advice通知:具体的增强行为
- Aspect切面:通常是一个类,切点和通知的组合,Spring里面用Advisor表示
- Target目标对象:被增强的方法的类的实例化对象
- Weaving织入:将通知与切点整合,在编译时进行织入就是静态代理(代码写死),而在运行时进行织入则是动态代理(JDK、CGLIB)
- Proxy代理:用代理对象执行增强方法,可以用JDK或者CGLIB创建代理对象
简单示例
用@Aspect定义切面类,切点用@Pointcut定义,切点常用表达式类型有execution(匹配方法切入点)、 @annotation(匹配注解切入点),下面的@Before、@Around等等这些注解标注的方法就叫做Advice通知,对于每个通知都可以有选择的加上JoinPoint参数。然后在配置类或者启动类加上@EnableAspectJAutoProxy就可以使用AOP了。
1 |
|
JoinPoint能干嘛?
JoinPoint能拿到什么关于被增强方法的信息,如方法返回值、方法的入参、方法本身等等
1 | Object[] args = joinPoint.getArgs(); // 方法的参数 |
AOP如何实现?
代理bean的创建
代理bean创建时机
一般来说会有两个时候可能创建代理bean:
- 一个当然是我们熟知的初始化方法之后,调用BeanPostProcessor后置处理器进行处理,在IoC部分也有提到;
- 另外一个和三级缓存有关,在给对象属性赋值引用属性的时候会调用getBean(),getBean()会先从缓存中去拿到三级缓存,并且调用其getObject()方法创建代理bean,举个例子,有a,b两个对象,他们相互依赖,先创建a,再创建b,那么在给b.a赋值的时候,就回去三级缓存中找是否有对应的工厂(a在创建的时候,会被先加入第三级缓存),调用其getObject()发现需要代理,就会创建代理的a
如何创建代理bean?
上面提到了我们在查询缓存的时候会尝试从三级缓存获取、或者是从初始化的后置处理器去创建代理bean,前者是ObjectFactory.getBean()方法(其实也是调用了SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()后置处理方法),后者是是调用了AnnotationAwareAspectAutoProxyCreator.postProcessAfterInitialization()的后置处理方法
不管怎样,都是调用了AbstractAutoProxyCreator.wrapIfNecessary(Object bean, String beanName, Object cacheKey)方法
两个方法需要重点关注:
1 | getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, |
getAdvicesAndAdvisorsForBean()
找到所有的bean,遍历找到在@Aspect标注的切面类下的通知方法(Advise),放到缓存之后直接取
createProxy()
那么实际上Spring用到了AOP工厂去判断用什么方式创建代理bean,这里的工厂默认是DefaultAopProxyFactory,根据代理类有没有实现接口返回AopProxy(这是一个委托类,用于创建代理bean),如果实现了接口则返回JdkDynamicAopProxy(config)【默认方式】,如果没有则返回ObjenesisCglibAopProxy(config),在AopProxy.getProxy(@Nullable ClassLoader classLoader)里面用Proxy(对于JDK方式)、或者是Enhancer(对于CGLIB方式)完成配合AdvisedSupport config创建代理bean
方法的增强
如何增强目标方法?
执行链
事务
设计模式
9 种设计模式在 Spring 中的运用,一定要非常熟练! - Java技术干货 - SegmentFault 思否
简单工厂模式
Spring根据传入的参数(beanName或者beanClass)从BeanFactory.getBean()中获取对应的单例bean
单例模式
适配器模式
例如SpringMVC匹配路径,不同请求有着不同的处理器,为了让不同的处理器都能够有一个统一的处理方式,所以需要一个适配器
SimpleControllerHandlerAdapter
:适用于实现了Controller
接口的处理器。RequestMappingHandlerAdapter
:适用于基于注解的控制器方法。HttpRequestHandlerAdapter
:适用于实现了HttpRequestHandler
接口的处理器。首先有个HandlerAdapter适配器接口:
HandlerAdapter
接口用于将各种类型的请求处理器(Handler)适配为统一的处理方式,这里会定义一些统一的处理方法等待子类具体实现
1 | public interface HandlerAdapter { |
- 还有个DispatcherServlet分发器:把对应的请求分配给对应的HandlerAdapter的子类来适配处理
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
装饰器模式
Spring 中用到的包装器模式在类名上有两种表现:一种是类名中含有 Wrapper,另一种是类名中含有 Decorator。
实质:
动态地给一个对象添加一些额外的职责。
就增加功能来说,Decorator 模式相比生成子类更为灵活。