You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中基于配置的SOLID IoC:多连接客户端设计与实现问询

方案合理性判断

这个配置方案合理且具备良好的扩展性:

  • 它将不同通信协议(REST/gRPC/RabbitMQ)的连接参数集中管理,与业务逻辑完全解耦,后续调整连接地址、端口等配置无需修改业务代码;
  • 通过endpointType将通信端点与业务模块(EMPLOYEE/DEPARTMENT)绑定,能清晰映射业务服务所需的底层通信能力;
  • 业务服务仅依赖抽象客户端,无需关注底层协议细节,符合依赖反转原则,后续切换通信协议(比如把某业务的REST换成gRPC)只需调整配置和实现类,不影响业务逻辑。
Spring Boot 实现步骤

1. 绑定配置文件到Java类

通过@ConfigurationProperties将YAML配置映射为Java对象,方便代码中读取配置:

// 顶级配置类
@ConfigurationProperties(prefix = "connections")
@Component
public class ConnectionsProperties {
    private List<HttpConnection> http;
    private List<GrpcConnection> grpc;
    // 后续可添加rabbitmq配置列表
    // getter/setter
}

// HTTP连接配置
public class HttpConnection {
    private String name;
    private String host;
    private Integer port;
    private List<Endpoint> endpoints;
    // getter/setter
}

// gRPC连接配置
public class GrpcConnection {
    private String name;
    private String host;
    private Integer port;
    private Boolean disableTls;
    private List<Endpoint> endpoints;
    // getter/setter
}

// 端点配置
public class Endpoint {
    private String name;
    private String httpMethod; // 仅HTTP协议使用
    private String endpointType; // EMPLOYEE/DEPARTMENT
    // getter/setter
}

记得在启动类添加@EnableConfigurationProperties(ConnectionsProperties.class)开启配置绑定。

2. 定义抽象客户端接口

为业务模块定义抽象客户端,业务服务仅依赖这些接口,实现逻辑与底层协议解耦:

// 员工业务客户端抽象
public interface EmployeeClient {
    List<Employee> getEmployees();
}

// 部门业务客户端抽象
public interface DepartmentClient {
    Department createDepartment(Department dept);
}

3. 实现具体协议客户端

根据配置实现不同协议的客户端实现类:

HTTP客户端实现

public class EmployeeHttpClient implements EmployeeClient {
    private final RestTemplate restTemplate;
    private final String baseUrl;
    private final String httpMethod;

    // 构造器注入配置参数
    public EmployeeHttpClient(RestTemplate restTemplate, String host, Integer port, String httpMethod) {
        this.restTemplate = restTemplate;
        this.baseUrl = String.format("http://%s:%s/employees", host, port);
        this.httpMethod = httpMethod;
    }

    @Override
    public List<Employee> getEmployees() {
        if ("GET".equalsIgnoreCase(httpMethod)) {
            return restTemplate.exchange(baseUrl, HttpMethod.GET, null, 
                new ParameterizedTypeReference<List<Employee>>() {}).getBody();
        }
        throw new UnsupportedOperationException("不支持当前HTTP方法");
    }
}

public class DepartmentHttpClient implements DepartmentClient {
    private final RestTemplate restTemplate;
    private final String baseUrl;
    private final String httpMethod;

    public DepartmentHttpClient(RestTemplate restTemplate, String host, Integer port, String httpMethod) {
        this.restTemplate = restTemplate;
        this.baseUrl = String.format("http://%s:%s/departments", host, port);
        this.httpMethod = httpMethod;
    }

    @Override
    public Department createDepartment(Department dept) {
        if ("POST".equalsIgnoreCase(httpMethod)) {
            return restTemplate.postForObject(baseUrl, dept, Department.class);
        }
        throw new UnsupportedOperationException("不支持当前HTTP方法");
    }
}

gRPC客户端实现

public class EmployeeGrpcClient implements EmployeeClient {
    private final EmployeeServiceGrpc.EmployeeServiceBlockingStub blockingStub;

    public EmployeeGrpcClient(String host, Integer port, Boolean disableTls) {
        ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress(host, port);
        if (disableTls) {
            channelBuilder.usePlaintext();
        }
        ManagedChannel channel = channelBuilder.build();
        this.blockingStub = EmployeeServiceGrpc.newBlockingStub(channel);
    }

    @Override
    public List<Employee> getEmployees() {
        GetEmployeesResponse response = blockingStub.getEmployees(GetEmployeesRequest.newBuilder().build());
        return response.getEmployeesList().stream()
                .map(grpcEmp -> new Employee(grpcEmp.getId(), grpcEmp.getName()))
                .collect(Collectors.toList());
    }
}

4. 动态注册客户端Bean到Spring容器

编写配置类,遍历配置文件中的连接和端点,根据endpointType和协议类型创建对应的客户端实例并注册为Spring Bean:

@Configuration
public class ClientAutoConfiguration {
    private final ConnectionsProperties connectionsProperties;
    private final RestTemplate restTemplate;

    public ClientAutoConfiguration(ConnectionsProperties connectionsProperties, RestTemplate restTemplate) {
        this.connectionsProperties = connectionsProperties;
        this.restTemplate = restTemplate;
    }

    // 注册HTTP客户端Bean
    @Bean
    public void registerHttpClients() {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) ApplicationContextProvider.getApplicationContext().getAutowireCapableBeanFactory();
        for (HttpConnection conn : connectionsProperties.getHttp()) {
            for (Endpoint endpoint : conn.getEndpoints()) {
                switch (endpoint.getEndpointType()) {
                    case "EMPLOYEE":
                        BeanDefinitionBuilder empHttpBuilder = BeanDefinitionBuilder.genericBeanDefinition(EmployeeHttpClient.class)
                                .addConstructorArgValue(restTemplate)
                                .addConstructorArgValue(conn.getHost())
                                .addConstructorArgValue(conn.getPort())
                                .addConstructorArgValue(endpoint.getHttpMethod());
                        registry.registerBeanDefinition("employeeHttpClient", empHttpBuilder.getBeanDefinition());
                        break;
                    case "DEPARTMENT":
                        BeanDefinitionBuilder deptHttpBuilder = BeanDefinitionBuilder.genericBeanDefinition(DepartmentHttpClient.class)
                                .addConstructorArgValue(restTemplate)
                                .addConstructorArgValue(conn.getHost())
                                .addConstructorArgValue(conn.getPort())
                                .addConstructorArgValue(endpoint.getHttpMethod());
                        registry.registerBeanDefinition("departmentHttpClient", deptHttpBuilder.getBeanDefinition());
                        break;
                }
            }
        }
    }

    // 注册gRPC客户端Bean
    @Bean
    public void registerGrpcClients() {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) ApplicationContextProvider.getApplicationContext().getAutowireCapableBeanFactory();
        for (GrpcConnection conn : connectionsProperties.getGrpc()) {
            for (Endpoint endpoint : conn.getEndpoints()) {
                if ("EMPLOYEE".equals(endpoint.getEndpointType())) {
                    BeanDefinitionBuilder empGrpcBuilder = BeanDefinitionBuilder.genericBeanDefinition(EmployeeGrpcClient.class)
                            .addConstructorArgValue(conn.getHost())
                            .addConstructorArgValue(conn.getPort())
                            .addConstructorArgValue(conn.getDisableTls());
                    registry.registerBeanDefinition("employeeGrpcClient", empGrpcBuilder.getBeanDefinition());
                }
            }
        }
    }

    // 提供RestTemplate Bean
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

注:ApplicationContextProvider是实现ApplicationContextAware的工具类,用于获取Spring上下文,方便动态注册Bean。

5. 业务服务使用客户端

业务服务通过@Qualifier指定具体客户端实现,按需调用:

@Service
public class EmployeeService {
    private final EmployeeHttpClient employeeHttpClient;
    private final EmployeeGrpcClient employeeGrpcClient;

    public EmployeeService(@Qualifier("employeeHttpClient") EmployeeHttpClient employeeHttpClient,
                           @Qualifier("employeeGrpcClient") EmployeeGrpcClient employeeGrpcClient) {
        this.employeeHttpClient = employeeHttpClient;
        this.employeeGrpcClient = employeeGrpcClient;
    }

    public List<Employee> getEmployeesByHttp() {
        return employeeHttpClient.getEmployees();
    }

    public List<Employee> getEmployeesByGrpc() {
        return employeeGrpcClient.getEmployees();
    }
}

@Service
public class DepartmentService {
    private final DepartmentHttpClient departmentHttpClient;

    public DepartmentService(@Qualifier("departmentHttpClient") DepartmentHttpClient departmentHttpClient) {
        this.departmentHttpClient = departmentHttpClient;
    }

    public Department createDepartment(Department dept) {
        return departmentHttpClient.createDepartment(dept);
    }
}

6. RabbitMQ 扩展实现(可选)

如果需要支持RabbitMQ,只需在YAML中添加rabbitmq配置段,定义连接、队列/交换机信息,然后实现EmployeeRabbitClient等类,在ClientAutoConfiguration中添加对应的注册逻辑即可,思路与HTTP/gRPC一致。


内容的提问来源于stack exchange,提问作者Spirotrack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 18:55:18