0
点赞
收藏
分享

微信扫一扫

spring 常见9个相关面试问题


spring 常见9个相关面试问题_事务管理

  1. 谈谈你对springIOC的理解
    IOC:控制反转,原来我们使用的时候对象是由使用者控制的,有了spring之后,可以将整个对象交给容器来帮我们进行管理(理论思想)
    既然用到了控制反转,就需要依赖注入bean也就是DI。
    DI:将对应的属性注入到具体的对象中,注入的方式常见的三种{构造器,setter,注解} 比如注解使用@Autowired:默认是按照类型进行注入 @Resource 默认是按照名字进行注入,如果想要是用@Autowired通过名字进行注入的话,可以使用@Autowired+@Qulified指定名称进行注入。
    populateBean方法来完成属性的注入,这个方法中有通过名字有通过类型进行注入的。
    容器:存储对象,使用map结构在存储对象,在spring中存储对象的时候一般有三级缓存,singletonObjects存放完整对象,earlySingletonObjects存放半成品对象,singletonFactory用来存放lambda表达式和对象名称的映射,整个bean的生命周期,从创建到使用销毁,各个环节都是由容器帮我们控制的。
    spring中所有的bean都是通过反射机制形成的 ,还有两个比较重要的接口BeanPostProcessor 和BeanProcessor用来实现扩展功能。aop就是在iod基础之上的一个扩展实现。
  2. 简单表述bean 的生命周期?
  3. spring 常见9个相关面试问题_spring_02

  4. bean生命周期过程如下图
  5. spring 常见9个相关面试问题_java_03

  6. BeanFactory 和 FactoryBean有什么区别?
    BeanFactory 和 FactoryBean都可以用来创建对象,只不过创建的流程和方式不同,当使用BeanFactory的时候,必须要严格的遵守bean 的生命周期,经过一系列繁杂的步骤之后才可以创建出单例对象,是流水线式的创建过程。
    而FactoryBean使用户可以自定义bean对象的创建流程,不需要按照bean的生命周期来创建,在此接口中包含了三个方法:
    isSingleton:判断是否是单例对象
    getObjectType:获取对象的类型
    getObject:在这个方法中可以自己创建对象,使用new 的方式或者代理的方式都可以,用户可以按照自己的需要随意创建对象,在很多框架继承的时候都会实现FactoryBean接口,比如Feign。
  7. spring中使用到了哪些设计模式
    单例模式:spring中的bean都是单例
    工厂模式:BeanFactory

    模板方法:postProcessBeanFactory,onRefresh
    观察者模式:listener,event
    适配器模式:Adapter
    装饰者模式:BeanWrapper
    责任链模式:使用aop的时候有一个责任链模式
    代理模式:aop动态代理
    委托者模式:带有delegate
    建造者模式:带有builder
    策略模式:XmlBeanDefinitionReader if可能改成策略模式
  8. applicationContext和BeanFactory的区别
    BeanFactory是访问spring容器的根接口,里边知识提供了某些基本方法的约束和规范,为了满足更多的需求,ApplicationContext实现了此接口,并在此接口的基础上做了某些扩展功能,提供了更加丰富的api调用,一般我们在使用的时候用applicationContext更多。
  9. 谈谈对循环依赖的理解
    什么是循环依赖?
    因为spring中bean都是单例的,当两个对象持有对方的属性的时候,会造成一个循环依赖。
  10. spring 常见9个相关面试问题_事务管理_04

  11. 关于解决上边的循环依赖问题,需要相关知识即成品对象(singletonObject)和半成品对象(earlySingletonObject)
  12. spring 常见9个相关面试问题_java_05

  13. 解决方法:
    当在属性进行初始化的时候在容器中没有成品,可以使用半成品对象这样就解决了循环依赖的问题。
    如下图所示:a实例化之后,在a中b属性初始化的时候,容器中没有找到b对象,b对象创建的时候其属性又有a对象的创建,只在缓存中有a半成品,就把a的半成品对象赋值给a对象。从而b完成构成一个成品,反向路径返回构成一个a对象的成品,从而解决了循环依赖。
  14. spring 常见9个相关面试问题_事务管理_06

  15. 成品和半成品在存储的时候需要分为不同的缓存进行存储,一级缓存和二级缓存放的对象是object类型的,三级缓存放的是ObjectFactory类型的。
  16. spring 常见9个相关面试问题_spring_07

    spring 常见9个相关面试问题_事务管理_08

  17. 三级缓存中并不是严格的从三级到二级到一级,这样的一个对象转化顺序,有可能三级和一级中有对象,也有可能一二三级中都有对象。
  18. springaop的底层实现原理
    spring的两大核心之一就是AOP,AOP:面向切面编程。
    AOP:
    连接点(JoinPoint):增强执行的位置(增加代码的位置),spring只支持方法;
    切点(PointCut): 具体的连接点,一般可以通过一个表达式来描述。
    增强(Advice):也成为消息,指的是增加的额外代码,Spring中,增强除了包含代码外,还包含位置信息。
    Spring中一共有四种增强
  • MethodBeforeAdvice: 前置增强
  • MethodInterceptor: 环绕增强
  • ThrowsAdvice:异常增强
  • AfterReturningAdvice: 返回值增强

引介:特殊的增强,动态为类增加方法
织入:将增强加入到目标类的过程,织入分为三种时期

  • 编译器:AspectJ
  • 类加载
  • 运行期:jdk动态代理(通过实现接口的方法) cglib(子类,不能用final)

目标对象(Target):原始对象
代理对象(Proxy):加入了增强的对象,是生成的。
切面(Aspect):切点+增强
接下来我要说的就是在运行期间植入的两种实现方式

什么是代理模式呢?
代理模式有三个角色,分别是

  • 抽象角色:接口
  • 目标角色:实现类
  • 代理角色:实现接口(InvocationHandler),并引用目标角色
    代理模式和装饰者模式的区别
    类图(结构)一样,但目的不同,装饰者模式的前提是已经所有的类,并进行组装;而使用代理模式时,我们不能直接访问目标角色或没有权限访问时,可以使用代理模式。

代理模式分为两种

  • 静态代理:需要为每个目标角色,创建一个对应的代理角色,类的数量会几句膨胀。
  • 动态代理:自动为每个目标角色生成对应的代理角色。

动态代理分为两种

  1. jdk动态代理的代码
    实现jdk动态代理的前提是所有的目标类都必须要基于一个统一的接口。
    eg:

package com.lf.shejimoshi.proxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;
//JDK动态代理实现InvocationHandler接口
public class JdkProxy implements InvocationHandler {
private Object target ;//需要代理的目标对象

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理,监听开始!");
Object result = method.invoke(target, args);
System.out.println("JDK动态代理,监听结束!");
return result;
}
//定义获取代理对象方法
private Object getJDKProxy(Object targetObject){
//为目标对象target赋值
this.target = targetObject;
//JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}

public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();//实例化JDKProxy对象
UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//获取代理对象
user.addUser("admin", "123123");//执行新增方法
}

}

  1. CGlib代理
    和JDK动态代理不同,不需要创建统一的接口。

package com.lf.shejimoshi.proxy.cglib;

import java.lang.reflect.Method;

import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目标对象

//重写拦截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib动态代理,监听开始!");
Object invoke = method.invoke(target, arr);//方法执行,参数:target 目标对象 arr参数数组
System.out.println("Cglib动态代理,监听结束!");
return invoke;
}
//定义获取代理对象方法
public Object getCglibProxy(Object objectTarget){
//为目标对象target赋值
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 设置回调
Object result = enhancer.create();//创建并返回代理对象
return result;
}

public static void main(String[] args) {
CglibProxy cglib = new CglibProxy();//实例化CglibProxy对象
UserManager user = (UserManager) cglib.getCglibProxy(new UserManagerImpl());//获取代理对象
user.delUser("admin");//执行删除方法
}

}

可以看出在获取代理对象的方法上不一样jdk是实现一个代理接口的匿名类,而cglib是对传入的对象获取其classs继承其对应的类生成一个子类进行代理。

  1. spring事务时如何回滚的?
    spring的事务管理是如何实现的?
    spring的事务是由aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不能通过通知来实现的,而是通过一个TransactionInterceptor来实现的,然后调用invoke来实现具体的逻辑。
  1. 先做准备工作,解析各个方法上事务相关的属性,根据具体的属性来判断是否开启新事务。
  2. 当需要开启的时候,获取数据库连接,关闭自动提交功能,开启事务。
  3. 执行具体的sql逻辑操作
  4. 在操作的过程中,如果执行失败了,那么会通过completeTransactionAfterThrowing来完成事务的回滚操作,回滚具体的逻辑是通过doRollBack方法来实现的,实现的时候也是先获取连接对象,通过连接对象来回滚
  5. 如果执行过程中,没有发生任何异常,那么会通过commitTransactionAfterReturning来完成事务的提交操作,提交的具体逻辑是通过doCommit方法来实现的,实现的时候也是先获取连接对象,通过连接对象提交
  6. 当事务执行完毕之后需要清除相关的事务信息cleanupTransactionInfo
  1. spring事务传播的特性,@Transactional注解
    spring支持编程式事务管理和声明式事务管理两种方式。
    编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
    声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
    显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
    声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解,显然基于注解的方式更简单易用,更清爽。
    spring事务特性
    spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口,其中TransactionDefinition接口定义以下特性:
  1. 事务的隔离级别
    隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
  • TransactionDefinition。ISOLATION_DEFAULT: 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED,但但是不可重复读。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,但是不可防止重复读,这也是大多数情况下的推荐值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
  1. 事务的传播行为
    所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED,创建一个新事务执行。
  1. 事务超时
    所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
    默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
  2. 事务只读属性
    只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
    事务的属性默认为读写事务。
    只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
    但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。
    因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可。
  3. spring事务回滚规则
    指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
    默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
    默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下 如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承 这样事务才会回滚!


举报

相关推荐

0 条评论