大家好,我是大花,这是我在掘金社区发的第一篇文章,主要讲述如何编写一个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依赖

xml
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
编写MemcachedAutoConfiguration配置类,这里注意需要在类的最开始填充协议内容
java
/*
* 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()));
}
}
配置类具体内容,同样需要注意需要在类的最开始填充协议内容
java
/*
* 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类
arduino
/*
* 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
less
@Bean
@ConditionalOnMissingBean
public MemcachedService memcachedService(MemcachedClient memcachedClient) {
return new MemcachedService(memcachedClient);
}
less
/*
* 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里面写我们的处理逻辑即可。这里我们已经编写好对应的逻辑,下面我们进行测试用例编写和执行
java
/*
* 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方法,打印日志如下代表组件编写的逻辑都成功了

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