SpringBoot框架:兩個方法同時呼叫時父方法使內部方法的DataSource註解失效的解決辦法

我命傾塵 發表於 2020-09-22

一、問題如下:

  使用的是SpringBoot框架:通過AOP和自定義註解完成druid連線池的動態資料來源切換(三)中的兩個資料庫spring_boot_demoother_data

  在UserController中同時呼叫兩個方法getAgeOfUser()getAgeOfUser2(),這裡方法裡都是使用UserService中的同一方法接收資料。

  不同的是在getAgeOfUser2()上使用了DataSource(DataSourceName.SECOND)自定義註解來切換資料來源,當兩個方法同時被呼叫時,程式碼如下:

package com.example.demo.controller;

import com.example.demo.annotation.DataSource;
import com.example.demo.enums.DataSourceName;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author 我命傾塵
 */
@RestController
public class UserController {

    @Autowired
    UserService userService;

    /**
     * 從spring_boot_demo資料庫中得到資料
     * @return int
     */
    public int getAgeOfUser(){
        return userService.getAgeByUsername("springbootdemo");
    }

    /**
     * 從other_data資料庫中得到資料
     * @return
     */
    @DataSource(DataSourceName.SECOND)
    public int getAgeOfUser2(){
        return userService.getAgeByUsername("springbootdemo");
    }

    /**
     * 兩個方法同時執行
     * @return String
     */
    @RequestMapping("/user/age")
    public String getAge(){
        int age1=getAgeOfUser();
        int age2=getAgeOfUser2();
        return "spring_boot_demo:"+age1+";other_data:"+age2;
    }
}

  得到的執行結果如下:

  SpringBoot框架:兩個方法同時呼叫時父方法使內部方法的DataSource註解失效的解決辦法

  很顯然,切換資料來源沒有成功,加了切換註解的方法得到的顯示結果還是主資料來源的資料

二、問題思考:

  相比於之前使用單方法切換資料來源成功的測試結果,這次的測試和之前的差別無非以下兩點:

  1不使用註解(即預設資料來源)的方法和使用註解(切換輔資料來源)的方法同時被呼叫;

  2、方法被巢狀在一個不使用註解的父方法中呼叫。

  那麼針對以上兩個情況再進行測試,去掉使用預設資料來源的方法,只在父方法中巢狀一個進行呼叫:

  @RequestMapping("/user/age")
    public String getAge(){
        int age2=getAgeOfUser2();
        return "other_data:"+age2;
    }

  得到的結果如下所示:

  SpringBoot框架:兩個方法同時呼叫時父方法使內部方法的DataSource註解失效的解決辦法

  結果得到的還是主資料來源的資料,也就是說,切換資料來源的註解失效與另一個無註解的方法無關,而是因為被巢狀在一個無註解的父方法

  在無註解的父方法上加上切換資料來源的註解,再次進行測試如下:

  @RequestMapping("/user/age")
  @DataSource(DataSourceName.SECOND)
  public String getAge(){
    int age2=getAgeOfUser2();
    return "other_data:"+age2;
  }

  得到的結果是:

  SpringBoot框架:兩個方法同時呼叫時父方法使內部方法的DataSource註解失效的解決辦法

  到這兒,基本就清楚了。

  之所以切換資料來源的註解失效,是因為方法巢狀時,子方法的DataSource註解被父方法覆蓋了

三、問題解決

  1、使用代理物件:

  (1)引入spring-aspects依賴包:

        <!-- spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>

  (2)修改入口類的@EnableAspectJAutoProxy註解為:

@EnableAspectJAutoProxy(exposeProxy = true)

  exposeProxy = true的作用是暴露當前代理物件到當前執行緒繫結

  (3)修改controller層中方法的呼叫方式:

  AopContext.currentProxy()使用ThreadLocal儲存了代理物件,所以直接把方法之間的呼叫方式改為代理物件之間的呼叫即可。

/**
     * 兩個方法同時執行
     * @return String
     */
    @RequestMapping("/user/age")
    public String getAge(){
        int age1=((UserController)AopContext.currentProxy()).getAgeOfUser();
        int age2=((UserController)AopContext.currentProxy()).getAgeOfUser2();
        return "spring_boot_demo:"+age1+";other_data:"+age2;
    }

  程式碼如上所示,使用((UserController)AopContext.currentProxy()).getAgeOfUser()的方式,通過代理物件呼叫方法,所得結果為:

  SpringBoot框架:兩個方法同時呼叫時父方法使內部方法的DataSource註解失效的解決辦法

  兩個資料庫的結果同時獲取並顯示了。

  2、把DataSource註解放到service層的方法中:

  (1)修改Service層的方法,加上註解:

    @Override
    public int getAgeByUsername(String username) {
        return userMapper.getAgeByUsername(username);
    }

    @Override
    @DataSource(DataSourceName.SECOND)
    public int getAgeByUsername2(String username) {
        return userMapper.getAgeByUsername(username);
    }

  (2)在controller中直接呼叫即可:

  /**
     * 兩個方法同時執行
     * @return String
     */
    @RequestMapping("/user/age")
    public String getAge(){
        int age1=userService.getAgeByUsername("springbootdemo");
        int age2=userService.getAgeByUsername2("springbootdemo");
        return "spring_boot_demo:"+age1+";other_data:"+age2;
    }

  結果如下:

  SpringBoot框架:兩個方法同時呼叫時父方法使內部方法的DataSource註解失效的解決辦法