Spring
1. ioc是如何创建对象的
ioc控制反转,实现创建对象与程序解耦,底层是反射。调用无参构造器创建对象–>依赖注入属性值–>初始化(aware接口、@PostConstrust、AOP) -->放入单例池。
如果经过aop,代理对象放入单例池,否则把普通对象放入单例池
2. aop 项目中的应用
日志、事务、监控、权限控制。
3. spring中使用到了哪几种设计模式
- 单例模式 创建bean默认为单例模式
- 工厂模式 BeanFactory和ApplicationContext
- 代理模式 aop
- 观察者模式 listener
- 策略模式 aop中的jdk和cglib
- 模板方法 RestTemplate, JmsTemplate, JpaTemplate
4. aop底层实现(动态代理)
- jdk是基于接口的,用implements方式实现,cglib是基于类的,用继承方式实现,但是cglib不能代理final修饰的类。
- jdk是基于java原生的,底层基于反射,cglib是基于ASM字节码框架,需要引入三方类库,底层将方法全部存入一个数组中,通过数组索引直接进行方法调用。
- Aop的核心是动态代理,根据是否为接口代理,决定使用jdk或者cglib,spring中更推荐使用java原生的代理方式,原因在于,第三方lib的实现有太多不确定性,使用java原生的代理方式更加有保障和稳定。
- 可通过<aop:aspectj-autoproxy proxy-target-class=“true”/>配置来强制使用cglib代理方式
- 二者实现方式:
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
1.实现InvocationHandler接口,重写invoke()
2.使用Proxy.newProxyInstance()产生代理对象
3.被代理的对象必须要实现接口
CGLib 必须依赖于CGLib的类库,需要满足以下要求:
1.实现MethodInterceptor接口,重写intercept()
2.使用Enhancer对象.create()产生代理对象
5. spring中使用到的事务注解
- @EnableTransactionMannagement
- @Transactional
- @TransactionEventListener
详细解释见:添加链接描述
6. 事务传播机制
① PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。
② PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
③ PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘
④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑤ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。
⑥ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。
⑦ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
7. Spring中事务失效的场景
- 抛出检查异常导致事务不能正确回滚
原因:Spring 默认只会回滚非检查异常
解法:配置rollbackFor - 业务方法内自己 try-catch 异常导致事务不能正确回滚
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
解法1:异常原样抛出
解法2:手动设置 Transactionstatus.setRollbackOnly() - aop 切面顺序导致导致事务不能正确回滚
原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确地出异常
解法:同情况2 - 非 public 方法导致的事务失效
原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的 - 父子容器导致的事务失效
原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来
解法1:各扫描各的,不要图简便
解法2:不要用父子容器,所有 bean 放在同一容器 - 调用本类方法导致传播行为失效
原因:本类方法调用不经过代理,因此无法增强
解法1:依赖注入自己(代理)来调用
解法2:通过 AopContext 拿到代理对象,来调用 - @Transactional 没有保证原子行为
原因:事务的原子性仅涵盖 insert、update、delete、select…for update 语句,select 方法并不阻塞 - @Transactional 方法导致的synchronized 失效
原因:synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
解法1:synchronized 范围应扩大至代理方法调用
解法2:使用 select…for update 替换 select
8. 如何获得对象的实例,spring如何创建对象实例
9.bean的生命周期
1.处理名称,检查缓存
2.检查父工厂
3.检查dependsOn
4.按scope创建bean
singleton
prototpye
其他
5.创建bean
创建bean实例
依赖注入
初始化
登记可销毁bean
6.类型转换
7.销毁bean
10. bean的作用域
著作权归https://pdai.tech所有。
链接:https://pdai.tech/md/interview/x-interview-2.html
- singleton:唯一bean实例,Spring中的bean默认都是单例的。
- prototype:每次请求都会创建一个新的bean实例。
- request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
- global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话
11. bean的线程安全
著作权归https://pdai.tech所有。
链接:https://pdai.tech/md/interview/x-interview-2.html
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
有两种常见的解决方案:
- 在bean对象中尽量避免定义可变的成员变量(不太现实)。
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
12.Spring 是如何解决的循环依赖?
详细参见
Spring 为了解决单例的循环依赖问题,使用了三级缓存。其中一级缓存为单例池(singletonObjects),二级缓存为提前曝光对象(earlySingletonObjects),三级缓存为提前曝光对象工厂(singletonFactories)。
假设A、B循环引用,实例化 A 的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了 B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖 A,这时候从缓存中查找到早期暴露的 A,没有 AOP 代理的话,直接将 A 的原始对象注入 B,完成 B 的初始化后,进行属性填充和初始化,这时候 B 完成后,就去完成剩下的 A 的步骤,如果有 AOP 代理,就进行 AOP 处理获取代理后的对象 A,注入 B,走剩下的流程。
13.为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
如果没有 AOP 代理,二级缓存可以解决问题,但是有 AOP 代理的情况下,只用二级缓存就意味着所有 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 设计的原则,Spring 在设计之初就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。
14.Spring容器启动流程
(1)初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中:
① 实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象
② 实例化BeanDefinitionReader注解配置读取器,用于对特定注解(如@Service、@Repository)的类进行读取转化成 BeanDefinition 对象,(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等)
③ 实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象
(2)将配置类的BeanDefinition注册到容器中:
(3)调用refresh()方法刷新容器:
① prepareRefresh()刷新前的预处理:
② obtainFreshBeanFactory():获取在容器初始化时创建的BeanFactory:
③ prepareBeanFactory(beanFactory):BeanFactory的预处理工作,向容器中添加一些组件:
④ postProcessBeanFactory(beanFactory):子类重写该方法,可以实现在BeanFactory创建并预处理完成以后做进一步的设置
⑤ invokeBeanFactoryPostProcessors(beanFactory):在BeanFactory标准初始化之后执行BeanFactoryPostProcessor的方法,即BeanFactory的后置处理器:
⑥ registerBeanPostProcessors(beanFactory):向容器中注册Bean的后置处理器BeanPostProcessor,它的主要作用是干预Spring初始化bean的流程,从而完成代理、自动注入、循环依赖等功能
⑦ initMessageSource():初始化MessageSource组件,主要用于做国际化功能,消息绑定与消息解析:
⑧ initApplicationEventMulticaster():初始化事件派发器,在注册监听器时会用到:
⑨ onRefresh():留给子容器、子类重写这个方法,在容器刷新的时候可以自定义逻辑
⑩ registerListeners():注册监听器:将容器中所有的ApplicationListener注册到事件派发器中,并派发之前步骤产生的事件:
⑪ finishBeanFactoryInitialization(beanFactory):初始化所有剩下的单实例bean,核心方法是preInstantiateSingletons(),会调用getBean()方法创建对象;
⑫ finishRefresh():发布BeanFactory容器刷新完成事件
15.SpringMVC启动流程
16.Spring通知(Advice)有哪些类型?
(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。
(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知
17.spring的装配方式
- 手动装配
xml、构造方法、setter - 自动装配
- 设置xml的bean标签中的autwired属性
- no:spring默认方式,不使用自动装配,只能设置ref属性来装配
- byName: 通过参数名称,装配和该bean属性中相同名字的bean
- byType:通过参数类型,装配和该bean属性中类型相同的bean,如果多个bean符合条件则抛错
- constructor:类似byType,需要提供带参构造,否则抛出异常
- autodetect:先constructor,如果无法工作,使用byType
- 使用注解
@Autowired
1.按照类型自动匹配
2.需要导入spring-aop的包@Qualifier
1.@Autowired+@Qualifier 可以根据名字匹配
2.不可单独使用
@Resource
1.如果指定name,先按照byname方式匹配
2.其次进行默认的byName方式匹配
3.以上都不成功,则按照byType方式装配
3.都不成功,抛出异常
SpringBoot
1.springboot自动装配(转载)
思路一
springboot的自动装配,大概可以概括为:
- 在启动类的run方法传入启动类的class(方便后面获取其注解信息)。
- 执行run方法,创建SpringApplication对象,并用LoadSpringFactories()方法将/META-INF/spring.factories文件里的k-v读入缓存(方便后面加载时使用)。
- 然后继续run方法,在某处会获取传入的启动类的class,并解析上面的注解,当解析到@Import({AutoConfigurationImportSelector.class})时会将AutoConfigurationImportSelector加载进方法区,通过反射创建对象,调用其某一个方法,从缓存读取前面存储的k-v,并经过一系列的过滤、去重等,最后将需要的配置类加载,生成BD对象,创建Bean对象,放入spring容器。
思路二
装配是指bean对象托管到ioc容器的过程。
自动装配是通过注解@SpringBootApplication 实现的
此注解由3个注解实现
@SpringBootConfiguration 就是@Configuration注解,用于定义bean,作为一个bean注入到ioc容器
@ComponentScan 注解扫描,扫描需要装配的类
@EnableAutoConfiguration 自动注入配置,为自动装配核心实现
注解由组合实现
—@AutoConfigurationPackage 扫描当前包将其配置类注入ioc
—@import(AutoConfigurationImportSelector.class)
—AutoConfigurationImportSelector.class 的目的是筛选所有符合条件的@Configuration配置类注入ioc,实现方式是:
selectImports方法调用getCandidateConfigurations方法,获取META-INF/spring.factories中配置文件中的需要自动装配的类名
总结:springboot启动时,从类路径下的META-INF/spring.factories中获取需要自动装配的类,找到这些类(XXcConfiguration),
通过SpringFactoriesLoader机制创建对应的bean,注入到ioc容器,而不必从spring的xml文件中配置bean操作
2.springboot的核心注解
启动类上面的注解是@SpringBootApplication,他是SpringBoot的核心注解,主要组合包含了以下3个注解:
- @SpringBootConfiguration:组合了@Configuration注解,实现配置文件的功能;
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置的功能:
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.class});
- @ComponentScan:Spring组件扫描。
3.springboot启动流程
- 创建 SpringApplication
- 保存一些信息。
- 判定当前应用的类型。ClassUtils。Servlet
- bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper
- 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer
List<ApplicationContextInitializer<?>> initializers - 找 ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener
List<ApplicationListener<?>> listeners
- 运行 SpringApplication
- StopWatch
记录应用的启动时间 - 创建引导上下文(Context环境)createBootstrapContext()
获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置 - 让当前应用进入headless模式。java.awt.headless
- 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener. - 遍历 SpringApplicationRunListener 调用 starting 方法;
相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。 - 保存命令行参数;ApplicationArguments
- 准备环境 prepareEnvironment();
- 返回或者创建基础环境信息对象。StandardServletEnvironment
- 配置环境信息对象。
读取所有的配置源的配置属性值。 - 绑定环境信息
- 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
- 创建IOC容器(createApplicationContext())
- 根据项目类型(Servlet)创建容器,
- 当前会创建 AnnotationConfigServletWebServerApplicationContext
- 准备ApplicationContext IOC容器的基本信息 prepareContext()
- 保存环境信息
- IOC容器的后置处理流程。
- 应用初始化器;applyInitializers;
- 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
- 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared - 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
- 刷新IOC容器。refreshContext
创建容器中的所有组件(Spring注解) - 容器刷新完成后工作?afterRefresh
- 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
- 调用所有runners;callRunners()
- 获取容器中的 ApplicationRunner
- 获取容器中的 CommandLineRunner
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner。调用 run 方法
- 如果以上有异常,
- 调用Listener 的 failed
- 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
- running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed