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

如何在Spring MVC控制器中简化自定义请求处理的配置?

可行方案:自定义注解简化REST控制器样板代码

针对你的需求,有两种主流实现方式,都能大幅减少控制器中的重复代码,完全匹配你期望的注解式写法:

方案一:方法级@Provider注解 + AOP拦截

这种方式适合保留控制器类,仅简化方法实现,写法和你给出的第一个示例一致。

步骤1:定义自定义@Provider注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Provider {
    // 指定要调用的ListProvider实现类
    Class<? extends ListProvider> value();
}

步骤2:调整ListProvider适配Spring请求(可选,也可保留原RequestObject参数)

如果允许替换为HttpServletRequest,可让Provider自己处理请求解析:

public abstract class ListProvider {
    protected final ObjectMapper objectMapper = new ObjectMapper();

    // 改为接收HttpServletRequest,由子类自行解析请求体
    public abstract ResponseObject getResponse(HttpServletRequest request) throws IOException;

    // 通用解析工具方法,子类可直接调用
    protected RequestObject parseRequest(HttpServletRequest request) throws IOException {
        return objectMapper.readValue(request.getInputStream(), RequestObject.class);
    }
}

步骤3:编写AOP切面拦截注解方法

通过切面自动调用指定Provider的业务逻辑,完全替代控制器方法的样板代码:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class ProviderMethodInterceptor {
    private final ApplicationContext context;

    public ProviderMethodInterceptor(ApplicationContext context) {
        this.context = context;
    }

    @Around("@annotation(providerAnnotation)")
    public Object handleProviderRequest(ProceedingJoinPoint joinPoint, Provider providerAnnotation) throws Throwable {
        // 从Spring容器获取指定的Provider实例
        ListProvider provider = context.getBean(providerAnnotation.value());
        // 获取当前HTTP请求对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 调用Provider的业务方法并返回结果
        return provider.getResponse(request);
    }
}

最终控制器写法

@RestController
@RequestMapping("/client")
public class ClientController {
 
    @PostMapping("/list")
    @Provider(ClientListProvider.class)  
    public ResponseObject list() {
        // 空方法即可,业务逻辑由AOP切面自动处理
        return null;
    }
}

方案二:类级@ProviderController注解 + 动态注册请求映射

这种方式更极致,连控制器方法都可以省略,直接通过类注解生成完整的REST接口,匹配你给出的第二个示例。

步骤1:定义类级@ProviderController注解

import java.lang.annotation.*;
import org.springframework.web.bind.annotation.RequestMethod;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
public @interface ProviderController {
    // 基础路径(对应原@RequestMapping)
    String basePath();
    // 指定关联的ListProvider实现类
    Class<? extends ListProvider> providerClass();
    // 列表接口路径(默认/list)
    String listPath() default "/list";
    // 请求方法(默认POST)
    RequestMethod method() default RequestMethod.POST;
}

步骤2:编写请求映射注册器

通过Spring的RequestMappingHandlerMapping动态为带注解的控制器注册请求映射:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMappingInfo;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;

@Component
public class ProviderControllerRegistrar implements BeanPostProcessor, ApplicationContextAware {
    private ApplicationContext context;
    private final RequestMappingHandlerMapping handlerMapping;

    @Autowired
    public ProviderControllerRegistrar(RequestMappingHandlerMapping handlerMapping) {
        this.handlerMapping = handlerMapping;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> beanClass = bean.getClass();
        ProviderController annotation = beanClass.getAnnotation(ProviderController.class);
        if (annotation != null) {
            // 构建完整的请求路径
            String fullPath = annotation.basePath() + annotation.listPath();
            // 创建请求映射信息
            RequestMappingInfo mappingInfo = RequestMappingInfo
                    .paths(fullPath)
                    .methods(annotation.method())
                    .build();
            
            // 获取指定的Provider实例
            ListProvider provider = context.getBean(annotation.providerClass());
            // 创建对应的HandlerMethod(绑定Provider的getResponse方法)
            HandlerMethod handlerMethod = new HandlerMethod(provider, "getResponse", HttpServletRequest.class);
            
            // 将映射注册到Spring MVC
            handlerMapping.registerMapping(mappingInfo, handlerMethod, null);
        }
        return bean;
    }
}

最终控制器写法

@ProviderController(basePath = "/client", providerClass = ClientListProvider.class)
public class ClientController {
    // 完全不需要写任何方法,自动生成/client/list的POST接口
}

注意事项

  1. 如果你想保留原RequestObject参数(不替换为HttpServletRequest),可以调整切面或注册器的逻辑,让Spring自动通过@RequestBody解析参数,只需修改HandlerMethod的参数列表为RequestObject即可。
  2. 确保所有ListProvider实现类都标注了@Service,能被Spring容器扫描到。
  3. 两种方案都支持自定义异常处理,只需在Provider中抛出异常,配合Spring的@ControllerAdvice统一处理即可。

内容的提问来源于stack exchange,提问作者Javaddict

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 10:45:37