從零開始設(shè)計(jì)基于SpringBoot的Serverless(本地函數(shù)計(jì)算)引擎
前言
最近突然冒出一個(gè)想法:能不能用SpringBoot自己實(shí)現(xiàn)一個(gè)類似AWS Lambda或阿里云函數(shù)計(jì)算的執(zhí)行引擎?
說干就干,于是從零開始設(shè)計(jì)了一套基于SpringBoot的Serverless執(zhí)行框架。
這套框架支持函數(shù)動(dòng)態(tài)加載、按需執(zhí)行、資源隔離,甚至還實(shí)現(xiàn)了簡(jiǎn)單的冷啟動(dòng)優(yōu)化。
今天分享給大家,看看如何用SpringBoot的強(qiáng)大能力,打造一個(gè)屬于自己的Serverless引擎。
設(shè)計(jì)思路
核心特性
我們要實(shí)現(xiàn)的Serverless引擎包含以下特性:
動(dòng)態(tài)函數(shù)加載:支持運(yùn)行時(shí)加載新的函數(shù)代碼
函數(shù)隔離執(zhí)行:每個(gè)函數(shù)在獨(dú)立的上下文中運(yùn)行
生命周期管理:自動(dòng)管理函數(shù)的創(chuàng)建、執(zhí)行和銷毀
資源限制:控制函數(shù)的執(zhí)行時(shí)間
函數(shù)調(diào)用:支持HTTP、定時(shí)器等多種觸發(fā)方式
監(jiān)控統(tǒng)計(jì):記錄函數(shù)執(zhí)行次數(shù)、耗時(shí)、成功率等指標(biāo)
架構(gòu)設(shè)計(jì)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Function API │ │ Event Trigger │ │ Management UI │
└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘
│ │ │
└──────────────────────┼──────────────────────┘
│
┌────────────┴──────────────┐
│ Serverless Engine │
└────────────┬──────────────┘
│
┌────────────────────────┼──────────────────────┐
│ │ │
┌───────▼───────┐ ┌───────────▼─────────┐ ┌───────▼───────┐
│ Function Pool │ │ Execution Manager │ │ Resource Pool │
└───────────────┘ └─────────────────────┘ └───────────────┘
核心實(shí)現(xiàn)
項(xiàng)目結(jié)構(gòu)
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ ├── ServerlessEngine.java
│ │ ├── core/
│ │ │ ├── FunctionManager.java
│ │ │ ├── ExecutionEngine.java
│ │ │ ├── ResourceManager.java
│ │ │ └── EventDispatcher.java
│ │ ├── model/
│ │ │ ├── ServerlessFunction.java
│ │ │ ├── ExecutionContext.java
│ │ │ ├── ExecutionResult.java
│ │ │ └── FunctionMetrics.java
│ │ ├── executor/
│ │ │ ├── FunctionExecutor.java
│ │ │ └── IsolatedClassLoader.java
│ │ ├── trigger/
│ │ │ ├── HttpTrigger.java
│ │ │ ├── TimerTrigger.java
│ │ │ └── EventTrigger.java
│ │ ├── api/
│ │ │ └── ServerlessController.java
│ └── resources/
│ ├── application.yml
│ └── functions/
│ ├── demo-function.jar
│ └── user-function.jar
函數(shù)接口定義
package com.example.model;
import java.util.Map;
/**
* Serverless函數(shù)接口
* 所有用戶函數(shù)都需要實(shí)現(xiàn)這個(gè)接口
*/
@FunctionalInterface
public interface ServerlessFunction {
/**
* 函數(shù)執(zhí)行入口
* @param input 輸入?yún)?shù)
* @param context 執(zhí)行上下文
* @return 執(zhí)行結(jié)果
*/
Object handle(Map<String, Object> input, ExecutionContext context) throws Exception;
}
執(zhí)行上下文
package com.example.model;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 函數(shù)執(zhí)行上下文
*/
public class ExecutionContext {
private String requestId;
private String functionName;
private String functionVersion;
private LocalDateTime startTime;
private long timeoutMs;
private Map<String, Object> environment;
private Map<String, Object> attributes;
public ExecutionContext(String requestId, String functionName) {
this.requestId = requestId;
this.functionName = functionName;
this.functionVersion = "1.0";
this.startTime = LocalDateTime.now();
this.timeoutMs = 30000; // 默認(rèn)30秒超時(shí)
this.environment = new ConcurrentHashMap<>();
this.attributes = new ConcurrentHashMap<>();
}
// 獲取剩余執(zhí)行時(shí)間
public long getRemainingTimeMs() {
long elapsed = System.currentTimeMillis() -
java.sql.Timestamp.valueOf(startTime).getTime();
return Math.max(0, timeoutMs - elapsed);
}
@Override
public String toString() {
return "ExecutionContext{" +
"requestId='" + requestId + ''' +
", functionName='" + functionName + ''' +
", functionVersion='" + functionVersion + ''' +
", startTime=" + startTime +
", timeoutMs=" + timeoutMs +
'}';
}
}
執(zhí)行結(jié)果
package com.example.model;
import java.time.LocalDateTime;
/**
* 函數(shù)執(zhí)行結(jié)果
*/
public class ExecutionResult {
private String requestId;
private String functionName;
private boolean success;
private Object result;
private String errorMessage;
private String errorType;
private LocalDateTime startTime;
private LocalDateTime endTime;
private long executionTime;
public ExecutionResult(String requestId, String functionName) {
this.requestId = requestId;
this.functionName = functionName;
this.startTime = LocalDateTime.now();
}
// 標(biāo)記執(zhí)行成功
public void markSuccess(Object result) {
this.success = true;
this.result = result;
this.endTime = LocalDateTime.now();
this.executionTime = calculateExecutionTime();
}
// 標(biāo)記執(zhí)行失敗
public void markFailure(String errorType, String errorMessage) {
this.success = false;
this.errorType = errorType;
this.errorMessage = errorMessage;
this.endTime = LocalDateTime.now();
this.executionTime = calculateExecutionTime();
}
// 計(jì)算執(zhí)行時(shí)間
private long calculateExecutionTime() {
if (startTime != null && endTime != null) {
return java.sql.Timestamp.valueOf(endTime).getTime() -
java.sql.Timestamp.valueOf(startTime).getTime();
}
return 0;
}
// Getter和Setter方法省略
@Override
public String toString() {
return "ExecutionResult{" +
"requestId='" + requestId + ''' +
", functionName='" + functionName + ''' +
", success=" + success +
", executionTime=" + executionTime +
'}';
}
}
函數(shù)指標(biāo)統(tǒng)計(jì)
package com.example.model;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/**
* 函數(shù)執(zhí)行指標(biāo)
*/
public class FunctionMetrics {
private String functionName;
private AtomicLong invocationCount = new AtomicLong(0);
private AtomicLong successCount = new AtomicLong(0);
private AtomicLong errorCount = new AtomicLong(0);
private AtomicLong totalExecutionTime = new AtomicLong(0);
private AtomicLong minExecutionTime = new AtomicLong(Long.MAX_VALUE);
private AtomicLong maxExecutionTime = new AtomicLong(0);
private AtomicReference<LocalDateTime> lastInvocation = new AtomicReference<>();
private AtomicReference<LocalDateTime> createTime = new AtomicReference<>(LocalDateTime.now());
public FunctionMetrics(String functionName) {
this.functionName = functionName;
}
// 記錄函數(shù)調(diào)用
public void recordInvocation(ExecutionResult result) {
invocationCount.incrementAndGet();
lastInvocation.set(LocalDateTime.now());
if (result.isSuccess()) {
successCount.incrementAndGet();
} else {
errorCount.incrementAndGet();
}
long executionTime = result.getExecutionTime();
totalExecutionTime.addAndGet(executionTime);
// 更新最小執(zhí)行時(shí)間
minExecutionTime.updateAndGet(current -> Math.min(current, executionTime));
// 更新最大執(zhí)行時(shí)間
maxExecutionTime.updateAndGet(current -> Math.max(current, executionTime));
}
// 獲取平均執(zhí)行時(shí)間
public double getAvgExecutionTime() {
long count = invocationCount.get();
if (count == 0) {
return 0.0;
}
return (double) totalExecutionTime.get() / count;
}
// 獲取成功率
public double getSuccessRate() {
long total = invocationCount.get();
if (total == 0) {
return 0.0;
}
return (double) successCount.get() / total * 100;
}
// 獲取錯(cuò)誤率
public double getErrorRate() {
return 100.0 - getSuccessRate();
}
@Override
public String toString() {
return "FunctionMetrics{" +
"functionName='" + functionName + ''' +
", invocationCount=" + invocationCount.get() +
", successCount=" + successCount.get() +
", errorCount=" + errorCount.get() +
", avgExecutionTime=" + String.format("%.2f", getAvgExecutionTime()) +
", successRate=" + String.format("%.2f", getSuccessRate()) + "%" +
'}';
}
}
隔離類加載器
package com.example.executor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
/**
* 隔離類加載器
* 為每個(gè)函數(shù)提供獨(dú)立的類加載環(huán)境
*/
public class IsolatedClassLoader extends URLClassLoader {
private final String functionName;
private final Map<String, Class<?>> loadedClasses = new HashMap<>();
private final ClassLoader parentClassLoader;
public IsolatedClassLoader(String functionName, URL[] urls, ClassLoader parent) {
super(urls, parent);
this.functionName = functionName;
this.parentClassLoader = parent;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 檢查是否已經(jīng)加載過
Class<?> loadedClass = loadedClasses.get(name);
if (loadedClass != null) {
return loadedClass;
}
// 對(duì)于Java系統(tǒng)類,使用父類加載器
if (name.startsWith("java.") || name.startsWith("javax.") ||
name.startsWith("sun.") || name.startsWith("com.sun.")) {
return super.loadClass(name, resolve);
}
// 對(duì)于Spring相關(guān)類,使用父類加載器
if (name.startsWith("org.springframework.") ||
name.startsWith("org.apache.") ||
name.startsWith("com.fasterxml.")) {
return super.loadClass(name, resolve);
}
try {
// 嘗試自己加載類
Class<?> clazz = findClass(name);
loadedClasses.put(name, clazz);
if (resolve) {
resolveClass(clazz);
}
return clazz;
} catch (ClassNotFoundException e) {
// 如果找不到,使用父類加載器
return super.loadClass(name, resolve);
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
String path = name.replace('.', '/') + ".class";
InputStream is = getResourceAsStream(path);
if (is == null) {
throw new ClassNotFoundException(name);
}
byte[] classData = readClassData(is);
return defineClass(name, classData, 0, classData.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
private byte[] readClassData(InputStream is) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(data)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
}
public String getFunctionName() {
return functionName;
}
public int getLoadedClassCount() {
return loadedClasses.size();
}
@Override
public void close() throws IOException {
loadedClasses.clear();
super.close();
}
}
函數(shù)執(zhí)行器
package com.example.executor;
import com.example.model.ExecutionContext;
import com.example.model.ExecutionResult;
import com.example.model.ServerlessFunction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.*;
/**
* 函數(shù)執(zhí)行器
* 負(fù)責(zé)在隔離環(huán)境中執(zhí)行函數(shù)
*/
@Component
@Slf4j
public class FunctionExecutor {
@Autowired
private ClassLoaderPool classLoaderPool;
private final ExecutorService executorService;
public FunctionExecutor() {
// 創(chuàng)建線程池用于執(zhí)行函數(shù)
this.executorService = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r);
t.setName("function-executor-" + System.currentTimeMillis());
t.setDaemon(true);
return t;
});
}
/**
* 執(zhí)行函數(shù)
*/
public ExecutionResult execute(String functionName, String jarPath, String className,
Map<String, Object> input, ExecutionContext context) {
ExecutionResult result = new ExecutionResult(context.getRequestId(), functionName);
Future<Object> future = executorService.submit(() -> {
// 從池中獲取ClassLoader(不需要每次創(chuàng)建)
IsolatedClassLoader classLoader = classLoaderPool.getClassLoader(
functionName, jarPath, className);
// 加載函數(shù)類
Class<?> functionClass = classLoader.loadClass(className);
Object functionInstance = functionClass.getDeclaredConstructor().newInstance();
// 檢查是否實(shí)現(xiàn)了ServerlessFunction接口
if (!(functionInstance instanceof ServerlessFunction)) {
throw new IllegalArgumentException(
"Function class must implement ServerlessFunction interface");
}
ServerlessFunction function = (ServerlessFunction) functionInstance;
// 執(zhí)行函數(shù)
return function.handle(input, context);
});
try {
// 等待執(zhí)行結(jié)果,支持超時(shí)
Object functionResult = future.get(context.getTimeoutMs(), TimeUnit.MILLISECONDS);
result.markSuccess(functionResult);
} catch (TimeoutException e) {
future.cancel(true);
result.markFailure("TIMEOUT", "Function execution timeout");
} catch (ExecutionException e) {
Throwable cause = e.getCause();
log.error(cause.getMessage(),cause);
result.markFailure(
cause.getClass().getSimpleName(),
cause.getMessage()
);
} catch (Exception e) {
result.markFailure(
e.getClass().getSimpleName(),
e.getMessage()
);
}
return result;
}
/**
* 關(guān)閉執(zhí)行器
*/
public void shutdown() {
executorService.shutdown();
try {
if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
函數(shù)管理器
package com.example.core;
import com.example.model.FunctionMetrics;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 函數(shù)管理器
* 負(fù)責(zé)函數(shù)的注冊(cè)、查找、生命周期管理
*/
@Component
public class FunctionManager {
// 函數(shù)注冊(cè)表
private final Map<String, FunctionDefinition> functions = new ConcurrentHashMap<>();
// 函數(shù)指標(biāo)
private final Map<String, FunctionMetrics> metrics = new ConcurrentHashMap<>();
/**
* 函數(shù)定義
*/
public static class FunctionDefinition {
private String name;
private String description;
private String jarPath;
private String className;
private long timeoutMs;
private Map<String, Object> environment;
private Date createTime;
private Date updateTime;
public FunctionDefinition(String name, String jarPath, String className) {
this.name = name;
this.jarPath = jarPath;
this.className = className;
this.timeoutMs = 30000; // 默認(rèn)30秒
this.environment = new HashMap<>();
this.createTime = new Date();
this.updateTime = new Date();
}
// Getter和Setter方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getJarPath() { return jarPath; }
public void setJarPath(String jarPath) { this.jarPath = jarPath; }
public String getClassName() { return className; }
public void setClassName(String className) { this.className = className; }
public long getTimeoutMs() { return timeoutMs; }
public void setTimeoutMs(long timeoutMs) { this.timeoutMs = timeoutMs; }
public Map<String, Object> getEnvironment() { return environment; }
public void setEnvironment(Map<String, Object> environment) { this.environment = environment; }
public Date getCreateTime() { return createTime; }
public Date getUpdateTime() { return updateTime; }
public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
}
/**
* 注冊(cè)函數(shù)
*/
public void registerFunction(String name, String jarPath, String className) {
// 驗(yàn)證jar文件是否存在
File jarFile = new File(jarPath);
if (!jarFile.exists()) {
throw new IllegalArgumentException("JAR file not found: " + jarPath);
}
FunctionDefinition definition = new FunctionDefinition(name, jarPath, className);
functions.put(name, definition);
// 初始化指標(biāo)
metrics.put(name, new FunctionMetrics(name));
System.out.println("Function registered: " + name + " -> " + className);
}
/**
* 注冊(cè)函數(shù)(帶配置)
*/
public void registerFunction(String name, String jarPath, String className,
long timeoutMs, Map<String, Object> environment) {
registerFunction(name, jarPath, className);
FunctionDefinition definition = functions.get(name);
definition.setTimeoutMs(timeoutMs);
if (environment != null) {
definition.setEnvironment(new HashMap<>(environment));
}
}
/**
* 獲取函數(shù)定義
*/
public FunctionDefinition getFunction(String name) {
return functions.get(name);
}
/**
* 檢查函數(shù)是否存在
*/
public boolean functionExists(String name) {
return functions.containsKey(name);
}
/**
* 獲取所有函數(shù)名稱
*/
public Set<String> getAllFunctionNames() {
return new HashSet<>(functions.keySet());
}
/**
* 獲取所有函數(shù)定義
*/
public Collection<FunctionDefinition> getAllFunctions() {
return new ArrayList<>(functions.values());
}
/**
* 更新函數(shù)
*/
public void updateFunction(String name, String jarPath, String className) {
if (!functionExists(name)) {
throw new IllegalArgumentException("Function not found: " + name);
}
FunctionDefinition definition = functions.get(name);
definition.setJarPath(jarPath);
definition.setClassName(className);
definition.setUpdateTime(new Date());
System.out.println("Function updated: " + name);
}
/**
* 刪除函數(shù)
*/
public void removeFunction(String name) {
if (functions.remove(name) != null) {
metrics.remove(name);
System.out.println("Function removed: " + name);
}
}
/**
* 獲取函數(shù)指標(biāo)
*/
public FunctionMetrics getFunctionMetrics(String name) {
return metrics.get(name);
}
/**
* 獲取所有函數(shù)指標(biāo)
*/
public Collection<FunctionMetrics> getAllMetrics() {
return new ArrayList<>(metrics.values());
}
/**
* 清理所有函數(shù)
*/
public void clear() {
functions.clear();
metrics.clear();
}
/**
* 獲取函數(shù)數(shù)量
*/
public int getFunctionCount() {
return functions.size();
}
}
執(zhí)行引擎
package com.example;
import cn.hutool.core.io.FileUtil;
import com.example.core.FunctionManager;
import com.example.trigger.TimerTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.HashMap;
import java.util.Map;
/**
* Serverless引擎啟動(dòng)類
*/
@SpringBootApplication
@EnableScheduling
public class ServerlessEngine implements CommandLineRunner {
@Autowired
private FunctionManager functionManager;
@Autowired
private TimerTrigger timerTrigger;
public static void main(String[] args) {
FileUtil.writeBytes("123".getBytes(),"functions/function.txt");
SpringApplication.run(ServerlessEngine.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("=== Serverless Engine Started ===");
// 注冊(cè)示例函數(shù)
registerDemoFunctions();
// 注冊(cè)示例定時(shí)任務(wù)
registerDemoTimerTasks();
System.out.println("=== Demo Functions and Tasks Registered ===");
System.out.println("API available at: http://localhost:8080/serverless");
}
/**
* 注冊(cè)演示函數(shù)
*/
private void registerDemoFunctions() {
// 注冊(cè)Hello World函數(shù)
functionManager.registerFunction(
"hello-world",
"functions/demo-function.jar",
"com.example.functions.HelloWorldFunction"
);
// 注冊(cè)用戶服務(wù)函數(shù)
Map<String, Object> userEnv = new HashMap<>();
userEnv.put("DB_URL", "jdbc:h2:mem:testdb");
userEnv.put("MAX_USERS", "1000");
functionManager.registerFunction(
"user-service",
"functions/user-function.jar",
"com.example.functions.UserServiceFunction",
60000, // 60秒超時(shí)
userEnv
);
}
/**
* 注冊(cè)演示定時(shí)任務(wù)
*/
private void registerDemoTimerTasks() {
// 注冊(cè)清理任務(wù)
timerTrigger.registerTimerTask(
"cleanup-task",
"user-service",
"0 0 2 * * ?" // 每天凌晨2點(diǎn)執(zhí)行
);
// 注冊(cè)健康檢查任務(wù)
timerTrigger.registerTimerTask(
"health-check",
"hello-world",
"0/10 * * * * ?" // 每10秒執(zhí)行一次
);
}
}
HTTP觸發(fā)器
package com.example.trigger;
import com.example.core.ExecutionEngine;
import com.example.model.ExecutionResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* HTTP觸發(fā)器
* 處理HTTP請(qǐng)求觸發(fā)的函數(shù)調(diào)用
*/
@Component
public class HttpTrigger {
@Autowired
private ExecutionEngine executionEngine;
/**
* 處理HTTP請(qǐng)求
*/
public ExecutionResult handleRequest(String functionName, HttpServletRequest request,
Map<String, Object> body) {
// 構(gòu)建輸入?yún)?shù)
Map<String, Object> input = new HashMap<>();
// 添加HTTP相關(guān)信息
Map<String, Object> httpInfo = new HashMap<>();
httpInfo.put("method", request.getMethod());
httpInfo.put("path", request.getRequestURI());
httpInfo.put("queryString", request.getQueryString());
httpInfo.put("remoteAddr", request.getRemoteAddr());
httpInfo.put("userAgent", request.getHeader("User-Agent"));
// 添加請(qǐng)求頭
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}
}
httpInfo.put("headers", headers);
// 添加查詢參數(shù)
Map<String, String[]> queryParams = request.getParameterMap();
Map<String, Object> params = new HashMap<>();
queryParams.forEach((key, values) -> {
if (values.length == 1) {
params.put(key, values[0]);
} else {
params.put(key, values);
}
});
httpInfo.put("queryParams", params);
input.put("http", httpInfo);
// 添加請(qǐng)求體
if (body != null) {
input.put("body", body);
}
// 調(diào)用函數(shù)
return executionEngine.invoke(functionName, input);
}
/**
* 簡(jiǎn)化的GET請(qǐng)求處理
*/
public ExecutionResult handleGetRequest(String functionName, HttpServletRequest request) {
return handleRequest(functionName, request, null);
}
/**
* 簡(jiǎn)化的POST請(qǐng)求處理
*/
public ExecutionResult handlePostRequest(String functionName, HttpServletRequest request,
Map<String, Object> body) {
return handleRequest(functionName, request, body);
}
}
定時(shí)觸發(fā)器
package com.example.trigger;
import com.example.core.ExecutionEngine;
import com.example.model.ExecutionResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 定時(shí)觸發(fā)器
* 支持cron表達(dá)式定時(shí)觸發(fā)函數(shù)
*/
@Component
public class TimerTrigger {
@Autowired
private ExecutionEngine executionEngine;
// 定時(shí)任務(wù)注冊(cè)表
private final Map<String, TimerTask> timerTasks = new ConcurrentHashMap<>();
/**
* 定時(shí)任務(wù)定義
*/
public static class TimerTask {
private String name;
private String functionName;
private String cronExpression;
private boolean enabled;
private LocalDateTime lastExecution;
private LocalDateTime nextExecution;
private long executionCount;
public TimerTask(String name, String functionName, String cronExpression) {
this.name = name;
this.functionName = functionName;
this.cronExpression = cronExpression;
this.enabled = true;
this.executionCount = 0;
}
// Getter和Setter方法
public String getName() { return name; }
public String getFunctionName() { return functionName; }
public String getCronExpression() { return cronExpression; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public LocalDateTime getLastExecution() { return lastExecution; }
public void setLastExecution(LocalDateTime lastExecution) { this.lastExecution = lastExecution; }
public LocalDateTime getNextExecution() { return nextExecution; }
public void setNextExecution(LocalDateTime nextExecution) { this.nextExecution = nextExecution; }
public long getExecutionCount() { return executionCount; }
public void incrementExecutionCount() { this.executionCount++; }
}
/**
* 注冊(cè)定時(shí)任務(wù)
*/
public void registerTimerTask(String taskName, String functionName, String cronExpression) {
TimerTask task = new TimerTask(taskName, functionName, cronExpression);
timerTasks.put(taskName, task);
System.out.println("Timer task registered: " + taskName + " -> " + functionName + " (" + cronExpression + ")");
}
/**
* 移除定時(shí)任務(wù)
*/
public void removeTimerTask(String taskName) {
if (timerTasks.remove(taskName) != null) {
System.out.println("Timer task removed: " + taskName);
}
}
/**
* 啟用/禁用定時(shí)任務(wù)
*/
public void setTimerTaskEnabled(String taskName, boolean enabled) {
TimerTask task = timerTasks.get(taskName);
if (task != null) {
task.setEnabled(enabled);
System.out.println("Timer task " + taskName + " " + (enabled ? "enabled" : "disabled"));
}
}
/**
* 獲取所有定時(shí)任務(wù)
*/
public Map<String, TimerTask> getAllTimerTasks() {
return new HashMap<>(timerTasks);
}
/**
* 手動(dòng)執(zhí)行定時(shí)任務(wù)
*/
public ExecutionResult executeTimerTask(String taskName) {
TimerTask task = timerTasks.get(taskName);
if (task == null) {
throw new IllegalArgumentException("Timer task not found: " + taskName);
}
return executeTask(task);
}
/**
* 定時(shí)執(zhí)行 - 每分鐘檢查一次
*/
@Scheduled(fixedRate = 60000) // 每分鐘執(zhí)行一次
public void checkAndExecuteTimerTasks() {
LocalDateTime now = LocalDateTime.now();
timerTasks.values().stream()
.filter(TimerTask::isEnabled)
.forEach(task -> {
// 這里簡(jiǎn)化處理,實(shí)際應(yīng)該解析cron表達(dá)式
// 為了演示,我們每5分鐘執(zhí)行一次
if (task.getLastExecution() == null ||
task.getLastExecution().isBefore(now.minusMinutes(5))) {
executeTask(task);
}
});
}
/**
* 執(zhí)行定時(shí)任務(wù)
*/
private ExecutionResult executeTask(TimerTask task) {
// 構(gòu)建輸入?yún)?shù)
Map<String, Object> input = new HashMap<>();
Map<String, Object> timerInfo = new HashMap<>();
timerInfo.put("taskName", task.getName());
timerInfo.put("cronExpression", task.getCronExpression());
timerInfo.put("executionTime", LocalDateTime.now().toString());
timerInfo.put("executionCount", task.getExecutionCount());
input.put("timer", timerInfo);
// 執(zhí)行函數(shù)
ExecutionResult result = executionEngine.invoke(task.getFunctionName(), input);
// 更新任務(wù)信息
task.setLastExecution(LocalDateTime.now());
task.incrementExecutionCount();
System.out.println("Timer task executed: " + task.getName() +
" -> " + task.getFunctionName() +
", success: " + result.isSuccess());
return result;
}
}
Serverless控制器
package com.example.api;
import com.example.core.ExecutionEngine;
import com.example.core.FunctionManager;
import com.example.model.ExecutionResult;
import com.example.model.FunctionMetrics;
import com.example.trigger.HttpTrigger;
import com.example.trigger.TimerTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Serverless API控制器
*/
@RestController
@RequestMapping("/serverless")
public class ServerlessController {
@Autowired
private FunctionManager functionManager;
@Autowired
private ExecutionEngine executionEngine;
@Autowired
private HttpTrigger httpTrigger;
@Autowired
private TimerTrigger timerTrigger;
/**
* 調(diào)用函數(shù)
*/
@PostMapping("/functions/{functionName}/invoke")
public ResponseEntity<Map<String, Object>> invokeFunction(
@PathVariable String functionName,
@RequestBody(required = false) Map<String, Object> input,
HttpServletRequest request) {
ExecutionResult result = httpTrigger.handlePostRequest(functionName, request, input);
Map<String, Object> response = new HashMap<>();
response.put("requestId", result.getRequestId());
response.put("functionName", result.getFunctionName());
response.put("success", result.isSuccess());
response.put("executionTime", result.getExecutionTime());
response.put("memoryUsed", result.getMemoryUsed());
if (result.isSuccess()) {
response.put("result", result.getResult());
} else {
response.put("errorType", result.getErrorType());
response.put("errorMessage", result.getErrorMessage());
}
return ResponseEntity.ok(response);
}
/**
* GET方式調(diào)用函數(shù)
*/
@GetMapping("/functions/{functionName}/invoke")
public ResponseEntity<Map<String, Object>> invokeFunctionGet(
@PathVariable String functionName,
HttpServletRequest request) {
ExecutionResult result = httpTrigger.handleGetRequest(functionName, request);
Map<String, Object> response = new HashMap<>();
response.put("requestId", result.getRequestId());
response.put("functionName", result.getFunctionName());
response.put("success", result.isSuccess());
response.put("executionTime", result.getExecutionTime());
if (result.isSuccess()) {
response.put("result", result.getResult());
} else {
response.put("errorType", result.getErrorType());
response.put("errorMessage", result.getErrorMessage());
}
return ResponseEntity.ok(response);
}
/**
* 注冊(cè)函數(shù)
*/
@PostMapping("/functions/{functionName}")
public ResponseEntity<Map<String, String>> registerFunction(
@PathVariable String functionName,
@RequestBody Map<String, Object> config) {
String jarPath = (String) config.get("jarPath");
String className = (String) config.get("className");
Long timeoutMs = config.containsKey("timeoutMs") ?
((Number) config.get("timeoutMs")).longValue() : 30000L;
Long maxMemory = config.containsKey("maxMemory") ?
((Number) config.get("maxMemory")).longValue() : 128 * 1024 * 1024L;
@SuppressWarnings("unchecked")
Map<String, Object> environment = (Map<String, Object>) config.get("environment");
functionManager.registerFunction(functionName, jarPath, className,
timeoutMs, maxMemory, environment);
Map<String, String> response = new HashMap<>();
response.put("message", "Function registered successfully");
response.put("functionName", functionName);
return ResponseEntity.ok(response);
}
/**
* 獲取所有函數(shù)列表
*/
@GetMapping("/functions")
public ResponseEntity<Map<String, Object>> getAllFunctions() {
Collection<FunctionManager.FunctionDefinition> functions = functionManager.getAllFunctions();
Map<String, Object> response = new HashMap<>();
response.put("functions", functions);
response.put("count", functions.size());
return ResponseEntity.ok(response);
}
/**
* 獲取函數(shù)詳情
*/
@GetMapping("/functions/{functionName}")
public ResponseEntity<FunctionManager.FunctionDefinition> getFunctionDetail(
@PathVariable String functionName) {
FunctionManager.FunctionDefinition function = functionManager.getFunction(functionName);
if (function == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(function);
}
/**
* 刪除函數(shù)
*/
@DeleteMapping("/functions/{functionName}")
public ResponseEntity<Map<String, String>> deleteFunction(@PathVariable String functionName) {
functionManager.removeFunction(functionName);
Map<String, String> response = new HashMap<>();
response.put("message", "Function deleted successfully");
response.put("functionName", functionName);
return ResponseEntity.ok(response);
}
/**
* 獲取函數(shù)指標(biāo)
*/
@GetMapping("/functions/{functionName}/metrics")
public ResponseEntity<FunctionMetrics> getFunctionMetrics(@PathVariable String functionName) {
FunctionMetrics metrics = functionManager.getFunctionMetrics(functionName);
if (metrics == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(metrics);
}
/**
* 獲取所有函數(shù)指標(biāo)
*/
@GetMapping("/metrics")
public ResponseEntity<Map<String, Object>> getAllMetrics() {
Collection<FunctionMetrics> metrics = functionManager.getAllMetrics();
Map<String, Object> response = new HashMap<>();
response.put("metrics", metrics);
response.put("count", metrics.size());
return ResponseEntity.ok(response);
}
/**
* 注冊(cè)定時(shí)任務(wù)
*/
@PostMapping("/timer-tasks/{taskName}")
public ResponseEntity<Map<String, String>> registerTimerTask(
@PathVariable String taskName,
@RequestBody Map<String, String> config) {
String functionName = config.get("functionName");
String cronExpression = config.get("cronExpression");
timerTrigger.registerTimerTask(taskName, functionName, cronExpression);
Map<String, String> response = new HashMap<>();
response.put("message", "Timer task registered successfully");
response.put("taskName", taskName);
return ResponseEntity.ok(response);
}
/**
* 獲取所有定時(shí)任務(wù)
*/
@GetMapping("/timer-tasks")
public ResponseEntity<Map<String, Object>> getAllTimerTasks() {
Map<String, TimerTrigger.TimerTask> tasks = timerTrigger.getAllTimerTasks();
Map<String, Object> response = new HashMap<>();
response.put("tasks", tasks);
response.put("count", tasks.size());
return ResponseEntity.ok(response);
}
/**
* 手動(dòng)執(zhí)行定時(shí)任務(wù)
*/
@PostMapping("/timer-tasks/{taskName}/execute")
public ResponseEntity<Map<String, Object>> executeTimerTask(@PathVariable String taskName) {
ExecutionResult result = timerTrigger.executeTimerTask(taskName);
Map<String, Object> response = new HashMap<>();
response.put("requestId", result.getRequestId());
response.put("success", result.isSuccess());
response.put("executionTime", result.getExecutionTime());
if (result.isSuccess()) {
response.put("result", result.getResult());
} else {
response.put("errorType", result.getErrorType());
response.put("errorMessage", result.getErrorMessage());
}
return ResponseEntity.ok(response);
}
/**
* 系統(tǒng)狀態(tài)
*/
@GetMapping("/status")
public ResponseEntity<Map<String, Object>> getSystemStatus() {
Map<String, Object> status = new HashMap<>();
// 系統(tǒng)信息
Runtime runtime = Runtime.getRuntime();
status.put("totalMemory", runtime.totalMemory());
status.put("freeMemory", runtime.freeMemory());
status.put("usedMemory", runtime.totalMemory() - runtime.freeMemory());
status.put("maxMemory", runtime.maxMemory());
status.put("availableProcessors", runtime.availableProcessors());
// 函數(shù)統(tǒng)計(jì)
status.put("functionCount", functionManager.getFunctionCount());
status.put("timerTaskCount", timerTrigger.getAllTimerTasks().size());
// 總執(zhí)行次數(shù)
long totalInvocations = functionManager.getAllMetrics().stream()
.mapToLong(FunctionMetrics::getInvocationCount)
.sum();
status.put("totalInvocations", totalInvocations);
return ResponseEntity.ok(status);
}
}
主啟動(dòng)類
package com.example;
import cn.hutool.core.io.FileUtil;
import com.example.core.FunctionManager;
import com.example.trigger.TimerTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.HashMap;
import java.util.Map;
/**
* Serverless引擎啟動(dòng)類
*/
@SpringBootApplication
@EnableScheduling
public class ServerlessEngine implements CommandLineRunner {
@Autowired
private FunctionManager functionManager;
@Autowired
private TimerTrigger timerTrigger;
public static void main(String[] args) {
FileUtil.writeBytes("123".getBytes(),"functions/function.txt");
SpringApplication.run(ServerlessEngine.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("=== Serverless Engine Started ===");
// 注冊(cè)示例函數(shù)
registerDemoFunctions();
// 注冊(cè)示例定時(shí)任務(wù)
registerDemoTimerTasks();
System.out.println("=== Demo Functions and Tasks Registered ===");
System.out.println("API available at: http://localhost:8080/serverless");
}
/**
* 注冊(cè)演示函數(shù)
*/
private void registerDemoFunctions() {
// 注冊(cè)Hello World函數(shù)
functionManager.registerFunction(
"hello-world",
"functions/demo-function.jar",
"com.example.functions.HelloWorldFunction"
);
// 注冊(cè)用戶服務(wù)函數(shù)
Map<String, Object> userEnv = new HashMap<>();
userEnv.put("DB_URL", "jdbc:h2:mem:testdb");
userEnv.put("MAX_USERS", "1000");
functionManager.registerFunction(
"user-service",
"functions/user-function.jar",
"com.example.functions.UserServiceFunction",
60000, // 60秒超時(shí)
userEnv
);
}
/**
* 注冊(cè)演示定時(shí)任務(wù)
*/
private void registerDemoTimerTasks() {
// 注冊(cè)清理任務(wù)
timerTrigger.registerTimerTask(
"cleanup-task",
"user-service",
"0 0 2 * * ?" // 每天凌晨2點(diǎn)執(zhí)行
);
// 注冊(cè)健康檢查任務(wù)
timerTrigger.registerTimerTask(
"health-check",
"hello-world",
"0/10 * * * * ?" // 每10秒執(zhí)行一次
);
}
}
配置文件
# application.yml
server:
port: 8080
spring:
application:
name: serverless-engine
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
# Serverless引擎配置
serverless:
function:
# 函數(shù)存儲(chǔ)目錄
function-dir: ./functions/
# 默認(rèn)超時(shí)時(shí)間(毫秒)
default-timeout: 30000
# 最大并發(fā)執(zhí)行數(shù)
max-concurrent-executions: 100
executor:
# 核心線程數(shù)
core-pool-size: 10
# 最大線程數(shù)
max-pool-size: 50
# 線程存活時(shí)間(秒)
keep-alive-time: 60
# 隊(duì)列容量
queue-capacity: 1000
logging:
level:
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
management:
endpoints:
web:
exposure:
include: health,info,metrics,env
endpoint:
health:
show-details: always
示例函數(shù)
Hello World函數(shù)
package com.example.functions;
import com.example.model.ExecutionContext;
import com.example.model.ServerlessFunction;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* Hello World示例函數(shù)
*/
public class HelloWorldFunction implements ServerlessFunction {
@Override
public Object handle(Map<String, Object> input, ExecutionContext context) throws Exception {
Map<String, Object> result = new HashMap<>();
result.put("message", "Hello from Serverless Engine!");
result.put("timestamp", LocalDateTime.now().toString());
result.put("requestId", context.getRequestId());
result.put("functionName", context.getFunctionName());
result.put("input", input);
// 模擬一些處理時(shí)間
Thread.sleep(100);
return result;
}
}
用戶服務(wù)函數(shù)
package com.example.functions;
import com.example.model.ExecutionContext;
import com.example.model.ServerlessFunction;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 用戶服務(wù)示例函數(shù)
*/
public class UserServiceFunction implements ServerlessFunction {
// 模擬用戶存儲(chǔ)
private static final Map<Long, Map<String, Object>> users = new ConcurrentHashMap<>();
private static final AtomicLong idGenerator = new AtomicLong(1);
static {
// 初始化一些測(cè)試數(shù)據(jù)
Map<String, Object> user1 = new HashMap<>();
user1.put("id", 1L);
user1.put("name", "John Doe");
user1.put("email", "john@example.com");
users.put(1L, user1);
Map<String, Object> user2 = new HashMap<>();
user2.put("id", 2L);
user2.put("name", "Jane Smith");
user2.put("email", "jane@example.com");
users.put(2L, user2);
idGenerator.set(3);
}
@Override
public Object handle(Map<String, Object> input, ExecutionContext context) throws Exception {
String action = (String) ((Map)input.get("body")).get("action");
if (action == null) {
action = "list";
}
Map<String, Object> result = new HashMap<>();
switch (action.toLowerCase()) {
case "list":
result.put("users", users.values());
result.put("count", users.size());
break;
case "get":
Long userId = Long.valueOf(input.get("userId").toString());
Map<String, Object> user = users.get(userId);
if (user != null) {
result.put("user", user);
} else {
result.put("error", "User not found");
}
break;
case "create":
@SuppressWarnings("unchecked")
Map<String, Object> userData = (Map<String, Object>) ((Map)input.get("body")).get("user");
Long newId = idGenerator.getAndIncrement();
userData.put("id", newId);
users.put(newId, userData);
result.put("user", userData);
result.put("message", "User created successfully");
break;
case "delete":
Long deleteId = Long.valueOf(input.get("userId").toString());
Map<String, Object> deletedUser = users.remove(deleteId);
if (deletedUser != null) {
result.put("message", "User deleted successfully");
} else {
result.put("error", "User not found");
}
break;
default:
result.put("error", "Unknown action: " + action);
}
result.put("action", action);
result.put("timestamp", System.currentTimeMillis());
return result;
}
}
功能測(cè)試
#!/bin/bash
# test-serverless-engine.sh
BASE_URL="http://localhost:8080/serverless"
echo "=== Testing Serverless Engine ==="
# 1. 獲取系統(tǒng)狀態(tài)
echo "1. Getting system status..."
curl -s "${BASE_URL}/status" | jq '.'
echo
# 2. 獲取所有函數(shù)
echo "2. Getting all functions..."
curl -s "${BASE_URL}/functions" | jq '.'
echo
# 3. 調(diào)用Hello World函數(shù)
echo "3. Invoking hello-world function..."
curl -s -X POST "${BASE_URL}/functions/hello-world/invoke" \
-H "Content-Type: application/json" \
-d '{"name": "Serverless Test"}' | jq '.'
echo
# 4. 調(diào)用用戶服務(wù)函數(shù) - 列出用戶
echo "4. Invoking user-service function - list users..."
curl -s -X POST "${BASE_URL}/functions/user-service/invoke" \
-H "Content-Type: application/json" \
-d '{"action": "list"}' | jq '.'
echo
# 5. 調(diào)用用戶服務(wù)函數(shù) - 創(chuàng)建用戶
echo "5. Invoking user-service function - create user..."
curl -s -X POST "${BASE_URL}/functions/user-service/invoke" \
-H "Content-Type: application/json" \
-d '{
"action": "create",
"user": {
"name": "Bob Wilson",
"email": "bob@example.com"
}
}' | jq '.'
echo
# 6. 獲取函數(shù)指標(biāo)
echo "6. Getting function metrics..."
curl -s "${BASE_URL}/metrics" | jq '.'
echo
# 7. 獲取定時(shí)任務(wù)
echo "7. Getting timer tasks..."
curl -s "${BASE_URL}/timer-tasks" | jq '.'
echo
echo "=== Test Completed ==="
Maven配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>serverless-engine</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>SpringBoot Serverless Engine</name>
<description>A serverless execution engine built with SpringBoot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Jackson for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>
總結(jié)
通過SpringBoot,我們成功實(shí)現(xiàn)了一個(gè)功能完整的Serverless執(zhí)行引擎。這個(gè)引擎具備了以下核心能力:
核心特性
函數(shù)隔離:每個(gè)函數(shù)在獨(dú)立的類加載器中運(yùn)行
生命周期管理:自動(dòng)管理函數(shù)的創(chuàng)建、執(zhí)行和銷毀
多種觸發(fā)方式:支持HTTP和定時(shí)器觸發(fā)
監(jiān)控統(tǒng)計(jì):完整的執(zhí)行指標(biāo)和性能統(tǒng)計(jì)
RESTful API:完整的管理和調(diào)用接口
技術(shù)亮點(diǎn)
動(dòng)態(tài)類加載:使用自定義ClassLoader實(shí)現(xiàn)函數(shù)隔離
異步執(zhí)行:基于線程池的并發(fā)執(zhí)行機(jī)制
資源控制:支持超時(shí)和內(nèi)存限制
指標(biāo)收集:實(shí)時(shí)統(tǒng)計(jì)函數(shù)執(zhí)行情況
這套自研的Serverless引擎展示了SpringBoot強(qiáng)大的擴(kuò)展能力,不僅能快速構(gòu)建業(yè)務(wù)應(yīng)用,還能打造底層基礎(chǔ)設(shè)施。
以上就是從零開始設(shè)計(jì)基于SpringBoot的Serverless(本地函數(shù)計(jì)算)引擎的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Serverless引擎的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java簡(jiǎn)易計(jì)算器程序設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易計(jì)算器程序設(shè)計(jì)的相關(guān)參考資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-10-10
SpringBoot請(qǐng)求響應(yīng)方式示例詳解
這篇文章主要介紹了SpringBoot請(qǐng)求響應(yīng)的相關(guān)操作,本文通過示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-06-06
搭建簡(jiǎn)單的Spring-Data JPA項(xiàng)目
本文主要介紹了搭建簡(jiǎn)單的Spring-Data JPA項(xiàng)目,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
聊聊@RequestParam,@PathParam,@PathVariable等注解的區(qū)別
這篇文章主要介紹了聊聊@RequestParam,@PathParam,@PathVariable等注解的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Spring如何使用PropertyPlaceholderConfigurer讀取文件
這篇文章主要介紹了Spring如何使用PropertyPlaceholderConfigurer讀取文件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Java編程實(shí)現(xiàn)打地鼠文字游戲?qū)嵗a
這篇文章主要介紹了Java編程實(shí)現(xiàn)打地鼠文字游戲?qū)嵗a,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-11-11
java設(shè)計(jì)模式之裝飾模式詳細(xì)介紹
這篇文章主要介紹了java設(shè)計(jì)模式之裝飾模式,有需要的朋友可以參考一下2013-12-12
探討Java 將Markdown文件轉(zhuǎn)換為Word和PDF文檔
這篇文章主要介紹了Java 將Markdown文件轉(zhuǎn)換為Word和PDF文檔,本文通過分步指南及代碼示例展示了如何將 Markdown 文件轉(zhuǎn)換為 Word 文檔和 PDF 文件,需要的朋友可以參考下2024-07-07

