余声-个人博客


  • 首页

  • 分类

  • 归档

  • 标签

👌线上问题处理?

发表于 2025-04-11 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

👌线上问题处理方法论

方法论都是精华,希望各位鸡友们,理解其中深邃之处。

当产生线上问题的时候,如果没有一套方法处理机制,很容易就陷入慌乱,思路不清晰,会导致损失越来越大,反馈越来越多,耗时越老越长。基于经验,可以沉淀出一些操作步骤。

问题发生反馈

当收到问题发生反馈的时候,第一个思路!!!止损,止损,止损!可能工作经验少,或者没有工作经验的人体会不到。记住,不是去追究问题是什么原理发生的,问题是谁造成的,就一个目地,任何方案,快速止损,立马恢复系统。涉及金钱、损失之类的线上问题,立马向上升级,切忌闷头自己处理,不上报。老板们的资源比你多,经验多,说不定可以快速帮到你!

立马根据自己经验评估,是否非常熟悉,可快速解决,如果不可以,立马拉群,寻求其他小伙伴帮助!

在止损的过程中,尽可能保留线上的证据,比如日志、dump、异常流量情况 等等。方便后面复盘。

心态稳住,事情已经发生,我们在这个阶段要做的是解决问题,而不是思考之后会不会挨骂,等等等。心态要平和,专注到当前事件的解决上。

如果你是旁观者,或者来帮忙的,不要打扰主解决者的思路,问一些奇奇怪怪的,不了解情况的问题。

问题定位思路

收集问题产生现象,定位入口,定位日志,定位报错信息。

收集要保证尽可能的多汲取信息,不仅仅是日志,还可能是用户的表达,用户的操作行为,手机机型,网络等等。

第一反应:快速确认今天是否有上线操作影响。如果有立马切流量,或回滚。

开始观察日志分析,确认实际的报错信息。

观察报错后,依据自身经验开始排查,是否是调用量暴增,还是有明显的报错日志。抓取错误流量参数。服务器是否正常。

基于以上,联想问题点。

检查流量问题,服务调用异常问题,网络问题,服务器问题,数据库问题等等。

同时测试同学快速复现,看是否可以产生其他有价值的信息。

最终综合多信息找到异常点,定位故障。一定要不慌,保证准确的同时,迅速!

问题后复盘

评估影响级别,用户量,gmv 受损等等业务重视的指标。

基于规则,来判定,p0,p1,p2 等等各级别的事故级别。

同时认真仔细的回顾当时问题产生的时候,大家的状态和操作是否变形,找到可以优化的地方,比如有一个什么角色去并行做什么东西可以提高整体的效率等等。

复盘不是为了追责,喷人,只是为了让大家都知悉问题发生的过程,后续不再踩坑。
比如后续的报警措施啊,异常处理措施啊,优化思路啊,这次排查效率还有什么要优化的呀。沉淀思路啊。知识库沉淀,问题归档。

同时分析出问题产生的根本原因。开发阶段不行还是测试不行,还是产品流程问题,还是上线问题!

同时查看其他需求是否也有此问题。

铭记

海恩法则,也被称为海恩法则或海因里希法则,是由德国飞机涡轮机的发明者帕布斯·海恩提出的。这一法则主要应用于航空界,强调飞行安全,但也被广泛应用于企业生产管理、安全管理和人力资源管理等领域。海恩法则的核心思想是,每一起严重事故的背后,必然有29次轻微事故和300起未遂先兆以及1000起事故隐患。

慢sql优化方向

发表于 2025-04-10 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

慢sql优化方向?

详细解读

1. 优化查询语句

使用适当的索引

  • 创建索引:确保查询使用了适当的索引。对频繁出现在WHERE、JOIN、ORDER BY和GROUP BY子句中的列创建索引。
1
CREATE INDEX idx_column_name ON table_name(column_name);
  • 复合索引:对于多列查询,考虑使用复合索引(多列索引)。
1
CREATE INDEX idx_columns ON table_name(column1, column2);

避免全表扫描

  • 使用合适的过滤条件:确保WHERE子句中的条件能够有效地利用索引,避免全表扫描。
1
SELECT*FROM table_name WHERE indexed_column ='value';
  • 限制返回的行数:使用LIMIT子句限制返回的行数,减少数据库的负担。
1
SELECT*FROM table_name WHEREcondition LIMIT 10;

优化JOIN操作

  • 使用小表驱动大表:在JOIN操作中,确保小表在前,大表在后。
1
SELECT*FROM small_table ST JOIN large_table LT ON ST.id = LT.id;
  • 索引连接列:确保连接列上有索引,以加快JOIN操作。

避免不必要的复杂查询

  • 简化查询:尽量简化查询,避免使用不必要的子查询和嵌套查询。
1
2
-- 避免复杂的嵌套查询SELECT*FROM table_name WHERE id IN (SELECT id FROM another_table WHEREcondition);
-- 使用JOIN替代SELECT table_name.*FROM table_name JOIN another_table ON table_name.id = another_table.id WHERE another_table.condition;

2. 优化数据库设计

规范化与反规范化

  • 规范化:确保数据库设计符合第三范式,减少数据冗余。
  • 反规范化:在某些情况下,为了性能,可以适度反规范化,减少复杂的JOIN操作。

分区表

  • 表分区:对于非常大的表,可以使用表分区,将数据分成更小的部分,提高查询性能。
1
2
3
4
5
6
7
8
9
CREATETABLE orders (
order_id INT,
order_date DATE,
...
)PARTITIONBYRANGE (YEAR(order_date)) (
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022)
);

3. 优化服务器配置

调整 MySQL 配置参数

  • 调整缓冲池大小:对于 InnoDB 存储引擎,调整innodb_buffer_pool_size参数,使其尽量大(但不要超过物理内存的 70-80%)。
1
[mysqld]innodb_buffer_pool_size = 4G
  • 调整查询缓存:根据应用需求,调整查询缓存大小。
1
[mysqld]query_cache_size = 64M

使用合适的存储引擎

  • 选择适当的存储引擎:根据应用需求选择合适的存储引擎(如 InnoDB、MyISAM)。

4. 使用性能分析工具

使用EXPLAIN分析查询

  • 分析执行计划:使用EXPLAIN分析查询的执行计划,识别性能瓶颈。
1
EXPLAIN SELECT*FROM your_table WHERE your_condition;

使用性能模式(Performance Schema)

  • 收集性能数据:使用 Performance Schema 收集详细的性能数据,分析慢查询。
1
2
SELECT*FROM performance_schema.events_statements_summary_by_digestORDERBY SUM_TIMER_WAIT DESC
LIMIT 10;

5. 监控和调优

持续监控

  • 使用监控工具:使用 MySQL Enterprise Monitor、Percona Monitoring and Management (PMM)、New Relic、Datadog 等工具持续监控数据库性能。

定期调优

  • 定期审查查询:定期审查和优化慢查询,确保数据库性能持续提升。

👌如何快速熟悉一个复杂的系统

发表于 2025-04-09 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

👌如何快速熟悉一个复杂的系统?

这个问题,是一道开放性的问题,很多小伙伴在面试的时候,尤其是一些高级的岗位,会被面试官问到,其实像这种问题,面试官的目的就是看你对于此是否有一定的方法论沉淀,如果平时只是一直在写crud,那么对于这种问题,一定是没有思考过的。通过此也能筛选出一些人的区分。

鸡翅老哥曾经做过好几次的大型项目的系统交接,每次交接都是对于一个系统从陌生到熟悉的过程,要进行快速掌控的过程。在这个过程中,你会遇到文档资料不全,原交接人可能直接离职了,找不到人。交接人没时间讲解业务等等等难受的问题,这个时候都是要靠自己来花时间处理。今天鸡翅老哥给大家沉淀一些方法论。

本篇文章的背景,我们就以一个背景来展开描述:一个大型的电商项目进行交接,你是其中的交接负责人,你该如何做?以及如何快速熟悉系统。

盘资源,盘权限

在进行系统交接的时候,我们一定要做好的一件事就是和原来的团队,确认当前大项目所需要的各种清单,例如代码库,数据库资源,redis资源等中间件情况,网关域名资源等等等。这个时候要建立表格,确保交接人和被交接人,一项一项的都要进行check,防止遗漏,不然交接完之后,再发现缺东西,那就直接难受了。

盘优先级

一个大型的项目是由很多子系统组成的,我见过最多的是100多个微服务应用。如果这么多的应用,全部由团队来进行熟悉,那就累到死了,一定要知道哪些是核心应用,哪些是非核心,我们可以编号,p0,p1,p2这种,这个过程和原来的团队要做好沟通,也为后续的熟悉做好基础。

盘业务

从看的见,摸得着的层面盘清之后,就可以开始盘业务,这个过程要依靠原有团队的讲解,以及沉淀的业务文档来进行学习和梳理。如果二者都没有,那么恭喜,步入地狱难度,需要自己通过体验产品,来划分出模块,盘出整个业务,这个地方也是有技巧,首先要划清用例者,用户身份,运营身份,等等各种身份,基于身份的形式,体验产品的整个流程并形成自己的认知业务文档。

盘技术栈

从这个地方开始就是熟悉系统的流程,先去熟悉技术栈,都是采用的什么语言,什么中间件,什么开源,基于什么样的形式进行交互,这个过程从核心系统开始,看pom文件,配置文件来了解其中的技术点。比如看到mq,大概就能猜到项目里面有异步的数据流交互,看到es,猜测这个应用是具备大数据量的处理和查询。

盘入口

什么叫入口呢,controller,对外提供的rpc,http能力,定时任务,这些都是入口,从入口来入手业务逻辑是最快的一种方式,所以先找项目的统一对外出口,不然直接看内部的逻辑,是会发懵的,比如从一个controller开始看起,就知道了这个项目对外提供的是什么模块,什么功能。方便我们去理解。

盘数据流

有了入口之后,其实我们已经大概知道该系统提供的能力是什么。在通过入口进行再深一层的看,就知道数据的流向是什么情况,在这个过程中,其实可以初步梳理出一个功能整体的系统交互和数据流向。这也是最耗时间的一个环节。

盘核心和易出问题的点

我们一个大项目的交接会有很多项目,如果挨个都看,不分优先级的去看,是很耗费时间和精力的,所以按照核心应用开始,以及找出平时容易出问题的点的应用,这种的收益是最大的。时间是宝贵的,我们也应该从这方面来进行熟悉。

盘细节

细节就不用说了,有了上面的这些铺垫之后,接下来要做的就是一个模块一个模块的整体的代码逻辑梳理,切忌每一行代码都看,要看核心的,简单的地方,了解数据流的大概即可。这样我们可以快速交接项目。

👌如何监控慢sql?

发表于 2025-04-09 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

👌如何监控慢sql?

1. 启用慢查询日志

慢查询日志是 MySQL 内置的一种功能,用于记录执行时间超过指定阈值的 SQL 查询。

配置 MySQL 配置文件(my.cnf 或 my.ini)

在 MySQL 配置文件中添加或修改以下参数:

1
2
3
4
5
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2
log_queries_not_using_indexes = 1
  • slow_query_log:启用慢查询日志。
  • slow_query_log_file:指定慢查询日志文件的路径。
  • long_query_time:定义慢查询的阈值(单位:秒)。
  • log_queries_not_using_indexes:记录未使用索引的查询。

重启 MySQL 服务

修改配置文件后,重启 MySQL 服务以使配置生效:

1
sudo systemctl restart mysql

2. 使用 MySQL 内置的性能模式(Performance Schema)

Performance Schema 是 MySQL 内置的一个工具,用于收集数据库内部的运行时统计信息。可以通过以下步骤启用和使用 Performance Schema:

启用 Performance Schema

在 MySQL 配置文件中添加或修改以下参数:

1
2
[mysqld]
performance_schema = ON

重启 MySQL 服务

修改配置文件后,重启 MySQL 服务以使配置生效:

1
sudo systemctl restart mysql

查询慢查询信息

使用以下 SQL 语句查询慢查询信息:

1
2
SELECT*FROM performance_schema.events_statements_summary_by_digestORDERBY SUM_TIMER_WAIT DESC
LIMIT 10;

3. 使用 MySQL 企业监控工具

MySQL 提供了企业版的监控工具,如 MySQL Enterprise Monitor,它可以自动收集、分析和报告慢查询信息。这个工具适合企业环境下的全面监控和管理。

4. 使用第三方监控工具

有许多第三方监控工具可以帮助你监控 MySQL 的性能,包括慢查询。这些工具通常提供更丰富的功能和更友好的界面。常见的第三方工具包括:

  • **Percona Monitoring and Management (PMM)**:一个开源的监控和管理工具,专为 MySQL 和 MongoDB 设计。
  • New Relic:一个强大的应用性能监控工具,支持 MySQL 和其他数据库。
  • Datadog:一个综合性的监控平台,支持 MySQL 和其他数据库。

5. 使用 SQL 分析工具

MySQL 提供了EXPLAIN语句,用于分析 SQL 查询的执行计划。通过分析执行计划,可以识别和优化慢查询。

使用EXPLAIN分析查询

1
EXPLAIN SELECT*FROM your_table WHERE your_condition;

EXPLAIN的输出结果包含了查询执行的详细信息,包括使用的索引、扫描的行数等。通过分析这些信息,可以找出查询的性能瓶颈并进行优化。

总结

监控慢 SQL 是一个持续的过程,需要结合多种工具和方法。以下是一个综合的监控策略:

  1. 启用慢查询日志:记录执行时间超过阈值的查询。
  2. 使用 Performance Schema:收集和分析详细的运行时统计信息。
  3. 使用企业监控工具:如 MySQL Enterprise Monitor,进行全面的监控和管理。
  4. 使用第三方监控工具:如 Percona Monitoring and Management (PMM)、New Relic、Datadog 等。
  5. 使用 SQL 分析工具:如EXPLAIN,分析和优化查询执行计划。

👌如何设计好一个接口?

发表于 2025-04-09 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

👌如何设计好一个接口?

这个问题是非常常见的一个问题。无论是在日常写代码的时候,还是实际面试的时候!

说实话,面试过很多人,上来一问,就是一句,没啥可以设计的,能正常使用查询即可,这种我都直接给他 pass 掉。

从设计接口的角度,主要需要遵循几个规范:

接口命名

命名的时候,别在那瞎搞,瞎起,不断增加后面人的理解成本,一定要见名知意。清晰,遵循开发规范。常见的规范可以直接参考阿里巴巴开发规范。不要觉得这些东西很麻烦,对于有经验的开发人员,大家遵守相同的规范,可以快速入手,一下子就明白,高手过招,无需多言。

接口设计的时候,遵循单一指责,不要做什么大而全的,比如下单并支付,千万别这么设计,createOrderAndPay。一定要做拆分,createOrder。payOrder。职责单一。

参数校验

好的接口,入参和出参的校验,一定要做好处理!参数前置校验做的好,在后面的核心业务代码,可以节省很多时间,核心业务代码,可以专注的处理业务逻辑。大家在开发过程中,有很多的时候,参数校验没做好,到业务代码,空指针等等问题,这些都是可以提前规避的。

对于出参的校验,做好错误码的定义。定义好具体的错误文案对应。要做友好的展示。不要提示参数错误,而是要提示具体的参数问题。

日志打印

关键日志必须打印打印打印!!!!经常有问题的时候,因为关键日志不打印,导致排查和理解的成本特别高,方法的入参和出参的日志可以通过拦截器来进行处理。

然后就是核心代码的关键日志。一定要打,比如经历了一些复杂逻辑的转换,关键的步骤打印日志。

打印日志要注意级别。error 、warn 、info 、debug。

公共包的,经历过大规模考验的 debug 即可。

普通业务就是 info。要注意,大 json 转换,加 isinfo 的判断,防止影响性能。

error 不要乱打,真错误的时候再打。蛇打七寸。

性能设计

对于核心接口设计的时候,必须有监控和异常的报警机制。tp99,qps,可用率。这些都要看到。

要考虑调用第三方接口,第三方的性能会不会影响自己,第三方失败的时候,如何进行处理。重试还是失败等等。

要不要加缓存,提升性能,要不要用多线程提升性能。要不要幂等,数据库压力等等都要考虑。

异常处理

一定要考虑接口的各种异常情况,应该产生的相关行为。要尽量预知做处理。不要产生不可控情况。相关校验做好。还有就是常见的你提供的接口的超时问题,要做好超时事件设置,自身也可以进行快速熔断返回。自身接口超时时间要小于上游接口。

注释

都给鸡哥写注释,别给鸡哥在这防御性编程,不写注释是一个非常坏的习惯!!到时候,你自己回过头可能都看不懂了。

限流/熔断

自己的接口要考虑好下下策,如果大流量来了,我们的服务是否能支撑的住,提前最好评估和限制调用方流量,防止自己什么都吃,导致服务整体直接不可用。极端情况也可以做熔断和快速失败,总之就是要保护好自己!

接口文档

写个清晰的接口文档,可以减少很多不必要的沟通和交流,一个好的接口文档,调用方的人,一看就懂,自然就不用浪费你的时间,教他传什么了。这种可以极大的减少沟通成本。

分布式锁切面(DistributeLockAspect)代码解读

发表于 2025-03-22 | 更新于 2025-09-14 | 分类于 分布式
字数统计 | 阅读时长

分布式锁切面(DistributeLockAspect)代码解读

  • 这是一个基于Spring AOP和Redisson实现的分布式锁切面,用于在方法执行前获取分布式锁,执行后释放锁。下面我将从多个维度详细解析这段代码:

一、核心功能概述

该切面主要实现以下功能:

  • 通过@DistributeLock注解标记需要加锁的方法

  • 支持动态生成锁的key(使用SpEL表达式)

  • 提供多种加锁策略(立即加锁、尝试加锁、带过期时间的锁)

  • 确保锁的最终释放(在finally块中)

二、代码结构解析

  1. 类定义与依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Aspect
    @Component
    @Order(Integer.MIN_VALUE) // 确保最先执行
    public class DistributeLockAspect {
    private RedissonClient redissonClient; // Redisson客户端

    // 构造函数注入
    public DistributeLockAspect(RedissonClient redissonClient) {
    this.redissonClient = redissonClient;
    }
    }
    @Aspect:声明这是一个切面类

    @Order(Integer.MIN_VALUE):确保这个切面在调用链中最早执行

    通过构造函数注入RedissonClient,用于操作分布式锁
  2. 核心切面方法

    1
    2
    3
    4
    5
    @Around("@annotation(cn.hollis.nft.turbo.lock.DistributeLock)")
    public Object process(ProceedingJoinPoint pjp) throws Exception
    使用@Around注解拦截所有带有@DistributeLock注解的方法

    ProceedingJoinPoint参数可以获取被拦截方法的信息

三、关键实现逻辑

  1. 锁Key的生成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    String key = distributeLock.key();
    if (DistributeLockConstant.NONE_KEY.equals(key)) {
    // 使用SpEL表达式动态生成key
    SpelExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression(distributeLock.keyExpression());
    // 设置上下文变量
    EvaluationContext context = new StandardEvaluationContext();
    Object[] args = pjp.getArgs();
    String[] parameterNames = discoverer.getParameterNames(method);
    // 绑定参数到上下文
    for (int i = 0; i < parameterNames.length; i++) {
    context.setVariable(parameterNames[i], args[i]);
    }
    key = String.valueOf(expression.getValue(context));
    }
    String scene = distributeLock.scene();
    String lockKey = scene + "#" + key; // 最终锁key

支持静态key和动态key两种方式

动态key使用SpEL表达式,可以从方法参数中取值

最终key格式为scene#key,便于分类管理

  1. 加锁逻辑
    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
    RLock rLock= redissonClient.getLock(lockKey);
    try {
    boolean lockResult = false;
    if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
    // 立即加锁逻辑
    if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
    rLock.lock(); // 永久锁
    } else {
    rLock.lock(expireTime, TimeUnit.MILLISECONDS); // 带过期时间的锁
    }
    lockResult = true;
    } else {
    // 尝试加锁逻辑
    if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
    lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
    } else {
    lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
    }
    }

    if (!lockResult) {
    throw new DistributeLockException("acquire lock failed... key : " + lockKey);
    }

    // 执行目标方法
    response = pjp.proceed();
    } finally {
    // 释放锁
    if (rLock.isHeldByCurrentThread()) {
    rLock.unlock();
    }
    }

支持四种加锁模式:

  • 永久锁(无过期时间)

  • 带过期时间的锁

  • 尝试获取锁(带等待时间)

  • 尝试获取带过期时间的锁(带等待时间和过期时间)

确保锁最终会被释放(finally块)

四、设计亮点

灵活的锁Key生成:支持静态key和SpEL动态key

多种加锁策略:满足不同业务场景需求

线程安全:确保只有持有锁的线程才能释放锁

日志完备:记录加锁、释放锁的关键信息

异常处理:加锁失败抛出特定异常

五、使用示例

1
2
3
4
5
6
7
8
9
10
11
@Service
public class OrderService {
@DistributeLock(
scene = "order",
keyExpression = "#orderId", // 使用SpEL从参数获取值
expireTime = 5000
)
public void processOrder(String orderId) {
// 业务逻辑
}
}

六、改进建议

锁的可重入性:Redisson的RLock本身支持可重入,但可以增加重入计数日志

锁的自动续期:对于长时间任务,可以实现锁的自动续期

更细粒度的异常处理:区分业务异常和锁相关的异常

性能监控:添加锁获取时间、持有时间等监控指标

这个切面实现了一个完整且健壮的分布式锁解决方案,可以很好地应用于需要分布式锁的业务场景中。

什么是TCC(Try-Confirm/Cancel)模式?

发表于 2025-03-16 | 更新于 2025-09-14 | 分类于 分布式
字数统计 | 阅读时长

什么是TCC(Try-Confirm/Cancel)模式?

TCC(Try-Confirm/Cancel)模式是一种分布式事务管理模式,用于确保分布式系统中的数据一致性。它通过将事务分解为三个阶段:尝试(Try)、确认(Confirm)和取消(Cancel),来实现分布式事务的管理和控制。

TCC模式的三个阶段

  1. 尝试(Try):
    • 尝试阶段是预留资源或锁定资源的阶段。
    • 该阶段确保所有需要的资源都可以被成功预留,以便后续的确认操作能够顺利进行。
    • 这一步不会真正提交事务,只是做预留操作。
  2. 确认(Confirm):
    • 确认阶段是实际提交事务的阶段。
    • 在这个阶段,系统会确认并提交所有的操作,确保数据的最终一致性。
    • 如果尝试阶段成功,确认阶段应该始终成功。
  3. 取消(Cancel):
    • 取消阶段是回滚事务的阶段。
    • 如果尝试阶段失败,或者确认阶段未能执行,系统会执行取消操作来释放预留的资源或回滚已经进行的操作。
    • 这一步确保系统能够恢复到尝试阶段之前的状态。

TCC模式的工作流程

  1. 尝试阶段:
    • 各个服务预留资源或锁定资源。
    • 如果所有服务的尝试操作都成功,则进入确认阶段;否则,进入取消阶段。
  2. 确认阶段:
    • 各个服务正式提交事务,完成实际操作。
    • 确认阶段的操作应当是幂等的(即多次执行结果相同)。
  3. 取消阶段:
    • 如果尝试阶段中的任何一个操作失败,或者确认阶段未能成功执行,系统会调用取消操作来回滚事务。
    • 取消阶段的操作也应当是幂等的。

TCC模式的优缺点

优点:

  1. 强一致性:通过明确的三阶段操作,确保了分布式事务的强一致性。
  2. 灵活性:可以根据业务需求灵活定义每个阶段的操作。
  3. 幂等性:由于确认和取消操作需要幂等性,确保了系统的稳定性和可靠性。

缺点:

  1. 复杂性:需要为每个操作定义对应的尝试、确认和取消操作,增加了系统设计和实现的复杂性。
  2. 资源占用:尝试阶段需要预留资源,可能会导致资源占用时间较长。
  3. 实现成本:需要确保每个阶段的操作都是幂等的,增加了开发和维护成本。

👌解释一下 Spring 的 ioc 控制反转

发表于 2025-03-10 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌解释一下 Spring 的 ioc 控制反转?

口语化回答

好的,面试官,控制反转通过将对象的创建和依赖关系的管理交给Spring IoC容器,极大地提高了代码的模块化和可维护性。IoC的主要实现方式是依赖注入,其中通过构造函数注入、Setter方法注入和字段注入等形式来注入,这样 Spring容器能够自动管理对象的依赖关系,使得应用程序代码更加简洁。以上。

题目解析

重点高频题。主要思路就是聊 ioc 是什么,再说说 di 的形式,最后说一下好处即可。

面试得分点

反转,解耦,注入

题目详细答案

什么是控制反转(IoC)?

在传统的编程模型中,应用程序代码通常直接控制对象的创建和依赖关系。例如,一个对象需要依赖另一个对象时,通常会在代码中直接创建依赖对象。这种方式使得代码紧密耦合,不利于测试和维护。

控制反转的理念是将这种控制权从应用程序代码中移除,转而交给一个容器来管理。这个容器就是Spring IoC容器。通过这种方式,对象的创建和依赖关系的管理被反转了,应用程序代码不再负责这些任务,而是由容器来处理。

依赖注入(DI)

依赖注入是实现控制反转的一种方式。它主要有以下几种形式:

构造函数注入:通过构造函数将依赖对象传递给被依赖对象。

1
2
3
4
5
6
7
public class Service {
private final Repository repository;

public Service(Repository repository) {
this.repository = repository;
}
}

Setter方法注入:通过Setter方法将依赖对象注入到被依赖对象中。

1
2
3
4
5
6
7
public class Service {
private Repository repository;

public void setRepository(Repository repository) {
this.repository = repository;
}
}

字段注入:直接在字段上使用注解进行注入。

1
2
3
4
public class Service {
@Autowired
private Repository repository;
}

Spring IoC 容器

Spring IoC容器负责管理应用程序中对象的生命周期和依赖关系。它的主要职责包括:

对象的创建:根据配置文件或注解创建对象。

依赖注入:将对象的依赖注入到相应的对象中。

对象的销毁:在适当的时候销毁对象,释放资源。

配置方式

Spring IoC容器可以通过多种方式进行配置:

XML配置:通过XML文件定义Bean及其依赖关系。

1
2
3
4
5
6
<beans>
<bean id="repository" class="com.example.Repository"/>
<bean id="service" class="com.example.Service">
<constructor-arg ref="repository"/>
</bean>
</beans>

Java配置:通过Java类和注解定义Bean及其依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class AppConfig {
@Bean
public Repository repository() {
return new Repository();
}

@Bean
public Service service() {
return new Service(repository());
}
}

注解配置:通过注解(如@Component,@Autowired)自动扫描和注入Bean。

1
2
3
4
5
6
7
8
9
@Component
public class Repository {
}

@Component
public class Service {
@Autowired
private Repository repository;
}

👌什么是动态代理

发表于 2025-03-09 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌什么是动态代理

口语化回答

好的,面试官。动态代理主要是在我们的程序运行时动态生成一种代理类的机制,让我们在不修改原始类的情况下,可以帮助原始类增加一些功能。通常有两种方式,一种是 jdk 的 proxy,一种是 cglib。动态代理在 spring 框架中的应用也非常常见。主要的 aop 机制就是通过动态代理实现。那么动态代理主要的优势就是解耦,减少代码重复,同时增强现有代码。以上。

题目解析

问到这道题的时候,大家一定要 cglib 和 spring 框架上面靠一靠,去埋点,让面试官继续追问下面的题目。

面试得分点

增强行为,动态代理过程,减少重复代码

题目详细答案

动态代理是一种在运行时动态生成代理类的机制,它允许我们在不修改原始类的情况下增强或修改其行为。动态代理是通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现的。

动态代理的基本概念

代理类(Proxy Class):一个代理类是一个实现了一个或多个接口的类,它可以在运行时动态生成。代理类的实例可以用来代替原始对象,并在调用方法时执行额外的逻辑。

调用处理器(Invocation Handler):调用处理器是一个实现了InvocationHandler接口的类,它定义了代理类的方法调用逻辑。每次代理对象的方法被调用时,调用处理器的invoke方法都会被执行。

动态代理的作用和优势

1、 解耦业务逻辑和通用功能:动态代理允许将通用功能(如日志记录、事务管理、安全性检查等)从业务逻辑中分离出来,从而提高代码的模块化和可维护性。

2、 灵活性:动态代理在运行时生成代理类,不需要在编译时确定代理类,因此具有很大的灵活性。

3、 减少代码重复:通过动态代理,可以将通用功能集中到一个地方,从而减少代码重复。

4、 增强现有代码:动态代理允许在不修改现有代码的情况下增强其功能。

动态代理的应用场景

1、AOP(面向切面编程):动态代理是实现 AOP 的核心技术之一,通过动态代理可以在方法执行前后添加横切关注点(如日志记录、事务管理等)。

2、 远程方法调用(RMI):动态代理可以用来实现客户端和服务器之间的远程方法调用。

3、 装饰器模式:动态代理可以用来实现装饰器模式,在不修改原始类的情况下增强其功能。

4、 框架和中间件:许多框架和中间件(如 Spring、Hibernate 等)都使用动态代理来实现其核心功能。

动态代理通过在运行时生成代理类,提供了一种灵活且强大的方式来增强现有代码的功能,而无需修改原始代码。

👌Spring事务中的隔离级别有哪几种?

发表于 2025-03-06 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌Spring事务中的隔离级别有哪几种?

题目详细答案

在 Spring 中,事务的隔离级别(Isolation Level)定义了一个事务与其他事务隔离的程度。隔离级别控制着事务在并发访问数据库时的行为,特别是如何处理多个事务同时读取和修改数据的情况。不同的隔离级别提供了不同的并发性和数据一致性保证。

Spring 提供了以下几种隔离级别,通过@Transactional注解的isolation属性来配置:

DEFAULT:使用底层数据库的默认隔离级别。通常情况下,这个默认值是READ_COMMITTED。

READ_UNCOMMITTED:允许一个事务读取另一个事务尚未提交的数据。可能会导致脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)问题。

READ_COMMITTED:保证一个事务只能读取另一个事务已经提交的数据。可以防止脏读,但可能会导致不可重复读和幻读问题。

REPEATABLE_READ:保证一个事务在读取数据时不会看到其他事务对该数据的修改。可以防止脏读和不可重复读,但可能会导致幻读问题。

SERIALIZABLE:最高的隔离级别。保证事务按顺序执行,完全隔离。可以防止脏读、不可重复读和幻读问题,但并发性最低,可能导致性能下降。

代码 Demo

假设有一个服务类MyService,可以通过不同的隔离级别来配置事务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class MyService {

@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommittedMethod() {
// 数据库操作
}

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void repeatableReadMethod() {
// 数据库操作
}

@Transactional(isolation = Isolation.SERIALIZABLE)
public void serializableMethod() {
// 数据库操作
}
}

隔离级别的选择

选择合适的隔离级别取决于具体的业务需求和对并发性和数据一致性的要求:

READ_UNCOMMITTED:适用于对数据一致性要求不高,且需要最高并发性的场景。

READ_COMMITTED:适用于大多数应用,能够防止脏读,提供较好的并发性和数据一致性平衡。

REPEATABLE_READ:适用于需要防止脏读和不可重复读,但可以容忍幻读的场景。

SERIALIZABLE:适用于对数据一致性要求极高的场景,尽管会牺牲并发性。

👌Spring事务传播行为

发表于 2025-03-05 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌Spring事务传播行为

口语化回答

好的,面试官,传播行为主要有几种,REQUIRED,如果当前存在事务,则加入,否则创建一个新的。这是默认的事务传播行为。还有就是其他的不常用的事务形式,比如REQUIRES_NEW,总是创建一个新的事务。如果当前存在事务,则挂起当前事务。SUPPORTS总是创建一个新的事务。使用方式就是@Transactional 里面的propagation 属性进行直接指定即可。其他不常见的就不多提了,就是围绕有事务,无事务,嵌套,以及异常来分不同情况选择事务创建,以上。

题目解析

问的比较少,大家就记住前两种就 ok 了。如果全部理解的口诀,就是,有用,没有创建,异常建,没则抛异常。

面试得分点

required,Transactional

题目详细答案

Spring 事务传播行为定义了在事务方法被调用时,事务如何传播。Spring 提供了多种传播行为,允许开发者定义事务的边界和行为。这些传播行为通过@Transactional注解的propagation属性来配置。

传播行为类型

REQUIRED(默认值):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

REQUIRES_NEW:总是创建一个新的事务。如果当前存在事务,则挂起当前事务。

SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续执行。

NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则挂起当前事务。

MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

NEVER:总是以非事务方式执行,如果当前存在事务,则抛出异常。

NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。

代码 Demo

假设有两个服务类ServiceA和ServiceB,可以通过不同的传播行为来控制事务的传播。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class ServiceA {

@Autowired
private ServiceB serviceB;

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// Some database operations
serviceB.methodB();
// Some more database operations
}
}

@Service
public class ServiceB {

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// Some database operations
}
}

ServiceA的methodA方法使用REQUIRED传播行为,这意味着如果methodA被调用时没有事务存在,它将创建一个新的事务。如果methodA被调用时已经有一个事务存在,它将加入该事务。

ServiceB的methodB方法使用REQUIRES_NEW传播行为,这意味着它总是会创建一个新的事务。如果methodB被调用时已经有一个事务存在,该事务将被挂起,直到methodB的事务完成。

事务传播行为的选择

REQUIRED:大多数情况下使用的默认传播行为,适用于大多数需要事务管理的方法。

REQUIRES_NEW:适用于需要独立事务的情况,例如记录日志、审计等操作,即使外层事务回滚,这些操作也应该提交。

SUPPORTS:适用于可选事务的情况,例如读取操作,可以在事务内或事务外执行。

NOT_SUPPORTED:适用于不需要事务的情况,例如调用外部服务。

MANDATORY:适用于必须在事务内执行的方法,例如严格依赖事务上下文的操作。

NEVER:适用于必须在非事务上下文中执行的方法。

NESTED:适用于需要嵌套事务的情况,例如需要在一个事务内执行多个子事务,并且可以单独回滚子事务。

👌Spring 实例化 bean 有哪几种方式?

发表于 2025-03-04 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌Spring 实例化 bean 有哪几种方式?

口语化回答

好的,面试官。实例化 bean 的方式,主要有构造器实例化,bean 注解实例化,不常用的用静态工厂和实例工厂实例化。构造器实例化主要是通过调用类的构造器来实例化 Bean。构造器可以是无参构造器,也可以是有参构造器。配合 bean 标签的 xml 配置形式,放入容器中。bean 注解的方式就更加常用,尤其是现在都是 boot 的形式。一个注解就可以放入容器中。以上。

题目解析

这道题也很常问,同样的还是考察是否平时在学习中用过。应届生比较常问,工作的小伙伴问的概率不大。

面试得分点

构造器实例化,bean 方式,工厂方式

题目详细答案

通过构造器实例化

这是最常见的方式之一,Spring 可以通过调用类的构造器来实例化 Bean。构造器可以是无参构造器,也可以是有参构造器。

无参构造器

1
2
3
4
5
public class MyBean {
public MyBean() {
// 无参构造器
}
}

配置方式:

1
<bean id="myBean" class="com.example.MyBean"/>

有参构造器

1
2
3
4
5
6
7
public class MyBean {
private String name;

public MyBean(String name) {
this.name = name;
}
}

配置方式:

1
2
3
<bean id="myBean" class="com.example.MyBean">
<constructor-arg value="exampleName"/>
</bean>

通过静态工厂方法实例化

Spring 可以通过调用静态工厂方法来实例化 Bean。这种方式适用于需要复杂初始化逻辑的情况。

1
2
3
4
5
public class MyBeanFactory {
public static MyBean createInstance(String name) {
return new MyBean(name);
}
}

配置方式:

1
2
3
<bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance">
<constructor-arg value="exampleName"/>
</bean>

通过实例工厂方法实例化

这种方式类似于静态工厂方法,不同之处在于实例工厂方法需要先实例化工厂类,然后调用工厂类的实例方法来创建 Bean。

1
2
3
4
5
public class MyBeanFactory {
public MyBean createInstance(String name) {
return new MyBean(name);
}
}

配置方式:

1
2
3
4
<bean id="myBeanFactory" class="com.example.MyBeanFactory"/>
<bean id="myBean" factory-bean="myBeanFactory" factory-method="createInstance">
<constructor-arg value="exampleName"/>
</bean>

通过 FactoryBean 接口实例化

Spring 提供了一个FactoryBean接口,允许开发者定制 Bean 的创建逻辑。实现FactoryBean接口的类可以被用作工厂来生成其他 Bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyFactoryBean implements FactoryBean<MyBean> {
private String name;

public void setName(String name) {
this.name = name;
}

@Override
public MyBean getObject() throws Exception {
return new MyBean(name);
}

@Override
public Class<?> getObjectType() {
return MyBean.class;
}

@Override
public boolean isSingleton() {
return true;
}
}

配置方式:

1
2
3
4
<bean id="myFactoryBean" class="com.example.MyFactoryBean">
<property name="name" value="exampleName"/>
</bean>
<bean id="myBean" factory-bean="myFactoryBean" factory-method="getObject"/>

通过 @Bean 注解实例化

使用@Bean注解的方法可以用来实例化和配置 Bean。这种方式更加直观和灵活,适合基于 Java 配置的 Spring 应用。

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean("exampleName");
}
}

通过 @Component 注解实例化

使用@Component注解可以将类标记为 Spring 管理的 Bean。结合@ComponentScan注解,Spring 会自动扫描并实例化这些类。

1
2
3
4
@Component
public class MyBean {
// Bean 定义
}

配置方式:

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类
}

Autowired和@Resource的区别

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌@Autowired和@Resource的区别

口语化答案

好的,面试官,Autowired 和 Resource 都是依赖注入注解。一个是 spring 框架带的,一个是 javaee 框架带的。Autowired 主要是类型注入,Resource 是按照名称注入,名称找不到的话,会按照类型进行注入。Autowired 当存在多个的时候,可以配合Qualifier 来进行使用。一般在实际工作中比较常用 Resource。以上。

题目解析

常考题,面试官喜欢问,其实两者比较好区分,记住一个是类型,一个是名称即可。然后就是提供方的不同。

面试得分点

类型注入,名称注入,指定名称。

题目详细答案

@Autowired和@Resource都是用于依赖注入的注解。

@Autowired

来源:Spring 框架。

注入方式:默认按类型注入。

用法:可以用于字段、构造器、Setter 方法或其他任意方法。

可选性:可以与@Qualifier一起使用,以指定具体的 Bean。

处理机制:Spring 的AutowiredAnnotationBeanPostProcessor处理@Autowired注解。

@Resource

来源:由 Java EE 提供。

注入方式:默认按名称注入,如果按名称找不到,则按类型注入。

用法:可以用于字段或 Setter 方法。

属性:可以指定name和type属性。

处理机制:Spring 的CommonAnnotationBeanPostProcessor处理@Resource注解。

详细比较

  1. 注入方式:

@Autowired:默认按类型注入。如果需要按名称注入,可以结合@Qualifier注解使用。

@Resource:默认按名称注入。如果名称匹配失败,则按类型注入。

  1. 属性:

@Autowired:没有name和type属性,但可以使用@Qualifier指定名称。

@Resource:有name和type属性,可以明确指定要注入的 Bean 名称或类型。

  1. 兼容性:

@Autowired:是 Spring 框架特有的注解。

@Resource:是 Java 标准注解,兼容性更广,适用于任何支持 JSR-250 的容器。

  1. 使用场景:

@Autowired:在 Spring 应用中更为常见,尤其是在需要按类型注入的场景中。

@Resource:在需要兼容标准 Java EE 规范的应用中更为常见,或者在需要明确指定 Bean 名称时使用。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/bnn9a113lxo4uvpm

👌Rabbitmq如何避免消息丢失?

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 面试
字数统计 | 阅读时长

👌Rabbitmq如何避免消息丢失?

口语化回答

好的,面试官,rabbitmq 主要通过持久化机制,ack 确认机制,镜像等保证消息不丢失。rabbitmq 在创建队列的时候,可以设置为持久化队列,消息也可以设置为持久化消息,这样消息会被写入磁盘。即使重启,也不会产生丢失的问题。确认机制是我们平时最常见的机制,ack,消费者正常消费成功过消息后,会发出 ack 信号。mq 收到之后,才会认为消息成功消费,否则会认为消费失败,此时不会抛弃会让其他消费者继续进行消费。镜像队列是高可用的一种形式。可以确保一个节点挂了,另一个节点可以正常的存储消息,类似 redis 主从复制。以上。

消息持久化

1、 持久化队列:创建队列时,可以将其声明为持久化队列(durable)。持久化队列在 RabbitMQ 重启后依然存在。

1
channel.queueDeclare("queue_name", true, false, false, null);

2、 持久化消息:在发送消息时,可以将消息标记为持久化(persistent)。持久化消息会被写入磁盘,即使 RabbitMQ 重启,消息也不会丢失。

1
2
3
4
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 2 表示持久化消息
.build();
channel.basicPublish("exchange_name", "routing_key", props, messageBody);

消息确认机制(Acknowledgements)

1、 消费者确认:消费者处理完消息后,向 RabbitMQ 发送确认(ack)。如果消费者未确认消息(如消费者崩溃),RabbitMQ 会将消息重新投递给其他消费者。

1
2
3
4
5
// 自动确认关闭
channel.basicConsume("queue_name", false, consumer);

// 消费者处理完消息后手动确认
channel.basicAck(deliveryTag, false);

2、 发布者确认:发布者可以启用发布确认模式(publisher confirms),RabbitMQ 会在消息成功写入队列后发送确认给发布者。

1
2
3
channel.confirmSelect();
channel.basicPublish("exchange_name", "routing_key", null, messageBody);
channel.waitForConfirmsOrDie(); // 等待确认

事务支持

RabbitMQ 支持 AMQP 事务,可以在一个事务中发布多条消息,确保这些消息要么全部成功,要么全部失败。

1
2
3
channel.txSelect();
channel.basicPublish("exchange_name", "routing_key", null, messageBody);
channel.txCommit(); // 提交事务

镜像队列(Mirrored Queues)

RabbitMQ 的集群模式支持镜像队列(也称为高可用队列),可以将队列的数据复制到多个节点上,确保在一个节点故障时,其他节点仍然可以提供服务。

1
2
// 定义队列策略,将队列配置为镜像队列
rabbitmqctl set_policy ha-all "^queue_name$" '{"ha-mode":"all"}'

消费者重试机制

消费者可以实现重试机制,当消息处理失败时,可以将消息重新投递到队列或死信队列,避免消息丢失。

1
2
3
4
5
6
try {
// 处理消息
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
channel.basicNack(deliveryTag, false, true); // 重试
}

👌@Component和@Bean的区别是什么?

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌@Component和@Bean的区别是什么

口语化答案

好的,面试官。component 和 bean 是非常常用的将类注册到容器中的注解。component 需要配合@ComponentScan注解使用,Spring 会自动扫描指定包及其子包中的所有类,找到带有@Component注解的类,并将它们注册为 Bean。@Bean 标记在方法上,方法所在的类需要用@Configuration注解标记。不需要扫描,主动的注入到 spring 容器中。

题目解析

经典的一道问题。两种方式都在实际工作中非常常见的使用。考察平时常见的扫描方式和假设有一个 bean 比如第三方,想要加载到容器中,不是类的形式,应该如何去做。

面试得分点

自动扫描,ComponentScan,方法级别,@Configuration

题目详细答案

@Component

用途:用于将一个类标记为 Spring 组件类,使其被 Spring 容器自动扫描并注册为 Bean。

使用场景:通常用于标记那些需要自动检测和注册为 Bean 的类。

位置:直接标记在类上。

自动扫描:需要配合@ComponentScan注解使用,Spring 会自动扫描指定包及其子包中的所有类,找到带有@Component注解的类,并将它们注册为 Bean。

@Bean

用途:用于定义一个方法,该方法返回一个要注册为 Spring 容器管理的 Bean。

使用场景:通常用于显式定义 Bean,特别是当需要一些复杂的初始化逻辑或需要从第三方库创建 Bean 时。

位置:标记在方法上,方法所在的类需要用@Configuration注解标记。

显式配置:通过显式的 Java 配置方式定义 Bean,而不是通过类路径扫描。

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyComponent myComponent() {
return new MyComponent();
}
}

详细比较

定义方式:

@Component:用于类级别,通过类路径扫描自动检测和注册。

@Bean:用于方法级别,通过显式的 Java 配置注册。

使用场景:

@Component:适用于那些可以通过类路径扫描自动检测的类,通常是应用中的主要组件,如服务类、数据访问类等。

@Bean:适用于需要显式定义的 Bean,尤其是那些需要复杂初始化逻辑或从第三方库创建的 Bean。

配置方式:

@Component:需要配合@ComponentScan使用,Spring 会自动扫描并注册。

@Bean:需要在@Configuration类中定义,显式地通过 Java 配置注册。

灵活性:

@Component:更适合于常规的 Spring 组件,配置较为简单。

@Bean:提供了更高的灵活性,可以在方法中包含复杂的创建逻辑。

选择建议

使用@Component:当你的类是应用中的主要组件,并且可以通过类路径扫描自动检测和注册时。例如,服务类、数据访问类、控制器类等。

使用@Bean:当你需要显式定义 Bean,特别是那些需要复杂初始化逻辑或从第三方库创建的 Bean 时。例如,配置第三方库的 Bean、需要自定义初始化逻辑的 Bean 等。

代码 Demo

使用@Component和@ComponentScan:

1
2
3
4
5
6
7
8
9
10
@Component
public class MyComponent {
// 组件逻辑
}

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类内容
}

使用@Bean和@Configuration:

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyComponent myComponent() {
return new MyComponent();
}
}

AOP

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌Aop中的相关术语

口语化答案

哈哈,这个就不多说了,面试官问到什么概念,你就直接解释一下就可以了。

题目详细答案

切面(Aspect)

切面是 AOP 的核心概念之一,它是一个模块,包含了横切关注点的逻辑。切面可以看作是对某些功能(如日志记录、事务管理)的封装,这些功能会在特定的连接点上应用。

连接点(Join Point)

连接点是程序执行过程中可以插入切面的一个点。通常,连接点是方法的调用或执行。AOP 框架允许在这些连接点上插入额外的行为。

通知(Advice)

通知是在切面的某个特定的连接点上执行的动作。通知定义了切面在连接点上的具体行为。根据执行时间的不同,通知可以分为以下几种类型:

  • 前置通知(Before):在目标方法执行之前执行。
  • 后置通知(After):在目标方法执行之后执行(无论方法是否成功完成)。
  • 返回通知(After Returning):在目标方法成功返回之后执行。
  • 异常通知(After Throwing):在目标方法抛出异常时执行。
  • 环绕通知(Around):包围目标方法的执行,可以在方法执行之前和之后自定义行为。

切入点(Pointcut)

切入点是一个表达式,定义了哪些连接点会被切面所影响。切入点表达式用于匹配连接点,从而决定切面应该应用到哪些方法上。常见的切入点表达式语言包括 AspectJ 的表达式语言。

目标对象(Target Object)

目标对象是被一个或多个切面所通知的对象。目标对象是 AOP 代理的实际对象,即被代理的对象。

AOP 代理(AOP Proxy)

AOP 代理是一个对象,用于实现切面契约(即通知方法)并将调用委托给目标对象。代理对象负责在调用目标方法之前或之后执行切面逻辑。AOP 代理可以是 JDK 动态代理(用于代理接口)或 CGLIB 代理(用于代理类)。

织入(Weaving)

织入是将切面应用到目标对象并创建 AOP 代理对象的过程。织入可以在以下几个时机进行:

  • 编译时(Compile-time Weaving):在编译阶段将切面织入到目标类中。
  • 类加载时(Load-time Weaving):在类加载阶段使用类加载器将切面织入到目标类中。
  • 运行时(Runtime Weaving):在运行时通过动态代理将切面织入到目标对象中。

引入(Introduction)

引入是 AOP 提供的一种机制,允许在不修改现有类的情况下向其添加新的方法或属性。引入可以用于增强现有类的功能。

ApplicationContext的实现类有哪些?

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌ApplicationContext的实现类有哪些?

口语化答案

好的,面试官。常用的只要用 classpathxml 的基于 xml 的方式,annotion 的基于注解的方式,不常见的还有 web 和 groovy。在目前的实际情况下,主要是 annotion,注解形式放入到容器中。像老的 tomcat 那种方式,用的是 web 形式,不过现在都是 boot 注解形式够用。groovy 适用于一些动态加载 bean 的方式,通过脚本的形式处理。以上。

题目解析

经典题,问的还比较多,主要是想借此考察一下,你在项目中,都用过什么样的形式的。其实主要答出注解和 xml 这道题就 ok。

面试得分点

xml,注解,web,groovy

题目详细答案

基于XML配置的实现类

ClassPathXmlApplicationContext:从类路径下加载XML配置文件。

1
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

FileSystemXmlApplicationContext:从文件系统路径加载XML配置文件。

1
ApplicationContext context = new FileSystemXmlApplicationContext("C:/path/to/applicationContext.xml");

基于注解配置的实现类

AnnotationConfigApplicationContext:从Java配置类(使用@Configuration注解的类)加载配置。

1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

基于Web应用的实现类

XmlWebApplicationContext:专门为Web应用设计的ApplicationContext实现类,从Web应用的上下文中加载XML配置文件。

通常在web.xml中配置:

1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

AnnotationConfigWebApplicationContext:专门为Web应用设计的ApplicationContext实现类,从Java配置类加载配置

1
2
3
4
5
6
7
8
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
container.addListener(new ContextLoaderListener(context));
}
}

基于Groovy配置的实现类

GenericGroovyApplicationContext:从Groovy脚本配置文件加载配置。

1
ApplicationContext context = new GenericGroovyApplicationContext("applicationContext.groovy");

BeanFactory 和 ApplicationContext 的区别?

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌BeanFactory 和 ApplicationContext 的区别?

口语化答案

好的,面试官。BeanFactory和ApplicationContext都是用于管理Bean的容器接口。BeanFactory功能相对简单。提供了Bean的创建、获取和管理功能。默认采用延迟初始化,只有在第一次访问Bean时才会创建该Bean。因为功能较为基础,BeanFactory通常用于资源受限的环境中,比如移动设备或嵌入式设备。ApplicationContext是BeanFactory的子接口,提供了更丰富的功能和更多的企业级特性。默认会在启动时创建并初始化所有单例Bean,支持自动装配Bean,可以根据配置自动注入依赖对象。有多种实现,如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext等。以上

题目解析

经典题,早几年爱考,现在随着时间的发展,问的比较少,不排除古老面试官,应届生可以重点看一下。

面试得分点

初始化时机,延迟,企业级应用场景

题目详细答案

BeanFactory和ApplicationContext都是用于管理Bean的容器接口,它们的功能和用途有所不同。

BeanFactory

BeanFactory是Spring框架的核心接口之一,负责管理和配置应用程序中的Bean。它提供了基本的Bean容器功能,但功能相对简单。BeanFactory提供了Bean的创建、获取和管理功能。它是Spring IoC容器的最基本接口。

BeanFactory默认采用延迟初始化(lazy loading),即只有在第一次访问Bean时才会创建该Bean。这有助于提升启动性能。

因为功能较为基础,BeanFactory通常用于资源受限的环境中,比如移动设备或嵌入式设备。

ApplicationContext

ApplicationContext是BeanFactory的子接口,提供了更丰富的功能和更多的企业级特性。

不仅提供了BeanFactory的所有功能,还提供了更多高级特性,如事件发布、国际化、AOP、自动Bean装配等。

ApplicationContext默认会在启动时创建并初始化所有单例Bean(除非显式配置为延迟初始化)。这有助于在应用启动时尽早发现配置问题。

ApplicationContext支持自动装配Bean,可以根据配置自动注入依赖对象。

ApplicationContext有多种实现,如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext等,适用于不同的配置方式和场景。

具体区别总结

BeanFactory ApplicationContext
初始化时机 延迟初始化,只有在第一次访问Bean时才创建该Bean。 立即初始化,在容器启动时就创建并初始化所有单例Bean。
特性 功能较为基础,只提供Bean的创建、获取和管理功能。 提供更多企业级特性,如事件发布、国际化、AOP、自动装配等。
使用场景 适用于资源受限的环境,或者需要延迟初始化的场景。 适用于大多数企业级应用

代码 Demo

使用 BeanFactory

1
2
3
Resource resource = new ClassPathResource("beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
MyBean myBean = (MyBean) beanFactory.getBean("myBean");

使用 ApplicationContext

1
2
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyBean myBean = (MyBean) context.getBean("myBean");

Bean 标签的属性?

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌Bean 标签的属性?

口语化回答

好的,面试官。bean 是最长使用的标签,如果是使用 xml 形式。最常见的基本属性就是 id,name,class。分别标识唯一的 bean,bean 的别名和实际要注入的类。也可以通过一些属性实现,bean 开始时候的操作,比如init-method,配置的方法,可以在 bean 初始化的时候,进行执行。bean 还有常见的构造函数注入标签,注入 bean 中的属性。以上。

题目解析

应届生常问,有工作经验的基本不问,目的其实就是为了,考察一下你到底有没有实际使用过 spring 的 bean 的配置。

面试得分点

id,name,class,一些初始化属性

题目详细答案

常见属性

id:Bean的唯一标识符。

1
<bean id="myBean" class="com.example.MyBean"/>

name:Bean的别名,可以为Bean定义一个或多个别名。

1
<bean id="myBean" name="alias1,alias2" class="com.example.MyBean"/>

class:Bean的全限定类名。

1
<bean id="myBean" class="com.example.MyBean"/>

scope:Bean的作用域,常见值包括singleton(默认)、prototype、request、session、globalSession、application。

1
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>

init-method:Bean初始化时调用的方法。

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

destroy-method:Bean销毁时调用的方法。

1
<bean id="myBean" class="com.example.MyBean" destroy-method="cleanup"/>

factory-method:用于创建Bean实例的静态工厂方法。

1
<bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance"/>

factory-bean:用于创建Bean实例的工厂Bean的名称。

1
2
<bean id="myFactory" class="com.example.MyFactory"/>
<bean id="myBean" factory-bean="myFactory" factory-method="createInstance"/>

依赖注入相关属性

constructor-arg:用于构造函数注入。

1
2
3
4
<bean id="myBean" class="com.example.MyBean">
<constructor-arg value="someValue"/>
<constructor-arg ref="anotherBean"/>
</bean>

property:用于Setter方法注入。

1
2
3
4
<bean id="myBean" class="com.example.MyBean">
<property name="propertyName" value="someValue"/>
<property name="anotherProperty" ref="anotherBean"/>
</bean>

其他常用属性

autowire:自动装配模式,常见值包括no(默认)、byName、byType、constructor、autodetect。

1
<bean id="myBean" class="com.example.MyBean" autowire="byName"/>

depends-on:指定Bean的依赖关系,即在初始化当前Bean之前需要先初始化的Bean。

1
<bean id="myBean" class="com.example.MyBean" depends-on="anotherBean"/>

lazy-init:是否延迟初始化,默认值为false。

1
<bean id="myBean" class="com.example.MyBean" lazy-init="true"/>

primary:当自动装配时,如果有多个候选Bean,可以将某个Bean标记为主要候选者。

1
<bean id="myBean" class="com.example.MyBean" primary="true"/>

配置 demo

1
2
3
4
5
6
<bean id="myBean" class="com.example.MyBean" scope="singleton" init-method="init" destroy-method="cleanup" lazy-init="true" primary="true" autowire="byType">
<constructor-arg value="someValue"/>
<constructor-arg ref="anotherBean"/>
<property name="propertyName" value="someValue"/>
<property name="anotherProperty" ref="anotherBean"/>
</bean>

👌Cglib的Enhancer实现动态代理?

发表于 2025-03-02 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

👌Cglib的Enhancer实现动态代理?

口语化回答

好的,面试官,cglib 代理相比 jdk 动态代理不同的就是不需要被代理的类实现接口。假设我们现在有一个MyService,其中有一个方法是performTask,我们只需要定义一个新的类,实现MethodInterceptor 接口,然后再里面的 intercept 方法实现需要增强的方法。最终通过 cglib 的enhancer 类,先设置父类,父类就是我们要增强的类,再设置 callback 也就是我们要增强的功能。最后使用 create 就生成了 cglib 的一个代理类。以上

题目解析

和 jdk 的动态代理异曲同工,相比 jdk 反而 cglib 的更常考一点。大家理解一下下面的代码流程就很容易说出其中的过程。

面试得分点

目标类,方法拦截器,创建代理对象

题目详细答案

CGLIB是一种强大的代码生成库,能够在运行时生成代理类。与 JDK 动态代理不同的是,CGLIB 不需要接口,可以直接代理具体类。CGLIB 通过创建目标类的子类并覆盖其中的方法来实现代理。

实现步骤

1、 引入 CGLIB 库:确保在项目中添加 CGLIB 依赖。

2、 创建目标类:定义需要代理的具体类。

3、 创建方法拦截器:实现MethodInterceptor接口,并在intercept方法中定义代理逻辑。

4、 创建代理对象:通过Enhancer类创建代理对象。

代码 Demo

假设我们有一个简单的服务类MyService,通过 CGLIB 动态代理为MyService创建一个代理对象,并在方法调用前后添加日志。

引入 CGLIB 依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

创建目标类

1
2
3
4
5
public class MyService {
public void performTask() {
System.out.println("Performing task");
}
}

创建方法拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LoggingMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Logging before method execution: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("Logging after method execution: " + method.getName());
return result;
}
}

创建代理对象并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import net.sf.cglib.proxy.Enhancer;

public class MainApp {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();

// 设置目标类为代理类的父类
enhancer.setSuperclass(MyService.class);

// 设置方法拦截器
enhancer.setCallback(new LoggingMethodInterceptor());

// 创建代理对象
MyService proxyInstance = (MyService) enhancer.create();

// 调用代理对象的方法
proxyInstance.performTask();
}
}

详细解释

引入 CGLIB 库:在项目中添加 CGLIB 依赖,以便使用 CGLIB 提供的类和接口。

目标类:MyService是一个具体类,定义了一个方法performTask。

方法拦截器:LoggingMethodInterceptor实现了MethodInterceptor接口。它的intercept方法在代理对象的方法调用时被调用。

intercept方法接收四个参数:obj:代理对象。method:被调用的方法。args:方法参数。proxy:用于调用父类方法的代理对象。

在intercept方法中,我们在方法调用前后添加了日志打印,并通过proxy.invokeSuper调用父类的方法。

创建代理对象:使用Enhancer类创建代理对象。setSuperclass方法设置目标类为代理类的父类。setCallback方法设置方法拦截器。create方法创建代理对象。

使用代理对象:

通过代理对象调用方法时,实际调用的是LoggingMethodInterceptor的intercept方法。

在intercept方法中,首先打印日志,然后通过proxy.invokeSuper调用目标对象的方法,最后再打印日志。

<i class="fa fa-angle-left"></i>1…567…12<i class="fa fa-angle-right"></i>

239 日志
22 分类
30 标签
GitHub
© 2025 javayun
由 Hexo 强力驱动
主题 - NexT.Gemini