You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 09:01:24