SpringBoot对Spring MVC都做了哪些事?(三)

上一篇:《​​SpringBoot对Spring MVC都做了哪些事?对S都(二)​​》

错误处理

默认情况下,Spring Boot提供了一个/error映射,对S都以合理的对S都方式处理所有错误,并在servlet容器中注册为“全局”错误页。对S都对于机器客户端,对S都它生成一个JSON响应,对S都其中包含错误、对S都HTTP状态和异常消息的对S都详细信息。对于浏览器客户端,对S都有一个以HTML格式呈现相同数据的对S都“whitelabel”错误视图(要对其进行自定义,请添加一个解决错误的对S都视图)。

如果要自定义默认错误处理行为,对S都可以设置许多server.error属性。对S都

要完全替换默认行为,对S都可以实现ErrorController并注册该类型的对S都bean定义,或者添加ErrorAttributes类型的bean以使用现有机制,但替换内容。

你还可以定义一个用@ControllerAdvice注释的类,以自定义JSON格式,以针对特定控制器和/或异常类型返回,如以下示例所示:

@ControllerAdvice(basePackageClasses = AcmeController.class)public class AcmeControllerAdvice extends ResponseEntityExceptionHandler { @ExceptionHandler(YourException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); }}

在前面的示例中,如果你的异常是由与AcmeController在同一个包中定义的控制器引发的,网站模板那么将使用CustomErrorType POJO的JSON表示,而不是ErrorAttributes表示。

自定义错误页

如果要显示给定状态代码的自定义HTML错误页面,可以将文件添加到/error目录。错误页面可以是静态HTML(即,添加到任何静态资源目录下),也可以使用模板构建。文件名应为准确的状态代码或序列掩码。

例如,要将404映射到静态HTML文件,目录结构如下:

src/ +- main/ +- java/ | + <source code> +- resources/ +- public/ +- error/ | +- 404.html +- <other public assets>

要使用FreeMarker模板映射所有5xx错误,目录结构如下:

src/ +- main/ +- java/ | + <source code> +- resources/ +- templates/ +- error/ | +- 5xx.ftlh +- <other templates>

对于更复杂的映射,你还可以添加实现ErrorViewResolver接口的bean,如以下示例所示:

public class MyErrorViewResolver implements ErrorViewResolver { @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { // Use the request or status to optionally return a ModelAndView return ... }}

系统默认提供的DefaultErrorViewResolver 在该类中我们可以看到默认从如下几个位置查找错误页。

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final Map<Series, String> SERIES_VIEWS; static { Map<Series, String> views = new EnumMap<>(Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views);} @Overridepublic ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { // 默认进入这里,根据错误码的序列进行解析视图 modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView;} private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; // ... return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { // 从以下路径进行查找(路径都会拼接上error/目录) // classpath:/META-INF/resources/ // classpath:/resources/ // classpath:/static/ // classpath:/public/ // 以上路径都可以通过spring.web.resources.staticLocations进行配置 for (String location : this.resources.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } } return null; }}

Spring MVC之外的映射错误页面

对于不使用Spring MVC的应用程序,可以使用ErrorPageRegistrar接口直接注册ErrorPages。此抽象直接与底层嵌入式servlet容器一起工作,即使你没有Spring MVC DispatcherServlet,也可以工作。

自定义错误页

@Beanpublic ErrorPageRegistrar errorPageRegistrar(){ return new MyErrorPageRegistrar();}private static class MyErrorPageRegistrar implements ErrorPageRegistrar { @Override public void registerErrorPages(ErrorPageRegistry registry) { registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400")); }}

工作原理

TomcatServletWebServerFactorypublic class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { private Set<ErrorPage> errorPages = new LinkedHashSet<>(); public WebServer getWebServer(ServletContextInitializer... initializers) { prepareContext(tomcat.getHost(), initializers); } protected void prepareContext(Host host, ServletContextInitializer[] initializers) { configureContext(context, initializersToUse); } protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); // 遍历所有的ErrorPage对象,服务器托管将其注册到Tomcat容器中 for (ErrorPage errorPage : getErrorPages()) { org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage(); tomcatErrorPage.setLocation(errorPage.getPath()); tomcatErrorPage.setErrorCode(errorPage.getStatusCode()); tomcatErrorPage.setExceptionType(errorPage.getExceptionName()); context.addErrorPage(tomcatErrorPage); } }}注册处理器// 在该自动配置类中,会注册一个BeanPostProcessorsRegistrar处理器@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,...})public class ServletWebServerFactoryAutoConfiguration { }注册错误页注册器

在上一步中注册了BeanPostProcessorsRegistrar处理器。在该处理器中会注册一个ErrorPageRegistrarBeanPostProcessor错误页注册器的处理器。

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // ... // 注册ErrorPageRegistrarBeanPostProcessor,又是一个处理器 registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new); }}注册错误页public class ErrorPageRegistrarBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { private ListableBeanFactory beanFactory; private List<ErrorPageRegistrar> registrars; @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = (ListableBeanFactory) beanFactory; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 判断当前的Bean是不是ErrorPageRegistry对象,在9.2中最后我们提到了TomcatServletWebServerFactory类实现了 // ErrorPageRegistry接口。 if (bean instanceof ErrorPageRegistry) { // 处理 postProcessBeforeInitialization((ErrorPageRegistry) bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } // registry = TomcatServletWebServerFactory private void postProcessBeforeInitialization(ErrorPageRegistry registry) { // 获取容器中所有ErrorPageRegistrar类型的Bean对象 for (ErrorPageRegistrar registrar : getRegistrars()) { // 将错误页注册到TomcatServletWebServerFactory中 registrar.registerErrorPages(registry); } } private Collection<ErrorPageRegistrar> getRegistrars() { if (this.registrars == null) { // 从容器中获取所有ErrorPageRegistrar类型的Bean // 容器默认是注册了一个ErrorPageRegistrar this.registrars = new ArrayList<>( this.beanFactory.getBeansOfType(ErrorPageRegistrar.class, false, false).values()); this.registrars.sort(AnnotationAwareOrderComparator.INSTANCE); this.registrars = Collections.unmodifiableList(this.registrars); } return this.registrars; }}

以上的过程就实现了自定义错误页的注册实现。

跨域支持

跨源资源共享(CORS)是大多数浏览器实现的W3C规范,允许您以灵活的方式指定授权何种跨域请求。而不是使用一些不太安全、功能不太强大的方法,如IFRAME或JSONP。

从版本4.2开始,Spring MVC支持CORS。在Spring Boot应用程序中使用带有@CrossOrigin注释的控制器方法CORS配置不需要任何特定配置。可以通过使用自定义的addCorsMappings(CorsRegistry)方法注册WebMVCConfiguer bean来定义全局CORS配置,服务器租用如下例所示:

@Configuration(proxyBeanMethods = false)public class MyConfiguration { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**"); } }; }}
热点
上一篇:2016年1月1日:注册价格将降至每年7欧元。
下一篇:注册域名要了解几大点?新手有什么方式注册域名?