【spring mvc】全局处理请求体和响应体

目录

说明

由于项目中需要进行加密传输数据提高项目安全性,前端调用接口时,请求体中的数据全部加密成密文传输到后端接口,后端接口接收之后解密回明文。本例中使用的RSA非对称加解密方式处理。

如果后端接口参数使用字符串String 接收密文参数,则需要在每个接口方法中进行解密操做,代码会产生冗余。

如下伪代码:

java 复制代码
@Slf4j
@RestController
@RequestMapping(value = "user")
public class TUserController {
    /**
    * 说明: 添加
    * @author   zhangxiaosan
    * @create   2024/12/3
    * @param
    * @return
    */
    @PostMapping(value = "add")
    public String add(@RequestBody String user){
    	// user 参数为前端传递过来的 密文
    	// 使用RSA 解密方法,传入密文和密钥解密得到明文
    	String data = RSA.decryption(user,key); // 此部分为冗余代码。
		
		// todo 业务操做逻辑
				
        return user.toString();
    }
}

现在,有个需求是,接口方法中正常使用实体类来作为参数类型接收。前端依旧是传递加密后的密文。后端接收到密文之后,解密得到明文后,将明文数据转成接口参数所需要的数据类型。

业务处理完成之后,将返回的数据再进行加密传给前端。前端解密后才能得到明文展示 。

实现效果

逻辑图

实现步骤

创建公共处理的请求和响应的类

此类主要是公共处理api接收前端请求的密文参数,解密。处理方法返回数据的加密。

java 复制代码
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import www.three.common.response.Response;
import www.three.common.security.rsa.RSAUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Objects;

/**
 * 说明:
 * 对请求体和响应体进行加密解密
 * 此类用于全局处理请求和响应的功能类
 * RequestBodyAdvice 接口
 * RequestBodyAdvice 接口用于在请求的请求体被反序列化(即转化为 Java 对象)之前对其进行处理。你可以使用它来解密请求体中的数据。
 * <p>
 * ResponseBodyAdvice<Response> 接口
 * ResponseBodyAdvice 接口用于在响应体被序列化(即将 Java 对象转化为响应内容)之前对其进行处理。你可以使用它来加密响应体中的数据。Response 为自定义的响应格式实体类。
 *
 * @author 张小三
 * @create 2024-12-04 10:00
 * @verson 1.0.0
 */
@Slf4j
@ControllerAdvice
public class EncryptRequestResponseBodyAdvice implements ResponseBodyAdvice<Response>, RequestBodyAdvice {
	// 前端 RSA算法的私钥,用于解密前端传递过来的密文。前端需要于此私钥对应的公钥进行加密数据。
    private static final String privateKey = "";// 此处填写为前端的RSA私钥
	
	// 后端Api RSA 算法的公钥,用于加密响应体的数据。前端需要使用此公钥对应的私钥才能进行数据解密。
    private static final String publicKey = ""; // 此处填写为后端api的RSA公钥

    /**
     * 此方法判断当前处理器是否支持给定的方法参数、目标类型和HTTP消息转换器类型
     * @param methodParameter 方法参数
     * @param targetType      目标类型
     * @param converterType   HTTP消息转换器类型
     * @return 总是返回false,表示不支持
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;//返回 true 表示我们想要处理所有请求的请求体
    }

    /**
     * 在读取请求体之前调用此方法进行预处理
     * @param inputMessage  输入消息
     * @param parameter     方法参数
     * @param targetType    目标类型
     * @param converterType HTTP消息转换器类型
     * @return 返回null,表示不需要进行特殊处理
     * @throws IOException 如果处理过程中发生I/O错误
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
            	// 读取响应体的流
                InputStream inputStream = inputMessage.getBody();
                byte[] bytes = new byte[inputStream.available()];
                inputStream.read(bytes);
                // 转成字符串
                String data = JSON.parseObject(bytes).getString("data");
                log.info("RequestBody 密文:" + data);
                // 使用RSA工具方法解密
                data = RSAUtil.decryption(data,privateKey);
                log.info("RequestBody 明文:" + data);
                return new ByteArrayInputStream(data.getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }
        };
    }

    /**
     * 在读取请求体之后调用此方法进行后处理
     *
     * @param body          请求体内容
     * @param inputMessage  输入消息
     * @param parameter     方法参数
     * @param targetType    目标类型
     * @param converterType HTTP消息转换器类型
     * @return 返回null,表示不需要进行特殊处理
     */
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    /**
     * 当请求体为空时调用此方法进行处理
     *
     * @param body          请求体内容(此时为空)
     * @param inputMessage  输入消息
     * @param parameter     方法参数
     * @param targetType    目标类型
     * @param converterType HTTP消息转换器类型
     * @return 返回null,表示不需要进行特殊处理
     */
    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    /**
     * 此方法判断当前处理器是否支持给定的返回类型和HTTP消息转换器类型
     * @param returnType    返回类型
     * @param converterType HTTP消息转换器类型
     * @return 总是返回false,表示不支持
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * 在写入响应体之前调用此方法进行预处理
     * 本实现中不需要任何预处理,因此返回null
     *
     * @param body                  响应体内容
     * @param returnType            返回类型
     * @param selectedContentType   选定的内容类型
     * @param selectedConverterType 选定的HTTP消息转换器类型
     * @param request               HTTP请求
     * @param response              HTTP响应
     * @return 返回null,表示不需要进行特殊处理
     */
    @Override
    public Response beforeBodyWrite(Response body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (Objects.nonNull(body) && body.getData() != null) {
            String data = JSON.toJSONString(body.getData());
            log.info("ResponseBody 明文:" + data);
            // 用RSA工具将返回的数据进行加密
            data = RSAUtil.encryption(data,publicKey);
            log.info("ResponseBody 密文:" + data);
            body.setData(data);
        }
        return body;
    }
}

api接口

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import www.three.common.response.Response;
import www.three.components.log.annotations.Log;
import www.three.system.user.model.po.TUser;

/**
 * 说明:
 *
 * @author 张小三
 * @create 2024-12-03 17:37
 * @verson 1.0.0
 */
@Slf4j
@RestController
@RequestMapping(value = "user")
public class TUserController {
    /**
    * 说明: 添加
    * @author   zhangxiaosan
    * @create   2024/12/3
    * @param
    * @return
    */
    @Log
    @PostMapping(value = "add")
    public Response<?> add(@RequestBody TUser user){
        log.info("添加用户:{}",user.toString());
        return Response.success(user);
    }
}

测试

前端请求

使用idea的 HTTP Client 工具 请求案例:

json 复制代码
POST http://127.0.0.1:8081/three-user/user/add
Content-Type: application/json

{"data":"6944323931323669415876424a653867445233636247553464552b3256466c2b3631586639314675584a6b5068394a6f6b54457235464c4743666e496b5342574a6256564e484b4c49667a6d0d0a6c55696d74444b4778363858465945516f34756d4b6938714f494b732f5157742b6a3235543256737733356c7677465976474463613043454e636376726257502f2f6f3337686c6d6b6e46680d0a38367237467663634c6950586648657135534b31736d6e372b6637667658454d386a79454e794c346b44546c444f367050476e594c793041366d65324a4a6136387251536a616963325a51680d0a6756446369386a743346755a47565377574147332f76306739472f57614e4b3330372b4455467377677536536e79534e71703530635637576a5a38533732654477744d4365587945653638550d0a4976704d4c6e6a303465446c754f506474626f33493578693479724265446a6f356f46557a513d3d"}

响应结果

json 复制代码
{
  "code": 0,
  "message": "成功",
  "data": "4b637168342f4a7537637956784c7368637a527068314772783139515555506e336e7049474469524a654754464a7242327873545766574a666c346d66427268414b754264306a45454766640d0a5a764a76426e644d77526664613632635a48596a72562b546447597366653667796f6c68616648536f786f6737464575765342686b706473624b4d78574b645145637257655a62615a6e4c670d0a6d79566f467a697a7a5468445153744264627a6d304b42524d54654b6f41347746376d6137427878774c66537159526856484261486f305578734b686f4d78763638434342763959653249320d0a4f557073423545784b354e5a724465416663535a502f4b34516455464d4f33655a335552505067674f714f6d42755a4351654858586c3366334441704448444f5a6b644561354b436e702f330d0a636d6e2f38347771586c62373038344d316479546b4e466778696358592b62555864567a51773d3d",
  "count": null
}

扩展

Response响应格式实体

java 复制代码
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import lombok.Data;

/**
 * 说明:
 *
 * @author 张小三
 * @create 2024-07-12 16:12
 * @verson 1.0.0
 */
@Data
public class Response<T> {
    /**
     * 状态码
     * 其他值表示失败
     */
    private Integer code;

    /**
     * 提示信息
     */
    private String message;

    /**
     * 响应数据
     */
    private T data;

    /**
     * 数据总量(layui 列表使用)
     */
    private Integer count;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public Response(Integer code, String message, T data, Integer count) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.count = count;
    }

    public static <T> Response<T> success(){
        return response(ResponseCode.successCode,ResponseCode.successMessage,null,null);
    }

    public static <T> Response<T> success(T data){
        return response(ResponseCode.successCode,ResponseCode.successMessage,data,null);
    }

    public static <T> Response<T> success(T data, String message){
        return response(ResponseCode.successCode,message,data,null);
    }

    public static <T> Response<T> fail(){
        return response(ResponseCode.failCode,ResponseCode.failMessage,null,null);
    }

    public static <T> Response<T> fail(String message){
        return response(ResponseCode.failCode,message,null,null);
    }


    private static <T> Response<T> response(Integer code,String message,T data,Integer count){
        return new Response(code,message,data,count);
    }

    @Override
    public String toString() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code",code);
        jsonObject.put("message",message);
        jsonObject.put("count",count);
        jsonObject.put("data",data);
        return JSON.toJSONString(jsonObject, JSONWriter.Feature.WriteNulls);
    }

}

ResponseCode 响应状态码

java 复制代码
import java.io.Serializable;

/**
 * 说明:
 *  响应状态码
 * @author 张小三
 * @create 2024-07-12 14:19
 * @verson 1.0.0
 */
public class ResponseCode implements Serializable {
    /**
    * 说明: 成功状态码
    * @author   zhangxiaosan
    * @create   2024/7/12
    * @param
    * @return
    */
    public static final Integer successCode = 0;

    /**
    * 说明: 成功默认信息
    * @author   zhangxiaosan
    * @create   2024/7/12
    * @param
    * @return
    */
    public static final String successMessage = "成功";

    /**
     * 说明: 失败状态码
     * @author   zhangxiaosan
     * @create   2024/7/12
     * @param
     * @return
     */
    public static final Integer failCode = -1;

    /**
     * 说明: 失败默认信息
     * @author   zhangxiaosan
     * @create   2024/7/12
     * @param
     * @return
     */
    public static final String failMessage = "失败";

}

RSA工具类

java 复制代码
import com.alibaba.fastjson2.JSONObject;
import www.three.common.security.hex.HexUtil;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 说明:
 *  rsa 工具类
 * @author 张小三
 * @create 2024-12-03 11:52
 * @verson 1.0.0
 */
public class RSAUtil {
    private static String keySize =  "2048";
    /**
     * 创建公钥和私钥
     *
     * @return
     * @throws RSAException
     */
    public static Map<String, String> createKeys() throws RSAException {
        KeyPairGenerator keygen;
        try {
            keygen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            throw new RSAException("RSA初始化密钥出现错误,算法异常");
        }
        SecureRandom secrand = new SecureRandom();
        //初始化随机产生器
        secrand.setSeed("jst".getBytes());
        //初始化密钥生成器
        if (Objects.isNull(keySize)){
            keySize = "2048";
        }
        keygen.initialize(Integer.valueOf(keySize), secrand);
        KeyPair keyPair = keygen.genKeyPair();
        //获取公钥并转成base64编码
        byte[] pub_key = keyPair.getPublic().getEncoded();
        String publicKeyStr = Base64.getEncoder().encodeToString(pub_key);
        //获取私钥并转成base64编码
        byte[] pri_key = keyPair.getPrivate().getEncoded();
        String privateKeyStr = Base64.getEncoder().encodeToString(pri_key);

        // 公钥和私钥转成16进制
        publicKeyStr = HexUtil.encode(publicKeyStr);
        privateKeyStr = HexUtil.encode(privateKeyStr);

        //创建一个Map返回结果
        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);
        return keyPairMap;
    }


    /**
     * RSA 加密
     *
     * @param data 加密内容
     * @param key  密钥
     * @return
     */
    public static  String encryption(String data,String key) throws RSAException {
        // 16进制密钥还原
        key = HexUtil.decode(key);
        byte[] publicKeyByte = Base64.getMimeDecoder().decode(key);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyByte);
        String res = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE,publicKey);
            byte[] bytes = cipher.doFinal(data.getBytes());
            res = Base64.getMimeEncoder().encodeToString(bytes);
            res = HexUtil.encode(res); // 密文转成16进制
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException |
                 IllegalBlockSizeException | BadPaddingException e) {
            throw new RSAException("RSA出现错误,算法异常:"+e.getMessage());
        }
        return res ;
    }

    /**
     * RSA 解密
     *
     * @param data 密文
     * @param key 密钥
     * @return
     */
    public  static String decryption(String data ,String key) throws RSAException {
        // 16进制密钥还原
        key = HexUtil.decode(key);
        byte[] privateKeyByte = Base64.getMimeDecoder().decode(key);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
        String res = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE,privateKey);
            // 密文还原
            data = HexUtil.decode(data);
            byte[] decode = Base64.getMimeDecoder().decode(data);
            byte[] bytes = cipher.doFinal(decode);
            res = new String(bytes);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RSAException("RSA出现错误,算法异常:"+e.getMessage());
        }
        return res ;
    }

    /**
     * RSA 签名
     *
     * @param data 待签名的数据
     * @param privateKey 私钥
     * @return 签名后的字符串
     * @throws RSAException 如果签名过程中出现异常
     */
    public static String sign(String data, String privateKey) throws RSAException {
        // 16进制密钥还原
        privateKey = HexUtil.decode(privateKey);
        byte[] privateKeyByte = Base64.getMimeDecoder().decode(privateKey);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
        String signature = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKeyObj = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Signature signer = Signature.getInstance("SHA256withRSA");
            signer.initSign(privateKeyObj);
            signer.update(data.getBytes());
            byte[] signedData = signer.sign();
            signature = Base64.getMimeEncoder().encodeToString(signedData);
            signature = HexUtil.encode(signature); // 签名转成16进制
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
            throw new RSAException("RSA签名出现错误,算法异常:" + e.getMessage());
        }
        return signature;
    }

    /**
     * RSA 验签
     *
     * @param data 原始数据
     * @param publicKey 公钥
     * @param signature 签名
     * @return 验签结果
     * @throws RSAException 如果验签过程中出现异常
     */
    public static boolean verify(String data, String publicKey, String signature) throws RSAException {
        // 16进制密钥还原
        publicKey = HexUtil.decode(publicKey);
        byte[] publicKeyByte = Base64.getMimeDecoder().decode(publicKey);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyByte);
        boolean isVerify = false;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKeyObj = keyFactory.generatePublic(x509EncodedKeySpec);
            Signature signer = Signature.getInstance("SHA256withRSA");
            signer.initVerify(publicKeyObj);
            signer.update(data.getBytes());
            // 签名还原
            signature = HexUtil.decode(signature);
            byte[] decodedSignature = Base64.getMimeDecoder().decode(signature);
            isVerify = signer.verify(decodedSignature);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
            throw new RSAException("RSA验签出现错误,算法异常:" + e.getMessage());
        }
        return isVerify;
    }
}

RequestBodyAdvice 介绍

用于在请求体被反序列化之前对其进行处理,典型场景是解密请求体。用于拦截请求体(Request Body)的处理,在请求体被反序列化为 Java 对象之前对其进行处理。这个接口允许开发者在请求进入控制器之前做一些预处理,比如解密或格式转换等。

RequestBodyAdvice 接口提供了 5 个方法,具体如下:

  1. boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType)

    用于判断当前请求是否需要处理,返回 true 表示该请求体需要进行处理。你可以根据 methodParameter、targetType 或 converterType 来做特定的控制。

  2. Object handleBodyBeforeRead(MethodParameter methodParameter, Type targetType, HttpInputMessage inputMessage, Class<? extends HttpMessageConverter<?>> converterType) throws IOException

    在请求体反序列化之前调用,可以对请求体进行解密、转换或者其他处理。返回值将是处理后的请求体内容,或者可以直接读取 inputMessage 数据并进行修改。

  3. Object handleBodyAfterRead(Object body, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType, HttpInputMessage inputMessage)

    请求体被成功反序列化后调用。在此方法中你可以进一步处理数据,比如验证或转换成其他格式。

  4. boolean handleBodyAfterWrite(Object body, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType, HttpOutputMessage outputMessage) throws IOException

    在响应体被序列化后调用,允许你对响应体进行最后的修改,比如加密、转换等。返回值决定了是否继续执行后续处理。

  5. boolean handleBodyBeforeWrite(Object body, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType, HttpOutputMessage outputMessage) throws IOException

    在响应体被写入前调用。你可以在这里对响应体数据进行修改、加密等处理。

使用场景

解密请求体: 在请求到达控制器之前,你可能需要对请求体进行解密操作(例如,使用某种加密算法加密请求体数据)。

日志记录: 记录请求的请求体内容,或者修改请求体中的数据。

格式转换: 例如,将请求体从 JSON 转换成 XML,或者做其他格式的处理。

ResponseBodyAdvice 介绍

用于在响应体被序列化并发送之前对其进行处理,典型场景是加密响应体。当控制器方法执行完毕并返回数据后,Spring 会通过 ResponseBodyAdvice 对响应体进行处理。它可以用于对响应数据进行加密、修改、过滤等操作。

ResponseBodyAdvice 接口提供了 2 个方法,具体如下:

  1. boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType)
    用于判断是否对当前的响应体执行处理。你可以根据返回类型、converterType 等条件判断是否处理。
  2. Object handleBodyBeforeWrite(Object body, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType, HttpOutputMessage outputMessage) throws IOException
    在响应体被序列化并写入 HTTP 响应之前调用。你可以在这里对响应体进行加密或修改等操作。

使用场景

加密响应体: 例如,在返回给客户端的数据需要加密时,你可以在 ResponseBodyAdvice 中对响应体进行加密。

日志记录: 记录响应体内容,或者根据需要修改返回的数据。

修改响应体格式: 可以根据需要改变响应的格式,比如将返回的 JSON 数据转换成其他格式。

相关推荐
hERS EOUS17 小时前
SpringBoot 使用 spring.profiles.active 来区分不同环境配置
spring boot·后端·spring
超梦dasgg18 小时前
智慧充电系统设备管理服务对外接口实现方案
java·spring·微服务
xiaoye370819 小时前
Spring 事务传播机制 + 隔离级别
java·后端·spring
xuhaoyu_cpp_java20 小时前
Spring学习(一)
java·经验分享·笔记·学习·spring
苍煜1 天前
SpringBoot AOP切面编程精讲:实现方式、Spring区别及与自定义注解生产实战
java·spring boot·spring
流年似水~1 天前
Java新手5分钟接AI:Spring AI Alibaba实战
java·人工智能·spring
jnrjian1 天前
Library Cache Load Lock library cache pins are replaced by mutexes
java·后端·spring
952361 天前
SpringAOP
java·后端·学习·spring
zx2859634001 天前
Laravel6.x新特性全解析
java·后端·spring
未若君雅裁1 天前
Spring Statemachine 实战入门:从零实现一个订单状态流转 Demo
java·spring·状态模式