LangChain 工具调用(Tool Calling)

一、简介

LangChain 工具调用(Tool Calling) 是让大模型(LLM)连接外部世界、执行实际操作(搜索、计算、读写文件、调用 API 等)的核心机制,也是构建智能 Agent 的基础能力。LangChain 1.0 对工具调用做了标准化重构,API 更简洁、兼容性更强、稳定性更高。模型不直接执行代码,而是输出一个结构化的调用请求(包含函数名和参数),由应用程序负责执行并将结果返回给模型。

1.1 核心概念

  • 工具(Tool):封装了特定功能的可调用对象(函数 / API),包含名称、描述、参数 Schema、执行逻辑。
  • 工具调用(Tool Calling):模型根据用户意图,自主选择工具、生成符合 Schema 的参数,并由框架执行、返回结果的流程。
  • 等价术语:函数调用(Function Calling)、结构化工具调用。

1.2 核心价值

LangChain 的工具调用系统提供了:

  1. 统一抽象:通过 @tool 装饰器定义,bind_tools() 绑定
  2. 扩展能力:让 LLM 突破 "文本生成",能操作外部系统
  3. 多提供商支持:OpenAI、Anthropic、Gemini、本地模型等
  4. 自动 Schema 生成:从函数签名和文档字符串自动生成 JSON Schema
  5. 并行调用:支持单次响应多个工具调用
  6. 流式支持:逐步返回工具调用参数
  7. 深度集成:与 LangGraph 无缝配合,构建复杂 Agent

二、工具调用与函数调用的区别

在 LangChain 中,工具调用(Tool Calling) 和 函数调用(Function Calling) 是两个密切相关但又有本质区别的概念。简单来说:函数调用是工具调用的底层技术基础,而工具调用是 LangChain 对函数调用的高级封装。

2.1 核心定义与关系

  • 函数调用 (Function Calling)

    • 起源:源自 OpenAI 于 2023 年推出的原生 API 功能(functions/tools 参数),是 LLM 厂商定义的模型能力。
    • 本质:让模型输出结构化的 JSON 指令(包含函数名、参数),而非自然语言。模型只负责 "决定调用" 和 "生成参数",不负责执行。
    • 狭义:特指单个函数的结构化调用协议。
  • 工具调用 (Tool Calling)

    • 起源:LangChain 对 "函数调用" 能力的框架级封装与扩展。
    • 本质:LangChain 中的 Tool = 一个带元数据的函数。它包含:
      • 执行函数(Python 代码、API 调用等)
      • Schema 元数据(名称、描述、参数 JSON Schema)
    • 广义:LangChain 统一了各大厂商(OpenAI, Anthropic, Gemini 等)的函数调用接口,不仅支持函数,还支持多工具、并行调用、Agent 智能路由等复杂场景。

2.2 核心区别对比表

维度 函数调用 (Function Calling) 工具调用 (Tool Calling)
概念层级 模型层能力 (LLM 输出格式) 框架层能力 (LangChain 执行层)
核心对象 纯函数 / API 接口 Tool 对象(函数 + Schema + 生命周期)
定义方式 手动构造 JSON Schema @tool 装饰器 / 继承 BaseTool 类
模型支持 仅限支持 Function Calling 的模型 (GPT-3.5+/Claude 3+) 统一兼容所有厂商的工具调用协议
执行主体 开发者手动解析、执行 LangChain 自动解析、调度、执行、回传
能力边界 单函数、基础结构化输出 多工具、并行调用、工具组合、Agent 决策、错误处理
在 LangChain 中的角色 底层通信协议 上层业务接口(开发者主要使用)
开发者关注点 如何让模型输出正确格式 如何定义工具、绑定工具、处理执行结果

2.3 函数调用(Function Calling)详解

函数调用是 LLM 提供商(如 OpenAI、Anthropic、Google)原生提供的能力:模型在生成文本的同时,可以决定调用一个函数并返回结构化参数。

原生用法示例:

python 复制代码
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")

# 定义函数 schema
functions = [{
    "name": "get_weather",
    "description": "获取指定城市的天气",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "城市名称"},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
        },
        "required": ["city"]
    }
}]

# 绑定函数
llm_with_functions = llm.bind_functions(functions)

response = llm_with_functions.invoke("北京天气怎么样?")
print(response.additional_kwargs["function_call"])
# 输出: {'name': 'get_weather', 'arguments': '{"city": "北京"}'}

关键特点:

  • 返回值嵌入在 AIMessage.additional_kwargs["function_call"] 中
  • 参数是 JSON 字符串,需要手动 json.loads() 解析
  • 需要手动调用实际函数并处理结果

2.4 工具调用(Tool Calling)详解

工具调用是 LangChain 在函数调用基础上构建的统一抽象:将函数、API、RAG 检索器等包装为"工具",让 LLM 可以标准化的方式请求执行。

核心用法示例:

python 复制代码
from langchain.tools import tool
from langchain_openai import ChatOpenAI

# 定义工具(自动生成 schema)
@tool
def get_weather(city: str, unit: str = "celsius") -> str:
    """获取指定城市的天气"""
    return f"{city} 当前25度"

llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools([get_weather])

response = llm_with_tools.invoke("北京天气怎么样?")
print(response.tool_calls)
# 输出: [{'name': 'get_weather', 'args': {'city': '北京'}, 'id': 'call_xxx'}]

自动执行工具调用:

python 复制代码
from langgraph.prebuilt import create_react_agent

# 创建可以自动执行工具的 Agent
agent = create_react_agent(llm, [get_weather])
result = agent.invoke({"messages": [("user", "北京天气怎么样?")]})
# Agent 自动调用 get_weather 并将结果返回给 LLM 生成最终答案

关键特点:

  • 统一的 tool_calls 属性,跨模型一致
  • 自动处理参数解析(args 已是 dict)
  • 支持并行调用多个工具
  • 可与 LangGraph 的 ToolNode 无缝集成

2.5 关键概念澄清

  1. 模型不执行代码,只输出指令

    无论是 Function Calling 还是 Tool Calling,LLM 都不直接执行函数。

    • Step 1 (模型):生成 {"name": "get_weather", "parameters": {"city": "北京"}}
    • Step 2 (LangChain):解析指令 → 调用 get_weather("北京") → 获取结果
    • Step 3 (模型):将结果喂给模型,生成最终自然语言回答
  2. @tool 装饰器的作用

    这是 LangChain 最常用的工具定义方式,它做了 3 件事:

    1. 自动生成 Schema:从函数名、文档字符串、类型注解中提取信息,生成供模型理解的 JSON Schema。
    2. 标准化接口:将普通函数包装成统一的 Tool 对象,拥有 invoke() / ainvoke() 方法。
    3. 集成生态:可直接被 bind_tools() 绑定到模型,可被 Agent 自动调用。
  3. 为什么 LangChain 要区分?

    • 历史兼容:早期 LangChain 使用自定义的 Agent 提示词(ReAct 框架)让模型调用工具,没有依赖原生 Function Calling。
    • 生态统一:不同厂商(OpenAI, Anthropic)的 Function Calling 格式略有差异。LangChain 的 Tool Calling 提供了统一抽象,切换模型无需改代码。
    • 能力增强:原生 Function Calling 较简单,LangChain Tool 增加了异步执行、参数校验、状态管理、回调钩子等生产级特性。

三、工具调用完整工作流

bash 复制代码
1. 定义工具 → 2. 绑定工具到模型 → 3. 模型生成ToolCall →
4. 解析并执行工具 → 5. 返回ToolMessage → 6. 模型整理结果 → 7. 输出

3.1 定义工具

LangChain 提供了多种定义工具的方式。

3.1.1 使用 @tool 装饰器(推荐)

最简单的方式,从函数签名自动生成工具模式。

python 复制代码
from langchain.tools import tool

@tool
def calculate(expression: str) -> float:
    """计算数学表达式的结果。
    
    Args:
        expression: 有效的数学表达式,如 "2 + 3 * 4"
    """
    return eval(expression)

# 工具对象包含名称、描述、参数模式
print(calculate.name)        # "calculate"
print(calculate.description) # "计算数学表达式的结果。..."
print(calculate.args)        # 自动生成的 JSON Schema

3.1.2 BaseTool 子类(高度自定义)

适合复杂工具(异步、状态、错误处理)。

python 复制代码
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field

class WeatherInput(BaseModel):
    city: str = Field(..., description="城市名(中文/英文)")
    country: str = Field(default="CN", description="国家代码")

class GetWeatherTool(BaseTool):
    name: str = "get_weather"
    description: str = "获取指定城市实时天气"
    args_schema: type[BaseModel] = WeatherInput

    def _run(self, city: str, country: str="CN") -> str:
        """同步执行"""
        return f"{city}({country}):晴天,25℃"

    async def _arun(self, city: str, country: str="CN") -> str:
        """异步执行"""
        return self._run(city, country)

weather_tool = GetWeatherTool()

3.1.3 内置工具(开箱即用)

LangChain 社区提供大量预定义工具:

python 复制代码
# 搜索
from langchain_community.tools import DuckDuckGoSearchRun
search = DuckDuckGoSearchRun()

# 计算
from langchain_community.tools import CalculatorTool
calc = CalculatorTool()

# 维基百科/ArXiv
from langchain_community.tools import WikipediaQueryRun, ArxivQueryRun

# 文件操作
from langchain_community.tools import ReadFileTool, WriteFileTool

3.2 绑定工具到模型

使用 bind_tools() 方法将工具列表传给模型,让模型感知工具能力。其工作原理:

  • bind_tools() 在幕后将工具 schema 转换为模型 API 支持的格式(如 OpenAI 的 tools 参数)
  • 模型在生成回复时,会决定是否需要调用工具
python 复制代码
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o")

# 绑定单个工具
model_with_tools = model.bind_tools([calculate])

# 绑定多个工具
tools = [calculate, get_weather, web_search]
model_with_tools = model.bind_tools(tools)

# 强制调用特定工具(使用 tool_choice)
model_with_tool_choice = model.bind_tools(
    tools, 
    tool_choice={"type": "function", "function": {"name": "calculate"}}
)

3.3 模型调用与 ToolCall 解析

模型响应包含 tool_calls 字段(结构化调用指令)。

python 复制代码
# 用户输入
user_input = "计算3*4,并搜索'AI大模型最新进展'"

# 调用模型
response = llm_with_tools.invoke(user_input)

# 查看响应
print("模型响应:", response.content)  # 文本内容(空或提示)
print("工具调用:", response.tool_calls)  # 结构化调用指令

ToolCall 结构:

python 复制代码
[
    {
        "name": "multiply",
        "args": {"a": 3, "b": 4},
        "id": "call_xxx"  # 唯一ID
    },
    {
        "name": "search_web",
        "args": {"query": "AI大模型最新进展", "num_results": 5},
        "id": "call_yyy"
    }
]

3.4 执行工具并返回 ToolMessage

用 invoke() 执行工具,结果包装为 ToolMessage 传回模型LangChain。

python 复制代码
from langchain_core.messages import ToolMessage

# 执行所有工具调用
tool_messages = []
for tool_call in response.tool_calls:
    # 匹配工具
    tool_map = {t.name: t for t in tools}
    tool = tool_map[tool_call["name"]]
    # 执行
    result = tool.invoke(tool_call["args"])
    # 生成ToolMessage(必须带call_id)
    tool_messages.append(
        ToolMessage(content=str(result), tool_call_id=tool_call["id"])
    )

print("工具执行结果:", tool_messages)

3.5 多轮对话:模型整理最终结果

将 用户消息 + AI响应 + ToolMessage 传回模型,生成自然语言答案。

python 复制代码
# 构建完整消息历史
messages = [
    ("user", user_input),
    response,  # AI的ToolCall响应
    *tool_messages  # 工具结果
]

# 模型生成最终回答
final_response = llm_with_tools.invoke(messages)
print("最终答案:", final_response.content)

四、LangChain 1.0 高级特性

4.1 create_agent:自动工具调用(推荐)

1.0 新 API,内置完整工具调用循环,无需手动解析 / 执行。

python 复制代码
from langchain.agents import create_agent
from langchain_core.prompts import ChatPromptTemplate

# 1. 定义提示词
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是专业助手,可调用工具完成任务。"),
    ("user", "{input}"),
    ("agent", "{agent_scratchpad}")  # 工具调用历史占位
])

# 2. 创建Agent(自动处理工具调用)
agent = create_agent(
    model=llm,
    tools=tools,
    prompt=prompt
)

# 3. 直接调用(自动执行:调用→执行→总结)
result = agent.invoke({
    "input": "北京天气如何?计算123*456,再搜索'2026世界杯'"
})

print("最终结果:", result["output"])

4.2 并行工具调用

模型同时调用多个工具,框架并行执行(提升效率)。

python 复制代码
# 模型可能返回多个 tool_calls
response.tool_calls = [
    {"name": "get_weather", "args": {"city": "Beijing"}, "id": "call_1"},
    {"name": "get_weather", "args": {"city": "Shanghai"}, "id": "call_2"},
    {"name": "search", "args": {"query": "news"}, "id": "call_3"}
]

# 并行执行所有工具
import asyncio

async def execute_tool(tool_call):
    tool = tool_map[tool_call["name"]]
    return ToolMessage(
        content=tool(**tool_call["args"]),
        tool_call_id=tool_call["id"]
    )

results = await asyncio.gather(*[execute_tool(tc) for tc in response.tool_calls])

4.3 工具参数校验(Pydantic)

自动校验参数类型 / 范围,避免无效调用。

python 复制代码
from pydantic import Field, ValidationError

@tool
def get_weather(city: str=Field(..., min_length=2)) -> str:
    """城市名至少2字符"""
    return f"{city}:晴天"

# 错误调用会抛出ValidationError
try:
    get_weather.invoke({"city": "B"})
except ValidationError as e:
    print("参数错误:", e)

4.4 工具调用策略(ToolStrategy)

控制模型如何选择 / 调用工具:

python 复制代码
# 强制调用某工具
llm_forced = llm.bind_tools(tools, tool_choice={"type": "tool", "name": "search_web"})

# 禁止调用工具
llm_no_tool = llm.bind_tools(tools, tool_choice="none")

# 自动选择(默认)
llm_auto = llm.bind_tools(tools, tool_choice="auto")

4.5 异步工具调用(高性能)

python 复制代码
# 异步定义工具
@tool
async def async_search(query: str) -> str:
    """异步搜索"""
    return f"异步结果:{query}"

# 异步调用Agent
import asyncio
async def main():
    result = await agent.ainvoke({"input": "异步搜索AI"})
    print(result["output"])

asyncio.run(main())

4.6 工具元数据与运行时(ToolRuntime)

1.0 新特性:工具访问上下文、状态、配置。

python 复制代码
from langchain_core.tools import ToolRuntime

@tool
def user_info(runtime: ToolRuntime) -> str:
    """访问用户上下文(runtime对模型不可见)"""
    user_id = runtime.context.get("user_id", "unknown")
    return f"用户ID:{user_id}"

# 调用时传入上下文
result = user_info.invoke({}, runtime=ToolRuntime(context={"user_id": "123"}))

4.7 错误处理与重试

python 复制代码
from langchain_core.tools import ToolException

@tool
def fragile_tool(param: str) -> str:
    """可能失败的复杂操作"""
    try:
        # 可能抛出异常的操作
        result = dangerous_operation(param)
        return result
    except Exception as e:
        # 抛出 ToolException,模型可以理解错误信息
        raise ToolException(f"工具执行失败: {str(e)}")

# 在调用时捕获异常
try:
    result = fragile_tool.invoke({"param": "value"})
except ToolException as e:
    # 将错误信息返回给模型,模型可能会尝试修正参数
    error_message = ToolMessage(content=str(e), tool_call_id=tool_call_id)

4.8 超时控制

python 复制代码
import asyncio
from functools import wraps

def with_timeout(seconds: int):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            try:
                return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
            except asyncio.TimeoutError:
                return f"工具执行超时({seconds}秒)"
        return wrapper
    return decorator

@tool
@with_timeout(5)
async def slow_api_call(param: str) -> str:
    await asyncio.sleep(10)  # 模拟慢调用
    return "结果"

4.9 流式工具调用

python 复制代码
from langchain_core.tools import BaseTool
from typing import AsyncIterator

class StreamingTool(BaseTool):
    name = "streaming_calculator"
    description = "逐步显示计算过程"
    
    async def _arun(self, expression: str) -> AsyncIterator[str]:
        # 流式返回中间结果
        steps = expression.split('+')
        total = 0
        for step in steps:
            value = int(step.strip())
            total += value
            yield f"加上 {value},当前结果: {total}\n"
        yield f"最终结果: {total}"


# 流式调用时,工具调用信息在最后才完整出现
async for chunk in model_with_tools.astream(messages):
    # chunk 可能是文本增量或工具调用增量
    if chunk.tool_call_chunks:
        # 工具调用正在构建中
        print(f"工具调用片段: {chunk.tool_call_chunks}")
    elif chunk.content:
        print(chunk.content, end="")

五、最佳实践

5.1 工具设计原则

  1. 描述精准:docstring 清晰告诉模型 "工具能做什么"
  2. 参数简单:避免复杂嵌套结构,模型易理解
  3. 单一职责:一个工具只做一件事(如 "查天气"≠"查天气 + 发邮件")
  4. 错误处理:捕获异常、返回友好错误信息
  5. 幂等性:工具调用应可重复执行(尤其写操作)
  6. 超时控制:为外部调用设置合理超时
  7. 权限控制:敏感工具(删数据、转账)加中间件审核

5.2 必选优化配置

python 复制代码
# 1. 温度设为0(工具调用需精准)
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 2. 工具调用超时
from langchain_core.tools import tool
import time

@tool
def search_with_timeout(query: str) -> str:
    """搜索(5秒超时)"""
    import threading
    result = [None]
    def task():
        result[0] = f"搜索:{query}"
    t = threading.Thread(target=task)
    t.start()
    t.join(5)
    return result[0] if result[0] else "搜索超时"

# 3. 工具结果截断(避免Token超限)
@tool
def search_short(query: str) -> str:
    result = f"长文本结果..."
    return result[:1000] + "..."  # 截断

5.3 中间件增强(安全 / 监控)

结合 Middleware 实现工具调用管控:

python 复制代码
from langchain.agents.middleware import (
    HumanInTheLoopMiddleware,  # 敏感工具人工审核
    ToolRetryMiddleware,       # 失败重试
    TokenCounterMiddleware     # 调用统计
)

agent = create_agent(
    model=llm,
    tools=tools,
    prompt=prompt,
    middleware=[
        HumanInTheLoopMiddleware(tool_names=["delete_data", "transfer_money"]),
        ToolRetryMiddleware(max_retries=2),
        TokenCounterMiddleware()
    ]
)

5.4 提示词中的工具指导

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个助手,可以使用以下工具:
    - calculate: 计算数学表达式,参数 expression
    - get_weather: 获取天气,参数 city
    
    当用户需要计算时使用 calculate,需要天气时使用 get_weather。
    如果同时需要多个信息,可以并行调用多个工具。
    """),
    ("human", "{input}")
])

chain = prompt | model_with_tools

5.5 限制工具调用轮数

python 复制代码
class LimitedToolAgent:
    def __init__(self, model, tools, max_turns=3):
        self.model = model.bind_tools(tools)
        self.tools = {t.name: t for t in tools}
        self.max_turns = max_turns
    
    def invoke(self, input_text):
        messages = [HumanMessage(content=input_text)]
        for _ in range(self.max_turns):
            response = self.model.invoke(messages)
            if not response.tool_calls:
                return response.content
            # 执行工具
            for tool_call in response.tool_calls:
                result = self.tools[tool_call["name"]].invoke(tool_call["args"])
                messages.append(response)
                messages.append(ToolMessage(content=str(result), tool_call_id=tool_call["id"]))
        return "已达到最大工具调用次数,无法完成请求"

5.6 工具调用追踪

python 复制代码
from langchain.callbacks import BaseCallbackHandler

class ToolCallTracer(BaseCallbackHandler):
    def on_tool_start(self, serialized, input_str, **kwargs):
        print(f"[工具开始] {serialized['name']} - 参数: {input_str}")
    
    def on_tool_end(self, output, **kwargs):
        print(f"[工具结束] 输出: {output[:100]}...")

model = ChatOpenAI(callbacks=[ToolCallTracer()])

六、常见问题与解决方案

6.1 模型不调用工具

原因:描述不清晰或用户输入与工具不匹配。

解决:优化工具描述,或在系统提示中明确指导何时使用。

python 复制代码
@tool
def get_current_time() -> str:
    """获取当前日期和时间。当用户询问时间、日期、现在几点时使用此工具。"""
    return datetime.now().isoformat()

6.2 模型调用错误参数

解决:使用 Pydantic 模型进行严格验证,并提供清晰的参数描述。

python 复制代码
class SearchInput(BaseModel):
    query: str = Field(description="搜索关键词,如'Python教程'")
    max_results: int = Field(default=5, ge=1, le=10, description="结果数量,1-10之间")

6.3 工具结果太长超过上下文

解决:截断或总结工具结果。

python 复制代码
def truncate_result(result: str, max_tokens: int = 2000) -> str:
    if len(result) > max_tokens:
        return result[:max_tokens] + "...(结果已截断)"
    return result

# 在返回 ToolMessage 前截断
ToolMessage(content=truncate_result(long_result), tool_call_id=id)

6.4 工具循环(模型反复调用同一工具)

解决:在状态中记录已调用过的工具,或限制单轮调用次数。

python 复制代码
# 使用 LangGraph 时设置递归限制
app = graph.compile(checkpointer=memory, recursion_limit=10)

七、完整示例

python 复制代码
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain_core.prompts import ChatPromptTemplate

# 1. 定义工具
@tool
def add(a: int, b: int) -> int:
    """加法运算"""
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """乘法运算"""
    return a * b

@tool
def get_weather(city: str) -> str:
    """获取城市天气"""
    return f"{city}:多云,18-25℃"

# 2. 初始化模型
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [add, multiply, get_weather]

# 3. 创建Agent
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是全能助手,可调用工具精准完成任务。"),
    ("user", "{input}"),
    ("agent", "{agent_scratchpad}")
])

agent = create_agent(model=llm, tools=tools, prompt=prompt)

# 4. 执行复杂任务
result = agent.invoke({
    "input": "先算10+20,再乘3,最后查北京天气"
})

print("最终输出:", result["output"])
# 输出:10+20=30,30×3=90;北京天气:多云,18-25℃

八、总结

  1. Tool Calling = LLM + 外部能力,是 Agent 核心基础。
  2. 三步核心:定义工具 → 绑定模型 → 自动 / 手动执行。
  3. LangChain 1.0 优势:create_agent 简化循环、标准化 API、稳定可靠。
  4. 生产关键:精准描述、参数校验、错误处理、权限管控、中间件增强。
相关推荐
_MyFavorite_4 小时前
JAVA重点基础、进阶知识及易错点总结(28)接口默认方法与静态方法
java·开发语言·windows
Elastic 中国社区官方博客4 小时前
当 TSDS 遇到 ILM:设计不会拒绝延迟数据的时间序列数据流
大数据·运维·数据库·elasticsearch·搜索引擎·logstash
Omics Pro4 小时前
虚拟细胞:开启HIV/AIDS治疗新纪元的关键?
大数据·数据库·人工智能·深度学习·算法·机器学习·计算机视觉
helx825 小时前
SpringBoot中自定义Starter
java·spring boot·后端
_MyFavorite_5 小时前
JAVA重点基础、进阶知识及易错点总结(31)设计模式基础(单例、工厂)
java·开发语言·设计模式
沐风___5 小时前
Claude Code 权限模式完全指南:Auto、Bypass、Ask 三模式深度解析
大数据·elasticsearch·搜索引擎
ILYT NCTR5 小时前
SpringSecurity 实现token 认证
java
rleS IONS5 小时前
SpringBoot获取bean的几种方式
java·spring boot·后端
014-code6 小时前
Java SPI 实战:ServiceLoader 的正确打开方式(含类加载器坑)
java·开发语言
程序员榴莲6 小时前
Javase(七):继承
java