1、異常處理javascript
2.一、數據驗證html
如今假設說要進行表單信息提交,確定須要有一個表單,然後這個表單要將數據提交到 VO 類中,因此如今的基本實現以下:前端
一、 創建一個 Member.java 的 VO 類:java
package cn.study.microboot.vo; import java.io.Serializable; import java.util.Date; @SuppressWarnings("serial") public class Member implements Serializable { private String mid ; private Integer age ; private Double salary ; private Date birthday ; public String getMid() { return mid; } public void setMid(String mid) { this.mid = mid; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Member [mid=" + mid + ", age=" + age + ", salary=" + salary + ", birthday=" + birthday + "]"; } }
二、 因爲此時的程序之中須要進行日期的轉換處理操做,那麼就須要爲其作一個轉換處理的格式配置,修改 AbstractBaseController 類,追加以下的轉換操做方法綁定:nginx
@InitBinder public void initBinder(WebDataBinder binder) { // 在本程序裏面須要針對於日期格式進行處理 // 首先創建一個能夠將字符串轉換爲日期的工具程序類 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ; // 明確的描述此時須要註冊一個日期格式的轉化處理程序類 binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(sdf, true)); }
三、 創建一個 MemberController 程序類,負責實現 Member 的控制層處理操做。git
package cn.study.microboot.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import cn.study.microboot.util.controller.AbstractBaseController; import cn.study.microboot.vo.Member; @Controller public class MemberController extends AbstractBaseController { @RequestMapping(value = "/addPre", method = RequestMethod.GET) public String addPre() { // 增長前的準備操做路徑 return "member_add" ; } @RequestMapping(value = "/add", method = RequestMethod.POST) @ResponseBody public Object add(Member vo) { // 增長前的準備操做路徑 return vo ; } }
四、 編寫一個頁面進行用戶的表單填寫(在 src/main/view/templates 下創建):member_add.html;web
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>SpringBoot模版渲染</title> <link rel="icon" type="image/x-icon" href="/images/study.ico"/> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> </head> <body> <form action="add" method="post"> 用戶郵箱:<input type="text" name="mid" value="studyjava@163.com"/><br/> 用戶年齡:<input type="text" name="age" value="18"/><br/> 用戶工資:<input type="text" name="salary" value="1000"/><br/> 用戶生日:<input type="text" name="birthday" value="2010-10-10"/><br/> <input type="submit" value="提交"/> <input type="reset" value="重置"/> </form> </body> </html>
五、 此時的代碼只是一個最爲普通的處理操做,可是這個時候對於該程序也是存在有以下問題的:ajax
· 若是某些數據沒有輸入,則內容是 null,若是要進行嚴格控制,這些 null 不該該存在;spring
· 某些數據須要進行格式驗證,例如:用戶名應該是郵箱,這個的信息應該進行郵箱驗證;sql
因此如今若是要想進行這些的驗證,SpringBoot 裏面有默認的支持,只不過這種支持未必是最好的,在 SpringBoot 裏面爲了 方便用戶編寫驗證專門提供有一個 hibernate-validation.jar 工具包,這個工具包是由 hibernate 開發框架提供的。
六、 若是要想進行驗證,那麼首先要解決的問題就必須是錯誤的提示信息問題,而在 SpringBoot 裏面對於錯誤信息的保存,都要 求其保存在 ValidationMessages.properties 文件,在「src/main/resources」目錄中創建此文件;
member.mid.notnull.error=用戶名不容許爲空! member.mid.email.error=用戶名的註冊必須輸入正確的郵箱! member.mid.length.error=用戶名的格式錯誤! member.age.notnull.error=年齡不容許爲空 member.age.digits.error=年齡必須是合法數字! member.salary.notnull.error=工資不容許爲空! member.salary.digits.error=工資必須是合法數字! member.birthday.notnull.error=生日不容許爲空!
提示:你一個表單就須要編寫這麼多的配置項,那麼若是要有幾百個表單呢?這樣的配置項太可怕了,因此最好的數據檢測仍是利 用攔截器處理最合適。
七、 修改 Member.java 程序類追加驗證的處理方式:
package cn.study.microboot.vo; import java.io.Serializable; import java.util.Date; import javax.validation.constraints.Digits; import javax.validation.constraints.NotNull; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; @SuppressWarnings("serial") public class Member implements Serializable { @NotNull(message="{member.mid.notnull.error}") @Email(message="{member.mid.email.error}") @Length(min=6,message="{member.mid.length.error}") private String mid ; @NotNull(message="{member.age.notnull.error}") @Digits(integer=3,fraction=0,message="{member.age.digits.error}") private Integer age ; @NotNull(message="{member.salary.notnull.error}") @Digits(integer=20,fraction=2,message="{member.salary.digits.error}") private Double salary ; @NotNull(message="{member.birthday.notnull.error}") private Date birthday ; public String getMid() { return mid; } public void setMid(String mid) { this.mid = mid; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Member [mid=" + mid + ", age=" + age + ", salary=" + salary + ", birthday=" + birthday + "]"; } }
八、 修改 MemberController 類中的 add()方法來觀察錯誤信息的顯示:
package cn.study.microboot.controller; import java.util.Iterator; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import cn.study.microboot.util.controller.AbstractBaseController; import cn.study.microboot.vo.Member; @Controller public class MemberController extends AbstractBaseController { @RequestMapping(value = "/add", method = RequestMethod.POST) @ResponseBody public Object add(@Valid Member vo, BindingResult result) { // 增長前的準備操做路徑 if (result.hasErrors()) { // 如今表示執行的驗證出現錯誤 Iterator<ObjectError> iterator = result.getAllErrors().iterator(); // 獲取所有錯誤信息 while (iterator.hasNext()) { ObjectError error = iterator.next() ; // 取出每個錯誤 System.out.println("【錯誤信息】code = " + error.getCode() + ",message = " + error.getDefaultMessage()); } return result.getAllErrors() ; } else { return vo; } } @RequestMapping(value = "/addPre", method = RequestMethod.GET) public String addPre() { // 增長前的準備操做路徑 return "member_add"; } }
對於此類的驗證你們理解便可,不須要將其做爲重點,可是須要清楚,默認狀況下 SpringBoot 提供的數據驗證須要經過註解 以及一系列的資源文件進行定義後纔可使用,並且全部的錯誤都必須用戶本身來處理,這一點的設計不如直接編寫具體的反射攔 截器方便。
2.二、處理錯誤頁
錯誤頁絕對是全部的 WEB 項目之中必須具備的一項信息顯示處理,可是在傳統的 WEB 項目開發過程之中,錯誤頁都是在 web.xml 文件之中進行配置的,不過遺憾的是 SpringBoot 之中並不存在有 web.xml 配置文件這一項,那麼若是要想進行錯誤頁的處 理,最好的作法是須要根據每個錯誤代碼建立一個屬於本身的錯誤顯示頁。
一、 全部的錯誤頁都是普通的靜態文件,那麼建議在「src/main/view/static」目錄下建立幾個常見的錯誤頁(常見的錯誤的 HTTP 返回編碼:40四、500、400)
二、 添加一個錯誤頁的配置類,在啓動類中編寫一個錯誤頁的配置項;
package cn.study.microboot.config; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.web.servlet.ErrorPage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @Configuration public class ErrorPageConfig { @Bean public EmbeddedServletContainerCustomizer containerCustomizer() { return new EmbeddedServletContainerCustomizer() { @Override public void customize( ConfigurableEmbeddedServletContainer container) { ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST, "/error-400.html"); ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-404.html"); ErrorPage errorPage500 = new ErrorPage( HttpStatus.INTERNAL_SERVER_ERROR, "/error-500.html"); container.addErrorPages(errorPage400, errorPage404, errorPage500); } }; } }
那麼此時只要出現了錯誤,就會找到相應的 http 狀態碼,然後跳轉到指定的錯誤路徑上進行顯示。
2.三、全局異常
下面首先來觀察一個程序代碼,例如:如今創建一個控制器,然後這個控制器本身拋出一個異常。
@RequestMapping(value="/get") @ResponseBody public String get() { System.out.println("除法計算:" + (10 / 0)); return "hello world" ; }
若是此時配置有錯誤頁,那麼這個時候錯誤會統一跳轉到 500 所在的路徑上進行錯誤的顯示,可是若是說如今但願可以顯示 出錯誤更加詳細的內容呢?
因此這個時候能夠單獨定義一個頁面進行錯誤的信息顯示處理,而這個頁面,能夠定義在「src/main/view/templates/error.html」, 這個頁面裏面要求能夠輸出一些信息;
一、 定義一個全局的異常處理類:
import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice // 做爲一個控制層的切面處理 public class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; // 定義錯誤顯示頁,error.html @ExceptionHandler(Exception.class) // 全部的異常都是Exception子類 public ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) { // 出現異常以後會跳轉到此方法 ModelAndView mav = new ModelAndView(DEFAULT_ERROR_VIEW); // 設置跳轉路徑 mav.addObject("exception", e); // 將異常對象傳遞過去 mav.addObject("url", request.getRequestURL()); // 得到請求的路徑 return mav; } }
二、 定義 error.html 頁面:
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>SpringBoot模版渲染</title> <link rel="icon" type="image/x-icon" href="/images/study.ico"/> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> </head> <body> <p th:text="${url}"/> <p th:text="${exception.message}"/> </body> </html>
對於全局異常信息顯示除了採用以上的跳轉處理方式以外,也能夠作的簡單一些,使用 Rest 進行顯示。
範例:修改全局異常處理類
package cn.study.microboot.advice; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; //@ControllerAdvice// 做爲一個控制層的切面處理 @RestControllerAdvice public class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; // 定義錯誤顯示頁,error.html @ExceptionHandler(Exception.class) // 全部的異常都是Exception子類 public Object defaultErrorHandler(HttpServletRequest request,Exception e) { class ErrorInfo { private Integer code ; private String message ; private String url ; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } ErrorInfo info = new ErrorInfo() ; info.setCode(100); // 標記一個錯誤信息類型 info.setMessage(e.getMessage()); info.setUrl(request.getRequestURL().toString()); return info ; } // public ModelAndView defaultErrorHandler(HttpServletRequest request, // Exception e) { // 出現異常以後會跳轉到此方法 // ModelAndView mav = new ModelAndView(DEFAULT_ERROR_VIEW); // 設置跳轉路徑 // mav.addObject("exception", e); // 將異常對象傳遞過去 // mav.addObject("url", request.getRequestURL()); // 得到請求的路徑 // return mav; // } }
2、服務配置
Spring Boot 其默認是集成web容器的,啓動方式由像普通Java程序同樣,main函數入口啓動。其內置Tomcat容器或Jetty容器,具體由配置來決定(默認Tomcat)。固然你也能夠將項目打包成war包,放到獨立的web容器中(Tomcat、weblogic等等),固然在此以前你要對程序入口作簡單調整。
1、內嵌 Server 配置
Spring Boot將容器內置後,它經過配置文件的方式類修改相關server配置。
其中經常使用的配置只有少數幾個,已經用紫色標記起來。紅框圈起來的部分,看名稱分類就能夠明白其做用。
對server的幾個經常使用的配置作個簡單說明:
# 項目contextPath,通常在正式發佈版本中,咱們不配置 server.context-path=/myspringboot
# 錯誤頁,指定發生錯誤時,跳轉的URL。請查看BasicErrorController源碼便知
server.error.path=/error
# 服務端口
server.port=9090
# session最大超時時間(分鐘),默認爲30
server.session-timeout=60
# 該服務綁定IP地址,啓動服務器時如本機不是該IP地址則拋出異常啓動失敗,只有特殊需求的狀況下才配置 # server.address=192.168.16.11
Tomcat
Tomcat爲Spring Boot的默認容器,下面是幾個經常使用配置:
# tomcat最大線程數,默認爲200
server.tomcat.max-threads=800
# tomcat的URI編碼
server.tomcat.uri-encoding=UTF-8
# 存放Tomcat的日誌、Dump等文件的臨時文件夾,默認爲系統的tmp文件夾(如:C:\Users\Shanhy\AppData\Local\Temp)
server.tomcat.basedir=H:/springboot-tomcat-tmp
# 打開Tomcat的Access日誌,並能夠設置日誌格式的方法:
#server.tomcat.access-log-enabled=true
#server.tomcat.access-log-pattern=
# accesslog目錄,默認在basedir/logs
#server.tomcat.accesslog.directory=
# 日誌文件目錄
logging.path=H:/springboot-tomcat-tmp
# 日誌文件名稱,默認爲spring.log logging.file=myapp.log
Jetty
若是你要選擇Jetty,也很是簡單,就是把pom中的tomcat依賴排除,並加入Jetty容器的依賴,以下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependencies>
打包
打包方法:
CMD進入項目目錄,使用 mvn clean package 命令打包,以個人項目工程爲例:
D:\spring-boot-sample>mvn clean package
能夠追加參數 -Dmaven.test.skip=true 跳過測試。
打包後的文件存放於項目下的target目錄中,如:spring-boot-sample-0.0.1-SNAPSHOT.jar
若是pom配置的是war包,則爲spring-boot-sample-0.0.1-SNAPSHOT.war
2、部署到JavaEE容器
- 修改啓動類,繼承 SpringBootServletInitializer 並重寫 configure 方法
public class SpringBootSampleApplication extends SpringBootServletInitializer{
private static final Logger logger = LoggerFactory.getLogger(SpringBootSampleApplication.class);
@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(this.getClass());
}
}
- 修改pom文件中jar 爲 war
<!-- <packaging>jar</packaging> --> <packaging>war</packaging>
- 修改pom,排除tomcat插件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
- 打包部署到容器
使用命令 mvn clean package 打包後,同通常J2EE項目同樣部署到web容器。
3、使用Profile區分環境
spring boot 能夠在 「配置文件」、「Java代碼類」、「日誌配置」 中來配置profile區分不一樣環境執行不一樣的結果
一、配置文件
使用配置文件application.yml 和 application.properties 有所區別
以application.properties 爲例,經過文件名來區分環境 application-{profile}.properties
application.properties
app.name=MyApp server.port=8080 spring.profiles.active=dev
application-dev.properties
server.port=8081
application-stg.properties
server.port=8082
在啓動程序的時候經過添加 –spring.profiles.active={profile} 來指定具體使用的配置
例如咱們執行 java -jar demo.jar –spring.profiles.active=dev 那麼上面3個文件中的內容將被如何應用?
Spring Boot 會先加載默認的配置文件,而後使用具體指定的profile中的配置去覆蓋默認配置。
app.name 只存在於默認配置文件 application.properties 中,由於指定環境中不存在一樣的配置,因此該值不會被覆蓋
server.port 默認爲8080,可是咱們指定了環境後,將會被覆蓋。若是指定stg環境,server.port 則爲 8082
spring.profiles.active 默認指定dev環境,若是咱們在運行時指定 –spring.profiles.active=stg 那麼將應用stg環境,最終 server.port 的值爲8082
二、Java類中@Profile註解
下面2個不一樣的類實現了同一個接口,@Profile註解指定了具體環境
// 接口定義
public interface SendMessage {
// 發送短信方法定義
public void send();
}
// Dev 環境實現類
@Component
@Profile("dev")
public class DevSendMessage implements SendMessage {
@Override
public void send() {
System.out.println(">>>>>>>>Dev Send()<<<<<<<<");
}
}
// Stg環境實現類
@Component
@Profile("stg")
public class StgSendMessage implements SendMessage { @Override
public void send() {
System.out.println(">>>>>>>>Stg Send()<<<<<<<<");
}
}
// 啓動類
@SpringBootApplication
public class ProfiledemoApplication {
@Value("${app.name}")
private String name;
@Autowired
private SendMessage sendMessage;
@PostConstruct
public void init(){
sendMessage.send();// 會根據profile指定的環境實例化對應的類
}
}
三、logback-spring.xml也支持有節點來支持區分
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<logger name="org.springframework.web" level="INFO"/>
<springProfile name="default">
<logger name="org.springboot.sample" level="TRACE" />
</springProfile>
<springProfile name="dev">
<logger name="org.springboot.sample" level="DEBUG" />
</springProfile>
<springProfile name="staging">
<logger name="org.springboot.sample" level="INFO" />
</springProfile>
</configuration>
再說一遍文件名不要用logback.xml 請使用logback-spring.xml
4、指定外部的配置文件
有些系統,關於一些數據庫或其餘第三方帳戶等信息,因爲安全問題,其配置並不會提早配置在項目中暴露給開發人員。
對於這種狀況,咱們在運行程序的時候,能夠經過參數指定一個外部配置文件。
以 demo.jar 爲例,方法以下:
java -jar demo.jar --spring.config.location=/opt/config/application.properties
其中文件名隨便定義,無固定要求。
5、建立一個Linux 應用的sh腳本
下面幾個腳本僅供參考,請根據本身須要作調整
start.sh
#!/bin/sh rm -f tpid
nohup java -jar myapp.jar --spring.config.location=application.yml > /dev/null 2>&1 & echo $! > tpid
echo Start Success!
stop.sh
#!/bin/sh APP_NAME=myapp tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
echo 'Stop Process...'
kill -15 $tpid
fi
sleep 5
tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
echo 'Kill Process!'
kill -9 $tpid
else
echo 'Stop Success!'
fi
check.sh
#!/bin/sh APP_NAME=myapp tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
echo 'App is running.'
else
echo 'App is NOT running.'
fi
kill.sh
#!/bin/sh APP_NAME=myapp tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
echo 'Kill Process!'
kill -9 $tpid
fi
6、使用Linux服務的方式啓動、中止、重啓
一、首先在 pom.xml 中配置插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration> </plugin>
</plugins>
</build>
特別注意一下 <executable>true</executable>
二、而後正常使用 mvn clean package -Dmaven.test.skip=true
將工程打成jar包
三、上傳jar包到服務器,假設部署路徑爲 /var/apps/myapp.jar
,使用命令作一個軟鏈接到 /etc/init.d
目錄,命令:
ln -s /var/apps/myapp.jar /etc/init.d/myapp
其中 /etc/init.d/myapp
最後的 myapp 能夠是別的名字,這個就是服務名,咱們後面使用 service [服務名] start
來啓動(下面有說明)。
四、給jar文件授予可執行權限,命令:
chmod +x myapp.jar
五、接下來,就可使用咱們熟悉的 service myapp start|stop|restart|status
來對應用進行啓停了。
執行命令後將獲得形如 Started|Stopped [PID] 的結果反饋。
默認PID文件路徑:/var/run/appname/appname.pid
默認服務日誌文件路徑:/var/log/appname.log(能夠經過下面.conf 的方式修改LOG_FOLDER
)
六、使用自定義的.conf文件來變動默認配置,方法以下:
在jar包相同路徑下建立一個.conf文件,名稱應該與.jar的名稱相同,如myapp.conf(若是咱們打包的文jar文件爲 myapp-1.0.0.jar 那麼這裏的conf文件也應該是 myapp-1.0.0.conf),其內容配置能夠以下:
JAVA_HOME=/usr/local/jdk
JAVA_OPTS=-Xmx1024M
LOG_FOLDER=/data/logs/myapp
注:LOG_FOLDER 對應的文件夾目錄要必須存在,若是目錄不存在,服務並不會自從建立目錄。
若是你是CentOS 7或紅帽7以上,你還能夠用下面的方法處理,爲何要用這樣的方法(請自行研究),這裏直接提供結果,哈哈
編輯服務文件 vim /usr/lib/systemd/system/myapp.service
[Unit]
Description=myapp
After=network.target
[Service]
WorkingDirectory=/var/apps/myapp
ExecStart=/usr/local/java/bin/java -Dsun.misc.URLClassPath.disableJarChecking=true -jar /var/apps/myapp.jar
ExecStop=kill $MAINPID
Restart=always
[Install]
WantedBy=multi-user.target
使用Linux 7 之後服務新的啓動方式,相關命令
啓動
systemctl start myapp 中止 systemctl stop myapp 重啓 systemctl restart myapp 查看日誌 journalctl -u myapp 關於更多 systemctl 命令的使用方法,度娘有不少。
3、aop
一、添加pom依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
它依賴於org.aspectj.aspectjweaver,而經常使用的aop註解都在這個包下org.aspectj.lang.annotation,像 @Before、@After、@AfterReturning、@AfterThrowing、@Around、@Pointcut等。
通知方法:
前置通知(@Before): 在目標方法運行以前執行。
後置通知(@After): 在目標方法運行結束以後執行,無論正常結束仍是異常結束,都會執行。
返回通知(@AfterReturn): 在目標方法正常放回以後執行。
異常通知(@AfterThrowing): 在目標方法出現異常之後執行。
環繞通知(@Around): 動態代理,手動推動目標方法的執行。
二、Aop例子
@Aspect
public class LogAspects {
// 抽取公共的切入點表達式
@Pointcut("execution(* org.com.cay.spring.annotation.aop.*.*(..))")
public void logging() {
}
@Before(value = "logging()")
public void logStart(JoinPoint joinPoint) {
System.out
.println(joinPoint.getSignature().getName() + "運行,參數列表是: {" + Arrays.asList(joinPoint.getArgs()) + "}");
}
@After(value = "logging()")
public void logEnd(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + "結束...");
}
@AfterReturning(value = "logging()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println(joinPoint.getSignature().getName() + "正常結束,結果是: {" + result + "}");
}
@AfterThrowing(value = "logging()", throwing = "e")
public void logException(JoinPoint joinPoint, Exception e) {
System.out.println(joinPoint.getSignature().getName() + "異常,異常信息: {" + e.getMessage() + "}");
}
}
4、日誌
本節將經過配置Spring Boot的默認日誌logback來實現日誌管理。
說明
日誌級別從低到高分:
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
配置
在src/main/resources下面添加logback.xml,內容以下:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!--======================================= 本地變量 ======================================== --> <!--在沒有定義${LOG_HOME}系統變量的時候,能夠設置此本地變量。提交測試、上線時,要將其註釋掉,使用系統變量。 --> <!-- <property name="LOG_HOME" value="D:/data/logs" /> --> <!-- 應用名稱:和統一配置中的項目代碼保持一致(小寫) --> <property name="APP_NAME" value="base" /> <!--日誌文件保留天數 --> <property name="LOG_MAX_HISTORY" value="180" /> <!--定義日誌文件的存儲地址 勿在 LogBack 的配置中使用相對路徑 --> <!--應用日誌文件保存路徑 --> <property name="LOG_APP_HOME" value="${APP_NAME}/app" /> <!--=========================== 按照天天生成日誌文件:默認配置=================================== --> <!-- 控制檯輸出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 按照天天生成日誌文件:主項目日誌 --> <appender name="APP" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--日誌文件輸出的文件名 --> <FileNamePattern>${LOG_APP_HOME}/base.%d{yyyy-MM-dd}.log </FileNamePattern> <!--日誌文件保留天數 --> <MaxHistory>${LOG_MAX_HISTORY}</MaxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{500} - %msg%n</pattern> </encoder> </appender> <!--=============================== 日誌輸出: 默認主業務日誌 ====================================== --> <logger name="org.springframework"> <level value="WARN" /> </logger> <logger name="org.apache.shiro"> <level value="WARN" /> </logger> <logger name="freemarker"> <level value="WARN" /> </logger> <logger name="org.hibernate"> <level value="WARN" /> </logger> <logger name="org.hibernate.SQL"> <level value="DEBUG" /> </logger> <root level="DEBUG"> <appender-ref ref="APP" /> <appender-ref ref="STDOUT" /> </root> </configuration>
配置application.properties
logging.config=classpath:logback.xml
5、自定義事件處理
統一返回結果集
不要使用 Map 來返回結果, Map 不易控制且容易犯錯, 應該定義一個 Java 實體類. 來表示統一結果來返回, 如定義實體類:
public class ResultBean<T> { private int code; private String message; private Collection<T> data; private ResultBean() { } public static ResultBean error(int code, String message) { ResultBean resultBean = new ResultBean(); resultBean.setCode(code); resultBean.setMessage(message); return resultBean; } public static ResultBean success() { ResultBean resultBean = new ResultBean(); resultBean.setCode(0); resultBean.setMessage("success"); return resultBean; } public static <V> ResultBean<V> success(Collection<V> data) { ResultBean resultBean = new ResultBean(); resultBean.setCode(0); resultBean.setMessage("success"); resultBean.setData(data); return resultBean; } // getter / setter 略 }
- 正常狀況: 調用
ResultBean.success()
或ResultBean.success(Collection<V> data)
, 不須要返回數據, 即調用前者, 須要返回數據, 調用後者. 如:
@RequestMapping("/goods/add") @ResponseBody public ResultBean<Goods> getAllGoods() { List<Goods> goods = goodsService.findAll(); return ResultBean.success(goods); } @RequestMapping("/goods/update") @ResponseBody public ResultBean updateGoods(Goods goods) { goodsService.update(goods); return ResultBean.success(); }
通常只有查詢方法須要調用 ResultBean.success(Collection<V> data)
來返回 N 條數據, 其餘諸如刪除, 修改等方法都應該調用 ResultBean.success()
, 即在業務代碼中只處理正確的功能, 不對異常作任何判斷. 也不須要對 update 或 delete 的更新條數作判斷(我的建議, 實際須要根據業務). 只要沒有拋出異常, 咱們就認爲用戶操做成功了. 且操做成功的提示信息在前端處理, 不要後臺返回 「操做成功」 等字段.
前臺接受到的信息爲:
{
"code": 0, "message": "success", "data": [ { "name": "商品1", "price": 50.00, }, { "name": "商品2", "price": 99.99, } ] }
- 拋出異常: 拋出異常後, 咱們應該調用
ResultBean.error(int code, String message)
, 來將狀態碼和錯誤信息返回, 咱們約定code
爲 0 表示操做成功,1
或2
等正數表示用戶輸入錯誤,-1
,-2
等負數表示系統錯誤.
前臺接受到的信息爲:
{
"code": -1, "message": "XXX 參數有問題, 請從新填寫", "data": null }
前端統一處理:
返回的結果集規範後, 前端就很好處理了:
/** * 顯示錯誤信息 * @param result: 錯誤信息 */ function showError(s) { alert(s); } /** * 處理 ajax 請求結果 * @param result: ajax 返回的結果 * @param fn: 成功的處理函數 ( 傳入data: fn(result.data) ) */ function handlerResult(result, fn) { // 成功執行操做,失敗提示緣由 if (result.code == 0) { fn(result.data); } // 用戶操做異常, 這裏能夠對 1 或 2 等錯誤碼進行單獨處理, 也能夠 result.code > 0 來粗粒度的處理, 根據業務而定. else if (result.code == 1) { showError(result.message); } // 系統異常, 這裏能夠對 -1 或 -2 等錯誤碼進行單獨處理, 也能夠 result.code > 0 來粗粒度的處理, 根據業務而定. else if (result.code == -1) { showError(result.message); } // 若是進行細粒度的狀態碼判斷, 那麼就應該重點注意這裏沒出現過的狀態碼. 這個判斷僅建議在開發階段保留用來發現未定義的狀態碼. else { showError("出現未定義的狀態碼:" + result.code); } } /** * 根據 id 刪除商品 */ function deleteGoods(id) { $.ajax({ type: "GET", url: "/goods/delete", dataType: "json", success: function(result){ handlerResult(result, deleteDone); } }); } function deleteDone(data) { alert("刪除成功"); }
showError
和 handlerResult
是公共方法, 分別用來顯示錯誤和統一處理結果集.
而後將主要精力放在發送請求和處理正確結果的方法上便可, 如這裏的 deleteDone 函數, 用來處理操做成功給用戶的提示信息, 正所謂各司其職, 前端負責操做成功的消息提示更合理, 而錯誤信息只有後臺知道, 因此須要後臺來返回.
後端統一處理異常
說了這麼多, 還沒講到後端不在業務層捕獲任何異常的事, 既然全部業務層都沒有捕獲異常, 那麼全部的異常都會拋出到 Controller 層, 咱們只須要用 AOP 對 Controller 層的全部方法處理便可.
好在 Spring 爲咱們提供了一個註解, 用來統一處理異常:
@ControllerAdvice @ResponseBody public class WebExceptionHandler { private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class); @ExceptionHandler public ResultBean unknownAccount(UnknownAccountException e) { log.error("帳號不存在", e); return ResultBean.error(1, "帳號不存在"); } @ExceptionHandler public ResultBean incorrectCredentials(IncorrectCredentialsException e) { log.error("密碼錯誤", e); return ResultBean.error(-2, "密碼錯誤"); } @ExceptionHandler public ResultBean unknownException(Exception e) { log.error("發生了未知異常", e); // 發送郵件通知技術人員. return ResultBean.error(-99, "系統出現錯誤, 請聯繫網站管理員!"); } }
在這裏統一配置須要處理的異常, 一樣, 對於未知的異常, 必定要及時發現, 並進行處理. 推薦出現未知異常後發送郵件, 提示技術人員.
總結
總結一下統一異常處理的方法:
- 不使用隨意返回各類數據類型, 要統一返回值規範.
- 不在業務代碼中捕獲任何異常, 所有交由
@ControllerAdvice
來處理.