一次nacos配置变更后没有被spring感知的bug排查

1,110 阅读2分钟

问题描述

某天,业务要求我们对某个webservice接口做个开关,实现动态控制接口的放行与拦截。我们的实现方案是在nacos上增加一个配置,同时在项目中获取该配置判断是否执行后续业务逻辑。看上去很简单的一个流程(后面却坑的我们好惨),我一顿操作就写完了代码通知测试同学可以提测了。结果过了一段时间后测试同学过来说我这个开关不好使,不能动态感知到配置变更。。。没办法了,只能去看看什么原因导致的。

相关代码

webservice实现类

package com.sunl888.nacossync.webservice.impl;

import com.sunl888.nacossync.config.AppConfig;
import com.sunl888.nacossync.webservice.TestWebService;
import jakarta.annotation.Resource;
import jakarta.jws.WebService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@WebService(
        serviceName = "testWebService",
        targetNamespace = "https://service.nacos.sync.sunl888.com/",
        endpointInterface = "com.sunl888.nacossync.webservice.TestWebService")
@Service
@Slf4j
public class TestWebServiceImpl implements TestWebService {
    @Resource
    private AppConfig appConfig;

    @Override
    public String sync(String request) {
        if (!Boolean.TRUE.equals(appConfig.getEnabled())) {
           return "未执行业务逻辑";
        }
        return "OK";
    }
}

应用配置类

package com.sunl888.nacossync.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

@Data
@RefreshScope
@Configuration
public class AppConfig {

    @Value("${switch.enabled:}")
    private Boolean enabled;
}

webservice配置类

package com.sunl888.nacossync.config;

import com.sunl888.nacossync.client.TestWebServiceRemote;
import com.sunl888.nacossync.webservice.TestWebService;
import jakarta.xml.ws.Endpoint;
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.jaxws.JaxWsClientFactoryBean;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebServiceConfig {
    @Bean
    public ServletRegistrationBean<CXFServlet> cxfServletServletRegistrationBean() {
        return new ServletRegistrationBean<>(new CXFServlet(), "/ws/*");
    }

    @Bean
    public Endpoint testEndpoint(Bus bus, TestWebService testWebService) {
        Endpoint endpoint = new EndpointImpl(bus, testWebService);
        endpoint.publish("/testWebService");
        return endpoint;
    }
}

问题排查

测试

以上就是关键代码,启动后我们就可以通过构建 JaxWsProxyFactoryBean 来调用这个 webservice 接口了,我测试了一下,系统确实没有获取到 appConfig 的最新配置。

JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();

jaxWsProxyFactoryBean.setAddress("http://localhost:8088/ws/testWebService?wsdl");
jaxWsProxyFactoryBean.setServiceClass(TestWebServiceRemote.class);
TestWebServiceRemote remote = jaxWsProxyFactoryBean.create(TestWebServiceRemote.class);

@RefreshScope知识

通过翻阅相关资料,我们知道被 @RefreshScope 注解的实例,在扫描生成 BeanDefiniton 时被动态修改了,实际上是向 spring ioc 中注册了两个 Bean 定义,一个是与 beanName 同名但类型是 LockedScopedProxyFactoryBean.class 的代理工厂 Bean,另一个是 scopedTarget.beanName 的 Bean。当我们修改了nacos配置后,spring 实际上只会修改其中一个 bean,那么问题就出在webservice引用的AppConfig实例上了,很可能引用的是原始实例。

调试

我们从webservice服务的publish流程开始debug,经过一段时间的排查发现 webservicepublish 服务时会自动从 spring 中获取依赖并注入到服务中,见如下图:

企业微信截图_20240930182924.png 从图中我们可以看出来webservice框架默认是根据type取第一个name对应的bean,那么我们只要指定@Resource(name="appConfig")是不是就可以了呢?接下来我把代码改成如下方式:

package com.sunl888.nacossync.webservice.impl;

import com.sunl888.nacossync.config.AppConfig;
import com.sunl888.nacossync.webservice.TestWebService;
import jakarta.annotation.Resource;
import jakarta.jws.WebService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@WebService(
        serviceName = "testWebService",
        targetNamespace = "https://service.nacos.sync.sunl888.com/",
        endpointInterface = "com.sunl888.nacossync.webservice.TestWebService")
@Service
@Slf4j
public class TestWebServiceImpl implements TestWebService {
    @Resource(name="appConfig")
    private AppConfig appConfig;

    @Override
    public String sync(String request) {
        if (!Boolean.TRUE.equals(appConfig.getEnabled())) {
           return "未执行业务逻辑";
        }
        return "OK";
    }
}

经过一番测试发现果然可以正常感知到 nacos 的变更了。

结尾

这是测试项目地址:github.com/sunl888/nac… 希望能给大家提供一些帮助。