AOP 统一处理exception

本文介绍如何在 Dubbo 消费者中通过 AOP 技术统一处理异常,并自定义异常消息。利用 Spring 的 AOP 和 JAX-RS 的 WebApplicationException 实现了对不同类型的异常进行捕获并返回相应的 HTTP 状态码。

接上一篇《dubbox consumer获取provider的exception message》的内容。

使用AOP统一处理一些常用Exception,然后利用ExceptionMapper response数据到consumer

因为要自定义message,所以做的这么麻烦,不然直接全部在ExceptionMappe中处理就行,因为毕竟处理掉的是unchecked exception

//其实感觉需求有点莫名其妙。。

//重新补充,其实consumer真正需要的是checked exception,所以下面的实现对其作用不大。

资料:

http://ugibb510.iteye.com/blog/1762792

http://suntengjiao1.blog.163.com/blog/static/99211088201284112149284/

http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html

http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html


配置文件中增加:

xmlns:aop="http://www.springframework.org/schema/aop"
<pre name="code" class="html">http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
<pre name="code" class="html"><aop:aspectj-autoproxy/>


 
 定义注解: 

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    //提供 default 即可在使用是缺失
    String DataAccessEM() default "";//DataAccessException 自定义描述
    String DuplicateKeyEM() default "";//DuplicateKeyException 自定义描述
    String DfaultEM() default "";//Exception 自定义描述

}
定义切面:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;

/**
 * Created by leedongwei on 15/10/20.
 */
@Aspect
@Component //注入依赖
public class MyAspect {

    @AfterThrowing(pointcut="within(com.lee.test..*) && @annotation(ma)", throwing="ex")
    public void addMyTest(JoinPoint jp, MyAnnotation ma, Exception ex){
   //记录日志
        if(ex instanceof DataAccessException){
            System.out.println("in DataAccessException");
            logger.error(ea.DataAccessEM(), ex.getMessage());
            throw new WebApplicationException(ea.DataAccessEM(), Response.Status.BAD_REQUEST);

        }else if(ex instanceof DuplicateKeyException){
            System.out.println("in DuplicateKeyException");
            logger.error(ea.DataAccessEM(), ex.getMessage());
            throw new WebApplicationException(ea.DataAccessEM(), Response.Status.CONFLICT);

        }else if(ex instanceof WebApplicationException){
            System.out.println("in WebApplicationException");
            logger.error("WebApplicationException", ex.getMessage());
            //donothing
            return;
        }
        System.out.println("in DfaultEM,end");
        //除了指定被处理的,其余的都记得记录logger,为后台提供default message 和code

        throw new WebApplicationException(ea.DfaultEM(), Response.Status.INTERNAL_SERVER_ERROR);
    }
}
配置文件中增加:

 <bean id="logAspect" class="com.lee.test.MyAspect">

    </bean>

测试:

 @MyAnnotation(DataAccessEM = "dataAccessEm lee!",DuplicateKeyEM = "duplicateKeyEm lee!",DfaultEM = "defaultem lee!")
//    @MyAnnotation()
    public User getUser(@PathParam("id") Long id) {
        if (RpcContext.getContext().getRequest(HttpServletRequest.class) != null) {
            System.out.println("Client IP address from RpcContext: " + RpcContext.getContext().getRequest(HttpServletRequest.class).getRemoteAddr());
        }
        if (RpcContext.getContext().getResponse(HttpServletResponse.class) != null) {
            System.out.println("Response object from RpcContext: " + RpcContext.getContext().getResponse(HttpServletResponse.class));
        }
//        int a = 1/0;//Exception test
        throw new WebApplicationException("in getUser ex", Response.Status.CONFLICT);

//        return userService.getUser(id);
    }









<think>我们使用Spring Boot的`@ControllerAdvice`和`ResponseBodyAdvice`接口来实现统一返回值加密。 需求:对Controller返回的响应体进行加密,加密算法使用AES,并且支持注解排除某些接口不加密。 步骤: 1. 定义加密注解,用于标记需要加密的方法或类(也可以用于标记不需要加密,根据需求) 2. 实现`ResponseBodyAdvice`接口,在写入响应体之前对数据进行加密 3. 配置AES加密工具 4. 在Controller方法上使用注解控制是否加密 注意:如果某些接口不需要加密,我们可以通过注解排除,或者通过其他条件(如URL模式)排除。 下面给出完整代码实现: </think>### 🔒 Spring Boot AOP ControllerAdvice 统一返回值加密方案 下面是一个完整的统一返回值加密实现方案,使用 `@ControllerAdvice` 结合自定义注解实现响应数据的自动加密处理,支持多种加密算法和灵活的策略配置。 #### 解决方案架构 ```mermaid classDiagram class EncryptedResponse { <<Record>> +String ciphertext +String algorithm } class EncryptionAdvice { +supports(MethodParameter, Class) boolean +beforeBodyWrite(Object, MethodParameter, MediaType, Class, ServerHttpRequest, ServerHttpResponse) EncryptedResponse } class EncryptionStrategy { <<Interface>> +encrypt(String, String) String +decrypt(String, String) String } class AESStrategy { +encrypt(String, String) String +decrypt(String, String) String } class SM4Strategy { +encrypt(String, String) String +decrypt(String, String) String } class AnnotationPackage { +@EnableEncryption +@DisableEncryption } EncryptionAdvice ..> AnnotationPackage : 检查加密注解 EncryptionAdvice ..> EncryptionStrategy : 使用加密策略 EncryptionStrategy <|.. AESStrategy EncryptionStrategy <|.. SM4Strategy ``` #### 1. 核心组件定义 **加密响应体结构:** ```java /** * 加密响应结构 * @param ciphertext Base64编码的加密数据 * @param algorithm 使用的加密算法标识 */ public record EncryptedResponse(String ciphertext, String algorithm) { } ``` **加密策略接口:** ```java public interface EncryptionStrategy { /** * 加密数据 * @param plaintext 明文数据 * @param secretKey 密钥 * @return Base64编码的加密数据 */ String encrypt(String plaintext, String secretKey); /** * 解密数据 * @param ciphertext Base64编码的加密数据 * @param secretKey 密钥 * @return 解密后的明文数据 */ String decrypt(String ciphertext, String secretKey); } ``` #### 2. 加密算法实现 **AES加密策略:** ```java import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class AESStrategy implements EncryptionStrategy { private static final String ALGORITHM = "AES/ECB/PKCS5Padding"; private static final String CHARSET = "UTF-8"; @Override public String encrypt(String plaintext, String secretKey) { try { SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(CHARSET), "AES"); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(plaintext.getBytes(CHARSET)); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new EncryptionException("AES加密失败", e); } } @Override public String decrypt(String ciphertext, String secretKey) { try { SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(CHARSET), "AES"); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decoded = Base64.getDecoder().decode(ciphertext); byte[] decrypted = cipher.doFinal(decoded); return new String(decrypted, CHARSET); } catch (Exception e) { throw new EncryptionException("AES解密失败", e); } } } ``` **国密SM4加密策略:** ```java import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.security.Security; import java.util.Base64; public class SM4Strategy implements EncryptionStrategy { static { Security.addProvider(new BouncyCastleProvider()); } @Override public String encrypt(String plaintext, String secretKey) { try { SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "SM4"); Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(encrypted); } catch (Exception e) { throw new EncryptionException("SM4加密失败", e); } } @Override public String decrypt(String ciphertext, String secretKey) { try { SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "SM4"); Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding", "BC"); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decoded = Base64.getDecoder().decode(ciphertext); byte[] decrypted = cipher.doFinal(decoded); return new String(decrypted, "UTF-8"); } catch (Exception e) { throw new EncryptionException("SM4解密失败", e); } } } ``` #### 3. 注解定义 ```java import java.lang.annotation.*; /** * 启用响应加密注解 * @param algorithm 加密算法(默认AES) * @param keyAlias 密钥别名(用于密钥管理) */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface EnableEncryption { String algorithm() default "AES"; String keyAlias() default "default"; } /** * 禁用响应加密注解(优先级高于@EnableEncryption) */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DisableEncryption {} ``` #### 4. ControllerAdvice实现 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; 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.ResponseBodyAdvice; @ControllerAdvice public class EncryptionAdvice implements ResponseBodyAdvice<Object> { @Autowired private KeyManager keyManager; // 密钥管理服务 @Autowired private EncryptionStrategyFactory strategyFactory; // 检查是否支持加密处理 @Override public boolean supports(MethodParameter returnType, Class converterType) { // 检查是否标记了禁用加密 if (returnType.hasMethodAnnotation(DisableEncryption.class) || returnType.getContainingClass().isAnnotationPresent(DisableEncryption.class)) { return false; } // 检查是否标记了启用加密 return returnType.hasMethodAnnotation(EnableEncryption.class) || returnType.getContainingClass().isAnnotationPresent(EnableEncryption.class); } // 响应体加密处理 @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 1. 获取加密配置 EnableEncryption config = getEncryptionConfig(returnType); // 2. 序列化响应体为JSON String jsonBody = JsonUtils.toJson(body); // 3. 获取密钥 String secretKey = keyManager.getKey(config.keyAlias()); // 4. 选择加密策略 EncryptionStrategy strategy = strategyFactory.getStrategy(config.algorithm()); // 5. 执行加密 String ciphertext = strategy.encrypt(jsonBody, secretKey); // 6. 返回加密响应结构 return new EncryptedResponse(ciphertext, config.algorithm()); } // 获取加密配置(方法级优先于类级) private EnableEncryption getEncryptionConfig(MethodParameter returnType) { if (returnType.hasMethodAnnotation(EnableEncryption.class)) { return returnType.getMethodAnnotation(EnableEncryption.class); } return returnType.getContainingClass().getAnnotation(EnableEncryption.class); } } // 加密策略工厂 @Component public class EncryptionStrategyFactory { private final Map<String, EncryptionStrategy> strategies = new ConcurrentHashMap<>(); public EncryptionStrategyFactory() { // 注册内置加密策略 registerStrategy("AES", new AESStrategy()); registerStrategy("SM4", new SM4Strategy()); } public void registerStrategy(String algorithm, EncryptionStrategy strategy) { strategies.put(algorithm.toUpperCase(), strategy); } public EncryptionStrategy getStrategy(String algorithm) { EncryptionStrategy strategy = strategies.get(algorithm.toUpperCase()); if (strategy == null) { throw new UnsupportedEncryptionAlgorithmException("不支持的加密算法: " + algorithm); } return strategy; } } // 密钥管理器 @Component public class KeyManager { @Value("${encryption.keys.default:A2B4C6D8E0F2H4J6}") private String defaultKey; private final Map<String, String> keyStore = new ConcurrentHashMap<>(); public KeyManager() { // 初始化默认密钥 keyStore.put("default", defaultKey); } public void addKey(String alias, String key) { keyStore.put(alias, key); } public String getKey(String alias) { String key = keyStore.get(alias); if (key == null) { throw new KeyNotFoundException("未找到密钥别名: " + alias); } return key; } } ``` #### 5. 控制器使用示例 ```java @RestController @RequestMapping("/api") @EnableEncryption(algorithm = "SM4", keyAlias = "user-data") // 类级加密配置 public class SecureController { // 使用类级加密配置 @GetMapping("/user/{id}") public User getUser(@PathVariable Long id) { return userService.getUserById(id); } // 方法级覆盖加密配置 @EnableEncryption(algorithm = "AES", keyAlias = "sensitive-data") @GetMapping("/payment/{id}") public PaymentDetail getPaymentDetail(@PathVariable Long id) { return paymentService.getDetail(id); } // 禁用加密 @DisableEncryption @GetMapping("/public/info") public PublicInfo getPublicInfo() { return infoService.getPublicInfo(); } // 接收加密请求体 @PostMapping("/secure-update") public ResponseEntity<?> updateUser( @RequestBody @EncryptedRequest(algorithm = "SM4", keyAlias = "user-data") User user) { userService.updateUser(user); return ResponseEntity.ok().build(); } } ``` #### 6. 加密请求体处理(可选) ```java @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface EncryptedRequest { String algorithm() default "AES"; String keyAlias() default "default"; } @ControllerAdvice public class DecryptionAdvice implements RequestBodyAdvice { @Autowired private KeyManager keyManager; @Autowired private EncryptionStrategyFactory strategyFactory; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.hasParameterAnnotation(EncryptedRequest.class); } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { try { // 1. 读取加密请求体 String encrypted = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8); // 2. 获取解密配置 EncryptedRequest config = parameter.getParameterAnnotation(EncryptedRequest.class); // 3. 获取密钥 String secretKey = keyManager.getKey(config.keyAlias()); // 4. 选择解密策略 EncryptionStrategy strategy = strategyFactory.getStrategy(config.algorithm()); // 5. 解密数据 String decrypted = strategy.decrypt(encrypted, secretKey); // 6. 创建新的输入流 return new DecryptedHttpInputMessage( new ByteArrayInputStream(decrypted.getBytes(StandardCharsets.UTF_8)), inputMessage.getHeaders() ); } catch (IOException e) { throw new DecryptionException("请求体解密失败", e); } } // 其他方法实现... } // 自定义HTTP输入消息 class DecryptedHttpInputMessage implements HttpInputMessage { private final InputStream body; private final HttpHeaders headers; public DecryptedHttpInputMessage(InputStream body, HttpHeaders headers) { this.body = body; this.headers = headers; } @Override public InputStream getBody() { return body; } @Override public HttpHeaders getHeaders() { return headers; } } ``` #### 7. 安全增强配置 **密钥轮换策略:** ```java @Configuration @EnableScheduling public class KeyRotationConfig { @Autowired private KeyManager keyManager; // 每天凌晨轮换密钥 @Scheduled(cron = "0 0 0 * * ?") public void rotateKeys() { String newKey = generateSecureKey(); keyManager.addKey("previous", keyManager.getKey("default")); keyManager.addKey("default", newKey); } private String generateSecureKey() { SecureRandom random = new SecureRandom(); byte[] key = new byte[16]; // 128-bit key random.nextBytes(key); return Base64.getEncoder().encodeToString(key); } } ``` **响应安全头配置:** ```java @Bean public WebMvcConfigurer securityHeadersConfigurer() { return new WebMvcConfigurer() { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new HandlerInterceptor() { @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 添加安全相关HTTP头 response.setHeader("Content-Security-Policy", "default-src 'self'"); response.setHeader("X-Content-Type-Options", "nosniff"); response.setHeader("X-Frame-Options", "DENY"); } }); } }; } ``` #### 8. 客户端交互示例 **请求加密数据:** ```http POST /api/secure-update HTTP/1.1 Content-Type: application/json X-Encryption-Algorithm: SM4 o5YyV6vMx7fO...(SM4加密后的数据) ``` **接收加密响应:** ```json { "ciphertext": "jf8S7dKk3Lm9...", "algorithm": "SM4" } ``` **客户端解密处理:** ```javascript // 前端解密示例(使用CryptoJS) async function decryptResponse(response) { const { ciphertext, algorithm } = await response.json(); const secretKey = await getKeyFromSecureStorage(); let decrypted; if (algorithm === 'AES') { decrypted = CryptoJS.AES.decrypt(ciphertext, secretKey).toString( CryptoJS.enc.Utf8 ); } else { // 处理其他算法... } return JSON.parse(decrypted); } ``` ### 性能优化建议 1. **算法加速:** ```java // 使用硬件加速的AES实现 public AESStrategy() { Security.setProperty("crypto.policy", "unlimited"); } ``` 2. **响应缓存:** ```java @Override public Object beforeBodyWrite(/*参数*/) { String cacheKey = "encrypted_" + body.hashCode(); EncryptedResponse cached = cache.get(cacheKey); if (cached != null) return cached; // ...正常加密逻辑 cache.put(cacheKey, encryptedResponse); return encryptedResponse; } ``` 3. **异步加密:** ```java @Override public Object beforeBodyWrite(/*参数*/) { if (body instanceof CompletableFuture) { return ((CompletableFuture<?>) body).thenApplyAsync(data -> { return encryptData(data, config, secretKey, strategy); }); } return encryptData(body, config, secretKey, strategy); } ``` 4. **批量处理优化:** ```java // 对列表数据分块加密 List<Object> encryptedList = Collections.synchronizedList(new ArrayList<>()); ExecutorService executor = Executors.newFixedThreadPool(4); for (Object item : dataList) { executor.submit(() -> { encryptedList.add(strategy.encrypt(item, secretKey)); }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); ``` ### 最佳实践场景 1. **敏感数据保护:** ```java @EnableEncryption(algorithm = "AES", keyAlias = "pii-key") @GetMapping("/user/personal-info") public PersonalInfo getPersonalInfo() { // 返回身份证号、手机号等敏感信息 } ``` 2. **合规性要求:** ```java // 金融类数据使用国密算法 @EnableEncryption(algorithm = "SM4", keyAlias = "finance-key") @GetMapping("/financial/transactions") public List<Transaction> getTransactions() { // 返回金融交易记录 } ``` 3. **动态密钥管理:** ```java @GetMapping("/dynamic-key") @EnableEncryption(keyAlias = "#{@keyManager.getSessionKey(request)}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值