Spring有哪些模块
Spring Core
框架的最基础部分,提供 IoC 容器,对 bean 进行管理。Spring Context
继承BeanFactory,提供上下文信息,扩展出JNDI、EJB、电子邮件、国际化等功能。Spring DAO
提供了JDBC的抽象层,还提供了声明性事务管理方法Spring ORM
提供了JPA、JDO、Hibernate、MyBatis 等ORM映射层.Spring AOP
集成了所有AOP功能Spring Web
提供了基础的 Web 开发的上下文信息,现有的Web框架,如JSF、Tapestry、Structs等,提供了集成Spring Web MVC
提供了 Web 应用的 Model-View-Controller 全功能实现。
Bean定义了哪几种作用域
singleton(单例) spring默认
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。prototype(原型)
每次请求(getBean)都会创建一个新的 bean 实例request
每一次 HTTP 请求都会产生一个新的 beansession
global
session
Spring IOC初始化流程
resource定位: 即寻找用户定义的bean资源,由 ResourceLoader通过统一的接口Resource接口来完成
beanDefinition载入: BeanDefinitionReader读取、解析Resource定位的资源 成BeanDefinition 载入到ioc中(通过HashMap进行维护BD)
BeanDefinition注册: 即向IOC容器注册这些BeanDefinition, 通过BeanDefinitionRegistery实现
BeanDefinition加载流程: 定义BeanDefinitionReader解析xml的document BeanDefinitionDocumentReader解析document成beanDefinition
DI依赖注入流程(实例化,处理Bean之间的依赖关系)
时机
过程在Ioc初始化后,依赖注入的过程是用户第一次向IoC容器索要Bean时触发
流程
如果设置lazy-init=true,会在第一次getBean的时候才初始化bean, lazy-init=false,会容器启动的时候直接初始化(singleton bean)
调用BeanFactory.getBean()生成bean的
生成bean过程运用装饰器模式产生的bean都是beanWrapper(bean的增强)
依赖注入怎么处理bean之间的依赖关系?
其实就是通过在beanDefinition载入时,如果bean有依赖关系,通过占位符来代替,在调用getBean时候,如果遇到占位符,从ioc里获取bean注入到本实例来
IOC:控制反转:将对象的创建权,由Spring管理
Spring的IOC注入方式
构造器注入
setter方法注入
注解注入
接口注入
DI(依赖注入)
在Spring创建对象的过程中,把对象依赖的属性注入到类中
怎么检测是否存在循环依赖?
Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了
Spring的controller默认是单例的,不要使用非静态的成员变量,否则会发生数据逻辑混乱。正因为单例所以不是线程安全的。
解决方案:
不要在controller中定义成员变量。
万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式。
在Controller中使用ThreadLocal变量
Spring 中使用了哪些设计模式?
工厂模式: spring中的BeanFactory就是简单工厂模式的体现,根据传入唯一的标识来获得bean对象;
观察者模式: spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
代理模式: AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)
单例模式: 提供了全局的访问点BeanFactory;
装饰器模式: 依赖注入就需要使用BeanWrapper
策略模式: Bean的实例化的时候决定采用何种方式初始化bean实例(反射或者CGLIB动态字节码生成)
模板方法模式
Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式
AOP
AOP 核心概念
切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点连接点(joinPoint)
被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在Spring 中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器切入点(pointcut)
对连接点进行拦截的定义通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。目标对象
代理的目标对象织入(weave)
将切面应用到目标对象并导致代理对象创建的过程引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加方法或字段
应用场景
记录日志
监控性能
权限控制
事务管理
线程池关闭等
源码分析
@EnableAspectJAutoProxy给容器(beanFactory)中注册一个AnnotationAwareAspectJAutoProxyCreator对象;
AnnotationAwareAspectJAutoProxyCreator对目标对象进行代理对象的创建,对象内部,是封装JDK和CGlib两个技术,实现动态代理对象创建的(创建代理对象过程中,会先创建一个代理工厂,获取到所有的增强器(通知方法),将这些增强器和目标类注入代理工厂,再用代理工厂创建对象);
代理对象执行目标方法,得到目标方法的拦截器链,利用拦截器的链式机制,依次进入每一个拦截器进行执行
AOP使用哪种动态代理?
当bean的是实现中存在接口或者是Proxy的子类,使用jdk动态代理;不存在接口,spring会采用CGLIB来生成代理对象
JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler
Proxy 利用 InvocationHandler(定义横切逻辑) 接口动态创建 目标类的代理对象
jdk动态代理
通过bind方法建立代理与真实对象关系,通过Proxy.newProxyInstance(target)生成代理对象
代理对象通过反射invoke方法实现调用真实对象的方法
动态代理与静态代理区别
静态代理: 程序运行前代理类的.class文件就存在了;
动态代理: 在程序运行时利用反射动态创建代理对象<复用性,易用性,更加集中都调用invoke>
CGLIB与JDK动态代理区别
Jdk必须提供接口才能使用;
CGLIB不需要,只要一个非抽象类就能实现动态代理
Bean的生命周期?(创建Bean的过程)
实例化Bean: Ioc容器通过获取BeanDefinition对象中的信息进行实例化,实例化对象被包装在BeanWrapper对象中
设置对象属性(DI):通过BeanWrapper提供的设置属性的接口完成属性依赖注入;
注入Aware接口(BeanFactoryAware, 可以用这个方式来获取其它 Bean,ApplicationContextAware):Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean
BeanPostProcessor:自定义的处理(分前置处理和后置处理)
InitializingBean和init-method:执行我们自己定义的初始化方法
使用
destroy:bean的销毁
Spring如解决Bean循环依赖问题?
Spring中循环依赖场景有
构造器的循环依赖
属性的循环依赖
缓存
singletonObjects
第一级缓存,里面放置的是实例化好的单例对象;earlySingletonObjects
第二级缓存,里面存放的是提前曝光的单例对象;singletonFactories
第三级缓存,里面存放的是要被实例化的对象的对象工厂
解决过程
创建bean的时候Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取,
如果还是获取不到就从三级缓存singletonFactories中取(Bean调用构造函数进行实例化后,即使属性还未填充,就可以通过三级缓存向外提前暴露依赖的引用值(提前曝光),
根据对象引用能定位到堆中的对象,其原理是基于Java的引用传递),取到后从三级缓存移动到了二级缓存完全初始化之后将自己放入到一级缓存中供其他使用.
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
构造器循环依赖解决办法:在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象说明:一种互斥的关系而非层次递进的关系,故称为三个Map而非三级缓存的缘由 完成注入
在upupor项目中,有些使用构造器会发生循环依赖,使用@Resource注解就ok了,原理就是这个
SpringBoot 自动装配原理
什么是 SpringBoot 自动装配?
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
SpringBoot 是如何实现自动装配的?
@EnableAutoConfiguration:实现自动装配的核心注解。AutoConfigurationImportSelector:加载自动装配类,AutoConfigurationImportSelector
类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。
SpringBoot
启动流程
new springApplication对象,利用spi机制加载applicationContextInitializer,applicationLister接口实例(META-INF/spring.factories);
调run方法准备Environment,加载应用上下文(applicationContext),发布事件 很多通过lister实现
创建spring容器, refreshContext() ,实现starter自动化配置,spring.factories文件加载, bean实例化
SpringBoot自动配置的原理
@EnableAutoConfiguration找到META-INF/spring.factories(需要创建的bean在里面)配置文件
读取每个starter中的spring.factories文件
Spring Boot 的核心注解
@SpringBootApplication,由以下三种组成:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能。
@ComponentScan:Spring组件扫描。
Spring Boot 的核心配置文件
Application.yml 一般用来定义单个应用级别的,如果搭配 spring-cloud-config 使用
Bootstrap.yml(先加载) 系统级别的一些参数配置,这些参数一般是不变的
Spring MVC
SpringMVC流程
用户请求发送给DispatcherServlet,DispatcherServlet调用HandlerMapping处理器映射器;
HandlerMapping根据xml或注解找到对应的处理器,生成处理器对象返回给DispatcherServlet;
DispatcherServlet会调用相应的HandlerAdapter;
HandlerAdapter经过适配调用具体的处理器去处理请求,生成ModelAndView返回给DispatcherServlet
DispatcherServlet将ModelAndView传给ViewReslover解析生成View返回给DispatcherServlet;
DispatcherServlet根据View进行渲染视图;
->DispatcherServlet->HandlerMapping->Handler ->DispatcherServlet->HandlerAdapter处理handler->ModelAndView ->DispatcherServlet->ModelAndView->ViewReslover->View ->DispatcherServlet->返回给客户
Spring事务@Transaction是如何实现的?
首先,对于spring中aop实现原理有了解的话,应该知道想要对一个方法进行代理的话,肯定需要定义切点。在@Transactional的实现中,同样如此,spring为我们定义了以 @Transactional 注解为植入点的切点,这样才能知道@Transactional注解标注的方法需要被代理。
有了切面定义之后,在spring的bean的初始化过程中,就需要对实例化的bean进行代理,并且生成代理对象。
生成代理对象的代理逻辑中,进行方法调用时,需要先获取切面逻辑,@Transactional注解的切面逻辑类似于@Around,在spring中是实现一种类似代理逻辑。
实际面试中,我第一次回答讲到了
AOP
ThreadLocal
没有说到
代理