框架篇总结
Spring
- Spring 框架中的单例 bean 是线程安全的吗?
- 什么是 AOP,你们项目中有没有使用到 AOP ?
- Spring 中的事务是如何实现的
- Spring 中有哪些事务传播行为
- Spring 事务失效的场景有哪些
- Spring 的 bean 的生命周期
- Spring 中的循环引用
- SpringMVC 的执行流程知道嘛
- Springboot 自动配置原理
- Spring 的常见注解有哪些?
- SpringMVC 常见的注解有哪些?
- Springboot 常见注解有哪些?
Mybatis
- MyBatis 执行流程?
- Mybatis 是否支持延迟加载?
- 延迟加载的底层原理知道吗?
- Mybatis 的二级缓存什么时候会清理缓存中的数据
Spring
Bean 线程安全
面试官:Spring框架中的单例 bean 是线程安全的吗?
@Service
@Scope("singleton")
public class UserServiceImpl implements UserService {
}
- Singleton : Bean在每个 Spring IOC 容器中只有一个实例。
- Prototype:一个 bean 的定义可以有多个实例。
Spring bean 并没有可变的状态 (比如Service类和DAO类) ,所以在某种程度上说 Spring 的单例 bean是线程安全的。
但如果存在成员方法则不是线程安全的。
@Controller
@RequestMapping("/user")
public class UserController {
// ! 存在线程安全问题
private int count;
@Autowired
private UserService userService;
@GetMapping("/getById/{id}")
public User getById(@PathVariable("id") Integer id) {
count++;
System.out.println(count);
return userService.getById(id);
}
}
💡思考:Spring 框架中的单例 Bean 是线程安全的吗?
不是线程安全的,Spring中有个 @Scope 注解,默认的值是 Singleton
,单例的。当 Spring 的 Bean 对象是无状态的对象,则是线程安全的。如果在 Bean 中定义了可修改的成员变量,则需要考虑线程安全问题。可以使用多例或加锁来解决。
Bean 生命周期
面试官:SpringBean 的生命周期
回答两方面内容:
- Spring 容器是如何管理和创建 Bean 实例
- 方便调试和解决问题
BeanDefinition
Spring容器在进行实例化时,会将XML配置的 <bean>
的信息封装成 BeanDefinition
对象,Spring根据 BeanDefinition
来创建Bean对象,里面有很多的属性来描述Bean
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" lazy-init="true"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl" scope="singleton">
<property name="userDao" ref="userDao"></property>
</bean>
beanClassName
:bean 的类名initMethodName
:初始化方法名称properryValues
:bean 的属性值scope
:作用域lazyInit
:延迟初始化
Bean生命周期
代码实例:
需要的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
配置代码
@Configuration
@ComponentScan("com.itheima.lifecycle")
public class SpringConfig {
}
用户类
package com.itheima.lifecycle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean {
public User() {
System.out.println("User的构造方法执行了.........");
}
private String name ;
@Value("张三")
public void setName(String name) {
System.out.println("setName方法执行了.........");
}
@Override
public void setBeanName(String name) {
System.out.println("setBeanName方法执行了.........");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("setBeanFactory方法执行了.........");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("setApplicationContext方法执行了........");
}
@PostConstruct
public void init() {
System.out.println("init方法执行了.................");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet方法执行了........");
}
@PreDestroy
public void destory() {
System.out.println("destory方法执行了...............");
}
}
继承了BeanPostProcessor
package com.itheima.lifecycle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("user")) {
System.out.println("postProcessBeforeInitialization方法执行了->user对象初始化方法前开始增强....");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("user")) {
System.out.println("postProcessAfterInitialization->user对象初始化方法后开始增强....");
//cglib代理对象
Enhancer enhancer = new Enhancer();
//设置需要增强的类
enhancer.setSuperclass(bean.getClass());
//执行回调方法,增强方法
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//执行目标方法
return method.invoke(method,objects);
}
});
//创建代理对象
return enhancer.create();
}
return bean;
}
}
测试类
package com.itheima.lifecycle;
import com.itheima.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class UserTest {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = ctx.getBean(User.class);
System.out.println(user);
}
}
打印信息
User的构造方法执行了.........
setName方法执行了.........
setBeanName方法执行了.........
setBeanFactory方法执行了.........
setApplicationContext方法执行了........
postProcessBeforeInitialization方法执行了->user对象初始化方法前开始增强....
init方法执行了.................
afterPropertiesSet方法执行了........
postProcessAfterInitialization->user对象初始化方法后开始增强....
User的构造方法执行了.........
public java.lang.String java.lang.Object.toString()
💡思考:Spring Bean的生命周期
首先会通过一个非常重要的类,叫做 BeanDefinition 获取 Bean 的定义信息,这里面就封装了 Bean 的所有信息,比如类的全路径,是否是延迟加载,是否是单例等等这些信息。
第一步是在创建 Bean 的时候,调用构造函数实例化 Bean
第二步是 Bean 的依赖注入,比如一些 set 方法注入,像平时开发用的 @Autowire 都是这一步完成
第三步是处理 Aware 接口,如果某一个 Bean 实现了 Aware 接口就会重写方法执行
第四步是 Bean 的后置处理器 BeanPostProcessor,这个是前置处理器
第五步是初始化方法,比如实现了接口 InitializingBean 或者自定义了方法 init-method 标签或 @PostContruct
第六步是执行了 Bean 的后置处理器 BeanPostProcessor,主要是对 Bean进行增强,有可能在这里产生代理对象
最后一步是销毁 Bean
Spring AOP
面试官:什么是AOP,你们项目中有没有使用到AOP
AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
常见的AOP使用场景:
- 记录操作日志
- 缓存处理
- Spring中内置的事务处理
Spring中的事务是如何实现的
Spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务控制:需使用 TransactionTemplate 来进行实现,对业务代码有侵入性,项目中很少使用
声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
💡思考:什么是 AOP ,你们项目中有没有使用到 AOP
AOP 的含义是面向切面编程。将与业务无关,但对多个对象产生影响的行为和逻辑,抽取成为公共的功能模块来进行复用。
我们系统中日志文件的方法,菜单按钮权限的管理都是通过 AOP 来实现的。通过切点表达式获取日志记录的方法,然后通过环绕通知获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取这些参数以后保存到数据库。
💡思考:Spring 中的事务是如何实现的?
Spring 本质是 AOP 来实现的,对方法的前后进行拦截,在执行方法之前开启事务,在方法执行之后提交或回滚事务。
Spring 事务传播行为
Spring 事务失效
面试官:对spring框架的深入理解、复杂业务的编码经验
- 异常捕获处理
- 抛出检查异常
- 非public方法
情况一:异常捕获处理
情况二:抛出检查异常
情况三:非public方法导致的事务失效
💡思考:Spring 事务失效的场景有哪些?
- 方法上异常捕获处理,自己处理了异常但是没有抛出就会导致事务失效,所以我们在处理异常之后需要对异常进行抛出。
- 方法抛出异常检查,如果报错也会导致事务失效,因为Spring默认只会回滚非检查异常,所以我们需要在Transactional注解上配置rollbackFor属性为Exception。
- 方法上不是public修饰也会失效。
*Spring 循环依赖
Spring中的循环引用
循环依赖流程图
三级缓存解决循环依赖
一级缓存作用:限制bean在beanFactory中只存一份,即实现singleton scope,解决不了循环依赖。
如果要想打破循环依赖, 就需要一个中间人的参与, 这个中间人就是二级缓存。对象A和对象B从二级缓存存入了一级缓存。
但是一级缓存和二级缓存只能解决一般对象的循环依赖问题。如果A是代理对象,则需要借助三级缓存
三级缓存解决循环依赖
手动解决循环依赖:构造方法出现了循环依赖。
回答要点:Spring中的循环引用
循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:缓存早期的bean对象(生命周期还没走完)
③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
面试官:Spring中的循环引用
候选人:
嗯,好的,我来解释一下
循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:缓存早期的bean对象(生命周期还没走完)
③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
面试官:那具体解决流程清楚吗?
候选人:
第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories
第二,A在初始化的时候需要B对象,这个走B的创建的逻辑
第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories
第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键
第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects
第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects
第七,二级缓存中的临时对象A清除
面试官:构造方法出现了循环依赖怎么解决?
候选人:
由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建
SpringMVC
SpringMVC 的执行流程
面试官:SpringMVC的执行流程你知道吗?
SpringMVC的执行流程是这个框架最核心的内容。
- 视图阶段(老旧JSP等)
- 前后端分离阶段(接口开发,异步)
视图阶段
前后端分离阶段
💡思考:SpringMVC的执行流程知道嘛
1、用户发送出请求到前端控制器 DispatcherServlet,这是一个调度中心
2、DispatcherServlet 收到请求调用 HandlerMapping(处理器映射器)。
3、HandlerMapping 找到具体的处理器 (可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet 调用 HandlerAdapter(处理器适配器)。
5、HandlerAdapter 经过适配调用具体的处理器(Handler/Controller)。
6、Controller 执行完成返回 ModelAndView 对象。
7、HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet。
8、DispatcherServlet 将 ModelAndView 传给 ViewReslover(视图解析器)。
9、ViewReslover 解析后返回具体 View(视图)。
10、DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet 响应用户。
当然现在的开发,基本都是前后端分离的开发的,并没有视图这些,一般都是handler中使用Response直接结果返回
SpringBoot
自动配置原理
SpringBoot 中最高频的一道面试题,也是框架最核心的思想。
@SpringBootConfiguration
: 该注解与@Configutation
注解作用相同,用来声明当前也是一个配置类。@ComponentScan
: 组件扫描,默认扫描当前引导类所在包及其子包。@EnableAutoConfiguration
: SpringBoot实现自动化配置的核心注解。
举例:查看 RedisAutoConfiguration
的配置信息
// 是一个配置类
@AutoConfiguration
// 判断是否有对应的字节码
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
// 判断环境中有没有对应的 Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
💡思考:Springboot自动配置原理
SpringBoot项目中有个注解 @SpringBootApplication,主要由三个注解组成。
@SpringBootConfiguration
表明这个类当前是配置类。@ComponentScan
表明默认扫描当前引导类所在包及其子包。@EnableAutoConfiguration
是实现自动化配置的核心包。其主要通过@Import
注解导入相应的配置。内部读取了该项目和该项目引用的 Jar 包下 classpath 下METE/spring.factories
文件中的所配置类的全类名。并会有@ConditionalOnClass
注解判断是否有对象的 class 文件,如果有则加载该类,把这个配置类的所有 Bean 放入 Spring 容器中。
Spring 框架常见注解
面试官:Spring框架常见注解(Spring、SpringBoot、SpringMVC)
- Spring 常见的注解有哪些?
- SpringMVC 常见的注解有哪些?
- SpringBoot 常见的注解有哪些?
Spring常见注解
SpringMVC常见注解
SpringBoot常见注解
💡思考:Spring 的常见注解有哪些?
第一类是:声明bean,有@Component、@Service、@Repository、@Controller
第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse
第三类是:设置作用域 @Scope
第四类是:spring配置相关的,比如@Configuration,@ComponentScan 和 @Bean
第五类是:跟aop相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut
💡思考:SpringMVC 常见的注解有哪些?
- @RequestMapping:用于映射请求路径;
- @RequestBody:注解实现接收http请求的json数据,将json转换为java对象;
- @RequestParam:指定请求参数的名称;
- @PathViriable:从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数;
- @ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户端。
- @RequestHeader:获取指定的请求头数据,还有像@PostMapping、@GetMapping这些。
💡思考:SpringBoot 常见的注解有哪些?
Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :
- @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
- @ComponentScan:Spring组件扫描
MyBatis
MyBatis执行流程
面试官:MyBatis执行流程是怎么样的,能描述一下吗?
- 理解各个组件的关系
- SQL执行过程(参数映射,SQL解析,执行和结果处理)
核心配置 :mybatis-config.xml
MappendStatement对象
Mybatis执行流程
💡思考:MyBatis执行流程
读取mybatis-config.xml加载运行环境和映射文件,并创建全局唯一的会话工厂SqlSessionFactory,生产SqlSession对象,包含了执行SQL语句的所有方法,并通过Executor执行器来执行数据库操作接口。其中Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息。通过输入参数映射来获取输出结果映射。
Mybatis延迟加载
面试官:Mybatis是否支持延迟加载?
查询用户的时候,把用户所属的订单数据也查询出来,这个是立即加载
查询用户的时候,暂时不查询订单数据,当需要订单的时候,再查询订单,这个就是延迟加载
延迟加载原理
- 使用CGLIB创建目标对象的代理对象
- 当调用目标方法
user.getOrderList()
时,进入拦截器invoke方法,发现user.getOrderList()
是null值,执行sql查询order列 - 把order查询上来,然后调用
user.setOrderList(List<Order> orderList)
,接着完成user.getOrderList()
方法的调用
💡思考:Mybatis是否支持延迟加载?
延迟加载就是指用到数据才加载,没有用到数据不加载。Mybatis支持一对一关联对象和一对多关联对象的延迟加载。可以通过配置是否加载延迟加载来实现,默认是关闭的。
💡思考:延迟加载的底层原理知道吗?
延迟加载底层是通过CGLIB动态代理实现的。通过创建目标对象的代理对象,目标对象是开启延迟加载的mapper,当调用目标方法后进入拦截器invoke方法,发现目标对象为空则执行SQL,在获取数据以后调用SET方法设置属性值,在查询目标方法则会有数据。
Mybatis一级二级缓存
面试官:Mybatis的一级,二级缓存用过吗?
一级缓存
基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存
二级缓存
二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储
注意事项
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear
二级缓存需要缓存的数据实现Serializable接口
只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
💡思考:Mybatis的二级缓存什么时候会清理缓存中的数据
- 一级缓存是基于session的HashMap本地缓存,当session进行flush或close则对缓存进行清空,默认开启。
- 二级缓存是基于namespace和mapper作用域的本地缓存,需要打开配置才会生效。
- 二级缓存会在数据进行新增,修改,删除后对所有默认改作用域下的select缓存进行clear。