springboot實現定時任務,異步操作,統一結果返回,全局異常處理,攔截器及事務處理


本文是作者原創,版權歸作者所有.若要轉載,請注明出處.

本文都是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

看控制台和數據庫

 

 

 

 可以看到數據存到數據庫了,事物沒有生效,這個情況還需注意

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM