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

Spring控制器设计模式继承相关两大技术问题咨询

Spring Controller 重复代码与参数一致性优化方案

针对你遇到的这两个Spring Controller开发中的痛点,我整理了几个实用的解决方案,都是实际项目里验证过的优化方式:


一、自动填充父类变量,告别重复赋值

现在你每个子类Controller都要手动给USER、REQUEST_TYPE这些父类变量赋值,不仅重复劳动,还容易漏填。这里有两种方式可以解决:

1. 父类抽离初始化方法,统一处理赋值

最简单的方式是把重复的赋值逻辑抽到父类的一个方法里,子类只需要调用一次就行,避免漏写:

@SessionAttributes(value = {"user", "..."}) 
public abstract class MasterController<T> { 
    public Model MODEL; 
    public HashMap<String, String> INCOMING_PARAMS; 
    public RequestType REQUEST_TYPE; 
    public User USER; 
    
    // 统一初始化方法,把所有赋值逻辑放这
    protected void init(User user, HashMap<String, String> params, Model model, RequestType requestType) {
        this.USER = user;
        this.INCOMING_PARAMS = params;
        this.MODEL = model;
        this.REQUEST_TYPE = requestType;
    }

    public abstract T request( 
        @ModelAttribute("user") User _user, 
        @RequestParam HashMap<String, String> _params, 
        Model _model, 
        SessionStatus _sessionStatus, 
        HttpServletRequest _request, 
        HttpServletResponse _response); 
        
    public String otherCommonFunction(String _a) { ... } 
} 

子类里就简化成一行调用:

@Controller 
public class LoginController extends MasterController<String> { 
    @GetMapping(value = "/account-login-page") 
    @Override 
    public String request( 
        @ModelAttribute("user") User _user, 
        @RequestParam HashMap<String, String> _params, 
        Model _model, 
        SessionStatus _sessionStatus, 
        HttpServletRequest _request, 
        HttpServletResponse _response) { 
        // 一行搞定所有变量赋值
        init(_user, _params, _model, RequestType.GET);
        // 你的业务逻辑
        return "..."; 
    } 
}

这种方式改动最小,马上就能见效,而且能保证所有变量都被正确赋值。

2. 用@ModelAttribute实现自动初始化

如果想彻底不用手动调用,可以在父类里加一个被@ModelAttribute标注的方法——这个方法会在每个请求处理方法执行前自动触发,刚好可以用来完成父类变量的填充:

@SessionAttributes(value = {"user", "..."}) 
public abstract class MasterController<T> { 
    public Model MODEL; 
    public HashMap<String, String> INCOMING_PARAMS; 
    public RequestType REQUEST_TYPE; 
    public User USER; 
    
    // 这个方法会在每个request方法执行前自动调用
    @ModelAttribute
    public void populateCommonAttributes(@ModelAttribute("user") User user, 
                                         @RequestParam HashMap<String, String> params, 
                                         Model model, 
                                         HttpServletRequest request) {
        this.USER = user;
        this.INCOMING_PARAMS = params;
        this.MODEL = model;
        // 根据请求方法自动判断REQUEST_TYPE
        this.REQUEST_TYPE = "GET".equalsIgnoreCase(request.getMethod()) ? RequestType.GET : RequestType.POST;
    }

    public abstract T request( 
        @ModelAttribute("user") User _user, 
        @RequestParam HashMap<String, String> _params, 
        Model _model, 
        SessionStatus _sessionStatus, 
        HttpServletRequest _request, 
        HttpServletResponse _response); 
        
    public String otherCommonFunction(String _a) { ... } 
} 

现在子类里完全不用写赋值代码了,Spring会自动帮你把父类的变量填充好,而且每次请求都会执行,保证变量是当前请求的最新数据。


二、单个Controller多方法的参数一致性问题

单个Controller里有多个@GetMapping方法,每个都要复制一长串参数,不仅麻烦还容易出错,抽象方法又只能实现一次。这里有两个进阶方案:

1. 封装公共参数为DTO类

把所有重复的参数封装成一个自定义的RequestContext类,这样每个方法只需要处理这个DTO对象,参数列表会简洁很多:

// 封装所有公共请求参数的DTO
public class RequestContext {
    private User user;
    private HashMap<String, String> params;
    private Model model;
    private SessionStatus sessionStatus;
    private HttpServletRequest request;
    private HttpServletResponse response;
    
    // 全参构造器
    public RequestContext(User user, HashMap<String, String> params, Model model, 
                          SessionStatus sessionStatus, HttpServletRequest request, 
                          HttpServletResponse response) {
        this.user = user;
        this.params = params;
        this.model = model;
        this.sessionStatus = sessionStatus;
        this.request = request;
        this.response = response;
    }
    
    // 生成所有getter方法
    public User getUser() { return user; }
    public HashMap<String, String> getParams() { return params; }
    // 其他getter...
}

然后修改父类的抽象方法,用这个DTO作为参数:

@SessionAttributes(value = {"user", "..."}) 
public abstract class MasterController<T> { 
    // 父类变量和初始化方法同上
    
    // 抽象方法改为接收RequestContext
    public abstract T request(RequestContext context); 
    
    public String otherCommonFunction(String _a) { ... } 
} 

子类的方法就可以写成这样:

@Controller 
public class LoginController extends MasterController<String> { 
    @GetMapping(value = "/account-login-page") 
    @Override 
    public String request(@ModelAttribute("user") User user, 
                          @RequestParam HashMap<String, String> params, 
                          Model model, 
                          SessionStatus sessionStatus, 
                          HttpServletRequest request, 
                          HttpServletResponse response) {
        RequestContext context = new RequestContext(user, params, model, sessionStatus, request, response);
        init(context.getUser(), context.getParams(), context.getModel(), RequestType.GET);
        // 业务逻辑
        return "...";
    }
    
    @GetMapping(value = "/account-forget-password")
    public String forgetPassword(@ModelAttribute("user") User user, 
                                 @RequestParam HashMap<String, String> params, 
                                 Model model, 
                                 SessionStatus sessionStatus, 
                                 HttpServletRequest request, 
                                 HttpServletResponse response) {
        RequestContext context = new RequestContext(user, params, model, sessionStatus, request, response);
        init(context.getUser(), context.getParams(), context.getModel(), RequestType.GET);
        // 业务逻辑
        return "...";
    }
}

虽然还是要写参数,但至少参数结构是统一的,以后要修改参数只需要改DTO类,不用每个方法都调整。

2. 自定义参数解析器,彻底简化参数列表

如果想完全去掉重复的参数,可以用Spring的HandlerMethodArgumentResolver自定义一个参数解析器,让Spring自动把这些公共参数封装成RequestContext对象,这样方法里只需要一个参数:

首先实现参数解析器:

public class RequestContextArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 只处理RequestContext类型的参数
        return RequestContext.class.equals(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, 
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // 从请求中获取各个参数并封装成RequestContext
        User user = (User) webRequest.getAttribute("user", NativeWebRequest.SCOPE_SESSION);
        
        // 转换请求参数为HashMap<String, String>(注意getParameterMap返回的是String[],需要处理)
        Map<String, String[]> paramMap = webRequest.getParameterMap();
        HashMap<String, String> params = new HashMap<>();
        paramMap.forEach((key, value) -> params.put(key, value.length > 0 ? value[0] : ""));
        
        Model model = mavContainer.getModel();
        SessionStatus sessionStatus = mavContainer.getSessionStatus();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        
        return new RequestContext(user, params, model, sessionStatus, request, response);
    }
}

然后把这个解析器注册到Spring配置中:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new RequestContextArgumentResolver());
    }
}

现在子类的方法就超级简洁了:

@Controller 
public class LoginController extends MasterController<String> { 
    @GetMapping(value = "/account-login-page") 
    @Override 
    public String request(RequestContext context) {
        init(context.getUser(), context.getParams(), context.getModel(), RequestType.GET);
        // 直接用父类变量或者context里的参数做业务逻辑
        return "...";
    }
    
    @GetMapping(value = "/account-forget-password")
    public String forgetPassword(RequestContext context) {
        init(context.getUser(), context.getParams(), context.getModel(), RequestType.GET);
        // 业务逻辑
        return "...";
    }
}

如果结合前面的@ModelAttribute自动初始化方法,连init都可以不用调用,直接用父类的变量就行,代码会更干净。


内容的提问来源于stack exchange,提问作者Gábor DANI

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:25:42