默认情况下,SpringBoot提供/error处理所有错误的映射,也就是说当出现错误时,SpringBoot底层会请求转发到/error这个映射路径所关联的页面或者控制器方法。(默认异常处理机制)
要验证这个点,我们只需要设置一个拦截器,当每次请求时都在preHandle()中打印请求URI。在浏览器访问不存在的路径映射时:
浏览器:SpringBoot会响应一个"whitelabel"的错误视图,并以HTML格式呈现
服务器:后台输出请求的URI为
整个过程:当浏览器访问不存在的路径映射时,就产生了错误。这时 SpringBoot(底层由默认错误视图解析器 DefaultErrorViewResolver 请求转发到 /error 路径关联的页面,该路径若没有被排除,也会经过拦截器处理)会立即去请求 /error 映射关联的资源(默认为"whitelabel" 错误视图),然后返回给浏览器,我们就看到了这个错误视图。
在SpringBoot发现浏览器请求的路径不存在后,底层发生了一系列的操作:
(1)当浏览器访问不存在的路径映射时,就产生了错误
(2)底层由 DefaultErrorViewResolver (默认错误视图解析器)先去遍历项目中设置的所有静态资源路径,尝试从这些静态资源路径中找到/error/404.html文件
//第一次执行resolve()方法:
//这里的viewName就是状态码
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;//第一次:errorViewName=error/404
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);//进入resolveResource()方法
}
遍历项目中设置的所有静态资源路径,尝试从这些静态资源路径中找到/error/404.html文件
//第一次执行resolveResource()方法
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//resources.getStaticLocations()就是项目中的静态资源路径,根据你的设置而变化
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);
}
}
catch (Exception ex) {
}
}
return null;
}
(3)如果找不到,退而求其次,再遍历项目中设置的所有静态资源路径,尝试从这些静态资源路径中找到/error/4xx.html文件
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//再一次调用resolve()方法
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;
}
//第二次执行resolve()方法:
//这里的viewName就是状态码
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;//第二次:errorViewName=/error/4xx
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);//进入resolveResource()方法
}
遍历项目中设置的所有静态资源路径,尝试从这些静态资源路径中找到/error/4xx.html文件
//第二次执行resolveResource()方法
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//遍历项目中设置的所有静态资源路径
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);
}
}
catch (Exception ex) {
}
}
return null;
}
示例:

(4)以上两轮都找不到匹配的视图,就执行下面的方法(位于AbstractErrorController.java):
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
如果以上方法仍然返回null,然后去BasicErrorController.java里执行如下方法:
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//modelAndView为null,产生一个默认的新的视图,并返回
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
(5)此时浏览器接收到的视图就是这个默认的视图
使用范围不同
(1)过滤器实现的是javax.servlet.Filter接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用依赖于Tomcat等容器,Filter只能在web程序中使用
(2)拦截器是一个Spring组件,由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。它不仅能应用在web程序中,也能用于Application等程序中。
两者的触发时机也不同
(1)过滤器Filter在请求进入容器后,在进入Servlet之前进行预处理。请求结束则是在servlet处理完以后。
(2)拦截器Interceptor是在请求进入Servlet之后(即DispatcherServlet,前端控制器),在进入Controller之前进行预处理的,Controller中渲染了对应的视图之后请求结束。
过滤器不会处理请求转发,拦截器会处理请求转发(前提是拦截器没有放行此请求)。原因是过滤器处理的是容器接收过来的外部请求,而请求转发是服务器内部,Servlet之间的处理。但拦截器是Spring的一个组件,依然会去处理请求转发,除非请求转发的路径被拦截器放行了。
例子演示1--过滤器与拦截器的执行顺序
我们分别在SpringBoot项目中配置一个过滤器和拦截器,测试它们的执行顺序
(1)创建过滤器
package com.li.thymeleaf.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
/**
* @author 李
* @version 1.0
*/
@Component
@Slf4j
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter的init()被调用...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter的doFilter()被调用...");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("MyFilter的destroy()被调用...");
}
}
(2)创建拦截器
package com.li.thymeleaf.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 李
* @version 1.0
*/
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("MyInterceptor的preHandle()被执行...");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("MyInterceptor的postHandle()被执行...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("MyInterceptor的afterCompletion()被执行...");
}
}
在配置类中注册拦截器,注入到spring容器中
package com.li.thymeleaf.config;
import com.li.thymeleaf.interceptor.MyInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author 李
* @version 1.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//addInterceptor注册自定义拦截器
//addPathPatterns指定拦截器规则(拦截所有请求/**)
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**");
}
}
(3)浏览器请求某一个资源,后台输出如下:
例子演示2--过滤器不会处理请求转发,拦截器会处理请求转发
我们在浏览器请求一个不存在的资源如http://localhost:8080/xxx,后台输出如下:
这是因为SpringBoot底层处理了/error,进行了请求转发(默认错误视图解析器请求转发到 /error 路径关联的页面)。而过滤器不会处理请求转发,因此可以看到途中只有拦截器被调用了两次。过滤器只在外部请求资源/xxx的时候被调用了一次。
Spring Boot Reference Documentation
如果要显示给定状态代码的自定义 HTML 错误页,可以将文件添加到目录中。 错误页面可以是静态 HTML(即,添加到任何静态资源目录下),也可以是使用模板构建的。 文件名应为确切的状态代码或系列掩码。/error
例如,要映射到静态 HTML 文件,目录结构如下所示:404
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
要使用 Mustache 模板映射所有错误,目录结构如下所示:5xx
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>
需求:自定义404.html、500.html、4xx.html、5xx.html,当发生相应错误时,显示自定义的页面信息。
(1)4xx.html,使用thymeleaf标签取出状态码和错误信息,其他页面同理
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>4xx</title>
</head>
<body bgcolor="#cedafe">
<div style="text-align: center">
<br/><br/><hr/>
<h1>4xx.html :)</h1>
状态码:<h1 th:text="${status}"></h1>
错误信息:<h1 th:text="${error}"></h1>
<hr/>
</div>
</body>
</html>
(2)5xx.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>5xx</title>
</head>
<body bgcolor="#cedafe">
<div style="text-align: center">
<br/><br/><hr/>
<h1>5xx.html :(</h1>
状态码:<h1 th:text="${status}"></h1>
错误信息:<h1 th:text="${error}"></h1>
<hr/>
</div>
</body>
</html>
(3)404.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>404</title>
</head>
<body bgcolor="#cedafe">
<div style="text-align: center">
<br/><br/><hr/>
<h1>404 Not Found~~</h1>
状态码:<h1 th:text="${status}"></h1>
错误信息:<h1 th:text="${error}"></h1>
<hr/>
</div>
</body>
</html>
(4)500.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>500</title>
</head>
<body bgcolor="#cedafe">
<div style="text-align: center">
<br/><br/><hr/>
<h1>500 服务器内部发生错误 :(</h1>
状态码:<h1 th:text="${status}"></h1>
错误信息:<h1 th:text="${error}"></h1>
<hr/>
</div>
</body>
</html>
(5)模拟500和405错误
package com.li.thymeleaf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @author 李
* @version 1.0
*/
@Controller
public class MyErrorController {
//模拟一个服务器内部错误500
@GetMapping("/abc")
public String abc() {
int i = 10 / 0;
return "manage";
}
//如果get方式请求此路径,会产生405的客户端错误
@PostMapping("/xyz")
public String xyz() {
return "manage";
}
}
(6)浏览器访问不存在的资源时,显示的是404.html。因为发生错误时首先会在静态资源目录中按照:404.html-->4xx.html的顺序寻找视图。
(7)服务器内部错误-500错误:
(8)如果出现出现的是4开头的错误,就会返回4xx.html
在 Java 程序发生异常时,可以通过全局异常来捕获处理异常。
在 SpringBoot 中通过 @ControllerAdvice(修饰的类即为全局异常处理器)加上@ExceptionHandler(修饰方法)处理全局异常,它的底层由 ExceptionHandlerExceptionResolver 类支撑。
需求:演示全局异常使用,当发生类似 ArithmeticException、NullPointException 时,不使用默认异常机制(通过状态码)匹配的 xxx.html,而是通过全局异常机制显式指定的错误页面。
(1)创建全局异常处理器 GlobalExceptionHandler.java
package com.li.thymeleaf.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* @author 李
* @version 1.0
*/
@ControllerAdvice //标识一个全局异常处理器/对象,标识的类将会被注入到spring容器中
@Slf4j
public class GlobalExceptionHandler {
/**
* 编写方法,处理指定异常(这里要处理的异常由你指定)
* @param e 表示发生异常后传递的异常对象
* @param model 将异常信息放入model,传递给下一个页面
* @return
*/
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String handleException(Exception e, Model model) {
log.info("异常信息={}", e.getMessage());
//model的数据会自动放入request域中
model.addAttribute("msg", e.getMessage());
return "/error/global";//指定转发到global.html
}
}
(2)global.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>全局异常</title>
</head>
<body bgcolor="#cedafe">
<div style="text-align: center">
<br/><br/><hr/>
<h1>发生了全局异常/错误 :(</h1>
错误信息:<h1 th:text="${msg}"></h1>
<hr/>
</div>
</body>
</html>
(3)模拟一个500错误
(4)浏览器访问,可以看到显示的是global.html而不是500.html,因为全局异常处理的优先级高于默认异常处理
全局异常的两个注解是通过 ExceptionHandlerExceptionResolver 类来支撑的,该类有一个重要方法:doResolveHandlerMethodException()
执行上述方法时,会获取异常发生的方法以及发生的异常。并在返回的模型和视图类也会带有这两个数据,我们可以根据这两个信息进行日志输出,在异常发生时可以迅速定位处理异常:
日志输出:
如果SpringBoot提供的异常不能满足开发需求,我们也可以自定义异常。
自定义异常处理(若采用默认的处理机制) = 自定义异常类 + @ResponseStatus
@ResponseStatus 底层是 ResponseStatusExceptionResolve,底层调用 response.sendError(statusCode,resolvedReason);
自定义异常的处理方式:
需求:自定义一个异常类 AccessException,当用户访问某个无权访问的路径时,抛出该异常,显示自定义异常的状态码。
(1)自定义异常类:AccessException.java
package com.li.thymeleaf.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @author 李
* @version 1.0
* 自定义的异常类
* (1)如果继承Exception,属于编译异常
* (2)如果继承RuntimeException,属于运行异常(一般来说都是继承RuntimeException)
* (3)@ResponseStatus(value = HttpStatus.FORBIDDEN)
* 指定发生此异常时,通过http协议返回的的状态码(403-Forbidden)
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class AccessException extends RuntimeException {
public AccessException() {
}
//提供一个构造器,可以指定信息
public AccessException(String message) {
super(message);
}
}
(2)在Controller中模拟发生异常
//模拟发生 AccessException
@GetMapping("/errTest")
public String test(String name) {
if (!"tom".equals(name)) {
throw new AccessException();
}
return "manage";//视图地址
}
(3)4xx.html(略)
(4)浏览器访问localhost:8080/errTest?name=jack,返回的页面如下:
因为返回的状态码为403,根据默认的异常处理机制,这里会寻找4xx.html页面返回给浏览器。如果是全局异常处理机制的话就会走全局异常处理的流程。
如果把自定义异常类放在全局异常处理器,因为全局异常处理优先级高,因此会走全局异常的处理机制。
比如在上一个例子中,添加如下代码:
package com.li.thymeleaf.exception;
import ...
/**
* @author 李
* @version 1.0
* 全局异常处理器
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({AccessException.class})
public String handleException(Exception e, Model model,HandlerMethod handlerMethod) {
log.info("出现异常的方法={}", handlerMethod.getMethod());
log.info("异常信息={}", e.getMessage());
//model的数据会自动放入request域中
model.addAttribute("msg", e.getMessage());
return "/error/global";//指定转发到global.html
}
}
浏览器访问localhost:8080/errTest?name=jack,返回的页面如下:(global.html)
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd
在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee
我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby-vips的github页面上的链接,我们将不胜感激!如果有ruby-
我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d
我刚刚安装了带有RVM的Ruby2.2.0,并尝试使用它得到了这个:$rvmuse2.2.0--defaultUsing/Users/brandon/.rvm/gems/ruby-2.2.0dyld:Librarynotloaded:/usr/local/lib/libgmp.10.dylibReferencedfrom:/Users/brandon/.rvm/rubies/ruby-2.2.0/bin/rubyReason:Incompatiblelibraryversion:rubyrequiresversion13.0.0orlater,butlibgmp.10.dylibpro
我们如何捕获或/和处理ruby中所有未处理的异常?例如,这样做的动机可能是将某种异常记录到不同的文件或发送电子邮件给系统管理。在Java中我们会做Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandlerex);在Node.js中process.on('uncaughtException',function(error){/*code*/});在PHP中register_shutdown_function('errorHandler');functionerrorHandler(){$error=error_
我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。