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

article/2025/9/2 13:34:12

本篇文章仅介绍Feign的核心机制,包括如何交由Spring容器托管、动态代理机制等内容,不会过分深入细节。

1、什么是Feign?

这里套用Feign官方Github上的介绍:“Feign是一个灵感来自于Retrofit、JAXRS-2.0、WebSocket的Java Http客户端,Feign的主要目标是降低大家使用Http API的复杂性”。

其实,Feign底层依赖于Java的动态代理机制,对原生Java Socket或者Apache HttpClient进行封装,实现了基于Http协议的远程过程调用。当然,Feign还在此基础上实现了负载均衡、熔断等机制。

2、为什么要使用Feign?

  • 声明式Http Client相对于编程式Http Client代码逻辑更加简洁,不需要处理复杂的编码请求和响应,只需要像调用本地方法即可,提高编码效率
  • 集中管理Http请求方法,代码边界更加清晰
  • 更好的集成负载均衡、熔断降级等功能

3、Feign依赖注入原理

使用过Feign的同学都知道,@EnableFeignClients注解是开启Fiegn功能的关键,我们通常会在该注解中添加FeignClient的所在包,以便Spring容器能够扫描到所有的FeignClient,并进行托管。后面我们便可以使用@Autowired注解自动导入了。

@SpringBootApplication
@EnableFeignClients(basePackages = {"com.**.feign"})
public class Application {}

该注解样式也是很多第三方包集成Springboot所使用的套路:一般都是开启该注解后,Springboot便可以自动装载第三方包所指定的Class,我们便可以直接使用第三方包所提供的功能,非常方便。

接下来不会详细介绍自动装载的部分,而是直接给出自动装载的主脉络,看看Spring容器到底装载了什么bean。

3.1、Feign自动装载

首先进入@EnableFeignClients源码中,查看该注解导入了什么Registrar注册器,这个注册器便是自动装载的关键。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {...}

从源码中可以看出,@EnableFeignClients 注解导入的是自定义的FeignClientsRegistrar类。

这种类型的注册器一般会继承Spring中的ImportBeanDefinitionRegistrar接口,并在registerBeanDefinitions实现方法中向Spring容器注册一些bean,以达到自动注入第三方功能的目的。

// [1] 继承ResourceLoaderAware和EnvironmentAware
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// [2] 注册默认的Feign配置registerDefaultConfiguration(metadata, registry);// [3] 注册所有定义的FeignClientregisterFeignClients(metadata, registry);}
}
  • [1] 从类的定义中,我们可以发现还实现了ResourceLoaderAware、EnvironmentAware两个Spring钩子接口,那么该注册类必然持有资源加载器和Spring的环境变量等信息,这个不过多叙述。
  • [2] 该方法会从@EnableFeignClients注解中提取defaultConfiguration这个key和对应的value,并把它当作默认的Feign配置注册到Spring容器中。如果没有该key,则不做任何处理。
  • [3] registerFeignClients方法会扫描@EnableFeignClients注解的basePackages,注册所有的FeignClient,下面详细介绍。
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());// [1] 获取clients属性final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {// [2] 如果clients属性为null,则获取basePackages属性,扫描其中的所有clientClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}} else {// [3] 如果clients属性不为null,则直接注入for (Class<?> clazz : clients) {candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));}}// [4] 遍历所有的clients,将其封装为BeanDefinition注册进Spring容器中for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = getClientName(attributes);registerClientConfiguration(registry, name, attributes.get("configuration"));registerFeignClient(registry, annotationMetadata, attributes);}}
}

registerFeignClients方法的代码稍微有点多,但主要思路就是解析@EnableFeignClients注解,根据解析信息获取FeignClients,然后包装成BeanDefinition注册进Spring容器中。具体分为以下几步:

  • [1] 首先解析@EnableFeignClients注解中的clients信息,如果存在,说明开发人员直接指定了FeignClient的全路径,因此只要加载这些全路径的class即可。如果未指定,则通过扫包的方式加载。
  • [2] 如果clients属性为null,则创建一个扫描器Scanner,并指定要扫描的类必须有FeignClient注解,然后通过getBasePackages()方法从@EnableFeignClients注解中获取basePackages信息,最后遍历所有待扫描的包,将扫描到的FeignClient类加入candidateComponents中,待后续加载进容器。
  • [3] 如果clients属性不为null,上面也说了,会直接注入
  • [4] 到了这里,所有的FeignClient都被扫描到并且封装成BeanDefinition,接下来会遍历这些BeanDefinition,然后在Assert.isTrue()方法中判断这些BeanDefinition是否为接口,因为@FeignClient注解只能使用在接口上。校验完后我们应该关注registerFeignClient()这个真正注册FeignClient的方法。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {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);FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);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);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);BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

registerFeignClient()是对单个FeignClient注册的方法。这个方法乍一看很长,但其实主线非常清晰。

就是将扫描到的BeanDefinition中的元信息提取出来,然后构造成一个Feign自定义的FactoryBean,即FeignClientFactoryBean,后面我们每次获取容器中的FeignClient时,就会通过该FactoryBean的getObject()方法中获取(这个涉及到了Spring容器中普通bean和FactoryBean的区别,大家可以自行去了解下)。

方法中的其它部分都是为上面所说的逻辑服务,包括FactoryBean的构造,BeanDefinition注入到容器中等过程。我们不必太过关心,只要抓住重点即可。

讲到这里,其实自动装载过程已经完成了,容器中已经包含了自定义的FeignClientFactoryBean。这里用一张流程图总结下自动装载的全过程。

在这里插入图片描述
但是我们暂时还不知道自动装载的FeignClientFactoryBean到底做了什么。下面我们就深入去了解。

3.2、FeignClientFactoryBean#getObject

FeignClientFactoryBean的源码如下:

public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {@Overridepublic Object getObject() {// [1] 真正的获取对象的方法委托给getTarget()方法了return getTarget();}<T> T getTarget() {// 获取Feign的上下文FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(url)) {// [2] 如果url没有定义,则进入到该判断中创建对象,该判断中创建的对象具有负载均衡功能if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));}if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}// [3] 如果url定义了,说明用户指定了某台机器,也就没有必要进行负载均衡了,则从下面的方法创建对象String url = this.url + cleanPath();// 可以发现下面的执行逻辑和loadBalance()非常像,只是多了两个if判断,这两个if判断就是移除负载均衡的关键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 targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));}
}
// 创建负载均衡的client
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {Client client = getOptional(context, Client.class);if (client != null) {builder.client(client);Targeter targeter = get(context, Targeter.class);return targeter.target(this, builder, context, target);}throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}

getObject()会根据用户定义的FeignClient是否定义url属性决定是否返回具有负载均衡属性的对象。具体过程如下:

  • [1] 重写的方法委托给getTarget()执行了
  • [2] 如果没有定义url,则直接通过loadBalance()方法创建代理对象。此时会先从容器中拿到Client对象,然后使用targeter.target()方法创建Client的代理对象。注意,我们最终使用的FeignClient是Client的动态代理对象,而Client对象是真正执行http请求的对象。Client一般是httpClient或者是Feign自定义的具有LoadBalance功能的LoadBalancerClient。前者很好理解,可以直接认为是Apache HttpClient;后者是Feign在Apache HttpClient的基础上封装了Spring Cloud Loadbalancer一系列对象,而Apache HttpClient作为被封装的delegate,在delegate真正执行http请求时同时进行Loadbalancer的负载均衡逻辑。
  • [3] 如果url已经定义了,说明用户指定了具体某台机器,此时已经没有必要进行负载均衡了(当然,如果配置的是域名,可能会由下游ng或者网关层进行负载均衡,这里说的是Feign没有必要负载均衡)。为了移除负载均衡的功能,这里比loadBalance()方法多了client的判断,如果client是FeignBlockingLoadBalancerClient或者RetryableFeignBlockingLoadBalancerClient,会直接代理这两个对象中的delegate对象,即直接代理Apache HttpClient,这样就能移除其中的负载均衡功能了。

从上面的源码中可知,无论是否需要负载均衡,都会通过targeter.target()方法创建动态代理对象。我们这里跳过中间不重要的环节,给出targeter.target()不太重要的调用栈,大家可以自行查看:Targeter.target()→DefaultTargeter.target()→Feign.Builder.target()→Feign.newInstance()→ReflectiveFeign.newInstance()

接下来我们来到了ReflectiveFeign.newInstance()这个重要的方法:

@Override
public <T> T newInstance(Target<T> target) {// [1] nameToHandler 里面基本上是SynchronousMethodHandler,主要用于处理用户自定义的方法Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();// [2] DefaultMethodHandler用于处理接口中default方法List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();// 遍历接口中的所有方法for (Method method : target.type().getMethods()) {if (method.getDeclaringClass() == Object.class) {// [3] 如果是Object中的方法,直接跳过continue;} else if (Util.isDefault(method)) {// [4] 如果是default方法,则创建DefaultMethodHandler处理DefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {// [5] 其它的都是用户自定义的方法了,此时从nameToHandler中拿出SynchronousMethodHandler进行映射methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}// [6] 这个就是动态代理的关键了,代理逻辑都在该handler中了InvocationHandler handler = factory.create(target, methodToHandler);// 创建代理对象T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}// 返回代理对象return proxy;  
}

从newInstance中我们看到了熟悉的proxy和InvocationHandler,这也就说明了Feign的底层还是依赖了JDK的动态代理:

  • [1] 这里会通过targetToHandlersByName.apply()方法创建configKey→SynchronousMethodHandler的映射
  • [2] defaultMethodHandlers用于存储处理用户定义的FeignClient接口中的default方法的handler
  • [3] 如果是Object方法,这里直接跳过了,因为会在后面的InvocationHandler中处理Object方法的代理逻辑
  • [4] 如果是接口中的default方法,则创建DefaultMethodHandler并添加进defaultMethodHandlers列表和methodToHandler 映射中
  • [5] 创建method→SynchronousMethodHandler的映射
  • [6] 创建InvocationHandler 核心代理对象,代理逻辑都封装在该对象中。注意,这里传递了methodToHandler(method→MethodHandler)这个映射,MethodHandler可能是DefaultMethodHandler(处理default方法)或者SynchronousMethodHandler(处理用户定义的远程调用方法)。代理过程中,会根据方法名称dispatch到这个映射中对应的MethodHandler进行处理。

后面就是创建代理对象并返回了。

下面我们来看核心代理逻辑究竟做了什么,InvocationHandler实现类为FeignInvocationHandler,是ReflectiveFeign的静态内部类。

static class FeignInvocationHandler implements InvocationHandler {private final Target target;private final Map<Method, MethodHandler> dispatch;FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {this.target = checkNotNull(target, "target");// dispatch就是我们上文提到的methodToHandlerthis.dispatch = checkNotNull(dispatch, "dispatch for %s", target);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 这下面都是判断是否为Object中的方法if ("equals".equals(method.getName())) {try {Object otherHandler =args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;return equals(otherHandler);} catch (IllegalArgumentException e) {return false;}} else if ("hashCode".equals(method.getName())) {return hashCode();} else if ("toString".equals(method.getName())) {return toString();}// 如果是用户自定义的远程调用方法,则执行MethodHandler中的invoke方法return dispatch.get(method).invoke(args);}@Overridepublic boolean equals(Object obj) {if (obj instanceof FeignInvocationHandler) {FeignInvocationHandler other = (FeignInvocationHandler) obj;return target.equals(other.target);}return false;}@Overridepublic int hashCode() {return target.hashCode();}@Overridepublic String toString() {return target.toString();}
}

FeignInvocationHandler 还是非常简单的,dispatch就是我们上文提到的methodToHandler(method→MethodHandler)映射,在执行invoke方法时:

  • 如果是Object方法,则调用重写的方法处理
  • 如果是default方法,则从dispatch映射中获取对应的DefaultMethodHandler.invoke()处理
  • 如果是用户定义的远程调用方法,则从dispatch映射中获取对应的SynchronousMethodHandler.invoke()处理

这里我们仅关心远程调用的实现机制,因此下面我们将进入到SynchronousMethodHandler中,观察invoke()方法的执行逻辑:

@Override
public Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);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;}}
}

invoke方法将具体的远程调用委托给executeAndDecode()执行,从方法名可知,该方法不仅执行http远程调用,同时还会对response进行节码操作,这也是Feign非常方便的一点,能够让开发者忽略http报文解析的过程。

invoke还提供了失败重试机制,主要逻辑由Retryer 这个对象实现,感兴趣的小伙伴可以自行了解。

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 {// 执行http请求response = client.execute(request, options);// ensure the request is set. TODO: remove in Feign 12response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}throw errorExecuting(request, e);}// ...忽略解码过程
}

executeAndDecode()方法首先构造请求对象,然后使用client对象发起http请求。之前说了,client可以是Apache HttpClient或者Feign封装的具有负载均衡能力的FeignBlockingLoadBalancerClient或者RetryableFeignBlockingLoadBalancerClient,但这两个client的execute()方法底层最终会调用其中的delegate(即Apache HttpClient)执行http远程调用。

讲到这里,FeignClientFactoryBean#getObject方法的执行逻辑我们也非常清楚了,就是通过JDK的动态代理,对Apache HttpClient进行多层封装,以实现远程调用的能力。这里也用一张图梳理下整个过程:

在这里插入图片描述

4、总结

本文主要介绍了Feign的自动装载和动态代理机制,并梳理了这两个机制的主要脉络,而忽略其它次要信息。Feign能够被众多开发人员所使用绝不仅仅是具备以上介绍的两个功能,它还提供了诸如负载均衡、熔断等机制,这些也在文章中有少量提及,大家感兴趣的话,可以沿着本文所介绍的主脉络,一一梳理下这些功能。


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

相关文章

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;所有内容均仅仅记录是…

大学物理(下)知识点总结

大学物理&#xff08;下&#xff09;知识点总结&#xff08;持续更新&#xff09; 文章目录 大学物理&#xff08;下&#xff09;知识点总结&#xff08;持续更新&#xff09;静电场知识点总结习题精选 磁场知识点总结习题精选 期中复习总结电磁场知识点总结习题精选 狭义相对论…

Lumerical官方案例、FDTD时域有限差分法仿真学习(二)——宽带光栅耦合器(Broadband grating coupler (2D))

这个 2D FDTD 示例展示了如何获得光栅耦合器的宽带特性&#xff0c;并将结果与实验数据进行比较。 此外&#xff0c;该应用示例演示了多频波束计算的正确使用和优势&#xff0c;并将结果与标准单频波束计算进行了比较。 要更好地了解单频和多频波束计算之间的差异&#xff0c;请…

三维计算机视觉(四)--关键点

关键点又称为感兴趣的点&#xff0c;是低层次视觉通往高层次视觉的捷径&#xff0c;抑或是高层次感知对低层次处理手段的妥协。 RangeImage 1.关键点&#xff0c;线&#xff0c;面 关键点特征点&#xff1b; 关键线边缘&#xff1b; 关键面foreground&#xff1b; 上述三个概…

信号完整性基础04:串扰(1)

说在开头&#xff1a;关于x射线&#xff08;2&#xff09; 1895年11月8日&#xff0c;德国维尔茨堡大学的一间实验室里&#xff0c;校长伦琴正在做克鲁克斯管实验&#xff0c;当时房间没开灯&#xff0c;屋子里一片漆黑&#xff0c;放电管用黑纸包得很严实&#xff0c;他突然发…

基于地平面的单目视觉里程计绝对尺度估计

点击上方“3D视觉工坊”&#xff0c;选择“星标” 干货第一时间送达 ●论文摘要 对于基于单目相机的系统来说&#xff0c;从单目相机中恢复绝对度量尺度是一个具有挑战性的问题。利用有限的已知条件&#xff0c;提出了多种尺度估计方法&#xff0c;如根据相机的高度、物体大小等…

comsol 学习笔记【基础知识,磁场与结构场耦合为主】

感悟 随时补充学习中的感悟&#xff0c;放在开头是为了后来者在学习过程中有所参考。 掌握基本操作&#xff0c;学会寻找资料先看基础视频&#xff0c;然后找专题视频&#xff0c;再去学习案例&#xff08;以官方视频优先、案例配有相关的文件&#xff09;学习案例会涉及到其…

硬件工程师面试基础知识点

目录 一、晶振电路 二、LDO选型 1.LDO工作原理 2.输入电压 3.效率 4.功耗 5.电源抑制比PSRR 6.线性调整率 Line Regulation 7.负载调节率 (Load Regulation) 8.静态电流 9.噪声 10.输出电容器 11.反向泄漏保护 12.RF、音频的应用 三、DC-DC 1.降压转换器-Buck…