一、Spring相关概念
框架设计原则
- 使用POJO(Plain Old Java Object,普通Java对象)取代EJB(Enterprise Java bean,企业级Java Bean),进行轻量级(1MB的jar包)和最小侵入式开发。
- 通过IoC(Inversion of Control,控制反转)的DI(Dependency Injection,依赖注入),配合基于接口编程实现松耦合。
- 通过AOP(Aspect Oriented Programming,面向切面编程),和默认习惯进行声明式编程。配合模板(template)减少模块化代码,用于事务管理、权限控制、日志记录、性能统计等。
常用jar包
- org.springframework.aop,Spring的面向切面编程,提供AOP(面向切面编程)的实现
- org.springframework.beans,所有应用都用到,包含访问配置文件,创建和管理bean等,是Spring IOC的基础实现。
- org.springframework.context,提供在基础IOC功能上的扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持。
- org.springframework.core,Spring的核心工具包,其他包依赖此包
- org.springframework.expression,Spring表达式语言
- org.springframework.jcl,日志框架
- org.springframework.test,对JUNIT等测试框架的简单封装
- org.springframework.web,包含Web应用开发时,用到Spring框架时所需的核心类。
- org.springframework.webmvc,基于web框架,提供MVC模式支持。
控制反转(IoC,Inversion of Control)
- 控制反转,不是什么技术,而是一种设计思想。它能指导我们如何设计出松耦合、更优良的程序。
- 传统Java程序设计,直接在“对象”内部通过new创建了“依赖对象”,是“对象”主动去创建“依赖对象”,由“对象”直接控制“依赖对象”。
- 在IoC中,有专门容器来协助“对象”创建并注入“依赖对象”,即由专有容器来控制“依赖对象”的创建。“对象”只是被动的接受“依赖对象”,而不是像传统方式一样直接控制“依赖对象”,因此被称为控制反转。
依赖注入(DI,Dependency Injection)
- 和IoC的关系:IoC和依赖注入本质上描述的是同一种概念,但如非要区分,可以认为IoC是一种思想,依赖注入是一种实现方式,即控制反转通过依赖注入来实现。
- 依赖注入指的是容器负责创建对象和维护对象间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。
- 依赖注入是注入某个对象所需要的属性(包括对象、资源、常量数据)。主要为了解耦,体现了一种“组合”的理念,组合则使耦合度大大降低。如果需要某个类具备某项功能的时候,也可以使用继承,但继承一个具有此功能的父类,子类将和父类耦合。
Spring中的IoC
- 对于Spring框架来说,就是由Spring的IoC容器(ApplicationContext)来负责控制对象的生命周期和对象间的关系。
- IoC容器用来帮“对象”查找“依赖对象”、创建“依赖对象”、把“依赖对象”交给“对象”。即IoC容器负责创建Bean,并将功能类Bean注入到你需要的Bean中。
- Spring提供3种元数据配置方式,基于xml配置、基于注解配置和基于Java类配置,来实现Bean的创建和注入。推荐使用Java配置和注解混合配置。全局配置使用Java类配置,业务Bean的配置使用注解配置。
- IoC容器通过解析配置的元数据(如dom4j解析xml,扫描注解),进行Bean初始化、配置和管理依赖,底层原理是利用java的反射来实现。
- 创建对象时,向类里面的属性设置值,可以称为属性注入。方式有三种:有参构造器注入、set方法(包括P名称空间注入)、接口方法注入。Spring支持前两种注入,还可以支持静态工厂和实例工厂的注入。
- 手动装配需要使用property进行设置。针对自动装配,xml使用autowire属性,注解使用@Autowired,xml中autowire属性可设置为no、byName、byType、constructor和Autodetect。
接口多态、工厂模式、spring依赖注入区别
- 接口多态,耦合,更换时需要所有对象创建的地方修改。
- 工厂模式,解耦,只需要修改工厂方法,直接去工厂获取对象。
- spring依赖注入,不需要写工厂方法,由spring自动扫描、获取,实现动态的拆装组建和组件重用。
二、Spring IoC代码实践
实体类,UserDao.java和UserService.java
1 | public class UserDao { |
1 | public class UserService { |
传统实现
1 | public class NormalTest { |
Spring基于xml的配置
xml-bean.xml文件
1 |
|
使用Spring的xml配置方式,可以对比上方的传统方式,便于理解IoC。
1 | //com.herr.xml为使用xml配置方式,由XMLTest.java进行测试。 |
Spring基于注解的配置
1、声明Bean的注解,以下方式是等效的。
- @Component,没有明确角色。
- @Controller,在展现层/控制层(web层)使用。@RestController,相当于@Controller和@ResponseBody的组合
- @Service,在业务逻辑层(service层)使用
- @Repository,在数据访问层/持久层(dao层)使用。
2、注入Bean的注解,可注解在set方法或者属性上(优先属性)。
- @Autowired,为Spring提供的注解,默认使用type进行匹配,如果有匹配到多个,会根据name来查找,如果匹配为空,会抛出异常,可使用required=false来处理。如果需要按name进行装配,则需要配合@Qualifier。
- @Resource,@Inject,为JSR提供的注解,默认使用name进行匹配,如果没有匹配到name,会继续按type。
annotation-bean.xml文件,只用来配置注解扫描
1 |
|
实体类,PersonDao和PersonService
1 | package com.herr.annotation; |
1 | package com.herr.annotation; |
测试Bean创建和注入
1 | //com.herr.annotation为使用注解配置方式,由AnnotationTest.java进行测试。 |
基于Java类配置
可通过@Configuration和@Bean来实现。@Configuration声明当前类是一个配置类,相当于一个xml文件。@Bean注解在方法上,声明当前方法的返回值是一个Bean。
配置类,JavaConfig、JavaImportConfig、MixConfig
1 |
|
1 |
|
以上java配置中的userDao方法,等同于在xml的配置。
1 | <beans> |
1 |
|
基于注解和基于java类的混合配置
1 | // com.herr.mix为使用java类和注解混合方式配置,由MixTest.java进行测试。 |
三、AOP相关概念
相关概念
- 切面(aspect),类是对物体特征的抽象,切面就是对横切关注点的抽象,抽象成一个类。
- 横切关注点,对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
- 连接点(joinpoint),是在应用执行过程中能够插入切面的一个点,是一个虚拟的概念。因为Spring基于动态代理,所以连接点只能是方法,实际上连接点还可以是字段或者构造器。
- 切点(pointcut),对连接点进行拦截的规则,可基于注解或基于方法规则。
- 建言(advice,也叫通知),所谓建言指的就是指拦截到连接点之后要执行的代码,分为前置、后置、环绕、异常、最终五类。
- 目标对象,代理的目标对象
- 织入(weave),将切面应用到目标对象并导致代理对象创建的过程
- 引入(introduction),在不修改代码的前提下,引入可以在运行期,为类动态的添加一些方法或字段。
面向切面编程(AOP,Aspect Oriented Programming)
- AOP是面向对象编程(OOP)的延续。这种采用切面进行横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。
- AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,而一些公用的权限认证、日志、事务则是横切关注点,他分布在核心关注点的各处。AOP的作用在于将系统中的核心关注点和横切关注点分离开来,即把业务逻辑和系统服务分开。
- Spring IOC用于解决对象依赖之间的解耦,而Spring AOP则用于解决业务依赖之间的解耦。AOP可以剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,便于减少系统的重复代码。
- Spring中的AOP是基于动态代理实现的,即JDK动态代理和Cglib动态代理。对目标对象,动态创建代理对象,在不修改目标对象代码情况下,实现功能增强。如需要了解动态代理,可参考《Java基础》里的“动态代理”章节。
AOP应用场景
- Authentication,权限、安全
- Caching,缓存
- Context passing,内容传递
- Error handling,错误处理
- Lazy loading,懒加载
- Debugging,调试
- logging tracing profiling and monitoring,日志的记录跟踪 优化 校准
- Performance optimization,性能优化
- Persistence,持久化
- Resource pooling,资源池
- Synchronization,同步
- Transactions,事务
AspectJ
- AspectJ是一个面向切面的框架,基于静态代理,在编译时将Aspect织入Java字节码,运行的时候就是经过增强之后的AOP对象。
- AspectJ的拦截方式分为基于注解拦截和基于方法规则拦截两种,Spring本身在事务处理(@Transactional)和数据缓存(@Cacheable)上有使用注解拦截。
- 定义连接点,使用@interface定义一个注解,符合条件的每一个被拦截处为连接点,在连接点的方法前使用该注解。
- 定义切面,使用@Aspect声明某个类是一个切面。需要使用@EnableAspectJAutoProxy允许AspectJ。
- 可定义切点,使用@Pointcut定义,可基于注解(使用@annotation)或方法(使用execution)。使用@PointCut专门定义拦截规则,是为了在各种建言中复用切点。当然也可以不用抽象,直接在建言中创建。
- 定义建言,使用@After、@Before、@Around定义建言(Advice),可直接将拦截规则(切点)作为参数。
四、Spring AOP代码实践
1、pom.xml
1 | <dependencies> |
2、Action.java,基于注解定义切片时使用
1 | package com.herr.aop; |
3、AnnotationService.java,业务代码,通过注解设置连接点
1 | package com.herr.aop; |
4、MethodService.java,业务代码,连接点没有特殊标记,可直接基于方法拦截
1 | package com.herr.aop; |
5、AopConfig.java,AOP配置
1 | package com.herr.aop; |
6、LogAspect.java,定义切面
1 | package com.herr.aop; |
7、AopTest.java
1 | package com.herr.aop; |
四、Spring IoC源码解析
BeanFactory
- BeanFactory,以Factory结尾,表示它是一个工厂类(接口),它负责生产和管理bean的一个工厂。
- 在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
- BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、ApplicationContext等。
- ListableBeanFactory,被ApplicationContext继承。可以获取多个Bean,而顶层的BeanFactory是获取单个Bean的。
- HierarchicalBeanFactory,在应用中起多个BeanFactory,然后可以将各个BeanFactory设置为层级关系。被ApplicationContext继承。
- AutowireCapableBeanFactory,用于自动装配Bean。ApplicationContext使用了组合。
- ConfigurableListableBeanFactory也是一个特殊的接口,它继承了第二层所有的3个接口,而ApplicationContext 没有。
- DefaultListableBeanFactory,是一个比较通用的 BeanFactory 实现,它同时也实现了 BeanDefinitionRegistry 接口,因此它就承担了 Bean 的注册管理工作。
- FactoryBean,这个类和BeanFactory容易混淆,是个工厂类接口,用户通过实现该接口来单独定制实例化bean,而不用使用传统的BeanFactory流程。
ApplicationContext
ApplicationContext,是spring继BeanFactory之外的另一个核心接口或容器,允许容器通过应用程序上下文环境创建、获取、管理bean。为应用程序提供配置的中央接口。一个ApplicationContext提供:
- 访问应用程序组件的Bean工厂方法。从org.springframework.beans.factory.ListableBeanFactory继承。
- 以通用方式加载文件资源的能力。继承自org.springframe.core.io。ResourceLoader接口。
- 向注册侦听器发布事件的能力。继承自ApplicationEventPublisher接口。
- 解析消息的能力,支持国际化。继承自MessageSource接口。
- ClassPathXmlApplicationContext,根据ClassPath中的xml构建上下文。
- FileSystemXmlApplicationContext,根据文件系统中的xml配置构建。
- AnnotationConfigApplicationContext,是基于注解来构建,它不需要配置文件,采用java配置类和各种注解来配置。
BeanDefinition
我们定义的各个 Bean ,会转换成一个个 BeanDefinition 存在于 Spring 的 BeanFactory 中(一般在DefaultListableBeanFactory类的beanDefinitionMap属性)。BeanDefinition 中保存了 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、依赖了哪些 Bean 等等。
BeanDefinitionRegistry 接口则包含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition 等注册管理 BeanDefinition 的方法。
1 | public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { |
核心源码
版本是5.2.9,以ClassPathXmlApplicationContext举例,相关代码如下,使用了ClassPathXmlApplicationContext进行初始化。
1 |
|
ClassPathXmlApplicationContext类中进行了refresh操作,销毁原来的ApplicationContext,重新执行初始化操作。
1 | public ClassPathXmlApplicationContext(String configLocation) throws BeansException { |
调用AbstractApplicationContext类进行refresh操作,核心方法
1 | public void refresh() throws BeansException, IllegalStateException { |
obtainFreshBeanFactory方法会初始化 BeanFactory、加载 Bean、注册 Bean 等等。
1 | protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { |
AbstractRefreshableApplicationContext的refreshBeanFactory方法。
DefaultListableBeanFactory实现了ConfigurableListableBeanFactory接口,而ConfigurableListableBeanFactory接口继承了BeanFactory下面一层的所有三个接口。DefaultListableBeanFactory是最高实现类,基本包含了BeanFactory相关的所有操作。
1 | protected final void refreshBeanFactory() throws BeansException { |
AbstractXmlApplicationContext类,使用XmlBeanDefinitionReader负责加载配置、解析。以下步骤较多,均为解析xml文件,加载Bean。
1 | protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { |
以上的流程为根据xml解析
下面开始Bean的注册。还是从刚才DefaultBeanDefinitionDocumentReader类的processBeanDefinition方法开始
1 | //Bean处理 |
来到BeanDefinitionReaderUtils类
1 | public static void registerBeanDefinition( |
DefaultListableBeanFactory实现了BeanDefinitionRegistry的接口。它初始化了 Bean 容器,
1 |
|
回到AbstractApplicationContext类的refresh方法,上面的代码都是执行的obtainFreshBeanFactory方法,
1 | public void refresh() throws BeansException, IllegalStateException { |
prepareBeanFactory用来准备Bean容器。
1 | protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { |
finishBeanFactoryInitialization用来初始化所有的 singleton beans。十分重要。
到目前为止,BeanFactory 已经创建完成,并且所有的实现了 BeanFactoryPostProcessor 接口的 Bean 都已经初始化并且其中的 postProcessBeanFactory(factory) 方法已经得到执行了。所有实现了 BeanPostProcessor 接口的 Bean 也都完成了初始化。剩下的就是初始化其他还没被初始化的 singleton beans 了,我们知道它们是单例的,如果没有设置懒加载,那么 Spring 会在接下来初始化所有的 singleton beans。
1 | protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { |
回到了DefaultListableBeanFactory这个类。
1 |
|
进入到getBean方法,跳转到AbstractBeanFactory类
1 |
|
AbstractAutowireCapableBeanFactory类有createBean的实现。
1 |
|
创建 Bean 实例的createBeanInstance方法
1 | protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, { Object[] args) |
Bean属性注入的populateBean方法,仍然在AbstractAutowireCapableBeanFactory类中。
1 | protected void populateBean(String beanName, RootBeanDefinition mbd, { BeanWrapper bw) |
处理Bean各种回调的initializeBean方法,仍然在AbstractAutowireCapableBeanFactory类中。
1 | protected Object initializeBean(String beanName, Object bean, { RootBeanDefinition mbd) |
五、Spring Bean生命周期
Spring Bean的完整生命周期是个很重要的概念,从创建Spring容器开始,直到最终Spring容器销毁Bean。
Bean的完整生命周期经历了各种方法调用,可用来实现Spring的拓展机制。这些方法可以划分为以下几类:
- Bean自身的方法,这个包括了Bean本身调用的方法和通过bean配置文件中init-method和destroy-method指定的方法
- Bean级生命周期接口方法,这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法
- 容器级生命周期接口方法,这个包括了InstantiationAwareBeanPostProcessor、BeanPostProcessor和FactoryPostProcessor等接口
- 工厂后处理器接口方法,这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等非常有用的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
BeanFactoryPostProcessor和BeanPostProcessor自定义
基础数据:
1 | public class ProcessorDao { |
1 |
|
配置类:
1 |
|
自定义BeanFactoryPostProcessor,实现BeanFactory初始化后的扩展
1 |
|
自定义BeanPostProcessor,实现Bean初始化前后的扩展
1 |
|
六、本节代码
https://zhuanlan.zhihu.com/p/78104880 给你一份Spring Boot核心知识清单
https://zhuanlan.zhihu.com/p/368769721 面试题系列:Spring 夺命连环10问
https://www.zhihu.com/question/30212464/answer/1786967139 Spring 拦截器和过滤器的区别?
https://zhuanlan.zhihu.com/p/33296468 如何零基础搭建一套微服务框架(Spring Boot + Dubbo + Docker + Jenkins)
https://mp.weixin.qq.com/s/NfQ01aS6kDHczbRUJD1Y0w SpringBoot分片上传、断点续传、大文件极速秒传功能,这篇都帮你搞定!(典藏版)