文章目录
- Spring基础
- Spring进阶
- applicationContextAware接口使用
- FactoryBean与BeanFactory
- InitializingBean接口使用
- BeanFactory和ApplicationContext有什么区别
- Spring的单例实现原理
- Spring框架中用到的哪些设计模式
- 循环依赖
- spring容器启动过程中开发者可以做哪些增强?
- spring默认生成bean name的规则是什么
- Spring事件监听的核心机制是什么?
- 什么情况下AOP会失效,怎么解决?
- Spring AOP and AspectJ AOP 有什么区别?
- Spring-AOP通知和执行顺序?
- spring 自动装配 bean 有哪些方式
- Spring框架中的单例bean是线程安全的吗
- 解释下Spring支持的几种bean的作用域
- Spring事务
- Spring使用场景
Spring基础
谈谈你对Spring的理解
可以从2个层面理解Spring:
- 通常Spring指的就是Spring Framework,它有两大核心:IOC和AOP。
- Spring是一个生态:可以构建企业级应用程序所需的一切基础设施 ,除了这两大核心还提供了丰富的功能和模块, 数据访问、事务管理、Web开发等。数据访问模块提供了对数据库的访问支持,可以方便地进行数据库操作。事务管理模块提供了对事务的管理支持,确保数据的一致性和完整性。Web开发模块则提供了构建Web应用程序的工具和框架,简化了Web开发的过程。
解释IOC/DI与AOP原理。
IOC(Inversion of Control):控制反转,即把对象的获取(控制权)交给容器管理,使用时注入即可,所以也叫依赖注入DI(dependency Injection)。
AOP(Aspect-Oriented Programming)面向切面编程,使用动态代理的方式实现。
SpringIOC如何实现
IoC 容器是 Spring用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
Spring IoC 的实现机制是什么?
Spring的IoC底层实现机制主要依赖于以下几个关键组件和技术:
- 反射:Spring使用Java的反射机制来实现动态创建和管理Bean对象。通过反射,Spring可以在运行时动态地实例化Bean对象、调用Bean的方法和设置属性值。
- 配置元数据:Spring使用配置元数据来描述Bean的定义和依赖关系。配置元数据可以通过XML配置文件、注解和Java代码等方式进行定义。Spring在启动时会解析配置元数据,根据配置信息创建和管理Bean对象。
- Bean定义:Spring使用BeanDefinition来描述Bean的属性、依赖关系和生命周期等信息。Bean定义可以通过XML配置文件中的<bean>元素、注解和Java代码中的@Bean注解等方式进行定义。Bean定义包含了Bean的类名、作用域、构造函数参数、属性值等信息。
- Bean工厂:Spring的Bean工厂负责创建和管理Bean对象。Bean工厂可以是BeanFactory接口的实现,如DefaultListableBeanFactory。Bean工厂负责解析配置元数据,根据Bean定义创建Bean对象,并将其放入容器中进行管理。
- 依赖注入:Spring使用依赖注入来解决Bean之间的依赖关系。通过依赖注入,Spring容器负责将Bean所依赖的其他Bean实例注入到它们之中。Spring使用反射和配置元数据来确定依赖关系,并在运行时进行注入。
总结起来,Spring的IoC底层实现机制主要依赖于反射、配置元数据、Bean定义、Bean工厂和依赖注入等技术和组件。通过这些机制,Spring实现了Bean的创建、配置和管理,以及Bean之间的解耦和依赖注入
SpringAOP实现原理
Spring AOP是Spring框架的一个重要组成部分,用于实现面向切面编程。它通过在方法调用前、调用后或异常抛出时插入通知,允许开发者在核心业务逻辑之外执行横切关注点的代码。
底层实现主要分两部分:创建AOP动态代理和调用代理
在启动Spring会创建AOP动态代理:
首先通过AspectJ解析切点表达式: 在创建代理对象时,Spring AOP使用AspectJ来解析切点表达式。它会根据定义的条件匹配目标Bean的方法。如果Bean不符合切点的条件,将跳过,否则将会通动态代理包装Bean对象:具体会根据目标对象是否实现接口来选择使用JDK动态代理或CGLIB代理。这使得AOP可以适用于各种类型的目标对象。
- 如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDKProxy,去创建代理对象。
- 而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用基于asm框架字节流的Cglib动态代理 ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理。
在调用阶段:
- Spring AOP使用责任链模式来管理通知的执行顺序。通知拦截链包括前置通知、后置通知、异常通知、最终通知和环绕通知,它们按照配置的顺序形成链式结构。
- 通知的有序执行: 责任链确保通知按照预期顺序执行。前置通知在目标方法执行前执行,后置通知在目标方法成功执行后执行,异常通知在方法抛出异常时执行,最终通知无论如何都会执行,而环绕通知包裹目标方法,允许在方法执行前后添加额外的行为。
综上所述,Spring AOP在创建启动阶段使用AspectJ解析切点表达式如果匹配使用动态代理,而在调用阶段使用责任链模式确保通知的有序执行。这些机制共同构成了Spring AOP的底层实现。
SpringBean的生命周期
- 实例化一个Bean:a. 通过反射去推断构造函数进行实例化。b. 实例工厂、 静态工厂。
- 依赖注入(DI): 解析自动装配(byname bytype constructor none @Autowired),按照Spring上下文对实例化的Bean进行配置。
- 初始化+钩子调用:
(1) 调用很多Aware回调方法:a. BeanNameAware:如果实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,beanId是配置文件中bean的id。b. ApplicationContextAware:如果实现了ApplicationContextAware接口,会调用setApplicationContext(applicationContextAware)方法。
(2)BeanPostProcessor:如果关联了BeanPostProcessor接口,则会在初始化之前调用postProcessBeforeInitialization方法,实现自己的逻辑。
(3)初始化(init):如果在Spring配置文件中配了init-method方法,会自动调用。
(4)BeanPostProcessor:如果关联了BeanPostProcessor接口,则会在初始化之后调用postProcessAfterInitialization方法,实现自己的逻辑。- 销毁:
(1)DisposableBean:当bean不再需要时,如果bean实现了DisposableBean接口,会调用其实现的destroy()方法。
(2)destroy-method:如果Spring配置中配置了destroy-method方法,会自动调用。
动态代理(cglib与jdk)
1.JDK是只能对实现了接口的类生成代理,而不是针对类。cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。
2.当bean实现接口时,Spring就会用jdk动态代理,没有实现接口时使用cglib,也可以通过在spring配置中加入(< aop:aspectj-autoproxy proxy-target-class=“true”/>)强制使用cglib。
3.cglib是使用ASM字节码生成框架,生成子类字节码,比Java反射效率更高。但有测试jdk性能也不一定比cglib差。
JDK动态代理和CGLIB动态代理的区别
- 从性能上特性对比:
JDK动态代理要求目标对象必须实现至少一个接口,因为它基于接口生成代理类。而CGLIB动态代理不依赖于目标对象是否实现接口,可以代理没有实现接口的类,它通过继承或者代理目标对象的父类来实现代理。- 从创建代理时的性能对比:
JDK动态代理通常比CGLIB动态代理创建速度更快,因为它不需要生成字节码文件。而CGLIB动态代理的创建速度通常比较慢,因为它需要生成字节码文件。另外,JDK代理生成的代理类较小,占用较少的内存,而CGLIB生成的代理类通常较大,占用更多的内存。- 从调用时的性能对比:
JDK动态代理在方法调用时需要通过反射机制来调用目标方法,因此性能略低于CGLIB,尽管JDK动态代理在Java 8中有了性能改进,但CGLIB动态代理仍然具有更高的方法调用性能。CGLIB动态代理在方法调用时不需要通过反射,直接调用目标方法,通常具有更高的方法调用性能,同时无需类型转换。选择使用JDK动态代理还是CGLIB动态代理取决于具体需求。如果目标对象已经实现了接口,并且您更关注创建性能和内存占用,那么JDK动态代理可能是一个不错的选择。如果目标对象没有实现接口,或者您更关注方法调用性能,那么CGLIB动态代理可能更合适。综上所述,这两种代理方式各有优势,根据实际情况进行选择是明智的, Spring默认情况如果目标类实现了接口用JDK代理否则用CGLIB。 而SpringBoot默认用CGLIB,所以用哪个问题都不大。
Bean有哪几种配置方式?
在Spring框架中,有以下几种常见的Bean配置方式:
- XML配置:使用XML文件来配置Bean,通过元素定义Bean的属性和依赖关系。可以使用Spring的XML命名空间和标签来简化配置。
- 注解配置:使用注解来配置Bean,通过在Bean类上添加注解,如@Component、@Service、@Repository等,来标识Bean的角色和作用。
- JavaConfig方式:使用Java类来配置Bean,通过编写一个配置类,使用@Configuration注解标识,然后在方法上使用@Bean注解来定义Bean。
- @Import:@Import注解可以用于导入其他配置类,也可以用于导入其他普通类。当导入的是配置类时,被导入的配置类中定义的Bean会被纳入到当前配置类的上下文中;当导入的是普通类时,被导入的类本身会被当作一个Bean进行注册。
这4种是最常用的,另外还有两种一些冷门的:
- Groovy配置:使用Groovy脚本来配置Bean,通过编写一个Groovy脚本文件,使用Spring的DSL(Domain Specific Language)来定义Bean。
- JSR-330:Spring提供了对JSR-330标准注释(依赖注入)的支持,可以使用@Named 或 @ManagedBean替代@Component,但是需要javax.inject依赖。
这些配置方式可以单独使用,也可以结合使用,根据项目需求和个人偏好选择适合的配置方式。
Spring-Ioc容器的加载过程
Spring 的 IOC 容器工作的过程,其实可以划分为两个阶段:配置解析阶段和Bean 的创建段。
- 配置解析阶段:配置解析阶段主要做的工作是加载和解析配置文件,将配置的bean解析成
BeanDefinition
。整个过程是:
- 读取配置:通过BeanDefinitionReader读取配置文件或配置类
- 解析配置信息:如ComonentScan、Bean配置等
- 扫描类注解:根据ComonentScan扫描@Component、@Bean、@Configuration、@Import等注解…
- 将符合的bean注册为BeanDefinition
- Bean的创建过程:Bean的创建过程主要做的工作是根据 BeanDefinition创建Bean。大概过程:
- 实例化Bean:容器根据配置文件中的Bean定义,实例化Bean对象。可以通过构造函数实例化、工厂方法实例化、静态工厂方法实例化等方式来创建Bean对象。这里不会加载懒加载的bean和多例bean,懒加载的bean会在使用时才创建,多例会在每一次使用时创建新的。
- 注入Bean属性:容器会为实例化的Bean对象设置属性值,可以通过setter方法注入属性值,也可以通过构造函数注入属性值。
- 处理依赖关系:容器会处理Bean之间的依赖关系,将依赖的Bean注入到需要的地方 。依赖注入是通过BeanPostProcessor来实现的。
- 执行初始化方法:容器会调用Bean的初始化方法,可以通过实现InitializingBean接口或在配置文件中指定初始化方法来定义Bean的初始化逻辑。
- 注册Bean:容器会将实例化、属性设置和初始化完成的Bean对象注册到容器中,以便后续的使用和管理。
- 完成加载:容器完成所有Bean的加载和初始化后,即完成了IoC容器的加载过程。此时,可以通过容器调用getBean获取Bean对象。
什么是bean的自动装配,有哪些方式?
通过autowiredtype=name、type、构造器进行注入。@autowired
Spring有哪些缺点
当然,Spring作为一个广泛使用的Java开发框架,也有一些缺点。以下是一些常见的Spring框架的缺点:
- 学习曲线较陡峭:Spring框架是一个功能强大且灵活的框架,但也因此学习曲线较陡峭。对于初学者来说,可能需要花费一些时间来理解和掌握Spring的核心概念和特性。
- 配置复杂:Spring框架的配置通常使用XML或注解进行,这种配置方式可能会导致配置文件变得复杂和冗长。特别是在大型项目中,配置文件的维护和管理可能会变得困难,当然这个问题在SpringBoot中得到解决。
- 运行时性能:由于Spring框架提供了很多功能和特性,它的运行时性能可能相对较低。尤其是在需要频繁创建和管理对象的场景下,可能会对系统的性能产生一定的影响。
- 过度依赖:在使用Spring框架时,可能会出现过度依赖的情况。由于Spring提供了很多功能和模块,开发人员可能会过度依赖Spring框架,导致项目的可移植性和可维护性下降。
- 文档和社区支持:尽管Spring框架有很多优秀的文档和活跃的社区支持,但有时候可能会遇到文档不完善或社区资源有限的情况。这可能会给开发人员带来一些困扰。
需要注意的是,这些缺点并不意味着Spring框架不好,而是在使用过程中需要注意和克服的问题。同时,Spring框架的优点和功能远远超过了它的缺点,因此它仍然是一个非常受欢迎和广泛使用的框架。
Spring进阶
applicationContextAware接口使用
为了让Bean获取它所在的Spring容器,可以让该Bean实现ApplicationContextAware接口。spring容器会检测容器中所有的bean,发现某个bean实现了ApplicationContextAware接口,spring会在创建该bean之后,自动调用该bean的setApplicationContext()方法,把容器传入该bean中使用。
FactoryBean与BeanFactory
BeanFactory是IOC最基本的容器,负责生产和管理bean,提供了IOC容器的最基本的规范。
XmlBeanFactory、ApplicationContext、DefaultListableBeanFactory等具体的容器都实现了BeanFactory,再在其基础上附加了其它的功能。
BeanFactory源码如下:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String name)获取到的是FactoryBean的实现类对象,而是这个实现类中的getObject方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName前加上&。
FactoryBean源码
package org.springframework.beans.factory;
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
InitializingBean接口使用
spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPreopertiesSet方法。
BeanFactory和ApplicationContext有什么区别
BeanFactory是原始factory,不支持AOP、Web应用等插件。
ApplicationContext是BeanFactory接口派生而来,提供BeanFactory的所有功能,并且以一种更向面向框架的方式工作以及对上下文进行分层和实现继承。Application还提供以下功能:
- 利用MessageSource实现国际化功能。
- 强大的事件机制:通过ApplicationEvent与ApplicationListener实现。
- 底层资源访问:ApplicationContext扩展了ResourceLoader,可以用来加载多个Resource。
- 对Web应用的支持:BeanFactory通常以编程方式创建,ApplicationContext能以声明的方式创建。
- BeanFactory采用延迟加载来注入,ApplicationContext则在容器启动时就一次性创建所有的Bean。这样,在容器启动时就可以发现Spring中存在的错误。
Spring的单例实现原理
Spring对单例的底层实现是采用单例注册表的方式实现的。详情见我的另一篇博客:Spring的单例实现原理
Spring框架中用到的哪些设计模式
- 代理模式:Spring的AOP(面向切面编程)底层通过代理模式来实现切面功能,包括JDK动态代理和CGLIB代理。
- 单例模式:在spring配置文件中定义的bean默认是单例模式。
- 模板方法模式:解决代码重复问题,Spring框架的许多模块和外部扩展都采用模板方法模式,例如JdbcTemplate、HibernateTemplate等。
- 前端控制器模式:spring提供了DispatcherServlet来对请求进行分发。
- 依赖注入模式:贯穿于BeanFactory和ApplicationContext接口的核心理念。
- 工厂模式:Spring的BeanFactory充当工厂,负责根据配置信息创建Bean实例。它是一种工厂模式的应用,根据指定的类名或ID创建Bean对象。
- 适配器模式:SpringMVC的HandlerAdapter允许不同类型的处理器适配到处理器接口,以实现统一的处理器调用。AOP中使用Adapter接口生产Advice(通知);
- 观察者模式:定义对象的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。Spring中Observer模式常用的地方是Listener的实现,如ApplicationListener、Spring 事件驱动模型就是观察者模式很经典的应用,它允许组件监听和响应特定类型的事件,实现了松耦合的组件通信。
- 策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。Spring允许使用策略模式来定义包扫描时的过滤策略,如在@ComponentScan注解中使用的excludeFilters和includeFilters。
- 回调模式:jdbcTemplate使用回调模式重定义部分实现逻辑。
- 工厂方法:FactoryBean接口允许用户自定义Bean的创建逻辑,实现了工厂方法模式。开发人员可以使用FactoryBean来创建复杂的Bean实例。
- 装饰器模式:Spring的BeanWrapper允许在不修改原始Bean类的情况下添加额外的功能,这是装饰器模式的实际应用。
- 责任链模式:Spring AOP通过责任链模式实现通知(Advice)的调用,确保通知按顺序执行。
循环依赖
循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A 依赖于B,B又依赖于A,这样创建A-bean时依赖注入B-bean,但是B-bean没有创建成功,因为它依赖A-bean注入后才能创建成功,而A-bean很明显也没有成功,这样就形成了死锁。
解决方案:
- 懒加载:@lazy,使用时才去获取依赖bean,这时bean已经创建成功。Spring启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始化时用,然后对每个 BeanDefinition 进行处理。普通 Bean 的初始化是在容器启动初始化阶段执行的,而被lazy-init=true修饰的 bean 则是在从容器里第一次进行context.getBean() 时进行触发。
- 三级缓存解决循环依赖问题:就是三个Map ; 关键: 一定要有一个缓存保存它的早期对象作为死循环的出口,核心代码源码就在DefaultSingletonBeanRegistry的getSingleton()方法中。
- 一级缓存单例池singletonObjects存放可以使用的单例。
- 二级缓存earlySingletonObjects存放的是早期的bean,即半成品,此时还无法使用。
- 三级缓存singletonFactories是一个对象工厂,用于创建对象并放入二级缓存中。如果引用的对象配置了AOP,那在单例池中最终就会需要注入动态代理对象,而不是原对象,而生成动态代理要在对象初始化完成之后才开始,于是Spring增加三级缓存,保存所有动态代理配置信息,在发现有循环依赖时,将这个对象的动态代理信息获取出来,提前进行AOP,生成动态代理,再把动态代理对象放入二级缓存。
面试官还可能问:
- 二级缓存能不能解决循环依赖?
a. 如果只是循环依赖导致的死循环的问题: 一级缓存就可以解决 ,但是解决在并发下获取不完整的Bean。
b. 二级缓存完全解决循环依赖: 只是需要在实例化后就创建动态代理,不优化也不符合spring生命周期规范。- Spring有没有解决多例Bean的循环依赖?
a. 多例不会使用缓存进行存储(多例Bean每次使用都需要重新创建)
b. 不缓存早期对象就无法解决循环- Spring有没有解决构造函数参数Bean的循环依赖?
a. 构造函数的循环依赖也是会报错
b. 可以通过在构造方法上增加懒加载注解@Lazy进行解决:
ⅰ. 就不会立即创建依赖的bean了
ⅱ. 而是等到用到才通过动态代理进行创建
spring容器启动过程中开发者可以做哪些增强?
Spring 提供了非常多的扩展接口,官方将这些接口称之为
钩子
,这些钩子会在特定的时间被回调,以此来增强 Spring 功能。众多优秀的框架也是通过扩展这些接口,来实现自身特定的功能,如 SpringBoot、mybatis 等。
- BeanFactoryPostProcessor:是 Bean 工厂的后置处理器,一般用来修改上下文中的 BeanDefinition,修改 Bean 的属性值。
- BeanPostProcessor:在 Bean 初始化前后对 Bean 进行一些修改包装增强,比如返回代理对象。BeanPostProcessor 和 InitializingBean 有点类似,也是可以在 Bean 的生命周期执行自定义操作,一般称之为 Bean 的后置处理器,不同的是,BeanPostProcessor 可以在 Bean 初始化前、后执行自定义操作,且针对的目标也不同,InitializingBean 针对的是实现 InitializingBean 接口的 Bean,而 BeanPostProcessor 针对的是所有的 Bean。
- Aware:一个标记接口,实现该接口及子接口的类会收到 Spring 的通知回调,aware翻译过来就是通知的意思,赋予某种 Spring 框架的能力,比如 ApplicationContextAware、EnvironmentAware 、BeanNameAware等,aware前的命名证明这个aware能获取到并进行修改的内容,如容器applicationContext、bean名称beanName。
- ApplicationContextInitializer:在上下文准备阶段,容器刷新之前做一些初始化工作,比如我们常用的配置中心 client 基本都是继承该初始化器,在容器刷新前将配置从远程拉到本地,然后封装成 PropertySource 放到 Environment 中供使用
- ApplicationListener:Spring 事件机制,监听特定的应用事件(ApplicationEvent),观察者模式的一种实现
- FactoryBean:用来自定义 Bean 的创建逻辑(Mybatis、Feign 等等)
- ImportBeanDefinitionRegistrar:定义@EnableXXX 注解,在注解上 Import 了一个 ImportBeanDefinitionRegistrar,实现注册 BeanDefinition 到容器中
- InitializingBean:是一个可以在 Bean 的生命周期执行自定义操作的接口,凡是实现该接口的 Bean,在初始化阶段都可以执行自定义的操作。
- ApplicationRunner/CommandLineRunner:容器启动后回调,执行一些初始化工作
spring默认生成bean name的规则是什么
- 通常情况,spring会将类名首字母大写改为小写,作为beanName。如“UserService.class”的beanName是“userService”。
- 如果首字母和第二个字母都是大写的情况,Spring会直接用类名作为beanName。如“URLService.class”的beanName是“URLService”。
Spring事件监听的核心机制是什么?
Spring事件监听的核心机制围绕观察者模式展开:
观察者模式: 它允许一个对象(称为主题或被观察者)维护一组依赖于它的对象(称为观察者),并在主题状态发生变化时通知观察者。
它包含三个核心:
- 事件: 事件是观察者模式中的主题状态变化的具体表示,它封装了事件发生时的信息。在Spring中,事件通常是普通的Java对象,用于传递数据或上下文信息。
- 事件发布者: 在Spring中,事件发布者充当主题的角色,负责触发并发布事件。它通常实现了ApplicationEventPublisher接口或使用注解@Autowired来获得事件发布功能。
- 事件监听器: 事件监听器充当观察者的角色,负责监听并响应事件的发生。它实现了ApplicationListener接口,通过onApplicationEvent()方法来处理事件。
总之,Spring事件监听机制的核心机制是观察者模式,通过事件、事件发布者和事件监听器的协作,实现了松耦合的组件通信,使得应用程序更加灵活和可维护。
什么情况下AOP会失效,怎么解决?
- 内部方法调用: 如果在同一个类中的一个方法调用另一个方法,AOP通知可能不会触发,因为AOP通常是通过代理对象拦截外部方法调用的。解决方式是注入本类对象进行调用, 或者设置暴露当前代理对象到本地线程, 可以通过AopContext.currentProxy() 拿到当前正在调用的动态代理对象。
- 静态方法: AOP通常无法拦截静态方法的调用,因为静态方法不是通过对象调用的。解决方法是将静态方法调用替换为实例方法调用,或者考虑其他技术来实现横切关注点。
- AOP配置问题: 错误的AOP配置可能导致通知不正确地应用于目标方法,或者在不希望的情况下应用。解决方法是仔细检查AOP配置,确保切点表达式和通知类型正确配置。
- 代理问题: 如果代理对象不正确地创建或配置,AOP通知可能无法生效。解决方法是调试底层源码确保代理对象正确创建,并且AOP通知能够拦截代理对象的方法调用。
Spring AOP and AspectJ AOP 有什么区别?
首先说下他们之间的关系:
- 当在Spring中要使用@Aspect、@Before.等这些注解的时候, 就需要添加AspectJ相关依赖
- Spring Aop主要用到了AspectJ的@PointCut 切点解析能力和切点匹配能力。 @Aspect、@Before.等这些注解都是由AspectJ 发明的,
- AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法增强),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。
区别:
(1)AspectJ 主要通过在编译阶段生成代理类,也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的对象,通过这种方式实现AOP。
(2)SpringAOP并没有使用AspectJ的织入Java字节功能来增强对象。而是使用的动态代理来增强对象。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。Spring AOP 在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好。
Spring-AOP通知和执行顺序?
Spring切面可以应用5种类型的通知:
- 前置通知:在目标方法被调用之前调用通知功能;
- 后置通知:在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知:在目标方法成功执行之后调用通知;
- 异常通知:在目标方法抛出异常后调用通知;
- 环绕通知:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
执行顺序:
Spring在5.2.7之前的执行顺序是:
1、正常执行:前置—>方法—>后置---->返回
2、异常执行:前置—>方法—>后置---->异常
Spring在5.2.7之后就改变的通知的执行顺序改为:
1、正常执行:前置—>方法---->返回—>后置
2、异常执行:前置—>方法---->异常—>后置
spring 自动装配 bean 有哪些方式
- 根据名称自动装配(byName):Spring容器会根据Bean的名称自动将相应的依赖注入到需要的地方。在XML配置中,可以使用autowire="byName"来启用byName自动装配。
- 根据类型自动装配(byType):Spring容器会根据Bean的类型自动将相应的依赖注入到需要的地方。在XML配置中,可以使用autowire="byType"来启用byType自动装配。
- 构造函数自动装配(constructor):Spring容器会根据构造函数的参数类型自动将相应的依赖注入到构造函数中。在XML配置中,可以使用autowire="constructor"来启用构造函数自动装配。
- 自动装配注解(Autowired):通过在需要自动装配的字段、构造函数或方法上使用@Autowired注解,Spring容器会自动将相应的依赖注入到标注了@Autowired的位置。
参考《spring 自动装配 bean 有哪些方式》
Spring框架中的单例bean是线程安全的吗
在Spring框架中, 由于单例Bean在整个Spring上下文只有一个实例,因此在多线程环境下访问该实例时,需要确保Bean的状态是线程安全的。如果单例Bean的状态是可变的,并且多个线程同时修改该状态,可能会导致线程安全问题。
为了确保单例Bean的线程安全性,可以采取以下几种方式:
- 避免在单例Bean中使用可变的实例变量,或者确保对这些变量的访问是线程安全的,例如使用同步机制(如synchronized关键字)或使用线程安全的数据结构。
- 尽量避免在单例Bean中使用共享的外部资源,如数据库连接、文件等。如果必须使用共享资源,需要确保对这些资源的访问是线程安全的(如使用ThreadLocal等)来保证线程安全。
- 使用无状态的单例Bean。无状态的单例Bean不包含任何实例变量,只包含方法和局部变量,因此不会有线程安全问题。
- 采用多例Bean。将bean的作用域改为"property"即每次使用创建一个新的实例,这样可以有效避免单例共享造成线程不安全。
解释下Spring支持的几种bean的作用域
Spring事务
Spring事务底层原理
动态代理:spring事务的原理是基于AOP,基于@EnableTransactionManagement和@Transactional注解,应用启动后会将目标类生成代理,并将事务能力织入进去。
ThreadLocal:Spring的事务管理通常将事务信息存储在ThreadLocal中,这意味着每个线程只能拥有一个事务。这确保了在单个线程内的数据库操作处于同一个事务中,保证了原子性。
说一下 spring 的事务隔离级别?
Spring的事务隔离级别是指在并发环境下,事务之间相互隔离的程度。Spring框架支持多种事务隔离级别,可以根据具体的业务需求来选择适合的隔离级别。以下是常见的事务隔离级别:
- READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据。事务可以读取其他事务未提交的数据,可能会导致脏读、不可重复读和幻读的问题。
- READ_COMMITTED:保证一个事务只能读取到已提交的数据。事务读取的数据是其他事务已经提交的数据,避免了脏读的问题。但可能会出现不可重复读和幻读的问题。
- REPEATABLE_READ:保证一个事务在同一个查询中多次读取的数据是一致的。事务期间,其他事务对数据的修改不可见,避免了脏读和不可重复读的问题。但可能会出现幻读的问题。
- SERIALIZABLE:最高的隔离级别,保证事务串行执行,避免了脏读、不可重复读和幻读的问题。但会降低并发性能,因为事务需要串行执行。
默认:使用数据库默认的事务隔离级别。通常为数据库的默认隔离级别,如Oracle为READ COMMITTED,MySQL为REPEATABLE READ。
//通过@Transactional注解的isolation属性来指定事务隔离级别。不同数据库对事务隔离级别的支持可能有所差异,需要进行适当的测试和验证。
@Transactional(isolation = Isolation.READ_COMMITTED)
public void method1() {
// ...
}
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void method2() {
// ...
}
说一下Spring的事务传播行为
事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?
Spring框架通过@Transactional注解的propagation属性来指定事务传播行为:
- REQUIRED:如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。这是最常用的传播行为,也是默认的,适用于大多数情况。
- REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。适用于需要独立事务执行的场景,不受外部事务的影响。
- SUPPORTS:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务方式执行。适用于不需要强制事务的场景,可以与其他事务方法共享事务。
- NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则将当前事务挂起。适用于不需要事务支持的场景,可以在方法执行期间暂时禁用事务。
- MANDATORY:如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常。适用于必须在事务中执行的场景,如果没有事务则会抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务中执行,如果当前没有事务,则创建一个新的事务。嵌套事务是外部事务的一部分,可以独立提交或回滚。适用于需要在嵌套事务中执行的场景。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。适用于不允许在事务中执行的场景,如果存在事务则会抛出异常。
Spring事务的失效原因?
spring事务的原理是AOP,进行了切面增强,失效的一般原因是这个AOP不起作用了,大部分失效是由于:
- 方法是private也会失效,解决:改成public: Spring的事务代理通常是通过Java动态代理或CGLIB动态代理生成的,这些代理要求目标方法是公开可访问的(public)。私有方法无法被代理,因此事务将无效。解决方法是将目标方法改为public或protected。
- 发生自调用:类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身。解决方法:把this变成UserService代理类即可。
- 目标类没有配置为Bean也会失效,解决:配置为Bean: Spring的事务管理需要在Spring容器中配置的Bean上才能生效。如果目标类没有被配置为Spring Bean,那么事务将无法被应用。解决方法是确保目标类被正确配置为Spring Bean。
- 自己捕获了异常,解决:不要捕获处理: Spring事务管理通常依赖于抛出未捕获的运行时异常来触发事务回滚。如果您在方法内部捕获了异常并处理了它,事务将不会回滚。解决方法是让异常在方法内部被抛出,以触发事务回滚。
- 使用CGLIB动态代理,但@Transactional声明在接口上: 默认情况下,Spring的事务代理使用基于接口的JDK动态代理。如果您将>1. **@Transactional注解声明在接口上,而目标类是使用CGLIB代理的,事务将不会生效。解决方法是将@Transactional注解移到目标类的方法上,或者配置Spring以使用CGLIB代理接口。
- 跨越多个线程的事务管理,解决:使用编程式事务或分布式事务: 如果您的应用程序在多个线程之间共享数据库连接和事务上下文,事务可能会失效,除非适当地配置事务传播属性。
- 事务传播属性或捕获异常等属性设置不正确: 事务传播属性定义了事务如何传播到嵌套方法或外部方法。如果事务传播属性设置不正确,可能会导致事务失效或不符合预期的行为。
Spring多线程事务 能否保证事务的一致性
在多线程环境下,Spring事务管理默认情况下无法保证全局事务的一致性。这是因为Spring的本地事务管理是基于线程的,每个线程都有自己的独立事务。
- Spring的事务管理通常将事务信息存储在ThreadLocal中,这意味着每个线程只能拥有一个事务。这确保了在单个线程内的数据库操作处于同一个事务中,保证了原子性。
- 可以通过如下方案进行解决:
- 编程式事务: 为了在多线程环境中实现事务一致性,您可以使用编程式事务管理。这意味着您需要在代码中显式控制事务的边界和操作,确保在适当的时机提交或回滚事务。
- 分布式事务: 如果您的应用程序需要跨多个资源(例如多个数据库)的全局事务一致性,那么您可能需要使用分布式事务管理(如2PC/3PC TCC等)来管理全局事务。这将确保所有参与的资源都处于相同的全局事务中,以保证一致性。
总之,在多线程环境中,Spring的本地事务管理需要额外的协调和管理才能实现事务一致性。这可以通过编程式事务、分布式事务管理器或二阶段提交等方式来实现,具体取决于您的应用程序需求和复杂性。
Spring使用场景
创建spring管理的自定义注解类
创建一个注解类@interface,在spring加载时,通过反射,如果检查到某个类(or方法or属性)标注了这个注解类,执行某些自定义操作。注解类举例:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}