Review --- 框架
Spring框架中的bean是单例的吗?
- Spring默认bean是单例的
- 可以通过注解Scope显示定义bean为单例或多例
- singleton:bean在每个Spring IOC容器中只有一个实例
- prototype:一个bean的定义可以有多个实例
Spring框架中的单例bean是线程安全的吗?
成员变量需要考虑线程安全,局部变量一般不需要考虑线程安全。实际开发中应尽量避免使用可变成员变量
如图,每个请求过来都会使这个count+1,不是线程安全的
Spring bean没有可变状态(无状态就是指当前变量能不能被修改),所以在某种程度上说Spring的单例bean是线程安全的.
But 一般在spring的bean中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑安全问题的,可以使用多例或者加锁来解决
综上,
回答参考:
嗯,spring中的单例bean不是线程安全的
当多个用户请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单例状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。
我们通常在项目中使用的Spring bean都是不可变状态(比如service类),所以在某种程度上说Spring的单例bean时线程安全的。
什么是AOP?你们项目中有没有用到AOP?
AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
Spring事务的底层就是AOP,AOP的底层是动态代理,动态代理的底层是反射44
常见的AOP使用场景:
- 记录操作日志
- 缓存处理
- Spring中内置的事务处理
记录操作日志思路
自定义日志注解结合切点表达式,前置通知,后置通知,环绕通知,异常通知等来进行请求信息的捕获和记录(保存)
Spring中的事务是如何实现的?
Spring支持编程式事务管理和声明式事务管理两种方式。
- 变成式事务控制:需使用Transaction Template来进行实现,对业务代码有侵入性,项目中很少使用
- 声明式事务管理:声明式事务管理建立在AOP之上。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编制到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
回答参考:
Spring中事务失效的场景有哪些?
如果业务层关系比较复杂,事务失效必须要注意!!!
在Spring中,事务管理是通过AOP(面向切面编程)实现的。尽管Spring的声明式事务管理非常强大且易于使用,但在某些特定情况下,事务可能会失效。
1. 异常捕获处理不当
- 问题:如果在方法内部捕获了异常,并且没有正确地重新抛出异常(特别是运行时异常),那么Spring事务管理器将不会回滚事务。
- 解决方案:确保在需要回滚事务的地方正确地抛出异常。如果你必须捕获异常,请考虑手动标记事务为回滚状态,例如通过调用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
。
2. 抛出检查异常
- 默认行为:Spring默认只会在遇到未捕获的运行时异常(RuntimeException)或错误(Error)时自动回滚事务。对于检查异常(Checked Exception),默认是不会触发回滚的。
- 解决方案:可以通过指定
rollbackFor
属性来改变这种行为,让特定的检查异常也能触发回滚。@Transactional(rollbackFor = Exception.class) public void someMethod() throws Exception {// 方法体 }
3. 非public方法
- 问题:只有被
@Transactional
注解标注的public方法才能启动事务。如果在非public方法上添加此注解,则该注解将不起作用。 - 解决方案:确保事务性的业务逻辑位于public方法中,并且这些方法上有
@Transactional
注解。
其他需要注意的情况
4. 自调用问题
- 问题:在一个类内部直接调用另一个带有
@Transactional
注解的方法不会触发事务代理,因为这是直接的方法调用而不是通过Spring AOP代理进行的。 - 解决方案:可以使用AOP代理的方式来调用目标方法,或者重构代码以避免自调用问题。例如,通过注入自身实例的方式来进行调用:
@Autowired private ApplicationContext applicationContext;public void callingMethod() {MyService self = applicationContext.getBean(MyService.class);self.transactionalMethod(); }
5. 嵌套事务
- 问题:Spring不支持真正的嵌套事务。虽然可以配置传播行为为
Propagation.NESTED
,但这实际上是保存点机制,而不是真正意义上的嵌套事务。 - 解决方案:理解并正确配置事务的传播行为,根据实际需求选择合适的传播方式。
6. 数据源配置错误
- 问题:如果数据源配置有误,比如事务管理器未正确绑定到正确的数据源,也可能导致事务无法正常工作。
- 解决方案:确保数据源和事务管理器的配置正确无误。
7. 使用不当的事务隔离级别
- 问题:选择不适当的事务隔离级别可能导致并发问题,如脏读、不可重复读等,影响系统性能和数据一致性。
- 解决方案:根据应用的实际需求选择合适的事务隔离级别。
总结
在复杂的业务层关系中,确保事务的有效性尤为重要。除了注意上述提到的常见失效场景外,还应该仔细设计事务边界,合理设置事务传播行为和隔离级别,以及正确处理异常情况,这样才能保证事务按照预期工作,维护数据的一致性和完整性。
Spring的bean的生命周期?
- 通过BeanDefinition获取bean的定义信息
- 调用构造函数实例化bean
- bean的依赖注入
- 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)
- Bean的后置处理器BeanPostProcessor-前置(它允许你在Spring容器完成bean实例化之后但在Spring容器将其交给应用程序使用之前对bean进行修改或增强)
- 初始化方法(InitializingBean、init-method(自定义初始化方法))
- Bean的后置处理器BeanPostProcessor-后置
- 销毁bean
Bean的创建和初始化赋值是分开的
了解Spring容器是如何管理和创建bean实例的
方便进行调试和解决问题
BeanDefinition(bean的定义信息)
Spring容器在进行实例化时,会将xml配置的的信息封装成一个BeanDefinition对象,Spring根据BeanDefinition来创建Bean对象,里面有很多的属性用来描述Bean
回答参考
Spring中的循环引用问题
A B循环依赖分析流程
如何解决循环依赖问题?Spring其实已经解决了大部分的循环依赖问题。
三级缓存解决循环依赖
Spring解决循环依赖是通过三级缓存,对应的三级缓存如下所示:
一级缓存作用:限制bean在beanFactory中只存一份,即实现singleton scope,解决不了循环依赖(只有走完bean生命周期的单例bean才能被缓存)
如果要想打破循环依赖,就需要一个中间人的参与,这个中间人就是二级缓存。
三级缓存存储的是对象工厂,可以生产普通对象和代理对象,主要解决代理对象问题。
构造方法循环引用如何解决?
给其中一个bean加@Lazy注解进行懒加载,需要使用时再进行实例化
回答参考:
- 循环依赖:循环依赖也叫循环引用,就是由两个或两个以上的bean互相持有对方,最终形成闭环,比如A依赖于B,B依赖于A
- 循环依赖在spring中式允许存在的,spring通过三级缓存已经解决了大部分的循环依赖
一级缓存:单例池,用来存储走完bean生命周期的单例bean
二级缓存:存储半成品的bean对象
三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建普通/代理对象的
SpringMVC的执行流程是什么?
SpringMVC的执行流程是这个框架最核心的内容
- 视图阶段(老旧JSP等)
- 前后端分离阶段(接口开发,异步)
视图阶段版本:jsp
- 用户发送出请求到前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
- HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet
- DispatcherServlet调用HandlerAdapter(处理器适配器)
- HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
- Controller执行完成返回ModelAndView对象
- HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
- DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)
- ViewReslover解析后返回具体View(视图)
10.DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
DispatcherServlet响应用户
前后端分离版本
- 用户发送出请求到前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
- HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet
- DispatcherServlet调用HandlerAdapter(处理器适配器)
- HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
- 方法上添加了@RequestBody
- 通过HttpMessageConverter来返回结果转换为JSON并响应
SpringBoot自动配置原理
SpringBoot自动装配原理是最高频的一道面试题,也是框架最核心的思想
SpringBoot的自动配置主要依赖@SpringBootApplication注解,这个注解里面包含了另外三个注解
- @SpringBootConfigruation:该注解与 @Configuration注解作用相同,用来声明当前类是一个配置类
- @ComponentScan:组件扫描,默认扫描当前引导类所在包及其子包
- @EnableAutoConfiguration:SpringBoot实现自动化配置的核心注解
回答参考
在SpringBoot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
其中***@EnableAutoConfiguration是实现自动化配置的核心注解,该注解通过@Import注解导入对应的配置选择器。内部就是读取了该项目引用的Jar包的classpath路径下 META-INF/spring.factories 文件中的所配置的类的全类名。在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定***是否需要将其导入到Spring容器中。
条件判断会有像***@ConditionalOnClass***这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有Bean放入spring容器中使用。
Spring框架常见注解(Spring、SpringBoot、SpringMVC)
- Spring常见注解有哪些?
- SpringMVC常见的注解有哪些?
- SpringBoot常见注解有哪些?
Spring 核心注解
注解 | 说明 |
---|---|
@Component 、@Controller 、@Service 、@Repository | 类级别注解,用于实例化Bean(分别表示通用组件、控制层、服务层、持久层) |
@Autowired | 根据类型自动依赖注入 |
@Qualifier | 结合@Autowired ,按名称指定注入的Bean |
@Scope | 定义Bean作用域(如singleton 、prototype ) |
@Configuration | 标记类为配置类,替代XML配置 |
@ComponentScan | 指定Spring扫描的包路径 |
@Bean | 方法级别注解,将返回值注册为Bean |
@Import | 导入其他配置类或普通类到IOC容器 |
@Aspect 、@Before 、@After 、@Around 、@Pointcut | AOP切面编程相关注解 |
@Lazy | 延迟初始化Bean |
@Primary | 优先注入同类型Bean中的指定实例 |
@Profile | 环境隔离(如dev /prod ) |
@PostConstruct 、@PreDestroy | 生命周期方法(JSR-250) |
@Value | 注入属性值(如${property} ) |
@PropertySource | 加载外部配置文件 |
Spring MVC 注解
注解 | 说明 |
---|---|
@RequestMapping | 映射请求路径(类/方法级别),可指定HTTP方法 |
@RequestBody | 接收HTTP请求的JSON数据并转换为Java对象 |
@RequestParam | 获取请求参数(可指定默认值和是否必填) |
@PathVariable | 从URL路径中提取变量(如/user/{id} ) |
@ResponseBody | 将方法返回值直接作为HTTP响应体(如JSON) |
@RequestHeader | 获取请求头信息 |
@RestController | @Controller + @ResponseBody 组合注解 |
@GetMapping | 限定GET请求的简写形式 |
@PostMapping | 限定POST请求的简写形式 |
@PutMapping 、@DeleteMapping | 限定PUT/DELETE请求的简写形式 |
@ModelAttribute | 绑定请求参数到Model对象或向Model添加属性 |
@SessionAttributes | 将Model中的属性存储到Session |
@CookieValue | 获取Cookie值 |
@CrossOrigin | 配置跨域请求支持 |
Spring Boot 注解
注解 | 说明 |
---|---|
@SpringBootConfiguration | 标记类为Spring Boot配置类(实际开发中更常用@SpringBootApplication ) |
@EnableAutoConfiguration | 启用自动配置机制 |
@ComponentScan | 自动扫描组件包路径 |
@SpringBootApplication | 核心注解,包含@Configuration +@EnableAutoConfiguration +@ComponentScan |
@Conditional 系列 | 条件化配置(如@ConditionalOnClass 、@ConditionalOnMissingBean ) |
@ConfigurationProperties | 批量绑定配置文件属性到Bean |
@EnableScheduling | 启用定时任务 |
@EnableAsync | 启用异步方法调用 |
@EnableCaching | 启用缓存支持 |
@EnableWebMvc /@EnableWebFlux | 显式启用MVC或响应式Web配置 |
Mybatis执行流程
- 读取Mybatis配置文件:mybatis-config。xml加载运行环境和映射文件
- 构建绘画工厂SqlSessionFactory
- 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
- 操作数据库的接口,Excutor执行器,同时负责查询缓存的维护
- Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
- 输入参数映射
- 输出结果映射
Mybatis是否支持延迟加载
Mybatis支持延迟加载,但默认没有开启
什么是延迟加载呢?
就是按需加载,需要时再加载。不需要这个信息就不查询,提高性能
比如下面这个xml文件内容,fetchType=“lazy”,就对查询订单开启了延迟加载
也可以全局配置mybatis进行延迟加载
在mybatis的配置文件中,设置lazyLoadingEnabled的值为true
延迟加载的原理
- 使用CGLIB创建目标对象的代理对象
- 当调用目标方法user.getOrderList()时,进入拦截器invoke方法,发现user。。getOrderList()是null值,执行sql查询order列表
- 把order查询上来,然后调用user.setOrderList(List orderList),接着完成user.getOrderList()的调用
参考回答
Mybatis的一级、二级缓存
一级缓存
一级缓存是基于PerpetualCached的HashMap 本地缓存,其存储作用域为Session,当Session进行flush或close之后,该Session中的所有Cache就清空,Mybatis默认打开一级缓存
二级缓存
二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SqlSession的,默认也是采用PerpetualCache,HashMap 存储
注意事项:
- 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 的缓存将被clear
- 二级缓存需要缓存的数据必须要实现Serializable接口
- 只有回话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中