Spring相关知识
文章目录
- Spring相关知识
- BeanFactory 与ApplicationContext 是干什么的,两者的区别
- Spring IOC容器如何实现
- Spring DI(依赖注入)的实现
- Spring如何解决循环依赖问题
- Spring Bean 生命周期
- Spring Bean的作用域,默认是哪个?
- AOP两种代理方式
- Spring AOP实现原理
- 哪些方法不能被AOP增强
- AOP 切点函数
- 六种增强类型
- Spring MVC运行流程
- Spring MVC 启动流程
- Spring 事务实现方式、事务的传播机制、默认的事务类别·
- Spring 事务模版
- Spring 事务底层原理
- Spring事务失效(事务嵌套), JDK动态代理给Spring事务埋下的坑
- Spring 单例实现原理
- Spring 中有哪些不同类型的事件
- 缓存的一些策略有哪几种类型
- Spring Cache 注解
- Spring BeanUtils bean拷贝工具用过吗?它是浅拷贝还是深拷贝?怎么实现的?有没有什么坑?其他还有什么bean 拷贝的方法,是浅拷贝还是深拷贝?如何实现深拷贝?
《精通Spring 4.x 企业应用开发实战》、《Spring技术内幕:深入Spring架构与设计原理》、《Spring源码深度解析》
BeanFactory 与ApplicationContext 是干什么的,两者的区别
BeanFactory、ApplicationContext都代表容器,BeanFactory是一个基础接口,实现了容器基础的功能,ApplicationContext是容器的高级形态,增加了许多了特性,顶级父类是BeanFactory。
跟FactoryBean的区别是:
FactoryBean 是一个Bean,用于生产其他的Bean实例
BeanFactory 是一个工厂
Spring IOC容器如何实现
Spring提供了各种各样的容器,有DefaultListableBeanFactory、FileSystemXmlApplicationContext等,这些容器都是基于BeanFactory,Spring通过refresh()
方法对容器进行初始化和资源的载入,其中Spring通过定义BeanDefinition来管理应用的各种对象及依赖关系,其是容器实现依赖反转功能的核心数据结构,容器解析得到BeanDefinition后,需要在容器中注册,这由IOC实现BeanDefinitionRegistry接口来实现,注册过程是IOC容器内部维护了一个ConcurrentHasmap来保存得到的BeanDefinition。
Spring DI(依赖注入)的实现
Spring 的依赖注入发生在以下两种情况:
- 用户第一次调用
getBean()
方法 - bean配置了lazy-init=false,容器会在解析注册Bean定义的时候进行预实例化,触发依赖注入
getBean()方法定义在BeanFactory接口中,具体实现在子类AbstractBeanFactory中,过程如下:
-
getBean()方法最终是委托给doGetBean方法来实例化Bean,doGetBean方法会先从缓存中找是否有创建过,没有再从父工厂中去查找
-
如果父工厂中没有找到,会根据Bean定义的模式来创建Bean,单例模式的Bean会先从缓存中查找,确保只创建一次,原型模式的Bean每次都会创建,其他模式根据配置的不同生命周期来选择合适的方法创建。创建的具体方法通过匿名类中getObject,并委托给createBean来完成bean的实例化。
-
在createBean中,先对Bean进行一些准备工作,然后会应用配置的前后处理器,如果创建成功就直接返回该代理Bean
-
没有创建代理Bean的话,会创建指定的Bean实例,委托给doCreateBean完成,该过程会通过提前实例化依赖Bean,并写入缓存来解决Bean的循环依赖
-
通过populateBean注入Bean属性,并调用init-method初始化方法
-
注册实例化的Bean
Spring如何解决循环依赖问题
比如A依赖B, B依赖A.
创建A的时候,会把A对应的ObjectFactory放入缓存中,当注入的时候发现需要B, 就会去调用B对象,B对象会先从singletonObjects 查找,没有再从earlySingletonObjects找,还没有就会调用singletonFactory创建对象B,B对象也是先从singletonObjects,earlySingletonObjects,singletonFactories三个缓存中搜索,只要找到就返回,相关方法AbstractBeanFactory.doGetBean()
Spring Bean 生命周期
- Bean实例的创建
- 为Bean实例设置属性
- 调用Bean的初始化方法
- 应用可以通过IOC容器使用Bean
- 当容器关闭时,调用Bean的销毁方法
Spring Bean的作用域,默认是哪个?
Singleton: 单例模式,IOC容器中只会存在一个共享的Bean实例,是Spring的默认模式;
prototype: 原型模式,每次从IOC容器获取Bean的时候,都会创建一个新的Bean实例;
request: 每次请求都生成一个实例;
session: 在一次请求会话中,容器返回该Bean的同一实例,不同的Session请求不同的实例,实例仅在该Session内有效,请求结束,则实例销毁;
globalsession: 全局的session中,容器返回该Bean的同一个实例,仅在portlet context 有效
AOP两种代理方式
AOP面向切面编程,可以通过预编译和运行时动态代理,实现在不修改源代码的情况下给程序动态添加功能。
程序入口是:AbstractAutowireCapableBeanFactory -> doCreateBean -> initializeBean -> applyBeanPostProcessorsAfterInitialization
默认策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理
-
JDK 动态代理
主要涉及java.lang.reflect中的两个类,Proxy 和 InvocationHandler
InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编辑在一起。只能为实现接口的类创建代理。
-
Cglib 代理
是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,Cglib封装了asm,可以在运行期动态生成新的class。可以是普通类,也可以是实现接口的类
Spring AOP实现原理
通过JDK代理,和CGLIB代理两种方式生成动态代理,构造不同的回调方法来对拦截器链的调用,比如JdkDynamicAopProxy的invoke方法,Cglib2AopProxy中的DynamicAdvisedInterceptor的intercept方法,首先获取配置的拦截器链,通过ReflectiveMethodInvocation的proceed方法实现对拦截器链的调用, 首先需要根据配置来对拦截器进行匹配,匹配成功后,拦截器发挥作用,在对拦截器调用完成后,再对目标对象的方法调用,这样一个普通的Java对象的功能就得到了增强
哪些方法不能被AOP增强
- 基于JDK代理,除public外的其他所有方法,包括public static也不能被增强
- 基于CGLIB代理,由于其通过生成目标类子类的方式来增强,因此不能被子类继承的方法都不能被增强,private、static、final 方法
AOP 切点函数
类别 | 函数 | 入参 | 说明 |
---|---|---|---|
方法切入点函数 | execution() | 方法匹配模式串 | 满足某一匹配模式的所有目标类方法连接点。 如execution(* greetTo(…)) 表示所有目标类中的greetTo()方法 |
@annotation() | 方法注解类名 | 标注了特定注解的目标类方法连接点。 如@annotation(com.smart.anno.NeedTest)表示任何标注了@NeedTest注解的目标类方法 | |
方法入参切入点函数 | args() | 类名 | 通过判断目标类方法运行时入参对象的类型定义指定连接点。 如args(com.smart.Waiter)表示所有有且仅有一个按类型匹配于Waiter入参的方法 |
@args() | 类型注解类名 | 通过判断目标类方法运行时入参对象的类是否标注特定注解来指定连接点。 如@args(com.smart.Monitorable)表示任何这样的一个目标方法:它有一个入参且 入参对象的类 标注@Monitorable注解 | |
目标类切点函数 | within() | 类名匹配串 | 表示特定域下的所有连接点。 如within(com.smart.service.*) 表示com.smart.service 包中的所有连接点,即包中所有类的所有方法; 而within(com.smart.service.*Service)表示在com.smart.service包中所有以Service结尾的类的所有连接点 |
target() | 类名 | 假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点 如通过target(com.smart.Waiter),Waiter及Waiter实现类NaiveWaiter中的所有连接点都匹配该切点 | |
@within() | 类型注解类名 | 假如目标类型按类型匹配于某个类A, 且类A标注了特定注解,则目标类的所有连接点匹配该切点 如@within(com.smart.Monitorable) 假如Waiter类标注了@Monitorable注解,则Waiter的所有连接点都匹配该切点, 说是这个注解也会匹配Waiter的子类,但试了后并没有用,Spring 5.1 | |
@target | 类型注解类名 | 假如目标类标注了特定注解,则目标类的所有连接点都匹配该切点。 如@target(com.smart.Monitorable),假如NaiveWaiter标注了@Monitorable,则NaiveWaiter的所有连接点都匹配这个切点 | |
代理类切点函数 | this() | 类名 | 代理类按类型匹配于指定类,则被代理的目标类的所有连接点都匹配该切点。 如this(com.smart.Seller) 匹配任何运行期对象为Seller类型的类 |
六种增强类型
- @Before 前置增强,相当于BeforeAdvice
- @AfterReturning 后置增强,相当于AfterReturningAdvice
- @Around 环绕增强,相当于MethodInterceptor
- @AfterThrowing 抛出增强,相当于ThrowsAdvice
- @AfterFinal增强,不管抛出异常还是正常退出,都会执行,没有对应的增强接口,一般用于释放资源
- @DeclareParents 引介增强,相当于IntroductionInterceptor
Spring MVC运行流程

- 客户端请求到DispatcherServlet
- DispatcherServlet根据请求地址查询映射处理器HandleMapping,获取Handler
- 请求HandlerAdatper执行Handler
- 执行相应的Controller方法,执行完毕返回ModelAndView
- 通过ViewResolver解析视图,返回View
- 渲染视图,将Model数据转换为Response响应
- 将结果返回给客户端
2,3 两步都在DispatcherServlet -> doDispatch中进行处理
Spring MVC 启动流程
- 在Tomcat启动的时候,ServletContext 会根据web.xml加载ContextLoaderListener,继而通过ContextLoaderListener 载入IOC容器,具体过程有ContextLoader完成,这个IOC容器是在Web环境下使用的WebApplicationContext, 这个容器在后面的DispatcherServlet中作为双亲根上下文来使用
- IOC容器加载完成后,开始加载DIspatcherServlet,这是Spring MVC的核心,由
HttpServletBean -> initServeltBean
启动(HttpServletBean是DispatcherServlet的父类,HttpServletBean继承了HttpServlet),最终调用DispatcherServlet -> initStrategies
方法对HandlerMapping、ViewResolver等进行初始化,至此,DispatcherServelt就初始化完成了,它持有一个第一步完成的上下文作为根上下文,以自己的Servlet名称命名的IOC容器,这个容器是一个WebApplicationContext对象。
Spring 事务实现方式、事务的传播机制、默认的事务类别·
-
事务实现方式
- 声明式,在xml文件中通过tx:advice来配置事务
- 注解式,在xml文件中定一个事务管理对象(DataSourceTransactionManager),然后加入<tx:annotation-driven/>, 这样就可以使用@Transactional注解配置事务
-
事务的传播机制
一共7种事务传播行为,相关code:
AbstractPlatformTransactionManager -> getTransaction
-
PROPAGATION_REQUIRED
如果当前没有事务,则新建一个事务;如果已经存在一个事务,则加入到这个事务中,这也是默认事务类别
-
PROPAGATION_SUPPORTS
支持当前事务。如果当前没有事务,则以非事务方式执行
-
PROPAGATION_MANDATORY
使用当前事务。如果当前没有事务,则抛出异常
-
PROPAGATION_REQUIRES_NEW
新建事务。如果当前存在事务,则把当前事务挂起
-
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作。如果当前存在事务,则把当前事务挂起
-
PROPAGATION_NEVER
以非事务方式执行。如果当前存在事务,则抛出异常
-
PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
-
Spring 事务模版
TransactionTemplate 事务模版是对原始事务管理方式的封装,原始事务管理是基于TransactionDefinition
、PlatformTransactionManager
、TransactionStatus
的编程式事务
事务模版主要通过execute(TransactionCallback action)来执行事务,TransactionCallback 有两种方式一种是有返回值TransactionCallback,一种是没有返回值TransactionCallbackWithoutResult。
Spring 事务底层原理
-
事务的准备
在声明式事务处理中,需要Ioc容器配置TransactionProxyFactoryBean,其父类AbstractSingletonProxyFactoryBean实现了InitializingBeean接口,因此在初始化过程中会调用afterPropertiesSet方法,这个方法实例化了ProxyFactory, 并为其设置了通知,目标对象后,最终返回Proxy代理对象,对象建立起来后,在调用其代理方法的时候,会调用相应的TransactionInterceptor拦截器,在这个调用中,会根据TransactionAttribute配置的事务属性进行配置,为事务处理做好准备
-
事务拦截器实现
经过TransactionProxyFactoryBean的AOP包装后,此时如果对目标对象进行方法调用,实际上起作用的是一个Proxy代理对象,拦截器会拦截其中的事务处理,在调用Proxy对象的代理方法时会触发invoke回调,其中会根据事务属性配置决定具体用哪一个PlatformTransactionManager来完成事务操作
Spring事务失效(事务嵌套), JDK动态代理给Spring事务埋下的坑
https://blog.youkuaiyun.com/bntx2jsqfehy7/article/details/79040349
Spring 单例实现原理
在创建Bean的时候AbstractAutowireCapableBeanFactory -> doCreateBea0
通过BeanDefinition 设置的是否单例属性,来判断该bean是否是单例,如果是单例就 根据Bean的名称删除bean缓存中同名称的bean,再在后面重新创建bean.
Spring 中有哪些不同类型的事件
对于ApplicationEvent 类和在ApplicationContext接口中处理的事件,如果一个Bean实现了ApplicationListener接口,但一个ApplicationEvent发布后,Bean会自动被通知
public class AllApplicationEventListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
}
}
Spring 提供了5种标准的事件:
- 上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在ConfigurableApplicationContext 接口中的refresh()方法时触发
- 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始或重新开始容器时触发该事件
- 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件
- 上下文关闭事件(ContextCloseEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁
- 请求处理事件(RequestHandledEvent):在Web应用中,当一个Http请求(Request)结束时触发该事件
- 自定义事件继承ApplicationEvent, 在通过ApplicationContext 接口的publishEvent()方法发布事件
缓存的一些策略有哪几种类型
常见的有FIFO、LRU、LFU、TTL、TTI
FIFO:先进先出策略,先放入缓存的数据先被移除
LRU:最久未使用策略,即使用时间距离现在最久的那个数据被移除
LFU:最近最少使用策略,即一定时间段内使用次数(频率)最少的那个数据被移除
TTL:存活期,即从缓存中创建时间点开始直至到期的一个时间段(不管这个时间段内有没有访问都将过期)
TTI:空闲期,即一个数据多久没被访问就从缓存中移除的时间
Spring Cache 注解
注解 | 用法 |
---|---|
@Cacheable | 先查询缓存,如果没有执行方法并缓存结果,用于取数据 |
@CachePut | 先执行方法,然后将返回值放入缓存,用于更新数据 |
@CacheEvict | 删除缓存,用于删除数据 |
@Caching | 基于前3者的注解数组,多用于一个类有多种实现的情况 |
@CacheConfig | 全局缓存注解,用于类上 |
缓存管理器
- SimpleCacheManager 可以配置缓存列表名称,基本的缓存管理器
- NoOpCacheManager 不缓存任何数据,用于测试
- ConcurrentMapCacheManager 不用配置缓存列表,自动生成缓存ConcurrentMapCache
- CompositeCacheManager 可以将不同的缓存管理器组合在一起,不同的缓存使用不同的缓存管理器,并且可以通过fallbackToNoOpCache属性回到NoOpCacheManager
Spring BeanUtils bean拷贝工具用过吗?它是浅拷贝还是深拷贝?怎么实现的?有没有什么坑?其他还有什么bean 拷贝的方法,是浅拷贝还是深拷贝?如何实现深拷贝?
BeanUtils是通过java反射机制来实现从源Bean将属性的值赋值到目标Bean中,两个Bean是不同的对象,但如果Bean中的属性是引用对象,那么copy的只是引用地址。其他的CGLIB的BeanCopier,Apache的BeanUtils都不是真正的深拷贝。
要实现真正的深度copy,需要将bean转化成json,再将json转化成目标bean。或者使用流的方法,将源bean转化成流,再从流读取bean。
public class User implements Serializable {
private int id;
private String userName;
public User myCopy() {
User copy = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
copy = (User)ois.readObject();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}