1 - SSM 篇
1.1 Spring
1.1.1 Spring
1.1.1.1 Spring 框架中的单例 bean 是线程安全的吗?
注解
@Scope
的默认值是singleton
,单例的。一般 bean 都是注入无状态的对象,没有线程安全问题。但如果在 bean 中定义了可修改的成员变量,是线程不安全的,可以使用 多例 或者 加锁 来解决。
- 例如,如果多个线程同时访问和修改 Bean 的成员变量,可能会导致数据不一致或竞态条件。
@Component // 1. 单例 public class Counter { private int count = 0; // 可修改状态变量,线程不安全 private final int count = 0; // 不可修改状态变量,线程安全 public void increment() {count++;} public void increment(int count) {count++;} // 无状态变量,线程安全 }
@Component @Scope("prototype") // 2. 多例,线程安全 public class Counter { private int count = 0; public void increment() {count++;} }
@Component public class Counter { private int count = 0; public synchronized void increment() {count++;} // 3. 加锁,线程安全 }
Bean的单例和非单例,生命周期是否一样?
- 单例 Bean:Spring 容器在启动时创建一个实例,并在整个生命周期内共享,因此对象的生命周期是整个应用程序的生命周期。
- 多例 Bean:每次请求(或注入)都会创建一个新的实例,生命周期仅限于每次请求或注入时存在。
1.1.1.2 AOP 实现原理?自己实现过动态代理么?
1. AOP 实现原理?
AOP 是一种编程范式,用来处理那些在多个地方都要用到的代码,比如日志记录、权限检查、事务管理等。
- AOP 通过一种特殊的类(切面)来定义这些横切关注点,然后在程序运行时,动态地将这些关注点应用到目标代码上。
Java 中,动态代理 是一种在运行时动态创建代理对象的技术,它允许你在不修改原代码的情况下,给类添加额外功能。
- JDK 动态代理:基于反射生成代理对象,要求被代理类实现一个或多个接口。
- CGLIB 动态代理:基于继承生成代理对象,适用于被代理类没有实现接口。
Java 反射是一种在运行时检查或修改程序行为的能力。反射可以用来动态创建对象、调用方法、访问字段等。
- 使用
java.lang.reflect
包中的类,如Class
、Method
、Field
等,可以实现反射功能。- 例如,使用
Class.forName("com.example.MyClass").newInstance()
来创建对象。
2. 自己实现过动态代理么?
我自己实现过动态代理来进行性能统计。(没有使用注解
@Aspect
、@Pointcut
、@Around
等)
首先,定义一个注解
@PerformanceMethod
,用于标记需要进行性能统计的方法。@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PerformanceMethod { }
然后,创建一个动态代理处理器,用于在方法执行前后采集性能数据。
public class PerformanceInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // ... (模拟自动采集并打印出方法的执行时间) } public static Object createProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new PerformanceInvocationHandler(target) ); } }
最后,在接口的方法上使用
@PerformanceMethod
注解,并使用动态代理来创建代理对象。public interface Service { @PerformanceMethod void performTask(); } public class ServiceImpl implements Service { @Override public void performTask() { // ... (模拟业务操作) } }
public class Test { public static void main(String[] args) { Service service = new ServiceImpl(); Service proxyService = (Service) PerformanceInvocationHandler.createProxy(service); proxyService.performTask(); } }
1.1.1.4 @Transactional 原理?事务失效的场景?
1. @Transactional 原理:
@Transactional
是 Spring 框架提供的声明式事务管理注解,其工作原理基于 AOP(面向切面编程)。它通过创建代理对象来拦截方法调用,并在方法执行前后进行事务的管理和控制。
2. 事务失效的场景:
- 类或方法没有被 Spring 管理:没有被标注为
@Service
、@Component
等,那么@Transactional
注解将不会生效。- 非事务方法内部调用:如果在一个非事务方法中通过 `this 调用事务方法,那么种调用不会经过 Spring 的代理对象。
- 方法不是
public
:@Transactional
注解只适用于public
方法。如果方法不是public
的,事务注解将不会生效。- 异常被捕获:如果事务方法内部捕获了应该导致事务回滚的异常,那么事务将不会回滚。
- 异常类型不对:
@Transactional
注解默认只对运行时异常(RuntimeException
)和错误(Error
)进行回滚。- 事务传播行为不正确:如果事务的传播行为设置不正确,可能会导致事务不按预期生效。
1.1.1.5 Spring 的 bean 的生命周期?
- 定义Bean:容器启动,加载配置文件或类中的 Bean 定义。
- Bean 的实例化:根据配置创建 Bean 实例(如
BeanDefinition
)。- Bean 的依赖注入:通过依赖注入,将依赖的属性赋值给 Bean。
- Bean 的初始化:执行自定义的初始化方法(如
InitializingBean
、AOP/动态代理
)。- Bean 的使用:Bean 可以被其他组件使用。
- Bean 的销毁:容器关闭时,执行销毁方法。
1.1.1.6 Spring 中的循环引用?
- 循环引用是指在Spring容器中,两个或多个bean相互依赖,形成一个闭环。例如,A依赖于B,同时B又依赖于A。
- 循环引用会导致Spring容器无法完成bean的创建和注入,因为每个bean都在等待对方先完成初始化。
解决方案:
- 使用
Setter
注入: 如果循环引用发生在构造方法注入中,可以使用Setter注入代替,避免在构造方法中就创建所有依赖。- 使用
@Lazy
注解: 可以在依赖注入的地方使用@Lazy
注解,这样依赖的bean会延迟加载,直到第一次被使用时才创建。- 使用
ApplicationContext
的getBean
方法: 通过编程方式获取bean时,可以避免循环依赖的问题。
1.1.1.7 谈谈你对 Spring IOC 和 DI 的理解?
1. 控制反转(Inversion of Control,IOC):
控制反转是一种设计原则,用来减少程序中各个组件之间的耦合度。
在传统的程序设计中,组件之间的依赖关系是由组件自身在内部创建或者直接实例化其依赖的类来实现的。
而在控制反转中,组件不再负责创建或管理它们的依赖关系,而是将这种控制权反转给第三方,通常是Spring容器。
实现原理:
容器管理: Spring容器负责管理对象的生命周期和依赖关系。
当容器启动时,它会读取配置文件(XML、注解或Java配置类),并根据这些配置创建对象以及设置它们的依赖关系。
工厂模式: Spring容器充当了一个工厂,它使用工厂模式来创建对象。
容器中有一个工厂方法,根据配置文件中的信息来决定实例化哪个类的实例。
单例和原型: Spring容器可以管理对象的生命周期,它可以创建单例(Singleton)或原型(Prototype)对象。
单例意味着该对象在容器中只有一个实例,而原型则每次请求都会创建一个新的实例。
2. 依赖注入(Dependency Injection,DI):
- 依赖注入是实现控制反转的一种手段,它将组件的依赖关系从组件内部转移到外部进行管理。
- 依赖注入可以通过构造函数注入、setter方法注入、接口注入等方式实现。
实现原理:
- 反射: Spring使用Java反射API来创建对象和调用方法。在运行时动态地访问对象的属性和方法,从而实现依赖注入。
- 核心接口: Spring容器有两个核心接口,
BeanFactory
和ApplicationContext
。- 自动装配: Spring支持自动装配功能,它可以通过类型、名称或构造函数参数来自动装配bean,减少了配置的复杂性。
- 注解: 常见的注解包括
@Autowired
、@Inject
、@Resource
等,它们可以应用于构造函数、字段或setter方法。
1.1.1.8 Spring 的事务传播机制?
事务传播机制定义了当多个事务方法存在调用关系时,事务如何在这些方法之间进行传播。
Spring 支持以下 7 种事务传播机制,通过
@Transactional
注解的propagation
属性来指定:
REQUIRED
:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。(默认)SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。MANDATORY
:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则把当前事务挂起。NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。NESTED
:如果当前存在事务,则创建一个事务作为嵌套事务来运行。如果当前没有事务,则该取值REQUIRED
。
1.1.2 Spring MVC
1.1.2.1 Spring MVC 执行流程?
- 前端控制器(
DispatcherServlet
):用来接收用户的请求。- 请求映射处理器(
HandlerMapping
):用来确定请求映射到哪个Handler
。- 处理器适配器(
HandlerAdapter
):用来调用Handler
方法。- 处理器(
Handler
):用来处理请求并返回模型和视图名称。- 视图解析器(
View Resolver
):用来解析视图名称,渲染视图。
1.1.2.2 过滤器(Filter)与拦截器(Interceptor)的区别?
1. 过滤器(Filter)
Filter 过滤器是 Java Web 的三大组件之一,用于拦截资源请求以实现特定功能,如登录校验、编码处理等。
过滤器通常包含三个方法:
init
(初始化)、doFilter
(拦截请求并处理)、destroy
(销毁)。例如,登录校验过滤器可以验证请求头中的 JWT 令牌,未登录用户会收到相应错误信息。
2. 拦截器(Interceptor)
- 拦截器是动态拦截控制器方法调用的机制,类似于过滤器。它可以在方法调用前后执行设定的代码。
- 例如,Interceptor 实现
preHandle
方法来决定是否放行请求,未登录用户会收到错误信息。3. 拦截器与过滤器的区别:
- 接口不同:过滤器实现
Filter
接口,拦截器实现HandlerInterceptor
接口。- 拦截范围不同:过滤器拦截所有资源,拦截器仅拦截 Spring 环境中的资源。
1.1.3 Spring Boot
1.1.3.1 Spring Boot 的自动配置原理?
@SpringBootApplication
注解是 Spring Boot 的核心注解,它组合了下面三个注解的功能:
@SpringBootConfiguration
表示这是一个 Spring Boot 的配置类,
@EnableAutoConfiguration
负责开启自动配置,
@ComponentScan
则是扫描指定包路径下的组件并注册为 Spring 容器的 Bean。
@EnableAutoConfiguration
是自动配置机制的核心注解。它的源码中使用了@Import
注解来导入配置选择器。这个配置选择器会读取项目 classpath 路径下
META-INF/spring.factories
文件中列出的自动配置类全限定名。在这些自动配置类中,定义的 Bean 会根据条件注解来决定是否被注册到 Spring 容器中。
例如,
@ConditionalOnClass
注解会检查指定的类是否存在于 classpath 中,如果存在,则会将该类注册为 Bean。
1.1.3.2 Spring、Spring MVC、Spring Boot 常见注解?
Spring 说明 @Controller、@Service、@Repository、@Component 使用在类上用于实例化 Bean @Autowired 使用在字段上用于根据类型依赖注入 @Configuration 指定当前类是 Spring 配置类,当创建容器时会从该类上加载注解 @Qualifier 结合 @Autowired 一起使用用于根据名称进行依赖注入 @Scope 标注 Bean 的作用范围 @ComponentScan 用于指定 Spring 在初始化容器时要扫描的包 @Bean 使用在方法上,标注将该方法的返回值存储到 Spring 容器中 @Import 使用 @Import 导入的类会被 Spring 加载到 IOC 容器中 @Aspect、@Before、@After、@Around、@Pointcut 用于切面编程(AOP)
Spring MVC 说明 @RequestMapping 用于映射请求路径,可以定义在类和方法上。用于类上,则表示所有方法都是以该地址作为父路径 @RequestBody 注解实现接收 http 请求的 json 数据,将 json 转换为 java 对象 @RequestParam 指定请求参数的名称 @PathViriable 从请求路径下中获取请求参数 /user/{id}
,传递给方法的形式参数@ResponseBody 注解实现将 controller 方法返回对象转化为 json 对象响应给客户端 @RequestHeader 获取指定的请求头数据
Spring Boot 说明 @SpringBootConfiguration 组合了@Configuration 注解,实现配置文件的功能 @RestController @Controller + @ResponseBody @Value 用来将配置文件中的值注入到 bean 的字段中 @EnableAutoConfiguration 打开自动配置的功能,也可以关闭某个自动配置的选 @ComponentScan Spring 组件扫描
1.1.3.3 Spring、SpringMVC、SpringBoot 区别和联系?
Spring 是一个开源的企业级应用程序开发框架,提供了全面的基础设施支持。
依赖注入(DI):通过控制反转(IoC)管理对象的生命周期。
面向切面编程(AOP):支持事务管理、日志记录等功能。
Spring MVC 是 Spring 框架的一部分,专注于构建基于 MVC(模型-视图-控制器)设计模式的 Web 应用。
提供了请求处理、视图解析、数据绑定等功能。
通过注解(如
@Controller
、@RequestMapping
)简化开发。Spring Boot:Spring Boot 是基于 Spring 的一个框架,旨在简化 Spring 应用的配置和开发。
提供了自动配置功能,减少了繁琐的配置。
内嵌服务器(如 Tomcat、Jetty),可以直接运行 Spring Boot 应用。
- 区别和联系:
- Spring 是基础框架,提供核心功能。
- Spring MVC 是构建 Web 应用的模块。
- Spring Boot 是简化 Spring 应用开发的工具,集成了 Spring 和 Spring MVC 的功能。
1.1.3.4 @Autowired 和 @Resource 区别和联系?
联系:
@Autowired
和@Resource
都是用于依赖注入的注解,它们都可以用来装配 Spring 容器中的 Bean。区别:
来源:
@Autowired
是 Spring 特有的,而@Resource
是 Java EE 的一部分。装配方式:
@Autowired
默认按类型装配,而@Resource
默认按名称装配。限定符:
@Autowired
可以通过@Qualifier
来指定注入的具体 Bean,而@Resource
通过名称来指定。异常处理:
@Autowired
在找不到 Bean 时可以设置为非必须的,而@Resource
在找不到 Bean 时会抛出异常。
1.2 Mybatis
1.2.1 MyBatis 的执行流程?
- 读取配置文件
mybatis-config.xml
,加载运行环境和映射文件- 构造会话工厂
SqlSessionFactory
- 会话工厂创建
SqlSession
对象- 操作数据库的接口
Executor
执行器,同时负责查询缓存的维护- Executor 接口的执行方法中有一个
MappedStatement
类型的参数,封装了映射信息- 输入参数映射,输出结果映射
1.2.2 Mybatis 是否支持延迟加载?
延迟加载是指在需要时才加载数据,而不是在一开始就加载所有数据。这对于减少数据库查询和提高性能非常有用。
MyBatis支持延迟加载,可以通过配置实现:
- 配置
mybatis-config.xml
文件: 在配置文件中设置lazyLoadingEnabled
属性为true
。- 使用
<association>
和<collection>
标签: 在映射文件中使用这些标签来指定哪些属性应该延迟加载。延迟加载的底层原理:
- MyBatis的延迟加载是通过代理模式实现的。当访问一个延迟加载的属性时,会检查这个属性是否已经被初始化。
- 如果没有,它会创建一个代理对象,并在代理对象的方法被调用时,去数据库查询数据并初始化这个属性。
1.2.3 Mybatis 的一级、二级缓存?
一级缓存 (Local Cache) 二级缓存 (Second Level Cache) 作用范围 单个 SQL Session 多个 SQL Session,跨 Session 共享 存储机制 PerpetualCache
和HashMap
PerpetualCache
和HashMap
默认开启 是 否 清空时机 Session 执行 flush 或 close 操作时清空 通常为 SQL 增删改操作后清空 配置位置 核心配置文件 Mapper 映射文件
1.3 其他题目
1.3.1 Spring容器里存的是什么?
- Bean 实例:容器创建并管理的对象。
- Bean 配置:定义 Bean 的属性、初始化方法、销毁方法等信息。
- Bean 依赖关系:管理 Bean 之间的依赖注入。
- 生命周期回调:处理 Bean 的初始化和销毁方法。
- 作用域和上下文信息:如单例、原型等作用域,及应用上下文的相关信息。
1.3.2 哪个注解可以让工具类获取配置文件的值?
配置文件定义:
db.url=jdbc:mysql://localhost:3306/mydb
在 Spring Boot 应用中,可以使用
@Value
注解来注入配置文件中的值。@Component public class DatabaseConfig { @Value("${db.url}") private String url; }
在 Spring 管理的其他Bean中,你可以直接自动装配工具类,并使用其属性。
1.3.3 操作数据库要引入哪些 jar 包?
- MyBatis 核心 jar 包:这是MyBatis框架的核心库,负责实现MyBatis的基本功能。例如,
mybatis
包。- 数据库驱动 jar 包:这是特定数据库的JDBC驱动,用于Java程序与数据库进行通信。例如,
mysql-connector-java
驱动。- 日志相关 jar 包(可选):MyBatis在执行过程中可能会使用日志框架来记录日志,例如
log4j
包来支持。
1.3.4 Maven 依赖传递、依赖冲突、工程继承、工程聚合?
Maven 依赖传递
- 当你的项目依赖了一个库,而这个库又依赖了其他的库时,Maven 会自动将这些间接依赖的库也添加到你的项目中。
- 例如,如果你的项目依赖了
A
库,而A
库又依赖了B
库,那么B
库也会被传递性地添加到你的项目依赖中。Maven 依赖冲突
依赖冲突发生在项目的多个依赖之中存在相同
groupId
和artifactId
的库,但是版本号不同。Maven 使用一套规则来解决依赖冲突,通常它会根据路径最短者优先的原则来选择版本。
Maven 工程继承
- Maven 工程继承是指一个 Maven 项目可以继承另一个 Maven 项目的构建配置。
- 继承是通过
pom.xml
文件中的<parent>
标签来实现的。主要用于模块化项目,其中子项目共享一些公共配置。Maven 工程聚合
- 工程聚合是指在多模块项目中,你可以将多个子项目聚合到一个父项目中,以便一次性构建所有的子项目。
- 聚合是通过
pom.xml
文件中的<modules>
标签来实现的。主要用于管理多模块项目的构建过程。
1.3.5 调用 mapper 接口的抽象方法操作数据库的原理?
Mapper接口定义:开发者定义一个Mapper接口,里面声明了需要执行的数据库操作方法。
XML映射文件:包含了SQL语句和对应的接口方法。这个文件告诉MyBatis如何将接口方法映射到具体的SQL语句。
MyBatis配置文件:在MyBatis的全局配置文件中,需要指定mapper接口和对应的XML映射文件。
SqlSessionFactory:MyBatis通过
SqlSessionFactory
创建SqlSession
对象,它包含了执行映射操作的方法。动态代理:当调用mapper接口的方法时,MyBatis使用动态代理机制来拦截这些方法调用。
它创建mapper接口的代理对象,当调用接口中的方法时,代理对象会根据XML映射文件中的配置找到对应的SQL语句并执行。
1.3.6 Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的?
- XML:Spring 容器使用 BeanFactory 或 ApplicationContext 解析 XML 配置,并根据配置创建 Bean 实例及其依赖。
- 注解:Spring 容器通过扫描类路径中的注解,生成相应的 BeanDefinition,并自动进行 Bean 的创建和注入。
两种方式,Spring 容器底层都会生成
BeanDefinition
对象,依据这些定义来创建和管理 Bean 实例。
1.3.7 MyBatis 中使用 # 和 $ 有什么区别?
- 使用#是防止SQL注入的推荐方式,因为它能够自动处理参数的转义。
- 例如:
SELECT * FROM users WHERE username = '#{username}'
,#{username} 是一个参数占位符。- 使用$不会将用户输入直接插入到SQL语句中,需要对参数值进行严格的验证和清理,以避免SQL注入攻击。
- 例如:
SELECT * FROM users WHERE username = '${username}'
,${username} 为 ' OR '1'='1 会导致SQL注入攻击。
1.3.8 介绍一下 mybatis plus?
简化开发,内置 CRUD、分页查询、条件构造器 Wrapper、代码生成器等。