摸索AI(四)Spring AI中的Http配置

Spring Ai
placeholder image
admin 发布于:2026-03-12 09:32:12
阅读:loading

之前也是花了巨多的时间从Github上下载了一些AI摸索的实践项目,涉及到的有文生图、文生视频、图生图、语音克隆、数字人等,它们的实践对电脑配置的依赖有一定的要求,而且实践的过程复杂程度较高,属于摸索着玩玩而已。本次摸索AI的范围则是面向对擅长领域的代码接入实践。如果你对Java代码交互的AI大模型有一些兴趣,又或者是跟我一样不知从哪里入手,或许看看我这里分享的前后实践过程与实践的案例范围,也是不错的选择。

所以,本系列教程相关的实践是在本地部署大模型,并且使用Java代码与本地的大模型进行交互。除了本地部署的大模型以为,也是可以付费接入网络上的一些付费大模型,比如Deepseek、千问等等,但是对我个人来讲,私有化的本地大模型更加有意义,毕竟可以免费的集成到企业级应用实践当中。

本次实践围绕使用Spring AI在企业级应用中的专业层面进行的实践,主要考虑Http请求配置的角度。Spring的AI本质上还是接口调用,有使用RestTemplate和RestClient等形式,同时考虑的细节有:Http连接池、各种超时时间、HttpClient、拦截器等等。

1.RestTemplate

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        //设置请求配置
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        //最大连接数
        connectionManager.setMaxTotal(PoolingHttpClientConnectionManager.DEFAULT_MAX_TOTAL_CONNECTIONS);
        //每个主机的最大连接数
        connectionManager.setDefaultMaxPerRoute(PoolingHttpClientConnectionManager.DEFAULT_MAX_CONNECTIONS_PER_ROUTE);

        //设置Socket超时时间
        SocketConfig socketConfig = SocketConfig.custom().setSoKeepAlive(true).setSoTimeout(Timeout.ofSeconds(30)).build();
        connectionManager.setDefaultSocketConfig(socketConfig);

        //设置Connection Config
        ConnectionConfig connectionConfig = ConnectionConfig.custom()
                //Socket 读取超时 连接在连接池中的最大存活时间
                .setTimeToLive(Timeout.ofSeconds(60))
                //建立连接的超时
                .setConnectTimeout(Timeout.ofSeconds(10))
                .build();
        connectionManager.setDefaultConnectionConfig(connectionConfig);

        RequestConfig requestConfig = RequestConfig.custom()
                //整个请求最大允许耗时时间
                .setResponseTimeout(Timeout.ofSeconds(300))
                //从连接池获取连接的最大等待时间
                .setConnectionRequestTimeout(Timeout.ofSeconds(5))
                .build();

        //创建 HttpClient 实例
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).build();
        //创建RestTemplate
        final RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
        restTemplate.setInterceptors(Collections.singletonList(new LoggingRequestInterceptor()));
        return restTemplate;
    }

}

2.LoggingRequestInterceptor

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        //记录请求日志
        this.logRequest(request , body);

        ClientHttpResponse response = execution.execute(request, body);
        CacheClientHttpResponseWrapper responseWapper;
        try (InputStream inputStream = response.getBody()) {
            final byte[] cacheBody = StreamUtils.copyToByteArray(inputStream);
            responseWapper = new CacheClientHttpResponseWrapper(response , cacheBody);
        }

        //记录响应日志
        this.logResponse(request , responseWapper);
        return responseWapper;
    }

    private void logResponse(HttpRequest request, ClientHttpResponse response) throws IOException {
        final MediaType contentType = request.getHeaders().getContentType();
        StringBuilder responseBuilder = new StringBuilder();
        boolean text = contentType != null &&
                (
                        contentType.isCompatibleWith(MediaType.APPLICATION_JSON)
                                ||
                                contentType.isCompatibleWith(MediaType.APPLICATION_XML)
                                ||
                                contentType.toString().contains("text/")
                );
        responseBuilder.append("=== Response Begin ===\n");
        responseBuilder.append("URI            :").append(request.getURI()).append("\n");
        responseBuilder.append("Status Code    :").append(response.getStatusCode().value()).append("\n");
        responseBuilder.append("Status Text    :").append(response.getStatusText()).append("\n");
        if (text) {
            responseBuilder.append("Body           :").append(StreamUtils.copyToString(response.getBody() , StandardCharsets.UTF_8)).append("\n");
        } else {
            responseBuilder.append("Body           :[Binary response,").append(" bytes, skipped for logging]\n");
        }
        responseBuilder.append("=== Response End ===\n");
        System.out.println(responseBuilder.toString());
    }

    private void logRequest(HttpRequest request, byte[] body) {
        final MediaType contentType = request.getHeaders().getContentType();
        StringBuilder requestBuilder = new StringBuilder();
        boolean text = contentType != null &&
                (
                        contentType.isCompatibleWith(MediaType.APPLICATION_JSON)
                        ||
                        contentType.isCompatibleWith(MediaType.APPLICATION_XML)
                        ||
                        contentType.toString().contains("text/")
                );
        requestBuilder.append("=== Request Begin ===\n");
        requestBuilder.append("URI            :").append(request.getURI()).append("\n");
        requestBuilder.append("Method         :").append(request.getMethod()).append("\n");
        requestBuilder.append("Headers        :").append(request.getHeaders()).append("\n");
        if (text) {
            requestBuilder.append("Body           :").append(new String(body , StandardCharsets.UTF_8)).append("\n");
        } else {
            requestBuilder.append("Body           :[Binary data, size: ]").append(body.length).append(" bytes, skipped for logging]\n");
        }
        requestBuilder.append("=== Request End ===\n");
        System.out.println(requestBuilder.toString());
    }
}

3.其它说明

(1)本次实践的代码是Http请求中常见的设置,准确来说与Spring AI关系不大,使用Spring的Http接口交互时的常用设置;

(2)许多案例给出的是结合Spring Boot后的配置注入方式,这种把大模型的服务URL、模型名称等等参数细节配置在application配置文件中的形式,我感觉不够灵活,后续相关的示例均使用硬编码的形式给出,面向底层代码更加便于新手水平理解掌握;


 点赞


 发表评论

当前回复:作者

 评论列表


留言区