欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot中4種接口冪等性的實(shí)現(xiàn)策略

 更新時間:2025年04月17日 08:14:26   作者:風(fēng)象南  
冪等性是指對同一操作執(zhí)行多次與執(zhí)行一次的效果相同,不會因?yàn)橹貜?fù)執(zhí)行而產(chǎn)生副作用,本文整理了4個SpringBoot實(shí)現(xiàn)接口冪等性的方法,大家可以根據(jù)需要進(jìn)行選擇

冪等性是指對同一操作執(zhí)行多次與執(zhí)行一次的效果相同,不會因?yàn)橹貜?fù)執(zhí)行而產(chǎn)生副作用。在實(shí)際應(yīng)用中,由于網(wǎng)絡(luò)延遲、用戶重復(fù)點(diǎn)擊提交、系統(tǒng)自動重試等原因,可能導(dǎo)致同一請求被多次發(fā)送到服務(wù)端處理,如果沒有實(shí)現(xiàn)冪等性,就可能導(dǎo)致數(shù)據(jù)重復(fù)、業(yè)務(wù)異常等問題。

1. 基于Token令牌的冪等性實(shí)現(xiàn)

Token令牌策略是最常見的冪等性實(shí)現(xiàn)方式之一,其核心思想是在執(zhí)行業(yè)務(wù)操作前先獲取一個唯一token,然后在調(diào)用接口時將其隨請求一起提交,服務(wù)端校驗(yàn)并銷毀token,確保其只被使用一次。

實(shí)現(xiàn)步驟

  • 客戶端先調(diào)用獲取token接口
  • 服務(wù)端生成唯一token并存入Redis,設(shè)置過期時間
  • 客戶端調(diào)用業(yè)務(wù)接口時附帶token參數(shù)
  • 服務(wù)端驗(yàn)證token存在性并刪除,防止重復(fù)使用

代碼實(shí)現(xiàn)

@RestController
@RequestMapping("/api")
public class OrderController {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private OrderService orderService;
    
    // 獲取token接口
    @GetMapping("/token")
    public Result<String> getToken() {
        // 生成唯一token
        String token = UUID.randomUUID().toString();
        // 存入Redis并設(shè)置過期時間
        redisTemplate.opsForValue().set("idempotent:token:" + token, "1", 10, TimeUnit.MINUTES);
        return Result.success(token);
    }
    
    // 創(chuàng)建訂單接口
    @PostMapping("/order")
    public Result<Order> createOrder(@RequestHeader("Idempotent-Token") String token, @RequestBody OrderRequest request) {
        // 檢查token是否存在
        String key = "idempotent:token:" + token;
        Boolean exist = redisTemplate.hasKey(key);
        if (exist == null || !exist) {
            return Result.fail("令牌不存在或已過期");
        }
        
        // 刪除token,保證冪等性
        if (Boolean.FALSE.equals(redisTemplate.delete(key))) {
            return Result.fail("令牌已被使用");
        }
        
        // 執(zhí)行業(yè)務(wù)邏輯
        Order order = orderService.createOrder(request);
        return Result.success(order);
    }
}

通過AOP簡化實(shí)現(xiàn)

可以通過自定義注解和AOP進(jìn)一步簡化冪等性實(shí)現(xiàn):

// 自定義冪等性注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    long timeout() default 10; // 過期時間,單位分鐘
}

// AOP實(shí)現(xiàn)
@Aspect
@Component
public class IdempotentAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        // 獲取請求頭中的token
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader("Idempotent-Token");
        
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException("冪等性Token不能為空");
        }
        
        String key = "idempotent:token:" + token;
        Boolean exist = redisTemplate.hasKey(key);
        
        if (exist == null || !exist) {
            throw new BusinessException("令牌不存在或已過期");
        }
        
        // 刪除token,保證冪等性
        if (Boolean.FALSE.equals(redisTemplate.delete(key))) {
            throw new BusinessException("令牌已被使用");
        }
        
        // 執(zhí)行目標(biāo)方法
        return joinPoint.proceed();
    }
}

// 控制器使用注解
@RestController
@RequestMapping("/api")
public class OrderController {

    @Autowired
    private OrderService orderService;
    
    @PostMapping("/order")
    @Idempotent(timeout = 30)
    public Result<Order> createOrder(@RequestBody OrderRequest request) {
        Order order = orderService.createOrder(request);
        return Result.success(order);
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 實(shí)現(xiàn)簡單,易于理解
  • 對業(yè)務(wù)代碼侵入小,可通過AOP實(shí)現(xiàn)
  • 可以預(yù)先生成token,減少請求處理時的延遲

缺點(diǎn)

  • 需要兩次請求才能完成一次業(yè)務(wù)操作
  • 增加了客戶端的復(fù)雜度
  • 依賴Redis等外部存儲

2. 基于數(shù)據(jù)庫唯一約束的冪等性實(shí)現(xiàn)

利用數(shù)據(jù)庫的唯一約束特性可以簡單有效地實(shí)現(xiàn)冪等性。當(dāng)嘗試插入重復(fù)數(shù)據(jù)時,數(shù)據(jù)庫會拋出唯一約束異常,我們可以捕獲這個異常并進(jìn)行合適的處理。

實(shí)現(xiàn)方式

  • 在關(guān)鍵業(yè)務(wù)表上添加唯一索引
  • 在插入數(shù)據(jù)時捕獲唯一約束異常
  • 根據(jù)業(yè)務(wù)需求決定是返回錯誤還是返回已存在的數(shù)據(jù)

代碼實(shí)現(xiàn)

@Service
public class PaymentServiceImpl implements PaymentService {

    @Autowired
    private PaymentRepository paymentRepository;
    
    @Transactional
    @Override
    public PaymentResponse processPayment(PaymentRequest request) {
        try {
            // 創(chuàng)建支付記錄,包含唯一業(yè)務(wù)標(biāo)識
            Payment payment = new Payment();
            payment.setOrderNo(request.getOrderNo());
            payment.setTransactionId(request.getTransactionId()); // 唯一交易ID
            payment.setAmount(request.getAmount());
            payment.setStatus(PaymentStatus.PROCESSING);
            payment.setCreateTime(new Date());
            
            // 保存支付記錄
            paymentRepository.save(payment);
            
            // 調(diào)用支付網(wǎng)關(guān)API
            // ...支付處理邏輯...
            
            // 更新支付狀態(tài)
            payment.setStatus(PaymentStatus.SUCCESS);
            paymentRepository.save(payment);
            
            return new PaymentResponse(true, "支付成功", payment.getId());
        } catch (DataIntegrityViolationException e) {
            // 捕獲唯一約束異常
            if (e.getCause() instanceof ConstraintViolationException) {
                // 冪等性處理 - 查詢已存在的支付記錄
                Payment existingPayment = paymentRepository
                        .findByTransactionId(request.getTransactionId())
                        .orElse(null);
                
                if (existingPayment != null) {
                    if (PaymentStatus.SUCCESS.equals(existingPayment.getStatus())) {
                        // 支付已成功處理,返回成功結(jié)果
                        return new PaymentResponse(true, "支付已處理", existingPayment.getId());
                    } else {
                        // 支付正在處理中,返回適當(dāng)提示
                        return new PaymentResponse(false, "支付處理中", existingPayment.getId());
                    }
                }
            }
            
            // 其他數(shù)據(jù)完整性問題
            log.error("支付失敗", e);
            return new PaymentResponse(false, "支付失敗", null);
        }
    }
}

// 支付實(shí)體類
@Entity
@Table(name = "payments")
public class Payment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String orderNo;
    
    @Column(unique = true) // 唯一約束
    private String transactionId;
    
    private BigDecimal amount;
    
    @Enumerated(EnumType.STRING)
    private PaymentStatus status;
    
    private Date createTime;
    
    // Getters and setters...
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 實(shí)現(xiàn)簡單,利用數(shù)據(jù)庫已有特性
  • 無需額外的存儲組件
  • 強(qiáng)一致性保證

缺點(diǎn)

  • 依賴數(shù)據(jù)庫的唯一約束特性
  • 可能導(dǎo)致頻繁的異常處理
  • 在高并發(fā)情況下可能成為性能瓶頸

3. 基于分布式鎖的冪等性實(shí)現(xiàn)

分布式鎖是實(shí)現(xiàn)冪等性的另一種有效方式,特別適合于高并發(fā)場景。通過對業(yè)務(wù)唯一標(biāo)識加鎖,可以確保同一時間只有一個請求能夠執(zhí)行業(yè)務(wù)邏輯。

實(shí)現(xiàn)方式

  • 使用Redis、Zookeeper等實(shí)現(xiàn)分布式鎖
  • 以請求的唯一標(biāo)識作為鎖的key
  • 在業(yè)務(wù)處理前獲取鎖,處理完成后釋放鎖

基于Redis的分布式鎖實(shí)現(xiàn)

@Service
public class InventoryServiceImpl implements InventoryService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    private static final String LOCK_PREFIX = "inventory:lock:";
    private static final long LOCK_EXPIRE = 10000; // 10秒
    
    @Override
    public DeductResponse deductInventory(DeductRequest request) {
        String lockKey = LOCK_PREFIX + request.getRequestId();
        String requestId = UUID.randomUUID().toString();
        
        try {
            // 嘗試獲取分布式鎖
            Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
            
            if (Boolean.FALSE.equals(acquired)) {
                // 獲取鎖失敗,說明可能是重復(fù)請求
                return new DeductResponse(false, "請求正在處理中,請勿重復(fù)提交");
            }
            
            // 查詢是否已處理過該請求
            Optional<InventoryRecord> existingRecord = inventoryRepository.findByRequestId(request.getRequestId());
            if (existingRecord.isPresent()) {
                // 冪等性控制 - 請求已處理過
                return new DeductResponse(true, "庫存已扣減", existingRecord.get().getId());
            }
            
            // 執(zhí)行庫存扣減邏輯
            Inventory inventory = inventoryRepository.findByProductId(request.getProductId())
                    .orElseThrow(() -> new BusinessException("商品不存在"));
                    
            if (inventory.getStock() < request.getQuantity()) {
                throw new BusinessException("庫存不足");
            }
            
            // 扣減庫存
            inventory.setStock(inventory.getStock() - request.getQuantity());
            inventoryRepository.save(inventory);
            
            // 記錄庫存操作
            InventoryRecord record = new InventoryRecord();
            record.setRequestId(request.getRequestId());
            record.setProductId(request.getProductId());
            record.setQuantity(request.getQuantity());
            record.setCreateTime(new Date());
            inventoryRepository.save(record);
            
            return new DeductResponse(true, "庫存扣減成功", record.getId());
        } catch (BusinessException e) {
            return new DeductResponse(false, e.getMessage(), null);
        } catch (Exception e) {
            log.error("庫存扣減失敗", e);
            return new DeductResponse(false, "庫存扣減失敗", null);
        } finally {
            // 釋放鎖,注意只釋放自己的鎖
            if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
    }
}

使用Redisson簡化實(shí)現(xiàn)

@Service
public class InventoryServiceImpl implements InventoryService {

    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    private static final String LOCK_PREFIX = "inventory:lock:";
    
    @Override
    public DeductResponse deductInventory(DeductRequest request) {
        String lockKey = LOCK_PREFIX + request.getRequestId();
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 嘗試獲取鎖,等待5秒,鎖過期時間10秒
            boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS);
            
            if (!acquired) {
                return new DeductResponse(false, "請求正在處理中,請勿重復(fù)提交");
            }
            
            // 查詢是否已處理過該請求
            // ...后續(xù)業(yè)務(wù)邏輯與前面例子相同...
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return new DeductResponse(false, "請求被中斷", null);
        } catch (Exception e) {
            log.error("庫存扣減失敗", e);
            return new DeductResponse(false, "庫存扣減失敗", null);
        } finally {
            // 釋放鎖
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 適用于高并發(fā)場景
  • 可以與其他冪等性策略結(jié)合使用
  • 提供較好的實(shí)時性控制

缺點(diǎn)

  • 實(shí)現(xiàn)復(fù)雜度較高
  • 依賴外部存儲服務(wù)

4. 基于請求內(nèi)容摘要的冪等性實(shí)現(xiàn)

這種方案通過計算請求內(nèi)容的哈希值或摘要,生成唯一標(biāo)識作為冪等鍵,確保相同內(nèi)容的請求只處理一次。

實(shí)現(xiàn)方式

  • 計算請求參數(shù)的摘要值(如MD5, SHA-256等)
  • 將摘要值作為冪等鍵存儲在Redis或數(shù)據(jù)庫中
  • 請求處理前先檢查該摘要值是否已存在
  • 存在則表示重復(fù)請求,不執(zhí)行業(yè)務(wù)邏輯

代碼實(shí)現(xiàn)

@RestController
@RequestMapping("/api")
public class TransferController {

    @Autowired
    private TransferService transferService;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @PostMapping("/transfer")
    public Result<TransferResult> transfer(@RequestBody TransferRequest request) {
        // 生成請求摘要作為冪等鍵
        String idempotentKey = generateIdempotentKey(request);
        String redisKey = "idempotent:digest:" + idempotentKey;
        
        // 嘗試在Redis中設(shè)置冪等鍵,使用SetNX操作確保原子性
        Boolean isFirstRequest = redisTemplate.opsForValue()
                .setIfAbsent(redisKey, "processed", 24, TimeUnit.HOURS);
        
        // 如果鍵已存在,說明是重復(fù)請求
        if (Boolean.FALSE.equals(isFirstRequest)) {
            // 查詢處理結(jié)果(也可以直接存儲處理結(jié)果)
            TransferRecord record = transferService.findByIdempotentKey(idempotentKey);
            
            if (record != null) {
                // 返回之前的處理結(jié)果
                return Result.success(new TransferResult(
                        record.getTransactionId(), 
                        "交易已處理", 
                        record.getAmount(),
                        record.getStatus()));
            } else {
                // 冪等鍵存在但找不到記錄,可能正在處理
                return Result.fail("請求正在處理中,請勿重復(fù)提交");
            }
        }
        
        try {
            // 執(zhí)行轉(zhuǎn)賬業(yè)務(wù)邏輯
            TransferResult result = transferService.executeTransfer(request, idempotentKey);
            return Result.success(result);
        } catch (Exception e) {
            // 處理失敗時,刪除冪等鍵,允許客戶端重試
            // 或者可以保留鍵但記錄失敗狀態(tài),取決于業(yè)務(wù)需求
            redisTemplate.delete(redisKey);
            return Result.fail("轉(zhuǎn)賬處理失敗: " + e.getMessage());
        }
    }
    
    /**
     * 生成請求內(nèi)容摘要作為冪等鍵
     */
    private String generateIdempotentKey(TransferRequest request) {
        // 組合關(guān)鍵字段,確保能唯一標(biāo)識業(yè)務(wù)操作
        String content = request.getFromAccount() 
                + "|" + request.getToAccount() 
                + "|" + request.getAmount().toString()
                + "|" + request.getRequestTime();
        
        // 計算MD5摘要
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(content.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(digest);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("生成冪等鍵失敗", e);
        }
    }
}

@Service
public class TransferServiceImpl implements TransferService {

    @Autowired
    private TransferRecordRepository transferRecordRepository;
    
    @Autowired
    private AccountRepository accountRepository;
    
    @Override
    @Transactional
    public TransferResult executeTransfer(TransferRequest request, String idempotentKey) {
        // 執(zhí)行轉(zhuǎn)賬業(yè)務(wù)邏輯
        // 1. 檢查賬戶余額
        // 2. 扣減來源賬戶
        // 3. 增加目標(biāo)賬戶
        
        // 生成交易ID
        String transactionId = UUID.randomUUID().toString();
        
        // 保存交易記錄,包含冪等鍵
        TransferRecord record = new TransferRecord();
        record.setTransactionId(transactionId);
        record.setFromAccount(request.getFromAccount());
        record.setToAccount(request.getToAccount());
        record.setAmount(request.getAmount());
        record.setIdempotentKey(idempotentKey);
        record.setStatus(TransferStatus.SUCCESS);
        record.setCreateTime(new Date());
        
        transferRecordRepository.save(record);
        
        return new TransferResult(
                transactionId,
                "轉(zhuǎn)賬成功",
                request.getAmount(),
                TransferStatus.SUCCESS);
    }
    
    @Override
    public TransferRecord findByIdempotentKey(String idempotentKey) {
        return transferRecordRepository.findByIdempotentKey(idempotentKey).orElse(null);
    }
}

// 轉(zhuǎn)賬記錄實(shí)體
@Entity
@Table(name = "transfer_records", indexes = {
    @Index(name = "idx_idempotent_key", columnList = "idempotent_key", unique = true)
})
public class TransferRecord {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String transactionId;
    
    private String fromAccount;
    
    private String toAccount;
    
    private BigDecimal amount;
    
    @Column(name = "idempotent_key")
    private String idempotentKey;
    
    @Enumerated(EnumType.STRING)
    private TransferStatus status;
    
    private Date createTime;
    
    // Getters and setters...
}

使用自定義注解簡化實(shí)現(xiàn)

// 自定義冪等性注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    /**
     * 過期時間(秒)
     */
    int expireSeconds() default 86400; // 默認(rèn)24小時
    
    /**
     * 冪等鍵來源,可從請求體、請求參數(shù)等提取
     */
    KeySource source() default KeySource.REQUEST_BODY;
    
    /**
     * 提取參數(shù)的表達(dá)式(如SpEL表達(dá)式)
     */
    String[] expression() default {};
    
    enum KeySource {
        REQUEST_BODY,  // 請求體
        PATH_VARIABLE, // 路徑變量
        REQUEST_PARAM, // 請求參數(shù)
        CUSTOM        // 自定義
    }
}

// AOP實(shí)現(xiàn)
@Aspect
@Component
public class IdempotentAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        // 獲取請求參數(shù)
        Object[] args = joinPoint.getArgs();
        
        // 根據(jù)注解配置生成冪等鍵
        String idempotentKey = generateKey(joinPoint, idempotent);
        String redisKey = "idempotent:digest:" + idempotentKey;
        
        // 檢查是否重復(fù)請求
        Boolean setSuccess = redisTemplate.opsForValue()
                .setIfAbsent(redisKey, "processing", idempotent.expireSeconds(), TimeUnit.SECONDS);
        
        if (Boolean.FALSE.equals(setSuccess)) {
            // 獲取存儲的處理結(jié)果
            String value = redisTemplate.opsForValue().get(redisKey);
            
            if ("processing".equals(value)) {
                throw new BusinessException("請求正在處理中,請勿重復(fù)提交");
            } else if (value != null) {
                // 已處理,返回緩存的結(jié)果
                return JSON.parseObject(value, Object.class);
            }
        }
        
        try {
            // 執(zhí)行實(shí)際方法
            Object result = joinPoint.proceed();
            
            // 存儲處理結(jié)果
            redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(result), 
                    idempotent.expireSeconds(), TimeUnit.SECONDS);
            
            return result;
        } catch (Exception e) {
            // 處理失敗,刪除鍵允許重試
            redisTemplate.delete(redisKey);
            throw e;
        }
    }
    
    /**
     * 根據(jù)注解配置生成冪等鍵
     */
    private String generateKey(ProceedingJoinPoint joinPoint, Idempotent idempotent) {
        // 提取請求參數(shù),根據(jù)KeySource和expression生成摘要
        // 實(shí)際實(shí)現(xiàn)會更復(fù)雜,這里簡化
        String content = "";
        
        // 計算MD5摘要
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(content.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(digest);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("生成冪等鍵失敗", e);
        }
    }
}

// 控制器使用注解
@RestController
@RequestMapping("/api")
public class TransferController {

    @Autowired
    private TransferService transferService;
    
    @PostMapping("/transfer")
    @Idempotent(expireSeconds = 3600, source = KeySource.REQUEST_BODY, 
                expression = {"fromAccount", "toAccount", "amount", "requestTime"})
    public Result<TransferResult> transfer(@RequestBody TransferRequest request) {
        // 執(zhí)行轉(zhuǎn)賬業(yè)務(wù)邏輯
        TransferResult result = transferService.executeTransfer(request);
        return Result.success(result);
    }
}

優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 方案更通用
  • 實(shí)現(xiàn)相對簡單,易于集成
  • 對客戶端友好,不需要額外的token請求

缺點(diǎn)

  • 哈希計算有一定性能開銷
  • 表單數(shù)據(jù)順序變化可能導(dǎo)致不同的摘要值

總結(jié)

冪等性設(shè)計是系統(tǒng)穩(wěn)定性和可靠性的重要保障,通過合理選擇和實(shí)現(xiàn)冪等性策略,可以有效防止因重復(fù)請求導(dǎo)致的數(shù)據(jù)不一致問題。在實(shí)際項(xiàng)目中,應(yīng)根據(jù)具體的業(yè)務(wù)需求和系統(tǒng)架構(gòu),選擇最適合的冪等性實(shí)現(xiàn)方案。

以上就是SpringBoot中4種接口冪等性的實(shí)現(xiàn)策略的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot接口冪等性的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • springboot整合SSE技術(shù)開發(fā)小結(jié)

    springboot整合SSE技術(shù)開發(fā)小結(jié)

    本文主要介紹了springboot整合SSE技術(shù)開發(fā)小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • SpringBoot使用classfinal-maven-plugin插件加密Jar包的示例代碼

    SpringBoot使用classfinal-maven-plugin插件加密Jar包的示例代碼

    這篇文章給大家介紹了SpringBoot使用classfinal-maven-plugin插件加密Jar包的實(shí)例,文中通過代碼示例和圖文講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-02-02
  • SpringMVC中controller接收json數(shù)據(jù)的方法

    SpringMVC中controller接收json數(shù)據(jù)的方法

    這篇文章主要為大家詳細(xì)介紹了SpringMVC中controller接收json數(shù)據(jù)的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • 基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的方法

    基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的方法

    最近項(xiàng)目在線上運(yùn)行出現(xiàn)了一些難以復(fù)現(xiàn)的bug需要定位相應(yīng)api的日志,通過nginx提供的api請求日志難以實(shí)現(xiàn),于是在gateway通過全局過濾器記錄api請求日志,本文給大家介紹基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄,感興趣的朋友一起看看吧
    2023-11-11
  • spring boot+mybatis搭建一個后端restfull服務(wù)的實(shí)例詳解

    spring boot+mybatis搭建一個后端restfull服務(wù)的實(shí)例詳解

    這篇文章主要介紹了spring boot+mybatis搭建一個后端restfull服務(wù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • 基于Java實(shí)現(xiàn)動態(tài)切換ubuntu壁紙功能

    基于Java實(shí)現(xiàn)動態(tài)切換ubuntu壁紙功能

    這篇文章主要為大家詳細(xì)介紹了如何使用 Java 在 Ubuntu Linux 系統(tǒng)中實(shí)現(xiàn)自動切換壁紙的示例程序,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-11-11
  • jvm信息jmap使用的基本方法教程

    jvm信息jmap使用的基本方法教程

    JDK本身提供了很多方便的JVM性能調(diào)優(yōu)監(jiān)控工具,除了集成式的VisualVM和jConsole外,還有jps、jstack、jmap、jhat、jstat等小巧的工具,下面這篇文章主要給大家介紹了關(guān)于jvm信息jmap使用的基本方法教程,需要的朋友可以參考下
    2018-08-08
  • Ajax實(shí)現(xiàn)省市區(qū)三級聯(lián)動

    Ajax實(shí)現(xiàn)省市區(qū)三級聯(lián)動

    這篇文章主要為大家詳細(xì)介紹了jQuery ajax實(shí)現(xiàn)省市縣三級聯(lián)動的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能幫助到你
    2021-07-07
  • 細(xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)

    細(xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)

    下面小編就為大家?guī)硪黄?xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • Java/Spring項(xiàng)目的包開頭為什么是com詳解

    Java/Spring項(xiàng)目的包開頭為什么是com詳解

    這篇文章主要介紹了Java/Spring項(xiàng)目的包開頭為什么是com的相關(guān)資料,在Java中包命名遵循域名反轉(zhuǎn)規(guī)則,即使用公司的域名反轉(zhuǎn)作為包的前綴,以確保其全球唯一性和避免命名沖突,這種規(guī)則有助于邏輯分層、代碼可讀性提升和標(biāo)識代碼來源,需要的朋友可以參考下
    2024-10-10

最新評論