Spring面试题_1
参考文档:https://zhuanlan.zhihu.com/p/137507309
https://blog.csdn.net/f641385712/article/details/92801300
https://blog.csdn.net/f641385712/article/details/92801300
spring设计模式:https://www.cnblogs.com/kyoner/p/10949246.html
1、使用Spring框架的好处是什么?
- 轻量:Spring是轻量的,基本的版本大约2MB。
- 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
- 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
- 容器:Spring包含并管理应用中对象的生命周期和配置。
- MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
- 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
- 异常处理:Spring提供方便的API把具体技术相关的异常(比如由JDBC,HibernateorJDO抛出的)转化为一致的unchecked异常。
2、Spring由哪些模块组成?
- Core Bean Context ExpressionLanguage JDBC ORM OXM JavaMessagingService(JMS) Transaction Web Web-Servlet Web-Struts Web-Portlet
3、控制反转(IOC)和依赖注入(DI)
依赖倒置原则:是一种思想,IOC是DI的实现。
ioc的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理。
第一,资源集中管理,实现资源的可配置和易管理。
第二,降低了耦合度。
依赖注入一般使用的方式:通过构造函数注入,setter注入,根据注解注入。
IoC让相互协作的组件保持松散的耦合,AOP将遍布于应用各层的功能分离出来形成可重用的功能组件,通过注解配置的形式进行调用。
- 提供了支持国际化的文本消息
- 统一的资源文件读取方式
- 能够将事件发布到注册为监听器的bean(已在监听器中注册的bean的事件)
①BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
以下是三种较常见的 ApplicationContext 实现方式:
1、ClassPathXmlApplicationContext:从classpath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中
ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。
ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
3、XmlWebApplicationContext:由Web应用的XML文件读取上下文。
4.AnnotationConfigApplicationContext(基于Java配置启动容器)
- 基于XML的配置
- 基于注解的配置
- 基于Java的配置
8、Spring常用注解:
@Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property。@Autowired默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。如果找到多个相同的bean,将@Autowired注解的required属性设置为false即可。
applicationContext.xml配置
<context:component-scan base-package="com.proc.bean" />
我们一般使用 @Autowired
注解让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,可以采用以下注解实现:
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
@RestController:
@RestController
注解是@Controller和
@ResponseBody
的合集,表示这是个控制器 bean,并且是将函数的返回值直 接填入 HTTP 响应体中,是 REST 风格的控制器。
@Scope:
声明 Spring Bean 的作用域:
-
- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
- prototype : 每次请求都会创建一个新的 bean 实例。
- request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
- session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
@Configuration:
一般用来声明配置类,可以使用 @Component
注解替代,使用Configuration
注解声明配置类更加语义化。
9、Spring Bean的生命周期?
(1)实例化Bean:对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口:pring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean实现了BeanNameAware接口,会调用setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean实现了BeanFactoryAware接口,会调用setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor:如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean 与 init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(6)如果这个Bean实现了BeanPostProcessor接口:会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(7)DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
(8)destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
10、Aware接口作用?
当Spring容器创建的Bean对象在进行具体的操作的时候,如果需要容器的其他对象,此时可以将对象实现aware接口,来满足当前需求。
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理,而 AspectJ 基于字节码操作。
Spring AOP 已经集成了 AspectJ 。AspectJ 相比于 Spring AOP 功能更加强大,但Spring AOP 相对来说更简单,功能更弱。
11、Spring AOP的实现原理?
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
jdk动态代理:自定义类实现InvocationHandler接口,并在类中重写invoke方法,如下
public class ProxyHandler implements InvocationHandler { private Object obj; //绑定要代理的对象,并返回代理类 public Object bind(Object obj) { this.obj = obj; //绑定该类实现的所有接口,获得代理类 return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), obj); } public Object invoke(Object proxy , Method method , Object[] args)throws Throwable { Object result = null; //这里进行的AOP切面方法了 //在调用具体函数方法前,需要执行的内容@before result = method.invoke(obj,args); //在调用具体函数方法后,需要执行的内容@after return result; } }
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
12、Spring容器的'三级缓存'
singletonObjects
:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用earlySingletonObjects
:提前暴光的单例对象的Cache 。存放原始的 bean 对象(尚未填充属性),【用于检测循环引用,与singletonFactories互斥】。singletonFactories
:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖
12、什么是 Spring 的循环依赖
简单来讲,就是有一个 A 对象,创建 A 的时候发现 A 对象依赖 B,然后去创建 B 对象的时候,又发现 B 对象依赖 C,然后去创建 C 对象的时候,又发现 C 对象依赖 A。这就是所谓的循环依赖。这里指的循环引用不是方法之间的循环调用,而是对象的相互依赖关系。
Spring的单例对象的初始化主要分为三步:
①:createBeanInstance:实例化,其实也就是 调用对象的构造方法实例化对象
②:populateBean:填充属性,这一步主要是多bean的依赖属性进行填充注入(@Autowired
)
③:initializeBean:调用spring xml中的init() 方法。
从对
单例Bean
的初始化可以看出,循环依赖主要发生在第二步(populateBean),也就是field属性注入的处理。
分析getSingleton()的整个过程:
- 先从
一级缓存singletonObjects
中去获取。(如果获取到就直接return) - 如果获取不到或者对象正在创建中(
isSingletonCurrentlyInCreation()
),那就再从二级缓存earlySingletonObjects
中获取。(如果获取到就直接return) - 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过
getObject()
获取。就从三级缓存singletonFactory
.getObject()获取。(如果获取到了就从singletonFactories
中移除,并且放进earlySingletonObjects
。其实也就是从三级缓存移动(是剪切、不是复制哦~)
到了二级缓存)
- 先从
加入
singletonFactories
三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
二级缓存earlySingletonObjects
的数据什么时候添加什么移除???
添加:向里面添加数据只有一个地方,就是上面说的getSingleton()
里从三级缓存里挪过来。
移除:addSingleton、addSingletonFactory、removeSingleton
从语义中可以看出添加单例、添加单例工厂ObjectFactory
的时候都会删除二级缓存里面对应的缓存值,是互斥的。
“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。
1. A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中。
2. 此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程。
3. B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A)。
4. B尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。
5. 返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,
6. 由于B拿到了A的对象引用,所以B中的A对象是完整的。
但是:
构造器注入构成的循环依赖,此种循环依赖方式是无法解决的,只能抛出BeanCurrentlyInCreationException
异常表示循环依赖。
根本原因
:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化
,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决.
Spring的循环依赖的理论依据基于Java的引用传递
,当获得对象的引用时,对象的属性是可以延后设置的。
如何检测是否有循环依赖?how to find?
- sSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
- allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
可以 Bean在创建的时候给其打个标记,如果递归调用回来发现正在创建中的话--->即可说明循环依赖。
怎么解决的? todo what?
Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或zh属性是可以延后设置的(但是构造器必须是在获取引用之前)。
13、Spring 中用到了那些设计模式?
- 工厂设计模式 : Spring使用工厂模式通过
BeanFactory
、ApplicationContext
创建 bean 对象。 - 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中
jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 - 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配
Controller
。
Spring事务的配置方式
Spring支持编程式事务管理以及声明式事务管理两种方式。
1. 编程式事务管理:侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
2. 声明式事务管理:建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
声明式事务不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,可以通过提取方法的方式完成声明式事务管理的配置。
14、Spring 是如何管理事务的?
Spring事务管理主要包括3个接口,Spring的事务主要是由(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三个共同完成的。
1. PlatformTransactionManager:事务管理器--主要用于平台相关事务的管理
主要有三个方法:
commit 事务提交; rollback 事务回滚; getTransaction 获取事务状态。
2. TransactionDefinition:事务定义信息--用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用
这个接口有下面四个主要方法:
getIsolationLevel:获取隔离级别; getPropagationBehavior:获取传播行为; getTimeout:获取超时时间;
isReadOnly:是否只读(保存、更新、删除时属性变为false--可读写,查询时为true--只读)
事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。
3. TransactionStatus:事务具体运行状态--事务管理过程中,每个时间点事务的状态信息。
例如它的几个方法:
hasSavepoint():返回这个事务内部是否包含一个保存点,
isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚
isNewTransaction():判断当前事务是否是一个新事务
声明式事务的优缺点:
优点:不需要在业务逻辑代码中编写事务相关代码,只需要在配置文件配置或使用注解(@Transaction),这种方式没有侵入性。
缺点:声明式事务的最细粒度作用于方法上,如果像代码块也有事务需求,只能变通下,将代码块变为方法。
15、Spring通知有哪些类型
(1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
(2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
(3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
(4)后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。
16、Spring框架中有哪些不同类型的事件
(1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
(2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
(3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
(4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
(5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
17、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才会按照类型来装配注入。
18、常见的http请求
- GET :请求从服务器获取特定资源。举个例子:
GET /users
(获取所有学生)
@GetMapping("users")
等价于@RequestMapping(value="/users",method=RequestMethod.GET)
- POST :在服务器上创建一个新的资源。举个例子:
POST /users
(创建学生)
@PostMapping("users")
等价于@RequestMapping(value="/users",method=RequestMethod.POST)
- PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:
PUT /users/12
(更新编号为 12 的学生)
@PutMapping("/users/{userId}")
等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)
- DELETE :从服务器删除特定的资源。举个例子:
DELETE /users/12
(删除编号为 12 的学生)
@DeleteMapping("/users/{userId}")
等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)
- PATCH :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新)。
@PathVariable
和 @RequestParam
@PathVariable
用于获取路径参数,@RequestParam
用于获取查询参数。
如果我们请求的 url 是:/klasses/{123456}/teachers?type=web
那么我们服务获取到的数据就是:klassId=123456,type=web
。
@RequestBody
用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter
或者自定义的HttpMessageConverter
将请求的 body 中的 json 字符串转换为 java 对象。
@value
(常用):使用 @Value("${property}")
@ConfigurationProperties
(常用):通过@ConfigurationProperties
读取配置信息并与 bean 绑定。
Spring如何处理线程并发问题?
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,默认为singleton作用域,Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。