Skip to content

Java extension components (IScriptExtension) developer guide

1. Overview

This guide is for developers who need to expose custom capabilities to the platform script engine from Java code in a self-hosted lovrabet-runtime deployment.

The platform defines a standard IScriptExtension service provider interface (SPI). It lets you wrap any Java logic — Redis operations, internal microservice calls, specialized algorithms, and so on — into a "component" that JS scripts can call, without ever touching the script engine's internals.

2. Core interface

The core interface to implement is com.lovrabet.runtime.core.sdk.IScriptExtension.

Java
public interface IScriptExtension {
    /**
     * 定义您的组件类型名称。
     * 这个名称将作为 JS 脚本中 context.client.extension.execute 的第一个参数,是组件的唯一标识符。
     * @return 组件类型名 (e.g., "redis", "myCustomLogic")
     */
    String getComponentType();
    
    /**
     * 执行组件的具体动作。
     * @param action JS 调用时传入的“动作”名,用于区分同一组件下的不同操作。
     * @param params JS 调用时传入的参数对象,平台会自动将其转换为一个 Map。
     * @return 执行结果。任何可序列化的 Java 对象都将被自动转为 JS 对象。
     */
    Object invoke(String action, Map<String, Object> params);
}

3. Development steps

Step 1: Create the implementation class

In your Spring Boot project (typically the host application or a custom starter), create a new Java class that implements IScriptExtension. The class must be declared as a Spring Bean — the simplest way is the @Component annotation.

Java
import org.springframework.stereotype.Component;
import java.util.Map;

@Component // 关键:确保 Spring 能够扫描并注册这个 Bean
public class MyCustomComponent implements IScriptExtension {
    
    @Override
    public String getComponentType() {
        // ... 见步骤 2
        return null; 
    }

    @Override
    public Object invoke(String action, Map<String, Object> params) {
        // ... 见步骤 3
        return null;
    }
}

Step 2: Define the component type

Implement getComponentType() and return a globally unique string that identifies your component in JS. Keep the name clear and concise, in camelCase (for example, myCustomLogic).

Java
@Override
public String getComponentType() {
    return "myCustomLogic"; // JS 中将通过 "myCustomLogic" 调用到这里
}

Step 3: Implement the core logic

Write your core business logic in invoke, dispatching on the incoming action and params.

Java
@Override
public Object invoke(String action, Map<String, Object> params) {
    // 使用 action 参数来分发不同的子任务
    if ("sayHello".equals(action)) {
        // 从 params Map 中安全地获取参数
        String name = (String) params.getOrDefault("name", "World");
        return "Hello, " + name + "!";
    }
    
    if ("calculate".equals(action)) {
        Number a = (Number) params.get("a");
        Number b = (Number) params.get("b");
        if (a == null || b == null) {
            throw new IllegalArgumentException("参数 'a' 和 'b' 不能为空");
        }
        return a.doubleValue() + b.doubleValue();
    }

    // 如果 action 不被支持,抛出异常
    throw new UnsupportedOperationException("不支持的动作: " + action);
}

Calling it from JS:

JavaScript
// 调用 sayHello
const message = context.client.extension.execute("myCustomLogic", "sayHello", { name: "开发者" });
// message -> "Hello, 开发者!"

// 调用 calculate
const sum = context.client.extension.execute("myCustomLogic", "calculate", { a: 10, b: 20 });
// sum -> 30.0

4. Key principles and best practices

4.1 Statelessness and thread safety

@Component produces a singleton bean by default, and your IScriptExtension implementation will be called concurrently by multiple script-execution threads. The implementation must therefore be stateless and thread-safe. Never use instance (member) variables to hold per-request state.

Java
@Component
public class BadExampleComponent implements IScriptExtension {

    private int count = 0; // 错误!这是一个非线程安全的实例变量

    public Object invoke(String action, Map<String, Object> params) {
        this.count++; // 在并发下会导致数据错乱
        return this.count;
    }
    // ...
}

4.2 Dependency injection

Feel free to @Autowired any other Spring Bean in your project — Services, Repositories, RestTemplate, RedisTemplate, and more.

Java
@Component
public class UserComponent implements IScriptExtension {

    @Autowired
    private UserService userService; // 正确:注入其他 Spring Bean

    @Override
    public String getComponentType() { return "user"; }

    @Override
    public Object invoke(String action, Map<String, Object> params) {
        if ("findById".equals(action)) {
            Long userId = ((Number) params.get("id")).longValue();
            return userService.findById(userId); // 调用已有业务逻辑
        }
        // ...
    }
}

4.3 Exception handling

Any Java exception thrown from invokeRuntimeException or checked — is caught by the platform's core execution engine. The exception's message is extracted and rethrown on the JS side inside the catch block.

Throw exceptions with clear business meaning, such as IllegalArgumentException or your own business exceptions.

4.4 Performance

execute runs on an isolated thread pool with a timeout. If your code runs too long (over 3 seconds by default), the platform interrupts it and throws a timeout exception to JS. Keep the implementation efficient and avoid long-blocking operations.

5. Deployment

No special deployment configuration is needed. As long as the class implementing IScriptExtension with the @Component annotation is on the classpath of the Spring Boot application you actually run (for example, as a regular business class or inside a dependency JAR), the platform's IScriptExtension scanning discovers and registers it automatically at startup.

基于飞书知识库同步生成,内容以飞书源文档为准