自定义AOP注解影响GetMapping注解功能的问题排查与解决
问题:AOP环绕通知导致Spring MVC响应Content-Type异常
我通过AOP实现了一个自定义参数注解@PermittedMarkets来扩展功能,接口定义如下:
@GetMapping(path="/my/path/{date}/{text}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public String subscribe(@PathVariable String date, @PathVariable String text, @PermittedMarkets String[] marketCodes) { // 此处执行处理并返回结果 }
注解与AOP代码:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface PermittedMarkets { } @Aspect @Component public class MarketsAspect { @Value("#{${config.market.permitted.marketmapping:{:}}}") private Map<String, String> roleToMarketCodeMap; @Value("${spring.profiles.active:}") private String profile; @Around("execution(* *(.., @com.org.app.service.security.annotation.PermittedMarkets (*), ..))") public void permittedMarkets(ProceedingJoinPoint pjp) throws Throwable { final var args = pjp.getArgs(); final var annotations = ((MethodSignature) pjp.getSignature()).getMethod().getParameterAnnotations(); for (int i = 0; i < args.length; i++) { for (Annotation annotation : annotations[i]) { if (annotation.annotationType() == PermittedMarkets.class) { if (profile.equals("dev")) { args[i] = "*"; continue; } final var grantedAuthorities = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) .map(Authentication::getAuthorities) .orElseThrow(() -> new AccessDeniedException("No authorities found")); args[i] = grantedAuthorities.stream() .map(GrantedAuthority::getAuthority) .filter(entitlement -> entitlement.contains("app_user")) .map(A_entitlement -> roleToMarketCodeMap.get(A_entitlement)) .filter(Objects::nonNull).collect(Collectors.joining(",")); } } } pjp.proceed(args); } }
运行异常:注释掉permittedMarkets方法时,前端响应头包含正确的Content-Type: text/event-stream;启用该方法后,响应头的Content-Type变为"-",前端报错:EventSource's response has a Content-Type specifying an unsupported type: -. Aborting connection。
解决方案
1. 修正环绕通知的返回值类型
你的环绕通知方法返回值定义为void,但原控制器方法返回String。Spring MVC在处理AOP代理的返回结果时,无法通过void类型关联原方法的@GetMapping注解配置(包括produces属性),导致响应头的Content-Type丢失。
需要将环绕通知的返回值改为Object,并返回pjp.proceed(args)的执行结果:
@Around("execution(* *(.., @com.org.app.service.security.annotation.PermittedMarkets (*), ..))") public Object permittedMarkets(ProceedingJoinPoint pjp) throws Throwable { // 原有参数处理逻辑... // 返回原方法的执行结果 return pjp.proceed(args); }
2. 修正参数类型不匹配问题
原方法中marketCodes是String[]类型,但你的代码中给它赋值了String(比如"*"或者逗号分隔的字符串),这会导致运行时类型转换异常。需要将字符串转换为数组:
- 开发环境下:
args[i] = new String[]{"*"}; - 生产环境下:将逗号分隔的字符串拆分为数组:
String marketStr = grantedAuthorities.stream() .map(GrantedAuthority::getAuthority) .filter(entitlement -> entitlement.contains("app_user")) .map(A_entitlement -> roleToMarketCodeMap.get(A_entitlement)) .filter(Objects::nonNull) .collect(Collectors.joining(",")); args[i] = marketStr.isEmpty() ? new String[0] : marketStr.split(",");
修正后的完整AOP代码
@Aspect @Component public class MarketsAspect { @Value("#{${config.market.permitted.marketmapping:{:}}}") private Map<String, String> roleToMarketCodeMap; @Value("${spring.profiles.active:}") private String profile; @Around("execution(* *(.., @com.org.app.service.security.annotation.PermittedMarkets (*), ..))") public Object permittedMarkets(ProceedingJoinPoint pjp) throws Throwable { final var args = pjp.getArgs(); final var annotations = ((MethodSignature) pjp.getSignature()).getMethod().getParameterAnnotations(); for (int i = 0; i < args.length; i++) { for (Annotation annotation : annotations[i]) { if (annotation.annotationType() == PermittedMarkets.class) { if (profile.equals("dev")) { // 修正为String数组类型 args[i] = new String[]{"*"}; continue; } final var grantedAuthorities = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) .map(Authentication::getAuthorities) .orElseThrow(() -> new AccessDeniedException("No authorities found")); String marketStr = grantedAuthorities.stream() .map(GrantedAuthority::getAuthority) .filter(entitlement -> entitlement.contains("app_user")) .map(A_entitlement -> roleToMarketCodeMap.get(A_entitlement)) .filter(Objects::nonNull) .collect(Collectors.joining(",")); // 将字符串转换为String数组 args[i] = marketStr.isEmpty() ? new String[0] : marketStr.split(","); } } } // 返回原方法执行结果,保证Spring MVC能正确处理响应头 return pjp.proceed(args); } }
内容的提问来源于stack exchange,提问作者chunkynuggy
相关产品推荐
相关产品推荐

