Java 實現(xiàn)RSA非對稱加密算法
公鑰與私鑰
公鑰與私鑰是成對的,一般的,我們認為的是公鑰加密、私鑰解密、私鑰簽名、公鑰驗證,有人說成私鑰加密,公鑰解密時不對的。
公鑰與私鑰的生成有多種方式,可以通過程序生成(下文具體實現(xiàn)),可以通過openssl工具:
# 生成一個私鑰,推薦使用1024位的秘鑰,秘鑰以pem格式保存到-out參數(shù)指定的文件中,采用PKCS1格式 openssl genrsa -out rsa.pem 1024 # 生成與私鑰對應(yīng)的公鑰,生成的是Subject Public Key,一般配合PKCS8格式私鑰使用 openssl rsa -in rsa.pem -pubout -out rsa.pub
RSA生成公鑰與私鑰一般有兩種格式:PKCS1和PKCS8,上面的命令生成的秘鑰是PKCS1格式的,而公鑰是Subject Public Key,一般配合PKCS8格式私鑰使用,所以就可能會涉及到PKCS1和PKCS8之間的轉(zhuǎn)換:
# PKCS1格式私鑰轉(zhuǎn)換為PKCS8格式私鑰,私鑰直接輸出到-out參數(shù)指定的文件中 openssl pkcs8 -topk8 -inform PEM -in rsa.pem -outform pem -nocrypt -out rsa_pkcs8.pem # PKCS8格式私鑰轉(zhuǎn)換為PKCS1格式私鑰,私鑰直接輸出到-out參數(shù)指定的文件中 openssl rsa -in rsa_pkcs8.pem -out rsa_pkcs1.pem # PKCS1格式公鑰轉(zhuǎn)換為PKCS8格式公鑰,轉(zhuǎn)換后的內(nèi)容直接輸出 openssl rsa -pubin -in rsa.pub -RSAPublicKey_out # PKCS8格式公鑰轉(zhuǎn)換為PKCS1格式公鑰,轉(zhuǎn)換后的內(nèi)容直接輸出 openssl rsa -RSAPublicKey_in -pubout -in rsa.pub
現(xiàn)實中,我們往往從pem、crt、pfx文件獲取公私和私鑰,crt、pfx的制作可以參考:簡單的制作ssl證書,并在nginx和IIS中使用,或者使用現(xiàn)成的:https://pan.baidu.com/s/1MJ5YmuZiLBnf-DfNR_6D7A (提取碼:c6tj),密碼都是:123456
Java實現(xiàn)
為簡化說明介紹,這里我直接封裝了一個工具類,因為要從pem、crt、pfx文件獲取公私和私鑰,因此引用了一個第三方包:BouncyCastle,可以直接在pom.xml中添加依賴:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.68</version> </dependency>
或者去mvn上下載:跳轉(zhuǎn)
簡單封裝的RsaUtil.java:
import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Enumeration; import javax.crypto.Cipher; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; import org.bouncycastle.util.io.pem.PemWriter; public class RsaUtil { static { Security.addProvider(new BouncyCastleProvider()); } /** * 隨機生成密鑰對 * * @param usePKCS8 * 是否采用PKCS8填充模式 */ public static Object[] generateRsaKey(boolean usePKCS8) throws Exception { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME); // 初始化 keyPairGen.initialize(1024, new SecureRandom()); // 生成一個密鑰對,保存在keyPair中 KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私鑰 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公鑰 // 這兩個公私鑰是PKCS8格式的 byte[] publicKeyBytes = publicKey.getEncoded(); byte[] privateKeyBytes = privateKey.getEncoded(); if (!usePKCS8) { // 將PSCK8格式公私鑰轉(zhuǎn)換為PKCS1格式 publicKeyBytes = pkcs8ToPkcs1(false, publicKeyBytes); privateKeyBytes = pkcs8ToPkcs1(true, privateKeyBytes); } return new Object[] { publicKeyBytes, privateKeyBytes }; } /** * 從Pem文件讀取密鑰對 * * @param reader * 輸入流 * @param pemFileName * pem文件 */ public static byte[] readFromPem(String pemFileName) throws Exception { PemReader pemReader = new PemReader(new FileReader(pemFileName)); PemObject pemObject = pemReader.readPemObject(); byte[] publicKey = pemObject.getContent(); pemReader.close(); return publicKey; } /** * 從Pem文件讀取密鑰 * * @param isPrivateKey * 是否是私鑰 * @param buffer * 字節(jié) * @param pemFileName * pem文件 */ public static void writeToPem(byte[] buffer, boolean isPrivateKey, String pemFileName) throws Exception { PemObject pemObject = new PemObject(isPrivateKey ? "RSA PRIVATE KEY" : "RSA PUBLIC KEY", buffer); FileWriter fileWriter = new FileWriter(pemFileName); PemWriter pemWriter = new PemWriter(fileWriter); pemWriter.writeObject(pemObject); pemWriter.close(); } /** * 從crt文件讀取公鑰(pkcs8) * * @param crtFileName * crt文件 * @return 公鑰 */ public static byte[] readPublicKeyFromCrt(String crtFileName) throws Exception { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(new FileInputStream(crtFileName)); PublicKey publicKey = cert.getPublicKey(); return publicKey.getEncoded(); } /** * 從pfx文件讀取秘鑰對(pkcs8) * * @param pfxFileName * pfx文件 * @return 秘鑰對 */ public static Object[] readFromPfx(String pfxFileName, String password) throws Exception { KeyStore keystore = KeyStore.getInstance("PKCS12"); char[] passwordChars = null; if (password == null || password.equals("")) { passwordChars = null; } else { passwordChars = password.toCharArray(); } keystore.load(new FileInputStream(pfxFileName), passwordChars); Enumeration<String> enums = keystore.aliases(); PrivateKey privateKey = null; Certificate certificate = null; while (enums.hasMoreElements()) { String alias = enums.nextElement(); System.out.println(alias); if (keystore.isKeyEntry(alias)) { privateKey = (PrivateKey) keystore.getKey(alias, passwordChars); certificate = keystore.getCertificate(alias); } if (privateKey != null && certificate != null) break; } if (privateKey == null || certificate == null) { throw new Exception("fail to read key from pfx"); } PublicKey publicKey = certificate.getPublicKey(); return new Object[] { publicKey.getEncoded(), privateKey.getEncoded() }; } /** * Pkcs8轉(zhuǎn)Pkcs1 * * @param isPrivateKey * 是否是私鑰轉(zhuǎn)換 * @param buffer * Pkcs1秘鑰 * @return Pkcs8秘鑰 * @throws Exception * 加密過程中的異常信息 */ public static byte[] pkcs8ToPkcs1(boolean isPrivateKey, byte[] buffer) throws Exception { if (isPrivateKey) { PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(buffer); return privateKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded(); } else { SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(buffer); return subjectPublicKeyInfo.parsePublicKey().toASN1Primitive().getEncoded(); } } /** * Pkcs1轉(zhuǎn)Pkcs8 * * @param isPrivateKey * 是否是私鑰轉(zhuǎn)換 * @param buffer * Pkcs1秘鑰 * @return Pkcs8秘鑰 * @throws Exception * 加密過程中的異常信息 */ public static byte[] pkcs1ToPkcs8(boolean isPrivateKey, byte[] buffer) throws Exception { AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption); ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(buffer); if (isPrivateKey) { PrivateKeyInfo privateKeyInfo = new PrivateKeyInfo(algorithmIdentifier, asn1Primitive); return privateKeyInfo.getEncoded(); } else { SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, asn1Primitive); return subjectPublicKeyInfo.getEncoded(); } } /** * RSA公鑰 * * @param usePKCS8 * 是否采用PKCS8填充模式 * @param publicKey * 公鑰 * @return 公鑰 * @throws Exception * 加密過程中的異常信息 */ public static RSAPublicKey generatePublicKey(boolean usePKCS8, byte[] publicKey) throws Exception { KeySpec keySpec; if (usePKCS8) { // PKCS8填充 keySpec = new X509EncodedKeySpec(publicKey); } else { // PKCS1填充 DLSequence sequence = (DLSequence) ASN1Primitive.fromByteArray(publicKey); BigInteger v1 = ((ASN1Integer) sequence.getObjectAt(0)).getValue(); BigInteger v2 = ((ASN1Integer) sequence.getObjectAt(1)).getValue(); keySpec = new RSAPublicKeySpec(v1, v2); } RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME).generatePublic(keySpec); return pubKey; } /** * RSA私鑰 * * @param usePKCS8 * 是否采用PKCS8填充模式 * @param privateKey * 私鑰 * @return 私鑰 * @throws Exception * 解密過程中的異常信息 */ public static RSAPrivateKey generatePrivateKey(boolean usePKCS8, byte[] privateKey) throws Exception { KeySpec keySpec; if (usePKCS8) { // PKCS8填充 keySpec = new PKCS8EncodedKeySpec(privateKey); } else { // PKCS1填充 DLSequence sequence = (DLSequence) ASN1Primitive.fromByteArray(privateKey); // BigInteger v1= ((ASN1Integer)sequence.getObjectAt(0)).getValue(); BigInteger v2 = ((ASN1Integer) sequence.getObjectAt(1)).getValue(); BigInteger v3 = ((ASN1Integer) sequence.getObjectAt(2)).getValue(); BigInteger v4 = ((ASN1Integer) sequence.getObjectAt(3)).getValue(); BigInteger v5 = ((ASN1Integer) sequence.getObjectAt(4)).getValue(); BigInteger v6 = ((ASN1Integer) sequence.getObjectAt(5)).getValue(); BigInteger v7 = ((ASN1Integer) sequence.getObjectAt(6)).getValue(); BigInteger v8 = ((ASN1Integer) sequence.getObjectAt(7)).getValue(); BigInteger v9 = ((ASN1Integer) sequence.getObjectAt(8)).getValue(); keySpec = new RSAPrivateCrtKeySpec(v2, v3, v4, v5, v6, v7, v8, v9); } RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME).generatePrivate(keySpec); return priKey; } /** * RSA公鑰加密 * * @param value * 加密字符串 * @param publicKey * 公鑰 * @return 密文 * @throws Exception * 加密過程中的異常信息 */ public static String rsaEncrypt(String value, RSAPublicKey publicKey) throws Exception { if (value == null || value.length() == 0) return ""; // RSA加密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] buffer = cipher.doFinal(value.getBytes("utf-8")); // 使用hex格式輸出公鑰 StringBuffer result = new StringBuffer(); for (int i = 0; i < buffer.length; i++) { result.append(String.format("%02x", buffer[i])); } return result.toString(); } /** * RSA私鑰解密 * * @param value * 加密字符串 * @param privateKey * 私鑰 * @return 明文 * @throws Exception * 解密過程中的異常信息 */ public static String rsaDecrypt(String value, RSAPrivateKey privateKey) throws Exception { if (value == null || value.length() == 0) return ""; byte[] buffer = new byte[value.length() / 2]; for (int i = 0; i < buffer.length; i++) { buffer[i] = (byte) Integer.parseInt(value.substring(i * 2, i * 2 + 2), 16); } // RSA解密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); buffer = cipher.doFinal(buffer); return new String(buffer, "utf-8"); } /** * RSA簽名 * * @param value * 加密字符串 * @param privateKey * 私鑰 * @param halg * 加密算法,如MD5, SHA1, SHA256, SHA384, SHA512等 * @return 簽名 * @throws Exception * 簽名過程中的異常信息 */ public static String sign(String value, RSAPrivateKey privateKey, String halg) throws Exception { Signature s = Signature.getInstance(halg.toUpperCase().endsWith("WithRSA") ? halg : (halg + "WithRSA")); s.initSign(privateKey); s.update(value.getBytes("utf-8")); byte[] buffer = s.sign(); // 使用hex格式輸出公鑰 StringBuffer result = new StringBuffer(); for (int i = 0; i < buffer.length; i++) { result.append(String.format("%02x", buffer[i])); } return result.toString(); } /** * RSA簽名驗證 * * @param value * 加密字符串 * @param publicKey * 公鑰 * @param halg * 加密算法,如MD5, SHA1, SHA256, SHA384, SHA512等 * @return 簽名合法則返回true,否則返回false * @throws Exception * 驗證過程中的異常信息 */ public static boolean verify(String value, RSAPublicKey publicKey, String signature, String halg) throws Exception { Signature s = Signature.getInstance(halg.toUpperCase().endsWith("WithRSA") ? halg : (halg + "WithRSA")); s.initVerify(publicKey); s.update(value.getBytes("utf-8")); byte[] buffer = new byte[signature.length() / 2]; for (int i = 0; i < buffer.length; i++) { buffer[i] = (byte) Integer.parseInt(signature.substring(i * 2, i * 2 + 2), 16); } return s.verify(buffer); } }
生成公鑰和私鑰:
// 生成公私鑰 Object[] rsaKey = RsaUtil.generateRsaKey(usePKCS8); //usePKCS8=true表示是否成PKCS8格式的公私秘鑰,否則乘車PKCS1格式的公私秘鑰 byte[] publicKey = (byte[]) rsaKey[0]; byte[] privateKey = (byte[]) rsaKey[1];
生成秘鑰后,需要保存,一般保存到pem文件中:
// 保存到pem文件,filePath是保存目錄 RsaUtil.writeToPem(publicKey, false, filePath + "rsa.pub"); RsaUtil.writeToPem(privateKey, true, filePath + "rsa.pem");
可以保存到pem文件中,當然也可以從pem文件中讀取了:
// 從Pem文件讀取公私鑰,filePath是文件目錄 byte[] publicKey = RsaUtil.readFromPem(filePath + "rsa.pub"); byte[] privateKey = RsaUtil.readFromPem(filePath + "rsa.pem");
還可以從crt證書中讀取公鑰,而crt文件不包含私鑰,因此需要單獨獲取私鑰:
// 從crt文件讀取公鑰(crt文件中不包含私鑰),filePath是文件目錄 byte[] publicKey = RsaUtil.readPublicKeyFromCrt(filePath + "demo.crt"); byte[] privateKey = RsaUtil.readFromPem(filePath + "demo.key");
pfx文件中包含了公鑰和私鑰,可以很方便就讀取到:
// 從pfx文件讀取公私鑰,filePath是文件目錄 Object[] rsaKey = RsaUtil.readFromPfx(filePath + "demo.pfx", "123456"); byte[] publicKey = (byte[]) rsaKey[0]; byte[] privateKey = (byte[]) rsaKey[1];
有時候我們還可能需要進行秘鑰的轉(zhuǎn)換:
// Pkcs8格式公鑰轉(zhuǎn)換為Pkcs1格式公鑰 publicKey = RsaUtil.pkcs8ToPkcs1(false, publicKey); // Pkcs8格式私鑰轉(zhuǎn)換為Pkcs1格式私鑰 privateKey = RsaUtil.pkcs8ToPkcs1(true, privateKey); // Pkcs1格式公鑰轉(zhuǎn)換為Pkcs8格式公鑰 publicKey = RsaUtil.pkcs1ToPkcs8(false, publicKey); // Pkcs1格式私鑰轉(zhuǎn)換為Pkcs8格式私鑰 privateKey = RsaUtil.pkcs1ToPkcs8(true, privateKey);
有了公鑰和私鑰,接下就就能實現(xiàn)加密、解密、簽名、驗證簽名等操作了:
RSAPublicKey rsaPublicKey = RsaUtil.generatePublicKey(usePKCS8, publicKey); RSAPrivateKey rsaPrivateKey = RsaUtil.generatePrivateKey(usePKCS8, privateKey); String encryptText = RsaUtil.rsaEncrypt(text, rsaPublicKey); System.out.printf("【%s】經(jīng)過【RSA】加密后:%s\n", text, encryptText); String decryptText = RsaUtil.rsaDecrypt(encryptText, rsaPrivateKey); System.out.printf("【%s】經(jīng)過【RSA】解密后:%s\n", encryptText, decryptText); String signature = RsaUtil.sign(text, rsaPrivateKey, "MD5"); System.out.printf("【%s】經(jīng)過【RSA】簽名后:%s\n", text, signature); boolean result = RsaUtil.verify(text, rsaPublicKey, signature, "MD5"); System.out.printf("【%s】的簽名【%s】經(jīng)過【RSA】驗證后結(jié)果是:" + result, text, signature);
這里完整的demo代碼:
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; public class RsaMain { public static void main(String[] args) { try { String text = "上山打老虎"; boolean usePKCS8 = true; // usePKCS8=true表示是否成PKCS8格式的公私秘鑰,否則乘車PKCS1格式的公私秘鑰 String filePath = RsaUtil.class.getClassLoader().getResource("").getPath(); System.out.printf("文件路徑:%s\n", filePath);// 存放pem,crt,pfx等文件的目錄 byte[] publicKey, privateKey;// 公鑰和私鑰 // 生成公私鑰 Object[] rsaKey = RsaUtil.generateRsaKey(usePKCS8); // usePKCS8=true表示是否成PKCS8格式的公私秘鑰,否則乘車PKCS1格式的公私秘鑰 publicKey = (byte[]) rsaKey[0]; privateKey = (byte[]) rsaKey[1]; // 從Pem文件讀取公私鑰,filePath是文件目錄 // publicKey = RsaUtil.readFromPem(filePath + "rsa.pub"); // privateKey = RsaUtil.readFromPem(filePath + "rsa.pem"); // 從pfx文件讀取公私鑰,filePath是文件目錄 // Object[] rsaKey = RsaUtil.readFromPfx(filePath + "demo.pfx", // "123456"); // publicKey = (byte[]) rsaKey[0]; // privateKey = (byte[]) rsaKey[1]; // 從crt文件讀取公鑰(crt文件中不包含私鑰),filePath是文件目錄 // publicKey = RsaUtil.readPublicKeyFromCrt(filePath + "demo.crt"); // privateKey = RsaUtil.readFromPem(filePath + "demo.key"); // 保存到pem文件,filePath是保存目錄 RsaUtil.writeToPem(publicKey, false, filePath + "rsa.pub"); RsaUtil.writeToPem(privateKey, true, filePath + "rsa.pem"); // Pkcs8格式公鑰轉(zhuǎn)換為Pkcs1格式公鑰 publicKey = RsaUtil.pkcs8ToPkcs1(false, publicKey); // Pkcs8格式私鑰轉(zhuǎn)換為Pkcs1格式私鑰 privateKey = RsaUtil.pkcs8ToPkcs1(true, privateKey); // Pkcs1格式公鑰轉(zhuǎn)換為Pkcs8格式公鑰 publicKey = RsaUtil.pkcs1ToPkcs8(false, publicKey); // Pkcs1格式私鑰轉(zhuǎn)換為Pkcs8格式私鑰 privateKey = RsaUtil.pkcs1ToPkcs8(true, privateKey); RSAPublicKey rsaPublicKey = RsaUtil.generatePublicKey(usePKCS8, publicKey); RSAPrivateKey rsaPrivateKey = RsaUtil.generatePrivateKey(usePKCS8, privateKey); String encryptText = RsaUtil.rsaEncrypt(text, rsaPublicKey); System.out.printf("【%s】經(jīng)過【RSA】加密后:%s\n", text, encryptText); String decryptText = RsaUtil.rsaDecrypt(encryptText, rsaPrivateKey); System.out.printf("【%s】經(jīng)過【RSA】解密后:%s\n", encryptText, decryptText); String signature = RsaUtil.sign(text, rsaPrivateKey, "MD5"); System.out.printf("【%s】經(jīng)過【RSA】簽名后:%s\n", text, signature); boolean result = RsaUtil.verify(text, rsaPublicKey, signature, "MD5"); System.out.printf("【%s】的簽名【%s】經(jīng)過【RSA】驗證后結(jié)果是:" + result, text, signature); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
以上就是Java 實現(xiàn)RSA非對稱加密算法的詳細內(nèi)容,更多關(guān)于Java RSA非對稱加密算法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring Security方法鑒權(quán)的實現(xiàn)
在Spring Security中,主要有兩種鑒權(quán)方式,一個是基于web請求的鑒權(quán),一個是基于方法的鑒權(quán),本文就來介紹一下Spring Security方法鑒權(quán)的實現(xiàn),感興趣的可以了解一下2023-12-12spring boot配置多個請求服務(wù)代理的完整步驟
這篇文章主要給大家介紹了關(guān)于spring boot配置多個請求服務(wù)代理的完整步驟,文中通過示例代碼介紹的非常詳細,對大家學(xué)習或者使用spring boot具有一定的參考學(xué)習價值,需要的朋友們下面來一起學(xué)習學(xué)習吧2019-11-11SpringBoot中OKHttp和壓縮文件的使用實戰(zhàn)教程
本文介紹了如何在SpringBoot中使用OKHttp發(fā)起請求和處理壓縮文件,包括文件的存儲配置、實體類、配置類和初始化類的設(shè)置,以及如何通過主程序和測試類進行實際操作,最后提供了必要的依賴添加方法,以確保功能的實現(xiàn)2024-10-10Java Mybatis中的 ${ } 和 #{ }的區(qū)別使用詳解
這篇文章主要介紹了Mybatis中的 ${ } 和 #{ }的區(qū)別使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2020-07-07GraalVM系列Native?Image?Basics靜態(tài)分析
這篇文章主要為大家介紹了GraalVM系列Native?Image?Basics靜態(tài)分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02SpringBoot 注解事務(wù)聲明式事務(wù)的方式
springboot使用上述注解的幾種方式開啟事物,可以達到和xml中聲明的同樣效果,但是卻告別了xml,使你的代碼遠離配置文件。今天就扒一扒springboot中事務(wù)使用注解的玩法,感興趣的朋友一起看看吧2017-09-09Java利用剪貼板實現(xiàn)交換程序間數(shù)據(jù)的方法
這篇文章主要介紹了Java利用剪貼板實現(xiàn)交換程序間數(shù)據(jù)的方法,需要的朋友可以參考下2014-07-07