本文是作者原創,版權歸作者所有.若要轉載,請注明出處.
本文都是springboot的常用和實用功能,話不多說開始吧
定時任務
1.啟動類開啟注解
@EnableScheduling //開啟基於注解的定時任務 @MapperScan("com.pdzx.dao") @SpringBootApplication public class VideoApplication { public static void main(String[] args) { SpringApplication.run(VideoApplication.class); } }
2.開啟定時任務
@Component public class ScheduledService { //每2秒 執行任務 @Scheduled(cron = "0/2 * * * * ?") public void hello(){ System.out.println("hello....."); } }
看結果
注意cron表達式的用法.
6個參數,分別為:秒 分 時 日 月 周
通配符說明:
*:表示匹配該域的任意值。在minutes域使用 * 表示每分鍾。在months里表示每個月。在daysOfWeek域表示一周的每一天。
?:只能用在daysofMonth和daysofWeek兩個域,表示不指定值,當兩個子表達式其中之一被指定了值以后,為了避免沖突,需要將另一個子表達式的值設為 ?。因為daysofMonth和daysofWeek會相互影響。例如想在每月的2號觸發調度,不管2號是周幾,則只能使用如下寫法:0 0 0 2 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管周幾都會觸發。
-:表示范圍。例如在minutes域使用5-20,表示從5分到20分鍾每分鍾觸發一次
/:表示起始時間開始觸發,然后每隔固定時間觸發一次。例如在minutes域使用5/20,則意味着從當前小時的第5分鍾開每20分鍾觸發一次。
,:表示列出枚舉值。例如:在minutes域使用5,20,則意味着在5分和20分時各觸發一次。
L:表示最后,是單詞“last”的縮寫,只能出現在daysofWeek和dayofMonth域。在daysofWeek域使用5L意思是在指定月的最后的一個星期四觸發。在dayofMonth域使用5L或者FRIL意思是在指定月的倒數第5天觸發。在使用L參數時,不要指定列表或范圍。
W:表示有效工作日(周一到周五),只能出現在daysofMonth域,系統將在離指定日期的最近的有效工作日觸發事件。例如:在daysofMonth使用5W,如果5號是周六,則將在最近的工作日周五,即4號觸發。如果5號是周日,則在6日(周一)觸發。如果5日在星期一到星期五中的一天,則就在5日觸發。另外,W的最近尋找不會跨過月份 。
LW:這兩個字符可以連用,表示指定月的最后一個工作日。
#:用於確定每個月第幾個周幾,只能出現在daysofMonth域。例如在4#2,表示某月的第二個周三。
常用表達式示例:
例如想在每月的2號觸發調度,不管2號是周幾,則只能使用如下寫法:0 0 0 2 * ?
, 其中最后一位只能用?
,而不能使用*
,如果使用*
表示不管周幾都會觸發
(1)0/2 * * * * ? 表示每2秒 執行任務 (1)0 0/2 * * * ? 表示每2分鍾 執行任務 (1)0 0 2 1 * ? 表示在每月的1日的凌晨2點調整任務 (2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15執行作業 (3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每個月的最后一個星期五上午10:15執行作 (4)0 0 10,14,16 * * ? 每天上午10點,下午2點,4點 (5)0 0/30 9-17 * * ? 朝九晚五工作時間內每半小時 (6)0 0 12 ? * WED 表示每個星期三中午12點 (7)0 0 12 * * ? 每天中午12點觸發 (8)0 15 10 ? * * 每天上午10:15觸發 (9)0 15 10 * * ? 每天上午10:15觸發 (10)0 15 10 * * ? 每天上午10:15觸發 (11)0 15 10 * * ? 2005 2005年的每天上午10:15觸發 (12)0 * 14 * * ? 在每天下午2點到下午2:59期間的每1分鍾觸發 (13)0 0/5 14 * * ? 在每天下午2點到下午2:55期間的每5分鍾觸發 (14)0 0/5 14,18 * * ? 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鍾觸發 (15)0 0-5 14 * * ? 在每天下午2點到下午2:05期間的每1分鍾觸發 (16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44觸發 (17)0 15 10 ? * MON-FRI 周一至周五的上午10:15觸發 (18)0 15 10 15 * ? 每月15日上午10:15觸發 (19)0 15 10 L * ? 每月最后一日的上午10:15觸發 (20)0 15 10 ? * 6L 每月的最后一個星期五上午10:15觸發 (21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一個星期五上午10:15觸發 (22)0 15 10 ? * 6#3 每月的第三個星期五上午10:15觸發
異步操作
1.啟動類開啟注解
@EnableAsync //開啟異步注解功能 @EnableScheduling //開啟基於注解的定時任務 @MapperScan("com.pdzx.dao") @SpringBootApplication public class VideoApplication { public static void main(String[] args) { SpringApplication.run(VideoApplication.class); } }
2.注解標注異步方法
@Component public class AsyncService { //告訴Spring這是一個異步方法,SpringBoot就會自己開一個線程池,進行調用! @Async public void hello(){ try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("業務進行中...."); } }
3.控制層測試
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/async/hello")
public String hello(){
long time1 = System.currentTimeMillis();
asyncService.hello();//調用異步方法
long time2 = System.currentTimeMillis();
System.out.println("Controller :"+(time2-time1));
return "success";
}
}
訪問該url,看結果
異步生效了
統一結果返回
封裝一個統一的返回類
public class Result<T> { //是否成功 private Boolean success; //狀態碼 private Integer code; //提示信息 private String msg; //數據 private T data; public Result() { } //自定義返回結果的構造方法 public Result(Boolean success,Integer code, String msg,T data) { this.success = success; this.code = code; this.msg = msg; this.data = data; } //自定義異常返回的結果 public static Result defineError(DefinitionException de){ Result result = new Result(); result.setSuccess(false); result.setCode(de.getErrorCode()); result.setMsg(de.getErrorMsg()); result.setData(null); return result; } //其他異常處理方法返回的結果 public static Result otherError(ErrorEnum errorEnum){ Result result = new Result(); result.setMsg(errorEnum.getErrorMsg()); result.setCode(errorEnum.getErrorCode()); result.setSuccess(false); result.setData(null); return result; } public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
統一異常
public enum ErrorEnum { // 數據操作錯誤定義 SUCCESS(200, "nice"), NO_PERMISSION(403,"你沒得權限"), NO_AUTH(401,"你能不能先登錄一下"), NOT_FOUND(404, "未找到該資源!"), INTERNAL_SERVER_ERROR(500, "服務器跑路了"), ; /** 錯誤碼 */ private Integer errorCode; /** 錯誤信息 */ private String errorMsg; ErrorEnum(Integer errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; } public Integer getErrorCode() { return errorCode; } public String getErrorMsg() { return errorMsg; } }
全局異常處理
Springboot對於異常的處理也做了不錯的支持,
它提供了一個 @ControllerAdvice注解以及 @ExceptionHandler注解,前者是用來開啟全局的異常捕獲,后者則是說明捕獲哪些異常,對那些異常進行處理。如下
1.自定義異常
public class DefinitionException extends RuntimeException { protected Integer errorCode; protected String errorMsg; public DefinitionException(){ } public DefinitionException(Integer errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; } public Integer getErrorCode() { return errorCode; } public void setErrorCode(Integer errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } }
2.全局異常處理
@ControllerAdvice public class GlobalExceptionHandler { /** * 處理自定義異常 */ @ExceptionHandler(value = DefinitionException.class) @ResponseBody public Result bizExceptionHandler(DefinitionException e) { return Result.defineError(e); } /** * 處理其他異常 */ @ExceptionHandler(value = Exception.class) @ResponseBody public Result exceptionHandler( Exception e) { return Result.otherError(ErrorEnum.INTERNAL_SERVER_ERROR); } }
測試
@RequestMapping("/getDeException") public Result DeException(){ throw new DefinitionException(400,"我出錯了"); } @RequestMapping("/getException") public Result Exception(){ Result result = new Result(); int a=1/0; return result; }
看結果
首先看自定義異常
看其他異常結果
攔截器
攔截器是在servlet執行之前執行的程序(這里就是controller代碼執行之前),它主要是用於攔截用戶請求並作相應的處理,比如說可以判斷用戶是否登錄,做相關的日志記錄,也可以做權限管理。
SpringBoot中的攔截器實現和spring mvc 中是一樣的,
它的大致流程是,先自己定義一個攔截器類,並將這個類實現一個HandlerInterceptor類,或者是繼承HandlerInterceptorAdapter,都可以實現攔截器的定義。
然后將自己定義的攔截器注入到適配器中,也有兩種方式,一種是實現WebMvcConfigurer接口,一種是繼承WebMvcConfigurerAdapter。下面我們來看看如何完成。
1.自定義攔截器
/**
*
* 自定義的攔截器可以實現HandlerInterceptor接口,也可以繼承HandlerInterceptorAdapter類。
重寫三個方法,當然也可以只實現一個最重要的preHandle方法。
preHandle方法:此方法會在進入controller之前執行,返回Boolean值決定是否執行后續操作。
postHandle方法:此方法將在controller執行之后執行,但是視圖還沒有解析,可向ModelAndView中添加數據(前后端不分離的)。
afterCompletion方法:該方法會在整個請求結束(請求結束,但是並未返回結果給客戶端)之后執行, 可獲取響應數據及異常信息。
*/
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("進入攔截器了");
//中間寫邏輯代碼,比如判斷是否登錄成功,失敗則返回false
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
//
System.out.println("controller 執行完了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("我獲取到了一個返回的結果:"+response);
System.out.println("請求結束了");
}
}
2.攔截器注入適配器
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/**")//攔截所有的路徑 .excludePathPatterns("/LoginController/*")//配置不需要攔截的路徑。 .excludePathPatterns("/hello/login");//配置多個路徑。 } }
3.先創建一個登陸的測試,這個接口是不會攔截的。
@RestController @RequestMapping("LoginController") public class Login { @RequestMapping("/login") public String login(){ System.out.println("controller開始執行"); return "login success"; } }
瀏覽器測試
http://localhost:5000/pdzx/LoginController/login
看結果
控制台只輸出了未攔截接口內的代碼,說明這個接口是未攔截的。瀏覽器的顯示就不管了。其實一般是攔截登錄接口,這里就將它放開了,以供測試。
4..創建一個攔截的controller
@RestController @RequestMapping("/hello") public class HelloController { @RequestMapping("/hello") public String hello(){ System.out.println("經過攔截的controller代碼執行完畢"); return "hello"; } @RequestMapping("/login") public String login(){ System.out.println("不攔截的controller代碼執行完畢"); return "hello login"; } }
測試
http://localhost:5000/pdzx/hello/hello
看結果
可以看到,首先是進入了攔截器,
通過攔截器之后,進入了controller中的方法,執行完controller的代碼之后就進入了自定義攔截器中的postHandle方法,最后進入afterCompletion方法,並獲取到了一個response對象。
事務處理
1.開啟事務支持
@EnableTransactionManagement//開啟事務支持 @EnableAsync //開啟異步注解功能 @EnableScheduling //開啟基於注解的定時任務 @MapperScan("com.pdzx.dao") @SpringBootApplication public class VideoApplication { public static void main(String[] args) { SpringApplication.run(VideoApplication.class); } }
2.@Transactional來修飾一個方法
@Service public class UserInfoServiceImpl { @Autowired private UserInfoMapper userInfoMapper; public int insertSelective(UserInfo record) { return userInfoMapper.insertSelective(record); } public int updateByPrimaryKeySelective(UserInfo record) { return userInfoMapper.updateByPrimaryKeySelective(record); } @Transactional public void add1(UserInfo record){ userInfoMapper.insertSelective(record); int i=1/0; } }
3.controller
@RequestMapping("userInfo") @RestController public class UserInfoController { @Autowired private UserInfoServiceImpl userInfoService; @RequestMapping("/add1") public void add1(){ UserInfo userInfo=new UserInfo(); userInfo.setName("張三"); userInfoService.add1(userInfo); } }
請求該路徑之前,看一下數據庫
請求該接口
http://localhost:5000/pdzx/userInfo/add1
看結果
看控制台和數據庫
數據沒進入數據庫
我們把那行錯誤注釋掉
@Transactional public void add1(UserInfo record){ userInfoMapper.insertSelective(record); //int i=1/0; }
再試一下,看結果
結果了,可以看出,事務處理生效了.
我們再看一個情況:
不帶事務的方法調用該類中帶事務的方法,不會回滾。
因為spring的回滾是用過代理模式生成的,如果是一個不帶事務的方法調用該類的帶事務的方法,直接通過this.xxx()
調用,而不生成代理事務,所以事務不起作用
看代碼,add2,調用帶事務處理的updateByPrimaryKeySelective
@Transactional public int updateByPrimaryKeySelective(UserInfo userInfo) { int i=1/0; userInfo=new UserInfo(); userInfo.setId((long)100); userInfo.setName("李四"); return userInfoMapper.updateByPrimaryKeySelective(userInfo); } public void add2(UserInfo record){ userInfoMapper.insertSelective(record); updateByPrimaryKeySelective(record); }
controller層
@RequestMapping("/add2") public void add2(){ UserInfo userInfo=new UserInfo(); userInfo.setId((long) 150); userInfo.setName("張三"); userInfoService.add2(userInfo); }
測試
http://localhost:5000/pdzx/userInfo/add2
看控制台和數據庫
可以看到數據存到數據庫了,事物沒有生效,這個情況還需注意