目录
- 1. 什么是IOC和DI
- 2. 谈谈对Spring的AOP的理解
- 3. Spring、SpringMvc常用注解
- 4. 说说SpringMvc的执行流程?(非常重要)
- 5. 讲一讲Bean的生命周期(非常重要)
- 6. Spring是如何解决循环依赖的(难)
- 7. Spring中Bean的一级缓存、二级缓存、三级缓存
- 8. Bean的作用域
- 9. Spring框架中都用到了哪些设计模式
- 10. Spring事务
1. 什么是IOC和DI
1. IOC 控制翻转
1. 定义
将创建对象的方式交给Spring来管理,应用程序需要的时候直接向Spring拿,而不是自己采用new的方式创建
2. 例子
对象a 依赖了对象 b,当对象 a 需要使用 对象 b的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b的时候, 我们可以指定 IOC 容器去创建一个对象b注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权反转了,这就是控制反转名字的由来。
2. DI 依赖注入
动态地向对象中注入它所需要的其他对象,原理是通过反射
注入的方式,setter方法、构造器注入、注解自动注入(@Autowired、@Resource、@Value)
2. 谈谈对Spring的AOP的理解
1. 要素
1. 切面@Aspect
@Aspect切面,切面类的注解,代表是一个切面类
2. 切入点@PointCut
@PointCut切入点,指定哪些注解(一般是自定义注解用@interface)会进入这个切面
3. 连接点JoinPoint
JoinPoint,连接点,程序进入切面的点
4. Advice通知
1. 前置通知@Before
@Before在方法执行前需要的切面操作
2. 后置通知@After
@After在方法执行完成后需要的切面操作
3. 环绕通知@Around(常用)
@Around两者都可以有,在方法调用前后完成自定义行为
2. 作用(解耦)
解耦、提高代码的复用率、开发者只需要注重纵向业务
3. 原理(代理)
1. 静态代理
AspectJ AOP使用的代理,编译时会生成AOP代理类,合并到Java字节码中(相当于帮我们改代码了)
2. 动态代理
Spring AOP使用的代理,如果被代理的实现了接口,就会采用的是jdk,其他情况采用的是cglib
1. jdk动态代理(Proxy类、InvocationHandler接口)
核心是Proxy类和InvocationHandler接口
步骤如下:
- Proxy类来动态创建目标类的代理类
- 生成的代理类调用真实对象的方法时,InvocationHandler调用invoke()反射来调用目标方法,将切面和业务逻辑合并在一起
1. Proxy的作用
创建代理类,通过代理类调用目标方法
2. InvocationHandler的作用
反射调用invoke方法,将代码整合到一起
2. cglib动态代理
cglib是一个优秀的代码生成类库,采用继承的方式,生成一个被代理对象的子类来作为代理
3. 问:AspectJ AOP和Spring AOP的区别
Spring AOP 基于代理(Proxying),而 AspectJ AOP基于字节码操作(Bytecode Manipulation)。
4. 问:AOP在Bean的生命周期
1. @EnableAspectJAutoProxy
@EnableAspectJAutoProxy注解实际上就是向容器中注册了一个AnnotationAwareAspectJAutoProxyCreator的BeanDefinition,这个类本身就是一个后置处理器,AOP代理就是由它在这一步完成的
2. postProcessBeforeInstantiation方法执行
执行AnnotationAwareAspectJAutoProxyCreator的postProcessBeforeInstantiation方法,实际上就是父类AbstractAutoProxyCreator的postProcessBeforeInstantiation被执行
注意:Instantiation是实例化;Initialization是初始化
3. postProcessAfterInitialization方法执行(AOP代理创建时机)
实际上也是执行父类AbstractAutoProxyCreator中的方法
4. wrapIfNecessary方法执行
对需要进行代理的Bean完成AOP代理的创建
图片来源:https://blog.csdn.net/qq_41907991/article/details/107141101
3. Spring、SpringMvc常用注解
1. Spring
1. 声明bean注解(4个)
@Controller、@Component、@Service、@Repository
2. 自动注入(@Autowired、@Resource)
@Autowired、@Resource、@Qualifier、@Value
1. 题外话 @Autowired、@Resource的区别
@Autowired按照类型(ByType)注入,存在多个的时候用@Qualifier制定具体的名称
@Resource按照名称(ByName)注入
3. 配置相关(@Configuration、@Bean)
@Configuration、@Bean、@ComponentScan(扫描Bean)
4. AOP相关
@Aspect、@PointCut、@Before、@After、@Around
5. 异步相关(@EnableAync、@Async)
@EnableAync、@Async
2. SpringMvc
1. 请求相关
@RequestMapping:映射请求url
@RequestParam:获取请求路径后面的参数值
@RequestBody:获取请求体上的参数值
@ResponseBody:接口返回以json的形式
2. 异常处理相关(@ControllerAdvice、@ExceptionHandler)
@ControllerAdvice:定义一个异常类
@ExceptionHandler:用于全局处理控制器里的异常
4. 说说SpringMvc的执行流程?(非常重要)
DispatcherServlet、HandlerMapping、Handler、HandlerAdapter、视图解析器
5. 讲一讲Bean的生命周期(非常重要)
四个步骤+各种Aware
1. 源码如下
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 1. 实例化阶段!
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 2. 属性赋值阶段!
populateBean(beanName, mbd, instanceWrapper);
// 3. 初始化阶段!
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
2. 步骤
主要流程:实例化、属性赋值、初始化、销毁
1. 实例化(构造器)
通过构造器实例化,选择最佳构造方法
注意:实例化结束后对外暴露的是ObjectFactory(用于获取Bean的工厂类)而不是实例化对象本身,可以提供getObject() 来获取Bean实例。
2. 属性赋值(setter方法)
使用setter方法对属性进行赋值操作
3. 初始化
4.销毁(调用destroy)
实现DisposableBean接口调用destroy方法
3. 扩展点
1. 影响多个Bean(2个BeanPostProcessor)
1. InstantiationAwareBeanPostProcessor(Aware相关的Bean加工器)
初始化Aware相关的Bean
1. 作用范围(实例化阶段前后)
实例化阶段前后
2. BeanPostProcessor(后置处理器)
1. 作用范围(初始化阶段前后)
作用于初始化阶段的前后,初始化前调用postProcessBeforeInitialization,初始化之后调用postProcessAfterInitialization
2. 题外话:Aop代理被创建的是什么时候?
BeanPostProcessor调用postProcessAfterInitialization方法创建的
2. 题外话:BeanFactoryPostProcessor和BeanPostProcessor的区别
1. 相同点(都是扩展点)
两者都是对Spring创建Bean提供的扩展点
2. 不同点(BeanFactoryPostProcessor执行早一点)
BeanFactoryPostProcessor早点,只有1方法,在Bean实例化之前执行的
BeanPostProcessor晚点,有2个方法,分别是在Bean初始化前后执行的
2. 影响单个Bean(各种Aware)
1. 调用各种Aware
1. Aware的调用时机(初始化之前)
所有的Aware方法都是在初始化阶段之前调用的
1. xxxAware的作用
帮助我们拿到Spring容器的xxx资源
2. 有哪些Aware(Bean名称、类加载器、工厂类)
- BeanNameAware:获取BeanName
- BeanClassLoaderAware:获取ClassLoader对象的实例
- BeanFactoryAware:获取BeanFactory对象的实例
参考:https://www.jianshu.com/p/1dec08d290c1.
6. Spring是如何解决循环依赖的(难)
1. 解决方案(setter注入)
用setter方法注入(非构造方法注入),且对于单例模式下的Bean
注意:如果是构造器注入引起的循环依赖,直接启动不了,无解
2. 关键词
Bean的生命周期(实例化和属性赋值分开)、ObjectFactory、三级缓存
2. Spring解决循环依赖的过程
1. 现象
ClassA引用ClassB,ClassB中又引用了ClassA。
2. 解决步骤
-
A的实例化,Spring选择合适的构造器实例化A,并把A的ObjectFactory放入到三级缓存中
-
A的属性赋值,赋值的过程中,发现成员变量B为被实例化,接着会去执行B的生命周期
-
B的实例化,Spring选择合适的构造器实例化B,并将B的ObjectFactory放入到三级缓存中
-
B的属性赋值,赋值的过程中,发现成员变量A能够在三级缓存中找到(半成品),B属性赋值成功,删除二级、三级缓存中的Bean,保存到一级缓存中。
-
回到A的属性赋值,A属性赋值成功(完成品),删除二级、三级缓存中的Bean,保存到一级缓存中
1. 问:这里第4步骤,属性B是如何找到A的半成品对象的
-
从三级缓存中取出Bean对应的ObjectFactory实例,调用其getObject方法,来获取早期曝光Bean,最终会调用getEarlyBeanReference方法
-
拿到半成品的bean实例(属性未填充),从三级缓存移除,放到二级缓存earlySingletonObjects中
注意:此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象
1. getEarlyBeanReference的作用
2种情况:
- 如果bean被AOP切面代理则返回的是beanProxy对象
- 如果未被代理则返回的是原bean实例
2. 问:为什么要用到二级缓存?(AOP代理的Bean)
好像只需要用到三级缓存就可以了,为什么要用到二级缓存?
1. bean被AOP进行了切面代理的场景
getEarlyBeanReference方法返回的是代理对象,还是被CGLIB代理的,每次执行都会产生一个新的代理对象
这就会有问题了,因为A是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象(也可以认为是ObjectFactory),执行getObject()方法又会产生新的代理对象,这是不行的,因为A是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了**singleFactory.getObject()**产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象
2. 二级缓存的作用
在Bean被AOP代理的情况下,保证始终只有一个代理对象
3. 总结
如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以需要借助另外一个缓存来保存产生的代理对象
参考一下优秀的文章:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
7. Spring中Bean的一级缓存、二级缓存、三级缓存
1. 一级缓存(初始化完成的Bean)singletonObjects
存在ConcurrentHashMap中,用来存放已经初始化完成的Bean
2. 二级缓存(有问题的Bean)earlySingletonObjects
存在HashMap中,存放半成品的Bean,一般处于循环引用的Bean才会暂时保存在这个缓存里
3. 三级缓存(存ObjectFactory)singletonFactories
存在HashMap中,存放ObjectFactory实例(用来获取Bean的工厂类)、在IoC容器中,所有刚被创建出来的ObjectFactory,默认都会保存到该缓存中。
1. 作用
spring会提前将已经实例化的bean通过ObjectFactory半成品暴露出去
半成品是指:bean对象已经实例化,但是未进行属性填充,是一个不完整的bean实例对象
4. 三者之间的关系
- 一级二级缓存中的Bean是互斥的,同一个Bean只可能存在其中一个缓存
- 二级缓存中保存的是BeanFactory产生的对象或AOP代理类对象
- 三级缓存只存工厂类,不存Bean实例
8. Bean的作用域
- singleton(默认):所有线程共用一个实例
- prototype(原型,每次请求都会创建一个bean实例)
- request
- session
- global-session
设定方式:xml用scope指定、注解@Scope(value = “singleton”)指定
小tips:singleton作用域下才会走spring的生命周期流程、prototype用到的时候才走
1. 题外话:Spring框架中的单例bean是线程安全的吗
不是,但是像Controller、Service这些大多数都是无状态的,不存在除了查询以外的操作,所以很安全;对于有状态的Bean 可以设置Bean的作用域为prototype,对于每个线程都有各自的Bean,绝对安全
9. Spring框架中都用到了哪些设计模式
1. 单例模式(单例注册表)
spring默认创建的bean就是单例的
spring通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式
2. 工厂模式(BeanFactory、ApplicationContext )
通过BeanFactory 或 ApplicationContext 创建 bean 对象。
1. BeanFactory(懒汉思想生成Bean)
BeanFactory 延迟注入,用到的时候才会实例化Bean,优点启动块。(以前内存还不够用的时候)
白话文:懒汉模式的思想
2. ApplicationContext(饿汉思想生成Bean)
ApplicationContext 启动时就会实例化所以Bean,扩展了BeanFactory,有额外功能(用的多)
白话文:饿汉模式的思想
3. 代理模式(AOP)
Spring AOP的原理就是采用动态代理。例如AopProxy、CglibAopProxy、JDKDynamicProxy
4. 模板方法
一般实现:使用继承抽象父类并@Override其父类的abstract方法
jdbcTemplate使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性
5. 适配器模式(AdvisorAdapter、HandlerAdapter)
1. AdvisorAdapter
Spring AOP 的Advice通知用到了AdvisorAdapter,搭配拦截器使用
2. HandlerAdapter
SpringMvc中 DispatcherServlet根据请求信息调用HandlerMapper,解析出来的Handler,交给HandlerAdapter(适配器),适配对应的Controller类
6. 策略模式(BeanPostProcessor)
例如:BeanPostProcessor (具体哪里体现策略模式?todo)
10. Spring事务
1. ACID特性
1. 原子性
事务内的操作,要么全部执行,要么全部回滚。
2. 一致性(逻辑上的一致性)
强调数据库在逻辑上的一致性状态,依赖于原子性。
3. 隔离性
两个事务之间互不影响
4. 持久性(落库)
事务被提交之后,事务的一系列操作的结果是需要落库的
1. 题外话:原子性和一致性的区别
- 一致性强调逻辑上一致,程序员可以写出原子性事务,但是在逻辑上是错误的代码(逻辑有问题,比如A转账给B,但代码只写了A扣钱,照样能符合原子性,但不符合一致性)。
- 一致性需要依赖于原子性,只有符合原子性的代码才可能有一致性
2. 隔离级别
1. 代码
@Transactional(isolation = Isolation.DEFAULT)
2. 级别
- 默认(default):数据库的隔离级别为主
- 读未提交(read_uncommit)
- 读已提交(read_commit)
- 不可重复读(repeat_table)
- 串行化(serializable)
3. 传播行为(7种)
1. 代码
@Transactional(propagation = Propagation.REQUIRE)
3. 总结(用事务、不用事务、报错)
对下面7种事务的方法就行总结
1. 如果当前有事务
- 用这个事务
- 创建个新的事务
- 把当前事务挂起,不用事务
- 报错
2. 如果当前没有事务
- 创建个新的事务
- 不用事务
- 报错
3. 支持事务
- PROPAGATION_REQUIRED:如果当前没有事务,就创建一个;如果有事务,则使用这个事务(常用)
- PROPAGATION_REQUIRES_NEW :不管有没有事务,都自己创建一个新事务(如果原来有事务,则会被挂起)
- PROPAGATION_SUPPORTS:使用当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
4. 不支持事务
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:如果有当前事务,则嵌套;如果没有,就创建一个。(和PROPAGATION_REQUIRED类似)
4. 优点(用注解、集成多种数据库)
- 支持声明式事务管理。
- 对各种数据库抽象访问层有很好的集成
- 编程式事务管理提供了一套简单的 API,就算不用声明式也很方便
5. 实现方式
1. 声明式事务
采用注解@Transactional实现,原理是AOP,Spring启动的时候会扫描这个注解相关的类,并生成代理类,主要功能如下:
-
在Before时,根据propagation属性确定事务的传播行为
-
在After时,切面需要确定事务被提交,回滚或者继续运行,真正的数据库层的事务提交和回滚是通过bin log或者redo log实现的
2. 编程式
对代码有侵入性,不建议