Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 一区二区三区在线免费看,色屁屁www免费观看影院,国产精品资源网

          整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Spring Boot 異常處理

          Spring Boot 異常處理

          . 前言

          程序中出現(xiàn)異常是普遍現(xiàn)象, Java 程序員想必早已習(xí)慣,根據(jù)控制臺(tái)輸出的異常信息,分析異常產(chǎn)生的原因,然后進(jìn)行針對(duì)性處理的過(guò)程。

          Spring Boot 項(xiàng)目中,數(shù)據(jù)持久層、服務(wù)層到控制器層都可能拋出異常。如果我們?cè)诟鲗佣歼M(jìn)行異常處理,程序代碼會(huì)顯得支離破碎,難以理解。

          實(shí)際上,異常可以從內(nèi)層向外層不斷拋出,最后在控制器層進(jìn)行統(tǒng)一處理。 Spring Boot 提供了全局性的異常處理機(jī)制,本節(jié)我們就分別演示下,默認(rèn)情況、控制器返回視圖、控制器返回 JSON 數(shù)據(jù)三種情況的異常處理方法。

          2. Spring Boot 默認(rèn)異常處理機(jī)制

          Spring Boot 開(kāi)發(fā)的 Web 項(xiàng)目具備默認(rèn)的異常處理機(jī)制,無(wú)須編寫(xiě)異常處理相關(guān)代碼,即可提供默認(rèn)異常機(jī)制,下面具體演示下。

          2.1 使用 Spring Initializr 創(chuàng)建項(xiàng)目

          Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-exception-default ,生成項(xiàng)目后導(dǎo)入 Eclipse 開(kāi)發(fā)環(huán)境。

          2.2 引入項(xiàng)目依賴

          引入 Web 項(xiàng)目依賴即可。

          實(shí)例:

          		<!-- web項(xiàng)目依賴 -->
          		<dependency>
          			<groupId>org.springframework.boot</groupId>
          			<artifactId>spring-boot-starter-web</artifactId>
          		</dependency>

          2.3 Spring Boot 默認(rèn)異常處理

          我們?cè)趩?dòng)項(xiàng)目, Spring Boot Web 項(xiàng)目默認(rèn)啟動(dòng)端口為 8080 ,所以直接訪問(wèn) http://127.0.0.1:8080 ,顯示如下:

          Spring Boot 默認(rèn)異常信息提示頁(yè)面

          如上圖所示,Spring Boot 默認(rèn)的異常處理機(jī)制生效,當(dāng)出現(xiàn)異常時(shí)會(huì)自動(dòng)轉(zhuǎn)向 /error 路徑。

          3. 控制器返回視圖時(shí)的異常處理

          在使用模板引擎開(kāi)發(fā) Spring Boot Web 項(xiàng)目時(shí),控制器會(huì)返回視圖頁(yè)面。我們使用 Thymeleaf 演示控制器返回視圖時(shí)的異常處理方式,其他模板引擎處理方式也是相似的。

          3.1 使用 Spring Initializr 創(chuàng)建項(xiàng)目

          Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-exception-controller,生成項(xiàng)目后導(dǎo)入 Eclipse 開(kāi)發(fā)環(huán)境。

          3.2 引入項(xiàng)目依賴

          引入 Web 項(xiàng)目依賴、熱部署依賴。此處使用 Thymeleaf 演示控制器返回視圖時(shí)的異常處理方式,所以引入 Thymeleaf 依賴。

          實(shí)例:

          		<!-- web項(xiàng)目依賴 -->
          		<dependency>
          			<groupId>org.springframework.boot</groupId>
          			<artifactId>spring-boot-starter-web</artifactId>
          		</dependency>
          		<!-- 熱部署 -->
          		<dependency>
          			<groupId>org.springframework.boot</groupId>
          			<artifactId>spring-boot-devtools</artifactId>
          		</dependency>
          		<!-- ThymeLeaf依賴 -->
          		<dependency>
          			<groupId>org.springframework.boot</groupId>
          			<artifactId>spring-boot-starter-thymeleaf</artifactId>
          		</dependency>

          3.3 定義異常類

          在異常處理之前,我們應(yīng)該根據(jù)業(yè)務(wù)場(chǎng)景具體情況,定義一系列的異常類,習(xí)慣性的還會(huì)為各種異常分配錯(cuò)誤碼,如下圖為支付寶開(kāi)放平臺(tái)的公共錯(cuò)誤碼信息。

          支付寶開(kāi)放平臺(tái)錯(cuò)誤碼

          本節(jié)我們?yōu)榱搜菔荆?jiǎn)單的定義 2 個(gè)異常類,包含錯(cuò)誤碼及錯(cuò)誤提示信息。

          實(shí)例:

          /**
           * 自定義異常
           */
          public class BaseException extends Exception {
          	/**
          	 * 錯(cuò)誤碼
          	 */
          	private int code;
          	/**
          	 * 錯(cuò)誤提示信息
          	 */
          	private String msg;
          
          	public BaseException(int code, String msg) {
          		super();
          		this.code=code;
          		this.msg=msg;
          	}
          	// 省略get set
          }
          代碼塊1234567891011121314151617181920

          實(shí)例:

          /**
           * 密碼錯(cuò)誤異常
           */
          public class PasswordException extends BaseException {
          	public PasswordException() {
          		super(10001, "密碼錯(cuò)誤");
          	}
          }

          實(shí)例:

          /**
           * 驗(yàn)證碼錯(cuò)誤異常
           */
          public class VerificationCodeException extends BaseException {
          	public VerificationCodeException() {
          		super(10002, "驗(yàn)證碼錯(cuò)誤");
          	}
          }

          3.4 控制器拋出異常

          定義控制器 GoodsController ,然后使用注解 @Controller 標(biāo)注該類,類中方法的返回值即為視圖文件名。

          在 GoodsController 類定義 4 個(gè)方法,分別用于正常訪問(wèn)、拋出密碼錯(cuò)誤異常、拋出驗(yàn)證碼錯(cuò)誤異常、拋出未自定義的異常,代碼如下。

          實(shí)例:

          /**
           * 商品控制器
           */
          @Controller
          public class GoodsController {
          	/**
          	 * 正常方法
          	 */
          	@RequestMapping("/goods")
          	public String goods() {
          		return "goods";// 跳轉(zhuǎn)到resource/templates/goods.html頁(yè)面
          	}
          
          	/**
          	 * 拋出密碼錯(cuò)誤異常的方法
          	 */
          	@RequestMapping("/checkPassword")
          	public String checkPassword() throws PasswordException {
          		if (true) {
          			throw new PasswordException();// 模擬拋出異常,便于測(cè)試
          		}
          		return "goods";
          	}
          
          	/**
          	 * 拋出驗(yàn)證碼錯(cuò)誤異常的方法
          	 */
          	@RequestMapping("/checkVerification")
          	public String checkVerification() throws VerificationCodeException {
          		if (true) {
          			throw new VerificationCodeException();// 模擬拋出異常,便于測(cè)試
          		}
          		return "goods";
          	}
          
          	/**
          	 * 拋出未自定義的異常
          	 */
          	@RequestMapping("/other")
          	public String other() throws Exception {
          		int a=1 / 0;// 模擬異常
          		return "goods";
          	}
          }

          3.5 開(kāi)發(fā)基于 @ControllerAdvice 的全局異常類

          @ControllerAdvice 注解標(biāo)注的類可以處理 @Controller 標(biāo)注的控制器類拋出的異常,然后進(jìn)行統(tǒng)一處理。

          實(shí)例:

          /**
           * 控制器異常處理類
           */
          @ControllerAdvice(annotations=Controller.class) // 全局異常處理
          public class ControllerExceptionHandler {
          	@ExceptionHandler({ BaseException.class }) // 當(dāng)發(fā)生BaseException類(及其子類)的異常時(shí),進(jìn)入該方法
          	public ModelAndView baseExceptionHandler(BaseException e) {
          		ModelAndView mv=new ModelAndView();
          		mv.addObject("code", e.getCode());
          		mv.addObject("message", e.getMessage());
          		mv.setViewName("myerror");// 跳轉(zhuǎn)到resource/templates/myerror.html頁(yè)面
          		return mv;
          	}
          
          	@ExceptionHandler({ Exception.class }) // 當(dāng)發(fā)生Exception類的異常時(shí),進(jìn)入該方法
          	public ModelAndView exceptionHandler(Exception e) {
          		ModelAndView mv=new ModelAndView();
          		mv.addObject("code", 99999);// 其他異常統(tǒng)一編碼為99999
          		mv.addObject("message", e.getMessage());
          		mv.setViewName("myerror");// 跳轉(zhuǎn)到resource/templates/myerror.html頁(yè)面
          		return mv;
          	}
          }
          

          按照 ControllerExceptionHandler 類的處理邏輯,當(dāng)發(fā)生 BaseException 類型的異常時(shí),會(huì)跳轉(zhuǎn)到 myerror.html 頁(yè)面,并顯示相應(yīng)的錯(cuò)誤碼和錯(cuò)誤信息;當(dāng)發(fā)生其他類型的異常時(shí),錯(cuò)誤碼為 99999 ,錯(cuò)誤信息為相關(guān)的異常信息。

          3.6 開(kāi)發(fā)前端頁(yè)面

          在 resource/templates 下分別新建 goods.html 和 myerror.html 頁(yè)面,作為正常訪問(wèn)及發(fā)生異常時(shí)跳轉(zhuǎn)的視圖頁(yè)面。

          實(shí)例:

          <!DOCTYPE html>
          <html lang="en">
          <head>
          <meta charset="UTF-8">
          <title>goods.html頁(yè)面</title>
          </head>
          <body>
          	<div>商品信息頁(yè)面</div>
          </body>
          </html>

          實(shí)例:

          <!DOCTYPE html>
          <html lang="en">
          <head>
          <meta charset="UTF-8">
          <title>myerror.html頁(yè)面</title>
          </head>
          <body>
          	錯(cuò)誤碼:
          	<span th:text="${code}"></span> 
          	錯(cuò)誤信息:
          	<span th:text="${message}"></span>
          </body>
          </html>
          代碼塊12345678910111213

          3.7 測(cè)試

          啟動(dòng)項(xiàng)目,分別訪問(wèn)控制器中的 4 個(gè)方法,結(jié)果如下:

          訪問(wèn)正常方法 /goods

          訪問(wèn)拋出自定義異常的方法 /checkPassword

          訪問(wèn)拋出自定義異常的方法 /checkVerification

          訪問(wèn)拋出未自定義異常的方法 /other

          可見(jiàn),當(dāng)控制器方法拋出異常時(shí),會(huì)按照全局異常類設(shè)定的邏輯統(tǒng)一處理。

          4. 控制器返回 JSON 數(shù)據(jù)時(shí)的異常處理

          在控制器類上添加 @RestController 注解,控制器方法處理完畢后會(huì)返回 JSON 格式的數(shù)據(jù)。

          此時(shí),可以使用 @RestControllerAdvice 注解標(biāo)注的類 ,來(lái)捕獲 @RestController 標(biāo)注的控制器拋出的異常。

          4.1 使用 Spring Initializr 創(chuàng)建項(xiàng)目

          Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-exception-restcontroller,生成項(xiàng)目后導(dǎo)入 Eclipse 開(kāi)發(fā)環(huán)境。

          4.2 引入項(xiàng)目依賴

          引入 Web 項(xiàng)目依賴、熱部署依賴即可。

          實(shí)例:

          		<!-- web項(xiàng)目依賴 -->
          		<dependency>
          			<groupId>org.springframework.boot</groupId>
          			<artifactId>spring-boot-starter-web</artifactId>
          		</dependency>
          		<!-- 熱部署 -->
          		<dependency>
          			<groupId>org.springframework.boot</groupId>
          			<artifactId>spring-boot-devtools</artifactId>
          		</dependency>
          

          4.3 定義異常類

          還是使用上文中定義的異常類即可。

          4.4 統(tǒng)一控制器返回?cái)?shù)據(jù)格式

          這時(shí)候,我們就需要思考一個(gè)問(wèn)題了。前端請(qǐng)求后端控制器接口后,怎么區(qū)分后端接口是正常返回結(jié)果,還是發(fā)生了異常?

          不論后端接口是正常執(zhí)行,還是中間發(fā)生了異常,最好給前端返回統(tǒng)一的數(shù)據(jù)格式,便于前端統(tǒng)一分析處理。

          OK,此時(shí)我們就可以封裝后端接口返回的業(yè)務(wù)邏輯對(duì)象 ResultBo ,代碼如下:

          實(shí)例:

          /**
           * 后端接口返回的統(tǒng)一業(yè)務(wù)邏輯對(duì)象
           */
          public class ResultBo<T> {
          
          	/**
          	 * 錯(cuò)誤碼 0表示沒(méi)有錯(cuò)誤(異常) 其他數(shù)字代表具體錯(cuò)誤碼
          	 */
          	private int code;
          	/**
          	 * 后端返回消息
          	 */
          	private String msg;
          	/**
          	 * 后端返回的數(shù)據(jù)
          	 */
          	private T data;
          
          	/**
          	 * 無(wú)參數(shù)構(gòu)造函數(shù)
          	 */
          	public ResultBo() {
          		this.code=0;
          		this.msg="操作成功";
          	}
          
          	/**
          	 * 帶數(shù)據(jù)data構(gòu)造函數(shù)
          	 */
          	public ResultBo(T data) {
          		this();
          		this.data=data;
          	}
          
          	/**
          	 * 存在異常的構(gòu)造函數(shù)
          	 */
          	public ResultBo(Exception ex) {
          		if (ex instanceof BaseException) {
          			this.code=((BaseException) ex).getCode();
          			this.msg=ex.getMessage();
          		} else {
          			this.code=99999;// 其他未定義異常
          			this.msg=ex.getMessage();
          		}
          	}
          	// 省略 get set
          }

          4.5 控制器拋出異常

          定義控制器 RestGoodsController ,并使用 @RestController 注解標(biāo)注。在其中定義 4 個(gè)方法,然后分別用于正常訪問(wèn)、拋出密碼錯(cuò)誤異常、拋出驗(yàn)證碼錯(cuò)誤異常,以及拋出不屬于自定義異常類的異常。

          實(shí)例:

          /**
           * Rest商品控制器
           */
          @RestController
          public class RestGoodsController {
          	/**
          	 * 正常方法
          	 */
          	@RequestMapping("/goods")
          	public ResultBo goods() {
          		return new ResultBo<>(new ArrayList());// 正常情況下應(yīng)該返回商品列表
          	}
          
          	/**
          	 * 拋出密碼錯(cuò)誤異常的方法
          	 */
          	@RequestMapping("/checkPassword")
          	public ResultBo checkPassword() throws PasswordException {
          		if (true) {
          			throw new PasswordException();// 模擬拋出異常,便于測(cè)試
          		}
          		return new ResultBo<>(true);// 正常情況下應(yīng)該返回檢查密碼的結(jié)果true或false
          	}
          
          	/**
          	 * 拋出驗(yàn)證碼錯(cuò)誤異常的方法
          	 */
          	@RequestMapping("/checkVerification")
          	public ResultBo checkVerification() throws VerificationCodeException {
          		if (true) {
          			throw new VerificationCodeException();// 模擬拋出異常,便于測(cè)試
          		}
          		return new ResultBo<>(true);// 正常情況下應(yīng)該返回檢查驗(yàn)證碼的結(jié)果true或false
          	}
          
          	/**
          	 * 拋出未自定義的異常
          	 */
          	@RequestMapping("/other")
          	public ResultBo other() throws Exception {
          		int a=1 / 0;// 模擬異常
          		return new ResultBo<>(true);
          	}
          }

          4.6 開(kāi)發(fā)基于 @RestControllerAdvice 的全局異常類

          @RestControllerAdvice 注解標(biāo)注的類可以處理 RestController 控制器類拋出的異常,然后進(jìn)行統(tǒng)一處理。

          實(shí)例:

          /**
           * Rest控制器異常處理類
           */
          @RestControllerAdvice(annotations=RestController.class) // 全局異常處理
          public class RestControllerExceptionHandler {
          	/**
          	 * 處理BaseException類(及其子類)的異常
          	 */
          	@ExceptionHandler({ BaseException.class })
          	public ResultBo baseExceptionHandler(BaseException e) {
          		return new ResultBo(e);
          	}
          
          	/**
          	 * 處理Exception類的異常
          	 */
          	@ExceptionHandler({ Exception.class })
          	public ResultBo exceptionHandler(Exception e) {
          		return new ResultBo(e);
          	}
          }
          

          4.7 測(cè)試

          啟動(dòng)項(xiàng)目,分別嘗試訪問(wèn)控制器中的 4 個(gè)接口,結(jié)果如下。

          訪問(wèn)正常方法 /goods

          訪問(wèn)拋出異常的方法 /checkPassword

          訪問(wèn)拋出異常的方法 /checkVerification

          訪問(wèn)拋出異常的方法 /other

          5. 小結(jié)

          Spring Boot 的默認(rèn)異常處理機(jī)制,實(shí)際上只能做到提醒開(kāi)發(fā)者 “這個(gè)后端接口不存在” 的作用,作用非常有限。

          所以我們?cè)陂_(kāi)發(fā) Spring Boot 項(xiàng)目時(shí),需要根據(jù)項(xiàng)目的實(shí)際情況,定義各類異常,并站在全局的角度統(tǒng)一處理異常。

          不管項(xiàng)目有多少層次,所有異常都可以向外拋出,直到控制器層進(jìn)行集中處理。

          • 對(duì)于返回視圖的控制器,如果沒(méi)發(fā)生異常就跳轉(zhuǎn)正常頁(yè)面,如果發(fā)生異常可以自定義錯(cuò)誤信息頁(yè)面。
          • 對(duì)于返回 JSON 數(shù)據(jù)的控制器,最好是定義統(tǒng)一的數(shù)據(jù)返回格式,便于前端根據(jù)返回信息進(jìn)行正常或者異常情況的處理。

          于數(shù)千個(gè)項(xiàng)目,通過(guò)收集和分析大量數(shù)據(jù)選出了 10 大 JavaScript 錯(cuò)誤,對(duì)其產(chǎn)生的根源,以及如何避免再發(fā)生這些錯(cuò)誤進(jìn)行剖析。實(shí)際開(kāi)發(fā)中,如果能夠避免這些錯(cuò)誤,就可以成為更好的開(kāi)發(fā)者。

          這里只關(guān)注影響面最大的那些錯(cuò)誤。為此,我們統(tǒng)計(jì)了錯(cuò)誤在各個(gè)公司的項(xiàng)目中發(fā)生的次數(shù),而不是錯(cuò)誤發(fā)生的總次數(shù),因?yàn)槿绻沁@樣的話,讀者就可能看到大量與他們不相干的統(tǒng)計(jì)信息。

          以下是排名靠前的 10 大 JavaScript 錯(cuò)誤:

          出于可讀性方面的考慮,每個(gè)錯(cuò)誤的描述經(jīng)過(guò)精簡(jiǎn)。

          1.Uncaught TypeError: Cannot read property

          如果你是一名 JavaScript 開(kāi)發(fā)者,對(duì)這個(gè)錯(cuò)誤可能已經(jīng)熟視無(wú)睹。在 Chrome 里讀取未定義對(duì)象的屬性或調(diào)用未定義對(duì)象的方法時(shí)就會(huì)發(fā)生這個(gè)錯(cuò)誤,在 Chrome 開(kāi)發(fā)者控制臺(tái)可以很容易地重現(xiàn)這個(gè)錯(cuò)誤。

          發(fā)生這個(gè)錯(cuò)誤的原因有很多,其中最為常見(jiàn)的是,在渲染 UI 組件時(shí)沒(méi)有正確初始化狀態(tài)。我們通過(guò)一個(gè)真實(shí)的例子來(lái)看看這個(gè)錯(cuò)誤是怎么發(fā)生的。我們選擇 React 作為示例,不過(guò)在其他框架(Angular、Vue 等)中也是一樣的。

          class Quiz extends Component {
          componentWillMount() {
           axios.get('/thedata').then(res=> {
           this.setState({items: res.data});
           });
          }
          render() {
           return (
           <ul>
           {this.state.items.map(item=>
           <li key={item.id}>{item.name}</li>
           )}
           </ul>
           );
          }
          }

          這里要注意兩件事:

          • 組件的狀態(tài)(如 this.state)在一開(kāi)始就是 undefined。

          • 如果是通過(guò)異步的方式來(lái)加載數(shù)據(jù),那么在數(shù)據(jù)加載進(jìn)來(lái)之前,至少要渲染一次組件——不管是在構(gòu)造器、componentWillMout() 還是 componentDidMout() 中加載數(shù)據(jù)。Quiz 在進(jìn)行第一次渲染時(shí),this.state.items 是 undefined,那么 ItemList 就會(huì)得到 undefined 的數(shù)據(jù)項(xiàng),這樣就會(huì)在控制臺(tái)看到這個(gè)錯(cuò)誤——“Uncaught TypeError:Cannot read property ‘map’ of undefined”。

          要解決這個(gè)問(wèn)題其實(shí)很簡(jiǎn)單,在構(gòu)造器里使用適當(dāng)?shù)哪J(rèn)值進(jìn)行初始化。

          class Quiz extends Component {
          // 增加這個(gè):
          constructor(props) {
           super(props);
           // 使用空數(shù)組給 state 賦值
           this.state={
           items: []
           };
          }
          componentWillMount() {
           axios.get('/thedata').then(res=> {
           this.setState({items: res.data});
           });
          }
          render() {
           return (
           <ul>
           {this.state.items.map(item=>
           <li key={item.id}>{item.name}</li>
           )}
           </ul>
           );
          }
          }

          2. TypeError: ’undefined’ is not an object

          在 Safari 里讀取未定義對(duì)象的屬性或調(diào)用未定義對(duì)象的方法時(shí)就會(huì)發(fā)生這個(gè)錯(cuò)誤,在 Safari 開(kāi)發(fā)者控制臺(tái)可以很容易地重現(xiàn)這個(gè)錯(cuò)誤。這個(gè)錯(cuò)誤與發(fā)生在 Chrome 里的是差不多的,只是 Safari 為它提供了不同的錯(cuò)誤信息。

          3. TypeError: null is not an object

          在 Safari 里讀取空(null)對(duì)象的屬性或調(diào)用空對(duì)象的方法時(shí)就會(huì)發(fā)生這個(gè)錯(cuò)誤,在 Safari 開(kāi)發(fā)者控制臺(tái)可以很容易地重現(xiàn)這個(gè)錯(cuò)誤。

          有意思的是,在 JavaScript 里,null 和 undefined 其實(shí)是不一樣的,所以我們會(huì)看到兩個(gè)不同的錯(cuò)誤消息。undefined 表示未賦值的變量,而 null 表示變量值為空。可以使用嚴(yán)格等于號(hào)來(lái)證明它們不是同一個(gè)東西。

          在實(shí)際應(yīng)用當(dāng)中,在 JavaScript 里調(diào)用一個(gè)未加載的 DOM 元素就會(huì)出現(xiàn)這個(gè)錯(cuò)誤。如果對(duì)象為空,DOM API 就會(huì)返回 null。

          DOM 元素需要在創(chuàng)建之后才能被訪問(wèn)。JavaScript 代碼是按照從上到下的順序進(jìn)行解析的,所以,如果在 DOM 元素之前有一個(gè)標(biāo)簽包含了 JavaScript 代碼,瀏覽器在解析 HTML 時(shí)就會(huì)執(zhí)行這些代碼。在加載 JavaScript 之前,如果 DOM 元素沒(méi)有被創(chuàng)建,就會(huì)出現(xiàn)這個(gè)錯(cuò)誤。

          在這個(gè)例子里,我們可以通過(guò)添加一個(gè)事件監(jiān)聽(tīng)器來(lái)解決這個(gè)問(wèn)題,在頁(yè)面加載完畢時(shí),事件監(jiān)聽(tīng)器會(huì)通知我們。在 addEventListener 被觸發(fā)之后,init() 方法就可以大膽地訪問(wèn) DOM 元素了。

          <script>
          function init() {
           var myButton=document.getElementById("myButton");
           var myTextfield=document.getElementById("myTextfield");
           myButton.onclick=function() {
           var userName=myTextfield.value;
           }
          }
          document.addEventListener('readystatechange', function() {
           if (document.readyState==="complete") {
           init();
           }
          });
          </script>
          <form>
          <input type="text" id="myTextfield" placeholder="Type your name" />
          <input type="button" id="myButton" value="Go" />
          </form>

          4. (unknown): Script error

          跨域的未捕捉 JavaScript 異常會(huì)變成 Script error。例如,假設(shè) JavaScript 托管在 CDN 上,那么未捕捉的錯(cuò)誤(錯(cuò)誤沒(méi)有在 try-catch 里被捕獲,一路直上到了 window.onerror 里)就會(huì)顯示成“Script error”,而不是顯示具體的錯(cuò)誤消息。這是瀏覽器出于安全方面的考慮,防止跨域傳遞數(shù)據(jù)。

          要想獲得具體的錯(cuò)誤信息,可以這樣做:

          1). 使用 Access-Control-Allow-Origin

          將 Access-Control-Allow-Origin 設(shè)置成“*”,表示該資源可以被任何一個(gè)域訪問(wèn)。如果有必要,可以把“*”替換成你的域名,例如 Access-Control-Allow-Origin: www.example.com。不過(guò),如果使用了 CDN,那么要支持多個(gè)域名可能就會(huì)得不償失,因?yàn)?CDN 存在緩存問(wèn)題。

          下面是在各種環(huán)境如何設(shè)置該字段的示例:

          Apache:

          在 JavaScript 文件所在的目錄創(chuàng)建一個(gè)叫作.htaccess 的文件,并加入如下內(nèi)容:

          Header add Access-Control-Allow-Origin "*"

          Nginx:

          在 JavaScript 對(duì)應(yīng)的 location 配置代碼塊中加入 add_header 指令:

          location ~ ^/assets/ {
           add_header Access-Control-Allow-Origin *;
          }

          HAProxy:

          在 JavaScript 文件對(duì)應(yīng)的 backend 配置塊中加入如下內(nèi)容:

          rspadd Access-Control-Allow-Origin:\ *

          2). 在 script 標(biāo)簽里設(shè)置 crossorigin=“anonymous”

          在每個(gè)設(shè)置了 Access-Control-Allow-Origin 字段的 HTML 頁(yè)面里,將它們的 script 標(biāo)簽的 crossorigin 屬性設(shè)置為“anonymous”。在 Firefox 里,如果出現(xiàn)了 crossorigin,但沒(méi)有設(shè)置 Access-Control-Allow-Origin,JavaScript 腳本就不會(huì)被執(zhí)行。

          5. TypeError: Object doesn’t support property

          在 IE 里讀取未定義對(duì)象的屬性或調(diào)用未定義對(duì)象的方法時(shí)就會(huì)發(fā)生這個(gè)錯(cuò)誤,在 IE 開(kāi)發(fā)者控制臺(tái)可以很容易地重現(xiàn)這個(gè)錯(cuò)誤。

          這個(gè)錯(cuò)誤與 Chrome 里的“TypeError: ‘undefined’ is not a function”是同一個(gè)東西。不同的瀏覽器為相同的錯(cuò)誤提供的錯(cuò)誤消息可能是不一樣的。

          在 IE 里使用 JavaScript 的命名空間時(shí),就很容易碰到這個(gè)錯(cuò)誤。發(fā)生這個(gè)錯(cuò)誤十有八九是因?yàn)?IE 無(wú)法將當(dāng)前命名空間里的方法綁定到 this 關(guān)鍵字上。例如,假設(shè)有個(gè)命名空間 Rollbar,它有一個(gè)方法叫 isAwesome()。在 Rollbar 命名空間中,可以直接使用 this 關(guān)鍵字來(lái)調(diào)用這個(gè)方法:

          this.isAwesome();

          在 Chrome、Firefox 和 Opera 中這樣做都是沒(méi)有問(wèn)題的,但在 IE 中就不行。所以,最安全的做法是指定全命名空間:

          Rollbar.isAwesome();

          6. TypeError: ‘undefined’ is not a function

          在 Chrome 里調(diào)用一個(gè)未定義的函數(shù)時(shí)就會(huì)發(fā)生這個(gè)錯(cuò)誤,可以在 Chrome 開(kāi)發(fā)者控制臺(tái)和 Mozilla 開(kāi)發(fā)者控制臺(tái)重現(xiàn)這個(gè)錯(cuò)誤。

          近年來(lái),JavaScript 的編碼技術(shù)和設(shè)計(jì)模式變得日趨復(fù)雜,回調(diào)和閉包中的自引用情況越來(lái)越普遍,讓人搞不清楚代碼中的 this/that 表示的是什么意思。

          比如下面這段代碼:

          function testFunction() {
          this.clearLocalStorage();
          this.timer=setTimeout(function() {
           this.clearBoard(); // 這里的”this"是指什么?
          }, 0);
          };

          執(zhí)行上面的代碼會(huì)出現(xiàn)這樣的錯(cuò)誤:“Uncaught TypeError: undefined is not a function”。因?yàn)樵谡{(diào)用 setTimeout() 方法時(shí),實(shí)際上是在調(diào)用 window.setTimeout()。傳給 setTimeout() 的匿名函數(shù)的上下文實(shí)際上是 window,而 window 并不包含 clearBoard() 方法。

          對(duì)于舊瀏覽器,以往的解決辦法是將 this 賦值給某個(gè)變量,然后在閉包里使用這個(gè)變量。例如:

          function testFunction () {
          this.clearLocalStorage();
          var self=this; // 將 this 賦值給 self
          this.timer=setTimeout(function(){
           self.clearBoard(); 
          }, 0);
          };

          在新瀏覽器中,可以使用 bind() 方法來(lái)傳遞引用:

          function testFunction () {
          this.clearLocalStorage();
          this.timer=setTimeout(this.reset.bind(this), 0); // 綁定到 'this'
          };
          function testFunction(){
           this.clearBoard(); // 以’this’作為上下文
          };

          7. Uncaught RangeError: Maximum call stack

          在 Chrome 里,有幾種情況會(huì)發(fā)生這個(gè)錯(cuò)誤,其中一個(gè)就是無(wú)限遞歸調(diào)用一個(gè)函數(shù)。這個(gè)錯(cuò)誤可以在 Chrome 開(kāi)發(fā)者控制臺(tái)重現(xiàn)。

          當(dāng)傳給函數(shù)的值超出可接受的范圍時(shí)也會(huì)出現(xiàn)這個(gè)錯(cuò)誤。很多函數(shù)只接受指定范圍的數(shù)值,例如,Number.toExponential(digits) 和 Number.toFixed(digits) 只接受 0 到 20 的數(shù)值,而 Number.toPrecision(digits) 只接受 1 到 21 的數(shù)值。

          var a=new Array(4294967295); //OK
          var b=new Array(-1); //range error
          var num=2.555555;
          document.writeln(num.toExponential(4)); //OK
          document.writeln(num.toExponential(-2)); //range error!
          num=2.9999;
          document.writeln(num.toFixed(2)); //OK
          document.writeln(num.toFixed(25)); //range error!
          num=2.3456;
          document.writeln(num.toPrecision(1)); //OK
          document.writeln(num.toPrecision(22)); //range error!

          8. TypeError: Cannot read property ‘length’

          在 Chrome 里讀取 undefined 變量的 length 屬性時(shí)會(huì)發(fā)生這個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤可以在 Chrome 開(kāi)發(fā)者控制臺(tái)重現(xiàn)。

          length 是數(shù)組的屬性,但如果數(shù)組沒(méi)有初始化或者數(shù)組的變量名被另一個(gè)上下文隱藏起來(lái)的話,訪問(wèn) length 屬性就會(huì)發(fā)生這個(gè)錯(cuò)誤。例如:

          var testArray=["Test"];
          function testFunction(testArray) {
           for (var i=0; i < testArray.length; i++) {
           console.log(testArray[i]);
           }
          }
          testFunction();

          函數(shù)的參數(shù)名會(huì)覆蓋全局的變量名。也就是說(shuō),全局的 testArray 被函數(shù)的參數(shù)名覆蓋了,所以在函數(shù)體里訪問(wèn)到的是本地的 testArray,但本地并沒(méi)有定義 testArray,所以出現(xiàn)了這個(gè)錯(cuò)誤。

          有兩種方法可用于解決這個(gè)問(wèn)題:

          1). 將函數(shù)的參數(shù)名移除(這就表示函數(shù)里要訪問(wèn)的變量已經(jīng)在函數(shù)外面定義好了,所以函數(shù)不需要參數(shù)):

          var testArray=["Test"];
          /* 前提是要在函數(shù)外面定義好 testArray */
          function testFunction(/* No params */) {
           for (var i=0; i < testArray.length; i++) {
           console.log(testArray[i]);
           }
          }
          testFunction();

          2). 在調(diào)用函數(shù)時(shí)將變量傳遞進(jìn)去:

          var testArray=["Test"];
          function testFunction(testArray) {
           for (var i=0; i < testArray.length; i++) {
           console.log(testArray[i]);
           }
          }
          testFunction(testArray);

          9. Uncaught TypeError: Cannot set property

          我們無(wú)法對(duì) undefined 變量進(jìn)行賦值或讀取操作,否則的話會(huì)拋出“Uncaught TypeError: cannot set property of undefined”異常。

          例如,在 Chrome 中:

          如果 test 對(duì)象不存在,就會(huì)拋出“Uncaught TypeError: cannot set property of undefined”異常。

          10. ReferenceError: event is not defined

          在訪問(wèn)一個(gè)未定義的對(duì)象或超出當(dāng)前作用域的對(duì)象時(shí)就會(huì)發(fā)生這個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤可以在 Chrome 開(kāi)發(fā)者控制臺(tái)重現(xiàn)。

          如果在進(jìn)行事件處理時(shí)遇到這個(gè)錯(cuò)誤,請(qǐng)確保事件對(duì)象被作為參數(shù)傳入到函數(shù)當(dāng)中。舊瀏覽器(IE)提供了全局的 event 變量,但并不是所有的瀏覽器都會(huì)這樣。盡管 jQuery 嘗試對(duì)這種行為進(jìn)行規(guī)范化,但最好還是使用傳給函數(shù)的 event 對(duì)象:

          function myFunction(event) {
           event=event.which || event.keyCode;
           if(event.keyCode===13){
           alert(event.keyCode);
           }
          }

          結(jié)論

          我們希望這些內(nèi)容能夠幫助大家在未來(lái)避免這些錯(cuò)誤,解決大家的痛點(diǎn)。不過(guò),即使有了這些最佳實(shí)踐,在生產(chǎn)環(huán)境中仍然會(huì)出現(xiàn)各種不可預(yù)期的錯(cuò)誤。關(guān)鍵是要及時(shí)發(fā)現(xiàn)那些影響用戶體驗(yàn)的錯(cuò)誤,并使用適當(dāng)?shù)墓ぞ呖焖俳鉀Q這些問(wèn)題。

          原文(英文)地址 | https://rollbar.com/blog/top-10-javascript-errors/

          質(zhì)文章,及時(shí)送達(dá)

          作者 | 嘟嘟MD

          來(lái)源 | tengj.top/2018/05/16/springboot13

          前言

          今天來(lái)一起學(xué)習(xí)一下Spring Boot中的異常處理,在日常web開(kāi)發(fā)中發(fā)生了異常,往往是需要通過(guò)一個(gè)統(tǒng)一的異常處理來(lái)保證客戶端能夠收到友好的提示。

          正文

          本篇要點(diǎn)如下:

          介紹Spring Boot默認(rèn)的異常處理機(jī)制

          如何自定義錯(cuò)誤頁(yè)面

          通過(guò)@ControllerAdvice注解來(lái)處理異常

          介紹Spring Boot默認(rèn)的異常處理機(jī)制

          默認(rèn)情況下,Spring Boot為兩種情況提供了不同的響應(yīng)方式。

          一種是瀏覽器客戶端請(qǐng)求一個(gè)不存在的頁(yè)面或服務(wù)端處理發(fā)生異常時(shí),一般情況下瀏覽器默認(rèn)發(fā)送的請(qǐng)求頭中Accept: text/html,所以Spring Boot默認(rèn)會(huì)響應(yīng)一個(gè)html文檔內(nèi)容,稱作“Whitelabel Error Page”

          另一種是使用Postman等調(diào)試工具發(fā)送請(qǐng)求一個(gè)不存在的url或服務(wù)端處理發(fā)生異常時(shí),Spring Boot會(huì)返回類似如下的Json格式字符串信息

          {
          "timestamp": "2018-05-12T06:11:45.209+0000",
          "status": 404,
          "error": "Not Found",
          "message": "No message available",
          "path": "/index.html"
          }

          原理也很簡(jiǎn)單,Spring Boot 默認(rèn)提供了程序出錯(cuò)的結(jié)果映射路徑/error。這個(gè)/error請(qǐng)求會(huì)在BasicErrorController中處理,其內(nèi)部是通過(guò)判斷請(qǐng)求頭中的Accept的內(nèi)容是否為text/html來(lái)區(qū)分請(qǐng)求是來(lái)自客戶端瀏覽器(瀏覽器通常默認(rèn)自動(dòng)發(fā)送請(qǐng)求頭內(nèi)容Accept:text/html)還是客戶端接口的調(diào)用,以此來(lái)決定返回頁(yè)面視圖還是 JSON 消息內(nèi)容。

          相關(guān)BasicErrorController中代碼如下:

          如何自定義錯(cuò)誤頁(yè)面

          好了,了解完Spring Boot默認(rèn)的錯(cuò)誤機(jī)制后,我們來(lái)點(diǎn)有意思的,瀏覽器端訪問(wèn)的話,任何錯(cuò)誤Spring Boot返回的都是一個(gè)Whitelabel Error Page的錯(cuò)誤頁(yè)面,這個(gè)很不友好,所以我們可以自定義下錯(cuò)誤頁(yè)面。

          1、先從最簡(jiǎn)單的開(kāi)始,直接在/resources/templates下面創(chuàng)建error.html就可以覆蓋默認(rèn)的Whitelabel Error Page的錯(cuò)誤頁(yè)面,我項(xiàng)目用的是thymeleaf模板,對(duì)應(yīng)的error.html代碼如下:

          <!DOCTYPE html>
          <html xmlns:th="http://www.thymeleaf.org">
          <head>
          <meta charset="UTF-8">
          <title>Title</title>
          </head>
          <body>
          動(dòng)態(tài)error錯(cuò)誤頁(yè)面
          <p th:text="${error}"></p>
          <p th:text="${status}"></p>
          <p th:text="${message}"></p>
          </body>
          </html>

          這樣運(yùn)行的時(shí)候,請(qǐng)求一個(gè)不存在的頁(yè)面或服務(wù)端處理發(fā)生異常時(shí),展示的自定義錯(cuò)誤界面如下:

          2、此外,如果你想更精細(xì)一點(diǎn),根據(jù)不同的狀態(tài)碼返回不同的視圖頁(yè)面,也就是對(duì)應(yīng)的404,500等頁(yè)面,這里分兩種,錯(cuò)誤頁(yè)面可以是靜態(tài)HTML(即,添加到任何靜態(tài)資源文件夾下),也可以使用模板構(gòu)建,文件的名稱應(yīng)該是確切的狀態(tài)碼。

          如果只是靜態(tài)HTML頁(yè)面,不帶錯(cuò)誤信息的,在resources/public/下面創(chuàng)建error目錄,在error目錄下面創(chuàng)建對(duì)應(yīng)的狀態(tài)碼html即可 ,例如,要將404映射到靜態(tài)HTML文件,您的文件夾結(jié)構(gòu)如下所示:

          靜態(tài)404.html簡(jiǎn)單頁(yè)面如下:

          <!DOCTYPE html>
          <html lang="en">
          <head>
          <meta charset="UTF-8">
          <title>Title</title>
          </head>
          <body>
          靜態(tài)404錯(cuò)誤頁(yè)面
          </body>
          </html>

          這樣訪問(wèn)一個(gè)錯(cuò)誤路徑的時(shí)候,就會(huì)顯示靜態(tài)404錯(cuò)誤頁(yè)面錯(cuò)誤頁(yè)面

          注:這時(shí)候如果存在上面第一種介紹的error.html頁(yè)面,則狀態(tài)碼錯(cuò)誤頁(yè)面將覆蓋error.html,具體狀態(tài)碼錯(cuò)誤頁(yè)面優(yōu)先級(jí)比較高。


          如果是動(dòng)態(tài)模板頁(yè)面,可以帶上錯(cuò)誤信息,在resources/templates/下面創(chuàng)建error目錄,在error目錄下面命名即可:

          這里我們模擬下500錯(cuò)誤,控制層代碼,模擬一個(gè)除0的錯(cuò)誤:

          @Controller 
          publicclassBaseErrorControllerextendsAbstractController{
          private Logger logger=LoggerFactory.getLogger(this.getClass);

          @RequestMapping(value="/ex")
          @ResponseBody
          public String error{
          int i=5/0;
          return "ex";
          }
          }

          500.html代碼:

          <!DOCTYPE html> 
          <html xmlns:th="http://www.thymeleaf.org">
          <head>
          <meta charset="UTF-8">
          <title>Title</title>
          </head>
          <body>
          動(dòng)態(tài)500錯(cuò)誤頁(yè)面
          <p th:text="${error}"></p>
          <p th:text="${status}"></p>
          <p th:text="${message}"></p>
          </body>
          </html>

          這時(shí)訪問(wèn) http://localhost:8080/spring/ex 即可看到如下錯(cuò)誤,說(shuō)明確實(shí)映射到了500.html

          注:如果同時(shí)存在靜態(tài)頁(yè)面500.html和動(dòng)態(tài)模板的500.html,則后者覆蓋前者。即templates/error/這個(gè)的優(yōu)先級(jí)比resources/public/error高。


          整體概括上面幾種情況,如下:

          • error.html會(huì)覆蓋默認(rèn)的 whitelabel Error Page 錯(cuò)誤提示

          • 靜態(tài)錯(cuò)誤頁(yè)面優(yōu)先級(jí)別比error.html高

          • 動(dòng)態(tài)模板錯(cuò)誤頁(yè)面優(yōu)先級(jí)比靜態(tài)錯(cuò)誤頁(yè)面高

          3、上面介紹的只是最簡(jiǎn)單的覆蓋錯(cuò)誤頁(yè)面的方式來(lái)自定義,如果對(duì)于某些錯(cuò)誤你可能想特殊對(duì)待,則可以這樣

          @Configuration 
          publicclassContainerConfig{
          @Bean
          public EmbeddedServletContainerCustomizer containerCustomizer{
          return new EmbeddedServletContainerCustomizer{
          @Override
          publicvoidcustomize(ConfigurableEmbeddedServletContainer container) {
          container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));
          }
          };
          }
          }

          上面這段代碼中HttpStatus.INTERNAL_SERVER_ERROR就是對(duì)應(yīng)500錯(cuò)誤碼,也就是說(shuō)程序如果發(fā)生500錯(cuò)誤,就會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到/error/500這個(gè)映射來(lái),那我們只要實(shí)現(xiàn)一個(gè)方法是對(duì)應(yīng)這個(gè)/error/500映射即可捕獲這個(gè)異常做出處理

          @RequestMapping("/error/500")
          @ResponseBody
          public String showServerError {
          return "server error";
          }

          這樣,我們?cè)僬?qǐng)求前面提到的異常請(qǐng)求 http://localhost:8080/spring/ex 的時(shí)候,就會(huì)被我們這個(gè)方法捕獲了。

          這里我們就只對(duì)500做了特殊處理,并且返還的是字符串,如果想要返回視圖,去掉 @ResponseBody注解,并返回對(duì)應(yīng)的視圖頁(yè)面。如果想要對(duì)其他狀態(tài)碼自定義映射,在customize方法中添加即可。

          上面這種方法雖然我們重寫(xiě)了/500映射,但是有一個(gè)問(wèn)題就是無(wú)法獲取錯(cuò)誤信息,想獲取錯(cuò)誤信息的話,我們可以繼承BasicErrorController或者干脆自己實(shí)現(xiàn)ErrorController接口,除了用來(lái)響應(yīng)/error這個(gè)錯(cuò)誤頁(yè)面請(qǐng)求,可以提供更多類型的錯(cuò)誤格式等(BasicErrorController在上面介紹SpringBoot默認(rèn)異常機(jī)制的時(shí)候有提到)

          這里博主選擇直接繼承BasicErrorController,然后把上面 /error/500映射方法添加進(jìn)來(lái)即可

          @Controller
          public class MyBasicErrorController extends BasicErrorController {

          public MyBasicErrorController {
          super(new DefaultErrorAttributes, new ErrorProperties);
          }

          /**
          * 定義500的ModelAndView
          * @param request
          * @param response
          * @return
          */

          @RequestMapping(produces="text/html",value="/500")
          public ModelAndView errorHtml500(HttpServletRequest request,HttpServletResponse response) {
          response.setStatus(getStatus(request).value);
          Map<String, Object> model=getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));
          model.put("msg","自定義錯(cuò)誤信息");
          return new ModelAndView("error/500", model);
          }

          /**
          * 定義500的錯(cuò)誤JSON信息
          * @param request
          * @return
          */

          @RequestMapping(value="/500")
          @ResponseBody

          public ResponseEntity<Map<String, Object>> error500(HttpServletRequest request) {
          Map<String, Object> body=getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));
          HttpStatus status=getStatus(request);
          return new ResponseEntity<Map<String, Object>>(body, status);
          }
          }

          代碼也很簡(jiǎn)單,只是實(shí)現(xiàn)了自定義的500錯(cuò)誤的映射解析,分別對(duì)瀏覽器請(qǐng)求以及json請(qǐng)求做了回應(yīng)。

          BasicErrorController默認(rèn)對(duì)應(yīng)的@RequestMapping是/error,固我們方法里面對(duì)應(yīng)的@RequestMapping(produces="text/html",value="/500")實(shí)際上完整的映射請(qǐng)求是/error/500,這就跟上面 customize 方法自定義的映射路徑對(duì)上了。

          errorHtml500 方法中,我返回的是模板頁(yè)面,對(duì)應(yīng)/templates/error/500.html,這里順便自定義了一個(gè)msg信息,在500.html也輸出這個(gè)信息<p th:text="${msg}"></p>,如果輸出結(jié)果有這個(gè)信息,則表示我們配置正確了。

          再次訪問(wèn)請(qǐng)求http://localhost:8080/spring/ex ,結(jié)果如下

          Tips:大家可以關(guān)注微信公眾號(hào):Java后端,獲取更多推送。

          通過(guò)@ControllerAdvice注解來(lái)處理異常

          Spring Boot提供的ErrorController是一種全局性的容錯(cuò)機(jī)制。此外,你還可以用@ControllerAdvice注解和@ExceptionHandler注解實(shí)現(xiàn)對(duì)指定異常的特殊處理。

          這里介紹兩種情況:

          • 局部異常處理 @Controller + @ExceptionHandler

          • 全局異常處理 @ControllerAdvice + @ExceptionHandler

          局部異常處理 @Controller + @ExceptionHandler

          局部異常主要用到的是@ExceptionHandler注解,此注解注解到類的方法上,當(dāng)此注解里定義的異常拋出時(shí),此方法會(huì)被執(zhí)行。如果@ExceptionHandler所在的類是@Controller,則此方法只作用在此類。如果@ExceptionHandler所在的類帶有@ControllerAdvice注解,則此方法會(huì)作用在全局。

          該注解用于標(biāo)注處理方法處理那些特定的異常。被該注解標(biāo)注的方法可以有以下任意順序的參數(shù)類型:

          Throwable、Exception 等異常對(duì)象;

          ServletRequest、HttpServletRequest、ServletResponse、HttpServletResponse;

          HttpSession 等會(huì)話對(duì)象;

          org.springframework.web.context.request.WebRequest;

          java.util.Locale;

          java.io.InputStream、java.io.Reader;

          java.io.OutputStream、java.io.Writer;

          org.springframework.ui.Model;

          并且被該注解標(biāo)注的方法可以有以下的返回值類型可選:

          ModelAndView;

          org.springframework.ui.Model;

          java.util.Map;

          org.springframework.web.servlet.View;

          @ResponseBody 注解標(biāo)注的任意對(duì)象;

          HttpEntity<?> or ResponseEntity<?>;

          void;

          以上羅列的不完全,更加詳細(xì)的信息可參考:Spring ExceptionHandler。

          舉個(gè)簡(jiǎn)單例子,這里我們對(duì)除0異常用@ExceptionHandler來(lái)捕捉。

          @Controller
          publicclassBaseErrorControllerextendsAbstractController{
          private Logger logger=LoggerFactory.getLogger(this.getClass);

          @RequestMapping(value="/ex")
          @ResponseBody
          public String error{
          int i=5/0;
          return "ex";
          }

          //局部異常處理
          @ExceptionHandler(Exception.class)
          @ResponseBody
          public String exHandler(Exception e){
          // 判斷發(fā)生異常的類型是除0異常則做出響應(yīng)
          if(e instanceof ArithmeticException){
          return "發(fā)生了除0異常";
          }
          // 未知的異常做出響應(yīng)
          return "發(fā)生了未知異常";
          }
          }

          全局異常處理 @ControllerAdvice + @ExceptionHandler

          在spring 3.2中,新增了@ControllerAdvice 注解,可以用于定義@ExceptionHandler、@InitBinder、@ModelAttribute,并應(yīng)用到所有@RequestMapping中。

          簡(jiǎn)單的說(shuō),進(jìn)入Controller層的錯(cuò)誤才會(huì)由@ControllerAdvice處理,攔截器拋出的錯(cuò)誤以及訪問(wèn)錯(cuò)誤地址的情況@ControllerAdvice處理不了,由SpringBoot默認(rèn)的異常處理機(jī)制處理。

          我們實(shí)際開(kāi)發(fā)中,如果是要實(shí)現(xiàn)RESTful API,那么默認(rèn)的JSON錯(cuò)誤信息就不是我們想要的,這時(shí)候就需要統(tǒng)一一下JSON格式,所以需要封裝一下。

          /**
          * 返回?cái)?shù)據(jù)
          */
          public class AjaxObject extends HashMap<String, Object> {
          private static final long serialVersionUID=1L;

          publicAjaxObject {
          put("code", 0);
          }

          public static AjaxObject error {
          return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知異常,請(qǐng)聯(lián)系管理員");
          }

          public static AjaxObject error(String msg) {
          return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
          }

          public static AjaxObject error(int code, String msg) {
          AjaxObject r=new AjaxObject;
          r.put("code", code);
          r.put("msg", msg);
          return r;
          }

          public static AjaxObject ok(String msg) {
          AjaxObject r=new AjaxObject;
          r.put("msg", msg);
          return r;
          }

          public static AjaxObject ok(Map<String, Object> map) {
          AjaxObject r=new AjaxObject;
          r.putAll(map);
          return r;
          }

          public static AjaxObject ok {
          return new AjaxObject;
          }

          public AjaxObject put(String key, Object value) {
          super.put(key, value);
          return this;
          }

          public AjaxObject data(Object value) {
          super.put("data", value);
          return this;
          }

          public static AjaxObject apiError(String msg) {
          return error(1, msg);
          }
          }

          上面這個(gè)AjaxObject就是我平時(shí)用的,如果是正確情況返回的就是:

          {
          code:0,
          msg:“獲取列表成功”,
          data:{
          queryList :
          }
          }

          正確默認(rèn)code返回0,data里面可以是集合,也可以是對(duì)象,如果是異常情況,返回的json則是:

          {
          code:500,
          msg:“未知異常,請(qǐng)聯(lián)系管理員”
          }

          然后創(chuàng)建一個(gè)自定義的異常類:

          public classBusinessExceptionextendsRuntimeExceptionimplementsSerializable{

          private static final long serialVersionUID=1L;
          private String msg;
          private int code=500;

          publicBusinessException(String msg) {
          super(msg);
          this.msg=msg;
          }

          publicBusinessException(String msg, Throwable e) {
          super(msg, e);
          this.msg=msg;
          }

          publicBusinessException(int code,String msg) {
          super(msg);
          this.msg=msg;
          this.code=code;
          }

          publicBusinessException(String msg, int code, Throwable e) {
          super(msg, e);
          this.msg=msg;
          this.code=code;
          }

          public String getMsg {
          return msg;
          }

          publicvoidsetMsg(String msg) {
          this.msg=msg;
          }

          publicintgetCode {
          return code;
          }

          publicvoidsetCode(int code) {
          this.code=code;
          }
          }

          注:spring 對(duì)于 RuntimeException 異常才會(huì)進(jìn)行事務(wù)回滾

          Controler中添加一個(gè)json映射,用來(lái)處理這個(gè)異常

          @Controller
          public class BaseErrorController{
          @RequestMapping("/json")
          public void json(ModelMap modelMap) {
          System.out.println(modelMap.get("author"));
          int i=5/0;
          }
          }

          最后創(chuàng)建這個(gè)全局異常處理類:

          /**
          * 異常處理器
          */
          @RestControllerAdvice
          publicclassBusinessExceptionHandler{
          private Logger logger=LoggerFactory.getLogger(getClass);



          /**
          * 應(yīng)用到所有@RequestMapping注解方法,在其執(zhí)行之前初始化數(shù)據(jù)綁定器
          * @param binder
          */
          @InitBinder
          publicvoidinitBinder(WebDataBinder binder) {
          System.out.println("請(qǐng)求有參數(shù)才進(jìn)來(lái)");
          }

          /**
          * 把值綁定到Model中,使全局@RequestMapping可以獲取到該值
          * @param model
          */
          @ModelAttribute
          publicvoidaddAttributes(Model model) {
          model.addAttribute("author", "嘟嘟MD");
          }

          @ExceptionHandler(Exception.class)
          public Object handleException(Exception e,HttpServletRequest req){
          AjaxObject r=new AjaxObject;
          //業(yè)務(wù)異常
          if(e instanceof BusinessException){
          r.put("code", ((BusinessException) e).getCode);
          r.put("msg", ((BusinessException) e).getMsg);
          }else{//系統(tǒng)異常
          r.put("code","500");
          r.put("msg","未知異常,請(qǐng)聯(lián)系管理員");
          }

          //使用HttpServletRequest中的header檢測(cè)請(qǐng)求是否為ajax, 如果是ajax則返回json, 如果為非ajax則返回view(即ModelAndView)
          String contentTypeHeader=req.getHeader("Content-Type");
          String acceptHeader=req.getHeader("Accept");
          String xRequestedWith=req.getHeader("X-Requested-With");
          if ((contentTypeHeader !=&& contentTypeHeader.contains("application/json"))
          || (acceptHeader !=&& acceptHeader.contains("application/json"))
          || "XMLHttpRequest".equalsIgnoreCase(xRequestedWith)) {
          return r;
          } else {
          ModelAndView modelAndView=new ModelAndView;
          modelAndView.addObject("msg", e.getMessage);
          modelAndView.addObject("url", req.getRequestURL);
          modelAndView.addObject("stackTrace", e.getStackTrace);
          modelAndView.setViewName("error");
          return modelAndView;
          }
          }
          }

          @ExceptionHandler 攔截了異常,我們可以通過(guò)該注解實(shí)現(xiàn)自定義異常處理。其中,@ExceptionHandler 配置的 value 指定需要攔截的異常類型,上面我配置了攔截Exception,

          再根據(jù)不同異常類型返回不同的相應(yīng),最后添加判斷,如果是Ajax請(qǐng)求,則返回json,如果是非ajax則返回view,這里是返回到error.html頁(yè)面。

          為了展示錯(cuò)誤的時(shí)候更友好,我封裝了下error.html,不僅展示了錯(cuò)誤,還添加了跳轉(zhuǎn)百度谷歌以及StackOverFlow的按鈕,如下:

          <!DOCTYPE HTML>
          <html xmlns:th="http://www.thymeleaf.org" layout:decorator="layout">
          <head>
          <title>Spring Boot管理后臺(tái)</title>
          <script type="text/javascript">
          </script>
          </head>
          <body>
          <div layout:fragment="content" th:remove="tag">
          <div id="navbar">
          <h1>系統(tǒng)異常統(tǒng)一處理</h1>
          <h3 th:text="'錯(cuò)誤信息:'+${msg}"></h3>
          <h3 th:text="'請(qǐng)求地址:'+${url}"></h3>

          <h2>Debug</h2>
          <a th:href="@{'https://www.google.com/webhp?hl=zh-CN#safe=strict&hl=zh-CN&q='+${msg}}"
          class="btn btn-primary btn-lg" target="_blank" id="Google">Google</a>
          <a th:href="@{'https://www.baidu.com/s?wd='+${msg}}" class="btn btn-info btn-lg" target="_blank" id="Baidu">Baidu</a>
          <a th:href="@{'http://stackoverflow.com/search?q='+${msg}}"
          class="btn btn-default btn-lg" target="_blank" id="StackOverFlow">StackOverFlow</a>
          <h2>異常堆棧跟蹤日志StackTrace</h2>
          <div th:each="line:${stackTrace}">
          <div th:text="${line}"></div>
          </div>
          </div>
          </div>
          <div layout:fragment="js" th:remove="tag">
          </div>
          </body>
          </html>

          訪問(wèn)http://localhost:8080/json的時(shí)候,因?yàn)槭菫g覽器發(fā)起的,返回的是error界面:

          如果是ajax請(qǐng)求,返回的就是錯(cuò)誤:

          { "msg":"未知異常,請(qǐng)聯(lián)系管理員", "code":500 }

          這里我給帶@ModelAttribute注解的方法通過(guò)Model設(shè)置了author值,在json映射方法中通過(guò) ModelMwap 獲取到改值。

          認(rèn)真的你可能發(fā)現(xiàn),全局異常類我用的是@RestControllerAdvice,而不是@ControllerAdvice,因?yàn)檫@里返回的主要是json格式,這樣可以少寫(xiě)一個(gè)@ResponseBody。

          總結(jié)

          到此,SpringBoot中對(duì)異常的使用也差不多全了,本項(xiàng)目中處理異常的順序會(huì)是這樣,當(dāng)發(fā)送一個(gè)請(qǐng)求:

          1.攔截器那邊先判斷是否登錄,沒(méi)有則返回登錄頁(yè)。

          2.在進(jìn)入Controller之前,譬如請(qǐng)求一個(gè)不存在的地址,返回404錯(cuò)誤界面。

          3.在執(zhí)行@RequestMapping時(shí),發(fā)現(xiàn)的各種錯(cuò)誤(譬如數(shù)據(jù)庫(kù)報(bào)錯(cuò)、請(qǐng)求參數(shù)格式錯(cuò)誤/缺失/值非法等)統(tǒng)一由@ControllerAdvice處理,根據(jù)是否Ajax返回json或者view。

          -END-


          主站蜘蛛池模板: 日本在线视频一区二区| 国产午夜精品一区二区三区嫩草 | 国产色欲AV一区二区三区| 欧洲精品码一区二区三区免费看| 亚洲AV日韩精品一区二区三区| 久久伊人精品一区二区三区| 国模无码一区二区三区不卡| 一区视频免费观看| 国产一区二区三区美女| 欧美日本精品一区二区三区 | 在线|一区二区三区| 无码日韩精品一区二区免费| 中文字幕一区二区三匹| 亚洲一区二区三区免费在线观看 | 久久人妻无码一区二区| 日韩免费一区二区三区在线| 亚洲一区二区三区丝袜| 极品少妇一区二区三区四区| 亚洲精品色播一区二区| 午夜一区二区在线观看| 国产a久久精品一区二区三区| 国语对白一区二区三区| 视频一区视频二区在线观看| 日韩一区二区三区在线观看 | 亚洲一区免费视频| 亚洲一区二区影院| 精品欧洲av无码一区二区| 五十路熟女人妻一区二区| 一区二区亚洲精品精华液| 日韩精品人妻一区二区三区四区| 色窝窝免费一区二区三区| 国产视频一区二区在线观看| 无码少妇一区二区浪潮av| 国产亚洲自拍一区| 日本午夜精品一区二区三区电影 | 亚洲色无码一区二区三区| 久久99国产精一区二区三区| 精品人妻AV一区二区三区| 日韩视频一区二区在线观看| 香蕉在线精品一区二区| 99精品一区二区三区|