Spring经典面试题

什么是代理

代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式,即通过代理访问目标对象。 这样好处:可以做到在不修改目标对象的功能前提下,对目标对象的功能进行扩展。*。(扩展目标对象的功能)。

AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

(1)AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理对象; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),代码生成器,可以动态的生成字节码对象,即可以在运行时动态的生成指定类的一个子类对象,覆盖父类方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

cglib动态代理

JDK动态代理是必须实现接口才能进行代理,如果一个类没实现接口,就不能使用Proxy动态代理。此时可以使用CGLIB(Code Generation Library)动态代理,来实现增强。

cglib【code generator bibrary】,代码生成器,可以动态的生成字节码对象,即生成目标类的子类,通过调用父类(目标类)的方法实现,在调用父类方法时再代理中进行增强

JDK动态代理与CGLIB对比

1)

  • JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象,在调用具体方法前调用InvokeHandler的invoke()处理。

  • CGLIB动态代理:利用asm开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

2)

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP

如何强制使用CGLIB实现AOP?
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

  • 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

3)

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final

4)

  • JDK Proxy的优势:

最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。

  • CGLIB的优势:

无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。

Spring AOP使用的代理方式

  • 默认使用 JDK 动态代理,这样便可以代理所有的接口类型
  • 如果目标对象没有实现任何接口,则默认是CGLIB的代理方式
  • 可强制使用CGLIB,指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)

Spring 框架中都用到了哪些设计模式?

Spring设计模式的详细使用案例可以阅读这篇文章:Spring中所使用的设计模式_张维鹏的博客-CSDN博客_spring使用的设计模式

(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象

(2)单例模式:Bean默认为单例模式

(3)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略

(4)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术

(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate

(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller

(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。

(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库

什么是spring

spring是一个轻量级的,控制反转(IoC)和面向切面(AOP)的容器(框架),目的是简化企业级应用程序的开发,使得开发者只需要关心业务需求。主要包括以下七个模块:

Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
Spring Core:核心类库,所有功能都依赖于该类库,提供IOC和DI服务;
Spring AOP:AOP服务;
Spring Web:提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器;
Spring MVC:提供面向Web应用的Model-View-Controller,即MVC实现。
Spring DAO:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务;
Spring ORM:对现有的ORM框架的支持;
下图对应的是Spring 4.x的版本,5.x版本中Web模块的Portlet组件已经被废弃

Spring优点

他属于非入侵式的设计,对代码的污染低。

通过IOC容器创建,管理bean,和维护bean之间的依赖关系,减少代码间的耦合。

通过AOP技术,将与业务无关的(如:事务管理,权限验证,安全检查,日志等)代码进行封装集中管理,方便功能扩展和复用

IOC本质

  • 控制反转IoC(Inversion of Controller),是一种设计思想,即创建对象的控制权转移。
  • 没有IoC的程序中,使用面向对象编程,对象的创建和对象间的依赖关系完全写死在程序中,对象的创建由程序控制,控制反转后将对象的创建,管理,装配交给了spring IoC容器,程序本身不创建对象,而变成被动的接受对象。
  • spring容器在初始化时先读取配置文件,根据配置文件和元数据创建与组织各对象间的依赖关系,对象与对象间的松散耦合,将对象存入容器中,程序使用时在IoC容器中取出需要的对象。
  • 通过工厂模式和反射实现

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

依赖注入

依赖注入是同一个概念的不同角度的描述,在依赖注入中,Spring通过我们描述的配置文件中哪些组件需要哪些服务,IoC 容器动态的将某个依赖关系注入到组件之中

依赖注入方式

构造器注入、set()注入,属性注入,接口注入

什么是spring bean**

构成用户应用程序主干和由spring Ioc容器管理的对象称为bean,spring IoC容器会基于用户提供的配置元数据实例化,装配和管理。

bean的作用域

Singleton:一个bean定义在IOC容器中仅有一份实例对象,通过同一个bean id获取到的始终是同一个对象。Singleton是单例类型,在创建容器时就同时自动创建了一个bean对象,不管是否使用,它都存在。

Prototype:一个bean定义对应多个对象实例,每次请求都 会产生一个新的实例。Prototype是原型类型,在创建容器的时候并没有实例化对象,而是当获取bean时才回去创建对象

Request:每一次 HTTP 请求都会产生一个新的实例,并 且该 bean 仅在当前 HTTP 请求内有效当处理请求结束,request作用域的bean实例将被销毁

Session:每一次 HTTP 请求都会产 生一个新的 bean,同时该 bean 仅在当前 HTTP session 内有效。当http session废弃时,在该作用域内的bean也会被废弃掉

global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

Spring AOP

AOP【Aspect Object Programm】:面向切面编程,OOP是自上而下的开发,OOP引入封装、继承和多态等概念来建立一种对象层次结构,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如权限验证,日志、安全监测,异常处理等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序不便于维护和复用,所以就有了一个面向切面编程的思想,他是横向开发,将那些与业务无关,被业务模块所共同调用的逻辑封装起来,使用时将其织入到对应位置,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP解决什么问题

AOP他是一种面向切面的编程思想,AOP处理的是一些横切性问题,这些横切性问题不会影响到主逻辑的实现,但是会散落到代码的各个部分,难以维护。AOP就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。关注点在切面的编程,就是所谓的AOP。

AOP应用场景:

日志记录,权限验证,事务管理,效率检查,事务管理,缓存,内容传递,错误处理,懒加载,调试,记录跟踪,优化,校准,性能优化,持久化,资源池,同步

AOP术语

连接点(Joinpoint) :具体要求拦截的方法
切点(PointCut)一个通知所被引发的一系列连接点的集合

AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围

增强(Advice): 增强是织入到目标类连接点上的一段程序代码。

通知(Advice): 在特定的连接点,AOP框架执行的动作。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。

1)通知类型:

前置通知(before):在执行业务代码前做些操作,比如获取连接对象
后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
返回通知(afterReturning),在执行业务代码后无异常,会执行的操作
环绕通知(around),包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

2)各种通知类型执行顺序

(1)没有异常情况下的执行顺序:

around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
(2)有异常情况下的执行顺序:

around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing
java.lang.RuntimeException: 异常发生

织入(Weaving): 织入就是将增强添加到对目标类具体连接点上的过程。具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
代理类(Proxy): 一个类被AOP织入增强后,就产生了一个代理类。
切面(Aspect): 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。

目标对象(Target) 需要被加强的业务对象

什么是事务:

**一条或多条sql语句组成的一个执行单元,这个执行单元要么全部执行,要么全部不执行。

spring支持的事务方式

Spring支持编程式事务管理声明式事务管理

编程式事务

  • 将事务管理代码嵌套到业务方法中来控制事物的提交和回滚
  • 缺点:必须在每个事务操作的业务逻辑中包含额外的事务管理代码,比较冗杂

声明式事务

  • 将事务管理代码从业务方法中分离出来,在配置文件中做相关事务规则声明和通过注解@Transactional方式实现事务管理
  • Spring中通过Spring AOP框架支持声明式事务管理,将事务管理作为横切关注点,通过aop方法模块化。

spring的事务规则

**隔离级别:**事务的隔离级别指若干个并发的事务之间的隔离程度。

传播特性:

事务的传播特性指当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行

**PROPAGATION_REQUIRED:**如果当前没有事务,就新建一个事务,如果存在一个事务中,则内存事务加入到外层事务中,一块提交,一块回滚

**PROPAGATION_SUPPORTS:**支持当前事务,如果当前没有事务,就以非事务方法执行

**PROPAGATION_MANDATORY:**如果当前的方法不存在事务,去调用另外一个带事务的方法时,会直接抛出异常。

**PROPAGATION_REQUIRES_NEW:**新建事务,如果当前存在事务,把当前事务挂起,新建一个事务(如果当前没有事务,则直接创建就行)
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起,调用的方法都已无事务的方式执行

**PROPAGATION_NEVER:**如果外层方法存在事务,则会直接抛出异常;如果当前不存在事务,将会以无事务的方式执行

**PROPAGATION_NESTED:**则使用SavePoint将外层方法的事务状态保存起来,然后底层和嵌套事务使用同一个连接,当嵌套事务出现异常时,将会自动回滚到SavePoint这个状态,如果嵌套事务出现的异常被当前事务捕获到之后,当前事务可以继续向下执行,而不会影响到当前事务。但是如果当前的事务中出现了异常,将会导致当前事务以及所有的内嵌事务全部回滚。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似

只读事务:

如果一个事务只对数据库执行读操作,那么该数据库就可能利用事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。

事务超时:

事务超时,指一个事务所允许执行的最长时间。由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务执行时间,超过该时间限制且事务还没有完成,则自动回滚事务

回滚规则

  • spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

  • 在默认设置下,事务只在出现运行时异常(runtime exception)或Error时回滚,而在出现受检查异常(checked exception)时不回滚。

  • 也可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

@Transactional使用:

1)@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
2)@Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和配置适当的具有事务行为的beans所使用。
3)@Transactional 注解应该只被应用到 public方法上。 如果在 protected、private 或者default修饰的方法上使用 @Transactional 注解,它也不会报错,但方法没有事务功能.
4)用sprin事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到RuntimeException或Error会回滚,即遇到不受检查(unchecked)的异常时会回滚;而遇到受检查(非运行时抛出的异常,编译器会检查到的异常叫受检查异常)的异常不会回滚。此时若想回滚,可指定能导致回滚的异常@Transactional( rollbackFor={Exception.class,其它异常}),如果让unchecked异常不回滚,@Transactional(notRollbackFor=RunTimeException.class)
5)默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解行修饰。
6)官方建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。

事务不生效情况

1)事务方法的权限修饰符不对。

spring要求被代理的方法必须是public。

2)方法用final修饰

有时,某个方法不想被子类重写,则可用final修饰,但会导致该方法的事务失效。在源码中spring是使用aop来织入事务,aop通过jdk动态代理或者cglib动态代理。如果方法使用final修饰,在代理类中无法重写该方法,则不能添加该事务功能

如果一个方法是static的,同样也无法通过动态代理变成事务方法

3)方法内部调用

在同一个类中直接调用其他内部方法,会导致事务失效,要用他的代理类去调用才能生效。

4)未被spring管理

使用spring事务的前提是:对象要被spring管理,需要创建bean实例。因为是根据bean创建他的动态代理对象将事务织入的

通常可用@Controller,@Service.@Component,@Repository等注解,自动实现bean实例化和依赖注入的功能

5)内部方法调用

多线程调用事务是通过数据库连接来实现的,数据库连接是通过ThreadLocal来保存的,ThreadLocal是基于线程的,线程间的数据是隔离的,所以如果用多线程调用一个方法的话,他就会产生多份ThreadLocal,从而产生多个数据库连接,当你的数据库连接都不一样了,那么事务也不一样了

6)表不支持事务

myisam不支持事务

7)未开启事务

springboot中通过DataSourceTransactionManagerAutoConfiguration类,已经默认开启了事务,我们只需配制数据源spring.datasource相关参数即可。但传统的spring项目则需要在applicationContext.xml中手动配置事务相关参数。

事务不回滚情况

1)错误的传播特性

如果在设置传播特性的时候设置错了,比如设置成Propagation.NEVER,该传播机制不支持外层事务,即如果外层有事务就抛出异常,

目前只有三种传播特性才会创建新事物:REQUIRED,REQUIRES_NEW,NESTED

2)自己吞了异常

开发者在代码中手动try…catch异常了

3)手动抛了别的异常

即使开发者抛出异常,但如果抛出的异常不正确,spring事务也不会回滚

4)自定义了回滚异常

你抛出的异常和你指定的可以回滚的异常不一样,也是不回滚的

5)嵌套事务回滚多了

Spring Bean的生命周期?

简单来说,Spring Bean的生命周期只有四个阶段:实例化 Instantiation --> 属性赋值 Populate --> 初始化 Initialization --> 销毁 Destruction

但具体来说,Spring Bean的生命周期包含下图的流程:

(1)实例化Bean:

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。

对于ApplicationContext容器,当容器启动结束后,通过解析器BeanDefinitionReader读取bean的配置文件获取BeanDefinition对象,然后确定bean的构造函数,实例化所有的bean。

(2)设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。

(3)处理Aware接口:Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:

①如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字;
②如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
②如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)【初始化前调用前置处理器】BeanPostProcessor前置处理:如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。可以在初始化前对bean的属性进行修改。

(5)InitializingBean:如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。

(6)init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。

(7)【初始化后调用后置处理器】BeanPostProcessor后置处理:如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;这里可进行AOP方法织入。

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

(8)DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

(9)destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

什么是循环依赖

如A依赖B,B依赖CA,在A依赖注入时需要B,然后就去创建B,这时候发现又需要依赖注入 A ,这样就导致了循环依赖。

循环依赖问题在Spring中主要有三种情况:

(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:

第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。

Spring创建Bean的过程大致为实例化->依赖注入->初始化,所以可以利用在实例化之后,依赖注入之前这段时间的间隙,将只实例化但未属性填充的的bean的ObjectFactory放到缓存中提前曝光,之后的对象可从缓存中获取提前曝光的对象进行属性填充。

解决循环依赖的流程总结:

假如A依赖B,B依赖A,先创建A只实例话但未属性填充的bean实例,并将bean的objectFactory放到三级缓存singletonFactories中提前曝光,接着执行populateBean()进行属性填充,填充时需要B,在缓存中未找到,所以如A的流程一样创建B的实例,对B进行属性填充时需要A,会从一二三级缓存中依次找,如果在三级缓存中找到了A的 ObjectFactory ,则通过执行他的getObject方法获取提前曝光的bean,并将bean放到二级缓存,然后注入,填充完后对B进行初始化,此时B是一个完整的对象会将他放到一级缓存中,B创建完后,A就可进行属性注入了,初始化完后也放到一级缓存中,从而避免了死循环。

一二三级缓存

在这里插入图片描述

Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?

Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。

(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。

有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。

无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。

也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

Spring的自动装配:

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。

在Spring框架xml配置中共有5种自动装配:

(1)no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。

(2)byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。

(3)byType:通过参数的数据类型进行自动装配。

(4)constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。

(5)autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

基于注解的方式:

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

如果查询的结果不止一个,那么@Autowired会根据名称来查找;

如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

@Autowired可用于:构造函数、成员变量、Setter方法

注:@Autowired和@Resource之间的区别

(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

Spring容器的启动流程:

(1)初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中:

① 实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象
② 实例化BeanDefinitionReader注解配置读取器,用于对特定注解(如@Service、@Repository)的类进行读取转化成 BeanDefinition 对象,(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等)
③ 实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象
(2)将配置类的BeanDefinition注册到容器中:

(3)调用refresh()方法刷新容器:

① prepareRefresh()刷新前的预处理:
② obtainFreshBeanFactory():获取在容器初始化时创建的BeanFactory:
③ prepareBeanFactory(beanFactory):BeanFactory的预处理工作,向容器中添加一些组件:
④ postProcessBeanFactory(beanFactory):子类重写该方法,可以实现在BeanFactory创建并预处理完成以后做进一步的设置
⑤ invokeBeanFactoryPostProcessors(beanFactory):在BeanFactory标准初始化之后执行BeanFactoryPostProcessor的方法,即BeanFactory的后置处理器:
⑥ registerBeanPostProcessors(beanFactory):向容器中注册Bean的后置处理器BeanPostProcessor,它的主要作用是干预Spring初始化bean的流程,从而完成代理、自动注入、循环依赖等功能
⑦ initMessageSource():初始化MessageSource组件,主要用于做国际化功能,消息绑定与消息解析:
⑧ initApplicationEventMulticaster():初始化事件派发器,在注册监听器时会用到:
⑨ onRefresh():留给子容器、子类重写这个方法,在容器刷新的时候可以自定义逻辑
⑩ registerListeners():注册监听器:将容器中所有的ApplicationListener注册到事件派发器中,并派发之前步骤产生的事件:
⑪ finishBeanFactoryInitialization(beanFactory):初始化所有剩下的单实例bean,核心方法是preInstantiateSingletons(),会调用getBean()方法创建对象;
⑫ finishRefresh():发布BeanFactory容器刷新完成事件

BeanFactory和ApplicationContext有什么区别?

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。

(1)BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

继承MessageSource,因此支持国际化。
资源文件访问,如URL和文件(ResourceLoader)。
载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
提供在监听器中注册bean的事件。
(2)①BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。

③ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。

(3)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

(4)BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

@Transactional使用:

-1)@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
2)@Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和配置适当的具有事务行为的beans所使用。
-3)@Transactional 注解应该只被应用到 public方法上。 如果在 protected、private 或者default修饰的方法上使用 @Transactional 注解,它也不会报错,但方法没有事务功能.
-4)用sprin事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到RuntimeException或Error会回滚,即遇到不受检查(unchecked)的异常时会回滚;而遇到受检查(非运行时抛出的异常,编译器会检查到的异常叫受检查异常)的异常不会回滚。此时若想回滚,可指定能导致回滚的异常@Transactional( rollbackFor={Exception.class,其它异常}),如果让unchecked异常不回滚,@Transactional(notRollbackFor=RunTimeException.class)
-5)默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解行修饰。
6)官方建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。

Spring框架中有哪些不同类型的事件?

Spring 提供了以下5种标准的事件:

(1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。

(2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。

(3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。

(4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。

(5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。

如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值