Spring Boot實戰:基于策略模式+代理模式手寫冪等性注解組件

一、為什么需要冪等性?

核心定義:在分布式系統中,一個操作無論執行一次還是多次,最終結果都保持一致。
典型場景

  • 用戶重復點擊提交按鈕
  • 網絡抖動導致的請求重試
  • 消息隊列的重復消費
  • 支付系統的回調通知

不處理冪等的風險

  • 重復創建訂單導致資金損失
  • 庫存超賣引發資損風險
  • 用戶數據重復插入破壞業務邏輯

二、實現步驟分解

1. 定義冪等注解

/*** 冪等注解** @author dyh*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {/*** 冪等的超時時間,默認為 1 秒** 注意,如果執行時間超過它,請求還是會進來*/int timeout() default 1;/*** 時間單位,默認為 SECONDS 秒*/TimeUnit timeUnit() default TimeUnit.SECONDS;/*** 提示信息,正在執行中的提示*/String message() default "重復請求,請稍后重試";/*** 使用的 Key 解析器** @see DefaultIdempotentKeyResolver 全局級別* @see UserIdempotentKeyResolver 用戶級別* @see ExpressionIdempotentKeyResolver 自定義表達式,通過 {@link #keyArg()} 計算*/Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;/*** 使用的 Key 參數*/String keyArg() default "";/*** 刪除 Key,當發生異常時候** 問題:為什么發生異常時,需要刪除 Key 呢?* 回答:發生異常時,說明業務發生錯誤,此時需要刪除 Key,避免下次請求無法正常執行。** 問題:為什么不搞 deleteWhenSuccess 執行成功時,需要刪除 Key 呢?* 回答:這種情況下,本質上是分布式鎖,推薦使用 @Lock4j 注解*/boolean deleteKeyWhenException() default true;}

2. 設計Key解析器接口

/*** 冪等 Key 解析器接口** @author dyh*/
public interface IdempotentKeyResolver {/*** 解析一個 Key** @param idempotent 冪等注解* @param joinPoint  AOP 切面* @return Key*/String resolver(JoinPoint joinPoint, Idempotent idempotent);}

3. 實現三種核心策略

  • 默認策略:方法簽名+參數MD5(防全局重復)
  • 用戶策略:用戶ID+方法特征(防用戶重復)
  • 表達式策略:SpEL動態解析參數(靈活定制)

3.1 默認策略


/*** 默認(全局級別)冪等 Key 解析器,使用方法名 + 方法參數,組裝成一個 Key** 為了避免 Key 過長,使用 MD5 進行“壓縮”** @author dyh*/
public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {/*** 核心方法:生成冪等Key(基于方法特征+參數內容)* @param joinPoint   AOP切入點對象,包含方法調用信息* @param idempotent  方法上的冪等注解對象* @return 生成的唯一冪等Key(32位MD5哈希值)*/@Overridepublic String resolver(JoinPoint joinPoint, Idempotent idempotent) {// 獲取方法完整簽名(格式:返回值類型 類名.方法名(參數類型列表))// 示例:String com.example.UserService.createUser(Long,String)String methodName = joinPoint.getSignature().toString();// 將方法參數數組拼接為字符串(用逗號分隔)// 示例:參數是 [123, "張三"] 將拼接為 "123,張三"String argsStr = StrUtil.join(",", joinPoint.getArgs());// 將方法簽名和參數字符串合并后計算MD5// 目的:將可能很長的字符串壓縮為固定長度,避免Redis Key過長return SecureUtil.md5(methodName + argsStr);}}

3.2 用戶策略


/*** 用戶級別的冪等 Key 解析器,使用方法名 + 方法參數 + userId + userType,組裝成一個 Key* <p>* 為了避免 Key 過長,使用 MD5 進行“壓縮”** @author dyh*/
public class UserIdempotentKeyResolver implements IdempotentKeyResolver {/*** 生成用戶級別的冪等Key** @param joinPoint  AOP切入點對象(包含方法調用信息)* @param idempotent 方法上的冪等注解* @return 基于用戶維度的32位MD5哈希值* <p>* 生成邏輯分四步:* 1. 獲取方法簽名 -> 標識具體方法* 2. 拼接參數值 -> 標識操作數據* 3. 獲取用戶身份 -> 隔離用戶操作* 4. MD5哈希計算 -> 壓縮存儲空間*/@Overridepublic String resolver(JoinPoint joinPoint, Idempotent idempotent) {// 步驟1:獲取方法唯一標識(格式:返回類型 類名.方法名(參數類型列表))// 示例:"void com.service.UserService.updatePassword(Long,String)"String methodName = joinPoint.getSignature().toString();// 步驟2:將方法參數轉換為逗號分隔的字符串// 示例:參數是 [1001, "新密碼"] 會拼接成 "1001,新密碼"String argsStr = StrUtil.join(",", joinPoint.getArgs());// 步驟3:從請求上下文中獲取當前登錄用戶ID// 注意:需確保在Web請求環境中使用,未登錄時可能返回nullLong userId = WebFrameworkUtils.getLoginUserId();// 步驟4:獲取當前用戶類型(例如:0-普通用戶,1-管理員)// 作用:區分不同權限用戶的操作Integer userType = WebFrameworkUtils.getLoginUserType();// 步驟5:將所有要素拼接后生成MD5哈希值// 輸入示例:"void updatePassword()1001,新密碼1231"// 輸出示例:"d3d9446802a44259755d38e6d163e820"return SecureUtil.md5(methodName + argsStr + userId + userType);}
}

3.3 表達式策略

/*** 基于 Spring EL 表達式,** @author dyh*/
public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {// 參數名發現器:用于獲取方法的參數名稱(如:userId, orderId)// 為什么用LocalVariableTable:因為編譯后默認不保留參數名,需要這個工具讀取調試信息private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();// 表達式解析器:專門解析Spring EL表達式// 為什么用Spel:Spring官方標準,支持復雜表達式語法private final ExpressionParser expressionParser = new SpelExpressionParser();/*** 核心方法:解析生成冪等Key** @param joinPoint  AOP切入點(包含方法調用信息)* @param idempotent 方法上的冪等注解* @return 根據表達式生成的唯一Key*/@Overridepublic String resolver(JoinPoint joinPoint, Idempotent idempotent) {// 步驟1:獲取當前執行的方法對象Method method = getMethod(joinPoint);// 步驟2:獲取方法參數值數組(例如:[訂單對象, 用戶對象])Object[] args = joinPoint.getArgs();// 步驟3:獲取方法參數名數組(例如:["order", "user"])String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);// 步驟4:創建表達式上下文(相當于給表達式提供變量環境)StandardEvaluationContext evaluationContext = new StandardEvaluationContext();// 步驟5:將參數名和參數值綁定到上下文(讓表達式能識別#order這樣的變量)if (ArrayUtil.isNotEmpty(parameterNames)) {for (int i = 0; i < parameterNames.length; i++) {// 例如:將"order"參數名和實際的Order對象綁定evaluationContext.setVariable(parameterNames[i], args[i]);}}// 步驟6:解析注解中的表達式(例如:"#order.id")Expression expression = expressionParser.parseExpression(idempotent.keyArg());// 步驟7:執行表達式計算(例如:從order對象中取出id屬性值)return expression.getValue(evaluationContext, String.class);}/*** 輔助方法:獲取實際執行的方法對象* 為什么需要這個方法:處理Spring AOP代理接口的情況** @param point AOP切入點* @return 實際被調用的方法對象*/private static Method getMethod(JoinPoint point) {// 情況一:方法直接定義在類上(非接口方法)MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();if (!method.getDeclaringClass().isInterface()) {return method; // 直接返回當前方法}// 情況二:方法定義在接口上(需要獲取實現類的方法)try {// 通過反射獲取目標類(實際實現類)的方法// 例如:UserService接口的create方法 -> UserServiceImpl的create方法return point.getTarget().getClass().getDeclaredMethod(point.getSignature().getName(), // 方法名method.getParameterTypes());    // 參數類型} catch (NoSuchMethodException e) {// 找不到方法時拋出運行時異常(通常意味著代碼結構有問題)throw new RuntimeException("方法不存在: " + method.getName(), e);}}
}

4. 編寫AOP切面

/*** 攔截聲明了 {@link Idempotent} 注解的方法,實現冪等操作* 冪等切面處理器** 功能:攔截被 @Idempotent 注解標記的方法,通過Redis實現請求冪等性控制* 流程:* 1. 根據配置的Key解析策略生成唯一標識* 2. 嘗試在Redis中設置該Key(SETNX操作)* 3. 若Key已存在 → 拋出重復請求異常* 4. 若Key不存在 → 執行業務邏輯* 5. 異常時根據配置決定是否刪除Key** @author dyh*/
@Aspect  // 聲明為AOP切面類
@Slf4j   // 自動生成日志對象public class IdempotentAspect {/*** Key解析器映射表(Key: 解析器類型,Value: 解析器實例)* 示例:* DefaultIdempotentKeyResolver.class → DefaultIdempotentKeyResolver實例* ExpressionIdempotentKeyResolver.class → ExpressionIdempotentKeyResolver實例*/private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers;/*** Redis操作工具類(處理冪等Key的存儲)*/private final IdempotentRedisDAO idempotentRedisDAO;/*** 構造方法(依賴注入)* @param keyResolvers 所有Key解析器的Spring Bean集合* @param idempotentRedisDAO Redis操作DAO*/public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {// 將List轉換為Map,Key是解析器的Class類型this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);this.idempotentRedisDAO = idempotentRedisDAO;}/*** 環繞通知:攔截被@Idempotent注解的方法* @param joinPoint 切入點(包含方法、參數等信息)* @param idempotent 方法上的@Idempotent注解實例* @return 方法執行結果* @throws Throwable 可能拋出的異常** 執行流程:* 1. 獲取Key解析器 → 2. 生成唯一Key → 3. 嘗試鎖定 → 4. 執行業務 → 5. 異常處理*/@Around(value = "@annotation(idempotent)")  // 切入帶有@Idempotent注解的方法public Object aroundPointCut(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {// 步驟1:根據注解配置獲取對應的Key解析器IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());// 斷言確保解析器存在(找不到說明Spring容器初始化有問題)Assert.notNull(keyResolver, "找不到對應的 IdempotentKeyResolver");// 步驟2:使用解析器生成唯一Key(例如:MD5(方法簽名+參數))String key = keyResolver.resolver(joinPoint, idempotent);// 步驟3:嘗試在Redis中設置Key(原子性操作)// 參數說明:// key: 唯一標識// timeout: 過期時間(通過注解配置)// timeUnit: 時間單位(通過注解配置)boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());// 步驟4:處理重復請求if (!success) {// 記錄重復請求日志(方法簽名 + 參數)log.info("[冪等攔截] 方法({}) 參數({}) 存在重復請求",joinPoint.getSignature().toString(),joinPoint.getArgs());// 拋出業務異常(攜帶注解中配置的錯誤提示信息)throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(),idempotent.message());}try {// 步驟5:執行原始業務方法return joinPoint.proceed();} catch (Throwable throwable) {// 步驟6:異常處理(參考美團GTIS設計)// 配置刪除策略:當deleteKeyWhenException=true時,刪除Key允許重試if (idempotent.deleteKeyWhenException()) {// 記錄刪除操作日志(實際生產可添加更詳細日志)log.debug("[冪等異常處理] 刪除Key: {}", key);idempotentRedisDAO.delete(key);}// 繼續拋出異常(由全局異常處理器處理)throw throwable;}}
}

5. 實現Redis原子操作

/*** 冪等 Redis DAO** @author dyh*/
@AllArgsConstructor
public class IdempotentRedisDAO {/*** 冪等操作** KEY 格式:idempotent:%s // 參數為 uuid* VALUE 格式:String* 過期時間:不固定*/private static final String IDEMPOTENT = "idempotent:%s";private final StringRedisTemplate redisTemplate;public Boolean setIfAbsent(String key, long timeout, TimeUnit timeUnit) {String redisKey = formatKey(key);return redisTemplate.opsForValue().setIfAbsent(redisKey, "", timeout, timeUnit);}public void delete(String key) {String redisKey = formatKey(key);redisTemplate.delete(redisKey);}private static String formatKey(String key) {return String.format(IDEMPOTENT, key);}}

6. 自動裝配


/*** @author dyh* @date 2025/4/17 18:08*/
@AutoConfiguration(after = DyhRedisAutoConfiguration.class)
public class DyhIdempotentConfiguration {@Beanpublic IdempotentAspect idempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {return new IdempotentAspect(keyResolvers, idempotentRedisDAO);}@Beanpublic IdempotentRedisDAO idempotentRedisDAO(StringRedisTemplate stringRedisTemplate) {return new IdempotentRedisDAO(stringRedisTemplate);}// ========== 各種 IdempotentKeyResolver Bean ==========@Beanpublic DefaultIdempotentKeyResolver defaultIdempotentKeyResolver() {return new DefaultIdempotentKeyResolver();}@Beanpublic UserIdempotentKeyResolver userIdempotentKeyResolver() {return new UserIdempotentKeyResolver();}@Beanpublic ExpressionIdempotentKeyResolver expressionIdempotentKeyResolver() {return new ExpressionIdempotentKeyResolver();}}

三、核心設計模式解析

1. 策略模式(核心設計)

應用場景:多種冪等Key生成策略的動態切換
代碼體現

// 策略接口
public interface IdempotentKeyResolver {String resolver(JoinPoint joinPoint, Idempotent idempotent);
}// 具體策略實現
public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {@Overridepublic String resolver(...) { /* MD5(方法+參數) */ }
}public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {@Overridepublic String resolver(...) { /* SpEL解析 */ }
}

UML圖示

?interface?
IdempotentKeyResolver
+resolver(JoinPoint, Idempotent) : String
DefaultKeyResolver
ExpressionKeyResolver
UserKeyResolver

2. 代理模式(AOP實現)

應用場景:通過動態代理實現無侵入的冪等控制
代碼體現

@Aspect
public class IdempotentAspect {@Around("@annotation(idempotent)") // 切入點表達式public Object around(...) {// 通過代理對象控制原方法執行return joinPoint.proceed(); }
}

執行流程
客戶端調用 → 代理對象攔截 → 執行冪等校驗 → 調用真實方法

四、這樣設計的好處

  1. 業務解耦
    • 冪等邏輯與業務代碼完全分離
    • 通過注解實現聲明式配置
  2. 靈活擴展
    • 新增Key策略只需實現接口
    • 支持自定義SpEL表達式
  3. 高可靠性
    • Redis原子操作防并發問題
    • 異常時自動清理Key(可配置)
  4. 性能優化
    • MD5壓縮減少Redis存儲壓力
    • 細粒度鎖控制(不同Key互不影響)
  5. 易用性
    • 開箱即用的starter組件
    • 三種內置策略覆蓋主流場景

五、使用示例

@Idempotent(keyResolver = UserIdempotentKeyResolver.class,timeout = 10,message = "請勿重復提交訂單"
)
public void createOrder(OrderDTO dto) {// 業務邏輯
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/76181.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/76181.shtml
英文地址,請注明出處:http://en.pswp.cn/web/76181.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何恢復極狐GitLab?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 恢復極狐GitLab (BASIC SELF) 極狐GitLab 提供了一個命令行界面來恢復整個安裝&#xff0c;足夠靈活以滿足您的需求。 恢復…

面試高階問題:android后臺任務(如數據同步、定位)消耗過多電量,導致用戶投訴。你會如何分析和優化后臺任務的執行?

在現代移動設備生態中,安卓系統以其開放性和靈活性占據了全球智能手機市場的絕大部分份額。作為一款功能強大的操作系統,安卓允許應用程序在后臺執行各種任務,例如數據同步、定位服務、消息推送以及其他周期性更新。這些后臺任務在提升用戶體驗方面扮演了不可或缺的角色——…

最近在學習web搞大屏看板

人到中年&#xff0c;delphi發展越來越不行&#xff0c;就業環境是真差啊&#xff0c;沒辦法&#xff0c;學唄 中國地圖&#xff1a; // 中國地圖function getChinaMapChart() {// 初始化echarts實例var myEcharts echarts.init(document.getElementById("china_box"…

117.在 Vue 3 中使用 OpenLayers 實現 CTRL 控制拖拽和滾動縮放

? 前言 在使用 OpenLayers 開發地圖類項目時,我們有時會希望用戶必須按下 CTRL(或 Mac 的 Command ? 鍵)才能拖拽地圖或使用鼠標滾輪縮放。這種交互方式能夠避免用戶在瀏覽頁面時意外滑動或拖動地圖,尤其是在地圖嵌入頁面中時非常有用。 本文將帶你一步一步實現在 Vue …

MATLAB 控制系統設計與仿真 - 34

多變量系統知識回顧 - MIMO system 這一章對深入理解多變量系統以及魯棒分析至關重要 首先,對于如下系統: 當G(s)為單輸入,單輸出系統時: 如果: 則: 所以 因此,對于SISO,系統的增益跟w有關系, 當G(s)為MIMO時,例如2X2時, 假設輸入信號為:

ARCGIS PRO DSK 利用兩期地表DEM數據計算工程土方量

利用兩期地表DEM數據計算工程土方量需要準許以下數據&#xff1a; 當前地圖有3個圖層&#xff0c;兩個柵格圖層和一個矢量圖層 兩個柵格圖層&#xff1a;beforeDem為工程施工前的地表DEM模型 afterDem為工程施工后的地表DEM模型 一個矢量圖層&#xf…

最快打包WPF 應用程序

在 Visual Studio 中右鍵項目選擇“發布”&#xff0c;目標選“文件夾”&#xff0c;模式選“自包含”&#xff0c;生成含 .exe 的文件夾&#xff0c;壓縮后可直接發給別人或解壓運行&#xff0c;無需安裝任何東西。 最簡單直接的新手做法&#xff1a; 用 Visual Studio 的“…

物聯網通信協議——TCP與MQTT的對比

在物聯網通信中&#xff0c;MQTT和TCP的實現方式和原理完全不同&#xff0c;因為兩者屬于協議棧的不同層級&#xff0c;解決的問題也不同。以下從協議層級、工作機制和典型場景三個角度詳細解釋&#xff1a; 1. 協議層級與定位 特性TCPMQTT協議層級傳輸層&#xff08;第4層&am…

【信息系統項目管理師】高分論文:論信息系統項目的成本管理(媒體融合采編平臺)

更多內容請見: 備考信息系統項目管理師-專欄介紹和目錄 文章目錄 論文1、規劃項目成本管理2、估算成本3、制訂項目預算4、控制成本論文 2017年7月,我作為項目經理參與了 XX省媒體融合采編平臺的建設,該項目總共投資530萬元,其中服務器、存儲、網絡等硬件設備投資200萬元、軟…

策略模式簡單介紹

什么是策略模式&#xff1f;一般用于什么場景&#xff1f; 策略模式一種行為型設計模式&#xff0c;它定義了一系列算法&#xff0c;并將每個算法封裝起來&#xff0c;使得它們可以相互替換&#xff0c;這樣&#xff0c;客戶端可以根據需要在運行時選擇合適的算法&#xff0c;…

基于PAI+專屬網關+私網連接:構建全鏈路 Deepseek 云上私有化部署與模型調用架構

DeepSeek - R1 是由深度求索公司推出的首款推理模型&#xff0c;該模型在數學、代碼和推理任務上的表現優異&#xff0c;市場反饋火爆。在大模型技術商業化進程中&#xff0c;企業級用戶普遍面臨四大核心挑戰&#xff1a; 算力投入成本高昂&#xff1a;構建千億參數級模型的訓…

【APM】How to enable Trace to Logs on Grafana?

系列文章目錄 【APM】Observability Solution 【APM】Build an environment for Traces, Metrics and Logs of App by OpenTelemetry 【APM】NET Traces, Metrics and Logs to OLTP 【APM】How to enable Trace to Logs on Grafana? 前言 本文將介紹如何在Grafana上啟用 …

在 Excel 中使用通義靈碼輔助開發 VBA 程序

VBA 簡介 VBA 是一種用于微軟辦公套件&#xff08;如 Word、Excel、PowerPoint 等&#xff09;的編程語言&#xff0c;它本質上是一種內嵌的腳本&#xff0c;或者可以認為是一段命令&#xff0c;其標準叫法被稱為宏。 VBA 只能依賴于對應的軟件進行開發&#xff0c;例如本文就…

vscode終端運行windows服務器的conda出錯

遠程windows服務器可以運行&#xff0c;本地vscode不能。 打開vscode settings.json文件 添加conda所在路徑

紫外相機的應用范圍及介紹

&#xff08;一&#xff09;工業領域 半導體制造&#xff1a;在晶圓制造和檢測過程中&#xff0c;紫外相機起著關鍵作用。它可用于裸晶圓檢測&#xff0c;能準確識別出制造過程中偶然引入的微粒&#xff08;如灰塵&#xff09;或因處理不當造成的劃痕等缺陷。對于圖案晶圓檢查…

08軟件測試需求分析案例-刪除用戶

刪除用戶是后臺管理菜單的一個功能模塊&#xff0c;只有admin才有刪除用戶的權限。不可刪除admin。 1.1 通讀文檔 通讀需求規格說明書是提取信息&#xff0c;提出問題&#xff0c;輸出具有邏輯、規則、流程的業務步驟。 信息&#xff1a;此功能應為用戶提供確認刪除的功能。…

Oracle DBMS_SCHEDULER 與 DBMS_JOB 的對比

Oracle DBMS_SCHEDULER 與 DBMS_JOB 的對比 一 基本概述對比 特性DBMS_JOB (舊版)DBMS_SCHEDULER (新版)引入版本Oracle 7 (1992年)Oracle 10g R1 (2003年)當前狀態已過時但仍支持推薦使用的標準設計目的基礎作業調度企業級作業調度系統 二 功能特性對比 2.1 作業定義能力 …

Linux網絡編程實戰:從字節序到UDP協議棧的深度解析與開發指南

網路通信的三大要素&#xff1a;協議&#xff0c;端口和IP 知識點1【字節序】 多字節在主機中的存放數據 把多字節看成一個整體存儲的順序。 為什么我們在文件中沒有這個概念呢&#xff1f; 因為文件是字節流&#xff08;流指針&#xff09;&#xff0c;流是以一個字節為操…

mvccc

. MVCC (多版本并發控制) 概念&#xff1a; MVCC 是一種并發控制技術&#xff0c;用于在數據庫中實現并發事務的讀寫操作&#xff0c;同時保證事務的隔離性。MVCC 的核心思想是&#xff0c;在數據庫中維護數據的多個版本&#xff0c;每個事務在讀取數據時&#xff0c;讀取的是…

Kotlin整數相除精度損失roundToInt

Kotlin整數相除精度損失roundToInt import kotlin.math.roundToIntfun main() {val a 0.0fval delta 0.1ffor (i in 0..10) {val r a i * deltaprintln("float${r} toInt${r.toInt()} (0.5 toInt)${(r 0.5).toInt()} round${Math.round(r)} roundToInt${r.roundToInt…