Spring MVC @Controller拦截自己的请求

Spring MVC @Controller to intercept own requests

假设我们有一个这样的控制器:

@RestController
@RequestMapping("/{parameter}")
public class MyController {

    @ExceptionHandler(SomeException.class)
    public Object handleSomeException() { /* handle */ }

    @RequestMapping("/something")
    public Object handleSomething(@PathVariable("parameter") String parameter) {
        /* handle */
    }

    @RequestMapping("/somethingElse")
    public Object handleSomethingElse(@PathVariable("parameter") String parameter) {
        /* handle */
    }
}

问题是,如何以类似于 @ExceptionHandler 的方式为这个特定的控制器实现一些通用的 pre-\post-handling?例如。我想在控制器中有一个方法在处理程序方法之前接收请求,但只请求这个特定的控制器。

我知道 RequestBodyAdviceResponseBodyAdvice 接口,但想要控制器本地的东西。

作为一个用法示例 - 我想在每个处理程序之前对公共 parameter 变量进行一些验证。

使用HandlerInterceptorAdapter

拦截controller执行前后,记录执行时间的开始和结束,保存到已有拦截的controller的modelAndView中,供以后显示。

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{

private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);

//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
    HttpServletResponse response, Object handler)
    throws Exception {

    long startTime = System.currentTimeMillis();
    request.setAttribute("startTime", startTime);

    return true;
}

//after the handler is executed
public void postHandle(
    HttpServletRequest request, HttpServletResponse response,
    Object handler, ModelAndView modelAndView)
    throws Exception {

    long startTime = (Long)request.getAttribute("startTime");

    long endTime = System.currentTimeMillis();

    long executeTime = endTime - startTime;

    //modified the exisitng modelAndView
    modelAndView.addObject("executeTime",executeTime);

    //log it
    if(logger.isDebugEnabled()){
       logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
    }
}

更多示例 - http://www.mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example/

您需要自己编写 HandlerInterceptor. You can do it easily by extending HandlerInterceptorAdapter。然后你可以覆盖 preHandle() and/or postHandle().

preHandle() is called after HandlerMapping determined an appropriate handler object, but before HandlerAdapter invokes the handler.

postHandle() is called after HandlerAdapter actually invoked the handler, but before the DispatcherServlet renders the view.

您可以使用 HttpServletRequestgetRequestURI() 方法为 preHandle() 中的不同处理程序添加逻辑。

示例:

public class ValidationInterceptor extends HandlerInterceptorAdapter {

    public static final String FOO_URL = "foo";
    public static final String BAR_URL = "bar";

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        String uri = request.getRequestURI();

        if (FOO_URL.equals(uri)) {        
            // for example - validation failed
            response.sendRedirect("/to/some/url");
            return false;
        } else if (BAR_URL.equals(uri)) {
            // for example - validation successful
        }
        return true;
    }
}

然后在您的 dispatcher-servlet.xml 中注册此 HandlerInterceptor

<mvc:interceptors>
    <bean class="your.package.ValidationInterceptor"/>
</mvc:interceptors>

您可以将其配置为更 url 具体。请参阅 Spring 参考资料的 22.16.5 Interceptors 部分。

虽然 HandlerInterceptorAdapter 似乎是 "correct" 解决方案, 它似乎不是您想要的解决方案。

下面的代码可能是您想要的解决方案 (或者至少是您在问题中要求的那个)。

总结:编写自己的preBlampostBlam方法。

一些代码:

@RestController
@RequestMapping("/{parameter}")
public class MyController
{

    @ExceptionHandler(SomeException.class)
    public Object handleSomeException()
    {
    /* handle */
    }

    @RequestMapping("/something")
    public Object handleSomething(@PathVariable("parameter") String parameter)
    {
        preBlam(desired params here);

        /* handle */

        postBlam(desired params here);
    }

    @RequestMapping("/somethingElse")
    public Object handleSomethingElse(@PathVariable("parameter") String parameter)
    {
        preBlam(desired params here);

        /* handle */

        postBlam(desired params here);
    }

    private blam preBlam(parameters)
    {
    // do initial blamish work
    }

    private blam postBlam(parameters)
    {
    // do post blamish work here
    }
}

另一种选择: 使用 AOP 为受影响的方法设置 pre 和 post 处理程序。 我不是 AOP 的大用户,所以我不能随便举个例子。

由于你想以通用的方式处理路径变量,考虑引入模型对象。有了这个你可以验证属性(java bean validation)并且还可以混合路径变量和查询参数(这里是一个非常简单的例子,你甚至可以创建自定义验证):

@Data
class SomeModel {
  @NotEmpty
  private String parameter;
}

在控制器中,您只需将模型添加为参数即可:

@RequestMapping("/something")
public Object handleSomething(@Valid SomeModel model) {
  /* handle using model.getParameter() */
}

上面遗漏的地方回答了如何为特定控制器注册拦截器,可以按如下方式完成:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor());
        registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }
}

在XML中,同样是:

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

spring documentation