MiniSpring框架学习笔记-动态代理:如何在运行时插入逻辑?

MiniSpring框架学习笔记-动态代理:如何在运行时插入逻辑?

教程: https://github.com/YaleGuo/minis

极客时间: 手把手带你写一个 MiniSpring

17. 动态代理:如何在运行时插入逻辑?

前面几章里,MiniSpring 已经有了 IoC、MVC、JDBC 模板和 SQL 配置化。接下来如果想继续理解 Spring 里的 AOP,就需要先补一块很重要的前置知识:代理,尤其是 JDK 动态代理。

AOP 是面向切面编程,全称是 Aspect Oriented Programming。它想解决的问题很直观:日志、权限检查、事务控制、性能监控这类代码,通常会散落在很多业务方法前后。如果每个业务方法都手写一遍,代码会越来越乱。

AOP 的核心想法是:

text 复制代码
业务方法只写业务逻辑
        ↓
日志、权限、事务等通用逻辑抽出来
        ↓
程序运行时,把这些通用逻辑插到业务方法前后

这一节先不做完整 AOP,只讲它最关键的一种实现手段:动态代理。

本节主线是:

text 复制代码
先理解代理模式
        ↓
再看 JDK 动态代理怎么用
        ↓
最后把动态代理接入 MiniSpring 容器

一、先理解代理是什么

代理的意思是:调用方不直接访问真实对象,而是先访问一个代理对象,再由代理对象去调用真实对象。

用生活里的例子看:

text 复制代码
你
 ↓
房屋中介
 ↓
房东

你真正想找的是房东,但中介可以在中间做一些额外的事情,比如预约看房、准备合同、沟通价格。放到代码里也是一样,业务对象负责真正的业务,代理对象负责在调用前后加一些通用逻辑。

假设我们有一个业务接口:

java 复制代码
package com.test.service;

public interface IAction {

    void doAction();
}

真实业务类只关心业务本身:

java 复制代码
package com.test.service;

public class Action1 implements IAction {

    @Override
    public void doAction() {
        System.out.println("do real action");
    }
}

如果用静态代理,可以手写一个代理类:

java 复制代码
package com.test.service;

/**
 * 静态代理:代理类提前写好,并且和真实类实现同一个接口。
 */
public class ActionProxy implements IAction {

    private final IAction target;

    public ActionProxy(IAction target) {
        this.target = target;
    }

    @Override
    public void doAction() {
        System.out.println("before call real object");
        this.target.doAction();
    }
}

它们的关系可以画成这样:

text 复制代码
                IAction
                  ^
                  |
        +---------+---------+
        |                   |
     Action1           ActionProxy
  真实业务对象          静态代理对象
                            |
                            | target
                            v
                         Action1

调用方只面向 IAction

java 复制代码
IAction action = new ActionProxy(new Action1());
action.doAction();

静态代理很好理解,但缺点也明显:如果很多类都要加日志、事务、权限检查,我们就要为很多真实类手写对应的代理类。通用逻辑还没真正通用起来。

所以我们需要动态代理:代理类不用提前手写,而是在程序运行期间由 JVM 动态生成。

二、JDK 动态代理怎么用

JDK 提供的动态代理入口是:

java 复制代码
Proxy.newProxyInstance(
        类加载器,
        要代理的接口数组,
        方法调用处理器
)

三个参数分别表示:

参数 作用
ClassLoader 用哪个类加载器生成代理类
Class<?>[] interfaces 代理对象要实现哪些接口
InvocationHandler 代理对象的方法被调用时,统一交给谁处理

重点是第三个参数 InvocationHandler。只要调用代理对象的方法,JDK 都会转进它的 invoke(...) 方法:

java 复制代码
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

可以把调用链理解成:

text 复制代码
调用 proxyAction.doAction()
        ↓
进入 InvocationHandler.invoke(...)
        ↓
执行增强逻辑
        ↓
通过反射调用 target.doAction()

先写一个最直接的动态代理类:

java 复制代码
package com.test.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 教学版动态代理。
 *
 * 传入一个真实对象,返回一个实现相同接口的代理对象。
 */
public class DynamicProxy {

    private final Object target;

    public DynamicProxy(Object target) {
        if (target == null) {
            throw new IllegalArgumentException("Target must not be null");
        }
        this.target = target;
    }

    public Object getProxy() {
        Class<?>[] interfaces = this.target.getClass().getInterfaces();
        if (interfaces.length == 0) {
            throw new IllegalStateException(
                    "JDK dynamic proxy requires at least one interface");
        }

        return Proxy.newProxyInstance(
                this.target.getClass().getClassLoader(),
                interfaces,
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        if ("doAction".equals(method.getName())) {
                            System.out.println("before call real object");
                        }
                        return invokeTarget(method, args);
                    }
                });
    }

    private Object invokeTarget(Method method, Object[] args) throws Throwable {
        try {
            return method.invoke(this.target, args);
        } catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

这里有两个点要注意。

第一,JDK 动态代理只能代理接口。也就是说,Action1 必须实现 IAction,最终拿到的代理对象也应该转成 IAction 使用。

第二,invoke(...) 里不能只处理 doAction,其他方法就直接返回 null。代理对象上可能还会调用 toString()equals(...),或者接口里以后新增的方法。更稳妥的写法是:需要增强的方法加增强,不需要增强的方法也继续转发给真实对象。

在 Controller 里可以这样手动使用:

java 复制代码
package com.test.controller;

import com.minis.beans.factory.Autowired;
import com.minis.web.RequestMapping;
import com.test.service.DynamicProxy;
import com.test.service.IAction;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloWorldBean {

    @Autowired
    private IAction action;

    @RequestMapping("/testaop")
    public void doTestAop(HttpServletRequest request,
                          HttpServletResponse response) {
        IAction proxyAction = (IAction) new DynamicProxy(this.action).getProxy();
        proxyAction.doAction();

        try {
            response.getWriter().write("test aop, hello world!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这段代码能跑通动态代理,但它有一个问题:业务代码侵入太强。

调用方本来只想写:

java 复制代码
action.doAction();

结果现在必须手动写:

java 复制代码
DynamicProxy proxy = new DynamicProxy(action);
IAction proxyAction = (IAction) proxy.getProxy();
proxyAction.doAction();

这就不像框架了。我们更希望业务代码看起来还是直接调用接口,至于拿到的是普通对象还是代理对象,由 MiniSpring 容器帮我们决定。

三、用 FactoryBean 把代理交给容器

为了让业务代码直接注入代理对象,我们可以引入 FactoryBean

普通 Bean 的逻辑是:

text 复制代码
getBean("action")
        ↓
返回 action 这个 Bean 本身

FactoryBean 的逻辑多了一层:

text 复制代码
getBean("action")
        ↓
发现 action 是一个 FactoryBean
        ↓
调用 factoryBean.getObject()
        ↓
返回 getObject() 生产出来的对象

在这一节里,action 这个 Bean 本身是 ProxyFactoryBean,但调用方真正拿到的是它创建出来的动态代理对象。

XML 可以这样配置:

xml 复制代码
<bean id="realaction" class="com.test.service.Action1"/>

<bean id="action" class="com.minis.aop.ProxyFactoryBean">
    <property type="java.lang.Object" name="target" ref="realaction"/>
</bean>

这里有两个 Bean:

Bean id 实际类型 作用
realaction Action1 真实业务对象
action ProxyFactoryBean 生产 realaction 的代理对象

因为当前 MiniSpring 的 @Autowired 是按字段名找 Bean,所以 Controller 里字段名写成 action,就会拿到 action 这个 Bean 对应的对象。由于 actionFactoryBean,最终注入进去的是代理对象。

四、FactoryBean 接口

FactoryBean 的接口很小:

java 复制代码
package com.minis.beans.factory;

public interface FactoryBean<T> {

    /**
     * 返回这个工厂真正生产出来的对象。
     */
    T getObject() throws Exception;

    /**
     * 返回生产对象的类型,当前教学版可以先用于说明和调试。
     */
    Class<?> getObjectType();

    /**
     * 默认按单例处理,也就是同一个 FactoryBean 只创建一个产品对象。
     */
    default boolean isSingleton() {
        return true;
    }
}

它和普通工厂类的区别在于:容器认识 FactoryBean,并且会在 getBean(...) 时对它做特殊处理。

五、BeanFactory 里怎么识别 FactoryBean

AbstractBeanFactory.getBean(...) 原来的职责是创建普通 Bean。现在它需要在返回前多判断一步:如果当前 Bean 是 FactoryBean,就返回它生产出来的对象。

只看关键逻辑,可以整理成这样:

java 复制代码
package com.minis.beans.factory.support;

import com.minis.beans.BeansException;
import com.minis.beans.factory.FactoryBean;
import com.minis.beans.factory.config.BeanDefinition;

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport
        implements ConfigurableBeanFactory, BeanDefinitionRegistry {

    @Override
    public Object getBean(String beanName) throws BeansException {
        Object singleton = this.getSingleton(beanName);

        if (singleton == null) {
            BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
            if (beanDefinition == null) {
                return null;
            }

            singleton = createBean(beanDefinition);
            this.registerBean(beanName, singleton);

            applyBeanPostProcessorsBeforeInitialization(singleton, beanName);
            invokeInitMethodIfNecessary(beanDefinition, singleton);
            applyBeanPostProcessorsAfterInitialization(singleton, beanName);
        }

        return getObjectForBeanInstance(singleton, beanName);
    }

    protected Object getObjectForBeanInstance(Object beanInstance,
                                              String beanName) {
        if (!(beanInstance instanceof FactoryBean)) {
            return beanInstance;
        }

        FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
        return getObjectFromFactoryBean(factoryBean, beanName);
    }

    private void invokeInitMethodIfNecessary(BeanDefinition beanDefinition,
                                             Object bean) {
        String initMethodName = beanDefinition.getInitMethodName();
        if (initMethodName != null && !initMethodName.isEmpty()) {
            invokeInitMethod(beanDefinition, bean);
        }
    }
}

如果 FactoryBean 生产的是单例对象,还可以在 FactoryBeanRegistrySupport 里做一层缓存:

java 复制代码
package com.minis.beans.factory.support;

import com.minis.beans.factory.FactoryBean;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public abstract class FactoryBeanRegistrySupport
        extends DefaultSingletonBeanRegistry {

    private final Map<String, Object> factoryBeanObjectCache =
            new ConcurrentHashMap<>();

    protected Object getObjectFromFactoryBean(FactoryBean<?> factory,
                                              String beanName) {
        if (factory.isSingleton()) {
            return this.factoryBeanObjectCache.computeIfAbsent(
                    beanName, key -> doGetObjectFromFactoryBean(factory, key));
        }
        return doGetObjectFromFactoryBean(factory, beanName);
    }

    private Object doGetObjectFromFactoryBean(FactoryBean<?> factory,
                                              String beanName) {
        try {
            return factory.getObject();
        } catch (Exception ex) {
            throw new IllegalStateException(
                    "FactoryBean getObject failed: " + beanName, ex);
        }
    }
}

这样 getBean("action") 的结果就不是 ProxyFactoryBean 本身,而是它生产出来的代理对象。

六、ProxyFactoryBean:生产代理对象

ProxyFactoryBean 是这一节最核心的类。它自己是一个 Bean,但它返回给业务层的是代理对象。

java 复制代码
package com.minis.aop;

import com.minis.beans.factory.FactoryBean;

/**
 * 用 FactoryBean 把代理对象交给 IoC 容器管理。
 */
public class ProxyFactoryBean implements FactoryBean<Object> {

    private AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();

    /**
     * 真实业务对象,例如 Action1。
     */
    private Object target;

    /**
     * 缓存生成好的代理对象。
     */
    private Object singletonInstance;

    public void setAopProxyFactory(AopProxyFactory aopProxyFactory) {
        if (aopProxyFactory == null) {
            throw new IllegalArgumentException("AopProxyFactory must not be null");
        }
        this.aopProxyFactory = aopProxyFactory;
    }

    public Object getTarget() {
        return this.target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object getObject() {
        return getSingletonInstance();
    }

    @Override
    public Class<?> getObjectType() {
        if (this.target == null) {
            return null;
        }

        Class<?>[] interfaces = this.target.getClass().getInterfaces();
        return interfaces.length > 0 ? interfaces[0] : this.target.getClass();
    }

    private synchronized Object getSingletonInstance() {
        if (this.singletonInstance == null) {
            this.singletonInstance = createAopProxy().getProxy();
        }
        return this.singletonInstance;
    }

    private AopProxy createAopProxy() {
        if (this.target == null) {
            throw new IllegalStateException("Target must be configured");
        }
        return this.aopProxyFactory.createAopProxy(this.target);
    }
}

它的流程是:

text 复制代码
容器创建 ProxyFactoryBean
        ↓
给 target 注入 realaction
        ↓
业务代码 getBean("action")
        ↓
容器发现它是 FactoryBean
        ↓
调用 ProxyFactoryBean.getObject()
        ↓
创建并返回 JDK 动态代理对象

七、抽象 AopProxy

上面已经能通过 JDK 实现动态代理了,但 JDK 动态代理只支持接口。如果以后要支持没有接口的类,就需要换一种代理方式,比如 CGLIB 这类基于子类的代理。

真实 Spring AOP 会根据情况选择不同代理方式。简化到 MiniSpring 里,我们先抽象出两个接口:

java 复制代码
package com.minis.aop;

public interface AopProxy {

    Object getProxy();
}
java 复制代码
package com.minis.aop;

public interface AopProxyFactory {

    AopProxy createAopProxy(Object target);
}

这样 ProxyFactoryBean 不直接依赖 JDK 的 Proxy.newProxyInstance(...),而是依赖 AopProxyFactory

text 复制代码
ProxyFactoryBean
        ↓
AopProxyFactory
        ↓
AopProxy
        ↓
JdkDynamicAopProxy

这一层抽象的意义是:当前只实现 JDK 动态代理,以后如果要加其他代理方式,只需要新增一个 AopProxy 实现。

真实 Spring AOP 的思路也类似,只是完整很多:

text 复制代码
业务 Bean
        ↓
判断是否匹配切点
        ↓
ProxyFactory 创建代理
        ↓
选择 JDK 动态代理或 CGLIB 代理
        ↓
返回代理对象给容器

本节的 MiniSpring 先把重点放在"代理对象怎么生成、怎么交给容器"。所以增强逻辑暂时写死在 doAction 上。真实 Spring 不会这样硬编码方法名,而是用切点规则判断哪些方法需要增强,再把日志、事务等逻辑组织成拦截器链。

八、JdkDynamicAopProxy:JDK 代理实现

下面是 JDK 动态代理的封装。注意 InvocationHandler 是 JDK 提供的接口,核心方法是:

java 复制代码
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

因为 JdkDynamicAopProxy 实现了 InvocationHandler,所以 Proxy.newProxyInstance(..., this) 里的 this,指的就是当前这个方法调用处理器。以后代理对象上的方法被调用,都会进入这里的 invoke(...)

java 复制代码
package com.minis.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 基于 JDK Proxy 的 AopProxy 实现。
 */
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    private final Object target;

    public JdkDynamicAopProxy(Object target) {
        if (target == null) {
            throw new IllegalArgumentException("Target must not be null");
        }
        this.target = target;
    }

    @Override
    public Object getProxy() {
        Class<?>[] interfaces = this.target.getClass().getInterfaces();
        if (interfaces.length == 0) {
            throw new IllegalStateException(
                    "JDK dynamic proxy requires at least one interface");
        }

        return Proxy.newProxyInstance(
                this.target.getClass().getClassLoader(),
                interfaces,
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if ("doAction".equals(method.getName())) {
            System.out.println(
                    "before call real object, dynamic proxy");
        }

        return invokeTarget(method, args);
    }

    private Object invokeTarget(Method method, Object[] args) throws Throwable {
        try {
            return method.invoke(this.target, args);
        } catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

默认工厂先直接返回 JDK 代理:

java 复制代码
package com.minis.aop;

public class DefaultAopProxyFactory implements AopProxyFactory {

    @Override
    public AopProxy createAopProxy(Object target) {
        return new JdkDynamicAopProxy(target);
    }
}

到这里,MiniSpring 的代理对象创建就被拆成了几个清楚的角色:

职责
ProxyFactoryBean 作为容器里的 Bean,负责对外生产代理对象
AopProxyFactory 决定使用哪种代理实现
AopProxy 屏蔽具体代理技术
JdkDynamicAopProxy 使用 JDK Proxy 创建代理对象,并处理方法调用

九、最终使用方式

配置仍然是:

xml 复制代码
<bean id="realaction" class="com.test.service.Action1"/>

<bean id="action" class="com.minis.aop.ProxyFactoryBean">
    <property type="java.lang.Object" name="target" ref="realaction"/>
</bean>

Controller 里不再手动 new DynamicProxy

java 复制代码
package com.test.controller;

import com.minis.beans.factory.Autowired;
import com.minis.web.RequestMapping;
import com.test.service.IAction;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloWorldBean {

    /**
     * 字段名 action 对应 XML 中 id="action" 的 Bean。
     * 这个 Bean 是 ProxyFactoryBean,所以最终注入的是代理对象。
     */
    @Autowired
    private IAction action;

    @RequestMapping("/testaop")
    public void doTestAop(HttpServletRequest request,
                          HttpServletResponse response) {
        this.action.doAction();

        try {
            response.getWriter().write("test aop, hello world!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最终调用链是:

text 复制代码
浏览器请求 /testaop
        ↓
HelloWorldBean.doTestAop(...)
        ↓
action.doAction()
        ↓
action 实际是 JDK 动态代理对象
        ↓
JdkDynamicAopProxy.invoke(...)
        ↓
执行 before 增强逻辑
        ↓
method.invoke(target, args)
        ↓
Action1.doAction()

业务代码看到的还是普通接口:

java 复制代码
this.action.doAction();

但运行时真正被调用的是代理对象,所以通用逻辑就可以插入到业务方法前面。

十、三层实现思路总结

这一节其实是按三层递进来讲动态代理的。

第一层,最简单的动态代理实现,是新建一个 DynamicProxy 类。

text 复制代码
真实对象 target
        ↓ 注入
DynamicProxy
        ↓ getProxy()
JDK Proxy 生成的代理对象
        ↓ 调用方法
InvocationHandler.invoke(...)
        ↓
method.invoke(target, args)

这一层只解决一个问题:怎么用 JDK 动态代理在运行时生成代理对象。核心代码就是 Proxy.newProxyInstance(...),核心入口就是 getProxy()

第二层,为了让业务代码不被代理细节侵入,引入 FactoryBean

text 复制代码
XML 已经配置好:
realaction -> 真实对象
action     -> ProxyFactoryBean

业务代码:
@Autowired
private IAction action;

容器 getBean("action")
        ↓
发现 action 是 FactoryBean
        ↓
调用 ProxyFactoryBean.getObject()
        ↓
返回代理对象,而不是返回 ProxyFactoryBean 本身

这一步的关键不是"怎样生成代理",而是"怎样让业务代码无感拿到代理"。是否需要代理,早就在 XML 里通过 action -> ProxyFactoryBean 配好了;getBean(...) 只是在返回对象前判断一下当前 Bean 是否是 FactoryBean。如果是,就调用 getObject() 拿产品对象;如果不是,就返回普通 Bean。

第三层,是向 Spring AOP 靠近的抽象。

text 复制代码
ProxyFactoryBean
        |
        | createAopProxy(target)
        v
AopProxyFactory
        |
        | 决定使用哪种代理技术
        v
AopProxy
        |
        | getProxy()
        v
JdkDynamicAopProxy
        |
        | Proxy.newProxyInstance(..., this)
        v
JDK 代理对象
        |
        | 方法调用
        v
InvocationHandler.invoke(...)

AopProxy 用来屏蔽具体代理技术,它只暴露 getProxy(),表示"给我一个代理对象"。AopProxyFactory 用来决定创建哪一种 AopProxy,当前默认创建的是 JdkDynamicAopProxy

invoke(...) 不是 AopProxy 接口的方法,而是 JDK 的 InvocationHandler 方法。JdkDynamicAopProxy 同时实现了 AopProxyInvocationHandler,所以它既能通过 getProxy() 创建代理对象,也能在代理对象的方法被调用时进入 invoke(...) 执行增强逻辑。

简单收一下:

层次 解决的问题 核心类/方法
直接使用 JDK 动态代理 运行时生成代理对象 DynamicProxy#getProxy()Proxy.newProxyInstance(...)
接入容器 业务代码无感拿到代理对象 FactoryBean#getObject()ProxyFactoryBeangetBean(...) 判断 FactoryBean
AOP 抽象 屏蔽具体代理技术,方便以后扩展 AopProxy#getProxy()AopProxyFactoryJdkDynamicAopProxy#invoke(...)

十一、学完后要能回答的问题

第一,什么是代理?

代理就是不直接访问真实对象,而是通过代理对象间接访问真实对象。代理对象和真实对象通常实现同一个接口,因此调用方可以面向接口编程。代理对象可以在调用真实对象前后增加日志、事务、权限检查等通用逻辑。

第二,什么是静态代理?

静态代理是提前手写代理类。它清晰、好理解,但每个需要增强的业务类都可能要配一个代理类,维护成本很高。

第三,什么是动态代理?

动态代理是不提前写代理类,而是在程序运行期间生成代理对象。JDK 动态代理需要目标类实现接口,并通过 InvocationHandler.invoke(...) 统一拦截方法调用。

第四,MiniSpring 为什么要引入 FactoryBean

因为我们不希望业务代码手动 new 代理对象。FactoryBean 可以让 ProxyFactoryBean 留在容器内部,而业务代码通过 getBean("action")@Autowired 拿到它生产出来的代理对象。

第五,AopProxy 抽象解决了什么问题?

它把"生产代理对象"这件事抽象出来。当前实现是 JdkDynamicAopProxy,以后如果要支持其他代理技术,可以新增实现,而不需要改业务代码和 ProxyFactoryBean 的主流程。

这一节可以总结成一句话:

动态代理让我们可以在运行时生成代理对象,再用 FactoryBean 把这个代理对象交给容器管理,业务代码保持 action.doAction() 不变,通用逻辑却已经被插入到了调用链中。

相关推荐
2601_953824615 小时前
【计算机毕业设计】基于Spring Boot的画师接稿平台设计与实现
java·spring boot·后端
吃好睡好便好6 小时前
近期几点体会
学习·生活
xlrqx6 小时前
商丘家电清洗培训零基础学习需掌握哪些要点零基础到底能不能学
python·学习
小宋10216 小时前
不用学算法也能玩转 AI?普通 Java 程序员转 AI 应用开发路线
java·人工智能·重构
编程圈子6 小时前
电机驱动开发学习19. 霍尔 BLDC 三段式 FOC 启动算法
驱动开发·学习·算法
我是唐青枫7 小时前
Java Jetty 实战详解:从嵌入式 HTTP 服务到 Spring Boot 容器替换
java
带刺的坐椅7 小时前
当所有人都在用 TS/Python 写 Agent,我们为什么坚持 Java
java·ai·solon·codex·opencode·soloncode
MartinYeung57 小时前
[论文学习]LLM 中的个性化安全:一个基准测试与基于规划的智能体方法
人工智能·学习·安全
小王师傅667 小时前
英语学习记
java·学习
早点睡啊Y7 小时前
深入学LangChain官方文档:Observability 与 Studio——先看清 Agent 到底做了什么
java·数据库·langchain