在Spring 4的许多新功能中,我发现了@ControllerAdvice的改进。 @ControllerAdvice是@Component的特殊化,用于定义适用于所有@RequestMapping方法的@ ExceptionHandler,@ InitBinder和@ModelAttribute方法。 在Spring 4之前,@ ControllerAdvice在同一Dispatcher Servlet中协助了所有控制器。 在Spring 4中,它已经发生了变化。 从Spring 4开始,可以将@ControllerAdvice配置为支持定义的控制器子集,而仍可以使用默认行为。
@ControllerAdvice协助所有控制器
假设我们要创建一个错误处理程序,它将向用户显示应用程序错误。 我们假设这是一个基本的Spring MVC应用程序,其中Thymeleaf作为视图引擎,并且我们有一个ArticleController,它具有以下@RequestMapping方法:
package pl.codeleak.t.articles;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("article")
class ArticleController {@RequestMapping("{articleId}")String getArticle(@PathVariable Long articleId) {throw new IllegalArgumentException("Getting article problem.");}
}
如我们所见,我们的方法抛出了一个假想异常。 现在,使用@ControllerAdvice创建一个异常处理程序。 (这不仅是Spring中处理异常的可能方法)。
package pl.codeleak.t.support.web.error;import com.google.common.base.Throwables;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;@ControllerAdvice
class ExceptionHandlerAdvice {@ExceptionHandler(value = Exception.class)public ModelAndView exception(Exception exception, WebRequest request) {ModelAndView modelAndView = new ModelAndView("error/general");modelAndView.addObject("errorMessage", Throwables.getRootCause(exception));return modelAndView;}
}
该课程不是公开的,不是公开的。 我们添加了@ExceptionHandler方法,该方法将处理所有类型的Exception,并将返回“错误/常规”视图:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head><title>Error page</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><link href="../../../resources/css/bootstrap.min.css" rel="stylesheet" media="screen" th:href="@{/resources/css/bootstrap.min.css}"/><link href="../../../resources/css/core.css" rel="stylesheet" media="screen" th:href="@{/resources/css/core.css}"/>
</head>
<body>
<div class="container" th:fragment="content"><div th:replace="fragments/alert :: alert (type='danger', message=${errorMessage})"> </div>
</div>
</body>
</html>
为了测试解决方案,我们可以运行服务器,或者(最好)使用Spring MVC Test模块创建一个测试。 由于我们使用Thymeleaf,因此可以验证渲染的视图:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {RootConfig.class, WebMvcConfig.class})
@ActiveProfiles("test")
public class ErrorHandlingIntegrationTest {@Autowiredprivate WebApplicationContext wac;private MockMvc mockMvc;@Beforepublic void before() {this.mockMvc = webAppContextSetup(this.wac).build();}@Testpublic void shouldReturnErrorView() throws Exception {mockMvc.perform(get("/article/1")).andDo(print()).andExpect(content().contentType("text/html;charset=ISO-8859-1")).andExpect(content().string(containsString("java.lang.IllegalArgumentException: Getting article problem.")));}
}
我们期望内容类型为text / html,并且视图包含带有错误消息HTML片段。 但是,它并不是真正的用户友好型。 但是测试是绿色的。
使用上述解决方案,我们提供了一种处理所有控制器错误的通用机制。 如前所述,我们可以使用@ControllerAdvice:做更多的事情。 例如:
@ControllerAdvice
class Advice {@ModelAttributepublic void addAttributes(Model model) {model.addAttribute("attr1", "value1");model.addAttribute("attr2", "value2");}@InitBinderpublic void initBinder(WebDataBinder webDataBinder) {webDataBinder.setBindEmptyMultipartFiles(false);}
}
@ControllerAdvice协助选定的控制器子集
从Spring 4开始,可以通过批注(),basePackageClasses(),basePackages()方法来自定义@ControllerAdvice,以选择控制器的一个子集来提供帮助。 我将演示一个简单的案例,说明如何利用此新功能。
假设我们要添加一个API以通过JSON公开文章。 因此,我们可以定义一个新的控制器,如下所示:
@Controller
@RequestMapping("/api/article")
class ArticleApiController {@RequestMapping(value = "{articleId}", produces = "application/json")@ResponseStatus(value = HttpStatus.OK)@ResponseBodyArticle getArticle(@PathVariable Long articleId) {throw new IllegalArgumentException("[API] Getting article problem.");}
}
我们的控制器不是很复杂。 如@ResponseBody批注所示,它返回Article作为响应正文。 当然,我们要处理异常。 而且我们不希望以text / html的形式返回错误,而是以application / json的形式返回错误。 然后创建一个测试:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {RootConfig.class, WebMvcConfig.class})
@ActiveProfiles("test")
public class ErrorHandlingIntegrationTest {@Autowiredprivate WebApplicationContext wac;private MockMvc mockMvc;@Beforepublic void before() {this.mockMvc = webAppContextSetup(this.wac).build();}@Testpublic void shouldReturnErrorJson() throws Exception {mockMvc.perform(get("/api/article/1")).andDo(print()).andExpect(status().isInternalServerError()).andExpect(content().contentType("application/json")).andExpect(content().string(containsString("{\"errorMessage\":\"[API] Getting article problem.\"}")));}
}
测试是红色的。 我们能做些什么使其绿色? 我们需要提出另一个建议,这次仅针对我们的Api控制器。 为此,我们将使用@ControllerAdvice注解()选择器。 为此,我们需要创建一个客户或使用现有注释。 我们将使用@RestController预定义注释。 带有@RestController注释的控制器默认情况下采用@ResponseBody语义。 我们可以通过将@Controller替换为@RestController并从处理程序的方法中删除@ResponseBody来修改我们的控制器:
@RestController
@RequestMapping("/api/article")
class ArticleApiController {@RequestMapping(value = "{articleId}", produces = "application/json")@ResponseStatus(value = HttpStatus.OK)Article getArticle(@PathVariable Long articleId) {throw new IllegalArgumentException("[API] Getting article problem.");}
}
我们还需要创建另一个将返回ApiError(简单POJO)的建议:
@ControllerAdvice(annotations = RestController.class)
class ApiExceptionHandlerAdvice {/*** Handle exceptions thrown by handlers.*/@ExceptionHandler(value = Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic ApiError exception(Exception exception, WebRequest request) {return new ApiError(Throwables.getRootCause(exception).getMessage());}
}
这次运行测试套件时,两个测试均为绿色,这意味着ExceptionHandlerAdvice辅助了“标准” ArticleController,而ApiExceptionHandlerAdvice辅助了ArticleApiController。
摘要
在以上场景中,我演示了如何轻松利用@ControllerAdvice批注的新配置功能,希望您像我一样喜欢所做的更改。
参考文献
- SPR-10222
- @RequestAdvice注释文档
翻译自: https://www.javacodegeeks.com/2013/11/controlleradvice-improvements-in-spring-4.html