掘金 人工智能 前天 12:15
如何编写一个spring ai alibaba工具
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了如何为 Spring AI Alibaba 项目开发一个操作 Memcached 的工具。通过创建 Maven 项目,引入必要的依赖,并配置自动装配和 SPI 机制,实现了 Memcached 的增、删、改、查及追加操作。文章提供了完整的代码示例,包括 MemcachedAutoConfiguration、MemcachedProperties、MemcachedConstants 和 MemcachedService 类,并展示了如何编写测试用例来验证这些功能的正确性。该工具的开发为 Spring AI 应用提供了高效的数据缓存能力。

⚙️ **项目搭建与依赖引入**:首先,创建一个新的 Maven 项目,命名为 `spring-ai-alibaba-starter-tool-calling-memcached`,并确保 JDK 版本为 17+。随后,将此项目引入到 `spring-ai-alibaba-starter-tool-calling-memcached` 的根 `pom.xml` 以及 `spring-ai-alibaba-bom` 项目的 `pom.xml` 中。核心的 Memcached 依赖 `net.spy:spymemcached` 版本为 `2.12.3`。

🔧 **核心配置与自动装配**:通过 `MemcachedAutoConfiguration` 类实现 Memcached 的自动配置。该类使用 `@Configuration`、`@ConditionalOnClass`、`@ConditionalOnProperty` 和 `@EnableConfigurationProperties` 注解,确保在 MemcachedClient 类存在且配置启用时进行装配。`MemcachedProperties` 类继承自 `CommonToolCallProperties`,用于管理 Memcached 的 IP 地址和端口,并支持通过 `ConfigurationProperties` 进行配置。

💡 **Memcached 功能实现**:`MemcachedService` 类是核心业务逻辑的载体,它封装了 Memcached 的五种基本操作:设置(set)、获取(get)、删除(delete)、替换(replace)和追加(append)。每种操作都以内部类的形式实现 `Function` 接口,并定义了相应的 Request 记录类型,用于接收参数并与 MemcachedClient 交互,同时处理了异常情况并记录日志。

📝 **SPI 机制与服务托管**:在 `MemcachedAutoConfiguration` 中,通过 `@Bean` 方法将 `MemcachedService` 托管到 Spring 容器中,并注入 `MemcachedClient` 实例,使得 `MemcachedService` 能够被其他组件方便地调用。

🧪 **单元测试验证**:文章提供了 `MemcachedTest` 类,包含多个 `@Test` 方法来验证 Memcached 工具的功能。通过 `@SpringBootTest` 注解加载配置类,并使用 `@Autowired` 注入 `MemcachedService`。测试用例覆盖了 set、get、replace、append 和 delete 操作,并利用 `@EnabledIfEnvironmentVariable` 注解确保在配置了 IP 和 PORT 后才执行相关测试,最终通过日志输出来确认功能的正确性。

大家好,我是大花,这是我在掘金社区发的第一篇文章,主要讲述如何编写一个spring ai alibaba项目的工具,项目的工具都放在community/tool-calls路径下

接下来我们写一个操作memcached的工具,首先创建一个maven项目,选中tool-calls文件夹然后File & New & Project

按照规范填写好项目信息spring-ai-alibaba-starter-tool-calling-memcached,Jdk需要17+,点击创建

可以看到这里已经创建好了,将项目通过maven引入

然后我们打开项目的根pom.xml和spring-ai-alibaba-bom项目下的pom.xml文件,引入我们刚才创建的项目

创建好spring starter所需要的自动配置类并配置好spi机制,这里我们引入memcached依赖

<dependency>    <groupId>net.spy</groupId>    <artifactId>spymemcached</artifactId>    <version>2.12.3</version></dependency>

编写MemcachedAutoConfiguration配置类,这里注意需要在类的最开始填充协议内容

/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import net.spy.memcached.MemcachedClient;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.IOException;import java.net.InetSocketAddress;/** * auth: dahua */@Configuration@ConditionalOnClass(MemcachedClient.class)@ConditionalOnProperty(prefix = MemcachedConstants.CONFIG_PREFIX, name = "enabled", havingValue = "true",        matchIfMissing = true)@EnableConfigurationProperties(MemcachedProperties.class)public class MemcachedAutoConfiguration {    // 配置类    private final MemcachedProperties memcachedProperties;    public MemcachedAutoConfiguration(MemcachedProperties memcachedProperties) {        this.memcachedProperties = memcachedProperties;    }    // 创建memcached客户端    @Bean    @ConditionalOnMissingBean    public MemcachedClient memcachedClient() throws IOException {        return new MemcachedClient(new InetSocketAddress(memcachedProperties.getIp(), memcachedProperties.getPort()));    }}

配置类具体内容,同样需要注意需要在类的最开始填充协议内容

/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallProperties;import org.springframework.boot.context.properties.ConfigurationProperties;/** * auth: dahua */@ConfigurationProperties(prefix = MemcachedConstants.CONFIG_PREFIX)public class MemcachedProperties extends CommonToolCallProperties {    // 这里我们定义了ip和端口    private String ip = "localhost";    private int port = 11211;    public String getIp() {        return ip;    }    public void setIp(String ip) {        this.ip = ip;    }    public int getPort() {        return port;    }    public void setPort(int port) {        this.port = port;    }}

Constants类

/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallConstants;import com.alibaba.cloud.ai.toolcalling.memcached.MemcachedConstants;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * auth: dahua */public class MemcachedConstants {    public static final String CONFIG_PREFIX = CommonToolCallConstants.TOOL_CALLING_CONFIG_PREFIX + ".memcached";}

创建memcached处理类MemcachedService,并在MemcachedAutoConfiguration中托管给容器,同时传递MemcachedClient

@Bean@ConditionalOnMissingBeanpublic MemcachedService memcachedService(MemcachedClient memcachedClient) {    return new MemcachedService(memcachedClient);}
/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.fasterxml.jackson.annotation.JsonClassDescription;import com.fasterxml.jackson.annotation.JsonPropertyDescription;import net.spy.memcached.MemcachedClient;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.function.Function;/** * auth: dahua */public class MemcachedService {    private static final Logger logger = LoggerFactory.getLogger(MemcachedService.class);    private final MemcachedClient memcachedClient;    private final MemcachedServiceSetter setter = new MemcachedServiceSetter();    private final MemcachedServiceGetter getter = new MemcachedServiceGetter();    private final MemcachedServiceDeleter deleter = new MemcachedServiceDeleter();    private final MemcachedServiceReplacer replacer = new MemcachedServiceReplacer();    private final MemcachedServiceAppender appender = new MemcachedServiceAppender();    public MemcachedService(MemcachedClient memcachedClient) {        this.memcachedClient = memcachedClient;    }    public class MemcachedServiceSetter implements Function<MemcachedServiceSetter.Request, Boolean> {        @Override        public Boolean apply(MemcachedServiceSetter.Request request) {            try {                return memcachedClient.set(request.key(), request.ttl(), request.value()).get();            } catch (Exception e) {                logger.error("Set data to memcached failed. key {} value {} exception {}"                        , request.key(), request.value(), e.getMessage(), e);            }            return false;        }        @JsonClassDescription("set data to memcached api")        public record Request(@JsonPropertyDescription("key to memcached") String key                , @JsonPropertyDescription("value to memcached") Object value                , @JsonPropertyDescription("key ttl") int ttl) {        }    }    public class MemcachedServiceGetter implements Function<MemcachedServiceGetter.Request, Object> {        @Override        public Object apply(MemcachedServiceGetter.Request request) {            try {                return memcachedClient.get(request.key());            } catch (Exception e) {                logger.error("Get data from memcached failed. key {} exception {}"                        , request.key(), e.getMessage(), e);            }            return null;        }        @JsonClassDescription("get data from memcached api")        public record Request(@JsonPropertyDescription("key to memcached") String key) {        }    }    public class MemcachedServiceDeleter implements Function<MemcachedServiceDeleter.Request, Boolean> {        @Override        public Boolean apply(MemcachedServiceDeleter.Request request) {            try {                return memcachedClient.delete(request.key()).get();            } catch (Exception e) {                logger.error("Delete data from memcached failed. key {} exception {}"                        , request.key(), e.getMessage(), e);            }            return false;        }        @JsonClassDescription("delete data from memcached api")        public record Request(@JsonPropertyDescription("key to memcached") String key) {        }    }    public class MemcachedServiceReplacer implements Function<MemcachedServiceReplacer.Request, Boolean> {        @Override        public Boolean apply(MemcachedServiceReplacer.Request request) {            try {                return memcachedClient.replace(request.key(), request.ttl(), request.value()).get();            } catch (Exception e) {                logger.error("Replace data to memcached failed. key {} value {} exception {}"                        , request.key(), request.value(), e.getMessage(), e);            }            return false;        }        @JsonClassDescription("replace data to memcached api")        public record Request(@JsonPropertyDescription("key to memcached") String key                , @JsonPropertyDescription("value to memcached") Object value                , @JsonPropertyDescription("key ttl") int ttl) {        }    }    public class MemcachedServiceAppender implements Function<MemcachedServiceAppender.Request, Boolean> {        @Override        public Boolean apply(MemcachedServiceAppender.Request request) {            try {                return memcachedClient.append(request.key(), request.value()).get();            } catch (Exception e) {                logger.error("Append data to memcached failed. key {} value {} exception {}"                        , request.key(), request.value(), e.getMessage(), e);            }            return false;        }        @JsonClassDescription("append data to memcached api")        public record Request(@JsonPropertyDescription("key to memcached") String key                , @JsonPropertyDescription("value to memcached") Object value) {        }    }    public MemcachedServiceSetter setter() {        return setter;    }    public MemcachedServiceGetter getter() {        return getter;    }    public MemcachedServiceDeleter deleter() {        return deleter;    }    public MemcachedServiceReplacer replacer() {        return replacer;    }    public MemcachedServiceAppender appender() {        return appender;    }}

在MemcachedService中创建增删改查和追加的静态内部类,分别实现Function接口,接着在apply里面写我们的处理逻辑即可。这里我们已经编写好对应的逻辑,下面我们进行测试用例编写和执行

/* * Copyright 2024-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.alibaba.cloud.ai.toolcalling.memcached;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallAutoConfiguration;import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallConstants;import org.junit.jupiter.api.DisplayName;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;/** * @author dahua */@SpringBootTest(classes = { MemcachedAutoConfiguration.class, CommonToolCallAutoConfiguration.class })@DisplayName("memcached tool call Test")class MemcachedTest {    private static final Logger logger = LoggerFactory.getLogger(MemcachedTest.class);    @Autowired    private MemcachedService memcachedService;    @Test    @DisplayName("Tool-Calling Test Memcached")    void testMemcached() {       testMemcachedSetter();       testMemcachedGetter();       testMemcachedReplacer();       testMemcachedGetter();       testMemcachedAppender();       testMemcachedGetter();       testMemcachedDeleter();       testMemcachedGetter();    }    @Test    @DisplayName("Tool-Calling Test Memcached Setter")    @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    void testMemcachedSetter() {       Boolean apply = memcachedService.setter()          .apply(new MemcachedService.MemcachedServiceSetter.Request("memcachedKey", "memcachedValue", 0));       logger.info("set result: {}", apply);    }    @Test    @DisplayName("Tool-Calling Test Memcached Getter")    @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    void testMemcachedGetter() {       Object apply = memcachedService.getter()          .apply(new MemcachedService.MemcachedServiceGetter.Request("memcachedKey"));       logger.info("get result: {}", apply);    }    @Test    @DisplayName("Tool-Calling Test Memcached Deleter")    @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    void testMemcachedDeleter() {       Boolean apply = memcachedService.deleter()          .apply(new MemcachedService.MemcachedServiceDeleter.Request("memcachedKey"));       logger.info("delete result: {}", apply);    }    @Test    @DisplayName("Tool-Calling Test Memcached Replacer")    @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    void testMemcachedReplacer() {       Boolean apply = memcachedService.replacer()          .apply(new MemcachedService.MemcachedServiceReplacer.Request("memcachedKey", "memcachedValueNew", 60));       logger.info("replace result {}", apply);    }    @Test    @DisplayName("Tool-Calling Test Memcached Appender")    @EnabledIfEnvironmentVariable(named = MemcachedConstants.IP, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    @EnabledIfEnvironmentVariable(named = MemcachedConstants.PORT, matches = CommonToolCallConstants.NOT_BLANK_REGEX)    void testMemcachedAppender() {       Boolean apply = memcachedService.appender()          .apply(new MemcachedService.MemcachedServiceAppender.Request("memcachedKey", "memcachedValueAppender"));       logger.info("append result: {}", apply);    }}

执行testMemcached方法,打印日志如下代表组件编写的逻辑都成功了

好的,以上就是本期的全部内容,感谢观看!

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Spring AI Memcached 工具开发 Java 后端开发
相关文章