最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實(shí)現(xiàn)加密字段模糊查詢的最佳實(shí)踐

 更新時(shí)間:2026年02月13日 08:57:58   作者:小沈同學(xué)呀  
在數(shù)據(jù)安全日益重要的今天,數(shù)據(jù)庫加密已經(jīng)成為企業(yè)級(jí)應(yīng)用的標(biāo)配,然而,加密字段的模糊查詢一直是一個(gè)技術(shù)難題,本文將深入探討Spring Boot環(huán)境下實(shí)現(xiàn)加密字段模糊查詢的主流方案,結(jié)合理論分析和可直接用于生產(chǎn)的代碼示例,需要的朋友可以參考下

前言

在數(shù)據(jù)安全日益重要的今天,數(shù)據(jù)庫加密已經(jīng)成為企業(yè)級(jí)應(yīng)用的標(biāo)配。然而,加密字段的模糊查詢一直是一個(gè)技術(shù)難題。傳統(tǒng)的加密方式(如AES)會(huì)將明文轉(zhuǎn)換為完全隨機(jī)的密文,導(dǎo)致無法直接使用LIKE語句進(jìn)行模糊匹配。本文將深入探討Spring Boot環(huán)境下實(shí)現(xiàn)加密字段模糊查詢的主流方案,結(jié)合理論分析和可直接用于生產(chǎn)的代碼示例,幫助開發(fā)者在保證數(shù)據(jù)安全的同時(shí),兼顧業(yè)務(wù)查詢需求

問題分析

1.1 傳統(tǒng)加密的局限性

傳統(tǒng)對(duì)稱加密(如AES)和非對(duì)稱加密(如RSA)都存在以下問題:

  • 加密后的密文與明文沒有語義關(guān)聯(lián)
  • 無法直接對(duì)密文進(jìn)行模糊查詢
  • 暴力破解風(fēng)險(xiǎn)高(尤其是短密碼)

1.2 常見解決方案對(duì)比

方案優(yōu)點(diǎn)缺點(diǎn)適用場(chǎng)景
明文存儲(chǔ)查詢性能好數(shù)據(jù)安全風(fēng)險(xiǎn)高非敏感數(shù)據(jù)
哈希加密安全性高無法反向解密密碼存儲(chǔ)
同態(tài)加密支持密文運(yùn)算性能開銷大高安全性要求場(chǎng)景
格式保留加密保持?jǐn)?shù)據(jù)格式實(shí)現(xiàn)復(fù)雜需要保持?jǐn)?shù)據(jù)格式的場(chǎng)景
部分加密平衡安全與性能部分?jǐn)?shù)據(jù)暴露非核心敏感數(shù)據(jù)

主流技術(shù)方案

1 格式保留加密(Format-Preserving Encryption)

1.1 理論基礎(chǔ)

加密特點(diǎn):

  1. 密文與明文具有相同的格式(如都是數(shù)字字符串)
  2. 密文長(zhǎng)度與明文長(zhǎng)度完全一致
  3. 提供完整性保護(hù)
  4. 支持關(guān)聯(lián)數(shù)據(jù)(AAD)

實(shí)現(xiàn)原理:
使用確定性加密算法對(duì)明文進(jìn)行加密,然后使用哈希函數(shù)和模運(yùn)算
將加密結(jié)果映射到與明文相同格式的字符集上,同時(shí)保存足夠的信息以便解密。

1.2 依賴配置

增加pom配置依賴

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>com.google.crypto.tink</groupId>
        <artifactId>tink</artifactId>
        <version>1.10.0</version>
    </dependency>
</dependencies>

1.3 完整實(shí)現(xiàn)代碼

FPE加密配置實(shí)現(xiàn)TrueFormatPreservingEncryption:

import com.google.crypto.tink.DeterministicAead;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.daead.DeterministicAeadConfig;
import com.google.crypto.tink.daead.DeterministicAeadFactory;
import com.google.crypto.tink.daead.DeterministicAeadKeyTemplates;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

/**
 * 真正的格式保留加密實(shí)現(xiàn)
 * @author senfel
 * @date 2026/2/12 15:06
 */
public class TrueFormatPreservingEncryption {
    private final DeterministicAead daead;
    private final MessageDigest hashFunction;

    /**
     * 構(gòu)造函數(shù)
     * @throws GeneralSecurityException 如果密鑰生成或初始化失敗
     */
    public TrueFormatPreservingEncryption() throws GeneralSecurityException {
        // 注冊(cè)確定性 AEAD 配置
        DeterministicAeadConfig.register();
        // 創(chuàng)建 AES-SIV 密鑰模板
        KeysetHandle keysetHandle = KeysetHandle.generateNew(
                DeterministicAeadKeyTemplates.AES256_SIV
        );
        // 獲取確定性 AEAD 原語
        this.daead = DeterministicAeadFactory.getPrimitive(keysetHandle);
        // 初始化哈希函數(shù)用于格式轉(zhuǎn)換
        try {
            this.hashFunction = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new GeneralSecurityException("無法初始化哈希函數(shù)", e);
        }
    }

    /**
     * 加密明文
     * @param plaintext 要加密的明文(如手機(jī)號(hào)、身份證號(hào)等)
     * @return 加密后的密文,與明文具有相同的格式和長(zhǎng)度
     * @throws GeneralSecurityException 如果加密失敗
     */
    public String encrypt(String plaintext) throws GeneralSecurityException {
        if (plaintext == null || plaintext.isEmpty()) {
            return plaintext;
        }
        // 將明文轉(zhuǎn)換為字節(jié)數(shù)組
        byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
        // 使用確定性加密算法加密
        byte[] encryptedBytes = daead.encryptDeterministically(plaintextBytes, null);
        // 將加密結(jié)果轉(zhuǎn)換為與原始格式相同的字符串
        // 使用加密后的字節(jié)數(shù)組作為種子,通過哈希和模運(yùn)算生成格式保留的密文
        return convertToSameFormat(encryptedBytes, plaintext);
    }

    /**
     * 解密密文
     * @param ciphertext 要解密的密文(格式保留的字符串)
     * @param originalPlaintext 原始明文(用于確定格式)
     * @return 解密后的明文
     * @throws GeneralSecurityException 如果解密失敗
     */
    public String decrypt(String ciphertext, String originalPlaintext) throws GeneralSecurityException {
        if (ciphertext == null || ciphertext.isEmpty()) {
            return ciphertext;
        }
        // 由于格式保留加密是不可逆的(信息丟失),我們需要使用暴力搜索或字典
        // 但這不是真正的格式保留加密的正確實(shí)現(xiàn)方式

        // 更好的方法:我們需要存儲(chǔ)加密后的完整字節(jié)數(shù)組
        // 但為了保持格式,我們可以使用一個(gè)映射表

        // 實(shí)際上,真正的格式保留加密需要使用 FF1 或 FF3 算法
        // 這里我們提供一個(gè)改進(jìn)的實(shí)現(xiàn):使用加密后的字節(jié)數(shù)組的哈希值來生成格式保留的密文
        // 同時(shí)我們需要能夠恢復(fù)原始加密字節(jié)數(shù)組
        
        throw new GeneralSecurityException("格式保留加密的解密需要原始加密上下文,當(dāng)前實(shí)現(xiàn)不支持直接解密格式保留的密文");
    }

    /**
     * 改進(jìn)的解密方法:需要存儲(chǔ)完整的加密信息
     * 在實(shí)際應(yīng)用中,應(yīng)該將加密后的完整字節(jié)數(shù)組(Base64編碼)與格式保留的密文一起存儲(chǔ)
     * 
     * @param encryptedBytesBase64 Base64編碼的加密字節(jié)數(shù)組
     * @return 解密后的明文
     * @throws GeneralSecurityException 如果解密失敗
     */
    public String decryptFromBase64(String encryptedBytesBase64) throws GeneralSecurityException {
        if (encryptedBytesBase64 == null || encryptedBytesBase64.isEmpty()) {
            return "";
        }

        try {
            // 將Base64字符串解碼為字節(jié)數(shù)組
            byte[] encryptedBytes = java.util.Base64.getDecoder().decode(encryptedBytesBase64);
            // 解密
            byte[] decrypted = daead.decryptDeterministically(encryptedBytes, null);
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (IllegalArgumentException e) {
            throw new GeneralSecurityException("Base64解碼失敗", e);
        }
    }

    /**
     * 改進(jìn)的加密方法:返回格式保留的密文和Base64編碼的完整加密數(shù)據(jù)
     * 
     * @param plaintext 要加密的明文
     * @return 包含格式保留密文和完整加密數(shù)據(jù)的對(duì)象
     * @throws GeneralSecurityException 如果加密失敗
     */
    public EncryptionResult encryptWithFullData(String plaintext) throws GeneralSecurityException {
        if (plaintext == null || plaintext.isEmpty()) {
            return new EncryptionResult("", "");
        }
        // 將明文轉(zhuǎn)換為字節(jié)數(shù)組
        byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
        // 使用確定性加密算法加密
        byte[] encryptedBytes = daead.encryptDeterministically(plaintextBytes, null);
        // 將加密結(jié)果轉(zhuǎn)換為與原始格式相同的字符串
        String formatPreservingCiphertext = convertToSameFormat(encryptedBytes, plaintext);
        // 將完整的加密字節(jié)數(shù)組編碼為Base64
        String encryptedBytesBase64 = java.util.Base64.getEncoder().encodeToString(encryptedBytes);
        return new EncryptionResult(formatPreservingCiphertext, encryptedBytesBase64);
    }

    /**
     * 將加密結(jié)果轉(zhuǎn)換為與原始格式相同的字符串
     * 使用加密字節(jié)數(shù)組的哈希值作為隨機(jī)種子,確保相同輸入產(chǎn)生相同輸出(確定性)
     * 
     * @param encryptedBytes 加密后的字節(jié)數(shù)組
     * @param plaintext 原始明文(用于確定格式)
     * @return 與原始格式相同的字符串
     */
    private String convertToSameFormat(byte[] encryptedBytes, String plaintext) {
        StringBuilder sb = new StringBuilder();
        // 使用加密字節(jié)數(shù)組生成確定性哈希值
        hashFunction.reset();
        hashFunction.update(encryptedBytes);
        byte[] hash = hashFunction.digest();
        // 為了處理長(zhǎng)字符串,我們需要擴(kuò)展哈希值
        List<Byte> hashBytes = new ArrayList<>();
        for (byte b : hash) {
            hashBytes.add(b);
        }
        // 如果哈希值不夠長(zhǎng),循環(huán)使用
        int hashIndex = 0;
        for (int i = 0; i < plaintext.length(); i++) {
            char originalChar = plaintext.charAt(i);
            
            // 獲取用于生成該字符的哈希字節(jié)
            byte hashByte = hashBytes.get(hashIndex % hashBytes.size());
            hashIndex++;
            
            // 為了增加隨機(jī)性,也使用位置索引
            hashFunction.reset();
            hashFunction.update(encryptedBytes);
            hashFunction.update(String.valueOf(i).getBytes(StandardCharsets.UTF_8));
            byte[] positionHash = hashFunction.digest();
            int combinedValue = (hashByte & 0xFF) ^ (positionHash[0] & 0xFF);

            if (Character.isDigit(originalChar)) {
                // 數(shù)字格式:將哈希值映射到 0-9
                sb.append((char) ('0' + (combinedValue % 10)));
            } else if (Character.isLetter(originalChar)) {
                // 字母格式:將哈希值映射到字母
                int offset = Character.isUpperCase(originalChar) ? 'A' : 'a';
                sb.append((char) (offset + (combinedValue % 26)));
            } else {
                // 其他格式:保持原始字符(但這不是安全的,可以考慮加密)
                sb.append(originalChar);
            }
        }
        
        return sb.toString();
    }

    /**
     * 加密結(jié)果類
     * 包含格式保留的密文和完整的加密數(shù)據(jù)(Base64編碼)
     */
    public static class EncryptionResult {
        private final String formatPreservingCiphertext; // 格式保留的密文
        private final String encryptedBytesBase64; // Base64編碼的完整加密數(shù)據(jù)
        public EncryptionResult(String formatPreservingCiphertext, String encryptedBytesBase64) {
            this.formatPreservingCiphertext = formatPreservingCiphertext;
            this.encryptedBytesBase64 = encryptedBytesBase64;
        }
        public String getFormatPreservingCiphertext() {
            return formatPreservingCiphertext;
        }
        public String getEncryptedBytesBase64() {
            return encryptedBytesBase64;
        }
    }
}

1.4 測(cè)試用例

寫一個(gè)簡(jiǎn)單的FPE測(cè)試用例:

/**
 * TrueFormatPreservingEncryption測(cè)試
 * @param args 命令行參數(shù)
 * @author senfel
 * @date 2026/2/12 15:06
 * @return void
 */
public static void main(String[] args) {
    try {
        TrueFormatPreservingEncryption fpe = new TrueFormatPreservingEncryption();
        String plaintext = "13800138000";

        System.out.println("=== 測(cè)試格式保留加密 ===");
        
        // 方法1:僅格式保留加密(無法解密,因?yàn)樾畔G失)
        //明文: 13800138000
        //格式保留密文: 39508471166
        //明文長(zhǎng)度: 11
        //密文長(zhǎng)度: 11
        //明文格式是否保留: true
        String formatPreservingCiphertext = fpe.encrypt(plaintext);
        System.out.println("明文: " + plaintext);
        System.out.println("格式保留密文: " + formatPreservingCiphertext);
        System.out.println("明文長(zhǎng)度: " + plaintext.length());
        System.out.println("密文長(zhǎng)度: " + formatPreservingCiphertext.length());
        System.out.println("明文格式是否保留: " + isSameFormat(plaintext, formatPreservingCiphertext));
        System.out.println();

        // 方法2:格式保留加密 + 完整加密數(shù)據(jù)(可解密)
        EncryptionResult result = fpe.encryptWithFullData(plaintext);
        System.out.println("=== 使用完整數(shù)據(jù)加密 ===");
        //明文: 13800138000
        //格式保留密文: 39508471166
        //完整加密數(shù)據(jù)(Base64): AXRF5PpcdHbhqKhwTZAEkYwBlHrkQJ2J9PFRg8ApXtw=
        //格式是否保留: true
        //解密結(jié)果: 13800138000
        //加密解密是否成功: true
        System.out.println("明文: " + plaintext);
        System.out.println("格式保留密文: " + result.getFormatPreservingCiphertext());
        System.out.println("完整加密數(shù)據(jù)(Base64): " + result.getEncryptedBytesBase64());
        System.out.println("格式是否保留: " + isSameFormat(plaintext, result.getFormatPreservingCiphertext()));
        
        // 解密
        String decrypted = fpe.decryptFromBase64(result.getEncryptedBytesBase64());
        System.out.println("解密結(jié)果: " + decrypted);
        System.out.println("加密解密是否成功: " + plaintext.equals(decrypted));
        
        // 測(cè)試多次加密同一明文,驗(yàn)證確定性
        System.out.println();
        System.out.println("=== 測(cè)試確定性加密 ===");
        //第一次加密: 39508471166
        //第二次加密: 39508471166
        //兩次加密結(jié)果是否相同(確定性): true
        String ciphertext1 = fpe.encrypt(plaintext);
        String ciphertext2 = fpe.encrypt(plaintext);
        System.out.println("第一次加密: " + ciphertext1);
        System.out.println("第二次加密: " + ciphertext2);
        System.out.println("兩次加密結(jié)果是否相同(確定性): " + ciphertext1.equals(ciphertext2));
        
    } catch (GeneralSecurityException e) {
        System.err.println("加密解密失敗: " + e.getMessage());
        e.printStackTrace();
    }
}

/**
 * 檢查兩個(gè)字符串是否具有相同的格式
 * @param s1 第一個(gè)字符串
 * @param s2 第二個(gè)字符串
 * @return 是否具有相同的格式
 */
private static boolean isSameFormat(String s1, String s2) {
    if (s1.length() != s2.length()) {
        return false;
    }
    for (int i = 0; i < s1.length(); i++) {
        char c1 = s1.charAt(i);
        char c2 = s2.charAt(i);
        if (Character.isDigit(c1) != Character.isDigit(c2)) {
            return false;
        }
        if (Character.isLetter(c1) != Character.isLetter(c2)) {
            return false;
        }
        if (Character.isUpperCase(c1) != Character.isUpperCase(c2)) {
            return false;
        }
    }
    return true;
}

2 AES部分加密與明文索引

2.1 理論基礎(chǔ)

加密特點(diǎn):

  1. 對(duì)敏感數(shù)據(jù)(如身份證號(hào))進(jìn)行部分加密
  2. 加密核心部分(前6位+后4位),保留中間部分作為明文索引
  3. 支持通過索引部分進(jìn)行模糊查詢,然后解密驗(yàn)證完整匹配

使用場(chǎng)景:
身份證號(hào):加密前6位(地區(qū)碼)+ 后4位(校驗(yàn)位),保留中間8位(出生日期)作為索引
手機(jī)號(hào):可以加密前3位+后4位,保留中間部分作為索引

2.2 完整實(shí)現(xiàn)代碼

增加一個(gè)用戶類:

import lombok.Getter;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * AES部分加密與明文索引
 * @author senfel
 * @date 2026/2/12
 */
public class AESPartialEncryption {
    
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES";
    private final SecretKeySpec secretKey;
    
    /**
     * 構(gòu)造函數(shù)
     *
     * @param key AES密鑰(必須是16、24或32字節(jié))
     * @throws IllegalArgumentException 如果密鑰長(zhǎng)度不正確
     */
    public AESPartialEncryption(String key) {
        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("密鑰不能為空");
        }
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        int keyLength = keyBytes.length;
        // AES密鑰長(zhǎng)度必須是16、24或32字節(jié)
        if (keyLength != 16 && keyLength != 24 && keyLength != 32) {
            throw new IllegalArgumentException(
                String.format("AES密鑰長(zhǎng)度必須是16、24或32字節(jié),當(dāng)前長(zhǎng)度:%d", keyLength)
            );
        }
        this.secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
    }
    
    /**
     * 使用默認(rèn)密鑰創(chuàng)建實(shí)例
     * 
     * @return AESPartialEncryption實(shí)例
     */
    public static AESPartialEncryption createDefault() {
        return new AESPartialEncryption("1234567890123456");
    }
    
    /**
     * 加密字符串
     * 
     * @param plaintext 要加密的明文
     * @return Base64編碼的密文
     * @throws Exception 如果加密失敗
     */
    public String encrypt(String plaintext) throws Exception {
        if (plaintext == null || plaintext.isEmpty()) {
            return plaintext;
        }
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
    
    /**
     * 解密字符串
     * 
     * @param ciphertext Base64編碼的密文
     * @return 解密后的明文
     * @throws Exception 如果解密失敗
     */
    public String decrypt(String ciphertext) throws Exception {
        if (ciphertext == null || ciphertext.isEmpty()) {
            return ciphertext;
        }
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    
    /**
     * 部分加密身份證號(hào)
     * 加密前6位(地區(qū)碼)+ 后4位(校驗(yàn)位),保留中間8位(出生日期)作為索引
     * 
     * @param idCard 身份證號(hào)(18位)
     * @return 加密結(jié)果對(duì)象,包含加密的核心部分和明文索引部分
     * @throws Exception 如果加密失敗或身份證號(hào)格式不正確
     */
    public IdCardEncryptionResult encryptIdCard(String idCard) throws Exception {
        if (idCard == null || idCard.length() != 18) {
            throw new IllegalArgumentException("身份證號(hào)必須是18位");
        }
        // 提取核心部分:前6位 + 后4位
        String corePart = idCard.substring(0, 6) + idCard.substring(14);
        // 加密核心部分
        String encryptedPart = encrypt(corePart);
        // 保留中間部分作為索引:第7-14位(出生日期)
        String indexPart = idCard.substring(6, 14);
        return new IdCardEncryptionResult(encryptedPart, indexPart);
    }
    
    /**
     * 解密身份證號(hào)
     * 
     * @param encryptedPart 加密的核心部分
     * @param indexPart 明文索引部分(出生日期)
     * @return 完整的身份證號(hào)
     * @throws Exception 如果解密失敗
     */
    public String decryptIdCard(String encryptedPart, String indexPart) throws Exception {
        if (encryptedPart == null || encryptedPart.isEmpty()) {
            throw new IllegalArgumentException("加密部分不能為空");
        }
        if (indexPart == null || indexPart.length() != 8) {
            throw new IllegalArgumentException("索引部分必須是8位(出生日期)");
        }
        // 解密核心部分
        String decryptedCore = decrypt(encryptedPart);
        if (decryptedCore.length() != 10) {
            throw new IllegalArgumentException("解密后的核心部分長(zhǎng)度不正確");
        }
        // 組合完整身份證號(hào):前6位 + 中間8位(索引) + 后4位
        return decryptedCore.substring(0, 6) + indexPart + decryptedCore.substring(6);
    }
    
    /**
     * 部分加密手機(jī)號(hào)
     * 加密前3位(運(yùn)營(yíng)商號(hào)段)+ 后4位(用戶號(hào)),保留中間部分作為索引
     * 
     * @param phone 手機(jī)號(hào)(11位)
     * @return 加密結(jié)果對(duì)象
     * @throws Exception 如果加密失敗或手機(jī)號(hào)格式不正確
     */
    public PhoneEncryptionResult encryptPhone(String phone) throws Exception {
        if (phone == null || phone.length() != 11) {
            throw new IllegalArgumentException("手機(jī)號(hào)必須是11位");
        }
        // 提取核心部分:前3位 + 后4位
        String corePart = phone.substring(0, 3) + phone.substring(7);
        // 加密核心部分
        String encryptedPart = encrypt(corePart);
        // 保留中間部分作為索引:第4-7位
        String indexPart = phone.substring(3, 7);
        return new PhoneEncryptionResult(encryptedPart, indexPart);
    }
    
    /**
     * 解密手機(jī)號(hào)
     * 
     * @param encryptedPart 加密的核心部分
     * @param indexPart 明文索引部分
     * @return 完整的手機(jī)號(hào)
     * @throws Exception 如果解密失敗
     */
    public String decryptPhone(String encryptedPart, String indexPart) throws Exception {
        if (encryptedPart == null || encryptedPart.isEmpty()) {
            throw new IllegalArgumentException("加密部分不能為空");
        }
        if (indexPart == null || indexPart.length() != 4) {
            throw new IllegalArgumentException("索引部分必須是4位");
        }
        // 解密核心部分
        String decryptedCore = decrypt(encryptedPart);
        if (decryptedCore.length() != 7) {
            throw new IllegalArgumentException("解密后的核心部分長(zhǎng)度不正確");
        }
        // 組合完整手機(jī)號(hào):前3位 + 中間4位(索引) + 后4位
        return decryptedCore.substring(0, 3) + indexPart + decryptedCore.substring(3);
    }
    

    /**
     * 身份證號(hào)加密結(jié)果
     */
    @Getter
    public static class IdCardEncryptionResult {
        private final String encryptedPart; // 加密的核心部分(Base64編碼)
        private final String indexPart;     // 明文索引部分(出生日期,8位)
        
        public IdCardEncryptionResult(String encryptedPart, String indexPart) {
            this.encryptedPart = encryptedPart;
            this.indexPart = indexPart;
        }

        @Override
        public String toString() {
            return String.format("IdCardEncryptionResult{encryptedPart='%s', indexPart='%s'}", 
                encryptedPart, indexPart);
        }
    }
    
    /**
     * 手機(jī)號(hào)加密結(jié)果
     */
    @Getter
    public static class PhoneEncryptionResult {
        private final String encryptedPart; // 加密的核心部分(Base64編碼)
        private final String indexPart;     // 明文索引部分(4位)
        
        public PhoneEncryptionResult(String encryptedPart, String indexPart) {
            this.encryptedPart = encryptedPart;
            this.indexPart = indexPart;
        }

        @Override
        public String toString() {
            return String.format("PhoneEncryptionResult{encryptedPart='%s', indexPart='%s'}", 
                encryptedPart, indexPart);
        }
    }
}

2.3 測(cè)試用例

增加AES部分加密測(cè)試用例:

/**
 * AES部分加密測(cè)試
 * @param args
 * @author senfel
 * @date 2026/2/12 15:33
 * @return void
 */
public static void main(String[] args) {
    try {
        // 創(chuàng)建AES加密服務(wù)
        AESPartialEncryption aesService = new AESPartialEncryption("1234567890123456");
        
        System.out.println("=== 測(cè)試身份證號(hào)部分加密 ===");
        String idCard = "110101199001011234";
        System.out.println("原始身份證號(hào): " + idCard);
        
        // 加密
        IdCardEncryptionResult idCardResult = aesService.encryptIdCard(idCard);
        //加密結(jié)果: IdCardEncryptionResult{encryptedPart='8FiTU/zAnjzz4fJey48+Fw==', indexPart='19900101'}
        //加密的核心部分: 8FiTU/zAnjzz4fJey48+Fw==
        //明文索引部分(出生日期): 19900101
        System.out.println("加密結(jié)果: " + idCardResult);
        System.out.println("加密的核心部分: " + idCardResult.getEncryptedPart());
        System.out.println("明文索引部分(出生日期): " + idCardResult.getIndexPart());
        
        // 解密
        String decryptedIdCard = aesService.decryptIdCard(
            idCardResult.getEncryptedPart(), 
            idCardResult.getIndexPart()
        );
        //解密后的身份證號(hào): 110101199001011234
        //加密解密是否成功: true
        System.out.println("解密后的身份證號(hào): " + decryptedIdCard);
        System.out.println("加密解密是否成功: " + idCard.equals(decryptedIdCard));
        
        System.out.println("\n=== 測(cè)試手機(jī)號(hào)部分加密 ===");
        String phone = "13800138000";
        System.out.println("原始手機(jī)號(hào): " + phone);
        
        // 加密
        PhoneEncryptionResult phoneResult = aesService.encryptPhone(phone);
        //加密結(jié)果: PhoneEncryptionResult{encryptedPart='Dhf5t5O0Tn1sSwUKB/WKyg==', indexPart='0013'}
        //加密的核心部分: Dhf5t5O0Tn1sSwUKB/WKyg==
        //明文索引部分: 0013
        System.out.println("加密結(jié)果: " + phoneResult);
        System.out.println("加密的核心部分: " + phoneResult.getEncryptedPart());
        System.out.println("明文索引部分: " + phoneResult.getIndexPart());
        
        // 解密
        String decryptedPhone = aesService.decryptPhone(
            phoneResult.getEncryptedPart(), 
            phoneResult.getIndexPart()
        );
        //解密后的手機(jī)號(hào): 13800138000
        //加密解密是否成功: true
        System.out.println("解密后的手機(jī)號(hào): " + decryptedPhone);
        System.out.println("加密解密是否成功: " + phone.equals(decryptedPhone));
        
        System.out.println("\n=== 測(cè)試通用加密解密 ===");
        String plaintext = "HelloWorld123456";
        System.out.println("原始文本: " + plaintext);

        String encrypted = aesService.encrypt(plaintext);
        //加密后: d0Ae9K3M+jCUTX227btKawUBh6DN5amHLLqwkatz5VM=
        System.out.println("加密后: " + encrypted);
        
        String decrypted = aesService.decrypt(encrypted);
        //解密后: HelloWorld123456
        //加密解密是否成功: true
        System.out.println("解密后: " + decrypted);
        System.out.println("加密解密是否成功: " + plaintext.equals(decrypted));
        
    } catch (Exception e) {
        System.err.println("測(cè)試失敗: " + e.getMessage());
        e.printStackTrace();
    }
}

3 可搜索加密(Searchable Encryption)

3.1 理論基礎(chǔ)

加密特點(diǎn):

  1. 支持對(duì)文檔進(jìn)行加密,同時(shí)生成可搜索的索引
  2. 支持在不解密文檔的情況下,通過關(guān)鍵詞進(jìn)行搜索
  3. 使用對(duì)稱加密(AES)和HMAC實(shí)現(xiàn)可搜索加密

實(shí)現(xiàn)原理:
加密文檔時(shí),提取關(guān)鍵詞并為每個(gè)關(guān)鍵詞生成加密的索引token
搜索時(shí),使用關(guān)鍵詞生成trapdoor(陷阱門),與索引token匹配
匹配成功則說明文檔包含該關(guān)鍵詞,無需解密整個(gè)文檔

使用場(chǎng)景:
加密郵件搜索
加密數(shù)據(jù)庫查詢
云存儲(chǔ)中的加密文檔搜索

3.2 完整實(shí)現(xiàn)代碼

可搜索加密SearchableEncryption:

import lombok.Getter;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 可搜索加密
 * @author senfel
 * @version 2.0
 * @date 2026/2/12
 */
public class SearchableEncryption {
    
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES";
    private static final String HMAC_ALGORITHM = "HmacSHA256";
    // 用于加密文檔的密鑰
    private final SecretKeySpec encryptionKey;
    // 用于生成索引的密鑰
    private final SecretKeySpec indexKey;
    
    /**
     * 構(gòu)造函數(shù)
     * @param encryptionKey 加密密鑰(16、24或32字節(jié))
     * @param indexKey 索引密鑰(用于生成搜索索引)
     * @throws IllegalArgumentException 如果密鑰長(zhǎng)度不正確
     */
    public SearchableEncryption(String encryptionKey, String indexKey) {
        if (encryptionKey == null || encryptionKey.isEmpty()) {
            throw new IllegalArgumentException("加密密鑰不能為空");
        }
        if (indexKey == null || indexKey.isEmpty()) {
            throw new IllegalArgumentException("索引密鑰不能為空");
        }
        byte[] encKeyBytes = encryptionKey.getBytes(StandardCharsets.UTF_8);
        byte[] idxKeyBytes = indexKey.getBytes(StandardCharsets.UTF_8);
        validateKeyLength(encKeyBytes.length, "加密密鑰");
        validateKeyLength(idxKeyBytes.length, "索引密鑰");
        this.encryptionKey = new SecretKeySpec(encKeyBytes, ALGORITHM);
        this.indexKey = new SecretKeySpec(idxKeyBytes, HMAC_ALGORITHM);
    }
    
    /**
     * 使用默認(rèn)密鑰創(chuàng)建實(shí)例
     * @return SearchableEncryption實(shí)例
     */
    public static SearchableEncryption createDefault() {
        return new SearchableEncryption(
            "1234567890123456",  // 16字節(jié)加密密鑰
            "abcdefghijklmnop"   // 16字節(jié)索引密鑰
        );
    }
    
    /**
     * 驗(yàn)證密鑰長(zhǎng)度
     */
    private void validateKeyLength(int length, String keyName) {
        if (length != 16 && length != 24 && length != 32) {
            throw new IllegalArgumentException(
                String.format("%s長(zhǎng)度必須是16、24或32字節(jié),當(dāng)前長(zhǎng)度:%d", keyName, length)
            );
        }
    }
    
    /**
     * 加密文檔
     * @param plaintext 明文文檔
     * @param keywords 關(guān)鍵詞列表(用于生成搜索索引)
     * @return 加密結(jié)果,包含加密文檔和索引token列表
     * @throws Exception 如果加密失敗
     */
    public EncryptionResult encrypt(String plaintext, Set<String> keywords) throws Exception {
        if (plaintext == null || plaintext.isEmpty()) {
            throw new IllegalArgumentException("明文不能為空");
        }
        if (keywords == null || keywords.isEmpty()) {
            keywords = extractKeywords(plaintext);
        }
        // 加密文檔
        String encryptedDocument = encryptDocument(plaintext);
        // 為每個(gè)關(guān)鍵詞生成索引token
        List<String> indexTokens = new ArrayList<>();
        for (String keyword : keywords) {
            String token = generateIndexToken(keyword);
            indexTokens.add(token);
        }
        return new EncryptionResult(encryptedDocument, indexTokens);
    }
    
    /**
     * 加密文檔(不提取關(guān)鍵詞,需要手動(dòng)提供)
     * @param plaintext 明文文檔
     * @return Base64編碼的密文
     * @throws Exception 如果加密失敗
     */
    public String encryptDocument(String plaintext) throws Exception {
        if (plaintext == null || plaintext.isEmpty()) {
            return plaintext;
        }
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
        byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
        byte[] encryptedBytes = cipher.doFinal(plaintextBytes);
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
    
    /**
     * 解密文檔
     * @param encryptedDocument Base64編碼的密文
     * @return 解密后的明文
     * @throws Exception 如果解密失敗
     */
    public String decryptDocument(String encryptedDocument) throws Exception {
        if (encryptedDocument == null || encryptedDocument.isEmpty()) {
            return encryptedDocument;
        }
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, encryptionKey);
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedDocument);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    
    /**
     * 為關(guān)鍵詞生成索引token
     * 使用HMAC生成確定性的token,相同關(guān)鍵詞總是生成相同的token
     * @param keyword 關(guān)鍵詞
     * @return Base64編碼的索引token
     * @throws Exception 如果生成失敗
     */
    public String generateIndexToken(String keyword) throws Exception {
        if (keyword == null || keyword.isEmpty()) {
            throw new IllegalArgumentException("關(guān)鍵詞不能為空");
        }
        Mac mac = Mac.getInstance(HMAC_ALGORITHM);
        mac.init(indexKey);
        byte[] keywordBytes = keyword.toLowerCase().trim().getBytes(StandardCharsets.UTF_8);
        byte[] tokenBytes = mac.doFinal(keywordBytes);
        return Base64.getEncoder().encodeToString(tokenBytes);
    }
    
    /**
     * 生成搜索陷阱門(Trapdoor)
     * 與索引token使用相同的生成方法,確??梢云ヅ?
     *
     * @param keyword 搜索關(guān)鍵詞
     * @return Base64編碼的trapdoor
     * @throws Exception 如果生成失敗
     */
    public String generateTrapdoor(String keyword) throws Exception {
        // Trapdoor與IndexToken使用相同的生成方法
        return generateIndexToken(keyword);
    }
    
    /**
     * 搜索加密文檔
     * 通過比較trapdoor和索引token來判斷文檔是否包含關(guān)鍵詞
     * 
     * @param indexTokens 文檔的索引token列表
     * @param trapdoor 搜索關(guān)鍵詞的trapdoor
     * @return 如果文檔包含該關(guān)鍵詞返回true
     */
    public boolean search(List<String> indexTokens, String trapdoor) {
        if (indexTokens == null || indexTokens.isEmpty() || trapdoor == null || trapdoor.isEmpty()) {
            return false;
        }
        // 直接比較trapdoor和索引token
        return indexTokens.contains(trapdoor);
    }
    
    /**
     * 批量搜索多個(gè)關(guān)鍵詞
     * 
     * @param indexTokens 文檔的索引token列表
     * @param keywords 搜索關(guān)鍵詞列表
     * @return 匹配的關(guān)鍵詞集合
     * @throws Exception 如果生成trapdoor失敗
     */
    public Set<String> searchMultiple(List<String> indexTokens, Set<String> keywords) throws Exception {
        Set<String> matchedKeywords = new HashSet<>();
        if (indexTokens == null || indexTokens.isEmpty() || keywords == null || keywords.isEmpty()) {
            return matchedKeywords;
        }
        for (String keyword : keywords) {
            String trapdoor = generateTrapdoor(keyword);
            if (search(indexTokens, trapdoor)) {
                matchedKeywords.add(keyword);
            }
        }
        return matchedKeywords;
    }
    
    /**
     * 從文本中提取關(guān)鍵詞(簡(jiǎn)單實(shí)現(xiàn))
     * 實(shí)際應(yīng)用中可以使用更復(fù)雜的分詞和停用詞過濾
     * 
     * @param text 文本內(nèi)容
     * @return 關(guān)鍵詞集合
     */
    private Set<String> extractKeywords(String text) {
        Set<String> keywords = new HashSet<>();
        if (text == null || text.isEmpty()) {
            return keywords;
        }
        // 簡(jiǎn)單的關(guān)鍵詞提?。喊纯崭窈蜆?biāo)點(diǎn)符號(hào)分割
        String[] words = text.toLowerCase()
            .replaceAll("[^\\p{L}\\p{N}\\s]", " ")
            .split("\\s+");
        
        // 過濾掉太短的詞(少于2個(gè)字符)
        for (String word : words) {
            if (word.length() >= 2) {
                keywords.add(word.trim());
            }
        }
        
        return keywords;
    }
    
    /**
     * 加密結(jié)果類
     */
    @Getter
    public static class EncryptionResult {
        private final String encryptedDocument;  // 加密后的文檔(Base64)
        private final List<String> indexTokens;  // 索引token列表
        
        public EncryptionResult(String encryptedDocument, List<String> indexTokens) {
            this.encryptedDocument = encryptedDocument;
            this.indexTokens = indexTokens != null ? new ArrayList<>(indexTokens) : new ArrayList<>();
        }
        
        @Override
        public String toString() {
            return String.format("EncryptionResult{encryptedDocument='%s', indexTokens=%d}", 
                encryptedDocument.substring(0, Math.min(50, encryptedDocument.length())), 
                indexTokens.size());
        }
    }
}

3.3 測(cè)試用例

可搜索加密測(cè)試用例:

/**
 * 測(cè)試可搜索加密
 * @param args
 * @author senfel
 * @date 2026/2/12 15:45
 * @return void
 */
public static void main(String[] args) {
    try {
        // 創(chuàng)建可搜索加密服務(wù)
        SearchableEncryption se = SearchableEncryption.createDefault();
        
        System.out.println("=== 測(cè)試可搜索加密 ===");
        
        // 測(cè)試文檔
        String document = "這是一份關(guān)于Java加密技術(shù)的文檔。文檔介紹了AES加密、RSA加密和可搜索加密等技術(shù)。";
        System.out.println("原始文檔: " + document);
        
        // 提取關(guān)鍵詞
        Set<String> keywords = new HashSet<>(Arrays.asList("Java", "加密", "AES", "RSA", "技術(shù)"));
        System.out.println("關(guān)鍵詞: " + keywords);
        
        // 加密文檔并生成索引
        EncryptionResult result = se.encrypt(document, keywords);
        //加密結(jié)果: EncryptionResult{encryptedDocument='VWEdDDd2SgA7UFWDvuenNphVrloKhQGPWveLfiQOqtjtoSBY9b', indexTokens=5}
        //索引token數(shù)量: 5
        System.out.println("加密結(jié)果: " + result);
        System.out.println("索引token數(shù)量: " + result.getIndexTokens().size());
        
        // 解密文檔驗(yàn)證
        String decrypted = se.decryptDocument(result.getEncryptedDocument());
        //解密后的文檔: 這是一份關(guān)于Java加密技術(shù)的文檔。文檔介紹了AES加密、RSA加密和可搜索加密等技術(shù)。
        //解密是否成功: true
        System.out.println("解密后的文檔: " + decrypted);
        System.out.println("解密是否成功: " + document.equals(decrypted));
        
        System.out.println("\n=== 測(cè)試搜索功能 ===");
        
        // 搜索單個(gè)關(guān)鍵詞
        String searchKeyword = "Java";
        String trapdoor = se.generateTrapdoor(searchKeyword);
        //搜索關(guān)鍵詞: Java
        //生成的Trapdoor: PIc6gWBjjkhSYi5sCQq4cf18M3HHOpg494+wwZQZyeA=
        System.out.println("搜索關(guān)鍵詞: " + searchKeyword);
        System.out.println("生成的Trapdoor: " + trapdoor);
        
        boolean found = se.search(result.getIndexTokens(), trapdoor);
        //是否找到關(guān)鍵詞 'Java': true
        System.out.println("是否找到關(guān)鍵詞 '" + searchKeyword + "': " + found);
        
        // 搜索不存在的關(guān)鍵詞
        String notFoundKeyword = "Python";
        String notFoundTrapdoor = se.generateTrapdoor(notFoundKeyword);
        boolean notFound = se.search(result.getIndexTokens(), notFoundTrapdoor);
        //是否找到關(guān)鍵詞 'Python': false
        System.out.println("是否找到關(guān)鍵詞 '" + notFoundKeyword + "': " + notFound);
        
        // 批量搜索
        Set<String> searchKeywords = new HashSet<>(Arrays.asList("Java", "加密", "Python", "AES"));
        Set<String> matched = se.searchMultiple(result.getIndexTokens(), searchKeywords);
        System.out.println("\n批量搜索結(jié)果:");
        //搜索關(guān)鍵詞: [Java, 加密, Python, AES]
        //匹配的關(guān)鍵詞: [Java, 加密, AES]
        System.out.println("搜索關(guān)鍵詞: " + searchKeywords);
        System.out.println("匹配的關(guān)鍵詞: " + matched);
        
        System.out.println("\n=== 測(cè)試自動(dòng)提取關(guān)鍵詞 ===");
        String document2 = "Spring Boot是一個(gè)優(yōu)秀的Java框架,支持RESTful API開發(fā)。";
        // 自動(dòng)提取關(guān)鍵詞
        EncryptionResult result2 = se.encrypt(document2, null);
        //文檔2: Spring Boot是一個(gè)優(yōu)秀的Java框架,支持RESTful API開發(fā)。
        //自動(dòng)提取的索引token數(shù)量: 4
        System.out.println("文檔2: " + document2);
        System.out.println("自動(dòng)提取的索引token數(shù)量: " + result2.getIndexTokens().size());
        
        // 搜索
        String trapdoor2 = se.generateTrapdoor("Spring");
        boolean found2 = se.search(result2.getIndexTokens(), trapdoor2);
        //是否找到 'Spring': true
        System.out.println("是否找到 'Spring': " + found2);
        
    } catch (Exception e) {
        System.err.println("測(cè)試失敗: " + e.getMessage());
        e.printStackTrace();
    }
}

總結(jié)

Spring Boot環(huán)境下實(shí)現(xiàn)加密字段模糊查詢需要在安全性和性能之間找到平衡。本文介紹了三種主流方案:
1.格式保留加密:適用于需要保持?jǐn)?shù)據(jù)格式的場(chǎng)景,實(shí)現(xiàn)復(fù)雜但安全性高
2.部分加密與明文索引:平衡安全與性能,適用于大多數(shù)業(yè)務(wù)場(chǎng)景
3.可搜索加密:支持密文直接搜索,性能開銷較大但安全性最高
在實(shí)際項(xiàng)目中,應(yīng)根據(jù)業(yè)務(wù)需求和安全要求選擇合適的方案。對(duì)于大多數(shù)企業(yè)級(jí)應(yīng)用,部分加密與明文索引是一個(gè)不錯(cuò)的選擇,它在保證核心數(shù)據(jù)安全的同時(shí),能夠提供較好的查詢性能。
未來,隨著同態(tài)加密技術(shù)的成熟和性能提升,可能會(huì)成為加密字段模糊查詢的終極解決方案。但在當(dāng)前階段,上述三種方案仍然是最實(shí)用的選擇。

以上就是SpringBoot實(shí)現(xiàn)加密字段模糊查詢的最佳實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot加密字段模糊查詢的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 使用工具類-java精確到小數(shù)點(diǎn)后6位

    使用工具類-java精確到小數(shù)點(diǎn)后6位

    這篇文章主要介紹了使用工具類-java精確到小數(shù)點(diǎn)后6位,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • Spring的@CrossOrigin注解處理請(qǐng)求源碼解析

    Spring的@CrossOrigin注解處理請(qǐng)求源碼解析

    這篇文章主要介紹了Spring的@CrossOrigin注解處理請(qǐng)求源碼解析,@CrossOrigin源碼解析主要分為兩個(gè)階段@CrossOrigin注釋的方法掃描注冊(cè),請(qǐng)求匹配@CrossOrigin注釋的方法,本文從源碼角度進(jìn)行解析,需要的朋友可以參考下
    2023-12-12
  • MyBatis動(dòng)態(tài)SQL標(biāo)簽用法實(shí)例詳解

    MyBatis動(dòng)態(tài)SQL標(biāo)簽用法實(shí)例詳解

    本文通過實(shí)例代碼給大家介紹了MyBatis動(dòng)態(tài)SQL標(biāo)簽用法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-07-07
  • Java實(shí)現(xiàn)調(diào)用jython執(zhí)行python文件的方法

    Java實(shí)現(xiàn)調(diào)用jython執(zhí)行python文件的方法

    這篇文章主要介紹了Java實(shí)現(xiàn)調(diào)用jython執(zhí)行python文件的方法,結(jié)合實(shí)例形式分析了Java調(diào)用jython執(zhí)行python文件的常見操作技巧及相關(guān)問題解決方法,需要的朋友可以參考下
    2018-03-03
  • springboot之Validation參數(shù)校驗(yàn)詳細(xì)解讀

    springboot之Validation參數(shù)校驗(yàn)詳細(xì)解讀

    這篇文章主要介紹了springboot之Validation參數(shù)校驗(yàn)詳細(xì)解讀,本篇是關(guān)于springboot的參數(shù)校驗(yàn)知識(shí),當(dāng)然也適用其它java應(yīng)用,讀完本篇將學(xué)會(huì)基本的參數(shù)校驗(yàn),自定義參數(shù)校驗(yàn)和分組參數(shù)校驗(yàn),需要的朋友可以參考下
    2023-10-10
  • Spring Boot 動(dòng)態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹

    Spring Boot 動(dòng)態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹

    這篇文章給大家介紹了Spring Boot 動(dòng)態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2026-03-03
  • java為何不能多繼承的原因詳解

    java為何不能多繼承的原因詳解

    多重繼承是一個(gè)子類從多個(gè)父類中繼承屬性和方法。C++, Common Lisp是時(shí)下支持多重繼承的流行語言。那java為何不能多繼承呢,下面小編帶大家來一起學(xué)習(xí)一下吧
    2019-06-06
  • BeanFactory與ApplicationContext的區(qū)別示例解析

    BeanFactory與ApplicationContext的區(qū)別示例解析

    這篇文章主要為大家介紹了BeanFactory與ApplicationContext的區(qū)別示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • Java兩種方式實(shí)現(xiàn)動(dòng)態(tài)代理

    Java兩種方式實(shí)現(xiàn)動(dòng)態(tài)代理

    Java 在 java.lang.reflect 包中有自己的代理支持,該類(Proxy.java)用于動(dòng)態(tài)生成代理類,只需傳入目標(biāo)接口、目標(biāo)接口的類加載器以及 InvocationHandler 便可為目標(biāo)接口生成代理類及代理對(duì)象。我們稱這個(gè)Java技術(shù)為:動(dòng)態(tài)代理
    2020-10-10
  • Spring AOP面向切面編程實(shí)現(xiàn)原理方法詳解

    Spring AOP面向切面編程實(shí)現(xiàn)原理方法詳解

    這篇文章主要介紹了Spring AOP面向切面編程實(shí)現(xiàn)原理方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08

最新評(píng)論

新龙县| 康乐县| 济源市| 南昌市| 和平县| 英德市| 应城市| 胶南市| 正宁县| 关岭| 安新县| 巴马| 漾濞| 化州市| 勐海县| 徐闻县| 兴化市| 澎湖县| 武定县| 德阳市| 平度市| 塔城市| 华宁县| 四川省| 包头市| 青川县| 连州市| 敖汉旗| 兴山县| 英超| 济阳县| 扬州市| 奎屯市| 永定县| 葵青区| 兰考县| 酒泉市| 郸城县| 天峻县| 开原市| 巢湖市|