Spring
1.什么叫线程安全:
多个线程访问一个对象时,不需要额外的调度与交替执行也不需要额外的同步,调用这个对象的行为都可以获得正确的调度结果
如何保持线程安全:
- 使用final修饰变量,让其只可读不可修改
- 使用局部变量,公共数据私有化:这样堆内读取的数据就会改成在栈内读取
- 使用ThreadLocal,这样每个线程都会在进程内copy一份数据,各自使用自己的数据
- 使用互斥锁,让后续操作等待,这个适合线程比较多的情况
- 使用乐观锁CAS:失败重试机制:即将修改的数据的原数据不一致,重新获取数据再修改。在线程数较少时,使用锁比较消耗资源,使用此方法
2.spring框架中的单例bean是线程安全的吗?
spring中的bean默认是单例的,bean内有一个@scope("singleton")默认注解,说明他是单例的,另一种就是prototype原型设计模式。
单例bean是线程不安全的:如果springbean中有可变的状态,例如成员变量,他是公共的,这时就会有线程安全问题。
3.什么是AOP
面相切面编程,用于将与业务无关,但是会对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用模块,这个模块就是aspect(切面)
在事务@Transactional中,就是对方法前后进行了拦截,在执行方法前开启事务,在执行完目标方法之后根据执行情况提交或回滚事务
4.JDK动态代理和CGLIB动态代理
JDK代理只提供接口的代理,不支持类的代理
- 运行时会为目标生成一个$proxy*.class的动态代理类
- 代理类会实现接口的所有方法增强代码
- 调用的时候通过代理类先去调用处理类进行增强,再通过反射去调用目标方法实现AOP
如果代理类没有实现接口,那么就是CGLIB来动态代理目标类
- CGLIB底层是通过ASM在运行时动态的生成目标类的一个子类(会生成多个为了增强调用效率的类)
- 重写父类所有增强代码
- 调用是先通过代理类增强,再直接调用父类的方法,从而实现AOP(因此,如果被final修饰,无法通过CGLIB进行动态代理;CGLIB还会生成一个FastClass类路由类,可以让本类方法调用进行增强,而不会失效)
一般来说JDK生成类速度快(因为生成类比较少)而调用慢(采用反射所以慢);CGLIB生成类速度慢,后续调用快。但是JDK在版本升级性能都在提升。在JDK8的时候性能已经比CGLIB快20%左右了。
5.事务失效的场景
异常捕获处理,try catch你自己都把异常捕获了系统认为代码没问题就不会回滚了。当然也可以在catch中重新throw new runtimeexpection
抛出检查异常,默认只会回滚非检查异常,如果需要回滚,需要在事务注解内加上rollbackFor
非public方法(默认情况为default,而非public)
类内自调用,事务基于AOP,AOP基于动态代理,this指针的对象是普通对象而非动态代理对象(参考4,我是先写的5再写的4)
数据库引擎不支持、分布式情况
事务传播行为不一致:顺带讲一下常用的两种事务:1.required,适用于绝大多修改方法。A有事务时,B会融入A事务;A没有事务时,B会开启一个新事务。2.support,适用于绝大多查询方法,A有事务,B会加入A事务;A没事务,B会以非事务方法执行
6.springbean的生命周期
spring容器在实例化时会将xml中的<bean>封装成一个beanDefinition,其中有很多属性用来描述bean:
- beanClassName(可以通过getBeanClassName()获取类名,反射创建bean)
- scope(作用域:比如singleton,prototype,getScope)
- lazy-init(懒加载)
生命周期:
- beanDefinition调用bean的构造函数,(这里只负责创建bean,不负责赋值,赋值和创建是分开算的)
- 依赖注入(通过@Autowired,@Value注入)
- aware接口(BeanNameAware[运行bean获取自己的name]、BeanFactoryAware[用于获取bean,检查bean是否存在]、ApplicationContextAware[])方便对bean进一步扩展
- BeanPostProcessor#before bean的后置处理器-前置
- 初始化方法
- BeanPostProcessor#after bean的后置处理器-后置(AOP就是通过这个来增强类的,AOP的底层为动态代理,动态代理分为JDK动态代理和CGlib动态代理)
7.如何解决循环依赖
为什么spring bean会有循环依赖:bean的生命周期中提到,bean的构造和赋值是分开的;在实例化时beanA会现在堆内申请空间,然后再进行初始化,初始化如果依赖于一个不存在的beanB,则需要优先去创建beanB,beanB也需要先实例化,然后初始化的时候发现他也需要beanA,从而形成循环
spring提供了三层缓存解决了大部分循环依赖问题:
一级缓存(singletonObjects)用于存储经历了完整生命周期的单例bean对象
二级缓存(earlySingletonObjects)用于存储早期的bean对象(生命周期还没有走完)
三级缓存(singletonFactories)用于存储ObjectFactory,对象工厂,用来创建对象的(后面可能会创建普通对象、代理对象)
二级缓存如何解决循环依赖:实例化A后,A的原始对象会被存入二级缓存中,初始化需要注入B,B不存在再实例化B。B先初始化,再初始化的时候将B原始对象存入二级缓存,这里通过二级缓存取出A的原始对象。B创建成功后再将B对象存入一级缓存中,B注入A,A也放入一级缓存中。这时A,B都将对象存入了一级缓存中,但是没有代理对象
三级缓存如何解决循环依赖:实例化A后,A会生成一个A-ObjectFactory,这时再去进行初始化,初始化需要注入B。实例化B,B再生成一个B-ObjectFactory,初始化需要注入A,B会让A-ObjectFactory创建一个A的代理对象。A的代理对象会被放入二级缓存,然后将A的代理对象注入给B,这时B创建成功了,B会被放入一级缓存中。将B注入给A代理对象,A代理对象也创建成功,这时A的代理对象也会放入一级缓存。至此创建成功
构造方法出现了循环依赖:因为bean的生命周期中会有构造和初始化两个阶段,三级缓存解决的是初始化阶段循环依赖的问题;通过在构造方法内为循环依赖的bean加上@Lazy,让其懒加载。这样就可以将问题从构造方法中抛到初始化阶段从而解决问题
8.讲讲springMVC的执行流程
老版本(JSP这类)
1.浏览器发送请求给DispatcherServlet(由tomcat初始化的前端控制器)
2.通过handlermapping查询handler,handlermapping里存了一张map { key:前端发起的路径 , value:"类名#方法名" } 例如:{key:/user/getById/1,value:"UserController#getUserById(Long id)"}。再将handler、拦截器一并返回给dispatcherServlet
3.DispatcherServlet调用handlerAdaptor,handlerAdaptor调用适用的handler/controller,将得到的ModelAndView对象返回给dispatcherServlet。
4.dispatcherServlet将modelAndView传给ViewResolver,ViewResolver解析后返回具体的view
5.dispatcherServlet根据view渲染具体视图响应用户
新版本
1.浏览器发送请求给DispatcherServlet(由tomcat初始化的前端控制器)
2.通过handlermapping查询handlermapping,生成处理器对象及处理器拦截器,再一起返回给dispatcherServlet
3.dispatcherServlet调用handleradapter
4.方法上有responseBody,通过HttpMessageConverter来返回结果转换为JSON并响应
9.springboot的自动配置原理
springboot项目的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装:@SpringbootConfiguration,@EnableAutoConfiguration,@CompentScan
其中@EnableAutoConfiguration是实现自动装配类的注解,通过@Import导入了默认的选择器。选择器就是读取了项目以及项目引用的jar包的classpath路径下META-INF/spring.factories所配置的类的类名,这些配置类内的bean会根据条件注解所指定的条件来决定是否需要将其导入到spring容器中
条件判断会类似@ConditionalOnClass的注解来决定是否加载该类,如果有则加载,其中的所有bean也会有@ConditionalOnMissingBean控制,如果一切正确则bean会自动置入spring容器中。
10.spring框架常见注解(spring,springboot,springMVC)
spring:bean相关,例如@Component @Controller @Service @Autowired @Scope @Configuration AOP相关 @PointCut @Around @Aspect
springboot:@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan @SpringbootApplication
springMVC:主要是controller里那些 @RequestMapping @RequestBody @RequsetHeader @RequestParam @PathViriable @RestController @ResponseBody
Mybatis
1.mybatis的执行流程
读取mybatis配置:spring版本有个mybatis-config.xml;springboot版本实现自动装配功能,直接在yaml文件写jdbc的链接相关内容就可以了
构造会话工厂SqlSessoinFactory
会话工厂构造会话对象SqlSession(用于执行映射SQL的语句、管理事务、代理mapper),sqlsession中的功能是基于executor实现的
executor是mybatis的核心接口,他的执行方法中有一个MapperedStatement类型的参数,其中封装了映射信息,会对输入参数和输出参数进行java到sql以及sql到java的映射。包括了id,resource,resultType,sql。
executor来执行实际的数据库操作。
2.mybatis是否支持延迟加载?
支持,但是默认情况下是未开启的
什么是延迟加载?举例来说:有User表和Order表,他们是1对多的关系;User实体类中有List<Order> orderList字段,Order实体类中有User user字段
查询用户时如果把用户所属的订单数据页查询出来,这就是正常加载
查询用户时暂时不查询订单数据,需要订单时再查询订单,这就是延迟加载
如何实现延迟加载:在映射的结果集里的子查询中有fetchType属性,设置为lazy即可实现延迟加载。上例中,只有当你调用了user.getOrderList()才会去执行对应的查询语句
延迟加载主要是通过CGLIB代理对象实现的,调用目标方法时会进入invoke拦截器,发现目标为null会执行sql查询再通过set设参然后才会进入目标方法
3.mybatis的一级缓存/二级缓存
mybatis的缓存都是由map集合存储的;sqlSession是与当前线程绑定的,一旦事务结束、请求处理完毕、发生异常,sqlSession都会关闭
一级缓存(默认开启的):作用域是同一个sqlSession,同一个sqlSession内执行两次参数一样的sql语句会在缓存中读取数据,持续到sqlSession没有close或者flush。一般在完成一次数据库操作/事务结束后sqlSession就会关闭了,因此其实一级缓存意义不大。
二级缓存(不建议使用):多个sqlSession共享的,其作用域是mapper的同一个namespace。不同sqlSession执行两次参数一样同一个namespace的sql会在缓存中读取数据。
二级缓存只建议用在数据字典这种基本不会改动,不会有/被外表链接的mapper中。