SpringBoot整合Nacos自動重新整理配置

Naylor發表於2022-01-28

目的

Nacos作為SpringBoot服務的註冊中心和配置中心。

在NacosServer中修改配置檔案,在SpringBoot不重啟的情況下,獲取到修改的內容。

本例將在配置檔案中配置一個 cml.age=100 的配置項,程式中編寫一個方法讀取配置檔案,並通過 Get--->/test/age 介面提供給瀏覽器訪問。

  • 若配置檔案中的 age 修改為 200 ,不用重新啟動程式,直接訪問 /test/age 介面,將獲取到最新的值 200
  • 若配置檔案中沒有age 的配置項,或乾脆沒有 cml 的配置項,訪問 /test/age 介面將返回預設的值 18

環境

  • SpringCloud:2020.0.3
  • SpringCloudAlibaba:2021.1
  • SpringBoot:2.5.2

pom

pom中引入 nacos 相關配置:discovery,config,bootstrap

網上有人說,需要引入 actuator ,其實不用。也沒有 SpringSecurity 攔截的問題,NacosClient 端定時從 NacosServer 端拉取最新配置,以http方式請求。


  <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.dependencies}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.dependencies}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- nacos-discovery -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
           <!--nacos config -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
         <!--bootstrap-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
    </dependencies>


配置檔案

bootstrap.yml


server:
  port: 9556
spring:
  application:
    name: app
  profiles:
    active: test
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: 192.168.1.61:8848
      config:
        server-addr: 192.168.1.61:8848
        file-extension: yaml

app-dev.yml

此配置指 NacosServer 中的配置檔案 app-dev.yml ,僅擷取 cml.age 部分


cml:
   age: 100

程式碼

  • RefreshScope註解:必須加在 controller 上面,加在主啟動內上面不好使。哪些資源需要自動重新整理配置就在該controller上面新增此註解,可封裝一個 BaseController 。
  • @Value("${cml.age:18}"):讀取配置檔案中的 cml.age 配置項值,賦給變數 age ,預設值為 18
  • getAge:獲取年齡介面
  • /test/age介面需要新增到 Security.permitAll

問題:RefreshScope註解為什麼一定要新增在 controller 上面?為什麼在主啟動類上面新增不生效


     @RefreshScope
     @Api(tags = "測試 - api")
     @Slf4j
     @RestController
     @RequestMapping("/test")
     public class TestController {
     
         /**
          * 獲取配置檔案中的 cml.age 內容,若未獲取到,預設值為18
          */
         @Value("${cml.age:18}")
         private String age;
     
         @ApiOperation(value = "獲取年齡 - 測試配置自動重新整理", notes = "獲取年齡 - 測試配置自動重新整理")
         @GetMapping("/age")
         public String getAge() {
             return age;
         }
     }
     

日誌

開啟 nacos-refresh 日誌,列印配置內容更新情況


logging:
  level:
     com.alibaba.cloud.nacos.refresh: debug

列印的日誌:

2022-01-28 13:43:30.574 [com.alibaba.nacos.client.Worker.longPolling.fixed-192.168.1.61_8848-zjrkm-admin] DEBUG com.alibaba.cloud.nacos.refresh.NacosContextRefresher.innerReceive:136 - Refresh Nacos config group=DEFAULT_GROUP,dataId=identityQrCodeAdmin-service-cml-test.yaml,configInfo=spring:
  application:
    name: 
    .
    .
    .
    .
    .
    

測試

在不重啟SpringBoot服務的情況,多次在 NacosServer 中修改 cml.age 配置項的值,然後通過瀏覽器訪問 /test/age 介面,發現每次都可以獲取到最新的 age 值。

特殊的情況

當專案整合了 SpringSecurity 的時候,如果在 UserDetailsService 實現類上新增 RefreshScope 註解,並在類中直接使用 @Value 獲取配置檔案的內容,會導致使用者在登入的時候找不到該類,從而無法呼叫loadUserByUsername 方法做使用者名稱密碼校驗,進而登入功能無法使用。

解決方案是:

  • 新建一個配置類(假設類名為 Account ),使用 ConfigurationProperties 將需要的配置注入到類屬性;並在類上新增 RefreshScope 註解
  • 將 Account 類注入容器,UserDetailsService 實現類中將 Account 類 Autowired 進來再使用

核心程式碼:
Account 類:


import lombok.Data;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@RefreshScope
@Component
@ConfigurationProperties(prefix = "cml.admin")
@Data
@ToString
public class Account  implements Serializable {
    private  String defaultUserName;
    private  String defaultPassword;
    private  String defaultUserName1;
    private  String defaultPassword1;
}


UserDetailsService 實現類:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private Account account;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (account.getDefaultUserName().equals(username)) {
            String password = passwordEncoder.encode(account.getDefaultPassword());
            return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
        if (account.getDefaultUserName1().equals(username)) {
            String password = passwordEncoder.encode(account.getDefaultPassword1());
            return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin1"));
        }
        return null;
    }
}


相關文章