微恐连续剧
67.38M · 2026-02-05
面试十几家公司(小中大企业)总结的[Java八股文],标记重点的一定要掌握,几乎50%概率会被问到。一直不推荐死记硬背,应该结合场景业务代码、手动画图加深理解,传承程序猿开源精神,现分享有需要的人。 **
一,框架篇**
单例Bean 的线程安全性取决于 Bean 的具体实现。一般来说,在spring中的bean都是无状态(没有可修改的成员变量)的对象,就没有[线程安全问题,但如果在bean中定义了可修改的成员变量,是需要考虑线程安全问题的。
我们可以加锁,使用synchronized关键字进行同步,保证同一时刻只有一个线程能访问该方法;也可以通过 @Lookup 注解设置bean为多例,每次请求都会创建一个bean实例,多个线程操作互不影响,但性能受损;
动态代理是指在程序运行时通过反射机制自动生成代理对象,确保在不修改目标类的情况下,增强目标类的功能(类增强),Java提供了两种方式来实现动态代理:
在SpringBoot2之前,Spring会根据目标对象的特性自动选择,比如目标类实现了至少一个接口默认使用JDK动态代理,如果没有实现接口就默认CGLIB代理;而SpringBoot2.2之后,统一使用CGLIB代理(无论目标类是否实现接口)
JDK动态代理:基于接口的动态代理,只能代理实现了接口的类,创建代理对象速度快(直接根据接口生成字节码并由类加载器加载),方法调用较慢(需要通过Mehtod.invoke( )方法调用,反射方法相对较慢。说明:JDK6方法调用比CGLIB慢,但JDK7/8对反射调用做了优化,不比CGLIB慢);
CGLIB动态代理:基于类继承的动态代理,生成目标类的子类来实现代理,代理对象创建速度慢(使用ASM字节码生成目标类的子类),方法调用快(直接覆盖目标方法,并在方法中插入拦截逻辑,没有走反射);
动态代理提供了高度的灵活性和扩展性,但也带来了性能和复杂性,广泛应用于AOP、远程代理、Spring的依赖注入等场景。
(1)过滤器是Servlet层面的,随Servlet容器启动初始化;拦截器是Spring框架的,随Spring容器启动初始化;
(2)过滤器在拦截器之前执行,可以处理所有的请求;而拦截器只能处理SpingMVC的Controller请求;
(3)过滤器操作更底层,一般处理跟业务无关的逻辑操作,比如请求/响应的全局编码设置;拦截器更贴近业务,比如用户登陆状态验证;
例子:过滤器设置请求编码 UTF-8
例子:登录拦截器(未登录跳转到登录页)
事务底层是基于AOP实现的,而AOP是通过代理对象实现的。@Transactional注解的实现逻辑,可修饰类或方法:
(1)启动时扫描注解
当Spring容器启动时,TransactionAnnotationParser会扫描@Transactional注解,并将其解析为一个TransactionAttribute对象
(2)创建代理对象
Spring使用AOP为带有@Transactional的类生成代理对象,核心是TransactionInterceptor(事务拦截器)
(3)方法调用拦截
当外部调用代理对象的方法时,事务拦截器会拦截方法调用,拦截器通过TransactionManager来管理事务:判断当前是否存在事务,如果需要新建事务就调用begin( )方法,执行目标方法,方法执行成功则提交事务,异常时根据回滚规则决定是否回滚。
spring事务失效的场景如下:
(1)方法没有用public 修饰,代理默认只会拦截公共方法;
(2)同一个类中通过this来调用事务方法,这时候调用不会经过代理对象(事务的开启、提交、回滚等逻辑都是在代理对象的方法调用链中实现的,this是当前对象本身,不是代理对象)。解决方案:通过将自调用的方法提取到另一个服务类解决;
(3)事务注解标记在接口上而不是实现类上;
(4)自己手动捕获异常没有抛出,事务无法知悉
(5)事务默认只会回滚运行时异常和错误。发生受检查异常时事务无法回滚,可通过在catch中抛出RuntimeException异常;或者通过配置@Transactional(rollbackFor = Exception.class)设置回滚所有异常。
IOC容器的创建过程为:
(1)读取配置文件:加载配置文件或配置类中的Bean定义;
(2)创建BeanFactory:初始化 DefaultListableBeanFactory 作为默认的Bean工厂,来管理 Bean的定义(BeanDefinition)和实例;
(3)解析BeanDefinition:Spring解析每个Bean,封装成BeanDefinition对象,存入BeanDefinitionMap中;
(4)注册BeanDefinition到BeanFactory中;
(5)创建单例Bean:ApplicationContext在容器刷新时就创建单例Bean
(1)实例化:Spring容器启动时,根据XML配置文件或注解扫描来获取Bean的定义信息,通过反射调用构造函数来创建Bean实例;
(2)依赖注入:Spring容器根据Bean的配置,通过setter注入、字段注入或构造器注入进行依赖注入;
(3)初始化:Spring容器为每个Bean调用初始化方法,比如Aware接口回调、前置处理等;
(4)使用Bean:在容器中,Bean可以随时被获取和使用;
(5)销毁bean:当容器关闭时,Spring会调用Bean的销毁方法来销毁bean
需要全套面试笔记及答案【扫一扫】 即可免费获取**
循环依赖就是两个或多个模块、类(Bean)、组件之间互相依赖,形成一个闭环。循环依赖在spring中是允许的,spring框架利用三级缓存能解决大部分的循环依赖,其关键就是要提前暴露未完全创建好的Bean。
一级缓存(Singleton Objects Map),单例池,用来存储完全初始化好的单例Bean;二级缓存(Early Singleton Objects Map):用来存储已经实例化但未完全初始化的Bean(用于提前暴露对象);三级缓存(Singleton Factories Map):用来存储对象工厂,可以通过工厂创建早期Bean(用于解决代理对象的创建)。
一般的循环依赖,比如A和B对象通过setter注入形成循环依赖,首先创建A实例,将这个半成品A加入到二级缓存中,接着注入依赖B,从二级缓存中获取半成品A将B初始化, B创建成功,将B存储到单例池中,在单例池中将B注入给A,A创建成功,存储到单例池中;(但二级缓存不能解决代理对象,所以引入了三级缓存)
创建A实例,将对象A生成ObjectFactory对象放入到三级缓存中,接着注入B,发现B不存在,实例化B,B也生成ObjectFactory对象放入三级缓存,B从三级缓存中获取A对象工厂创建的代理对象,代理对象放入二级缓存中,将A的代理对象注入给B,B创建成功放入单例池,最后将B注入给A,A也创建成功放入单例池;
以上的方法有两种条件:1.依赖的Bean必须是单例;2.不是通过构造器注入依赖的。对于构造行循环依赖可以使用@Lazy 注解进行懒加载来实现。
SpringMVC 是Spring框架中用于构建Web应用程序的核心模块,以前端控制器为基础(类似于调度器),提供了一套灵活强大的组件来处理HTTP请求、业务逻辑和视图渲染
(1)发送请求:客户端发送请求,被前端控制器拦截,将请求发送给处理器映射器;
(2)处理器映射:处理器映射器根据URL找到对应的Controller类和方法;
(3)处理器适配:找到处理器后,前端控制器交给处理器适配器执行,处理器适配器处理参数,将请求参数绑定到方法参数或对象里
(4)调用处理器:处理器适配器通过反射调用Controller中的业务方法,返回对象数据;
(5)响应数据:将对象转化为JSON,返回给前端渲染页面;
SprintBoot项目中的启动类上有一个注解@SpringBootApplication,这个注解封装@EnableAutoConfiguration注解:用来启动SpringBoot的自动配置机制,通过@Import 导入了 AutoConfigurationImportSelector , 在SpringBoot启动时,会扫描META-INF/ spring.factories文件(springboot2)/ AutoConfiguration.import(springboot3),文件里列出了所有自动配置类的路径位置,配置类通过各种@Conditional条件注解动态加载满足条件的bean,将其导入到Spring容器中;
常见的有:
这保证了 默认生效,但允许用户覆盖。
(1)配置加载阶段:解析Mybatis配置文件(如XML文件,或直接添加依赖,在 .yml 文件上配置Mybatis)根据配置文件创建会话工厂SqlSessionFactory;
(2)SQL执行阶段:通过会话工厂创建会话SqlSession实例(封装了JDBC操作,包含了执行SQL语句的方法);通过动态代理生成Mapper接口的实例;通过Executor执行SQL语句:1.将Java对象属性转换为SQL参数,2.进行SQL解析,替换${ } 和 #{ },生成最终的SQL执行,将结果转换为Java对象;
(3)释放资源阶段:Mybatis还提供了事务的管理,支持自动提交和手动控制事务;最后释放SqlSession对象资源,关闭数据库连接
(1)依赖注入(Dependency Injection,DI)
依赖注入是 Spring 框架的核心功能之一,它是基于控制反转(IoC,Inversion of Control)来实现。传统的开发中,组件通常会显式地创建其依赖的对象,而在 Spring 中, IoC 容器负责管理对象的生命周期及其依赖关系。
简化开发:通过将对象的创建和管理交给 Spring 容器,开发者只需要关注业务逻辑,而无需手动创建和管理对象的实例。
解耦:对象之间的依赖关系由 Spring 容器管理,而不是硬编码在代码中,这样可以降低模块之间的耦合度,提高代码的灵活性和可测试性。
依赖注入方式:
注意:官方推荐使用构造器注入的方式(因为依赖对象定义往往用final修饰,确保了依赖的不可变性和不为null,更加稳定),但企业中常用的是字段注入,更加方便;
AOP称为面向切面编程,核心思想是将类中的公共行为(横切关注点)切割出来,封装为一个可重用的模块(切面类),在切面类中定义了切入点和通知。作用是减少系统中的重复代码,降低模块之间的耦合度,提高可维护性和可扩展性;
而我在项目中也经常用到AOP,比如有很多数据库表都有一些公共字段(createTime, updateTime, createUser, updateUser),每次对这些表进行插入或修改操作都要更新这些公共字段,所以可以利用AOP封装一个模块,根据当前时间、用户ID对属性赋值,在mapper中添加注解就实现了公共字段的自动填充功能。
而AOP在一些业务场景用的比较多的比如:记录操作日志、缓存处理等等。
(3)Spring 的 IoC 容器
IOC(Inversion of Control),控制反转,这是一种设计模式,核心思想是将对象的创建、依赖注入和生命周期管理交给IOC容器。在传统的编程方式中,我们一般需要在类中显示地创建依赖对象,通过硬编码方式来控制对象的创建和管理,而在Spring中,Bean以及对象之间的依赖关系都交给IOC容器负责,降低了代码之间的耦合度,也提高了系统的灵活性;
Spring中主要是通过XML配置和注解配置(@Component 、@Autowired、@Configuration)两种方式来注册Bean
例如:使用AOP給业务添加日志记录
需要全套面试笔记及答案【扫一扫】 即可免费获取**
BeanFactory实际就是IOC容器,而ObjectFactory的作用如下:
(1)当需要推迟对象的创建来避免循环依赖或优化性能时,可以使用ObjectFactory
(2)在单例Bean中注入一个短作用域的Bean时,可以通过ObjectFactory确保获取最新的实例
在注解配置@Scope或XML配置scope属性就可以设置Bean的作用域。
(1)singleton(默认),每个IOC容器仅存在一个实例,所有依赖注入共享同一个对象;
(2)prototype:每次注入依赖(或getBean( )方法)都会创建一个新实例;
(3)request:每个HTTP请求创建一个新实例,仅在Web应用中有效;
(4)session:每个HTTP Session创建一个实例,用户会话期间共享;
(5)application:每个ServletContext生命周期内一个实例;
(6)websocket:每个websocket会话一个实例,生命周期和websocket连接一致;
在Spring框架中,拦截链通过责任链模式实现,核心目的是将多个拦截器(或通知)按顺序组织,在目标方法或请求处理的不同阶段依次触发:
在Spring Web中,拦截器实现HandlerInterceptor接口,编写preHandel、postHandle、afterCompletion方法实现业务执行前后逻辑添加(如权限校验、日志记录),另外需要编写一个实现WebMvcConfigurer接口的配置类,注册拦截器,设置哪些路径需要拦截和放行,
执行步骤如下:
(1)初始化链:根据Hander(处理器/controller层)对应的URL和拦截器列表配置,创建处理器执行链;
(2)preHandel阶段:按拦截器配置顺序依次调用preHandle方法。如果某个拦截器返回false,终止后续拦截器和handler的执行;
(3)执行handler:调用Controller方法处理请求;
(4)postHandler阶段:按拦截器配置的逆序调用postHandle( );
(5)渲染视图:处理ModelAndView,生成相应内容;
(6)afterCompletion阶段:无论请求成功或异常,按拦截器配置的逆序调用afterCompletion方法
1. 编写拦截器类
2. 注册拦截器(配置类)
(1)工厂模式:BeanFactory和ApplicationContext是Spring的核心容器,负责创建和管理Bean实例;
(2)单例模式:Spring默认将Bean的作用域设置为单例,保证容器中只有一个实例;
(3)代理模式:Spring AOP使用动态代理生成代理对象增强类;
(4)模板方法模式:jdbcTemplate、RestTemplate等模板类封装了固定流程,用户只需实现具体逻辑(SQL执行、HTTP请求);
(5)适配器模式:在SpringMVC中适配不同类型的控制器
(1)基于XML配置文件,显示声明Bean的类和依赖关系;
(2)基于注解扫描注册,通过@Component、@Service、@Controller注解定义为Bean,通过组件扫描(@ComponentScan)注册为Bean
(3)基于Java配置类注册,在@Configuration注解的类中的方法添加@Bean注解;
@Qualifier注解用于解决依赖注入的歧义性问题,当容器中存在多个相同类型的Bean时,@Autowired注解无法自动选择具体的Bean注入,此时需要配合@Qualifier来指定具体的Bean名称。
@Qualifier注解和@Primary注解的区别:@Primary用来标记某个Bean为默认优先注入的候选者,而@Qualifier用来显示指定具体Bean的名称;
(1)作用目标:@Component注解作用在类上;@Bean注解作用在方法上,一般在配置类;
(2)使用场景:@Service、@Controller等注解都是@Component的特化形式,一般用于自己编写的类;而@Bean一般用于第三方库的类或需要手动控制实例化的对象;
(3)使用方式:@Component标记一个类,让Spring自动扫描并创建Bean;@Bean需要显示地配置和创建,返回一个对象作为Bean
(1)@Component是它们的通用注解,标记任意类为Bean,没有特定的功能,是其他注解的元注解;
(2)@Controller用于标记控制层,处理HTTP请求,支持请求URL映射;
(3)@Service用于标记业务类,没有额外功能,区分职责提高代码可读性;
(4)@Repository用于标记持久层,自动转换数据访问异常
(1)容器初始化:调用Application.run( )方法启动应用,创建ApplicationContext;
(2)配置加载:解析配置类、扫描组件、注册Bean的定义;
(3)Bean实例化和依赖注入:根据Bean的定义创建对象并注入依赖;
(4)扩展处理:在Bean初始化前后插入逻辑(如AOP代理),实现生命周期回调增强功能;
(5)容器就绪:完成启动,通过事件机制实现扩展点
(1)初始化:调用Application.run( )方法启动应用,加载配置器和器;
(2)准备环境:加载配置文件(如application.properties)
(3)创建ApplicationContext:初始化容器;
(4)刷新容器:加载Bean定义、执行自动配置、启动内嵌容器、Bean的依赖注入初始化;
(5)执行自定义执行后逻辑;
(6)发布 ApplicationReadyEvent:标志应用完全就绪
(1)语法格式:.properties文件使用键值对格式,通过 . 表示层级关系;yml和ymal文件基于YAML格式,使用换行缩进表示层级关系,可读性更高;
(2)加载顺序:同目录下,.properties 文件的优先级高于 .yml/.yaml ; 不同目录下,按目录优先级覆盖(例如根目录的配置覆盖类路径的配置)
(3)复杂数据结构:.properties文件对于列表需要逗号隔开,对象之间需要手动拆分;yml和ymal文件对于多个对象只需要换行,对于列表只需要换行加短横线,操作更加方便;
示例:application.properties
示例:application.yml
需要全套面试笔记及答案【扫一扫】 即可免费获取**
SpringBoot是一个基于Spring框架的开源工具,旨在简化Java应用(尤其是Web应用)的初始搭建和开发过程。通过“约定优于配置”的理念,大幅度减少传统Spring开发中的复杂配置,让开发者能快速构建独立运行、生产级的应用。
说明:传统的Spring需要大量的XML或@Bean配置,而Spring Boot内置了一套默认的约定,如果没有提供配置,Spring Boot就会按照“约定”的默认逻辑工作,只有当你的需求不同于默认约定时,才需要额外配置。比如,在pom.xml引入spring-boot-starter-data-jpa依赖,SpringBoot自动创建DataSource的Bean,默认使用application.yml中的配置。
它的特性如下:
(1)[自动配置]:SpringBoot根据@EnableAutoConfiguration注解开启自动配置机制,扫描AutoConfiguration.imports文件根据条件动态加载预定义的配置类;传统的Spring需要手动编写配置类,显示定义dataSource等一些Bean。
(2)起步依赖:通过预定义集成的依赖包(一系列Starter),自动引入所有相关依赖包,避免版本冲突;传统的Spring需要手动指定每个依赖及其兼容版本;
(3)内嵌服务器:添加spring-boot-starter-web依赖后,就会内嵌Tomcat、SpringMVC服务器,应用可直接打包为可执行JAR包,无需额外部署到web服务器;传统的Spring需要打包为WAR包,部署到独立的Tomcat服务器;
(4)生产就绪功能:SpringBoot提供坚控和管理端点(如健康检查、性能指标),方便运维;传统的Spring需要自行配置健康检查、指标收集等;
(5)简化配置:约定优于配置原则,默认提供合理的配置参数(如端口号、数据库连接池参数)和创建默认配置类,可通过properties文件或yml文件做修改覆盖;
Mybatis的缓存机制分为一级缓存(本地缓存)和二级缓存(全局缓存),通过缓存减少数据库查询次数来提升性能;
(1)一级缓存仅在同一个SqlSession中生效(默认开启),生命周期与Sqlsession绑定,会话结束或执行写操作时、事务提交回滚缓存都会失效;
(2)二级缓存在同一个Mapper中生效,多个SqlSession共享缓存(需手动开启,在对应XML 文件中添加 标签),生命周期跟SqlsessionFactory一致
开启二级缓存后,查询时会先查一级缓存,未命中则查二级缓存,如果二级缓存也未命中就访问数据库,并将结果存入一级和二级缓存;需要注解一级和二级缓存都可能导致脏读,频繁修改的数据不适合开启二级缓存。
(1)对象关系映射:Hibernate完全将数据库表映射为Java对象,开发者几乎无需手动编写SQL,他自动生成SQL;Mybatis中需要手动编写SQL,通过XML或注解将SQL映射到Java方法中;
(2)SQL控制权:Hibernate自动生成SQL,开发者无法直接干预,可能会生成冗余SQL,性能较差;Mybatis中开发者完全控制SQL,需要自行维护SQL;
(3)性能:Hibernate内置强大的一级缓存和二级缓存减少数据库访问,适合读多写少场景;Mybatis手动编写高效SQL,避免不必要的查询,适合高性能场景;
Hibernate 配置(hibernate.cfg.xml)
保存数据的代码
说明:受检查异常和运行时异常
1.受检查异常
在编译阶段,Javac编译器会强制检查方法中是否显示地处理这些异常,如果没有使用try-catch捕获或没有通过throws向上抛出,编译就会报错。这种异常一般是外部资源或环境问题引起的,这样做的目的是提醒开发者写代码时考虑这些异常情况。
如果不写 throws IOException 或 try-catch,编译就过不了。
受检查异常常见例子:
IOException:文件不存在、读写失败
SQLException:数据库连接失败、SQL 执行错误
ClassNotFoundException:类加载失败
InterruptedException:线程被中断
2.运行时异常
继承了RuntimeException,在运行阶段才会被发现,编译器不会强制要求处理,通常是编程错误或逻辑缺陷导致的。
运行时异常常见例子:
NullPointerException(空指针)
ArrayIndexOutOfBoundsException(数组越界)
ArithmeticException(除以 0)
ClassCastException(类型转换错误)
如何实现全局异常捕获,方法如下:
首先使用@ControllerAdvice注解将类注册为一个全局异常处理器组件,作用范围是所有的控制层;在SpringMVC的请求处理链中有一个关键接口HandleExceptionResolver(处理异常解析器),所有实现了该接口的类在出现异常时会依次调用该接口,将异常转化为响应;
步骤如下:1、控制层抛出异常;2、Spring捕获异常后,调用HandlerExceptionResolverComposite(处理异常解析器组合);3、遍历所有注册的HandleExceptionResolver实例(例如ExceptionHandleExceptionResolver);4、ExceptionHandleExceptionResolver查找对应的@ExceptionHandler方法;5、匹配成功后执行该方法,返回结果;