
目录
1.Spring是什么 ?
- Spring是一款主流的Java EE轻量级开源框架
- 用于简化Java企业级应用的开发难度和开发周期
2.Spring框架中的单例bean是线程安全的吗?
线程安全问题:多线程并发的情况下,同时对同一个共享资源(Bean)进行读写,就出现线程不安全。
- 不是!
- 有实例变量的bean,可以保存数据,是非线程安全的。
- 没有实例变量的bean,不能保存数据,是线程安全的。
3.IOC容器是什么?
loC-->“控制反转",它不是一门技术,而是一种设计思想
Spring通过loC容器来管理所有Java对象的实例化和初始化,控制对象与对象之间的依赖关系
- 将对象的创建权交出去,交给第三方容器负责
- 将对象和对象之间的关系维护权交出去,交给第三方容器负
4.什么是依赖注入?
- Spring创建对象的过程中,把对象依赖的属性注入到对象中。依赖注入主要有两种方式:构造器注入和set注入。
IOC就是一种思想,DI是对IOC的一种具体实现
6.什么是AOP ?
- aop是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等
7.IOC容器初始化过程?
- 1.从XML中读取配置文件。
- 2.将bean标签解析成BeanDefinition,如解析property元素,并注入到BeanDefinition实例中。
- 3.将BeanDefinition注册到容器BeanDefinitionMap中。
- 4. BeanFactory 根据 BeanDefinition 的定义信息创建实例化和初始化 bean。
8.Bean的生命周期?
Spring容器在进行实例化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,Spring根据BeanDefinition来创建Bean对象,里面有很多的属性用来描述Bean
- 1.bean对象的创建(调用无参构造)
- 2.给bean对象设置相关属性
- 3.bean后置处理器(初始化之前)
- 4.bean对象初始化(调用指定的初始化方法)
- 5.bean后置处理器(初始化之后)
- 6.bean对象创建完成,可以使用了
- 7.bean对象销毁(配置指定的销毁方法)
- 8.IoC容器关闭
9.BeanFactory和FactoryBean的区别?
- BeanFactory:这是 loC 容器的基本实现,是 Spring 内部使用的接口。面向 Sping 本身,不提供给开发人员使用。几乎所有场合都使用ApplicationContext而不是底层的 BeanFactory
- FactoryBean:配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。
10.Bean注入容器有哪些方式?
- 1.@Configuration+@Bean:@Configuration用来声明一个配置类,然后使用 @Bean 注解,用于声明一个bean,将其加入到Spring容器中
- 2.@ComponentScan:@ComponentScan放置在我们的配置类上,然后可以指定一个路径,进行扫描带有特定注解的bean,然后加至容器中,特定注解包括@Controller、@Service、@Repository、@Component。
- 3.@Import注解导入:
- 3.1.把用到的资源导入到当前配置类中,(注解标识的类,必须是一个bean)
- 3.2导入一个类,这个类实现ImportSelector,并重写selectImports方法,返回bean的完整类路径数组
- 4.ImportBeanDefinitionRegistrar:重写registerBeanDefinitions方法,结合beanDefinition,定义bean完整类路径,创建bean。
11.Spring自动装配的方式有哪些 ?
自动装配方式一: byType
- byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
- 若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null.
- 若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常
自动装配方式二:byNam
- byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
自动装配方式三:constructor(根据构造函数)
- 存在单个实例则优先按类型进行参数匹配(无论名称是否匹配),当存在多个类型相同实例时,按名称优先匹配,如果没有找到对应名称,则注入失败。
bean标签里面的 id属性值必须和 自动装配的属性的属性名一致
12.AOP有哪些实现方式?
- AOP有两种实现方式:静态代理和动态代理。
静态代理
- 静态代理:代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。
- 缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理
- 动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。
13.Spring通知有哪些类型?
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的逻辑。
14.Spring中的事务是如何实现的?
- spring实现的事务本质就是aop完成,对方法前后进行拦截,在执行方法之前 开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
15.Spring改变自动注入顺序?
- 使用@Order注解,注解的属性值代表权重,值越小,就越靠前
16.Spring中的循环引用?
循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
Spring支持基于单例Bean的setter方法的循环依赖,Spring使用了三级缓存来解决循环依赖的问题
- ① 一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
- singletonObjects:一级缓存,存储所有实例化,并且为属性赋值的单实例Bean,实例化的Bean都会存储在这个Map集合中。
- ② 二级缓存:缓存早期的bean对象(生命周期还没走完)
- earlySingletonObjects:二级缓存,存放未完成的bean的缓存,存储实例化后还没来得及为属性赋值的单实例Bean,如果有代理的话,存放的是代理对象。
- ③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
- singletonFactories:三级缓存,存储生产单实例Bean的工厂。存放的是一个ObjectFactory,数据通过getObject方法获得。
大概流程:
/** 源码 */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { synchronized(this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
- 在获取单例Bean的时候,会先从一级缓存singletonObjects里获取,如果没有获取到(说明不存在或没有实例化完成) ,会去第二级缓存earlySingletonObjects中去找,如果还是没有找到的话,就会三级缓存中获取单例工厂singletonFactory。
- 通过从singletonFactory中获取正在创建中的引用,将singletonFactory存储在earlySingletonObjects二级缓存中。
- 这样就将创建中的单例引用从三级缓存中升级到了二级缓存中,二级缓存earlySingletonObjects,是会提前暴露已完成构造,还未执行属性注入的单例bean的。
- 这个时候如何还有其他的bean也是需要属性注入,那么就可以直接从earlySingletonObjects中获取了。
注意:
- 为了防止多线程并发环境下重复执行三级缓存中创建Bean的过程,在对singletonObjects加锁后,还会先从一级缓存singletonObjects中获取数据。
- 如果数据不存在则从二级缓存earlySingletonObjects中获取数据,如果数据仍然不存在,才会从三级缓存singletonFactories中获取singletonFactory,调用singletonFactory的getObject()方法获取实例化但未对属性赋值的Bean对象,将其存入二级缓存,并且从三级缓存中移除对应的singletonFactory,
Spring解决循环依赖为什么需要二级缓存?
- 二级缓存主要是为了分离创建出的完整的Bean和未对属性赋值的Bean,二级缓存中实际主要存储的是未对属性赋值的Bean,这样做的目的就是为了防止在多线程并发的场景中,读取到还未创建完成的Bean。
- 所以,为了保证在多线程并发的环境中,读取到的Bean是完整的(已经为属性赋值) ,不会读取到未对属性赋值的Bean,需要使用二级缓存解决循环依赖。
- 另外,就一、二、三级缓存而言,二级缓存主要存储的是三级缓存创建出来的,并且未对属性赋值的Bean,这样做的目的也是为了防止三级缓存中的工厂类重复执行创建对象的逻辑。
Spring只用二级缓存能否解决循环依赖?为什么一定要用三级缓存来解决循环依赖呢?
- Spring使用二级缓存就完全能够解决循环依赖的问题。但是,由于Spring中的方法在设计上遵循了单一职责的原则,一个方法通常只做一件事情, getBean()方法就是获取Bean对象。
- 但是,调用BeanPostProcessor创建动态代理是处于创建Bean的过程,如果在getBean()中实现这个逻辑,显然代码逻辑比较耦合。
- 为了解决代码耦合的问题,保持方法的职责单一,方面后期维护。需要将创建动态代理的BeanPostProcessor放在创建Bean的方法中,并且将判断是否存在循环依赖的逻辑放在getSingleton()方法中。
- 此时就需要三级缓存,在三级缓存中存放一个工厂接口,在接口的实现类中调用BeanPostProcessor创建动态代理对象。为了防止重复创建代理对象,将三级缓存中创建的代理对象存入二级缓存。
- 在Spring中使用三级缓存完美解决了解耦、性能、扩展的问题。
17.Bean的作用域?
- 1、singleton:单例,Spring中的bean默认都是单例的。
- 2、prototype:每次请求都会创建一个新的bean实例。
- 3、request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- 4、session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
- 5、global-session:全局session作用域。
18.@Autowired和@Resource的区别?
- 1.@AutoWired:
- 默认情况下@Autowired根据类型去spring容器中找,如果有多个类型,会根据名字再去spring容器中找(byname);如果需要按名称(byName)匹配的话,可以使用@Qualifier注解与@Autowired结合。
- @Autowired可以写在构造函数、方法、字段、参数
- 2.@Resource:默认按 byName模式自动注入。
19.@Bean和@Component有什么区别?
- 1.@Bean:
- 写在方法上面,通常写在配置类里面,可以干预实例化过程。
- @Bean 注解更加灵活,jar包中的类如果要配置bean就需要用@Bean
- 2.@Component:
- @Component 注解用在类上,表明一个类会作为组件类
20.Spring 事务实现方式有哪些?
Spring事务机制主要包括声明式事务和编程式事务。
- 编程式事务:通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
- 声明式事务:将事务管理代码从业务方法中分离出来,通过aop进行封装使用 @Transactional 注解开启声明式事务。
21.事务有哪些事务传播行为?
- 1.REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个方法都加了事务注解,并且运行在相同线程中,则这两个方法使用相同的事务中。如果运行在不同线程中,则会开启新的事务。
- 2.SUPPORTS:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
- 3.MANDATORY:如果已经存在一个事务,支持当前事务。如果不存在事务,则抛出异常IllegalTransactionStateException。
- 4.REQUIRES_NEW: 总是开启一个新的事务。需要使用JtaTransactionManager作为事务管理器。
- 5.NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。需要使用JtaTransactionManager作为事务管理器。
- 6.NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常。
- 7.NESTED: 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按PROPAGATION_REQUIRED 属性执行。
深度理解事务的传播行为
- 1.外部方法无事务注解,内部方法添加REQUIRED事务传播类型时,内部方法抛出异常,内部方法执行失败回滚内部事务,不会影响外部方法的执行,外部方法执行成功。
- 2.外部方法添加REQUIRED事务传播类型,内部方法无事务注解,内部方法抛出异常,会影响外部方法的执行,导致外部方法的事务回滚。
- 3.外部方法添加REQUIRED事务传播类型,内部方法添加REQUIRED事务传播类型,内部方法抛出异常,会影响外部方法的执行、事务会回滚。
- 4.外部方法添加REQUIRED事务传播类型,内部方法添加NOT_SUPPORTED事务传播类型,内部方法抛异常时,内部方法执行成功,事务会提交,外部方法执行失败,事务会回滚。
- 5.外部方法添加REQUIRED事务传播类型,内部方法添加REQUIRES_NEW事务传播类型,内部方法抛出异常时,内部方法和外部方法都会执行失败,事务回滚。
- 6.外部方法添加REQUIRED事务传播类型,内部方法添加REQUIRES-NEW事务传播类型,并且把异常代码移动到外部方法的末尾,内部方法抛异常时,外部方法执行失败,事务回滚;内部方法执行成功,事务提交。
- 7.外部方法添加REQUIRED事务传播类型,内部方法添加REQUIRES-NEW事务传播类型,并且把异常代码移动到外部方法的末尾,同时外部方法和内部方法在同一个类中,内部方法抛出异常,外部方法和内部方法都会执行失败 事务回滚
22.事务隔离级别?
- 读未提交(Read Uncommitted):允许脏读、不可重复读和幻读。一个事务可以读取另一个事务未提交的数据。
- 读已提交(Read Committed):避免脏读。一个事务只能读取已提交的数据。但是,可能发生不可重复读和幻读,因为在同一个事务中,另一个事务可能会修改数据。
- 可重复读(Repeatable Read):避免脏读和不可重复读。在同一个事务中,多次读取同一行数据时,得到的结果是一致的。但是,可能发生幻读,因为在同一个事务中,另一个事务可能会插入或删除数据。
- 串行化(Serializable):避免脏读、不可重复读和幻读。事务串行执行,保证了数据的一致性和完整性。
- 脏读(Dirty Read):指一个事务读取了另一个事务未提交的数据。如果一个事务可以读取未提交的数据,则会发生脏读。
- 不可重复读(Non-repeatable Read):指在同一个事务中,多次读取同一行数据时,得到的结果不一致。这是因为在读取期间,另一个事务修改了该行数据。
- 幻读(Phantom Read):指在同一个事务中,多次查询同一个范围的数据时,得到的结果集不一致。这是因为在查询期间,另一个事务插入或删除了符合查询条件的数据。
23.Spring事务在什么情况下会失效?
- 1.访问权限问题
- 事务的方法不是public修饰
- 2. 方法没有被public修饰
- 如果事务所在的方法没有被public修饰,此时Spring的事务会失效
- 如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
- 如果事务方法用final修饰,将会导致事务失效。
- 3.事务方法未被Spring管理
- 如果事务方法所在的类没有加载到Spring IOC容器中,也就是说,事务方法所在的类没有被Spring管理,则Spring事务会失效。
- 4.数据库不支持事务
- Spring事务生效的前提是所连接的数据库要支持事务,如果底层的数据库都不支持事务,则Spring的事务肯定会失效。
- 例如,如果使用的数据库为MySQL,并且选用了MyISAM存储引擎,则Spring的事务就会失效。
- 5.同一个类中,非事务方法调用了本类中事务方法
- Spring的事务管理是通过AOP(面向切面编程)代理来实现的。当你使用
@Transactional注解时,Spring会创建一个代理对象来包装你的bean。这个代理对象负责在方法调用前后添加事务管理的逻辑。- 当一个非事务方法在同一个类中调用一个事务方法时,这个调用是直接方法调用(即非代理对象的调用)。由于调用发生在类的内部,而不是通过代理对象,因此Spring的事务管理逻辑不会被触发。这意味着事务方法内部的事务性操作不会被Spring的事务管理器所管理。
- 所以在同一个类中,非事务方法调用事务方法,要想不失效,就需要把这个事务方法抽取到service的接口层,或者暴露这个事务方法的代理对象
- 6.未开启事务
- 如果是spring项目,则需要在配置文件中手动配置事务相关参数。
- 如果是springboot项目,那么不需要手动配置。因为springboot已经在
DataSourceTransactionManagerAutoConfiguration类中帮我们开启了事务。- 7.不正确的捕获异常
- 如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。
- 不能在事务中直接try-catch捕获异常,不然事务就会提交而不会回滚,要往外抛出异常
- 如果事务方法调用了内部的事务方法,但内部方法出现异常并try-catch捕获了,则内部提交事务而不会回滚,外部方法也是提交事务而不会回滚
- 8.错误的标注异常类型
- 如果在@Transactional注解中标注了错误的异常类型,则Spring事务的回滚会失效
- 9.方法的事务传播类型不支持事务
- 例如外部方法的传播行为是REQUIRED,内部方法的传播行为是NOT_SUPPORTED,外部方法调用内部方法,会导致内部方法的事务失效
24.Spring启动过程?
- 加载配置文件:Spring框架首先会加载配置文件,这些配置文件可以是XML文件、Java配置文件或者注解等,用于定义和管理应用程序中的Bean。
- 实例化Bean容器:在加载完配置文件后,Spring会实例化一个Bean容器(通常是一个ApplicationContext的实例),用于管理Bean的生命周期和依赖关系。
- 注册Bean:Bean容器会读取配置文件中的Bean定义,并将这些Bean注册到容器中。注册Bean的过程包括解析Bean的定义、创建Bean的实例以及将Bean的实例存储到容器中。
- 依赖注入:在注册Bean的过程中,Spring会解析Bean之间的依赖关系,并进行依赖注入。依赖注入是Spring框架的核心功能之一,它允许开发人员通过配置文件或注解的方式定义Bean之间的依赖关系,并在运行时自动将依赖的Bean注入到需要的地方。
- 初始化Bean:在Bean注册完成后,Spring会调用Bean的初始化方法(如果有的话)对Bean进行初始化。这通常包括执行一些启动时的任务,如加载数据、建立连接等。
- 配置事件发布:Spring还支持事件发布机制,允许在应用程序的不同阶段发布事件,并通知感兴趣的监听器进行处理。在启动过程中,Spring会配置事件发布器,以便在需要时发布事件。
- 应用上下文启动:在所有Bean都初始化完成后,Spring会启动应用上下文(ApplicationContext),此时应用程序已经准备就绪,可以接受请求并进行处理。
- 请求处理:当应用程序接收到请求时,Spring会根据请求的类型和内容选择合适的Bean进行处理。这通常涉及到查找匹配的Bean、调用Bean的方法以及处理返回结果等步骤。
- 响应返回:在处理完请求后,Spring会将处理结果返回给客户端。这可以通过HTTP响应、数据库操作或其他方式实现。
- 关闭与清理:当应用程序需要关闭时(如服务器停机、应用程序退出等),Spring会执行关闭和清理操作。这包括销毁Bean的实例、释放资源以及执行其他必要的清理任务。
25.AOP的通知执行顺序?
正常:前置-->目标方法-->返回通知--->后置通知(finally)
异常:前置-->目标方法-->异常通知--->后置通知(finally)
26.AOP的相关术语?
- 目标对象(Target Object):目标对象指将要被增强的对象。 (要增强的对象)
- 切面(Aspect):要增强的代码放入那个类就叫切面类使用@Aspect标记
- 通知(Advice):用来放增强的代码的那个方法。
- 切点(Pointcut):增强代码要切入到哪些方法中,切点表达式
- 连接点(Join point):通知和目标方法的一个桥梁,可以获取目标方法的信息, 就得通过JoinPoint
- 引入(Introduction):引入允许我们向现有类添加新方法或属性。
- 织入(Weaving) :将通知切入连接点的过程叫织入
27.Spring AOP的实现原理?
- Spring的AOP实现原理就是通过动态代理实现的。
- 如果我们为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。
- Spring的AOP使用了两种动态代理,分别是JDK的动态代理,以及CGLib的动态代理。
28.JDK动态代理和CGLIB动态代理的区别?
- JDK动态代理
- 如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。
- 代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口
- JDK动态代理的核心是
InvocationHandler接口和Proxy类。- 缺点:目标类必须有实现的接口。
- CGLIB动态代理
- CGLIB动态代理通过继承实现。
- 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。

530

被折叠的 条评论
为什么被折叠?



