当前位置: 首页 > news >正文

常规八股 (w字解析,不定期更新)

SpringBoot

必备八股

能说说 SpringBoot 的启动流程吗?

首先呢是从 main 方法启动,然后调用 springApplicationrun 方法。方法里面呢会先创建 SpringApplication 对象,创建的时候会推断应用类型,比如说是 servlet ,还是 reactive 或者说是除了 web 外的其他应用,然后设置启动器。创建完 springApplication 对象之后呢,会调用该对象的 run 方法,通过 ConfigurableEnvironment 准备环境,这一步会读取里面的配置文件,比如是什么 .preperties 或是 .yml,然后接着创建 应用上下文,这一步呢就会读取所有的配置类和自动配置类,创建完后就开始刷新应用上下文了,这一步会进行 bean 的创建和初始化,其中也包括开发者自定义的 bean 以及自动注入的 bean。然后呢,如果你的应用类型是 web 应用的话 ,在刷新应用上下文的最后,会自动启动**嵌入式 web 服务器**,服务器启动完成后会发送已启动的事件,接着呢会调用实现了 CommandLineRunner 或者 ApplicationRunner 接口的 bean,执行一些初始化逻辑,最后发送applicationReayEvent ,到这里应用就启动完成了。

嵌入式 web 服务器你了解过吗?(很小概率会问)

默认是 tomcat 吧,springBoot 主要是为了简化配置,比如说写很多的:繁杂的xml 啊。简单来说呢:springBoot 通过提供默认配置、自动化配置和嵌入式等功能,简化了传统 Spring 应用的繁琐配置过程。然后除了 tomcat 的话,还了解 Jetty, 它比 tomcat 更加轻量,通常用于对资源占用较敏感的环境,并且适合长连接应用,比如说:(WebSocket,Comet),还从同学那里听过Netty,是一个非阻塞的异步事件驱动框架,非常适合响应式编程和高并发应用。这两个都是了解过,并没有实际的应用经验。

那它是(SpringBoot) 如何实现自动配置的呢?

嗯,这个 SpringBoot 的自动配置是通过一个叫做 EnableAutoConfiguration 的注解来实现的,这个注解其实是一个复合注解,它里面包含了一个 @Import 注解,导入的这个类会去扫描 classpath 下所有的 META-INF/spring.factories 中的文件,根据文件中指定的配置类加载对应的 Bean 的自动配置。对吧,然后这些配置的加载条件呢是通过一些注解来控制的,比如说 @ConditionalOnClass 等等。

举一个实例吧:当存在 spring-boot-starter-data-jpa 依赖时,DataSourceAutoConfiguration 会激活。其内部通过嵌套的 PooledDataSourceConfiguration 进一步检测类路径中的连接池实现(如 HikariCP、Tomcat、JDBC POOL),并利用 @ConditionalOnProperty 解析 spring,datasource.type 属性,最终按优先级动态装配最佳 DataSource 实例。这个过程完全透明,但每一步都通过 精确的条件判断 确保装配的准确性。

那它的 配置文件加载的优先级你有了解过吗?(一般都是以官方文档为主,不同 version 也会有不同的顺序)

首先呢:当 application.propertiesapplication.yml 同时存在的话,最终生效的是 application.properties 中的配置。大概就记得一句话吧:优先加载优先级高的配置,其他的记不太清了,但是呢如果是遇到了这种问题的话,bing 或 google 一下肯定都能解决,这是小问题。

---------------Springboot2.4.x以下文件类型加载顺序------------------------
1.Jar 包外的 Profile 配置(外部 application-{profile}.properties/yaml)
2.Jar 包外的默认配置(外部 application.properties/yaml)
3.Jar 包内的 Profile 配置(application-{profile}.properties/yaml)
4.Jar 包内的默认配置(application.properties/yaml)
---------------Springboot2.4.x以上文件类型加载顺序------------------------
1. Jar 包内的默认配置(application.properties/yaml)
2. Jar 包内的 Profile 配置(application-{profile}.properties/yaml)
3. Jar 包外的默认配置(外部 application.properties/yaml)
4. Jar 包外的 Profile 配置(外部 application-{profile}.properties/yaml)不同包下,不同Profile配置,仅修改了加载顺序,配合后加载的先覆盖机制,

SpringBoot 扫盲

如何理解 Spring Boot 中的 starter (基本不会问)

那个啊,那就是一组有用的依赖集合,主要用于简化构建配置。打个比方吧,超市有人把一些生活用的必需品都打包好了,那么顾客购买的时候直接拎包付款就行了,提升了效率。

关于 SpringBoot 中的异步你了解多少呢?

异步?嗯,有四种方法吧。分别是 @Async@Scheduled 注解,或者使用 CompletableFuture 方法 和 自定义线程池。

@Configuration
public class AsyncConfig {@Bean(name = "taskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数,最大线程池数量,executor.setCorePoolSize(2);executor.setMaxPoolSieze(2);executro.setQueueCapacity(500);executor.setThreadNamePrefix("Thread-");executor.initialize();return executor;}
}

SpringBoot 打成的 jar 和普通的 jar 有什么区别,这个总会吧?

这个是基础中的基础吧?普通的 jar 包仅仅只有源代码和一些依赖,并且部署呢还需要额外的服务器或者容器。但是 SpringBoot 打成的 jar 包不仅包含了这些,还包含了应用程序运行需要的配置、脚本和依赖服务,并且部署项目时,可以直接 java -jar xxxx 运行。

代码中推荐使用 @Autowired 注解吗?

嗯,虽然说现在我看到的代码一般都是 @Autowired 直接注入的,但是呢 spring 官方实际上推荐的是构造器注入,而不是字段注入并且字段注入可能会触发空指针问题,且对单元测试不友好,并且会掩盖循环依赖问题。还有一点就是耦合度较高,@Autowired 实际上是 Spring 提供的,导致了业务代码和框架绑定,假如你换了一个其他 IOC 框架就不太适用了,这里推荐使用字段 @Resource,他是 JSR-250 提供的,也是 Java 标准。

现在呢有这样一个场景,假如你有一些特定的代码需要在 SpringBoot 启动时来执行,你会选择怎么做呢。

嗯,第一种方法呢,是使用 Spring 事件监听器,通过监听 Spring 的 ContextRefreshedEvent 事件,可以在应用启动时执行特定代码。

第二种方法是实现 CommandLineRunner 和 ApplicationRunner 接口。其中 CommandLineRunner 接口可以有多个实现类,参照 @Order 注解的顺序执行

@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String ... args) throws Exception {System.out.println("www.cyx1205.com: Receive multiple offers!")}
}@Component
@Order(2)
public class MyApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("www.cyx1205.com:Receive multiple offers!!");}
}

然后第三种方法呢是 Spring 容器启动给的扩展点,可以在 Spring 容器初始 bean 之前或之后执行特定逻辑

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException {// bean 初始化前if (bean instanceof MySpecificBean) {System.out.println("BeanPostProcessor:Before initialization of " + cyx1205.com)}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// bean 初始化后if (bean instanceof MySpecificBean) {System.out.println("BeanPostProcessor:After initialization of " + cyx1205.com);}return bean;}
}

SpringBoot 3.x 与 2.x 版本有哪些主要的区别呢?

嗯,这个问题不是很了解,主要是没有这方面的意识。隐约还记得几点:首先呢是,Spring Boot 3.x 迁移到了 JakarataEE, 这意味着一些核心的包名从 javax.* 变更为了 jakarta.* ,开发者需要在迁移过程中注意相关的兼容性问题。然后呢是,SpringBoot 3.x 要求使用 JDK 17 作为最低版本,而 SpringBoot 2.x 支持 JDK8、11 和 17。最后是对一些框架的支持吧,3.x 在 Spring Security 6 中,进一步强化了身份认证、授权和安全配置得能力,特别是对于 OAuth 2.1、OpenID Connect 等标准的支持更加完善。

Spring

常规八股

Spring Bean 的生命周期了解吗?

嗯。首先 Spring 容器根据配置文件或者注解 实例化 Bean 对象,然后将依赖注入到 Bean 实例中, 如果 Bean 实现了 BeanNameAware 等 aware 接口,则执行 aware 注入。在 Bean 初始化之前,可以通过 BeanPostProcessor 接口对 Bean 进行一些额外的处理,然后就可以调用 InitializingBean 接口的 afterPropertiesSet() 方法或通过 init-method 属性指定的初始化方法进行初始化,在初始化后,可以通过 BeanPostProcessor 进行进一步的处理。在 Bena 初始完成后,就可以被容器中的其他 Bean 使用,当容器关闭时, Spring 调用 DisposableBean 接口的 destory() 方法或通过 destory-method 属性指定销毁方法。

Spring 一共有几种注入方式?

我大概了解三种注入方式。首先呢是字段注入,也就是 @Autowired,它虽然简洁,但是呢会破坏封装性,隐藏依赖关系,并且难以测试。然后呢是 setter 注入,顾名思义就是 setter 方法提供灵活性,适合可选依赖或需要动态更新的场景,但无法保证线程安全和依赖的强制性。最后就是构造器注入了,它通过构造函数强制注入所有必须的依赖,以此来保证对象初始化的完整性和不可变性,支持 final,线程安全,与此同时它也是 Spring 官方首选方式。

Spring 启动过程了解吧?说一下

Spring 启动时首先呢会读取配置文件(如 XML 配置文件、Java Config 类等),包括配置数据库连接、事务管理、AOP配置等,然后会根据配置文件中的信息创建容器 ApplicationContext,在容器启动阶段实例化 BeanFacotry,并加载容器中的 BeanDefinitions,加载完后呢,会解析配置文件中的 BeanDefinitions,(即声明的 Bean 元数据,Bean 的作用域、依赖关系等信息),然后呢会实例化 Bean 对象,并把它们放入其容器进行管理,再根据 Bean 之间的依赖关系进行注入(构造函数注入和属性注入),然后开始处理 Bean 生命周期的初始化,这里分两种,一种是直接调用 Bean 初始化方法(如果有定义的话),另一种是如果 Bean 实现了 InitializingBean 接口,那么 spring 会直接调用其 afterPropertiesSet 方法。之后接着处理 BeanPostProcessors,其中呢 Spring AOP 代理也是在这个阶段生成,最后呢 Spring 可能会在启动过程中发布一些事件,比如容器的启动事件,当 所有 Bean 初始化完毕、依赖注入完成、AOP 配置生效等都准备就绪时,Spring 容器就启动完成了。

Spring 中的 事务用过吧,遇见过什么失效的情况吗?

失效?嗯,简单的几种情况倒是遇见过,这里给您说一下吧。第一种是:代码报错时被 catch 了,但是没有抛出异常,仅仅是把它打出 log。第二种是:rollbackFor 没有设置对,比如说默认没有任何设置(RuntimeException 或者 Error 才能捕获),则方法内抛出 IOException 则不会回滚,必需得配置 @Transactional(rollbackFor = Exception.class),第三种情况呢,则是在多线程环境下:因为 @Transactional 是基于 ThreadLocal 来存储上下文的,多线程情况下每个线程都有自己的上下文,那么之间如何保持事务同步呢,这显然会失效

代理拦截失效:包括非 public方法无法被代理增强、同类自调用绕过代理(如 this.method())、以及 final方法或类阻碍 CGLIB 代理。
异常信号丢失:事务回滚依赖异常传播,若异常被 catch后未重新抛出或抛出的异常类型不匹配 rollbackFor规则(如默认仅回滚 RuntimeException,却抛出 IOException),则回滚机制失效。
上下文资源断裂:事务管理器(如 DataSourceTransactionManager)通过 ThreadLocal绑定数据库连接,多线程场景下子线程无法继承父线程资源,导致事务链断裂;此外,使用不支持事务的数据库引擎(如 MySQL MyISAM)则从根本上使事务无效。

根本原因在于: Spring 事务是“代理拦截 + 异常驱动 + 线程资源绑定”三位一体的机制,任一环节破坏(如代理未生效、异常未触发、资源未绑定)都将导致事务行为不符合预期。解决方案需紧扣代理调用链、异常传播路径与线程资源一致性三个维度进行设计。

IOC 容器的初始化过程应该知道吧?

首先呢,IOC 容器需要加载应用程序的配置信息进行创建,接着 BeanDefinitionReader 会读取解析配置众多额 Bean 定义,并将其注册到容器中,形成 BeanDefinition 对象,之后根据 BeanDefinition 来创建 Bean 的实例并根据 其中的依赖关系进行依赖注入,然后会被 BeanPostProcessor 处理(会在 Bean 初始化生命走起中加入定义的处理逻辑),如果实现了 Aware 接口调用的话如,BeanNameAware、BeanFactoryAware, spring 会回调这些接口,并传递容器相关信息,最后调用 Bean 的初始化方法(如通过 @PostConstruct 注解标准的方法,或实现 InitializingBean 接口的 Bean 会调用 afterPropertiesSet 方法。

Spring 的单例 Bean 是否有并发安全问题? 该怎么解决呢?

Bean 的话,默认是是 singleton 的,因此同一个实例会在整个 应用程序中被多个线程共享。解决方案呢有四种,一是:使用 ThreadLocal 保持变量,二是:避免在单例 Bean 中使用可变状态,三是:加锁。如果确实需要在单例 Bean 中管理共享资源,可以通过 synchronized 关键字或其他线程同步机制比如使用线程安全的数据结构来确保线程安全。第四呢是改变Bean的作用域:可以使用注解 @Scope("prototype")

Spring 源码看过吗?它由哪些部分组成呢

了解过,大概有五个部分吧。首先是它的核心容器(Core Container)模块,提供了依赖注入,上下文功能和Bean的定义与生命周期。然后是 面向切面编程(AOP)模块,再然后是数据访问模块(Data Access),这里面包括 JDBC, ORM,Transaction等等,还有 Web 层模块里面主要是提供了基础的 Web 开发支持。除了这些较为核心的模块的话,还有很多扩展模块,提供了一些相关的插件。

什么是循环依赖,该怎么解决

  1. 依赖的 Bean 必须都是单例
  2. 依赖注入的方式,必须不全是构造器注入,且 beanName 字母序在前的不能是构造器注入

在 Spring 中创建 Bean 分为三步:

  1. 实例化, createBeanInstance,就是 new 了个对象
  2. 属性注入,populateBean,就是 set 一些属性值
  3. 初始化,initializeBean,执行一些 aware 接口中的方法, initMethod、AOP 代理等

循环依赖呢就是A依赖于B,B又依赖A,彼此构成环了。解决方法是:spring 注入有三级缓存,能够解决大部分循环以来的问题。具体流程是这样的,一开始创造 A 时候查询一级缓存(里面存成名),发现没找到则看二级缓存是否在创建中(有没有半成品),都没有的话则需要创建 A 的 bean,调用的是 createBean,过程分别是实例化、属性注入、初始化,然后在 A 实例化之后往三级缓存加入一个 A 的 getAObject 方法,这个就是解决循环依赖的关键,然后到了属性注入的环节,因为 A 依赖 B,因此需要创建 B。同样的路线 B 也要 createBean,不一样的也是解决循环依赖的一环: 到了属性注入,查询二级缓存的 A 为创建中,则调用三级缓存工厂 getObject 创建一个半成品的 A,放入到二级缓存中,并完成 B 的第二步属性注入,到了后面初始化 initializeBean时,完成 B 的 Bean 创建,放入到一级缓存。

image-20251025165604754

为什么 Spring 循环依赖需要三级缓存,二级不够吗?

主要的原因在于 AOP 代理和 Bean 的早期引用问题。二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP)时,直接使用二级缓存不做任何处理会导致我们拿到的 Bean 是未代理的原始对象 如果二级缓存内存放的都是代理对象的话,则违反了 Bean 的生命周期。

正常代理对象的生成是基于后置处理器,是在被代理的对象初始化后期调用生成的,所以如果你提早代理了其实是违背了 Bean 定义的生命周期。

Spring AOP 默认用的是什么动态代理,两者的区别是什么

spring Framework 默认使用的动态代理是 JDK 动态代理(由于后续版本已经整合了 CGLIB,如果这个代理类没有实现接口,会用 CGLIB),SpringBoot 2.x 版本的默认动态代理是 CGLIB。区别的话主要体现在代理方式和使用场景。JDK 动态代理是基于接口实现的,主要通过 java.lang.reflect.proxy 动态生成代理类,但是 CGLIB 呢,它是基于类继承,通过字节码技术生成目标类的子类,来实现对目标方法的代理。

example
interface Service {void performAction();
}class ServiceImpl implements Service {public void performAction() {System.out.println("Executing performAction");}
}class ServiceInvocationHandler implements InvocationHandler {private Object target;public ServiceInvocationHandler(Object target) {this.target = target;}@overridepublic object invoke(object proxy,Method method,object[] args) throws Throwable {System.out.println("Before method execution");Object result = method.invoke(target, args);System.out.println("After method execution");return result;}
}       public class JDKProxyExample {public static void main(String[] args) {Service service = new ServiceImpl();Service proxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader();service.getClass().getInterfaces(),new ServiceInvocationHandler(service);)}}class Service {public void performAction() {System.out.println("Executing performAction");}
}class ServiceMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method execution");Object result = proxy.invokeSuper(obj, args);System.out.println("After method execution");return result;}
}public class CGLIBProxyExample {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Service.class);enhancer.setCallback(new ServiceMethodInterceptor());Service proxy = (Service) enhancer.create();proxy.performAction();}
}

Spring 扫盲

Spring Bean 的作用域有几种?

大概有六种作用域吧,第一种是 singleton,这也是默认的单例作用域,然后是 prototype,原型多实例,然后是 request:每个请求都会创建一个属于自己的 Bean 实例,session :一个 http session 中有一个 bean 的实例,这种作用域仅仅存在 Spring Web 中,application:整个 ServletContext 生命周期里,只有一个 bean,websocket 一个生命周期内一个 bean,以上这四种呢,这种作用域仅存在 Spring Web 应用中。

拦截器和过滤器的区别应该听说过吧?

嗯,拦截器的话是 Spring 框架的一部分,基于 Java 的反射机制实现的,主要用于拦截访问 DispatcherServlet 的请求,通常只用于基于 SpringMVC 的应用程序中的请求处理方法。它可以指定多个拦击器之间的执行顺序,通过 @Order 注解来控制,更侧重于业务逻辑的前置检查,权限检查,日志记录等。实现方式呢是实现 Filter 接口,或者加上一个 @Component 注解进行自动注册即可。

过滤器的话呢,它是 Servlet 规范中的一部分,所以独立于 Spring 框架存在。它主要用于过滤请求和响应,可以对所有的请求类型进行处理,先于拦截器执行,因为过滤器作用于 servlet 容器层面,而拦截器则是在 SpringMVC 的处理器映射器找到控制器之前和之后执行,更加侧重于过滤请求和响应的内容,例如设置编码格式、安全控制等。或者需要实现 org.springframework.web.servlet.HandlerInterceptor 接口,并通过实现 WebMvcConfigurer 接口的 addInterceptors 方法来注册自定义的拦截器。

Spring 用过吧?它注册到容器有哪些方式呢?

我知道的大概有两种吧,分别是 XML 配置和注解。注解的话有四个,分别是 @Component 和其衍生注解,使用 @Configuration 注解声明配置类和 Bean 注解定义 Bean,@Import 可以将普通类导入到 Spring 容器中,这些类会自动被注册为 Bean。

事务的传播行为了解过吗?(很少问到)

大概有了解过吧,主要呢是定义多个事务方法在相互调用时,事务边界应如何传递和嵌套的规则,是在接口 TransactionDefinition 中,有7种,经常使用的是默认的。

Spring 事务传播行为的本质是定义多个事务方法在相互调用时,事务边界应如何传递和嵌套的规则,其核心在于解决业务逻辑单元之间的数据一致性问题。七种行为可按“是否依赖外部事务”分为三类:依赖外部事务(REQUIRED-默认、SUPPORTS、MANDATORY)确保操作在统一事务上下文中进行;挂起外部事务(REQUIRES_NEW、NOT_SUPPORTED、NEVER)使内部操作独立执行,避免受外部事务失败影响,适用于日志、通知等非核心场景;而独特的 NESTED(嵌套事务)则是一种折中方案,它在外部事务内建立一个基于数据库保存点(Savepoint)的子事务,子事务可独立回滚而不影响外部事务,非常适合处理“批量操作中允许部分失败”的场景。理解这些行为的关键在于掌握其“新建、加入、挂起”的时机,以及由此带来的资源锁、数据一致性性能上的权衡。在实际开发中,绝大多数场景使用默认的 REQUIRED 即可,仅在需要精细控制事务边界时(如确保日志不丢失、批量任务部分回滚)才选用 REQUIRES_NEW 或 NESTED。

Spring 拦截链的实现,简单说一下

拦截器的核心实现主要包括三个方面:第一个是:MVC 的拦截器 (HandlerInterceptor),用于拦截 HTTP 请求并进行预处理和后处理。通过实现 HandlerInterceptor 接口的三个方法,可以在请求到达控制器之前,控制器方法执行之后以及请求完成后进行处理。第二个是:基于 servlet API 的过滤器,第三个是,AOP 拦截链,通过自定义切面可以实现方法的前后处理。

Spring 的 BeanFactory,FactoryBean,ObjectFactory, 和 ApplicationContext 是什么(详情看源码)

BeanFactory 负责从配置源种读取 Bean 的定义,并负责创建、管理这些 Bean 的生命周期,它的一个重要特性是延迟初始化。

FactoryBean 是一个实现了 FactoryBean<T> 接口的 Bean,通过它可以自定义复杂对象的创建逻辑,Spring 容器会调用 getObject() 方法来获取实际 Bean 的实例,通常用于需要创建复杂对象或需要使用代理模式生成 Bean 的场景。

ObjectFactory 是一个接口,主要用于延迟获取 Bean 实例,它提供了一种延迟加载的机制,通过 getObject() 方法返回一个 Bean 的实例,使用 ObejctFactory 可以避免在容器启动时立即创建所有的 Bean ,即只有在真正需要使用 Bean 时才会从 Spring 容器种获取该 Bean 实例

ApplicationContext

image-20251025130820023

SpringMvc

基础八股

SringMVC 的工作流程说一下 ?

首先呢:浏览器向服务器发送 HTTP 请求,请求首先由 Spring MVC 的核心前端控制器 DispatcherServlet 接收,它充当整个流程的调度中心,DispatcherServlet 根据请求的 URL 使用处理器映射器找到对应的控制器(Handler Mapping),控制器接收请求并处理业务逻辑,通常通过注解 @Controller@RequestMapping 定义请求的映射方法,控制器(ModelAndView)处理完业务逻辑后,将数据封装到模型对象中,并指定返回的视图名称,DispatcherServlet 调用视图解析器(ViewResolver),将逻辑视图名称解析为实际的视图,如 JSP、Thymeleaf 等模板引擎,视图渲染引擎根据模型中的数据生成 HTML 页面并返回给客户端。

SpringMVC 父子容器知道是什么吗?

父容器:主要指的是 Spring 的根容器,通常是 Spring 应用上下文(ApplicationContext),如 ContextLoaderListener 加载的根容器。子容器呢指的是 Web 容器,每个 DispatcherServlet 实例都会创建一个子容器,用于管理 Web 层 (如控制器和拦截器) 中的 Bean。它们之间的关系是,子容器可以访问父容器的 Bean,这种机制有助于 Web 层使用服务层或 DAO 层(父容器) 中的 Bean。但是父容器不能访问子容器的 Bean,主要目的是为了避免不必要的耦合。

Mybatis

基础八股

Mybatis 的缓存了解过吗?

大概了解过,它有两类缓存,分别是一级缓存(SqlSession 级别)和二级缓存(Mapper级别)。一级缓存在同一个 SqlSession 中生效,它基于命名空间、SQL 语句和参数 作为唯一标识,它是默认开启的,当执行 commit rollback 或手动清理缓存时会清空。二级缓存的话,可以跨 SqlSession 共享缓存,是基于 Mapper 的缓存,但是需要手动配置开启,生命周期与 SqlSessionFactory 一致,数据的更新、插入、删除会使相关缓存失效。

SqlSesion 是共享的,同一个namespace 下可能存在多表查询,所以这两种情况都有可能会有脏数据的出现。

说说 MyBatis 是如何实现 Java类型于数据库类型之间的转化的?

它里面呢主要有一个 TypeHandler 的处理器可以实现这个功能。具体的操作流程呢如下,第一 MyBatis 在加载映射文件时,根据字段类型(如 jdbcType) 和 Java 类型(如 resultType) 确定使用的 TypeHandler ,然后在执行 SQL 时,ParameterHandler 会使用 'TypeHandler将 Java 参数转换为 JDBC 类型,最后在解析结果集合时,ResultSetHandler 使用 TypeHandler 将 JDBC 类型转换为 Java 对象。

简单说说 MyBatis 的插件运行原理,以及如何用它来编写一个插件

首先 MyBatis 的插件机制是通过动态代理实现的,主要是在 SQL 执行的关键点(如执行查询、更新、插入)拦截操作并增强功能。它主要有四个拦截器点分别是 Executor, ParameterHandler, ResultSetHandler 和 StatementHandler。

MyBatis 在运行时会根据配置加载插件,并在拦截点处生成代理对象。当拦截点的方法被调用时,MyBatis 会先调用插件的 intercept 方法(进行逻辑的织入),如果有多个插件,则按配置顺序一次来执行。

详细说说 Mybatis 的执行流程

执行流程可以分为初始化阶段和执行阶段。

初始化阶段主要发生在项目启动的时候,它会先解析全局配置文件 mybatis-config.xml, 然后加载所有 Mapper 文件,创建 Configuration 对象,并把数据源、插件、类型别名、Mapper 注册等信息都放进去。每个 SQL 语句会被解析成一个 MappedStatement 对象存起来,最后通过这些配置构建出 SqlSessionFactory ,我们可以通过这个 SqlSessionFactory 获取一个 SqlSession ,再调用 getMapper 得到 Mapper 接口的动态代理对象,当我们执行 Mapper 方法时,比如 selectById(1),它的底层会进入 MapperProxy.invoke() ,根据方法映射找到对应的 SQL,然后再交给 Executor 去执行,在 Executor 内部会用到 StatementHandler, ParameterHandlerResultSetHandler,分别负责编译 SQL、绑定参数,映射结果。执行完成后,MyBatis 会把结果集封装成对应的 Java 对象返回,事务提交、回滚也都是通过 SqlSession 来完成的。

扫盲八股

MyBatis 的优点有哪些呢?

首先呢相较于 JDBC 来讲,它让编码更加简单,高效,然后又相对 Hibernate 来讲呢,MyBatis 更加轻量级,也提供了更多的 SQL 控制权,适合复杂查询和高性能需求的场景。同时它本身内置的一级缓存和二级缓存机制可以提高数据库查询性能,并且支持多种数据库和连接池,在多种开发环境中都能使用,并且提供了强大的动态 SQL 功能,能根据条件灵活生动SQL语句。

Mybatis 都有哪些 Executor 执行器? 它们之间有哪些区别呢。

Mybatis 大概有三种执行器,它基于 handler 实现了 SimpleExecutor,BatchExecutor,和 ReuseExecutor。BatchExecutor 和 ReuseExecutor 我的理解是用来提升性能的,首先是 BatchExecutor 批量执行,它会批量执行 SQL 语句,主要是利用 JDBC 的批处理功能来提升性能。然后是 ReuseExecutor 重用执行器,它把 SQL 当成键,将 Statement 缓存到一个 Map 中,然后如果后续执行相同的 SQL时,这直接从缓存中获取 Statement ,不用重新创建。

BatchExecutor 批量执行器会把多个 SQL 语句通过 Statement.addBatch() 展示存到批处理队列中,直到调用 Executor.flushStatements() 时,一次性提交所有的批处理 SQL

ReuseExecutor 重用处理器在执行完成后,不关闭 Statement 而是保留在缓存中供后续复用。

MyBatis 懒加载的原理了解过吗?

这里我从源码的角度分析一下吧,首先呢:MyBatis 在查询主对象时,会根据配置判断是否需要为关联对象创建代理对象,如果需要的话呢 ProxyFactory 会创建一个动态代理对象,代理未加载的关联数据,它的默认实现为 JavassistProxyFactoryCglibProxyFactory 。最后呢 MyBatis 使用 ResultLoader 封装延迟加载的 SQL 查询, ResultLoader 会在代理对象方法被调用时触发查询。简单来说就是,只有当访问具体关联的属性时,才能真正触发数据库的查询,特别是在处理大数据集合或者复杂对象关系时,对性能的提升比较大。

if (configuration.isLazyLoadingEnabled()) {Object proxy = configuration.getProxyFactory().createProxy(target, loader);return proxy;
}public Object loadResult() throws SQLException {// 执行延迟加载的查询return executor.query(ms, parameterObject, rowBounds, resultHandler);
}

MyBatis 动态 sql 有什么用?有哪些动态 SQL 以及这些动态 SQL 的执行原理是什么呢

我首先来说说动态 sql 的定义吧,它就是这个 orm 框架根据不同的条件、需求动态生成 SQL 语句的一种机制,常用的一般有,if, where, foreach, 还有 choose when 和 otherwise。动态 SQL 呢,它的执行是基于 XML 映射文件中定义的 SQL 片段与标签,这些标签被解析,在运行时根据传入的参数值评估,最终形成完整的 SQL 语句发送到数据库执行。

在映射文件加载时,MyBatis 会解析 XML 文件中的动态 SQL 标签,当执行 SQL 语句时, MyBatis 会根据传入的参数绑定具体的值,最后根据参数值和动态 SQL 标签生成最终的 SQL 语句,并在最后执行生成的 SQL 语句,并返回结果。

Java

基础八股

有了解过 Java 的各种 version 的新特性吗?

我自己经常用的大概有三个版本,分别是 Java8, Java11。首先说说 Java8 吧,它主要引入了 Lambda 表达式,Stream 流式 API,Optional 类和一些新的日期时间和 API 等等。Java 11 呢,主要就是新增加了一些方法,较为实用的就是 Http 客户端里的 API,可能对里面进行了一些底层优化,但是没关注到。

为什么会选择 Java 而不是 Go 呢?

可以从四个方面来回答吧。首先,Java 因为加了一层中间层 JVM,所以可以做到一次编写多平台运行也就是所谓的跨平台特性,第二个就是它拥有垃圾回收功能,不像 C++ 一样,需要手动 new 和 delete。第三个是因为生态好,对吧,网上各种 Java 的资料漫天飞,Java 入行完全靠自学就可以了。Go 的话,了解的比较少,它的优势体现在"极简"和"高效上",它更适合需要极致心能、快速启动、低资源消耗的高并发中间件、云原生基础设施或新兴的微服务场景。

Java 中 String、StringBuffer 和 StringBuilder 的区别是什么?

我们可以从演进的角度看待三者,首先 String 是 Java 中基础且重要的类,并且 String 也是 Immutable 类的典型实现,被声明为 final class , 除了 hash 这个属性其它属性都声明为 final, 也正是因为它的不可变性,会影响性能。StringBuffer 就是为了解决大量拼接字符时产生很多中间对象问题而提供的一个类,它本质上是一个线程安全的可修改的字符序列,把所有修改数据的方法上都加了 synchronized,但是加锁是需要一定的性能作为代价的,这时候 StringBuilder 就登场了,它去掉了保证线程安全的那部分,减少了开销,并且它们都继承了 AbstractStringBuilder,底层都是利用可修改的 char 数组(JDK9以后是 byte数组),所以还一个可以稍微提升性能的方式是,在能预知大小的情况下,提前分配好 capacity,避免多次扩容开销。

String 底层也是用的 char 数组存放的, String 被 final 修饰,且内部的 char 也被 private 和 final 修饰了,所以是不可变的,是典型的 Immutable 类,因此其不可变性,保证了线程安全,能实现字符串常量池等。

JDK9 中的 String 做了哪些优化呢

在 JDK9 中将 String 的 char 数组改为 byte 数组了,主要是为了节省内存空间,提高内存利用率。之前 String 类是基于 char[] 实现的,内部采用 UTF-16 编码,每个字符占用两个字节。这次采用 byte[] 数组来实现, ASCll 字符串(单字节字符) 通过 byte[] 存储,仅仅需要 1 字节,并引入了 coder 变量来标识编码方法(Latin-1或UTF-16)。如果字符串只包含 Latin-1 范围内的字符,则使用单字节编码,否则使用 UTF-16。

Java 的反射了解过吗?

反射机制允许我们在程序运行时通过字节码文件获得某个对象的方法、构造方法、成员变量等,并且可以使用它们。例如,可以通过反射机制获得的构造方法来创建一个新对象,并设置该对象的成员变量并修改调用其中的方法。反射机制常用于框架,例如 spring 中的依赖注入就是通过反射机制实现的。还有 IDEA 也是提供过反射实现的,我们通过一个 . 就可以得到某个对象得所有 public 方法或变量,其实也是反射得作用。反射最关键得在于获取某个类或对象得字节码对象,大概有三种方式,Class.forName类.class对象.getClass() 。在获得字节码对象后,我们就可以获得方法(Method)、字段 (Field) 和构造方法(constructor),并可以修改字段、调用方法或通过构造函数创建对象。

对于 Java 里面的泛型你了解多少呢?

先说说泛型的作用吧,首先呢,它能够将运行时异常转为编译时异常,然后呢还可以减少代码重复,提升可读性和维护性,并且允许在编译时指定类型参数,消除了运行时需要显式类型转换的麻烦。

然后谈谈它们的泛型擦除机制:在编译的时候,泛型会被删掉,并使用泛型的上界进行替换,通常是 Object,这是为了兼容 jdk1.4 及其以下的版本,它们没有泛型。那擦除了为什么反射还能得到泛型的类型,这是因为它们的字节码文件里面存了,并且擦除了还不用强转,也是因为字节码里面给强转了。
image-20251028111352298

最后谈谈泛型的上下界限定符吧也就是 PECS 原则,上界 extends 侧重于安全的读,放宽了对容器内容类型的限制,只要那个类是某类的子类即可;下届 super 侧重于安全的写,放宽了对容器本身类型的限制,它只要能容纳某类即可。并且它们共同增强了 API 的通用性,同时在编译器保证了类型的安全。

协变(Covariance):子类型可以替换父类型(派生类替换基类)

逆变(Contravariance):父类型可以替换子类型(基类替换派生类)

说一下你了解的 Java 中的不可变类

不可变类指的是在创建后其状态(对象的字段)无法被修改的类。一旦对象被创建,它的所有属性都不能被更改,并且这种类的实例在整个生命周期内都保持不变。在 Java 中的经典不可变类有:StringIntegerBigDecimalLocalDate 等,并且它们有五个关键特征:声明类,为 final,可以防止子类继承。类的所有字段都是 privatefinal ,确保它们在初始化后不能被更改。通过构造函数初始化所有字段,并且不提供任何修改对象状态的方法如 setter 方法。

了解过 BigDecimal 吗

它是 Java 中提供的一个用于高精度计算的类,能够保证精度不丢失(主要是因为它存的是整数),不像 float 和 double 存在精度限制,并且它和 String 一样都是不可变类。伪代码如下:

public class BigDecimal extends Number implements Comparable<BigDecimal> {private final BigInteger intVal; // 存储整数部分private final int scale; // 存储小数点位置public BigDecimal(String val) {// 使用 BigInteger 来表示数值intVal = new BigInteger(val.replace("."""));scale = val.contains(".") ? val.length() - val.indexof(".") - 1: 0;}
}

扫盲八股

Java 的序列化与反序列 (基础概念)?

序列化的话是将对象转为字节流的过程,这样对象可以通过网络传输、持久化存储或者缓存。Java 提供了 java.io.Serializable 接口来支持序列化,只要类实现了这个接口,就可以将该类的对象进行序列化。反序列化的化就是将 字节流量从心转为对象的过程**,即从存储中读取数据并重新创建对象**。

接口和抽象类了解吗?

首先呢可以从设计上来看:接口的设计是自上而下的,抽象类的设计是自下而上的。然后呢它们的构造函数和成员变量了,抽象类可以包含构造函数、并且成员变量可以有不同的修饰符(如 private、protected、public),并且可以不是常量,但接口不能包含构造函数,并且里面的成员变量默认为 public static final。

Java8 :引入了 default 和 static 方法,使得接口不仅仅是方法的声明,还可以提供具体的实现,并且 default 方法允许在接口中添加新的方法实现,而不影响已经实现接口的类。

Java9: 引入了私有方法,允许在接口中定义私有方法,用于 default 方法的内部逻辑复用。

你使用过哪些 JDK 提供的工具?

我常用的核心开发工具 javac 编译和 Java 运行,打包部署工具 jar,诊断工具如 jps 查进程、jstack 查线程、jmap 查内存,此外 javadoc 文档生成和 javap 反编译也可能会用。

供任何修改对象状态的方法(如 setter 方法)。如果类包含可变对象的引用,确保这些引用在对象外部无法被修改,例如 getter 方法中返回对象的副本 (new 一个新的对象 ) 来保护可变对象。

Java 字节码你了解多少呢?

Java 字节码是 JVM 执行的、平台无关的中间指令集,它是 Java 实现 “一次编译,到处运行” 的基石。它的核心是一个结构严谨的二进制 .class 文件,包含魔数、版本号、常量池、访问标准、字段/方法等元数据。字节码指令集涵盖了加载存储、算术运算、类型转换、对象创建、方法调用和控制转移等操作,JVM 通过解释器逐条执行这些指令,并且通过 JIT 编译器将热点代码实时编译为本地机器码以提升性能。此外,字节码的动态性为技术框架提供了强大支撑–通过 ASM、Javassist 等工具可在编译器或运行时修改字节码,实现 AOP、代理类生成(如 Spring 的 CGLIB 代理) 和代码增强等高级特性,这使得字节码不仅是编译产物,更是 Java 生态中动态能力的核心载体。

Java 中的 Integer 缓存池有了解过吗

主要作用还是为了提升性能和节省内存,因为根据实践发现大部分的数据操作都集中在值比较小的范围,在 -128127 范围内的 Integer 对象会被缓存和复用。这里还有一个小细节就是:在 Java8及之后的版本,可以通过 JVM 参数 -XX:AutoBoxCacheMax=size 来调整缓存池的上限,但是 Long 的缓存池的话是写死的从 -128-127 无法配置。

// 将缓存范围扩展到 `-128 - 500`
java -XX:AutoBoxCacheMax = 500

image-20251028124705673

使用 new String(“yupi”) 语句在 Java 中会创建多少个对象

image-20251028130527943

主要看字符串常量池中是否已经存在字符串对象的引入,如果存在,则在堆中就少创建一个字符串对象,否则就新创建一个再放入字符串常量池中。

Java 中 final、finally 和 finalize 异同点说一下吧

final: 用于装饰类、方法、和变量,主要用来设计不可变类、确保类的安全性、优化性能(编译器优化)。被它修饰的类不能被继承,被修饰的方法不能被重写,被修饰的变量不可重新赋值,常用于定义常量。

finally:与 try-catch 语句块结合使用,用于确保无论是否发生异常,finally 代码块都会执行,主要用于释放资源(如关闭文件、数据库链接等),以确保即使发生异常,资源也会被正确释放。

finalize:是 Object 类中的方法,允许对象在被垃圾回收前进行清理操作,它使用的较少,通常用于回收非内存资源(如关闭文件或释放外部资源),但不建议依赖于它,因为 JVM 不保证 finalize 会被及时的执行,特别是在 JDK9 之后,这个方法被遗弃了,因为 Java 提供了更好的替代方案。

Java 中的 Optional 类是什么,作用了解吗?

我认为 Optional 的好处在于可以简化平日里一系列判断 null 的操作,使得用起来得时候看着不需要判断 null,纵享丝滑。

Yes yes = getYes();
Yes yes = getYes();
if (yes != null) {Address yesAddress = yes.getAddress();if (yesAddress != null) {Province province = yesAddress.getProvince();System.out.println(province.getName());}
}
throw new NoSuchElementException(); //如果没找到就抛错// 引入 Optional得话,会变成下面这样
Optional.ofNullable(getYes()).map(a -> a.getAddress()).map(p -> p.getProvince()).map(n -> n.getName()).orElseThrow(NoSuchElementException::new);

Java 中的基本数据类型你了解多少呢?

Java 提供了 8 种基本数据类型(Primitive Types),分别是 byte,占用了一个字节,short, char 占用了两个字节,int, float 占用了四个字节,long, double 占用了 8 个字节。其中 byte, short, int, long 的默认值是 0,float, double 的默认值是 0.0,char的默认值是\u000,boolean 的默认值是 false

http://www.dtcms.com/a/540833.html

相关文章:

  • Python界面开发2
  • 做网站还有开发文档吗做一个游戏需要什么技术
  • C语言多变量scanf循环输入深度解析:==number vs !=EOF
  • 上海殷行建设网站空间做网站
  • 吴恩达DeepLearning课程我的笔记week2
  • 建设多语种网站静态网站设计与制作书籍
  • 软件危机:开发困境与解决之道
  • NewStarCTF2025-WEEK3
  • 手机网站建设运营方案网站怎么换模板
  • 消防器具-图形识别一键计量
  • 体育彩票数据分析 python双色球数据实时分析平台+实时监控大屏 数据爬虫 可视化大屏+Flask框架 大数据 (源码)✅
  • LabelMe的安装、实例分割数据集、数据格式转换(VOC转yolo)并划分 详细教程
  • 2025年上半年架构论文《论基于事件驱动的架构设计及其应用》
  • 迁安市住房和城乡建设局网站商业计划书ppt免费模板下载
  • SQL中的JOIN该如何优化
  • 云服务器10兆可以容纳服务多少人?
  • 网站如何做内链自己建设网站怎么盈利
  • Unity Shader unity文档学习笔记(二十二):雪地几种实现方式(1. 2D贴花式 2.3D曲面细分并且实现顶点偏移)
  • 浙人医信创实践:电科金仓异构多活架构破解集团化医院转型难题
  • 多agent框架被用于分布式环境中的任务执行 是什么意思
  • 系统架构设计师备考第56天——云原生架构基础
  • CNN(卷积神经网络)和 RNN(循环神经网络)
  • 成都网站开发工资网站建设忘记密码邮箱设置
  • 延边州建设厅网站公众号网页版
  • Eclipse 重启选项详解
  • 系统分析师-信息安全-信息系统安全体系数据安全与保密
  • JavaIO笔记
  • Agentic AI 与 AI Agent的核心区别
  • 广西网站开发建设定州网站建设公司
  • 医疗营销网站建设方案帝国cms建站实例教程