写在前面:本文为个人八股复习所用,整合了其他平台的答案加自己的理解,希望能对大家的八股复习有所帮助,答案可能存在出入,请大家理性食用~~
右边有对应的目录,方便查阅,希望大家都能面试一遍过^_^
1. spring 支持几种 bean scope?
Spring bean 支持 5 种 scope:
- Singleton(单例) - 每个 Spring IoC 容器仅有一个单实例。
- Prototype(原型) - 每次请求都会产生一个新的实例。
- Request(请求) - 每一次 HTTP 请求都会产生一个新的实例,并且该 bean 仅在当前 HTTP 请求内有效。
- Session(会话) - 每一次 HTTP 请求都会产生一个新的 bean,同时该 bean 仅在当前 HTTP session 内有效。
- Global-session(全局会话) - 类似于标准的 HTTP Session 作用域,不过它仅仅在基于 portlet 的 web 应用中才有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的 portlet 所共享。在 global session 作用域中定义的 bean 被限定于全局 portlet Session 的生命周期范围内。如果你在 web 中使用 global session 作用域来标识 bean,那么 web 会自动当成 session 类型来使用。
仅当用户使用支持 Web 的 ApplicationContext 时,最后三个才可用。
- Singleton和prototype的区别:
1.当把一个bean定义设置为singleton作用域时,Spring IOC容器只会创建该bean定义的唯一实例。这个单一 实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都将返回被缓存的 对象实例,这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个 ClassLoader中只有一个class存在,而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean 被标识为singleton时候,spring的IOC容器中只会存在一个该bean。
2.prototype作用域部署的bean,每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的 getBean()方法)都会产生一个新的bean实例,相当与一个new的操作,对于prototype作用域的bean,有一 点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,而对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype 作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。
Spring默认创建的Bean的作用域是singleton的。
2. 在哪些情况下要使用不同的作用域
- 线程安全性:
-
- 如果一个 bean 在多个线程之间共享,并且不是线程安全的,那么应该使用每次请求或原型作用域,以确保每次调用都有一个新的实例。
- 如果一个 bean 是线程安全的,可以使用单例作用域,以减少实例化和销毁的开销。
- 资源消耗:
-
- 如果一个 bean 的创建和销毁过程比较昂贵,而且在应用程序的整个生命周期内都不需要改变,可以使用单例作用域,以避免频繁创建和销毁实例。
- 如果一个 bean 是轻量级的,并且在每次请求时需要一个新的实例,可以使用原型作用域。
- 上下文的可见范围:
-
- 如果一个 bean 需要在整个应用程序的不同部分中共享状态,可以使用应用程序作用域。
- 如果一个 bean 需要在会话期间共享状态,可以使用会话作用域。
- 如果一个 bean 需要在每个 HTTP 请求期间共享状态,可以使用请求作用域。
- 缓存和数据一致性:
-
- 如果一个 bean 存储了一些计算结果或数据,而这些结果或数据在不同的请求之间保持一致性是重要的,可以使用会话或应用程序作用域,以避免在每次请求时重新计算。
- 特定的 Web 应用程序需求:
-
- 如果使用 Spring Web 应用程序上下文并且需要与 HTTP 请求、会话或 WebSocket 相关的作用域,则应根据具体需求选择相应的作用域。
3. Spring框架中的单例bean(singleton作用域)是线程安全的吗?
4. Spring 中的单例 bean 的线程安全问题?
单例bean不是线程安全的,因为Spring框架中有个一@Scope注解,默认的值为singletion,单例的。而一般在spring的bean中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,则要考虑线程安全问题,可以使用prototype作用域或使用多例、加锁等方式解决。
当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对单例状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全.
无状态bean和有状态bean:如Service类和DAO类不会修改,为无状态bean;View Model对象为有状态bean
- 有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
- 无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。
在spring中无状态的Bean适合用不变模式,就是单例模式,这样可以共享实例提高性能。有状态的Bean在多线程环境下不安全,适合用Prototype原型模式。
Spring使用ThreadLocal解决线程安全问题。如果你的Bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全 。
5. 什么是AOP?
AOP(Aspect-Oriented Programming), 即面向切面编程, 在AOP中的基本单元是 Aspect(切面)=切点(Pointcut)+通知(Advice),将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式织入到业务流程当中的过程被称为AOP。比如事务、日志、安全管理等。
6. AOP 有哪些实现方式?
实现 AOP 的技术,主要分为两大类:
- 静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;
-
- 编译时编织(特殊编译器实现)
- 类加载时编织(特殊的类加载器实现)。
- 动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
-
- JDK 动态代理(默认):基于接口的代理,只能代理接口。通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口 ,代理类通过实现与目标类相同的接口来对目标类的方法进行代理。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类 。
- CGLIB动态代理: 基于继承的代理,既可以代理接口,又可以代理类,底层是通过继承的方式实现的,被代理的目标类不能使用final修饰。它通过生成目标类的子类来实现代理,因此无需目标类实现接口。如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类 。CGLIB ( Code Generation Library ),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意, CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final ,那么它是无法使用 CGLIB 做动态代理的。
7. Spring AOP and AspectJ AOP 有什么区别?
Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现。
Spring AOP 仅支持方法级别的 PointCut;提供了完全的 AOP 支持,它还支持属性级别的 PointCut。
8. Spring中的事务是如何实现的?
- Spring 中的事务管理是通过 AOP(面向切面编程)来实现的,它利用 Spring 的事务管理模块来简化事务管理的配置和操作。主要的实现方式是使用声明式事务管理和编程式事务管理。
- Spring 提供了 @Transactional 注解,通过在需要事务管理的方法上添加该注解,可以实现事务的声明式管理。这个注解可以应用在类级别或方法级别上,用于标识哪些方法需要启用事务支持。
9. Spring 事务实现方式有哪些?
- 编程式事务管理:这意味着你可以通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
- 声明式事务管理(AOP):这种方式意味着你可以将事务管理和业务代码分离。你只需要通过注解或者XML配置管理事务。
10. 什么是IOC和DI?
IOC即Inversion of Control,控制反转,通俗的说就是我们不用自己创建实例对象,这些都交给Spring的bean工厂(beanfactory,存在spring-beans中)帮我们创建管理。控制指控制对象的创建,IOC容器根据配置文件来创建对象,在对象的生命周期内,在不同时期根据不同配置进行对象的创建和改造。反转指原始创建对象且注入对象的动作是由程序员手动编写指定的,反转后,这个动作就由IOC容器触发,IOC容器在创建对象A的时候,发现依赖对象B,根据配置文件,它会创建B,并将对象B注入A中(参照循环引用的解决思路注入流程)。
DI:Dependency Injection,依赖注入:Di是IOC的一种实现,大致可理解为容器在运行的时候,可以找到被依赖的对象,然后将其注入,通过这种方式,使得各对象之间的关系可由运行期决定,而不用在编码时候明确。组件之间依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。通过依赖注入机制,只需关注业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
- 优点:IOC或依赖注入减少了应用程序的代码量。它使得应用程序的测试很简单,因为在单元测试中不再需要单例或JNDI查找机制。简单的实现以及较少的干扰机制使得松耦合得以实现。
11. Spring的bean的生命周期
1.实例化Bean(执行构造函数)——依赖注入之前已经创建的java对象,但是Spring Bean对外不可用(相当于一个空壳 )
2.Bean属性赋值(依赖注入)(通过set方法实现)
3.初始化:初始化完成后,Spring 容器会将 Bean 标记为可用状态,可以被其他 Bean 引用和使用。
-
-
- 初始化前——BeanPostProcessor before方法:检查、修改或者增强 bean 的属性值,以及执行一些初始化前的逻辑处理等。
- 执行initMethod方法
- 初始化后——BeanPostProcessor after方法(诸如AOP就是在这里面做的):对Bean进行后处理,例如修改其属性值或执行某些自定义逻辑。
-
4.使用Bean
5.销毁Bean(destory方法)
bean的生命周期分为五个阶段:实例化bean+依赖注入+初始化+使用bean+销毁
Spring容器首先会根据BeanDefinition信息(如@Component注解、xml等)来获取要创建的bean的信息,然后执行对应bean的构造函数,来进行bean的实例化并将其放入容器中进行管理。然后再通过set方法来进行依赖注入(属性赋值:包括基本属性和引用类型属性等),在属性设置完成后容器会调用特定的初始化方法(init)来完成bean的初始化操作,通过初始化操作完成对bean的预处理、资源初始化等操作(如AOP是在BeanPostProcessor#after方法中执行的),在完成初始化后Spring容器会将bean标记为可用状态,能够被其他bean引用。接下来就可以使用bean最后再销毁bean了。
12. Spring中的循环依赖及解决方法
- 循环依赖:循环依赖是指两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A。
在Spring中解决循环依赖的方法是依据三级缓存来实现的,能够解决大部分循环依赖(除去构造注入导致的)。
①一级缓存(singletonObjects):单例池,用于保存实例化、注入、初始化完成的 bean 实例
②二级缓存(earlySingletonObjects):用于保存实例化完成、还没依赖注入的 bean 实例
③三级缓存(singletonFactories),用于保存 bean 创建工厂(ObjectFactory),以便于后面扩展有机会创建代理对象。
循环依赖发生的情况是指当A依赖于B,B依赖于A时,A在完成实例化进行依赖注入时,需要注入B,而此时B还没有,则需要实例化B,但B在实例化后也需要依赖输入,需要注入A,产生了死循环。解决思路就是引入三级缓存,当A实例化完成但未完成依赖注入时,将其放入二级缓存中,并将其进行“曝光”,然后B进行实例化,在依赖注入需要注入A的时候从二级缓存中拿到A,完成B的创建,并将B放入到一级缓存中,A再完成剩下的初始化后也放入到一级缓存中。
因此整体的思路就是利用singleton+set注入+三级缓存的方式,来将bean的实例化和依赖注入这两个步骤分开,再引入三级缓存,将实例化后的bean可以放入二级缓存中,供其他bean进行调用,依次来解决循环依赖问题。
三级缓存机制只能解决单例模式下的循环依赖问题。对于原型模式(prototype scope)的Bean,由于每次请求都会创建一个新的实例,因此无法利用三级缓存机制解决循环依赖问题。
13. 为什么set方法注入不会产生循环依赖?
Spring容器分两个阶段处理依赖注入:先实例化所有的Bean,然后再注入属性。
主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的
在Spring中,通过set方法注入的方式不会产生循环依赖的主要原因是因为Spring容器在进行依赖注入时是通过两个步骤来完成的:首先是实例化Bean对象,然后是注入依赖属性。
当容器创建一个Bean时,它首先会调用构造函数或工厂方法来实例化Bean对象。在这个过程中,如果存在循环依赖,即A依赖于B,B又依赖于A,那么其中一个Bean的实例化过程会被阻塞,直到拥有循环依赖的Bean对象创建完毕。这种情况下,如果使用构造函数注入,会导致循环依赖无法解决,从而抛出异常。针对于构造器注入所导致的循环依赖问题,可以使用@Lazy进行懒加载,什么时候需要对象再进行bean对象的创建。
而对于set方法注入,Spring容器在实例化Bean时会先实例化所有的Bean对象,然后再进行属性注入。这意味着即使存在循环依赖,由于Bean对象已经创建完成,Spring容器可以通过setter方法来注入依赖,从而解决循环依赖的问题。它会先完成对象的创建,再进行属性的注入,从而避免了循环依赖的异常。
总结起来,通过set方法注入时,Spring容器分两个阶段处理依赖注入:先实例化所有的Bean,然后再注入属性。这样的处理顺序能够解决循环依赖的问题,因此set方法注入不会产生循环依赖的异常。
14. Spring为什么可以解决set + singleton模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。给Bean属性赋值的时候:调用setter方法来完成。
实例化Bean是通过构造方法来完成。两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
15. Spring 为什么要三级缓存,二级缓存不行吗?
场景分析:
如果A的原始对象注入给B属性之后,A原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象起始应该是AOP之后的代理对象,而B的A属性对应的并不是AOP之后的代理对象,这就产生冲突了,B依赖的A和最终的A不是同一个对象
AOP就是通过一个BeanPostProcessor来实现的,这个BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator,它的父类是AspectJAwareAdvisorAutoProxyCreator,而在Spring中AOP利用的要么是JDK动态代理,要么是CGLIB动态代理,所以如果给一个类中的某个方法设置了切面,那么这个类最终就需要产生一个代理对象.
一般过程就是: A类->生成一个普通对象->属性注入->基于切面生成一个代理对象->把代理对象放入singletonObjects单例池中,而AOP可以说是Spring除开IOC的另外一个大功能,而循环依赖又属于IOC范畴的,所以这两个功能想要共存,Spring需要进行特殊处理.
如何进行处理的,那就是利用第三级缓存singletonFactories
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
重点在于if条件的判断,如果为false,说明返回就是参数传进来的bean,没任何变化。如果为true,说明有InstantiationAwareBeanPostProcessors,需要在BeanPostProcessor方法中进行AOP代理。正常代理对象的生成是基于后置处理器的,是在被代理的对象初始化后期调用生成的,因此不能提早代理,会违背Bean定义的生命周期。所以Spring先在三级缓存中放置一个工厂(ObjectFactory),如果产生循环依赖,就调用这个工厂提早得到代理对象,如果没产生依赖,这个工厂根本不会被调用,因为单从解决循环依赖的角度来说不需要设置三级缓存,但是碍于生命周期的问题,需要提前暴露工厂延迟代理对象的生成。
Spring 设计了三级缓存来解决循环依赖问题。
- singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的 bean 实例
- earlySingletonObjects 二级缓存,用于保存实例化完成、还没依赖注入的 bean 实例
- singletonFactories 三级缓存,用于保存 bean 创建工厂(ObjectFactory),以便于后面扩展有机会创建代理对象。
三级缓存的核心思想,就是把 Bean 的实例化和依赖注入进行分离。
singletonFactories:这个缓存用于存储 Bean 工厂对象,也就是用于创建 Bean 对象的工厂实例。在处理循环依赖时,Spring 会将正在创建的 Bean 放入该缓存,以便后续的循环依赖检测和处理。
三级缓存的目的是针对代理对象的创建的,代理对象需要通过ObjectFactory对象来创建,然后将其放入二级缓存中,当其他对象需要引用该依赖的时候,先从二级缓存中查找,提高了效率。
同时三级缓存是针对singleton单例bean的,每个bean在缓存中只会存放一份,因此无法解决prototype的bean。
16. SpringMVC的执行流程
上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 DispatcherServlet 的作用是接收请求,响应结果。
流程说明(重要):
版本1(视图版本,JSP)
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
- DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
- DispatcherServlet会调用Handler Adapter(处理器适配器)
- HandlerAdapter 会根据 Handler来调用真正的处理器来处理请求,并处理相应的业务逻辑。
- 处理器(Handler/Controller)处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
- HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)
- ViewReslover 会根据逻辑 View 查找实际的 View,将其传给DispatcherServlet。
- DispaterServlet 把返回的 Model 传给 View(视图渲染)。
- 把 View 返回给请求者(浏览器)
版本2(前后端开发,接口开发)
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
- DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
- DispatcherServlet会调用Handler Adapter(处理器适配器)
- HandlerAdapter 会根据 Handler来调用真正的处理器来处理请求,并处理相应的业务逻辑。
- 方法上添加了@Response Body
- 通过HttpMessageConverter将返回结果转为JSON并响应。
17. 简单介绍 Spring MVC 的核心组件
那么接下来就简单介绍一下 DispatcherServlet 和九大组件(按使用顺序排序的):
组件 | 说明 |
DispatcherServlet | Spring MVC 的核心组件,是请求的入口,负责协调各个组件工作 |
MultipartResolver | 内容类型( Content-Type )为 multipart/* 的请求的解析器,例如解析处理文件上传的请求,便于获取参数信息以及上传的文件 |
HandlerMapping | 请求的处理器匹配器,HandlerMapping 负责根据请求的 URL 映射到相应的处理器(Handler),负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors) |
HandlerAdapter | 处理器的适配器:HandlerAdapter 负责执行实际的处理器(Handler)来处理请求,并负责调用处理器方法。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户处理器可以实现 Controller 接口、HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器 |
HandlerExceptionResolver | 处理器异常解析器,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果 |
RequestToViewNameTranslator | 视图名称转换器,用于解析出请求的默认视图名 |
LocaleResolver | 本地化(国际化)解析器,提供国际化支持 |
ThemeResolver | 主题解析器,提供可设置应用整体样式风格的支持 |
ViewResolver | 视图解析器,根据视图名和国际化,获得最终的视图 View 对象 |
FlashMapManager | FlashMap 管理器,负责重定向时,保存参数至临时存储(默认 Session) |
Spring MVC 对各个组件的职责划分的比较清晰。DispatcherServlet 负责协调,其他组件则各自做分内之事,互不干扰。
18. Spring框架常见注解(Spring、Spring Boot、SpringMVC)
19. @Resource的依赖注入规则
@Resource注解是Java标准依赖注入(DI)的一部分,它由Java EE(现Jakarta EE)规范提供,用于在Java类中自动注入依赖对象。在Spring框架中,@Resource也被广泛使用,并且它与Spring的DI机制很好地集成。
@Resource默认使用名称映射,名称没有映射成功则使用类型映射
19.1. 使用 @Resource 注解
@Resource注解通常用于字段或setter方法上,以标识需要注入的依赖。基本语法如下:
import javax.annotation.Resource;
public class MyService {
@Resource
private MyDependency myDependency;
// 或者通过setter方法注入
@Resource
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
19.2. 映射规则
@Resource注解的映射规则主要依赖于以下几个属性:
- name:指定要注入的bean的名称。
- type:指定要注入的bean的类型。
如果没有指定name或type属性,Spring会根据以下规则进行自动匹配:
19.3. 按名称匹配
首先,Spring会尝试按名称匹配,即使用字段名或setter方法名找出对应的bean。例如:
@Resource
private MyDependency myDependency;
在这种情况下,Spring会查找一个名为myDependency的bean。如果找到,就会注入这个bean。
19.4. 按类型匹配
如果按名称匹配失败,Spring会尝试按类型匹配。例如:
@Resource
private MyDependency myDependency;
如果没有名为myDependency的bean,Spring会查找类型为MyDependency的bean。如果找到唯一的一个bean,就会注入这个bean。
19.5. 同时指定名称和类型
可以同时指定name和type属性,以确保更精确的匹配:
@Resource(name = "myBeanName", type = MyDependency.class)
private MyDependency myDependency;
在这种情况下,Spring会查找名为myBeanName且类型为MyDependency的bean。
19.6. 处理冲突
如果有多个bean符合名称或类型匹配条件,并且Spring无法确定要注入哪个bean,会抛出异常。这种情况下,你可以通过显式指定name或type属性来解决冲突,或者使用Spring的其他注解如@Qualifier来进一步限定要注入的bean。
19.7. 示例
以下是一个更完整的示例,展示了如何使用@Resource注解以及如何处理冲突:
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Resource(name = "specificBean")
private MyDependency myDependency;
// 另一种方式,通过setter方法注入
@Resource
public void setAnotherDependency(AnotherDependency anotherDependency) {
this.anotherDependency = anotherDependency;
}
private AnotherDependency anotherDependency;
// 业务逻辑方法
public void doSomething() {
myDependency.perform();
anotherDependency.execute();
}
}
@Service("specificBean")
public class SpecificDependency implements MyDependency {
@Override
public void perform() {
System.out.println("SpecificDependency performing...");
}
}
@Service
public class AnotherDependency {
public void execute() {
System.out.println("AnotherDependency executing...");
}
}
在这个示例中:
- MyService类有两个依赖:myDependency和anotherDependency。
- myDependency通过@Resource(name = "specificBean")注入,确保注入的是名为specificBean的bean。
- anotherDependency通过setter方法注入,没有指定名称或类型,因此按类型匹配。
19.8. 总结
@Resource注解提供了一种标准化的方式来进行依赖注入,具有很好的跨平台兼容性。在Spring中使用@Resource时,了解其映射规则(按名称匹配、按类型匹配)和属性(name、type)非常重要,可以帮助你更好地控制依赖注入的行为。
20. spring依赖注入的默认bean名称
spring通过使用@component、@service、@controller、@Respority来实现来注入,注入的bean的默认名称为注入名称将首字母小写的全称
@Service
public class TempServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public long userRegister(String userAccount, String password, String checkPassword) {
return 0;
}
}
@Controller
public class ViewController(){
}
以上bean的默认名称为tempServiceImpl(TempServiceImpl首字母小写)、viewController(ViewController首字母小写)
21. FactoryBean、BeanFactory和ObjectFactory的区别
BeanFactory是IOC最基本的容器,负责生产和管理bean,它为其他具体的IOC容器提供了最基本的规范,例如DefaultListableBeanFactory。通过调用getBean,可以从工厂中拿到Bean。
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 是一个 Bean,实现了 FactoryBean 接口的类有能力改变 bean,FactoryBean 希望你实现了它之后返回一些内容,Spring 会按照这些内容去注册 bean。
public class MyBeanFactory implements FactoryBean<MyBean> {
@Override
public MyBean getObject() throws Exception {
// 自定义创建 MyBean 的逻辑
return new MyBean();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
- BeanFactory:
-
- BeanFactory 是 Spring 框架中用于管理 bean 的工厂接口,它提供了一种高级的配置机制,能够管理任意类型的对象。
- BeanFactory 提供了对依赖注入、AOP、事件传播等特性的支持,是 Spring IoC 容器的核心接口。
- BeanFactory 是一个工厂设计模式的实现,它负责实例化、定位和配置应用程序中的对象及其之间的依赖关系。
- FactoryBean:
-
- FactoryBean 是一个特殊的 bean,它可以用于自定义 bean 的实例化逻辑。
- 实现了 FactoryBean 接口的类可以用于创建特定类型的 bean,并且可以在创建过程中进行一些定制化的操作,例如对象的初始化、销毁等。
- 在配置文件中,通过配置 FactoryBean 类的 id 来创建相应的 bean,并且通过 getBean("id") 获取到的是 FactoryBean 创建的对象,而不是 FactoryBean 本身。
- ObjectFactory :
-
- ObjectFactory 是一个接口,用于延迟获取 bean 的实例。它允许在运行时根据需要获取特定类型的 bean 实例,而不需要事先知道或者提前配置 bean 的具体信息。具体来说,ObjectFactory 接口的作用是提供一种延迟初始化 bean 的机制,尤其适用于在运行时动态决定需要使用的 bean 类型的情况。
22. 什么是ApplicationContext,它和BeanFactory的区别
在 Spring 中,一般情况下是由 ApplicationContext 负责管理和创建 Bean(应用启动时创建所有的Bean),因为 ApplicationContext 是 BeanFactory 的子接口,提供了更多的功能和扩展性。虽然 ApplicationContext 是更常用和推荐的方式,但是在需要更底层控制和更轻量级容器时,可以选择使用 BeanFactory 来创建和管理 Bean。
- ApplicationContext是BeanFactory的子接口,由BeanFactory派生而来,通常成为Spring上下文,具有BeanFactory的基本功能。大概理解为BeanFactory有的,ApplicationContext都有。此外,ApplicationContext扩展了BeanFactory接口的功能
-
- AOP
- 资源管理
- 注解
- 整合 Environment 环境(能通过它获取各种来源的配置信息)
- 事件发布与监听,实现组件之间的解耦
- 等等
- ApplicationContext和BeanFactory的区别
-
- 加载方式:
-
- BeanFactory:BeanFactory 是 Spring 框架最基础的容器接口,采用延迟加载的方式,即在第一次访问 bean 时才进行实例化。
- ApplicationContext:ApplicationContext 在初始化时就将所有的单例 bean 进行实例化和初始化,以便在应用程序运行期间可以立即使用。
-
- 功能扩展:
-
- BeanFactory:BeanFactory 提供了基本的 IoC 容器的功能,包括 bean 的实例化、依赖注入和生命周期管理等。
- ApplicationContext:ApplicationContext 在 BeanFactory 的基础上提供了更多的功能,如国际化支持、事件发布、资源加载、AOP 等。
-
- 启动时间:
-
- BeanFactory:BeanFactory 在容器启动时的初始化速度较快,因为只实例化非懒加载的单例 bean。
- ApplicationContext:ApplicationContext 在容器启动时的初始化速度较慢,因为会实例化和初始化所有的单例 bean,包括懒加载的。
23. Spring中用到了哪些设计模式
- 单例模式(Singleton Pattern):
-
- Spring 中的 Bean 默认是单例的,即容器中的一个 Bean 实例在整个应用中是唯一的。
- 这种单例模式确保了资源的有效利用,并且减少了对象的创建和销毁次数,提高了系统的性能。
- 工厂模式(Factory Pattern):
-
- Spring 使用工厂模式通过 BeanFactory 和 ApplicationContext 创建和管理对象(Bean)。
- BeanFactory 是 Spring 框架的核心接口,它使用工厂模式来创建 Bean 实例。
- ApplicationContext 是 BeanFactory 的子接口,提供了更多的企业级功能,如事件发布、国际化、资源加载等。
- 代理模式(Proxy Pattern):
-
- Spring AOP(面向切面编程)的实现就是基于代理模式的。
- Spring AOP 允许将逻辑分散到多个对象中,然后在运行时将这些对象组装到一个整体中。
- Spring 使用动态代理来实现 AOP,提供了基于代理的切面配置,可以在不修改现有代码的情况下添加新的行为(如日志、事务管理等)。
- 模板方法模式(Template Method Pattern):
-
- Spring 中的 JdbcTemplate 和 HibernateTemplate 是典型的模板方法模式的应用。
- 这些模板类封装了一些通用的业务操作,如查询、更新等数据库操作,提供了模板方法供子类(或回调)实现自定义的部分。
- 观察者模式(Observer Pattern):
-
- Spring 的事件驱动模型基于观察者模式。
- ApplicationContext 提供了支持观察者模式的事件处理机制,可以发布事件(Publish Event)并允许监听这些事件的处理器进行响应。
- 适配器模式(Adapter Pattern):
-
- Spring MVC 中的处理器适配器(HandlerAdapter)就是适配器模式的应用。
- 处理器适配器根据请求选择合适的 Controller 处理请求,并将请求结果适配为 ModelAndView 对象返回给前端控制器(DispatcherServlet)。
24. Spring事务的隔离级别
脏读(Dirty Read):事务读取了其他事务中未提交的数据,当其他事务将未提交的数据进行修改,导致另一个事务读取到的数据是不正确的,这种现象就是脏读,会导致数据的不一致性。
不可重复读(Non-Repeatable Read):指在一个事务内,不同时刻多次读取同一行的数据得到的结果不同。通常发生在一个事务内多次读取同一行数据,但在这些读取过程中,另一个事务修改了该行数据并且已经提交。因此,第一个事务两次读取同一行数据时得到的结果不一致,这种现象就是不可重复读。不可重复读可以导致事务逻辑错误或数据不一致的问题。如何实现可重复读:MVCC允许事务在读取数据时创建一个快照(或版本),并且在整个事务期间保持这个快照的一致性。这样,其他事务对数据的修改不会影响当前事务已经读取的数据版本。
幻读(Phantom Read):指在同一个事务中,多次查询同一个范围的数据时,得到的结果集不一致。这是因为在查询期间,另一个事务插入或删除了符合查询条件的数据。通常发生在一个事务内查询某个范围的数据,但在这两次查询之间,另一个事务插入了新的符合查询条件的数据并且已经提交。因此,第一个事务两次查询同一范围的数据时得到的结果集不同,这种现象就是幻读。幻读可能会导致查询结果不一致或业务逻辑错误。
- Default(默认)
- 含义:使用数据库默认的事务隔离级别
- 适用场景:在未指定事务隔离级别时,Spring将使用数据库的默认隔离级别,实际隔离级别取决于所用数据库的配置,MySQL数据库的默认事务隔离级别是 REPEATABLE READ(可重复读)。
- READ_UNCOMMITTED(读未提交)
- 含义:允许事务读取其他事务未提交的数据
- 适用场景:很少使用,会导致脏读(读取到未提交的数据)、不可重复读和幻读。
- READ_COMMITTED(读已提交)
- 含义: 确保一个事务不会读取到其他并发事务未提交的数据修改。它是大多数数据库的默认隔离级别。
- 适用场景: 适合大多数的应用场景,能够避免脏读问题。但在并发情况下,仍可能遇到不可重复读和幻读问题,因为其他事务可以在该事务读取过程中提交新的数据。
- REPEATABLE_READ(可重复读)
- 含义: 确保事务可以多次读取同一数据,并且在事务执行期间保持一致。即使其他事务更新了数据,当前事务多次读取的数据也不会发生变化。
- 适用场景: 当需要确保读取的数据在事务内部保持一致性时,适合选择这个隔离级别。它能够避免脏读和不可重复读,但在某些情况下可能会遇到幻读问题。
- SERIALIZABLE(串行化)
- 含义: 最高的隔离级别,确保事务之间完全串行执行。即使在高并发情况下,事务也会像串行执行一样,依次提交。这样可以避免脏读、不可重复读和幻读问题。
- 适用场景: 当应用对数据的完整性要求极高,或者要求绝对的读一致性时,应选择这个隔离级别。但需要注意,串行执行可能会导致系统性能的显著下降。
@Transactional(isolation = Isolation.READ_COMMITTED)
public void myTransactionalMethod() {
// 事务处理逻辑
}
25. Spring通知的类型和执行顺序
在Spring框架中,通知(Advice)是AOP(面向切面编程)的核心概念,用于在程序的特定切点上执行额外的逻辑。通知可以分为几种类型,并且它们可以按照一定的执行顺序被应用到目标方法上。
Spring通知的类型:
- 前置通知(Before advice):
- 在目标方法执行之前执行。
- 通常用于执行某些前置条件检查或准备工作。
- 后置通知(After returning advice):
- 在目标方法正常完成后执行。
- 可以访问到方法的返回值。
- 异常通知(After throwing advice):
- 在目标方法抛出异常后执行。
- 可以访问到抛出的异常,可以根据异常类型做相应的处理。
- 后置最终通知(After (finally) advice):
- 无论目标方法是正常返回还是抛出异常,在目标方法执行后都执行。
- 通常用于资源清理等操作。
- 环绕通知(Around advice):
- 环绕目标方法执行,在目标方法执行前后都可以添加额外逻辑。
- 最强大的通知类型,可以完全控制目标方法的执行,包括是否执行、如何执行以及在执行前后做什么。
Spring通知的执行顺序:
通知的执行顺序由它们的类型和连接点(Join point)的顺序决定,通常情况下,Spring AOP的通知执行顺序如下:
- 环绕通知(Around advice):环绕通知包裹目标方法的执行,在目标方法执行前后都可以执行逻辑,因此它的执行优先级最高。
- 前置通知(Before advice):在目标方法执行之前执行。
- 目标方法执行:即执行目标方法本身。
- 后置通知(After returning advice):在目标方法正常返回后执行。
- 异常通知(After throwing advice):在目标方法抛出异常后执行。
- 后置最终通知(After (finally) advice):无论目标方法是正常返回还是抛出异常,最终都会执行。
26. IOC容器初始化过程
参考Bean生命周期的前三步(实例化、Bean属性赋值、初始化)
1)加载配置文件或配置类,IOC容器首先需要加载应用程序的配置信息,这些配置信息可以是XML配置文件、Java配置类或注解配置等方式。
2)创建和配置BeanFactory或ApplicationContext(二者区别参照BeanFactory与ApplicationContext的区别题目答案)
3)加载BeanDefintion(Bean定义),这些BeanDefinition 包含了Bean的类名、作用域、依赖关系、初始化方法等配置信息。通过BeanDefinitionReader读取和解析BeanDefintion,得到BeanDefinition注册到BeanDefinitionRegistry中。
4)根据BeanDefinition中指定的类,使用反射机制创建Bean实例,通过构造方法或者工厂方法实例化Bean。
5)处理Bean的依赖注入,对Bean的属性完成注入,这个过程会根据配置信息中的依赖关系,对Bean进行依赖注入。
6)BeanPostProcessor处理,这些处理器会在Bean初始化生命周期中加入定义的处理逻辑,postProcessBeforeInitialization和postProcessAfterInitialization分别在Bean初始化前后被调用。
7)调用初始化方法,例如实现InitializingBean的Bean会调用afterPropertiesSet方法。
27. Bean注入容器有哪些方式?
- 构造函数注入
- 在Bean定义中指定构造函数,并通过构造函数将依赖项传递给Bean实例。
public class MyService {
private final MyRepository repository;
public MyService(MyRepository repository) {
this.repository = repository;
}
// 其他方法
}
<bean id="myService" class="com.example.MyService">
<constructor-arg ref="myRepository" />
</bean>
- Setter方法注入
- 在Bean类中定义Setter方法,并由Spring容器调用这些方法来设置依赖项。
public class MyService {
private MyRepository repository;
public void setRepository(MyRepository repository) {
this.repository = repository;
}
// 其他方法
}
<bean id="myService" class="com.example.MyService">
<property name="repository" ref="myRepository" />
</bean>
- 基于注解方式注入
使用@Component、@Service、@Controller、@Repository、@Resource、@Autowired(不推荐)注解在类或字段上进行依赖注入。
@Component
public class MyComponent{
//业务逻辑
}
- 通过@Configuration+@Bean注解注入
@Configuration
public class AppConfig{
@Bean
public xampleBean exampleBean(){
return new ExampleBean(dependencyBean())
};
@Bean
public DependencyBean dependencyBean(){
return new DependencyBean();
}
}
28. Spring自动装配的方式有哪些
所谓自动装配是指Spring可以根据一些特定的规则比如注解或者配置。自动在各个组件之间建立关联,完成依赖注入。
- 按类型(byType)
- 根据类型自动装配,Spring容器自动将一个与属性类型相符的Bean注入到该属性中
- 缺点:若一个属性要注入的类型有多种实现方式,则无法根据类型注入。此时可以搭配使用@Qualifer注解指定特定名称的Bean注入。
- 按名称(byName)
- 根据名称匹配来自动装配,Spring容器会自动将一个与属性名相同的Bean注入到该属性中。
- 构造器自动装配
- Spring容器会自动根据构造函数的参数类型进行装配
@Autowired
注解是根据类型进行自动装配的,Spring容器会自动查找与类型兼容的Bean进行注入。当装配的是接口时,若其有不同的实现类,则需使用@Qualifier
注解来指定名称进行注入
@RestController
public class UserController{
@Autowired
@Qualifier(value ='customer')//指定注入的是名为Customer的UserService实现类
public void UserService userSerivce;
//需要使用@Qualifer注解来指定要注入哪个实现类
}
public interface UserSercie{
//method
}
@Service
//在默认情况下注入的Bean名称为首字母小写的名称(customer)
public class Customer implements UserService{
//override method
}
@Service
public class User implements UserService{
//override method
}
@Resource
默认是根据名称进行匹配的,如果找到了与name属性匹配的Bean则注入成功,否则会尝试按照类型进行匹配。如果按类型也未匹配成功则会抛出异常。
public interface Service {
public String getData() {
return "Data from DataService";
}
}
@Service
//@Service("mongodbDataService")
public class MongoDBDataService implements Service {
@Override
public String getData() {
return "Data from MongoDBDataService";
}
}
@Service
//@Service("dataService")
public class DataService implements Service {
@Override
public String getData() {
return "Data from OtherDataService";
}
}
@Component
//@Component("dataManager")
public class DataManager {
@Resource
private Service dataService;
//首先根据name=dataService进行注入,未找到则根据type=Service进行注入
//此处无法使用@Autowired,因为有两个实现类,无法确定要注入的是哪个实现类
public String useDataService() {
return dataService.getData();
}
}
29. @Qualifer注解有什么作用?
通常与@Autowired
注解一起使用,用于告诉Spring容器应该注入哪个Bean。
主要作用是为了消除歧义性:如果有多个Bean类型一致,仅通过@Autowired
Spring就不知道该注入哪个了,因此需要@Qualifier
来指定具体的Bean名称,消除歧义。
30. @Bean和@Component有什么区别
- @Bean
- 方法级别注释:
@Bean
是一个方法级别的注解,用于在配置类中声明方法,这些方法将返回一个对象,该对象由Spring容器管理。 - 手动定义Bean:通过在配置类中使用@Bean注解,可以手动定义Bean的创建和初始化过程,可以指定Bean的作用域、初始化方法、销毁方法等。
- 适用范围:主要用于Java配置(JavaConfig)方法,与XML配置相对应,通过Java代码的方式来配置Spring应用上下文。
总结来说,@Bean 注解用于声明一个 Bean,并将其注册到 Spring IoC 容器中。
@Configuration
public class AppConfig{
@Bean
public MyService myService(){
return new MyServiceImpl();
}
};
- @Component
- 类级别注释:
@Component
是一个类级别的注释,用于标识一个类为Spring的组件(Component),告诉Spring要将该类实例化为一个Bean,并交给Spring容器进行管理。 - 自动扫描:在基于组件扫描的方式下,Spring会自动扫描带有@Component及其派生注解的类,并将其注册为Bean。
- 适用范围:适用于基于注解的配置方式(AnnotationConfig),通过标记类来声明Bean。
@Component
public class MyCommponent{
//method
}
31. Spring启动过程
Spring框架的启动过程涉及到各种模块的初始化、依赖注入、AOP(面向切面编程)配置等步骤,下面是Spring容器启动的一般过程:
- 加载配置:Spring容器启动时会加载指定的配置文件(如XML配置文件、Java Config类等),包括配置数据库连接、事务管理、AOP配置等。
- 实例化Bean:一旦配置文件被加载,Spring容器就开始实例化配置文件中定义的Bean。这包括使用构造函数或工厂方法创建Bean的实例,并将这些实例放入容器的Bean工厂中。
- 注入依赖:在Bean实例化完成后,Spring容器会处理Bean之间的依赖关系,即将其它Bean引用注入到各个Bean中。这可以通过构造函数注入、设值注入或注解方式来实现。
- 处理Bean生命周期初始化方法:
- Spring调用Bean初始化方法(如果有定义的话),对Bean进行初始化。
- 如果Bean实现了
InitializingBean
接口,Spring会调用afterPropertieSet
方法。
- 处理 BeanPostProcessors:
容器定义了很多 BeanPostProcessor
,处理其中的自定义逻辑,例如 postProcessBeforelnitialization
会在 Bean 初始化前调用, postProcessAfterInitialization
则在之后调用。
- 代理切面处理:
Spring根据配置注册AOP切面,生成代理对象,将切面织入到目标对象中。
- 发布事件:
Spring可能会在启动过程中发布一些事件,比如容器启动时间。
- 启动完成
当所有Bean初始化完毕、依赖注入完成、AOP配置生效等都准备就绪时,Spring容器启动完成。
32. @Primary注解的作用
在Spring框架中,@Primary注解用于标记当存在多个候选Bean时,应该优先注入哪一个。
它的主要作用是解决Bean注入时的歧义问题,当一个接口有多个实现类的时候,在未指定具体要注入哪个具体的Bean时,此时可以使用@Primary
来指定首选的Bean。
使用场景:
1)多实现类:当一个接口有多个实现类,并且在注入时未明确指定要注入哪个实现类时,Spring 会报错。这时可以使用 @Primary 注解来指定一个首选的实现类。
2)注入优先级:在某些情况下,虽然存在多个候选 Bean,但我们希望某个特定的 Bean 在没有明确指定时被优先注入,这时可以使用 @Primary
注解。
代码举例:
// 定义接口Animal
public interface Animal {
void eat();
}
// 定义Dog类实现Animal接口
@Serivice
@Primary
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("Dog is eating.");
}
}
// 定义Cat类实现Animal接口
@Serivice
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("Cat is eating.");
}
}
// 在其他类中进行自动装配并使用Animal接口
@Component
public class AnimalService {
private final Animal animal;
@Autowired
public AnimalService(Animal animal) {
this.animal = animal;
}
public void performEat() {
animal.eat();
}
}
在上述代码中,我们定义了Animal接口和两个实现类:Dog和Cat。在Dog类上添加了@Primary注解,标识它为首选的Bean。在AnimalService类中通过构造函数注入Animal接口,并调用eat方法。由于Dog类被标注为@Primary,所以在自动装配时会优先选择Dog类作为注入对象。
33. @Value注解的作用
@Value注解在Spring中用于将外部化的配置值(例如,属性文件、系统属性、环境变量等)注入到Spring组件(如Bean)的字段、方法参数或构造函数参数中。它允许我们通过占位符语法来引用这些外部资源,并方便地将其注入到我们的应用程序中。
使用场景:
1)配置文件注入:将属性文件中的值注入到Bean中。
2)系统属性和环境变量:将系统属性或环境属性变量的值注入到Bean中。
3)默认值设置:在属性不可用时,提供默认值。
@Value通常与Spring Boot应用程序中的application.properties或application.yml文件一起使用。这些文件是默认的外部化配置文件
要指定非默认配置文件,可使用@PropertySource("classpath:custom.properties")
代码示例:
my.property=Hello, Spring!
my.version=1.0.0
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
@Value("${my.property}")
private String myProperty;
public MyComponent(@Value("${my.property}") String myProperty) {
this.myProperty = myProperty;
}
public void printProperty() {
System.out.println("My Property: " + myProperty);
}
@Value("${my.property}")
public void setMyProperty(String myProperty) {
this.myProperty = myProperty;
}
}
package com.powernode.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value(value = "zhangsan")
private String name;
@Value("20")
private int age;
public User(@Value("隔壁老王") String name, @Value("33") int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
34. Spring中@RequestBody和@ResponseBody注解的作用是什么
在Spring框架中,@RequestBody和@ResponseBody注解用于处理HTTP请求和响应体的数据转换,通常用于RESTful风格的Web服务开发中。
- @RequestBody 注解:
@RequestBody
注解用于将HTTP请求的内容体(body)直接绑定到方法的参数上,用于接收并转换客户端发送的JSON、XML或其他格式的数据。
作用:
-
- 将HTTP请求的主体内容(body)映射为Java对象。
- 通常用于POST请求或PUT请求,用来接收客户端发送的数据,比如JSON数据。
- 使用时,Spring会根据请求的Content-Type自动转换请求体内容为对应的Java对象。
示例:
假设客户端发送了一个JSON对象
{
"username": "user1",
"parrword": "password123"
}
@PostMapping("/login")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 处理接收到的用户对象(从请求体映射而来)
// user对象将自动从请求体中反序列化
return ResponseEntity.ok(userService.createUser(user));
}
在上面的例子中,@RequestBody注解使得Spring能够将客户端发送的JSON数据(或其他格式)自动转换为User对象,并传递给createUser方法进行处理。
- @ResponseBody 注解:
@ResponseBody
注解用于将方法的返回值直接绑定到HTTP响应体上,用于将方法的返回对象序列化为JSON、XML或其他格式的数据,并将其作为HTTP响应的内容发送给客户端。
@ResponseBody通常与@Controller一起使用,合并为@RestController,表示该Controller中所有方法的返回值都会序列化为JSON、XML或其他格式的数据。
作用:
-
- 将方法的返回对象转换为指定格式的数据(通常是JSON或XML)并发送给客户端。
- 通常用于RESTful控制器的方法,将处理结果直接返回给客户端而不是通过视图解析器渲染视图。
示例:
@GetMapping("/users/{id}")
@ResponseBody
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
// 方法的返回值User对象将被自动序列化为JSON并作为响应体发送给客户端
return ResponseEntity.ok(user);
}
在上面的例子中,@ResponseBody注解确保getUserById方法返回的User对象会被转换为JSON格式,并作为HTTP响应的内容返回给客户端。
35. @PathVariable注解的作用
@PathVariable 是 Spring 框架中的一个注解,用于将请求 URL 中的某部分绑定到处理方法的参数上。它通常与 RESTful 风格的 URL 结合使用,以便从路径中提取信息并传递给控制器方法。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/users/{id}")
public String getUserById(@PathVariable("id") Long userId) {
// 假设这里有逻辑去获取用户信息,通过 userId
return "User ID: " + userId;
}
}
如果方法参数名称和路径变量名称相同,可以省略@PathVariable注解中的名称:
@GetMapping("/users/{id}")
public String getUserById(@PathVariable Long id) {
return "User ID: " + id;
}
36. @Lazy注解的作用
在Spring框架中,@Lazy注解用于指定一个Bean不在启动时立即创建,而是在首次被使用(即第一次被请求)时才创建。这个注解可以应用于类(类级别的@Lazy会使得整个类作为懒加载的Bean),或者在配置方法上。
- 类上使用:
@Service
@lazy
public class MyService {
// service implementation
// 这个Bean在启动时不会创建,直到第一次被请求
}
- 方法上使用:
@Configuration
public class SomeConfig{
@Bean
@Lazy
public SomeBean someBean(){
//这个Bean在启动时不会创建,直到第一次被请求
return new SomeBean();
}
}
- 依赖注入延迟加载:
在注入 bean 时,也可以使用 @Lazy 注解来控制依赖的延迟加载行为。当一个 bean 被标记为 @Lazy,它在被注入到另一个 bean 时也会遵循延迟加载的策略。
@Component
public class MyClient {
private final MyService myService;
@Autowired
public MyClient(@Lazy MyService myService) {
this.myService = myService;
}
// Other methods
}
- 注意事项:
-
- 不要滥用:虽然延迟加载有助于性能优化,但在某些情况下可能会影响应用程序的正常行为,特别是当依赖关系复杂或者 bean 之间有特定的初始化顺序要求时。
- 与单例模式:默认情况下,Spring 的 bean 是单例模式管理的,即在整个应用程序生命周期内只有一个实例。使用 @Lazy 可能会改变这种行为,需要谨慎使用以避免意外的行为。
37. SpringMVC Restful风格的接口的执行流程是什么样?
- 客户端发送HTTP请求
用户或其他客户端通过HTTP请求(如GET、POST、PUT、DELETE)访问服务端资源。
- 请求到达DispatcherServlet
HttpServletRequest被传递到SpringMVC的DispatcherServlet。
- URL路由
DispatcherServlet将请求信息发送给HandlerMapping,HandlerMapping根据请求信息解析URL,得到对应的handler以及拦截器的处理链路,然后返回HandlerExecutionChain给DispatcherServlet,其中包含拦截器Inteceptor和Handler。
- Controller处理请求,完成业务逻辑处理
DispatcherServlet调用HandlerAdapter来执行对应的Controller,使用@RequstBody来将请求中(POST)的属性转为对应的Java对象,然后调用对应的Service层的业务方法,Service层调用Mapper层的CRUD与数据库进行交互完成持久化操作,并返回执行结果。
- 数据转换和消息体编写
Controller方法使用@ResponseBody或@RestController注解,将返回值转换为客户端请求的格式(如JSON或XML),并写入HTTP响应体中。
- 视图解析(可选)
对于某些请求,如GET请求获取资源表示,Controller可能会返回一个视图名称,而不是直接写入响应体。
- 拦截器执行
如果配置了链接器(Interceptor),它们会在请求处理前后执行,如日志记录、权限认证等。
- 返回HTTP相应
最终,DispatcherServlet将返回一个包含状态码、响应头和消息体的HTTP响应。
- 客户端接收响应
客户端接收到服务端的响应,并根据状态码和响应码进行响应的处理。
38. SpringMVC中的拦截器是什么?如何定义一个拦截器
在Spring MVC框架中,拦截器(Interceptor)是一种用于拦截请求的机制,可以在请求处理前或处理后执行一些特定的逻辑。拦截器类似于过滤器(Filter),但它更加专注于对控制器处理过程的拦截和处理。
要定义一个拦截器,需要完成以下步骤:
- 创建拦截器类:首先创建一个类,实现HandlerInterceptor接口,并重写其方法来定义拦截器的处理逻辑。HandlerInterceptor接口包括三个方法:
- preHandle:在请求处理之前调用,返回true表示继续执行后续操作,返回false则中断请求。
- postHandle:在请求处理之后、视图渲染之前调用,可以对ModelAndView进行操作。
- afterCompletion:在整个请求处理完成后调用,可用于资源清理等操作。
- 注册拦截器:通过配置文件或Java类的方法将自定义的拦截器注册(注册为Bean)到SpringMVC框架中。
- XML配置方式:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.example.CustomInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
- Java配置方式:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor())
.addPathPatterns("/**") //拦截所有请求
.excludePathPatterns("/ignore") //排除路径
.order(1); //设置拦截器顺序
}
}
39. 过滤器和拦截器的区别
- Spring MVC的拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理。
- 过滤器是JavaWeb Servlet规范里面的,拦截器是spring框架里面的。拦截器依赖于Spring框架,必须在Spring容器中注册为Bean,才能生效。
拦截器和过滤器的区别在于它们的作用层面不同。
- 过滤器更注重在请求和响应的流程中进行处理,可以修改请求和响应的内容,例如设置编码和字符集、请求头、状态码等。
- 拦截器则更加侧重于对控制器进行前置或后置处理,在请求到达控制器之前或之后进行特定的操作,例如打印日志、权限验证等。
Filter、Servlet、Interceptor、Controller的执行顺序:
40. SpringMVC中数据绑定是什么?如何进行数据绑定
在Spring MVC中,数据绑定是指将HTTP请求参数自动映射到控制器方法的参数或模型对象上。这种机制使得开发人员可以轻松地从请求中提取数据并直接使用Java对象进行处理,而不需要手动解析和赋值。
数据绑定的主要方式包括以下几种:
- 请求参数绑定到方法参数:
- Spring MVC可以自动将请求参数绑定到控制器方法的简单类型参数(如String、int、double等)上。
@RequestMapping("/greet")
public String greet(@RequestParam("name") String name, @RequestParam("age") int age) {
// 使用name和age参数
return "greetView";
}
- 请求参数绑定到模型对象:
- 使用@ModelAttribute注解,Spring MVC可以将请求参数绑定到复杂类型的Java对象上,例如表单提交时传递的多个字段。
public class User {
private String name;
private int age;
// getters and setters
}
@PostMapping("/user")
public String createUser(@ModelAttribute User user) {
// 使用user对象
return "userView";
}
- 路径变量绑定:
- 使用@PathVariable注解,Spring MVC可以将URL中的路径变量绑定到方法参数上。
@GetMapping("/user/{id}")
public String getUser(@PathVariable("id") Long userId) {
// 使用userId参数
return "userDetailView";
}
- 请求体绑定:
- 使用@RequestBody注解,Spring MVC可以将请求体中的内容绑定到方法参数上,通常用于处理JSON或XML格式的数据。
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 使用user对象
return new ResponseEntity<>(user, HttpStatus.CREATED);
}
- 自定义数据绑定:
- 有时,默认的绑定机制可能无法满足复杂的需求,这时可以通过实现WebDataBinder来进行自定义的数据绑定。
@ControllerAdvice
public class GlobalBindingInitializer {
@InitBinder
public void initBinder(WebDataBinder binder) {
// 自定义绑定逻辑,例如日期格式转换
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
}
41. @ModelAttribute与@RequestBody注解的区别
- @ModelAttribute
- 作用:用于将请求参数绑定到方法参数或模型(Model)对象中。
- 适用场景:适用于表单提交、URL参数传递等场景,通常用于绑定请求参数到Java对象上。
@GetMapping("/form")
public String showForm(@ModelAttribute("user") User user) {
return "formView";
}
- @RequestBody
- 作用:用于将HTTP请求体中的内容绑定到方法参数上。
- 适用场景:适用于处理POST、PUT等请求方法,其中请求体包含了需要绑定的数据,常用于RESTful API中接收JSON或XML格式的数据。
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
// process the user object
return new ResponseEntity<>(user, HttpStatus.CREATED);
}
42. SpringMVC中如何处理异常?
在Spring MVC中,可以通过以下几种方式来处理异常:
- @ExceptionHandler注解:
在控制器类中定义一个带有@ExceptionHandler注解的方法,用于处理特定类型的异常。当控制器中抛出对应类型的异常时,Spring MVC会自动调用该方法来处理异常。
@Controller
public class MyController {
@ExceptionHandler(Exception.class)
public String handleException(Exception ex) {
// 处理异常逻辑
return "errorView";
}
}
- @ControllerAdvice注解
使用@ControllerAdvice注解来定义全局的异常处理器,可以在多个控制器中共享相同的异常处理逻辑。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception ex) {
// 全局异常处理逻辑
return "errorView";
}
}
- HandlerExceptionResolver接口
自定义实现HandlerExceptionResolver接口来处理异常,该接口定义了统一的异常处理机制,可以根据具体需求进行灵活的处理。
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 自定义异常处理逻辑
return new ModelAndView("errorView");
}
}
- @ResponseStatus注解
使用@ResponseStatus注解可以将特定的异常映射到指定的HTTP状态码,并定义异常处理后返回的状态码。
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class MyCustomException extends RuntimeException {
// 异常定义
}
- SimpleMappingExceptionResolver类
在Spring的配置文件中配置SimpleMappingExceptionResolver来映射特定的异常类型到指定的视图页面。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">errorView</prop>
</props>
</property>
</bean>
43. @RestControllerAdvice注解的作用和原理
@RestControllerAdvice是Spring框架中的一个注解,用于全局处理控制器层抛出的异常。
- 作用
- 全局异常处理:
@RestControllerAdvice注解可以通过结合@ExceptionHandler注解,统一处理控制器层抛出的异常,而不需要在每个控制器中单独处理异常情况。
- 返回值定制:
可以在@RestControllerAdvice中定义方法,返回自定义的错误信息或响应格式,从而统一控制全局异常返回给前端的数据结构。
- 日志记录:
可以在@RestControllerAdvice中捕获异常并记录日志,用于系统监控和故障排查。
- 原理
- AOP实现:
@RestControllerAdvice底层通过AOP(面向切面编程)技术实现。它会创建一个代理对象,拦截所有使用@Controller或@RestController注解的类中的方法调用。
- 异常处理:
当被代理的方法抛出异常时,@RestControllerAdvice中定义的异常处理方法会被触发,根据异常类型进行相应的处理。
- 返回值处理:
在异常处理方法中,可以根据业务逻辑和异常类型自定义返回给前端的数据结构,并将其序列化为JSON格式返回给客户端。
- 灵活配置:
@RestControllerAdvice注解支持设置basePackages属性,指定需要扫描的包路径,可以精确控制哪些包中的异常需要被全局处理。
- 示例代码
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
// 自定义异常处理逻辑,返回自定义错误信息
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred: " + e.getMessage());
}
}