Spring面试题hot1

事务

1.spring 事务失效的场景有哪些?

spring事务的实现原理是基于AOP的动态代理和TransacationInterceptorh还有底层依赖

AOP:Spring 通过 ProxyFactoryBean 生成代理对象,默认对接口用 JDK 动态代理,对类用 CGLIB 代理;是采用cglib继承目标类的方式去创建代理类,非pulic的方法不能能继承。

拦截器:核心拦截器,在方法调用前后开启 / 提交 / 回滚事务,基于 ThreadLocal 存储事务状态;基于运行时异常来回滚的,所以把运行时异常给catch或者返回没指定的异常

底层依赖:通过 PlatformTransactionManager 接口适配不同事务管理器(如 JDBC、JPA)。

使用ThreadLocal存储事务的状态,(如连接、隔离级别)通过TransactionSynchronizationManager存在线程本地变量,保证线程安全。

所以基于这个情况,spring事务失效的场景有:

1.吃掉运行时异常没抛出:

2.未配置回滚规则,要配置rollbackFor=Exception.class指定类型

3.调用this

4.非public方法

5.事务的传播属性设置为never,not_support这种不支持事务的

6.调用了不支持事务的数据库

7.事务嵌套:

REQUIRES_NEW 会挂起外部事务,提前提交

嵌套事务用 NESTED 会创建 savepoint,支持回滚子事务(但数据库需支持)

1
2
3
4
5
6
7
8
9
10
11
// 外部事务
@Transactional
public void outer() {
inner(); // REQUIRES_NEW
throw new RuntimeException(); // 外部异常不会影响 inner 提交的事务
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 已提交
}

Transactional 属性详解:

  • propagation:传播行为(如 REQUIRED:当前有事务则加入,无则新建);
  • isolation:隔离级别(如 READ_COMMITTED 避免脏读);
  • timeout:事务超时时间(如timeout=30秒);
  • rollbackFor/rollbackForClassName:指定回滚的异常类型;
  • noRollbackFor/noRollbackForClassName:指定不回滚的异常类型。

2.Spring的事务,使用this调用是否生效?

不能生效。

因为Spring事务是通过代理对象来控制的,只有通过代理对象的方法调用才会应用事务管理的相关规则。当使用this直接调用时,是绕过了Spring的代理机制,因此不会应用事务设置。

3.谈一下spring事务

Spring 提供了 统一的事务管理抽象,既支持编程式事务,也支持声明式事务(AOP)。核心目的是控制数据的一致性与完整性。

多个数据库操作要么都成功,要么都失败(回滚)

@Transactional

放在类上:表示该类的所有方法默认使用事务。

放在方法上:表示该方法启用事务。

1
2
3
4
5
6
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
rollbackFor = Exception.class
)

常用属性,用来指明信息。

ACID和事务的隔离级别和mysql一样

传播行为 含义
REQUIRED 默认值,当前存在事务就加入,否则新建事务
REQUIRES_NEW 每次都新建事务,挂起当前事务
NESTED 如果存在事务,就嵌套执行一个子事务(savepoint)
SUPPORTS 有事务就加入,无事务就非事务执行
NOT_SUPPORTED 始终非事务执行,挂起现有事务
NEVER 非事务执行,存在事务则抛异常
MANDATORY 必须在事务中运行,否则抛异常

spring支持编程式事务管理和声明式事务管理两种方式: ①编程式事务管理使用TransactionTemplate。 ②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

spring

1.说一下你对 Spring 的理解

Spring框架核心特性包括:

  • IoC容器:Spring通过控制反转实现了对象的创建和对象间的依赖关系管理。开发者只需要定义好Bean及其依赖关系,Spring容器负责创建和组装这些对象。
  • AOP:面向切面编程,允许开发者定义横切关注点,例如事务管理、安全控制等,独立于业务逻辑的代码。通过AOP,可以将这些关注点模块化,提高代码的可维护性和可重用性。
  • 事务管理:Spring提供了一致的事务管理接口,支持声明式和编程式事务。开发者可以轻松地进行事务管理,而无需关心具体的事务API。
  • MVC框架:Spring **MVC是一个基于Servlet API构建的Web框架,采用了模型-视图-控制器(MVC)架构。**它支持灵活的URL到页面控制器的映射,以及多种视图技术。

2.spring的核心思想说说你的理解?

核心思想 解决的问题 实现手段 典型应用场景
IOC 对象创建与依赖管理的高耦合 容器管理Bean生命周期 动态替换数据库实现、服务组装
DI 依赖关系的硬编码问题 Setter/构造器/注解注入 注入数据源、服务层依赖DAO层
AOP 横切逻辑分散在业务代码中 动态代理与切面配置 日志、事务、权限校验统一处理

Spring通过这IOC、DI、AOP三大核心思想,实现了轻量级、高内聚低耦合的企业级应用开发框架,成为Java生态中不可或缺的基石。

3.Spring IoC和AOP 介绍一下

  • IoC:即控制反转的意思,它是一种创建和获取对象的技术思想,**依赖注入(DI)是实现这种技术的一种方式。**传统开发过程中,我们需要通过new关键字来创建对象。使用IoC思想开发方式的话,我们不通过new关键字创建对象,而是通过IoC容器来帮我们实例化对象。 通过IoC的方式,可以大大降低对象之间的耦合度。
  • AOP:是面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,以减少系统的重复代码,降低模块间的耦合度。Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。

在 Spring 框架中,IOC 和 AOP 结合使用,可以更好地实现代码的模块化和分层管理。例如:

  • 通过 IOC 容器管理对象的依赖关系,然后通过 AOP 将横切关注点统一切入到需要的业务逻辑中。
  • 使用 IOC 容器管理 Service 层和 DAO 层的依赖关系,然后通过 AOP 在 Service 层实现事务管理、日志记录等横切功能,使得业务逻辑更加清晰和可维护。

4.Spring的aop介绍一下

Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。

我们知道,Java 就是一门面向对象编程的语言,在 OOP 中最小的单元就是“Class 对象”,但是在 AOP 中最小的单元是“切面”。一个“切面”可以包含很多种类型和对象,对它们进行模块化管理,例如事务管理。

在面向切面编程的思想里面,把功能分为两种

  • 核心业务:登陆、注册、增、删、改、查、都叫核心业务
  • 周边功能:日志、事务管理这些次要的为周边业务

在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

在 AOP 中有以下几个概念:

  • AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。
  • Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。
  • Advice:通知,即我们定义的一个切面中的横切逻辑,有“around”,“before”和“after”三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。
  • Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。
  • Introduction:引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。
  • Weaving:织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。
  • AOP proxy:AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。
  • Target object:目标对象,就是被代理的对象。

Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。

5.IOC和AOP是通过什么机制来实现的?

Spring IOC 实现机制

  • 反射:Spring IOC容器利用Java的反射机制动态地加载类、创建对象实例及调用对象方法,反射允许在运行时检查类、方法、属性等信息,从而实现灵活的对象实例化和管理。
  • 依赖注入:IOC的核心概念是依赖注入,即容器负责管理应用程序组件之间的依赖关系。Spring通过构造函数注入、属性注入或方法注入,将组件之间的依赖关系描述在配置文件中或使用注解。
  • 设计模式 - 工厂模式:Spring IOC容器通常采用**工厂模式来管理对象的创建和生命周期。**容器作为工厂负责实例化Bean并管理它们的生命周期,将Bean的实例化过程交给容器来管理。
  • 容器实现:Spring IOC容器是实现IOC的核心,通常使用BeanFactory或ApplicationContext来管理Bean。BeanFactory是IOC容器的基本形式,提供基本的IOC功能;ApplicationContext是BeanFactory的扩展,并提供更多企业级功能。

Spring AOP 实现机制

Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。

Spring AOP支持两种动态代理:

  • 基于JDK的动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。
  • 基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。

6.怎么理解SpringIoc?

IOC:Inversion Of Control,即控制反转,是一种设计思想。在传统的 Java SE 程序设计中,我们直接在对象内部通过 new 的方式来创建对象,是程序主动创建依赖对象;

img

而在Spring程序设计中,IOC 是有专门的容器去控制对象。

img

所谓控制就是对象的创建、初始化、销毁。

  • 创建对象:原来是 new 一个,现在是由 Spring 容器创建。
  • 初始化对象:原来是对象自己通过构造器或者 setter 方法给依赖的对象赋值,现在是由 Spring 容器自动注入。
  • 销毁对象:原来是直接给对象赋值 null 或做一些销毁操作,现在是 Spring 容器管理生命周期负责销毁对象。

总结:IOC 解决了繁琐的对象生命周期的操作,解耦了我们的代码。所谓反转:其实是反转的控制权,前面提到是由 Spring 来控制对象的生命周期,那么对象的控制就完全脱离了我们的控制,控制权交给了 Spring 。这个反转是指:我们由对象的控制者变成了 IOC 的被动控制者。

7.依赖倒置,依赖注入,控制反转分别是什么?

  • 控制反转:“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。
  • 依赖注入:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。
  • 依赖倒置:这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

8.依赖注入了解吗?怎么实现依赖注入的?

在传统编程中,当一个类需要使用另一个类的对象时,通常会在该类内部通过new关键字来创建依赖对象,这使得类与类之间的耦合度较高。

而依赖注入则是将对象的创建和依赖关系的管理交给 Spring 容器来完成,类只需要声明自己所依赖的对象,容器会在运行时将这些依赖对象注入到类中,从而降低了类与类之间的耦合度,提高了代码的可维护性和可测试性。

具体到Spring中,常见的依赖注入的实现方式,比如构造器注入、Setter方法注入,还有字段注入。

  • **构造器注入:**通过构造函数传递依赖对象,保证对象初始化时依赖已就绪。
1
2
3
4
5
6
7
8
9
@Service
public class UserService {
private final UserRepository userRepository;

// 构造器注入(Spring 4.3+ 自动识别单构造器,无需显式@Autowired)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
  • **Setter 方法注入:**通过 Setter 方法设置依赖,灵活性高,但依赖可能未完全初始化。
1
2
3
4
5
6
7
8
public class PaymentService {
private PaymentGateway gateway;

@Autowired
public void setGateway(PaymentGateway gateway) {
this.gateway = gateway;
}
}
  • **字段注入:**直接通过 @Autowired 注解字段,代码简洁但隐藏依赖关系,不推荐生产代码。
1
2
3
4
5
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
}

9.如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?

  • Bean的生命周期管理:需要设计Bean的创建、初始化、销毁等生命周期管理机制,可以考虑使用工厂模式和单例模式来实现。
  • 依赖注入:需要实现依赖注入的功能,包括属性注入、构造函数注入、方法注入等,可以考虑使用反射机制和XML配置文件来实现。
  • Bean的作用域:需要支持多种Bean作用域,比如单例、原型、会话、请求等,可以考虑使用Map来存储不同作用域的Bean实例。
  • AOP功能的支持:需要支持AOP功能,可以考虑使用动态代理机制和切面编程来实现。
  • 异常处理:需要考虑异常处理机制,包括Bean创建异常、依赖注入异常等,可以考虑使用try-catch机制来处理异常。
  • 配置文件加载:需要支持从不同的配置文件中加载Bean的相关信息,可以考虑使用XML、注解或者Java配置类来实现。

10.SpringAOP主要想解决什么问题

它的目的是对于面向对象思维的一种补充,而不是像引入命令式、函数式编程思维让他顺应另一种开发场景。在我个人的理解下AOP更像是一种对于不支持多继承的弥补,除开对象的主要特征(我更喜欢叫“强共性”)被抽象为了一条继承链路,对于一些“弱共性”,AOP可以统一对他们进行抽象和集中处理。

举一个简单的例子,打印日志。需要打印日志可能是许多对象的一个共性,这在企业级开发中十分常见,但是日志的打印并不反应这个对象的主要共性。而日志的打印又是一个具体的内容,它并不抽象,所以它的工作也不可以用接口来完成。而如果利用继承,打印日志的工作又横跨继承树下面的多个同级子节点,强行侵入到继承树内进行归纳会干扰这些强共性的区分。

这时候,我们就需要AOP了。AOP首先在**一个Aspect(切面)里定义了一些Advice(增强),其中包含具体实现的代码,同时整理了切入点,切入点的粒度是方法。**最后,我们将这些Advice织入到对象的方法上,形成了最后执行方法时面对的完整方法。

img

11.SpringAOP的原理了解吗

Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。

Spring AOP支持两种动态代理:

  • 基于JDK的动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。
  • 基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。

12.动态代理是什么?

ava的动态代理是一种在运行时动态创建代理对象的机制,主要用于在不修改原始类的情况下对方法调用进行拦截和增强。

Java动态代理主要分为两种类型:

  • 基于接口的代理(JDK动态代理): 这种类型的代理要求目标对象必须实现至少一个接口。Java动态代理会创建一个实现了相同接口的代理类,然后在运行时动态生成该类的实例。这种代理的实现核心是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。每一个**动态代理类都必须实现InvocationHandler接口,并且每个代理类的实例都关联到一个handler。**当通过代理对象调用一个方法时,这个方法的调用会被转发为由InvocationHandler接口的invoke()方法来进行调用。
  • 基于类的代理(CGLIB动态代理): CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB代理不需要目标类实现接口,而是通过继承的方式创建代理类。因此,如果目标对象没有实现任何接口,可以使用CGLIB来创建动态代理。

13.动态代理和静态代理的区别

代理是一种常用的设计模式,目的是:**为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。**代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。

区别:

  • 静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理。静态代理通常只代理一个类;
  • 动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类。

14.AOP实现有哪些注解?

常用的注解包括:

  • @Aspect:用于定义切面,标注在切面类上。
  • @Pointcut:定义切点,标注在方法上,用于指定连接点。
  • @Before:在方法执行之前执行通知。
  • @After:在方法执行之后执行通知。
  • @Around:在方法执行前后都执行通知。
  • @AfterReturning:在方法执行后返回结果后执行通知。
  • @AfterThrowing:在方法抛出异常后执行通知。
  • @Advice:通用的通知类型,可以替代@Before、@After等。

15.什么是反射?有哪些使用场景?

射机制是指程序在运行状态下,**对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法。**也就是说,Java 反射允许在运行时获取类的信息并动态操作对象,即使在编译时不知道具体的类也能实现。

反射具有以下特性:

  1. 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
  2. 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance()方法或Constructor对象的newInstance()方法实现的。
  3. 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
  4. 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。

img

Java反射机制在spring框架中,很多地方都用到了反射,让我们来看看Spring的IoC和AOP是如何使用反射技术的。

1、Spring框架的依赖注入(DI)和控制反转(IoC)

Spring 使用反射来实现其核心特性:依赖注入。

在Spring中,开发者可以通过XML配置文件或者基于注解的方式声明组件之间的依赖关系。当应用程序启动时,Spring容器会扫描这些配置或注解,然后利用反射来实例化Bean(即Java对象),并根据配置自动装配它们的依赖。

例如**,当一个Service类需要依赖另一个DAO类时,开发者可以在Service类中使用@Autowired注解,而无需自己编写创建DAO实例的代码。**Spring容器会在运行时解析这个注解,**通过反射找到对应的DAO类,实例化它,并将其注入到Service类中。**这样不仅降低了组件之间的耦合度,也极大地增强了代码的可维护性和可测试性。

2、动态代理的实现

在需要对现有类的方法调用进行拦截、记录日志、权限控制或是事务管理等场景中,反射结合动态代理技术被广泛应用。

一个典型的例子是Spring AOP(面向切面编程)的实现。Spring AOP允许开发者定义切面(Aspect),这些切面可以横切关注点(如日志记录、事务管理),并将其插入到业务逻辑中,而不需要修改业务逻辑代码。

例如,为了给所有的服务层方法添加日志记录功能,可以定义一个切面,在这个切面中,Spring会使用JDK动态代理或CGLIB(如果目标类没有实现接口)来创建目标类的代理对象。这个代理对象在调用任何方法前或后,都会执行切面中定义的代码逻辑(如记录日志),而这一切都是在运行时通过反射来动态构建和执行的,无需硬编码到每个方法调用中。

这两个例子展示了反射机制如何在实际工程中促进松耦合、高内聚的设计,以及如何提供动态、灵活的编程能力,特别是在框架层面和解决跨切面问题时。

16.spring是如何解决循环依赖的?

循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环,如下图。

img

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

  • 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
  • 第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
  • 第三种:通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring都会产生异常。

Spring 解决单例模式下的setter循环依赖问题的主要方式是通过三级缓存解决循环依赖。三级缓存指的是 Spring 在创建 Bean 的过程中,通过三级缓存来缓存正在创建的 Bean,以及已经创建完成的 Bean 实例。具体步骤如下:

  • 实例化 Bean:Spring 在实例化 Bean 时,会先创建一个空的 Bean 对象,并将其放入一级缓存中。
  • 属性赋值:Spring 开始对 Bean 进行属性赋值,如果发现循环依赖,会将当前 Bean 对象提前暴露给后续需要依赖的 Bean(通过提前暴露的方式解决循环依赖)。
  • 初始化 Bean:完成属性赋值后,Spring 将 Bean 进行初始化,并将其放入二级缓存中。
  • 注入依赖:Spring 继续对 Bean 进行依赖注入,如果发现循环依赖,会从二级缓存中获取已经完成初始化的 Bean 实例。

通过三级缓存的机制,Spring 能够在处理循环依赖时,确保及时暴露正在创建的 Bean 对象,并能够正确地注入已经初始化的 Bean 实例,从而解决循环依赖问题,保证应用程序的正常运行。

17.spring三级缓存的数据结构是什么?

都是 Map类型的缓存,比如Map {k:name; v:bean}。

  1. 一级缓存(Singleton Objects):这是一个Map类型的缓存,存储的是已经完全初始化好的bean,即完全准备好可以使用的bean实例。**键是bean的名称,值是bean的实例。**这个缓存在DefaultSingletonBeanRegistry类中的singletonObjects属性中。
  2. 二级缓存(Early Singleton Objects):这同样是一个Map类型的缓存,**存储的是早期的bean引用,即已经实例化但还未完全初始化的bean。**这些bean已经被实例化,**但是可能还没有进行属性注入等操作。**这个缓存在DefaultSingletonBeanRegistry类中的earlySingletonObjects属性中。
  3. 三级缓存(Singleton Factories):这也是一个Map类型的缓存,存储的是ObjectFactory对象,这些对象可以生成早期的bean引用。**当一个bean正在创建过程中,如果它被其他bean依赖,那么这个正在创建的bean就会通过这个ObjectFactory来创建一个早期引用,从而解决循环依赖的问题。**这个缓存在DefaultSingletonBeanRegistry类中的singletonFactories属性中。

核心机制是提前暴露Bean的早期引用,允许在依赖注入阶段使用未完全初始化的Bean。

仅支持单例作用域和setter/字段注入的循环依赖,构造函数注入和原型作用域无法自动解决。

优先选择使用setter注入或@Lazy注解解决循环依赖。

最佳实践:通过重构代码(如提取公共Bean、使用事件机制)从根本上消除循环依赖。

禁用循环依赖:在明确不需要循环依赖时,禁用Spring的循环依赖支持以提高代码健壮性。

1
2
3
4
5
6
7
8
9
@Component
public class BeanA {
private final BeanB beanB;

@Autowired
public BeanA(@Lazy BeanB beanB) {
this.beanB = beanB;
}
}
1
2
3
4
5
6
创建 A -> 依赖注入 B -> 发现 B 依赖 A
↓ ↑
A 被提早暴露给 B(从三级缓存创建放入二级缓存)
↓ ↑
B 创建完成,再回来注入 A

18.spring框架中都用到了哪些设计模式

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

19.spring 常用注解有什么?

@Autowired 注解

@Autowired:主要用于自动装配bean。当Spring容器 (opens new window)中存在与要注入的属性类型匹配的bean时,它会自动将bean注入到属性中。就跟我们new 对象一样。

用法很简单,如下示例代码:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class MyService {
}

@Component
public class MyController {

@Autowired
private MyService myService;

}

在上面的示例代码中,MyController类中的myService属性被@Autowired注解标记,Spring会自动将MyService类型的bean注入到myService属性中。

@Component

这个注解用于标记一个类作为Spring的bean。当一个类被@Component注解标记时,Spring会将其实例化为一个bean,并将其添加到Spring容器中。在上面讲解@Autowired的时候也看到了,示例代码:

1
2
3
@Component
public class MyComponent {
}

在上面的示例代码中,MyComponent类被@Component注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。

@Configuration

@Configuration,注解用于标记一个类作为Spring的配置类。配置类可以包含@Bean注解的方法,用于定义和配置bean,作为全局配置。示例代码:

1
2
3
4
5
6
7
8
9
@Configuration
public class MyConfiguration {

@Bean
public MyBean myBean() {
return new MyBean();
}

}

@Bean

@Bean注解用于标记一个方法作为Spring的bean工厂方法。当一个方法被@Bean注解标记时,Spring会将该方法的返回值作为一个bean,并将其添加到Spring容器中,如果自定义配置,经常用到这个注解。

1
2
3
4
5
6
7
8
9
10
@Configuration
public class MyConfiguration {

@Bean
public MyBean myBean() {
return new MyBean();

}

}

@Service

@Service,这个注解用于标记一个类作为服务层的组件。它是@Component注解的特例,用于标记服务层的bean,一般标记在业务service的实现类。

1
2
3
4
@Service
public class MyServiceImpl {

}

@Repository

@Repository注解用于标记一个类作为数据访问层的组件。它也是@Component注解的特例,用于标记数据访问层的bean。这个注解很容易被忽略,导致数据库无法访问。

1
2
3
4
@Repository
public class MyRepository {

}

在上面的示例代码中,MyRepository类被@Repository注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。

@Controller

@Controller注解用于标记一个类作为控制层的组件。它也是@Component注解的特例,用于标记控制层的bean。这是MVC结构的另一个部分,加在控制层

1
2
3
4
@Controller
public class MyController {

}

在上面的示例代码中,MyController类被@Controller注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。

20.@componet和@bean

(1)@Component 注解作用于类,而@Bean注解作用于方法。

(2)@Component注解通过类路径扫描来自动侦测以及自动装配到 Spring 容器中,而@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候创建这个bean实例。

(3)@Bean 注解比 @Component 注解的自定义性更强,当需要引用第三方库中的类装配到 Spring容器时,只能通过 @Bean来实现。

21.讲一下 Spring 中用到的设计模式?

工厂设计模式:用于管理bean对象,包括创建、初始化、销毁等

单例设计模式:默认bean的作用域

动态代理模式:springAop使用了动态代理模式

观察者模式:事件监听的作用

适配器模式:将不兼容的接口转换为客户端期望的接口,如springmvc中handler adapter适配不同的handler 模板方法模式:spring提供一些模板类,如jdbctemplate实现连接不同的数据库,将具体实现步骤放到子类

22.springsecurity是什么

springsecurity是Spring生态里的安全框架,主要用于web应用的权限管理和认证,支持多种登录方式并集成防护机制,保障企业级系统的安全性。可防范CSRF/XSS等攻击, 我们可以调整里面可以放行的页面是什么,比如登录页面注册页面等等,还有一些前端页面。通过.antMarch(“”)来放行页面,测试的时候可以关闭这个csrf保护

23.spring 框架里的事务传播机制有哪些?比如 Propagation.REQUIRED 和 REQUIRES_NEW 的区别是啥呀?

  • Propagation.REQUIRED(默认)
    如果当前有事务,就加入这个事务;如果没有,就新建一个事务。
    例子:A 方法调 B 方法,A 有事务,B 也用REQUIRED,那 AB 共用一个事务,要么都成功,要么都回滚。
  • Propagation.REQUIRES_NEW
    不管当前有没有事务,都新建一个事务。原来的事务会被挂起,等新事务完成后再继续。
    例子:A 方法调 B 方法,A 有事务,B 用REQUIRES_NEW,那 B 自己开一个新事务。如果 B 失败,A 的事务可能不受影响(看 A 有没有捕获异常)。
传播行为类型 含义总结 是否开启新事务 当前无事务时行为 适用场景简述
REQUIRED 默认行为,有事务用当前,无则新建 新建事务 绝大多数业务场景,默认选项
REQUIRES_NEW 始终新建事务,挂起外部事务 新建事务 独立操作如日志记录、通知发送等
NESTED 嵌套事务,外部事务失败可回滚内层(依赖保存点) 否(嵌套) 新建事务 数据段落逻辑回滚,不影响整体事务
SUPPORTS 有事务则加入,无事务则以非事务方式运行 非事务运行 兼容性要求不高的查询操作
NOT_SUPPORTED 不支持事务,挂起外部事务 非事务运行 读写分离、不需要事务控制的场景
MANDATORY 必须运行在事务中,没事务则抛异常 抛异常 强依赖事务环境的组件方法
NEVER 必须在非事务环境中运行,有事务则抛异常 正常运行 断言无事务的调用场景(如外部调用)

24.AOP 的实现原理

概念:适用场景

作用点:

  • 切面(Aspect):封装横切逻辑的类,用@Aspect注解标识;
  • 切点(Pointcut):定义横切逻辑作用的目标方法(如@Pointcut("execution(* com.service.*.*(..))"));
  • 通知(Advice):横切逻辑的具体实现,包括 5 种类型
  • 连接点(Joinpoint):程序执行中的具体点(如方法调用、字段修改),AOP 中主要指方法调用

动态代理:

  • JDK 动态代理:基于接口实现,生成InvocationHandler的代理对象,适用于目标类有接口的情况;
  • CGLIB 代理:基于子类继承,生成目标类的子类代理对象,适用于无接口的类(需引入cglib依赖);
  • Spring 默认策略:有接口用 JDK 代理,无接口用 CGLIB 代理(可通过proxy-target-class属性强制使用 CGLIB)。
  • 流程:
  • Spring 容器扫描到@Aspect注解的切面类,解析切点表达式和通知类型;
  • 对目标类判断是否适用 JDK/CGLIB 代理,生成代理对象;
  • 当调用代理对象的方法时,实际执行的是通知逻辑 + 目标方法的组合逻辑。

细节:

  1. 开启 AOP 支持:在配置类中添加@EnableAspectJAutoProxy注解,或在 XML 中配置<aop:aspectj-autoproxy/>

  2. 代理模式选择

    • 默认proxyTargetClass=false(优先 JDK 代理);
    • 若需强制 CGLIB 代理,设置proxyTargetClass=true@EnableAspectJAutoProxy(proxyTargetClass = true)
  3. 循环依赖与 AOP:若目标类被代理,注入的是代理对象,需注意this.方法()调用不会触发 AOP(因this指向原始对象)。

场景:

  • 日志记录:在方法执行前后记录入参 / 结果,避免业务代码污染;

  • 事务管理@Transactional本质是 AOP 实现,在方法调用时开启 / 提交 / 回滚事务;

  • 权限校验:在接口调用前校验用户权限,拒绝非法请求;

  • 性能监控:统计方法执行耗时,用于性能优化。

流程:

  1. 调用代理方法:客户端调用代理对象的方法(如userService.save());
  2. 匹配切点:Spring 判断该方法是否匹配切面的切点表达式;
  3. 执行通知逻辑
    • @Around中先执行前置逻辑(如日志记录开始);
    • 调用proceed()触发目标方法执行;
    • 执行后置逻辑(如日志记录结束、统计耗时);
  4. 返回结果:通知逻辑执行完毕后,将结果返回给客户端。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 定义切面类
@Aspect
@Component
public class LogAspect {

// 2. 定义切点(匹配所有Service方法)
@Pointcut("execution(* com.service..*.*(..))")
public void servicePointcut() {}

// 3. 环绕通知实现耗时统计
@Around("servicePointcut()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
// 调用目标方法
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
// 记录日志
String methodName = joinPoint.getSignature().getName();
System.out.println("方法 " + methodName + " 执行耗时:" + (end - start) + "ms");
return result;
}
}

Bean

1.Bean的生命周期说一下?

img

  1. Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
  2. Bean实例化后对将Bean的引入和值注入到Bean的属性中
  3. 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
  4. 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  5. 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来
  6. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
  7. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
  8. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  9. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

创建,初始化,调用,销毁; bean的创建方式有四种,构造器,静态工厂,实例工厂,setter注入的方式。 spring在调用bean的时候因为作用域的不同,不同的bean初始化和创建的时间也不相同。 在作用域为singleton的时候,bean是随着容器一起被创建好并且实例化的, 在作用域为pritotype的时候,bean是随着它被调用的时候才创建和实例化完成。 然后程序就可以使用bean了,当程序完成销毁的时候,bean也被销毁。

2.Bean是否单例?

Spring 中的 Bean 默认都是单例的

就是说,每个Bean的实例只会被创建一次,并且会被存储在Spring容器的缓存中,以便在后续的请求中重复使用。这种单例模式可以提高应用程序的性能和内存效率。

但是,Spring也支持将Bean设置为多例模式,即每次请求都会创建一个新的Bean实例。要将Bean设置为多例模式,可以在Bean定义中通过设置scope属性为"prototype"来实现。

需要注意的是,虽然Spring的默认行为是将Bean设置为单例模式,但在一些情况下,使用多例模式是更为合适的,例如在创建状态不可变的Bean或有状态Bean时。此外,需要注意的是,如果Bean单例是有状态的,那么在使用时需要考虑线程安全性问题。

3.Bean的单例和非单例,生命周期是否一样

不一样的,Spring Bean 的生命周期完全由 IoC 容器控制。Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 Bean,Spring 在创建好交给使用者之后,则不会再管理后续的生命周期。

具体区别如下:

阶段 单例(Singleton) 非单例(如Prototype)
创建时机 容器启动时创建(或首次请求时,取决于配置)。 每次请求时创建新实例。
初始化流程 完整执行生命周期流程(属性注入、Aware接口、初始化方法等)。 每次创建新实例时都会完整执行生命周期流程(仅到初始化完成)。
销毁时机 容器关闭时销毁,触发DisposableBeandestroy-method 容器不管理销毁,需由调用者自行释放资源(Spring不跟踪实例)。
内存占用 单实例常驻内存,高效但需注意线程安全。 每次请求生成新实例,内存开销较大,需手动管理资源释放。
适用场景 无状态服务(如Service、DAO层)。 有状态对象(如用户会话、临时计算对象)。

4.Spring bean的作用域有哪些?

Spring框架中的Bean作用域(Scope)定义了Bean的生命周期和可见性。不同的作用域影响着Spring容器如何管理这些Bean的实例,包括它们如何被创建、如何被销毁以及它们是否可以被多个用户共享。

Spring支持几种不同的作用域,以满足不同的应用场景需求。以下是一些主要的Bean作用域:

  • Singleton(单例):在整个应用程序中只存在一个 Bean 实例。默认作用域,Spring 容器中只会创建一个 Bean 实例,并在容器的整个生命周期中共享该实例。
  • Prototype(原型)每次请求时都会创建一个新的 Bean 实例。次从容器中获取该 Bean 时都会创建一个新实例,适用于状态非常瞬时的 Bean。
  • Request(请求):**每个 HTTP 请求都会创建一个新的 Bean 实例。**仅在 Spring Web 应用程序中有效,每个 HTTP 请求都会创建一个新的 Bean 实例,适用于 Web 应用中需求局部性的 Bean。
  • Session(会话)Session 范围内只会创建一个 Bean 实例。该 Bean 实例在用户会话范围内共享,仅在 Spring Web 应用程序中有效,适用于与用户会话相关的 Bean。
  • Application:**当前 ServletContext 中只存在一个 Bean 实例。**仅在 Spring Web 应用程序中有效,该 Bean 实例在整个 ServletContext 范围内共享,适用于应用程序范围内共享的 Bean。
  • WebSocket(Web套接字):**在 WebSocket 范围内只存在一个 Bean 实例。**仅在支持 WebSocket 的应用程序中有效,该 Bean 实例在 WebSocket 会话范围内共享,适用于 WebSocket 会话范围内共享的 Bean。
  • Custom scopes(自定义作用域)Spring 允许开发者定义自定义的作用域,通过实现 Scope 接口来创建新的 Bean 作用域。

在Spring配置文件中,可以通过标签的scope属性来指定Bean的作用域。例如:

1
<bean id="myBean" class="com.example.MyBeanClass" scope="singleton"/>

在Spring Boot或基于Java的配置中,可以通过@Scope注解来指定Bean的作用域。例如:

1
2
3
4
5
@Bean  
@Scope("prototype")
public MyBeanClass myBean() {
return new MyBeanClass();
}

5.Spring容器里存的是什么?

在Spring容器中,存储的主要是Bean对象

Bean是Spring框架中的基本组件,**用于表示应用程序中的各种对象。**当应用程序启动时,Spring容器会根据配置文件或注解的方式创建和管理这些Bean对象。Spring容器会负责创建、初始化、注入依赖以及销毁Bean对象。

6.在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做

在Spring框架中,如果你希望在Bean加载(即实例化、属性赋值、初始化等过程完成后)或销毁前后执行某些逻辑,你可以使用Spring的生命周期回调接口或注解。这些接口和注解允许你定义在Bean生命周期的关键点执行的代码。

使用init-method和destroy-method

在XML配置中,你可以通过init-method和destroy-method属性来指定Bean初始化后和销毁前需要调用的方法。

1
2
<bean id="myBean" class="com.example.MyBeanClass"  
init-method="init" destroy-method="destroy"/>

然后,在你的Bean类中实现这些方法:

1
2
3
4
5
6
7
8
9
10
public class MyBeanClass {  

public void init() {
// 初始化逻辑
}

public void destroy() {
// 销毁逻辑
}
}

实现InitializingBean和DisposableBean接口

你的Bean类可以实现org.springframework.beans.factory.InitializingBean和org.springframework.beans.factory.DisposableBean接口,并分别实现afterPropertiesSet和destroy方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.beans.factory.DisposableBean;  
import org.springframework.beans.factory.InitializingBean;

public class MyBeanClass implements InitializingBean, DisposableBean {

@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}

@Override
public void destroy() throws Exception {
// 销毁逻辑
}
}

使用@PostConstruct和@PreDestroy注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;

public class MyBeanClass {

@PostConstruct
public void init() {
// 初始化逻辑
}

@PreDestroy
public void destroy() {
// 销毁逻辑
}
}

使用@Bean注解的initMethod和destroyMethod属性

在基于Java的配置中,你还可以在@Bean注解中指定initMethod和destroyMethod属性。

1
2
3
4
5
6
7
8
@Configuration  
public class AppConfig {

@Bean(initMethod = "init", destroyMethod = "destroy")
public MyBeanClass myBean() {
return new MyBeanClass();
}
}

7.Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的

在Spring框架中,基于注解的Bean注入(如@Autowired@Resource)和基于XML的依赖注入虽然在配置方式上不同,但在底层最终都通过Spring容器的统一机制实现依赖注入。它们的核心流程可以归纳为以下步骤:

阶段 注解注入 XML注入
配置解析 通过注解处理器扫描类路径,解析@Component@Autowired等注解。 解析XML文件中的<bean><property><constructor-arg>标签。
生成BeanDefinition 将注解信息转换为AnnotatedBeanDefinition 将XML配置转换为GenericBeanDefinition
依赖注入 AutowiredAnnotationBeanPostProcessor等后处理器处理。 在BeanDefinition中直接记录属性或构造器参数,由容器直接注入。
最终结果 生成完整的Bean实例,完成依赖注入。 生成完整的Bean实例,完成依赖注入。

XML 注入

使用 XML 文件进行 Bean 注入时,Spring 在启动时会读取 XML 配置文件,以下是其底层步骤:

  • Bean 定义解析:Spring 容器通过 XmlBeanDefinitionReader 类解析 XML 配置文件,读取其中的 <bean> 标签以获取 Bean 的定义信息。
  • 注册 Bean 定义:解析后的 Bean 信息被注册到 BeanDefinitionRegistry(如 DefaultListableBeanFactory)中,包括 Bean 的类、作用域、依赖关系、初始化和销毁方法等。
  • 实例化和依赖注入:当应用程序请求某个 Bean 时,Spring 容器会根据已经注册的 Bean 定义:
    • 首先,使用反射机制创建该 Bean 的实例
    • 然后,根据 Bean 定义中的配置,通过 setter 方法、构造函数或方法注入所需的依赖 Bean。

注解注入

使用注解进行 Bean 注入时,Spring 的处理过程如下:

  • 类路径扫描:当 Spring 容器启动时,它首先会进行类路径扫描,查找带有特定注解(如 @Component@Service@Repository@Controller)的类。
  • 注册 Bean 定义:找到的类会被注册到 BeanDefinitionRegistry 中,Spring 容器将为其生成 Bean 定义信息。这通常通过 AnnotatedBeanDefinitionReader 类来实现。
  • 依赖注入:与 XML 注入类似,Spring 在实例化 Bean 时,也会检查字段上是否有 @Autowired@Inject@Resource 注解。如果有,Spring 会根据注解的信息进行依赖注入。

尽管使用的方式不同,但 XML 注入和注解注入在底层的实现机制是相似的,主要体现在以下几个方面:

  1. BeanDefinition:无论是 XML 还是注解,最终都会生成 BeanDefinition 对象,并存储在同一个 BeanDefinitionRegistry 中。
  2. 后处理器:
    • **Spring 提供了多个 Bean 后处理器(**如 AutowiredAnnotationBeanPostProcessor),用于处理注解(如 @Autowired)的依赖注入。
    • 对于 XML,Spring 也有相应的后处理器来处理 XML 配置的依赖注入。
  3. 依赖查找:在依赖注入时,Spring 容器会通过 ApplicationContext 中的 BeanFactory 方法来查找和注入依赖,无论是通过 XML 还是注解,都会调用类似的查找方法。

8.Spring给我们提供了很多扩展点,这些有了解吗?

Spring框架提供了许多扩展点,使得开发者可以根据需求定制和扩展Spring的功能。以下是一些常用的扩展点:

  1. BeanFactoryPostProcessor:允许在Spring容器实例化bean之前修改bean的定义。常用于修改bean属性或改变bean的作用域。
  2. BeanPostProcessor:可以在bean实例化、配置以及初始化之后对其进行额外处理。常用于代理bean、修改bean属性等。
  3. PropertySource:用于定义不同的属性源,如文件、数据库等,以便在Spring应用中使用。
  4. ImportSelector和ImportBeanDefinitionRegistrar:用于根据条件动态注册bean定义,实现配置类的模块化。
  5. Spring MVC中的HandlerInterceptor:用于拦截处理请求,可以在请求处理前、处理中和处理后执行特定逻辑。
  6. Spring MVC中的ControllerAdvice:用于全局处理控制器的异常、数据绑定和数据校验。
  7. Spring Boot的自动配置:通过创建自定义的自动配置类,可以实现对框架和第三方库的自动配置。
  8. 自定义注解:创建自定义注解,用于实现特定功能或约定,如权限控制、日志记录等。

9.Spring 框架中,Bean 的作用域有哪些?请分别说明它们的生命周期和应用场景

5 大标准作用域

  • Singleton:全局唯一实例,Spring 容器加载时创建,适用于无状态服务;
  • Prototype:每次获取新实例,适用于有状态对象(如 DAO 层);
  • Request:每次 HTTP 请求一个实例,需 Web 环境,适用于请求上下文数据;
  • Session:每个用户会话一个实例,需 Web 环境,适用于会话级数据;
  • Application:全局 ServletContext 共享实例,需 Web 环境。

生命周期:实例化→属性注入→@PostConstruct→初始化完成→使用→@PreDestroy→销毁。

循环依赖:setter单例注入,三级缓存,

  • 一级缓存(singletonObjects):存储完全初始化的单例 Bean;
  • 二级缓存(earlySingletonObjects):存储早期曝光的 Bean(解决 A→B→A 的循环依赖);
  • 三级缓存(singletonFactories):存储 Bean 的工厂,用于生成代理对象(解决 A→B(代理)→A 的循环依赖)。

10.BeanFactoryFactoryBean 的区别

BeanFactory 是 Spring 容器本身,负责管理所有 Bean;
FactoryBean 是你自己定义的 Bean,用来创建其他 Bean。

对比点 BeanFactory FactoryBean
类型 接口(Spring 顶层 IOC 容器) 接口(自定义 Bean 的工厂)
位置 Spring 框架提供 用户自定义实现
作用 提供获取 Bean 的基础功能,如 getBean() 用于控制某个复杂 Bean 的创建逻辑
返回对象 返回注册的原始 Bean 实例 返回由 getObject() 方法创建的对象
示例用途 XML 加载、懒加载场景用到 MyBatis 中用于创建 Mapper 接口代理
典型实现类 DefaultListableBeanFactory 用户自定义,如 MyCarFactoryBean

1️⃣ BeanFactory 示例(Spring 提供)

1
2
BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = factory.getBean("userService", UserService.class);
  • BeanFactory 就是容器,负责“拿 Bean”。
  • 常用于早期的 XML 配置方式。

FactoryBean 示例(用户自定义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class MyCarFactoryBean implements FactoryBean<Car> {
@Override
public Car getObject() throws Exception {
// 返回一个自定义创建的对象
return new Car("BMW", 2025);
}

@Override
public Class<?> getObjectType() {
return Car.class;
}
}
@Autowired
private Car car; // 实际注入的是 MyCarFactoryBean.getObject() 返回的 Car 实例
  • Spring 启动时会识别 MyCarFactoryBean,但注入时拿的是 getObject() 的返回值;
  • 如果你想拿到 FactoryBean 本身,可以用:
1
CarFactoryBean factoryBean = (CarFactoryBean) context.getBean("&carFactoryBean");

beanFactory 是 Spring 的底层 IOC 容器接口,提供了 Bean 的获取、懒加载等功能。
FactoryBean 是一个用于创建 Bean 的“工厂类”,由我们自定义创建逻辑,它本身是一个 Bean,但它返回的对象是 getObject() 产生的。
例如:MyBatis 就是通过 FactoryBean 动态创建 Mapper 接口代理对象。

MVC

1.MVC分层介绍一下

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

  • 视图(view): 为用户提供使用界面,与用户直接进行交互。
  • 模型(model): 代表一个存取数据的对象或 JAVA POJO(Plain Old Java Object,简单java对象)。它也可以带有逻辑,主要用于承载数据,并对用户提交请求进行计算的模块。模型分为两类,一类称为数据承载 Bean,一类称为业务处理Bean。所谓数据承载 Bean 是指实体类(如:User类),专门为用户承载业务数据的;而业务处理 Bean 则是指Service 或 Dao 对象, 专门用于处理用户提交请求的。
  • 控制器(controller): 用于将用户请求转发给相应的 Model 进行处理,并根据 Model 的计算结果向用户提供相应响应。它使视图与模型分离。

img

流程步骤:

  1. 用户通过View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等;
  2. 服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的Model,对用户请求进行处理Model 处理;
  3. 将处理结果再交给 Controller(控制器其实只是起到了承上启下的作用);
  4. 根据处理结果找到要作为向客户端发回的响应View 页面,页面经渲染后发送给客户端。

view->model->controller

2.了解SpringMVC的处理流程吗?

img

Spring MVC的工作流程如下:

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用处理器映射器HandlerMapping。
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
  4. DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
  5. 执行处理器Handler(Controller,也叫页面控制器)。
  6. Handler执行完成返回ModelAndView
  7. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
  11. DispatcherServlet响应用户。

请求->前端控制器->mapping->返回执行链给前端控制器->请求处理器适配器->controller->返回modelview->前端控制器给传输给视图解析器,解析完返回给前端控制器->渲染一下到request域->形成view

SpringMVC 的执行流程如下。

  1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
  10. 视图负责将结果显示到浏览器(客户端)。

3.Handlermapping 和 handleradapter有了解吗?

HandlerMapping:

  • 作用:HandlerMapping负责将请求映射到处理器(Controller)。
  • 功能:根据请求的URL、请求参数等信息,找到处理请求的 Controller。
  • 类型:Spring提供了多种HandlerMapping实现,如BeanNameUrlHandlerMapping、RequestMappingHandlerMapping等。
  • 工作流程:根据请求信息确定要请求的处理器(Controller)。HandlerMapping可以根据URL、请求参数等规则确定对应的处理器。

HandlerAdapter:

  • 作用:HandlerAdapter负责调用处理器(Controller)来处理请求
  • 功能:处理器(Controller)可能有不同的接口类型(Controller接口、HttpRequestHandler接口等),HandlerAdapter根据处理器的类型来选择合适的方法来调用处理器。
  • 类型:Spring提供了多个HandlerAdapter实现,用于适配不同类型的处理器。
  • 工作流程:根据处理器的接口类型,选择相应的HandlerAdapter来调用处理器。

工作流程:

  1. 当客户端发送请求时,HandlerMapping根据请求信息找到对应的处理器(Controller)。
  2. HandlerAdapter根据处理器的类型选择合适的方法来调用处理器。
  3. 处理器执行相应的业务逻辑,生成ModelAndView。
  4. HandlerAdapter将处理器的执行结果包装成ModelAndView。
  5. 视图解析器根据ModelAndView找到对应的视图进行渲染。
  6. 将渲染后的视图返回给客户端。

HandlerMapping和HandlerAdapter协同工作,通过将请求映射到处理器,并调用处理器来处理请求,实现了请求处理的流程。它们的灵活性使得在Spring MVC中可以支持多种处理器和处理方式,提高了框架的扩展性和适应性。

Springboot

1.为什么使用springboot

  • 简化开发:Spring Boot通过提供一系列的开箱即用的组件和自动配置,简化了项目的配置和开发过程,开发人员可以更专注于业务逻辑的实现,而不需要花费过多时间在繁琐的配置上。
  • 快速启动:Spring Boot提供了快速的应用程序启动方式,可通过内嵌的Tomcat、Jetty或Undertow等容器快速启动应用程序,无需额外的部署步骤,方便快捷。
  • 自动化配置:Spring Boot通过自动配置功能,根据项目中的依赖关系和约定俗成的规则来配置应用程序,减少了配置的复杂性,使开发者更容易实现应用的最佳实践。

2.SpringBoot比Spring好在哪里

  • Spring Boot 提供了自动化配置,大大简化了项目的配置过程。通过约定优于配置的原则,很多常用的配置可以自动完成,开发者可以专注于业务逻辑的实现。
  • Spring Boot 提供了快速的项目启动器,通过引入不同的 Starter,可以快速集成常用的框架和库(如数据库、消息队列、Web 开发等),极大地提高了开发效率。
  • Spring Boot 默认集成了多种内嵌服务器(如Tomcat、Jetty、Undertow),无需额外配置,即可将应用打包成可执行的 JAR 文件,方便部署和运行。

3.SpringBoot用到哪些设计模式?

  • 代理模式:Spring 的 AOP 通过动态代理实现方法级别的切面增强,有静态和动态两种代理方式,采用动态代理方式。
  • 策略模式:Spring AOP 支持 JDK 和 Cglib 两种动态代理实现方式,通过策略接口和不同策略类,运行时动态选择,其创建一般通过工厂方法实现。
  • 装饰器模式:Spring 用 TransactionAwareCacheDecorator 解决缓存与数据库事务问题增加对事务的支持。
  • 单例模式:Spring Bean 默认是单例模式,通过单例注册表(如 HashMap)实现。
  • 简单工厂模式:Spring 中的 BeanFactory 是简单工厂模式的体现,通过工厂类方法获取 Bean 实例。
  • 工厂方法模式:Spring中的 FactoryBean 体现工厂方法模式,为不同产品提供不同工厂。
  • 观察者模式:Spring 观察者模式包含 Event 事件、Listener 监听者、Publisher 发送者,通过定义事件、监听器和发送者实现,观察者注册在 ApplicationContext 中,消息发送由 ApplicationEventMulticaster 完成。
  • 模板模式:Spring Bean 的创建过程涉及模板模式,体现扩展性,类似 Callback 回调实现方式。
  • 适配器模式:Spring MVC 中针对不同方式定义的 Controller,利用适配器模式统一函数定义,定义了统一接口 HandlerAdapter 及对应适配器类。

4.怎么理解SpringBoot中的约定大于配置

约定大于配置是Spring Boot的核心设计理念,它通过预设合理的默认行为和项目规范,大幅减少开发者需要手动配置的步骤,从而提升开发效率和项目标准化程度。

理解 Spring Boot 中的“约定大于配置”原则,可以从以下几个方面来解释:

  • 自动化配置:Spring Boot 提供了大量的自动化配置,通过分析项目的依赖和环境,自动配置应用程序的行为。开发者无需显式地配置每个细节,大部分常用的配置都已经预设好了。例如,引入spring-boot-starter-web后,Spring Boot会自动配置内嵌Tomcat和Spring MVC,无需手动编写XML。
  • 默认配置:Spring Boot 为诸多方面提供大量默认配置,如连接数据库、设置 Web 服务器、处理日志等。开发人员无需手动配置这些常见内容,框架已做好决策。例如,默认的日志配置可让应用程序快速输出日志信息,无需开发者额外繁琐配置日志级别、输出格式与位置等。
  • 约定的项目结构:Spring Boot 提倡特定项目结构,通常主应用程序类(含 main 方法)置于根包,控制器类、服务类、数据访问类等分别放在相应子包,如com.example.demo.controller放控制器类,com.example.demo.service放服务类等。此约定使团队成员更易理解项目结构与组织,新成员加入项目时能快速定位各功能代码位置,提升协作效率。

5.SpringBoot的项目结构是怎么样的?

一个正常的企业项目里一种通用的项目结构和代码层级划分的指导意见。按这《阿里巴巴Java开发手册》时本书上说的,一般分为如下几层:

img

  • 开放接口层:可直接封装 Service 接口暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控制层等。
  • 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。
  • Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
  • Service 层:相对具体的业务逻辑服务层。
  • Manager 层:通用业务处理层,它有如下特征:
    • 1)对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。
    • 2)对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。
    • 3)与 DAO 层交互,对多个 DAO 的组合复用。
  • DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase、OceanBase 等进行数据交互。
  • 第三方服务:包括其它部门 RPC 服务接口,基础平台,其它公司的 HTTP 接口,如淘宝开放平台、支付宝付款服务、高德地图服务等。
  • 外部接口:外部(应用)数据存储服务提供的接口,多见于数据迁移场景中。

如果从一个用户访问一个网站的情况来看,对应着上面的项目代码结构来分析,可以贯穿整个代码分层:

img

对应代码目录的流转逻辑就是:

img

所以,以后每当我们拿到一个新的项目到手时,只要按照这个思路去看别人项目的代码,应该基本都是能理得顺的

6.SpringBoot自动装配原理是什么?

什么是自动装配?

SpringBoot 的自动装配原理是**基于Spring Framework的条件化配置和@EnableAutoConfiguration注解实现的。**这种机制允许开发者在项目中引入相关的依赖,SpringBoot 将根据这些依赖自动配置应用程序的上下文和功能。

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

通俗来讲,自动装配就是通过注解或一些简单的配置就可以在SpringBoot的帮助下开启和配置各种功能,比如数据库访问、Web开发。

SpringBoot自动装配原理

首先点进 @SpringBootApplication 注解的内部

img

接下来将逐个解释这些注解的作用:

  • @Target({ElementType.TYPE}): 该注解指定了这个注解可以用来标记在类上。在这个特定的例子中,这表示该注解用于标记配置类。
  • @Retention(RetentionPolicy.RUNTIME): 这个注解指定了注解的生命周期,即在运行时保留。这是因为 Spring Boot 在运行时扫描类路径上的注解来实现自动配置,所以这里使用了 RUNTIME 保留策略。
  • @Documented: 该注解表示这个注解应该被包含在 Java 文档中。它是用于生成文档的标记,使开发者能够看到这个注解的相关信息。
  • @Inherited: 这个注解指示一个被标注的类型是被继承的。在这个例子中,它表明这个注解可以被继承,如果一个类继承了带有这个注解的类,它也会继承这个注解。
  • @SpringBootConfiguration: 这个注解表明这是一个 Spring Boot 配置类。如果点进这个注解内部会发现与标准的 @Configuration 没啥区别,只是为了表明这是一个专门用于 SpringBoot 的配置。
  • @EnableAutoConfiguration: 这个注解是 Spring Boot 自动装配的核心。它告诉 Spring oot 启用自动配置机制,根据项目的依赖和配置自动配置应用程序的上下文。通过这个注解,SpringBoot 将尝试根据类路径上的依赖自动配置应用程序。
  • @ComponentScan: 这个注解用于配置组件扫描的规则。在这里,它告诉 SpringBoot 在指定的包及其子包中查找组件,这些组件包括被注解的类、@Component 注解的类等。其中的 excludeFilters 参数用于指定排除哪些组件,这里使用了两个自定义的过滤器,分别是 TypeExcludeFilter 和 AutoConfigurationExcludeFilter。

@EnableAutoConfiguration 这个注解是实现自动装配的核心注解

img

  • @AutoConfigurationPackage,将项目src中main包下的所有组件注册到容器中,例如标注了Component注解的类等
  • @Import({AutoConfigurationImportSelector.class}),是自动装配的核心,接下来分析一下这个注解

AutoConfigurationImportSelector 是 Spring Boot 中一个重要的类,它实现了 ImportSelector 接口,用于实现自动配置的选择和导入。具体来说,它通过分析项目的类路径和条件来决定应该导入哪些自动配置类。

代码太多,选取部分主要功能的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

// ... (其他方法和属性)

// 获取所有符合条件的类的全限定类名,例如RedisTemplate的全限定类名(org.springframework.data.redis.core.RedisTemplate;),这些类需要被加载到 IoC 容器中。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 扫描类路径上的 META-INF/spring.factories 文件,获取所有实现了 AutoConfiguration 接口的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

// 过滤掉不满足条件的自动配置类,比如一些自动装配类
configurations = filter(configurations, annotationMetadata, attributes);

// 排序自动配置类,根据 @AutoConfigureOrder 和 @AutoConfigureAfter/@AutoConfigureBefore 注解指定的顺序
sort(configurations, annotationMetadata, attributes);

// 将满足条件的自动配置类的类名数组返回,这些类将被导入到应用程序上下文中
return StringUtils.toStringArray(configurations);
}

// ... (其他方法)
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 获取自动配置类的候选列表,从 META-INF/spring.factories 文件中读取
// 通过类加载器加载所有候选类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());

// 过滤出实现了 AutoConfiguration 接口的自动配置类
configurations = configurations.stream()
.filter(this::isEnabled)
.collect(Collectors.toList());

// 对于 Spring Boot 1.x 版本,还需要添加 spring-boot-autoconfigure 包中的自动配置类
// configurations.addAll(getAutoConfigEntry(getAutoConfigurationEntry(metadata)));
return configurations;
}

// ... (其他方法)
protected List<String> filter(List<String> configurations, AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 使用条件判断机制,过滤掉不满足条件的自动配置类
configurations = configurations.stream()
.filter(configuration -> isConfigurationCandidate(configuration, metadata, attributes))
.collect(Collectors.toList());
return configurations;
}

// ... (其他方法)
protected void sort(List<String> configurations, AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 根据 @AutoConfigureOrder 和 @AutoConfigureAfter/@AutoConfigureBefore 注解指定的顺序对自动配置类进行排序
configurations.sort((o1, o2) -> {
int i1 = getAutoConfigurationOrder(o1, metadata, attributes);
int i2 = getAutoConfigurationOrder(o2, metadata, attributes);
return Integer.compare(i1, i2);
});
}

// ... (其他方法)

}

梳理一下,以下是AutoConfigurationImportSelector的主要工作:

  • 扫描类路径: 在应用程序启动时,AutoConfigurationImportSelector 会扫描类路径上的 META-INF/spring.factories 文件,这个文件中包含了各种 Spring 配置和扩展的定义。在这里,它会查找所有实现了 AutoConfiguration 接口的类,具体的实现为getCandidateConfigurations方法。
  • 条件判断: 对于每一个发现的自动配置类,AutoConfigurationImportSelector 会使用条件判断机制(通常是通过 @ConditionalOnXxx注解)来确定是否满足导入条件。这些条件可以是配置属性、类是否存在、Bean是否存在等等。
  • 根据条件导入自动配置类: 满足条件的自动配置类将被导入到应用程序的上下文中。这意味着它们会被实例化并应用于应用程序的配置。

SpringBoot自动配置通过@EnableAutoConfiguration触发,利用AutoConfigurationImportSelector**扫描META-INF/spring.factories中的配置类。**配置类基于@Conditional条件注解(如类路径存在性、Bean缺失等)选择性生效,自动创建所需Bean。该机制通过条件判断和SPI扩展实现零XML配置,简化环境适配与组件装配过程。

7.说几个启动器(starter)?

  • spring-boot-starter-web:这是最常用的起步依赖之一,它包含了Spring MVC和Tomcat嵌入式服务器,用于快速构建Web应用程序。
  • spring-boot-starter-security:提供了Spring Security的基本配置,帮助开发者快速实现应用的安全性,包括认证和授权功能。
  • mybatis-spring-boot-starter:这个Starter是由MyBatis团队提供的,用于简化在Spring Boot应用中集成MyBatis的过程。它自动配置了MyBatis的相关组件,包括SqlSessionFactory、MapperScannerConfigurer等,使得开发者能够快速地开始使用MyBatis进行数据库操作。
  • spring-boot-starter-data-jpaspring-boot-starter-jdbc:如果使用的是Java Persistence API (JPA)进行数据库操作,那么应该使用spring-boot-starter-data-jpa。这个Starter包含了Hibernate等JPA实现以及数据库连接池等必要的库,可以让你轻松地与MySQL数据库进行交互。你需要在application.properties或application.yml中配置MySQL的连接信息。如果倾向于直接使用JDBC而不通过JPA,那么可以使用spring-boot-starter-jdbc,它提供了基本的JDBC支持。
  • spring-boot-starter-data-redis:用于集成Redis缓存和数据存储服务。这个Starter包含了与Redis交互所需的客户端(默认是Jedis客户端,也可以配置为Lettuce客户端),以及Spring Data Redis的支持,使得在Spring Boot应用中使用Redis变得非常便捷。同样地,需要在配置文件中设置Redis服务器的连接详情。
  • spring-boot-starter-test:包含了单元测试和集成测试所需的库,如JUnit, Spring Test, AssertJ等,便于进行测试驱动开发(TDD)。

8.写过SpringBoot starter吗?

步骤1: 创建Maven项目

首先,需要创建一个新的Maven项目。在pom.xml中添加Spring Boot的starter parent和一些必要的依赖。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

步骤2: 添加自动配置

在src/main/resources/META-INF/spring.factories中添加自动配置的元数据。例如:

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.example.starter.MyAutoConfiguration

然后,创建MyAutoConfiguration类,该类需要@Configuration和@EnableConfigurationProperties注解。@EnableConfigurationProperties用于启用你定义的配置属性类。

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

@Autowired
private MyProperties properties;

@Bean
public MyService myService() {
return new MyServiceImpl(properties);
}
}

步骤3: 创建配置属性类

创建一个配置属性类,使用@ConfigurationProperties注解来绑定配置文件中的属性。

1
2
3
4
5
@ConfigurationProperties(prefix = "my")
public class MyProperties {
private String name;
// getters and setters
}

步骤4: 创建服务和控制器

创建一个服务类和服务实现类,以及一个控制器来展示你的starter的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public interface MyService {
String getName();
}

@Service
public class MyServiceImpl implements MyService {
private final MyProperties properties;

public MyServiceImpl(MyProperties properties) {
this.properties = properties;
}

@Override
public String getName() {
return properties.getName();
}
}

@RestController
public class MyController {
private final MyService myService;

public MyController(MyService myService) {
this.myService = myService;
}

@GetMapping("/name")
public String getName() {
return myService.getName();
}
}

步骤5: 发布Starter

将你的starter发布到Maven仓库,无论是私有的还是公共的,如Nexus或Maven Central。

步骤6: 使用Starter

在你的主应用的pom.xml中添加你的starter依赖,然后在application.yml或application.properties中配置你的属性。

1
2
my:
name: Hello World

9.SpringBoot里面有哪些重要的注解?还有一个配置相关的注解是哪个?

Spring Boot 中一些常用的注解包括:

  • @SpringBootApplication:用于标注主应用程序类,标识一个Spring Boot应用程序的入口点,同时启用自动配置和组件扫描。
  • @Controller:标识控制器类,处理HTTP请求。
  • @RestController:结合@Controller和@ResponseBody,返回RESTful风格的数据。
  • @Service:标识服务类,通常用于标记业务逻辑层。
  • @Repository:标识数据访问组件,通常用于标记数据访问层。
  • @Component:通用的Spring组件注解,表示一个受Spring管理的组件。
  • @Autowired:用于自动装配Spring Bean。
  • @Value:用于注入配置属性值。
  • @RequestMapping:用于映射HTTP请求路径到Controller的处理方法。
  • @GetMapping、@PostMapping、@PutMapping、@DeleteMapping:简化@RequestMapping的GET、POST、PUT和DELETE请求。

另外,一个与配置相关的重要注解是:

  • @Configuration:用于指定一个类为配置类,其中定义的bean会被Spring容器管理。通常与@Bean配合使用,@Bean用于声明一个Bean实例,由Spring容器进行管理。

其中最重要得注解是**@SpringBootApplication**,他是用于标注主应用程序,开启自动配置,具体是内部的

@EnablAutoConfiguration注解实现的自动配置,在springboot启动的时候开启自动配置,主要是基于项目classpath中引入的类和已定义的bean来实现的。@Import:@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。@Congiguration:配置类注解,根据一些特定条件来控制bean的实例化的行为。 @ComponentScan:位置在SpringBoot的启动类上,Spring包扫描。

10.springboot怎么开启事务?

在 Spring Boot 中开启事务非常简单,只需在服务层的方法上添加 @Transactional 注解即可。

例如,假设我们有一个 UserService 接口,其中有一个保存用户的方法 saveUser():

1
2
3
public interface UserService {
void saveUser(User user);
}

我们希望在这个方法中开启事务,只需在该方法上添加 @Transactional 注解,如下所示:

1
2
3
4
5
6
7
8
9
10
11
public class UserServiceImpl implements UserService {

@Autowired
private UserRepository userRepository;

@Override
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
}

这样,当调用 saveUser() 方法时,Spring 就会自动为该方法开启一个事务。如果方法执行成功,事务会自动提交;如果方法执行失败,事务会自动回滚。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

11.Springboot怎么做到导入就可以直接使用的?

这个主要依赖于自动配置、起步依赖和条件注解等特性。

起步依赖

起步依赖是一种特殊的 Maven 或 Gradle 依赖,它将项目所需的一系列依赖打包在一起。例如,spring-boot-starter-web 这个起步依赖就包含了 Spring Web MVC、Tomcat 等构建 Web 应用所需的核心依赖。

开发者只需在项目中添加一个起步依赖,Maven 或 Gradle 就会自动下载并管理与之关联的所有依赖,避免了手动添加大量依赖的繁琐过程。

比如,在 pom.xml 中添加 spring-boot-starter-web 依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

自动配置

Spring Boot 的自动配置机制会根据类路径下的依赖和开发者的配置,自动创建和配置应用所需的 Bean。它通过 @EnableAutoConfiguration 注解启用该注解会触发 Spring Boot 去查找 META - INF/spring.factories 文件。

spring.factories 文件中定义了一系列自动配置类,Spring Boot 会根据当前项目的依赖情况,选择合适的自动配置类进行加载。例如,如果项目中包含 spring-boot-starter-web 依赖,Spring Boot 会加载 WebMvcAutoConfiguration 类,该类会自动配置 Spring MVC 的相关组件,如 DispatcherServlet、视图解析器等。

开发者可以通过自定义配置来覆盖自动配置的默认行为。如果开发者在 application.propertiesapplication.yml 中定义了特定的配置,或者在代码中定义了同名的 Bean,Spring Boot 会优先使用开发者的配置。

条件注解

条件注解用于控制 Bean 的创建和加载,只有在满足特定条件时,才会创建相应的 Bean。Spring Boot 的自动配置类中广泛使用了条件注解,@ConditionalOnClass@ConditionalOnMissingBean 等。

比如,@ConditionalOnClass 表示只有当类路径中存在指定的类时,才会创建该 Bean。例如,在 WebMvcAutoConfiguration 类中,可能会有如下代码:

1
2
3
4
5
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
public class WebMvcAutoConfiguration {
// 配置相关的 Bean
}

这段代码表示只有当类路径中存在 ServletDispatcherServletWebMvcConfigurer 类时,才会加载 WebMvcAutoConfiguration 类中的配置。

12.SpringBoot 过滤器和拦截器说一下?

在 Spring Boot 中,过滤器(Filter)和拦截器(Interceptor)是用于处理请求和响应的两种不同机制。

特性 过滤器(Filter) 拦截器(Interceptor)
规范/框架 Servlet规范(javax.servlet.Filter Spring MVC框架(org.springframework.web.servlet.HandlerInterceptor
作用范围 全局(所有请求、静态资源) Controller层(仅拦截Spring管理的请求)
执行顺序 在Servlet之前执行 在DispatcherServlet之后、Controller方法前后执行
依赖注入支持 无法直接注入Spring Bean(需间接获取) 支持自动注入Spring Bean
触发时机 doFilter()在请求前/响应后被调用 preHandlepostHandleafterCompletion分阶段触发
适用场景 全局请求处理(编码、日志、安全) 业务逻辑相关的处理(权限、参数校验)

过滤器是 Java Servlet 规范中的一部分,**它可以对进入 Servlet 容器的请求和响应进行预处理和后处理。过滤器通过实现 javax.servlet.Filter 接口,并重写其中的 initdoFilterdestroy 方法来完成相应的逻辑。**当请求进入 Servlet 容器时,会按照配置的顺序依次经过各个过滤器,然后再到达目标 Servlet 或控制器;响应返回时,也会按照相反的顺序再次经过这些过滤器。

拦截器是 Spring 框架提供的一种机制,它可以对控制器方法的执行进行拦截。**拦截器通过实现 org.springframework.web.servlet.HandlerInterceptor 接口,并重写其中的 preHandlepostHandleafterCompletion 方法来完成相应的逻辑。**当请求到达控制器时,会先经过拦截器的 preHandle 方法,如果该方法返回 true,则继续执行后续的控制器方法和其他拦截器;在控制器方法执行完成后,会调用拦截器的 postHandle 方法;最后,在请求处理完成后,会调用拦截器的 afterCompletion 方法。

过滤器和拦截器的区别如下:

  • 所属规范:过滤器是 Java Servlet 规范的一部分,而拦截器是 Spring 框架提供的机制。
  • 执行顺序:过滤器在请求进入 Servlet 容器后,在到达目标 Servlet 或控制器之前执行;拦截器在请求到达控制器之后,在控制器方法执行前后执行。
  • 使用范围:过滤器可以对所有类型的请求进行过滤,包括静态资源请求;拦截器只能对 Spring MVC 控制器的请求进行拦截。
  • 功能特性:过滤器主要用于对请求和响应进行预处理和后处理,如字符编码处理、请求日志记录等;拦截器可以更细粒度地控制控制器方法的执行,如权限验证、性能监控等。

13.说说Spring Boot的启动流程。

SpringBoot将日常企业应用研发中的各种常见都抽取出来,做成一个个的**starter(**启动器),starter中整合了该场景下各种可能用到的依赖,用户只需要在Maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter提供了大量的自动配置,让用户摆脱了处理各种依赖和配置的困扰。所有这些starter都遵循这约定俗成的默认配置,并允许用户调整这些配置,即遵循“约定大于配置”的原子

14.@Autowied和@Resource

@Autowied是Spring提供的注解,@Resource是JDK提供的注解。@Autowied是只能按类型注入,@Resource默认按名称注入,也支持按类型注入。@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用

Mybatis

1.与传统的JDBC相比,MyBatis的优点?

  • 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任 何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
  • 与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不 需要手动开关连接;
  • 很好的与各种数据库兼容,因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持。
  • 能够与 Spring 很好的集成,开发效率高
  • 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射 标签,支持对象关系组件维护。

2.MyBatis觉得在哪方面做的比较好?

MyBatis 在 SQL 灵活性动态 SQL 支持结果集映射与 Spring 整合方面表现卓越,尤其适合重视 SQL 可控性的项目。

  • SQL 与代码解耦,灵活可控:MyBatis 允许开发者直接编写和优化 SQL,相比全自动 ORM(如 Hibernate),MyBatis 让开发者明确知道每条 SQL 的执行逻辑,便于性能调优。
1
2
3
4
5
6
7
8
<!-- 示例:XML 中定义 SQL -->
<select id="findUserWithRole" resultMap="userRoleMap">
SELECT u.*, r.role_name
FROM user u
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{userId}
</select>
  • 动态 SQL 的强大支持:比如可以动态拼接SQL,通过 <if>, <choose>, <foreach> 等标签动态生成 SQL,避免 Java 代码中繁琐的字符串拼接。
1
2
3
4
5
6
7
<select id="searchUsers" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">AND name LIKE #{name}</if>
<if test="status != null">AND status = #{status}</if>
</where>
</select>
  • 自动映射与自定义映射结合:自动将查询结果字段名与对象属性名匹配(如驼峰转换)。
1
2
3
4
5
6
7
<resultMap id="userRoleMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="roles" ofType="Role">
<result property="roleName" column="role_name"/>
</collection>
</resultMap>
  • 插件扩展机制:可编写插件拦截 SQL 执行过程,实现分页、性能监控、SQL 改写等通用逻辑。
1
2
3
4
5
6
@Intercepts({
@Signature(type=Executor.class, method="query", args={...})
})
public class PaginationPlugin implements Interceptor {
// 实现分页逻辑
}
  • 与 Spring 生态无缝集成:通过 @MapperScan 快速扫描 Mapper 接口,结合 Spring 事务管理,配置简洁高效。
1
2
3
4
5
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
// 数据源和 SqlSessionFactory 配置
}

3.还记得JDBC连接数据库的步骤吗?

使用Java JDBC连接数据库的一般步骤如下:

  1. 加载数据库驱动程序:在使用JDBC连接数据库之前,需要加载相应的**数据库驱动程序。可以通过 Class.forName(“com.mysql.jdbc.Driver”) 来加载MySQL数据库的驱动程序。**不同数据库的驱动类名会有所不同。
  2. 建立数据库连接:使用 DriverManager 类的 **getConnection(url, username, password) 方法来连接数据库,**其中url是数据库的连接字符串(包括数据库类型、主机、端口等)、username是数据库用户名,password是密码。
  3. 创建 Statement 对象:通过 Connection 对象的 createStatement() 方法创建一个 Statement 对象,用于执行 SQL 查询或更新操作。
  4. 执行 SQL 查询或更新操作:使用 Statement 对象的 executeQuery(sql) 方法来执行 SELECT 查询操作,或者使用 executeUpdate(sql) 方法来执行 INSERT、UPDATE 或 DELETE 操作。
  5. 处理查询结果:如果是 SELECT 查询操作,通过 ResultSet 对象来处理查询结果。可以使用 ResultSet 的 next() 方法遍历查询结果集,然后通过 getXXX() 方法获取各个字段的值。
  6. 关闭连接:在完成数据库操作后,需要逐级关闭数据库连接相关对象,即先关闭 ResultSet,再关闭 Statement,最后关闭 Connection。

以下是一个简单的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.sql.*;

public class Main {
public static void main(String[] args) {
try {
// 加载数据库驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");

// 建立数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");

// 创建 Statement 对象
Statement statement = connection.createStatement();

// 执行 SQL 查询
ResultSet resultSet = statement.executeQuery("SELECT * FROM mytable");

// 处理查询结果
while (resultSet.next()) {
// 处理每一行数据
}

// 关闭资源
resultSet.close();
statement.close();
connection.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

请注意,在实际应用中,需要进行异常处理以确保资源的正确释放,以及使用 try-with-resources 来简化代码和确保资源的及时关闭。

4.如果项目中要用到原生的mybatis去查询,该怎样写?

步骤概述:

  1. 配置MyBatis: 在项目中配置MyBatis的数据源、SQL映射文件等。
  2. 创建实体类: 创建用于映射数据库表的实体类。
  3. 编写SQL映射文件: 创建XML文件,定义SQL语句和映射关系。
  4. 编写DAO接口: 创建DAO接口,定义数据库操作的方法。
  5. 编写具体的SQL查询语句: 在DAO接口中定义查询方法,并在XML文件中编写对应的SQL语句。
  6. 调用查询方法: 在服务层或控制层调用DAO接口中的方法进行查询。

详细步骤:

  1. 配置MyBatis: 在配置文件中配置数据源、MyBatis的Mapper文件位置等信息。
  2. 创建实体类: 创建与数据库表对应的实体类,字段名和类型需与数据库表保持一致。
1
2
3
4
5
6
public class User {
private Long id;
private String username;
private String email;
// Getters and setters
}
  1. 编写SQL映射文件: 在resources目录下创建XML文件,定义SQL语句和映射关系。
1
2
3
4
5
6
<!-- userMapper.xml -->
<mapper namespace="com.example.dao.UserMapper">
<select id="selectUserById" resultType="com.example.model.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
  1. 编写DAO接口: 创建DAO接口,定义查询方法。
1
2
3
public interface UserMapper {
User selectUserById(Long id);
}
  1. 编写具体的SQL查询语句: 在XML文件中编写对应的SQL语句。
  2. 调用查询方法: 在服务层或控制层中调用DAO接口中的方法进行查询。
1
2
// 在Service层中调用
User user = userMapper.selectUserById(1);

通过以上步骤,你可以利用原生的MyBatis框架来进行数据库查询操作。请确保配置正确、SQL语句准确并与数据库字段匹配,以确保查询的准确性和高效性。

5.Mybatis里的 # 和 $ 的区别?

  • Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,将 SQL 中的 #{} 替换为 ? 号,**在执行 SQL 时会为预编译 SQL 中的占位符(?)赋值,调用 PreparedStatement 的 set 方法来赋值,预编译的 SQL 语句执行效率高,并且可以防止SQL 注入,**提供更高的安全性,适合传递参数值。
  • Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时 MyBatis 将参数直接拼入到 SQL 里,不能防止 SQL 注入,因为参数直接拼接到 SQL 语句中,如果参数未经过验证、过滤,可能会导致安全问题。

6.MybatisPlus和Mybatis的区别?

MybatisPlus是一个基于MyBatis的增强工具库,旨在简化开发并提高效率。以下是MybatisPlus和MyBatis之间的一些主要区别:

  • CRUD操作:MybatisPlus通过继承BaseMapper接口,提供了一系列内置的快捷方法,使得CRUD操作更加简单,无需编写重复的SQL语句。
  • 代码生成器:MybatisPlus提供了代码生成器功能,可以根据数据库表结构自动生成实体类、Mapper接口以及XML映射文件,减少了手动编写的工作量。
  • 通用方法封装:MybatisPlus封装了许多常用的方法,如条件构造器、排序、分页查询等,简化了开发过程,提高了开发效率。
  • 分页插件:MybatisPlus内置了分页插件,支持各种数据库的分页查询,开发者可以轻松实现分页功能,而在传统的MyBatis中,需要开发者自己手动实现分页逻辑。
  • 多租户支持:MybatisPlus提供了多租户的支持,可以轻松实现多租户数据隔离的功能。
  • 注解支持:MybatisPlus引入了更多的注解支持,使得开发者可以通过注解来配置实体与数据库表之间的映射关系,减少了XML配置文件的编写。

7.MyBatis运用了哪些常见的设计模式?

  • 建造者模式(Builder),如:SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder等;
  • 工厂模式,如:SqlSessionFactory、ObjectFactory、MapperProxyFactory;
  • 单例模式,例如ErrorContext和LogFactory;
  • 代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
  • 组合模式,例如SqlNode和各个子类ChooseSqlNode等;
  • 模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
  • 适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
  • 装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
  • 迭代器模式,例如迭代器模式PropertyTokenizer;

8.MyBatis 的两级缓存及其特点是什么?

MyBatis 的两级缓存包括一级缓存和二级缓存。一级缓存是 SqlSession 级别的本地缓存,默认开启,仅在同一个会话中有效,用于避免重复查询,同一个查询在会话中只访问一次数据库;但在执行更新操作或关闭会话后会失效,且不能跨线程、跨请求。二级缓存是 Mapper 级别的全局缓存,默认关闭,需要在全局配置中开启并在对应的 Mapper 中使用 <cache /> 标签声明,它支持多个 SqlSession 共享缓存,通常用于读多写少的场景,如商品信息、字典数据等。一级缓存生命周期短、线程不安全,适合单次请求优化;二级缓存生命周期长、线程安全,适合全局数据共享,但需注意缓存失效和一致性控制。

sqlsession:

在一次 SqlSession 生命周期内,相同的查询语句(SQL + 参数)会被缓存,第二次执行时直接从缓存中取值,不会发起数据库请求。当前会话有效,执行 insert/update/delete:任何更新操作会清空缓存。执行sqlSession.clearCache()。不同参数的时候也会失效

mapper:

二级缓存是 Mapper 级别(namespace)共享缓存。

多个 SqlSession 之间共享该 Mapper 的缓存数据。

启用条件:

在 MyBatis 配置中启用全局缓存:

1
2
3
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>

在对应 Mapper 文件中配置 <cache> 标签:

查询的 POJO 实体类必须实现 Serializable

查询语句不能使用 flushCache="true"(默认查询是 false,更新是 true)。

SqlSession 必须关闭后,一级缓存的数据才会被写入二级缓存。

默认缓存实现是 PerpetualCache + LRU

可自定义缓存策略(如 EhCache、Redis)。

9.MyBatis Plus 的 Wrapper 和原生 XML 写 SQL 的区别?什么时候该用 Wrapper?

Wrapper 是 MyBatis Plus 提供的条件构造器,用于构建 SQL 的 WHERE、ORDER BY 等子句,简化代码书写。

QueryWrapper:用于普通查询。

LambdaQueryWrapper:使用 lambda 表达式避免写字段名字符串。

UpdateWrapper / LambdaUpdateWrapper:用于更新条件构造。

XML 方式是传统 MyBatis 写 SQL 的方式,通过 Mapper.xml 文件自定义 SQL 语句,更加灵活和强大,支持复杂的多表连接、子查询等。

对比项 Wrapper(构造器) XML 原生 SQL
语法风格 Java 代码风格,链式调用 SQL 语法,放在 XML 中
可读性 简洁、类型安全(特别是 Lambda) 接近原生 SQL,清晰直观
编写速度 快速开发,尤其适合单表 CRUD 编写略慢,需额外维护 Mapper.xml 文件
SQL 灵活性 支持简单查询(单表、分页、排序) 支持复杂 SQL(多表连接、子查询、聚合)
维护性 逻辑分散在代码中,不易集中查看 逻辑集中在 XML,更适合团队协作维护
运行效率 两者本质上都由 MyBatis 执行,性能差异不大 性能主要看 SQL 写得是否合理
调试与日志 SQL 日志可查看 也可通过日志查看

✅ 使用 Wrapper 的场景:

  • 快速开发、原型项目。
  • 简单的单表查询、分页、筛选。
  • Controller/Service 中构造简单业务逻辑。
  • 需要链式调用构造条件,代码更加优雅。
  • 使用 Lambda 避免字段拼写错误风险。

推荐:日常开发中能用 Wrapper 就用 Wrapper,提高开发效率。


✅ 使用 XML 写 SQL 的场景:

  • 涉及复杂 SQL(多表关联、聚合函数、子查询、动态 SQL)。
  • 查询语句过长、不适合写在 Java 代码中。
  • 项目追求清晰的逻辑分层、SQL 可维护性。
  • 性能调优场景,需要手写精细 SQL。
  • 团队需要 DBA 审查 SQL。

推荐:复杂业务、线上稳定项目,用 XML 更清晰、更可控。

10.分页查询的方式

1 Limit Offset分页

2 Limit 指定主键Id过滤

3 HasMore 滚动查询

4 ElasticSearch 分页查询

Limit Offset:

支持页数跳转。用户选定第几页,就跳转到对应的页面。

返回记录总条数。用户可以看到共几页,一共多少条数据。

但是当出现深度分页时,MySQL 需要扫描大量数据才能找到指定页的数据,造成慢查询

分页查询必须指定排序方式。如果没有指定排序方式,使用分页很难保证数据不会出现重复。

Limit 指定主键Id过滤

select * from students where xxxx查询条件xxx order by id desc limit 1000,20;

改进后:select * from students where xxxx查询条件xxx AND id <lastMinId order by id desc limit 20;

改进后在原有的查询条件上 指定了lastMinId,上一轮最小的Id。在查询下一页时,把上一页的最小id 传下去,这样保证后续查到的列表都是小于lastMinId。从源头上增加了查询条件,减少了mysql的检索范围,每次都只获取前二十条数据。

这种方式前提条件是排序方式可以指定主键Id,每次查询都更新主键id范围,这样就能避免深度分页,查询全部的数据。

HasMore 滚动查询

滚动分页查询则无需获取总页数,无需查询总数。减少了一次select count(*)的查询。

只需要在每一次分页查询时,每页数量+1 即可。例如每页10条,可以指定11条,如果真查出来11条,hasMore=true,上游需要继续查,否则hasMore=false,上游无需再分页查询。

ElasticSearch 分页查询

ES 比较适用于检索条件复杂、实时性要求比较低的查询场景。例如B端的各类复杂查询条件检索场景以及 C端用户关键词订单列表搜索等场景。查询耗时基本在100ms以上、甚至1s以上。

值得一提的是需要mysql数据异构到ES,ES加载进索引也有1s左右延迟,数据从产生到ES索引延迟比较高。

ElasticSearch 支持分页查询,和Mysql Limit offset 类似。同时也强烈建议,使用分页查询时,指定排序方式。

1
2
3
4
5
6
7
8
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//计算出记录起始下标
int from = (pageNum - 1) * pageSize;
// 起始记录下标,从0开始
sourceBuilder.from(from);
//每页显示的记录数
sourceBuilder.size(pageSize);

和mysql类似,ES也有深度分页的查询压力,默认的最大查询深度max_result_window=1W, 阈值可以修改。在低频的B端查询场景,可以根据需要适当调整阈值。

以上4种分页查询方式没有最好,需要针对不同的场景选择最合适的

11.MyBatis Mapper 注册过程

*动态代理、FactoryBean、Configuration 注册流程**的理解

MyBatis 使用 MapperFactoryBean接口注册为代理对象,启动时会把接口方法解析成 MappedStatement 存入 Configuration 中,运行时由 MapperProxy 通过反射动态执行 SQL。

1
2
3
4
5
6
7
8
       XML or @MapperScan

【Mapper接口注册】 → 注册到 Configuration.mapperRegistry

【创建代理类】 MapperProxyFactory → MapperProxy

【执行 SQL】 通过 SqlSession 执行对应 MappedStatement

1.@MapperScan 启动时扫描 Mapper 接口

@MapperScan 会注册 MapperScannerConfigurerMapperScannerRegistrar

它们会扫描包下所有接口,对每个接口注册一个 MapperFactoryBean 到 Spring 容器中

2.每个 Mapper 接口注册成一个 MapperFactoryBean

MapperFactoryBean 实现了 Spring 的 FactoryBean 接口;

它不会直接注入自身,而是调用 getObject() 返回 Mapper 接口的代理类。

3.MapperProxyFactory 创建代理类

返回的是一个 MapperProxy 的动态代理对象,底层通过 Proxy.newProxyInstance(...) 实现;

所以你注入的 UserMapper 实际是 MapperProxy 代理类。

4.代理对象调用方法时执行 SQL

MapperMethod 会查找 Configuration 中注册的 MappedStatement

调用 sqlSession.selectOne(...)insert(...) 等执行 SQL;

SQL 语句和参数映射都来源于 XML 或注解注册的 MappedStatement。

5.在 SqlSessionFactoryBuilder.build() 构建过程中,MyBatis 会:

  • 加载 XML 映射文件或注解 Mapper;
  • 把每个 <mapper> 里的 SQL 语句解析为 MappedStatement
  • 调用 Configuration.addMappedStatement() 注册到全局配置里;
  • 最终保存在 configuration.getMappedStatements() 的 Map 中。

12.xml标签有啥

元素

属性

声明

注释

CDATA

处理指令

文档类型定义