【问题标题】:Spring MVC @Controller to intercept own requestsSpring MVC @Controller 拦截自己的请求
【发布时间】:2017-03-29 15:31:32
【问题描述】:
假设我们有一个这样的控制器:
@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 接口,但想要控制器本地的东西。
作为一个使用示例 - 我想在每个处理程序之前对常见的 parameter 变量进行一些验证。
【问题讨论】:
标签:
java
spring
spring-mvc
spring-restcontroller
【解决方案1】:
以上所有问题都回答了如何为特定控制器注册拦截器,可以如下完成:
@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
【解决方案2】:
您需要自己写HandlerInterceptor。您可以通过扩展HandlerInterceptorAdapter 轻松做到这一点。然后你可以覆盖preHandle() 和/或postHandle()。
preHandle() 在HandlerMapping 确定合适的后调用
处理程序对象,但在HandlerAdapter 调用处理程序之前。
postHandle() 在HandlerAdapter 实际调用
处理程序,但在 DispatcherServlet 呈现视图之前。
您可以使用HttpServletRequest 的getRequestURI() 方法为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 Reference 的 22.16.5 Interceptors 部分。
【解决方案3】:
使用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");
}
}
更多示例 - http://www.mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example/
【解决方案4】:
由于您想以通用方式处理路径变量,请考虑引入模型对象。有了这个,您可以验证属性(java bean 验证),还可以混合路径变量和查询参数(这里是一个非常简单的示例,您甚至可以创建自定义验证):
@Data
class SomeModel {
@NotEmpty
private String parameter;
}
在控制器中,您只需将模型添加为参数:
@RequestMapping("/something")
public Object handleSomething(@Valid SomeModel model) {
/* handle using model.getParameter() */
}
【解决方案5】:
虽然HandlerInterceptorAdapter 似乎是“正确”的解决方案,
它似乎不是您想要的解决方案。
下面的代码可能是您想要的解决方案
(或者至少是您在问题中要求的那个)。
总结:编写自己的preBlam 和postBlam 方法。
一些代码:
@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 的大用户,所以我不能随便举个例子。