框架

该笔记基于b站黑马程序员Java面试题视频制作



1. 单例bean是线程安全的吗?

1
2
3
4
5
@Service
@Scope("singleton")
public class UserServiceImpl implements UserService {

}
范围 范围
singleton (默认)将每个Spring IoC容器的单个 bean 定义范围限定为单个对象实例。
prototype 将单个 bean 定义的作用域限定为任意数量的对象实例。
request 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个自己的 bean 实例,它是在单个 bean 定义的后面创建的。仅在可感知网络的 Spring 上下文中有效 ApplicationContext
session 将单个 bean 定义的范围限定为 HTTP 的生命周期 Session。仅在可感知网络的 Spring 上下文中有效 ApplicationContext
application 将单个 bean 定义的作用域限定为的生命周期 ServletContext。仅在可感知网络的Spring上下文中有效 ApplicationContext
websocket 将单个 bean 定义的作用域限定为的生命周期 WebSocket。仅在可感知网络的Spring上下文中有效 ApplicationContext

Spring框架中的单例bean是线程安全的吗?

不是线程安全的

Spring框架中有一个@Scope注解,默认的值就是singleton,单例的。

因为一般在spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决




2. AOP

AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

常见的AOP使用场景:

  • 记录操作日志
  • 缓存处理
  • Spring中内置的事务处理

Spring支持编程式事务管理和声明式事务管理两种方式。

  • 编程式事务控制:需使用TransactionTemplate来进行实现,对业务代码有侵入性,项目中很少使用
  • 声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

什么是AOP?

面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合


项目中有没有使用到AOP?

记录操作日志,缓存,spring实现的事务
核心是:使用AOP中的环绕通知+切点表达式(找到要记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库


Spring中的事务是如何实现的?

其本质是通过AOP功能,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。




3. Spring中事务失效的场景有哪些?

  • 异常捕获处理
  • 抛出检查异常
  • 非public方法

情况一:异常捕获处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Transactional
public void update(Integer from, Integer to, Double money) {
try {
//转账的用户不能为空
Account fromAccount = accountDao.selectById(from);
//判断用户的钱是否够转账
if (fromAccount.getMoney() - money >= 0) {
fromAccount.setMoney(fromAccount.getMoney() - money);
accountDao.updateById(fromAccount);
//异常
int a = 1/0;
//被转账的用户
Account toAccount = accountDao.selectById(to);
toAccount.setMoney(toAccount.getMoney() + money);
accountDao.updateById(toAccount);
}
} catch (Exception e) {
e.printStackTrace();
}
}

原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉

解决:在catch块添加throw new RuntimeException(e)抛出


情况二:抛出检查异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void update(Integer from, Integer to, Double money) throws FileNotFoundException {
//转账的用户不能为空
Account fromAccount = accountDao.selectById(from);
//判断用户的钱是否够转账
if (fromAccount.getMoney() - money >= 0) {
fromAccount.setMoney(fromAccount.getMoney() - money);
accountDao.updateById(fromAccount);
//读取文件
new FileInputStream("dddd");
//被转账的用户
Account toAccount = accountDao.selectById(to);
toAccount.setMoney(toAccount.getMoney() + money);
accountDao.updateById(toAccount);
}
}

原因:Spring默认只会回滚会异常检查

解决:配置rollbackFor属性

1
@Transactional(rollbackFor=Exception.class)

情况三:非public方法导致的事务失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(rollbackFor = Exception.class)
void update(Integer from, Integer to, Double money) throws FileNotFoundException {
//转账的用户不能为空
Account fromAccount = accountDao.selectById(from);
//判断用户的钱是否够转账
if (fromAccount.getMoney() - money >= 0) {
fromAccount.setMoney(fromAccount.getMoney() - money);
accountDao.updateById(fromAccount);
//读取文件
new FileInputStream("dddd");
//被转账的用户
Account toAccount = accountDao.selectById(to);
toAccount.setMoney(toAccount.getMoney() + money);
accountDao.updateById(toAccount);
}
}

原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的

解决:改为public方法


Spring中事务失效的场景有哪些?

  1. 异常捕获处理,自己处理了异常,没有抛出,解决:手动抛出
  2. 抛出检查异常,配置rollbackFor属性为Exception
  3. 非public方法导致的事务失效,改为public


4. bean的生命周期

Spring容器在进行实例化时,会将xml配置的bean的信息封装成一个BeanDefinition对象,Spring根据BeanDefinition来创建Bean对象,里面有很多的属性用来描述Bean

1
2
3
4
<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:延迟初始化


  1. 通过BeanDefinition获取bean的定义信息:Spring首先通过BeanDefinition获取Bean的定义信息,包括类名、作用域、构造函数参数、属性值等
  2. 调用构造函数实例化bean:Spring通过反射机制调用Bean的构造函数或工厂方法来创建Bean的实例
  3. bean的依赖注入:Spring将Bean的依赖注入到Bean的实例中
  4. 处理Aware接口:如果Bean实现了Aware接口(如BeanNameAware、BeanFactoryAware、ApplicationContextAware),Spring会调用相应的方法,让Bean获取到相关的资源
    • BeanNameAware:实现该接口的Bean可以获取到Bean的名称
    • BeanFactoryAware:实现该接口的Bean可以获取到BeanFactory组件对象
    • ApplicationContextAware:实现该接口的Bean可以获取到ApplicationContext组件对象
  5. Bean的后置处理器BeanPostProcessor-前置:在Bean初始化之前,Spring会调用BeanPostProcessor的postProcessBeforeInitialization方法
  6. 初始化方法(InitializingBean、init-method):Spring会调用Bean的初始化方法,这可能是InitializingBean接口的afterPropertiesSet方法,或者是在XML中通过init-method属性指定的方法
  7. Bean的后置处理器BeanPostProcessor-后置:在Bean初始化之后,Spring会调用BeanPostProcessor的postProcessAfterInitialization方法
  8. 销毁bean



5. Spring的循环依赖


三级缓存解决循环依赖

Spring解决循环依赖是通过三级缓存,对应的三级缓存如下所示:

1
2
3
4
5
6
7
//单实例对象注册器
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
}
缓存名称 源码名称 作用
一级缓存 singletonObjects 单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
二级缓存 earlySingletonObjects 缓存早期的bean对象(生命周期还没走完)
三级缓存 singletonFactories 缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

一级缓存作用:限制bean在beanFactory中只存一份,即实现singleton scope,解决不了循环依赖


如果要想打破循环依赖, 就需要一个中间人的参与, 这个中间人就是二级缓存


三级缓存:


构造方法中出现了循环依赖怎么解决?

1
2
3
4
5
6
7
8
9
@Component
public class A {
// B成员变量
private B b;
public A(B b){
System.out.println("A的构造方法执行了...");
this.b = b ;
}
}
1
2
3
4
5
6
7
8
9
@Component
public class B {
// A成员变量
private A a;
public B(A a){
System.out.println("B的构造方法执行了...");
this.a = a ;
}
}

报错信息:

1
IS there an unresolvable circular reference?

解决

1
2
3
4
public A(@Lazy B b){
System.out.println("A的构造方法执行了...");
this.b = b ;
}

解释一下Spring中的循环引用?

  • 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
  • 循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
    1. 一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
    2. 二级缓存:缓存早期的bean对象(生命周期还没走完)
    3. 三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

如果构造方法中出现了循环引用怎么解决?

A依赖于B,B依赖于A,注入的方式是构造函数
原因:由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入
解决方案:使用@Lazy进行懒加载,什么时候需要对象再进行bean对象的创建




6. SpringMVC的执行流程

  • 视图阶段(老旧JSP等)
  • 前后端分离阶段(接口开发,异步)

视图阶段(JSP)

  1. 用户发送出请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给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响应用户

前后端分离阶段(接口开发,异步请求)

  1. 用户发送出请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
  3. HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter(处理器适配器)
  5. HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
  6. 方法上添加了@ResponseBody
  7. 通过HttpMessageConverter来返回结果转换为JSON并响应



7. SpringBoot自动装配原理

  • @SpringBootConfiguration:该注解与 @Configuration 注解作用相同,用来声明当前也是一个配置类。
  • @ComponentScan:组件扫描,默认扫描当前引导类所在包及其子包。
  • @EnableAutoConfiguration:SpringBoot实现自动化配置的核心注解。

SpringBoot自动配置原理

  1. 在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
    • @SpringBootConfiguration
    • @EnableAutoConfiguration
    • @ComponentScan
  2. 其中@EnableAutoConfiguration是实现自动化配置的核心注解。 该注解通过@Import注解导入对应的配置选择器。
    内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。 在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
  3. 条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。



8. Spring框架常见注解

Spring 的常见注解

注解 说明
@Component、@Controller、@Service、@Repository 使用在类上用于实例化Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualifier 结合@Autowired一起使用用于根据名称进行依赖注入
@Scope 标注Bean的作用范围
@Configuration 指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan 用于指定 Spring 在初始化容器时要扫描的包
@Bean 使用在方法上,标注将该方法的返回值存储到Spring容器中
@Import 使用@Import导入的类会被Spring加载到IOC容器中
@Aspect、@Before、@After、@Around、@Pointcut 用于切面编程(AOP)

SpringMVC 的常见注解

注解 说明
@RequestMapping 用于映射请求路径,可以定义在类上和方法上。用于类上,则表示类中的所有的方法都是以该地址作为父路径
@RequestBody 注解实现接收http请求的json数据,将json转换为java对象
@RequestParam 指定请求参数的名称
@PathViriable 从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数
@ResponseBody 注解实现将controller方法返回对象转化为json对象响应给客户端
@RequestHeader 获取指定的请求头数据
@RestController @Controller + @ResponseBody

SpringBoot的常见注解

注解 说明
@SpringBootConfiguration 组合了- @Configuration注解,实现配置文件的功能
@EnableAutoConfiguration 打开自动配置的功能,也可以关闭某个自动配置的选
@ComponentScan Spring组件扫描
注解 说明
@Component、@Controller、@Service、@Repository 使用在类上用于实例化Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualifier 结合@Autowired一起使用用于根据名称进行依赖注入
@Scope 标注Bean的作用范围
@Configuration 指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan 用于指定 Spring 在初始化容器时要扫描的包
@Bean 使用在方法上,标注将该方法的返回值存储到Spring容器中
@Import 使用@Import导入的类会被Spring加载到IOC容器中
@Aspect、@Before、@After、@Around、@Pointcut 用于切面编程(AOP)
注解 说明
@RequestMapping 用于映射请求路径,可以定义在类上和方法上。用于类上,则表示类中的所有的方法都是以该地址作为父路径
@RequestBody 注解实现接收http请求的json数据,将json转换为java对象
@RequestParam 指定请求参数的名称
@PathViriable 从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数
@ResponseBody 注解实现将controller方法返回对象转化为json对象响应给客户端
@RequestHeader 获取指定的请求头数据
@RestController @Controller + @ResponseBody


9. MyBatis的执行流程

Mybatis执行流程:

  1. 读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
  2. 构造会话工厂SqlSessionFactory
  3. 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
  4. 操作数据库的接口,Executor执行器,同时负责查询缓存的维护
  5. Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
  6. 输入参数映射
  7. 输出结果映射



10. Mybatis是否支持延迟加载

Mybatis支持延迟记载,但默认没有开启

什么叫做延迟加载?

查询用户的时候,把用户所属的订单数据也查询出来,这个是立即加载
查询用户的时候,暂时不查询订单数据,当需要订单的时候,再查询订单,这个就是延迟加载


延迟加载原理

  1. 使用CGLIB创建目标对象的代理对象
  2. 当调用目标方法user.getOrderList()时,进入拦截器invoke方法,发现user.getOrderList()是null值,执行sql查询order列表
  3. 把order查询上来,然后调用user.setOrderList(List orderList) ,接着完成user.getOrderList()方法的调用



11. Mybatis的一级、二级缓存

  • 本地缓存,基于PerpetualCache,本质是一个HashMap
  • 一级缓存:作用域是session级别
  • 二级缓存:作用域是namespace和mapper的作用域,不依赖于session

一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存

1
2
3
4
5
6
7
8
9
10
11
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行sql
//3.1 获取UserMapper接口的代理对象
UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);
User user = userMapper1.selectById(6);
System.out.println(user);
System.out.println("---------------------");
User user1 = userMapper2.selectById(6);
System.out.println(user1);

二级缓存:是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession1 = sqlSessionFactory.openSession();
//3. 执行sql
//3.1 获取UserMapper接口的代理对象UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectById(6);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
System.out.println("---------------------");
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.selectById(6);
System.out.println(user2);
//4.关闭资源
sqlSession2.close();

二级缓存默认是关闭的
开启方式,两步:

  1. 全局配置文件
1
2
3
4
<settings>
<setting name="cacheEnabled" value="true
</settings>

  1. 映射文件
    使用标签让当前mapper生效二级缓存

注意事项:

  1. 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear
  2. 二级缓存需要缓存的数据实现Serializable接口
  3. 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中


框架
https://lzhengjy.github.io/2023/09/28/框架面试题/
作者
Zheng
发布于
2023年9月28日
许可协议