Feign 的实现原理

article/2025/9/2 13:37:42

Feign 实现原理

Feign是申明式的 HTTP 客户端。代码中创建一个接口并加上@FeingClient 注解即可使用。其底层封装了 HTTP 客户端构建并发送的复杂逻辑。同时也可以整合注册中心及 Ribbon 为其提供负载均衡能力;通过整合 Histrix/sentinal 实现熔断限流功能。本期主要分享下 Feign 与 SpringCloud 的整合过程,及其底层 HTTP 调用的实现细节。

https://www.springcloud.cc/spring-cloud-greenwich.html#_spring_cloud_openfeign

使用

  1. pom.xml 中添加

    <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 启动类添加 @EnableFeignClients注解

    @SpringBootApplication(scanBasePackages = {"com.xxx"})
    // 指定 FeiginClient 扫描路径
    @EnableFeignClients(basePackages = {"com.xxx.biz.feign"})
    public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
    }
    
  3. 申明 FeignClient

    @FeignClient(name = "inverter-data-service", url = "${feign.property.inverter-data-service.url}", configuration = {xxx})
    public interface InverterDataFeign {@PostMapping(value = "/api/inverter/get_first_date")FirstDataResponse firstData(@RequestBody String sn, @RequestHeader("token") String token);
    }
    
  4. 服务调用

    @Service
    public class DemoService {@Autowiredprivate InverterDataFeign inverterDataFeign;public void remoteCall(String sn, String token) {// ...FirstDataResponse firstDataResponse = inverterDataFeign.firstData(sn, token);// ...}
    }
    

思考

  • @FeignClient注解是如何使普通的 Java 接口具有 HttpClient 能力的?
  • @FeignClient并非 @Component注解,为什么可以像其他Spring 组件一样注入到其他组件中(如 DemoService)?

实现流程图

在这里插入图片描述

实现细节

扫描 & 注册

启动类 Application 申明了注解 @EnableFeignClients,而@EnableFeignClients又申明了@Import(FeignClientsRegistrar.class)FeignClientsRegistrar实现了 ImportBeanDefinitionRegistrar

public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}/*** 子类实现*/default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}

当 Spring IOC 容器启动时,会去扫描 @ComponentScan 指定包下所有 @Import 的注解,如果 @Import 的 value 参数是 ImportBeanDefinitionRegistrar 的实现类,就会调用 ImportBeanDefinitionRegistrar#registerBeanDefinitions() 方法,而 FeignClientsRegistrar 就是 ImportBeanDefinitionRegistrar 的实现类;故容器初始化过程中 FeignClientsRegistrar#registerBeanDefinitions() 会被执行,这个方法会做两件事情,1. 注册 @EnableFeignClients 中 configuration 参数指定的默认配置类;2. 扫描并注册子包下被 @FeignClient 注解的 feign 客户端

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 注册默认配置类registerDefaultConfiguration(metadata, registry);// 扫描注册 feign 客户端至 spring 容器registerFeignClients(metadata, registry);}
}

下面主要看registerFeignClients(metadata, registry)的实现

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();// 获取 @EnableFeignClients 中所有的参数Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());// 拿到 clients 参数final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {/* 如果未设置 clients 参数,则去子包下扫描所有注解类型为 @FeignClient 的类 */// 获取扫描器ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);// 设置扫描器的过滤条件,只扫描 @FeignClient 类型scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));// 拿到 @FeignClient 的 basePackages 参数Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {// 执行扫描,构建并添加 BeanDefinition 实体candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}}else {// 如果 clients 参数不为空,则只去注册指定的 FeignClientfor (Class<?> clazz : clients) {candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));}}/* 遍历 BeanDefinitions(申明 @FeignClient 的类定义)1. 注册 @FeignClient 中 configuration 参数指定的配置类2. 注册具体的 FeignClient */for (BeanDefinition candidateComponent : candidateComponents) {// 查看组件是否是包含注解的 BeanDefinitionif (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;// 拿到类的注解元数据(包含 class 信息)AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();// 校验,@FeignClient 只能申明在接口上Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");// 拿到 @FeignClient 的属性数据Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());// 获取客户端名称,依次查看 contextId、value、name、serviceId,只要有值就返回String name = getClientName(attributes);// 拿到 configuration 参数,注册配置类registerClientConfiguration(registry, name, attributes.get("configuration"));// 注册 FeignClientregisterFeignClient(registry, annotationMetadata, attributes);}}}

接着看registerFeignClient(registry, annotationMetadata, attributes)的逻辑:

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {// 拿到接口类名,构建 class 对象String className = annotationMetadata.getClassName();Class clazz = ClassUtils.resolveClassName(className, null);ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory? (ConfigurableBeanFactory) registry : null;String contextId = getContextId(beanFactory, attributes);String name = getName(attributes);// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {factoryBean.setUrl(getUrl(beanFactory, attributes));factoryBean.setPath(getPath(beanFactory, attributes));factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));Object fallback = attributes.get("fallback");if (fallback != null) {factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback: ClassUtils.resolveClassName(fallback.toString(), null));}Object fallbackFactory = attributes.get("fallbackFactory");if (fallbackFactory != null) {factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory: ClassUtils.resolveClassName(fallbackFactory.toString(), null));}return factoryBean.getObject();});definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);definition.setLazyInit(true);validate(attributes);// 设置 BD 的一些属性,是否 primary,别名等...AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);// has a default, won't be nullboolean primary = (Boolean) attributes.get("primary");beanDefinition.setPrimary(primary);String[] qualifiers = getQualifiers(attributes);if (ObjectUtils.isEmpty(qualifiers)) {qualifiers = new String[] { contextId + "FeignClient" };}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);// 注册 FeignClient 对应的 BeanDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);// 使用 RefreshScope 创建 Request.Options(请求超时时间等配置) BeanDefinitionregisterOptionsBeanDefinition(registry, contextId);}

至此,Spring 容器启动过程中,扫描 FeignClient 并注册到容器的流程已经结束

初始化代理类

注册到 Spring 容器中的 BeanDefinition 会存入一个 Map 中(BeanDefinitionMap);所有 BeanDefinition 注册完毕后,Spring 会对其进行初始化,即实例化并存入 Spring 容器;注册 FeignClient 时,FeignClient 被包装成 FeignClientFactoryBean(FactoryBean 实现类)存入 BeanDefinitionMap;故初始化 FeignClientFactoryBean 时,会回调其 getObject() 方法

再来看刚刚注册 FeignClient 的逻辑:

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {// 省略前面逻辑...// 由于 FeignClient 是个接口,业务里没有实现类,所以这里将 feignClient 封装成 FactoryBean,后面对象初始化时,回调其 getObject() 方法,实现动态创建 FeignClient 实现类的 Bean 对象FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 创建 BeanDefinitionBuilder, 会传入一个回调接口 instanceSupplier 的实现,后续在实例化该 BeanDefinition 对应的 Bean 时,会去回调这个接口中实现的方法BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {// 初始化参数factoryBean.setUrl(getUrl(beanFactory, attributes));factoryBean.setPath(getPath(beanFactory, attributes));factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));Object fallback = attributes.get("fallback");if (fallback != null) {factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback: ClassUtils.resolveClassName(fallback.toString(), null));}Object fallbackFactory = attributes.get("fallbackFactory");if (fallbackFactory != null) {factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory: ClassUtils.resolveClassName(fallbackFactory.toString(), null));}// 主要逻辑在这个方法里return factoryBean.getObject();});// 省略后面逻辑...}

创建 FeignClient 对象的逻辑均在 FeignClientFactoryBean#getObject() 中:

	@Overridepublic Object getObject() {return getTarget();}/*** @param <T> the target type of the Feign client* @return a {@link Feign} client created with the specified data and the context* information*/<T> T getTarget() {// 获取 FeignContext(FeinContext 是在 FeignAutoConfiguration 中通过 @Bean 注入的)// 属于 ApplicationContext 的子容器FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);// 从容器中获取 feign builder 对象(该对象也是在 FeignAutoConfiguration 中通过 @Bean 注入的)Feign.Builder builder = feign(context);if (!StringUtils.hasText(url)) {if (url != null && LOG.isWarnEnabled()) {LOG.warn("The provided URL is empty. Will try picking an instance via load-balancing.");}else if (LOG.isDebugEnabled()) {LOG.debug("URL not provided. Will use LoadBalancer.");}if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();// 负载均衡,需结合 ribbon 使用return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));}if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}String url = this.url + cleanPath();Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 容器中获取 Targeter 实例(DefaultTargeter)Targeter targeter = get(context, Targeter.class);// 获取代理对象return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));}

DefaultTargeter#target 会去实例化 ReflectiveFeign, 并调用其 newInstance() 方法实例化代理对象,ReflectiveFeign#newInstance() 逻辑:

public <T> T newInstance(Target<T> target) {// feign 中调用的方法名 -> SynchronousMethodHandler映射;(SynchronousMethodHandler 是 InvocationHandler 的封装,包含 Feign 请求的配置信息) Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();for (Method method : target.type().getMethods()) {if (method.getDeclaringClass() == Object.class) {continue;} else if (Util.isDefault(method)) {DefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}// 创建 InvocationHandler 对象InvocationHandler handler = factory.create(target, methodToHandler);// JDK 动态代理,创建代理对象,T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;}

至此,代理对象创建结束.

发送请求

当 FeignClient 发起 http 请求时,会从容器中获取对应的代理类,并调用 FeignInvocationHandler#invoke() 方法,其最终实现在 SynchronousMethodHandler#invoke() 中:

public Object invoke(Object[] argv) throws Throwable {// 解析请求参数,封装 RequestTemplateRequestTemplate template = buildTemplateFromArgs.create(argv);// 获取 Request.Options 对象,封装了请求的 connectTimeout/readTimeout 等信息Options options = findOptions(argv);// 重试机制Retryer retryer = this.retryer.clone();while (true) {try {// 执行请求,并解析响应return executeAndDecode(template, options);} catch (RetryableException e) {// 重试逻辑try {retryer.continueOrPropagate(e);} catch (RetryableException th) {Throwable cause = th.getCause();if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}}if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}continue;}}}

执行请求的逻辑主要在 executeAndDecode() 方法:

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template);// 打印日志if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {// 执行请求,拿到响应response = client.execute(request, options);response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {// ...}// ...
}

如果未设置 http 客户端,则使用默认的客户端 Client.Default,即构建 java.net.HttpURLConnection 并发送请求:

 public Response execute(Request request, Options options) throws IOException {// 构建 java.net.HttpURLConnection 并发送请求HttpURLConnection connection = convertAndSend(request, options);return convertResponse(connection, request);}

问题

实例化 FeignClient 时,Feign 为什么要使用 FeignContext 来给每个 FeignClient 创建一个子IOC容器,如果直接使用父容器会有什么问题?


http://chatgpt.dhexx.cn/article/xZ1RQoDX.shtml

相关文章

OpenFeign的实现原理(附Feign和OpenFeign的区别)

目录 问题现象&#xff1a; 问题分析&#xff1a; 拓展&#xff1a; 1、Feign&#xff1a; 2、OpenFeign&#xff1a; 问题现象&#xff1a; 最近在复习SpringCloud的时候看到一个面试题&#xff1a; OpenFeign的实现原理&#xff1f; 问题分析&#xff1a; OpenFeign是Sp…

Feign原理

是一个HTTP请求调用轻量级框架&#xff0c;可以以Java接口注解的方式调用HTTP请求&#xff0c;而不用像Java中通过封装HTTP请求报文的方式直接调用。 Feign解决了什么问题 在服务调用的场景中&#xff0c;我们经常调用基于HTTP协议的服务&#xff0c;而我们经常使用到的框架可…

Feign的使用及原理剖析

feign使用及原理剖析 一、简介 Feign是一个http请求调用的轻量级框架&#xff0c;可以以Java接口注解的方式调用Http请求。Feign通过处理注解&#xff0c;将请求模板化&#xff0c;当实际调用的时候&#xff0c;传入参数&#xff0c;根据参数再应用到请求上&#xff0c;进而转…

Feign基本使用(超详细)

目录 一、Feign概述 二、Feign入门 1.创建服务提供者(provider) 2.创建feign接口 3、创建服务消费者(consumer) 三、Feign 原理 四、Feign优化 1、开启feign日志 2、feign超时问题 3、http连接池 4、gzip压缩 前言 当我们通过RestTemplate调用其它服务的API时&#xff0c;所…

简单理解Feign的原理与使用

文章目录 SpringCloud 总架构图一、简介1.1、负载均衡的概念2.2、Feign概念 二、入门案例2.1、导入依赖2.2、Feign的客户端2.3、调用Feign2.4、开启Feign功能2.5、启动测试2.6、Feign实现原理简单分析 三、负载均衡(Ribbon)四、熔断器支持五、请求压缩和响应压缩六、配置日志级…

【第四章】详解Feign的实现原理

1.1 Feign概述 这篇文章主要讲述如何通过Feign去消费服务&#xff0c;以及Feign的实现原理的解析。 Feign是Netflix开发的声明式、模板化的HTTP客户端&#xff0c;Feign可以帮助我们更快捷、优雅地调用HTTP API。 Feign是⼀个HTTP请求的轻量级客户端框架。通过 接口 注解的…

Feign底层原理分析-自动装载动态代理

本篇文章仅介绍Feign的核心机制&#xff0c;包括如何交由Spring容器托管、动态代理机制等内容&#xff0c;不会过分深入细节。 1、什么是Feign&#xff1f; 这里套用Feign官方Github上的介绍&#xff1a;“Feign是一个灵感来自于Retrofit、JAXRS-2.0、WebSocket的Java Http客户…

Feign原理 (图解)

1.1 简介&#xff1a;Feign远程调用的 Feign远程调用&#xff0c;核心就是通过一系列的封装和处理&#xff0c;将以JAVA注解的方式定义的远程调用API接口&#xff0c;最终转换成HTTP的请求形式&#xff0c;然后将HTTP的请求的响应结果&#xff0c;解码成JAVA Bean&#xff0c;放…

Feign的工作原理

Feign的工作原理 Feign是一个伪Java Http 客户端&#xff0c;Feign 不做任何的请求处理。Feign 通过处理注解生成Request模板&#xff0c;从而简化了Http API 的开发。开发人员可以使用注解的方式定制Request API模板。 在发送Http Request请求之前&#xff0c;Feign通过处理…

Linux命令——tar与gzip详解:文件的打包压缩与解压缩解打包

Linux系统中&#xff0c;最常用的打包命令就是tar了&#xff0c;不仅如此&#xff0c;tar命令还可以解打包解压缩&#xff0c;十分方便。如果单纯想压缩文件&#xff0c;就需要我们的gzip命令了。 使用tar打包归档的包叫做tar包&#xff0c;以.tar结尾 使用gzip压缩的文件&…

Linux 下使用 tar 命令打包指定目录下的所有文件,不包括目录

一&#xff0c;问题描述 无论是 Linux 系统&#xff0c;还是 macOS 系统&#xff0c;我们都可以使用 tar 命令进行文件的压缩打包。命令格式如下&#xff1a; # tar cvf xxxx.tar 要压缩的文件或目录名称但如果要压缩的目录层级比较多时&#xff0c;比如&#xff1a; # tar cvf…

linux tar (打包、压缩、解压)命令

打包程序&#xff1a;tar c: 创建文档t&#xff1a; 列出存档内容x&#xff1a;提取存档f&#xff1a; filename 要操作的文档名v&#xff1a;详细信息 一&#xff1a;打包 打包&#xff1a;是指把文件整合在一起&#xff0c;不压缩 1.将文件打包&#xff1a;tar cf a.ta…

(21)tar打包命令详解

Linux 系统中,最常用的归档(打包)命令就是 tar,该命令可以将许多文件一起保存到一个单独的磁盘中进行归档。不仅如此,该命令还可以从归档文件中还原所需文件,也就是打包的反过程,称为解打包。1.tar命令做打包操作  当 tar 命令用于打包操作时,该命令的基本格式为: …

vector中删除某个指定元素

class Solution { public:int removeElement(vector<int>& v, int val) {for (auto it v.begin(); it ! v.end(); it) {if (*it val) { // 条件语句v.erase(it); // 移除他it--; // 让该迭代器指向前一个}}return v.size();} };

vector删除指定元素

C vector中实际删除元素使用的是容器vecrot中std::vector::erase()方法。 C 中std::remove()并不删除元素&#xff0c;因为容器的size()没有变化&#xff0c;只是元素的替换。 1.std::vector::erase() 函数原型&#xff1a;iterator erase (iterator position);  //删除指定…

C++中vector中删除/添加指定位置处的元素

1、函数介绍 C中vector容器可以删除/添加制定位置处的元素&#xff0c;分别使用erase()与insert()函数。其中函数内需要两个参数&#xff0c;第一个为指定删除/添加的位置&#xff0c;第二个元素为删除/添加的元素值。 iterator insert(const_iterator _Where, _Ty&&…

C/C++ vector 删除指定元素

C vector 删除符合条件的元素C vector中实际删除元素使用的是容器vecrot中std::vector::erase()方法。C 中std::remove()并不删除元素&#xff0c;因为容器的size()没有变化&#xff0c;只是元素的替换。1.std::vector::erase()  函数原型&#xff1a;iterator erase (iterat…

【c++】vector中删除元素

目录 1.删除指定范围的元素2.删除指定大小的元素3.C20 std::erase, std::erase_if (std::vector)注意点 1.删除指定范围的元素 vector删除元素之pop_back(),erase(),remove() 向量容器vector的成员函数pop_back()可以删除最后一个元素. 而函数erase()可以删除由一个iterator指…

光纤光学原理相关基础知识点

记&#xff1a;研究生导师的方向是光纤光学和机器学习交叉的&#xff0c;导师给推荐了本书&#xff0c;书里面的理论和推导公式作为小白的我真的是不太行&#xff0c;后来在中国大学mooc上找了视频课跟着学习&#xff0c;顺便做下笔记&#xff0c;为日后使用&#xff0c;因此会…

机器人学重点知识点总结

机器人学重点知识点总结 坐标转换与机械臂运动学雅克比矩阵机械臂逆向动力学&#xff08;牛顿欧拉递推&#xff09;机械臂正向动力学运动轨迹生成动力学轨迹跟踪控制 这篇博客主要用来记录一下现代机器人学里面比较基础也比较重要的一些知识点&#xff0c;所有内容均仅仅记录是…