Spring
说一下你对 Spring 的理解
Spring IoC和AOP 介绍一下
IOC和AOP是通过什么机制来实现的
动态代理是什么
动态代理和静态代理的区别
什么是反射?有哪些使用场景
怎么理解SpringIoc
依赖倒置,依赖注入,控制反转分别是什么
如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计
SpringAOP主要想解决什么问题
单例bean是线程安全的吗
回答
- Spring中的bean是单例的,可以用@Scope注解指定单例或者多例
-
一般情况下,我们使用的bean都是一些像Service、DTO这样的类,这些类是不可变类,即这些类的属性(成员变量,不包括成员方法的参数/局部变量)是不可变的,因此在大部分情况下,我们使用的Spring是线程安全
-
但是,如果你自己使用的bean中某些属性(成员变量)是可变的,如count,每调用一次成员方法就使count++,那么当大量请求调用该方法时,对count的修改就是线程不安全的,因此需要通过使用多例或加锁来解决
AOP是什么,如何使用AOP
回答
- AOP就是在将一些公共的逻辑提取出来称为切面,在执行某些方法之前,可以拦截方法并执行这部分逻辑,或者在调用方法之后执行这部分逻辑。这部分公共的逻辑可复用,因此降低了代码的耦合度
2. 使用经历:
- 利用AOP记录操作日志:
首先自定义Log注解
在要调用的方法上加Log注解
封装切面类:加@Aspect注解、定义切入点(指定拦截加了Log注解的方法)、使用@Around、@Before等通知指定公共逻辑的执行时刻
公共逻辑使用一个joinPoint可以获取加了注解的方法的相关消息如方法名、参数等,那么就可以获取request参数,从request参数中就可以获取用户的相关消息,从而记录下操作日志消息
拓展
Spring中事务就是通过AOP实现的
自定义@Transactiobal注解
封装切面类,定义切入点指定拦截加了@Trasactional注解的方法,从而执行公共逻辑,在公共逻辑中开启事务,然后可以使用joinPoint执行目标方法(保存用户),最后提交事务
事务失效的原因
原因一
try-catch导致的事务失效:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉了异常,事务通知无法获悉
防止try-catch导致的事务失效:在catch块添加throw new RuntimeException(e)抛出异常
原因二
抛出检查异常导致事务失效:像RuntimeException是非检查异常,而FileNotFoundException是检查异常,Spring默认只会回滚非检查异常,因此抛出检查异常会使事务失效
防止抛出检查异常导致的事务失效:使用rollback指定回滚所有异常
原因三
非public方法导致的事务失效:Spring为方法创建代理、添加事务通知,前提条件都是该方法是public的
改为public方法即可
其他原因
Spring的事务,使用this调用是否生效
Bean的生命周期
Bean的单例和非单例,生命周期是否一样
Spring的循环引用
类似于死锁,需要了解Bean的生命周期,即先初始化后赋值,在赋值流程中如果相互注入则会死循环
避免循环依赖
使用缓存
一级缓存
一级缓存也就是循环依赖的过程,不能解决循环依赖
二级缓存
二级缓存就是不等Bean对象创建完毕,只要有半成品(已构造但未赋值)就先用半成品,所以有一个半成品池(earlySingletonObjects)来存储半成品,出现循环依赖时直接用半成品打破资源相互依赖的情况,从而先创建出完整的某一方对象,另一方对象则用这个对象依赖注入即可。因此二级缓存可以解决大部分的循环依赖
注意!最后完整的对象都会存入singletonObjects中
三级缓存
如果要创建的Bean对象是代理对象,也就是增强的对象,那么二级缓存是不足够的,二级缓存最终创建出的Bean对象不是代理对象
与二级缓存不同,为了解决资源相互依赖的问题,二级缓存使用的是半成品,而三级缓存使用的是对象工厂,对象工厂与半成品的区别在于对象工厂可以根据需要创建普通对象或者代理对象,因而进一步解决循环依赖的问题
懒加载
缓存只能解决Bean对象在构造后未赋值的循环依赖(即set注入方式),还有一类循环依赖是无法用缓存解决的,即构造器注入,原因是Bean的生命周期中构造函数是第一个执行的,Spring无法解决,在两个类的构造方法中互相用到了对方的对象,解决方法是在方法中的相互依赖参数加注解@lazy,即限定依赖的注入时间,什么时候需要用到依赖,再进行注入
Spring框架的常见注解
Spring的常见注解
SpringMVC相关注解
SpringBoot相关注解
Spring框架中都用到了哪些设计模式
Spring bean的作用域有哪些
主要是bean数量和适用范围的不同
Spring容器里存的是什么
在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做
Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的
Spring给我们提供了很多扩展点,这些有了解吗
Handlermapping 和 handleradapter有了解吗
SpringMVC
MVC分层介绍一下
SpringMVC的执行流程
JSP阶段
回答
首先,前端发送的请求会被后台的前端控制器DispatcherServlet接收,前端控制器就去处理器映射器查询handler(也就是接口方法,需要查询到接口方法的地址、接口方法的类名方法名等信息),查询成功后返回处理器执行链(包括了拦截器和查询到的handler,其中包含拦截器是因为有些接口方法执行之前可能有一些拦截操作)
然后前端控制器根据接收到的处理器执行链,去处理器适配器发送请求执行handler,之所以不直接执行handler而是通过处理器适配器间接执行,是因为不同的接口方法的参数可能不同(如地址参数、json等),通过处理器适配器可以处理好这些参数,同时对执行handler之后的返回值也可以进行处理(因为返回值也可能不同,如字符串、json等),最后向前端控制器返回逻辑视图
接着前端控制器将获得的逻辑视图送到视图解析器进行解析,解析成真正意义上的视图,最后由JSP进行渲染
前后端分离阶段
前五步和JSP阶段一致,只不过执行完handler后不会响应给处理器适配器,而是对一些加了@ResponseBody注解的方法的返回值进行转换为json直接响应给前端
SpringBoot
为什么使用springboot
SpringBoot比Spring好在哪里
SpringBoot用到哪些设计模式
怎么理解SpringBoot中的约定大于配置
Springboot的自动配置原理
回答
首先说明SpringBoot启动类上的注解@SpringBootApplication,这个注解封装了@SpingBootConfiguration、@EnableAutoConfiguration、@ComponentScan,其中@EnableAutoConfiguration是实现自动配置的核心,点进该注解可以发现使用了@Import注解用于读取META-INF/spring.factories里的各种配置类,但这些配置类并不是全都读取的,而是根据@ConditionalOnClass注解判断是否有在pom.xml文件中引入相关依赖(是否有对应的class文件),如果有才会进行加载
说几个启动器(starter)
写过SpringBoot starter吗?
SpringBoot里面有哪些重要的注解?还有一个配置相关的注解是哪个
springboot怎么开启事务
MyBatis
MyBatis执行流程
流程:
首先读取MyBatis的xml配置文件,主要读取连接哪个数据库,有哪些mapper文件
然后构建会话工厂,会话工厂只有一个,但可以创建多个项目与数据库的会话,每次只能操作一个会话,会话包含了执行sql语句的所有方法
接着是执行器,真正执行数据库操作的接口,也负责查询缓存的维护
然后是MappedStatement对象,定义了某些标签中的信息
再然后是将java的数据类型输入参数转换为数据库支持的数据类型,而数据库支持的数据类型输出参数转换为java的数据类型
MyBatis的延迟加载使用及原理
这部分不好做笔记,可以看看黑马的课程
一级缓存、二级缓存
一级缓存是SqlSession级别的,只有在同一个session才能使用缓存,执行一次sql查询,如果是不同的session,还是会进行另外的sql查询
对于不同的session,可以加一个cache标签,仍然可以查询缓存,这就是二级缓存
还记得JDBC连接数据库的步骤吗
如果项目中要用到原生的mybatis去查询,该怎样写
与传统的JDBC相比,MyBatis的优点
Mybatis里的 # 和 $ 的区别
MybatisPlus和Mybatis的区别
MyBatis运用了哪些常见的设计模式
JAVA框架的核心技术:动态代理
明星只负责唱歌、跳舞,像准备工作之类的不应该交给明星来干,所以需要一个代理对象来准备工作,这个代理对象是由中介公司根据所需要的接口生成出来的,确保了生成的代理对象包含sing、dance方法,而代理对象实现sing、dance方法,在sing、dance方法中又调用了明星来唱歌跳舞
其实这个流程与aop很类似,实际上aop底层就是通过动态代理实现的
代码演示
BigStar也要实现Star接口,这是java生成代理的规定
代理对象的sing方法会调用invoke方法,进入到对应的逻辑分支执行操作,最后返回调用的结果
public class ProxyUutil {
// 创建代理对象,返回的是
public static Star createProxy(Bigstar bigstar){
/* newProxyInstance(ClassLoader loader,
Class<?>[]interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器
参数2:指定生成的代理长什么样子,也就是有哪些方法
参数3:用来指定生成的代理对象要干什么事情
*/
//Star starProxy=ProxyUtil.createProxy(s);
//starProxy.sing("好日子")starProxy.dance()
Star starProxy =(star)Proxy.newProxyInstance(ProxyUtil.class.getclassLoader(),
new Class[]{Star.class},
new InvocationHandler(){
//回调方法
@0verride
public 0bject invoke(object proxy, Method method, Object[l args) throws Throwable {
// 代理对象要做的事情,会在这里写代码
// 代理对象通过invoke调用明星执行方法
if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱");
}else if(method.getName().equals("dance")){
System.out.println("准备场地,收钱1000万")
};
// 返回!!不能被代理对象吞了
return method.invoke(bigStar, args);
}
}
}
应用:和aop类似,都能干类似的活,比如打印操作日志,记录执行耗时等