Java Spring MVC@Controller拦截自己的请求

Java Spring MVC@Controller拦截自己的请求,java,spring,spring-mvc,spring-restcontroller,Java,Spring,Spring Mvc,Spring Restcontroller,假设我们有这样一个控制器: @RestController @RequestMapping("/{parameter}") public class MyController { @ExceptionHandler(SomeException.class) public Object handleSomeException() { /* handle */ } @RequestMapping("/something") public Object handle

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

@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类似的方式为这个特定的控制器实现一些常见的前置\后置处理?例如,我希望控制器中有一个方法,该方法在处理程序方法之前接收请求,但只接收对该特定控制器的请求

我知道RequestBodyAdvice和ResponseBodyAdvice接口,但希望控制器具有本地功能

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

使用HandlerInterceptorAdapter

截取控制器执行前后的数据,记录执行时间的开始和结束,将其保存到现有截取控制器的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");
    }
}
更多示例-

使用HandlerInterceptorAdapter

截取控制器执行前后的数据,记录执行时间的开始和结束,将其保存到现有截取控制器的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");
    }
}

还有一些例子-

你需要自己写。通过扩展,您可以很容易地做到这一点。然后可以覆盖预处理和/或后处理

在HandlerMapping确定适当的 handler对象,但在HandlerAdapter调用该处理程序之前

postHandle是在HandlerAdapter实际调用 处理程序,但在DispatcherServlet呈现视图之前

您可以使用HttpServletRequest的getRequestURI方法为预处理中的不同处理程序添加逻辑

例如:

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


您可以将其配置为更特定于url。请参阅Spring参考的一节

你需要自己写。通过扩展,您可以很容易地做到这一点。然后可以覆盖预处理和/或后处理

在HandlerMapping确定适当的 handler对象,但在HandlerAdapter调用该处理程序之前

postHandle是在HandlerAdapter实际调用 处理程序,但在DispatcherServlet呈现视图之前

您可以使用HttpServletRequest的getRequestURI方法为预处理中的不同处理程序添加逻辑

例如:

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

您可以将其配置为更特定于url。请参阅Spring参考的一节

虽然HandlerInterceptorAdapter似乎是正确的解决方案, 这似乎不是您想要的解决方案

下面的代码可能就是您想要的解决方案 或者至少是你在问题中要求的

总结:编写您自己的preBlam和postbrum方法

一些代码:

@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为受影响的方法设置前置和后置处理程序。 我不是一个大的AOP用户,所以我不能随便举个例子。

虽然HandlerInterceptorAdapter似乎是正确的解决方案, 这似乎不是您想要的解决方案

下面的代码可能就是您想要的解决方案 或者至少是你在问题中要求的

总结:编写您自己的preBlam和postbrum方法

一些代码:

@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为受影响的方法设置前置和后置处理程序。
我不是一个大的AOP用户,所以我不能只是喋喋不休地讲一个例子。

如果您想以一种通用的方式处理path变量,请考虑引入一个模型对象。有了它,您可以验证属性java bean validation,还可以混合路径变量和查询参数。这里有一个非常简单的示例,您甚至可以创建自定义验证:

@Data
class SomeModel {
  @NotEmpty
  private String parameter;
}
在控制器中,您只需添加模型作为参数:

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

由于希望以通用方式处理path变量,请考虑引入一个模型对象。有了它,您可以验证属性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>

以上所有问题中遗漏的是如何为特定控制器注册拦截器,具体操作如下:

@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的AOP:您也可以使用这里提到的HandlerInterceptor查看spring的AOP:您也可以使用这里提到的HandlerInterceptor