1. Spring AOP默认使用什么动态代理,两者的区别
spring aop默认使用JDK动态代理,但是springboot 2.x版本都改成使用cglib动态代理,因为JDK动态代理时,没有接口会报错,虽然效果差不多,但是cglib更方便。
区别:
- JDK动态代理只能代理实现了接口的类,他通过反射机制在运行时创建一个实现了指定接口的代理对象。
- cglib通过生成目标类的子类来实现代理,因此可以代理没有实现接口的类
- cglib的性能通常优于jdk动态代理,因为他是通过字节码生成的,避免了反射的开销,cglib不能代理final类和final方法,因为他无法继承这些类和方法。
2. Spring AOP和AspectJ有什么区别
AOP是一种编程范式,主要用于模块化横切关注点
,这些关注点通常贯穿多个对象或者类,例如事务管理、日志记录、安全控制等。AOP允许将这些关注点从业务逻辑中分离出来,并以声明的方式添加到应用程序中。
Spring AOP是动态代理,AspectJ是静态代理。一个是运行时织入,一个在编译时织入,在调用时候AspectJ没有额外的织入开销,性能更好些。
且AspectJ提供完整的AOP解决方案,像Spring AOP只支持方法级别的织入,且AspcetJ支持字段、方法、构造函数等,所以更加强大。
3. Spring Bean的生命周期
- 实例化:Spring容器根据配置文件或者注解实例化Bean对象。
- 属性注入:Spring将依赖注入到Bean实例中。
- 初始化前的扩展机制:如果Bean实现了BeanNameAware等aware接口,则执行aware注入。
- 初始化前:在Bean初始化前,可以通过BeanPostProcessor接口对bean进行一些额外的处理。
- 初始化:调用InitialzingBean接口的afterPropertiesSet()方法或者通过init-method属性指定的初始化方法。
- 初始化后:在Bean初始化后,可以通过BeanPostProcessor进行进一步的处理。
- 使用Bean:Bean已经初始化完成,可以被容器中的其他Bean使用。
- 销毁:当容器关闭时,Spring调用DisposableBean接口的destroy()方法。
1. 理解生命周期
之所以Bean容易被添加一些属性,或者能在运行时被改造就是因为在生成Bean的时候,Spring对外暴露出很多扩展点。
基于这些点,我们可以设置一些逻辑,Spring会在Bean创建的某些阶段根据这些扩展点,基于此进行Bean的改造。
Spring Bean的生命周期如下
对应上图的生命周期,进一步划分如下
注意细节,这幅图的颜色和上面那副有对应关系。
2. Spring Bean生命周期详细步骤
- 实例化阶段:Bean的实例化是通过反射机制创建的。Spring根据@Component、@Bean或者XML中的< bean>元素配置,来确定要创建的Bean。
- 属性赋值阶段:在实例化完成后,Spring会进行依赖注入。这包括将属性值注入到Bean的字段中,可能是通过构造函数注入、setter方法注入、或者直接字段注入。
- 初始化前的扩展机制:Bean可以实现BeanNameAware、BeanFactoryAware等Aware接口,从而在初始化之前获取Bean的名称,BeanFactory、ApplicationContext等容器资源。例如,ApplicationContextAware接口允许Bean获取ApplicationContext,以便进一步和Spring容器交互。
- BeanPostProcessor的作用:BeanPostProcessor接口允许开发者在Bean初始化前后添加自定义逻辑。例如,可以在postProcessBeforeInitialization方法中执行某些前置操作,如代理包装、AOP切面等。在postProcessAfterInitialization中,可以进一步修改或者替换Bean实例。
- 初始化的细节:InitializingBean接口提供了一个afterPropertiesSet方法,用于在Bean的所有属性设置完成后执行一些自定义初始化逻辑,开发者也可以通过@PostConstruct注解,来指定初始化方法。
- Bean的就绪状态:Bean完成初始化后,即进入就绪状态,在此状态下,Bean已经完成了所有的属性设置和初始化步骤,处于可用状态。
- 销毁阶段的清理:Bean的销毁通常在容器关闭时进行,DisposableBean接口提供了destroy方法,用于清理资源。开发者也可以通过@PreDestroy注解或者配置中的destroy-method属性,指定清理逻辑。
3. Bean的作用域和生命周期关系
- singleton:默认作用域,Bean的生命周期和Spring容器的生命周期一致。在容器启动时创建,在容器关闭时销毁。
- prototype:每次请求时创建一个新的Bean实例,容器只负责创建,不管理其生命周期。
- request、session、Application、websocket:这些作用域用于Web应用中,Bean的生命周期分别和HTTP请求、会话、应用或者websocket的生命周期一致。
4. 常见的生命周期应用场景
- 连接池管理:在初始化阶段创建数据库连接池,在销毁阶段关闭连接池。
- 缓存初始化:在afterProperties或者@PostConstruct中加载缓存数据,在销毁阶段清理缓存。
- 动态代理创建:在postProcessBeforeInitialization中为Bean创建动态代理,以实现AOP功能。
4. 介绍Spring MVC
他是Spring中基于经典的MVC模式来开发Web应用的模块,SpringMVC将请求处理流程分为三层:模型层(Model)、视图层(View)、和控制层,提供了一种松耦合的方式将用户请求、业务逻辑和视图渲染分离开来。
Spring MVC基于Servlet API构建,可以说核心就是 DispatcherServlet,即前端控制器。他通过注解、配置等方式,将HTTP请求映射到控制器方法,然后由控制器处理请求逻辑并将数据返回给视图层进行渲染。
Spring MVC工作流程
- 客户端请求:浏览器向服务器发送HTTP请求。
- DispatcherServlet:所有的请求首先由Spring MVC的核心前端控制器DispatcherServlet接收,他充当整个流程的调度中心。
- 处理器映射(Handler Mapping):DispatcherServlet根据请求的URL使用处理器映射器找到对应的控制器。
- 控制器(Controller):控制器接受请求并处理业务逻辑,通常通过注解@Controller和@RequestMapping定义请求的映射方法。
- 模型和视图(ModelAndView):控制器处理完业务逻辑后,将数据封装到模型对象中,并指定返回的视图名称。
- 视图解析器(ViewResolver):DispatcherServlet调用视图解析器,将逻辑视图名称解析为实际的视图。
- 视图渲染:视图渲染引擎根据模型中的数据生成HTML页面并返回给客户端。
5. Spring MVC具体的工作原理
- 客户端请求:用户发送HTTP请求,DispatcherServlet接收请求。
- 执行拦截器的preHandle(): 如果配置了拦截器,SpringMVC首先执行preHandle()方法,如果返回false,请求处理终止;否则,继续处理。
- 请求映射:DispatcherServlet通过HandlerMapping找到对应的HandlerExecutionChain(这里面包含了很多定义的HandlerInterceptor,拦截器)然后通过HandlerAdaptor适配器的适配后,执行Handler,即通过controller的调用。
- 执行控制器方法:如果返回的是视图,则执行视图解析器进行视图解析和渲染;如果返回的是对象,则通过消息转换器将对象转换为JSON数据。
- 执行拦截器的postHandle()方法
- 视图渲染或者JSON响应:对于视图,视图解析器会解析视图名称,并将其渲染为HTML页面,对于JSON响应,Spring MVC会将对象序列化为JSON,并返回给客户端。
- 执行拦截器的afterCompletion:视图渲染或者JSON响应完成后,Spring MVC调用拦截器的afterCompletion方法,进行资源清理或者日志记录
- 响应客户端:最终将渲染的视图或JSON数据返回给客户端。
拦截器的工作流程
拦截器在Spring MVC的请求处理流程中起到类似过滤器的作用,但比过滤器更具灵活性。允许开发者在请求进入控制器之前、控制器处理完成之后以及视图渲染前后执行自定义逻辑。常用的应用场景包括日志记录、用户认证、权限校验等。拦截器的三个核心方法
- preHandle:在控制器方法执行之前调用,如果返回false,请求将被拦截。
- postHandle:在控制器方法执行之后,视图渲染之前调用,可以用于修改模型数据或者视图
- afterCompletion:在视图渲染完成后调用,用于清理资源或者记录执行时间。
6. SpringMVC父子容器
父容器:父容器指的是Spring的根容器,通常是Spring应用上下文,如ApplicationContext,主要用于管理应用程序的全局Bean,如服务层、数据访问层等。
子容器:Web容器,每个DispatcherServlet实例都会创建一个子容器,用于管理Web层中的Bean。
父子容器的关系
- 子容器可以访问父容器的Bean:如果一个Bean在父容器中定义,子容器可以直接访问他。这种机制有助于Web层(子容器)使用服务层或者DAO层中的Bean。
- 父容器不能访问子容器的Bean:父容器中无法访问子容器中的Bean,父容器中的Bean和子容器中的Bean被分开管理,避免了不必要的耦合。
1. Spring父子容器的初始化过程
在Spring MVC中,Spring父子容器的初始化是分阶段完成的。
父容器初始化:通常通过ContextLoaderListener或者ContextLoaderServlet在应用启动时初始化根容器,也即父容器。这个容器管理了应用的核心服务类(如Service、Dao)等。
子容器初始化:当DispatcherServlet启动时,他会创建自己的子容器,用于加载和Web相关的Bean.子容器可以访问父容器中的Bean,但只能管理Web层的Bean。
2. 父子容器的意义
- 服务层和Web层分离:通过父子容器,开发者可以将业务逻辑和Web层分离,全局的Bean,如数据库连接池、事务管理等,可以放在父容器中,而与Web相关的控制器放在子容器中。
- 避免Bean重复定义:父容器和子容器的分离可以防止重复定义Bean。例如,数据层的配置可以在父容器中定义,Web层的配置可以在子容器中定义,这样可以防止冲突。
- 分模块管理:对于复杂的大型系统,可以通过父子容器来实现模块化管理。例如,可以将公共服务层的逻辑放在父容器中,多个不同模块的控制器放在各自的子容器中管理。
7. Spring都用到哪些设计模式
- 工厂模式:BeanFactory,整个Spring IOC都是一个工厂。
- 模版方法:例如JdbcTemplate、RestTemplate。
- 代理模式:AOP
- 单例模式:所有的Bean默认都是单例
- 责任链模式:比如Spring MVC中的拦截器,多个拦截器串联起来就形成了责任链
- 观察者模式:在Spring中的监听器实现
- 适配器模式:Spring MVC中的handlerApdator。
8. Spring事务隔离级别
Spring提供了五种事务隔离级别
- Default:使用底层数据库的默认隔离级别,如果数据库没有特定的设置,通常默认为read_committed.
- read_uncommitted(读未提交):最低的隔离级别,允许事务读取尚未提交的数据,可能导致脏读、幻读、不可重复读。
- read_committed(读已提交): 仅允许读取已经提交的数据,避免了脏读,但可能出现不可重复读和幻读。
- repeatable_read(可重复读):确保在同一个事务内的多次读取结果一致,避免脏读和不可重复读,但可能会有幻读。
- serializable(串行化):最高的隔离级别,通过强制事务按顺序执行,完全避免脏读、不可重复读和幻读,代价是性能显著下降。
隔离级别和性能之间的权衡
- 低隔离级别(read_uncommitted和read_committed):性能高,但可能存在并发问题,适合数据一致性要求不高的场景。
- 高隔离级别(repeatable_read和serializable):数据一致性强,但是性能较差,适合高数据一致性要求的场景。
9. Spring有哪几种事务传播行为
- REQUIRED:如果当前存在事务,则用当前事务,如果没有事务则新起一个事务。
- SUPPORTS: 支持当前事务,如果不存在,则以非事务方式运行
- MANDATORY: 支持当前事务,如果不存在,则抛出异常。
- REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起当前事务。
- NOT_SUPPORTED: 不支持当前事务,始终以非事务方式执行
- NEVER:不支持当前事务,如果当前存在事务,则抛出异常
- NESTED:如果当前事务存在,则在嵌套事务中执行,内层事务依赖外层事务,如外层失败,则回滚内层,内层失败不影响外层。
应用场景扩展
- required
- 应用场景:常见的业务逻辑调用,比如,在订单创建调用库存减少的方法,他们都应该共享同一个事务。
- 优点:事务复用,性能开销较小,适用于大多数业务逻辑。
- requires_new
- 应用场景:日志记录、通知服务等,即使主事务失败,独立事务的操作也应该成功执行。
- 优点:事务隔离,防止主要事务的失败影响到辅助操作。
- supports
- 应用场景:可选的事务支持,比如,某个方法可以在事务外部或者内部执行。
- not_supported
- 应用场景:需要明确禁止事务的场景,比如读取配置信息、不需要事务控制的数据查询。
- 优点:避免不必要的事务开销
- mandatory
- 应用场景:必须在现有事务中执行的场景,常用于确保方法调用链的一致性。
- Never
- 应用场景:需要保证绝对没有事务的场景,比如某些不允许在事务中执行的数据库操作。
- Nested
- 应用场景:需要部分回滚或者局部事务的业务逻辑,比如,订单中的部分操作可能会失败,但不希望整个订单回滚。
10. Spring事务传播行为的作用
主要作用是定义和管理事务边界,尤其是一个事务方法调用另一个事务方法时,事务如何传播的问题。他解决了多个事务方法嵌套执行时,是否要开启新事务、复用现有事务或者挂起事务等复杂情况。
- 控制事务的传播和嵌套:根据具体业务需求,可以指定是否使用现有事务或者开启新的事务,解决事务的传播问题。
- 确保独立操作的事务隔离:某些操作应当独立于主事务执行,即使主事务失败,这些操作也可以执行成功。
- 控制事务的边界和一致性:不同的业务场景可能需要不同的事务边界,例如强制某个方法必须在事务中执行,或者确保某个方法永远不再事务中执行。