Feign源码详解

news/2024/10/10 8:28:55/

一,入口 —— Feign的核心注解

Feign是我们在分布式开发中常用的RPC框架,关于Feign远程调用的秘密,我有很多想要探究的例如:

  • Feign是如何收集FeignClient的?
  • Feign是如何配置FeignClient的,让其拥有降级重试的能力?
  • Feign整个远程调用的流程是怎么样的?
  • Feign他是如何创建代理类的?
  • Feign他是怎么去将一个Interface里面的接口方法作为请求发送的?
  • Feign是如何兼容这么多HttpClient框架的?
  • Feign他是怎么做到负载均衡的,如何和Ribbon结合的?

这么多繁杂的问题,往往扰人心智,不如我们从我们开发中最常见最常用的两个注解入手。

①,@EnableFeignClients

这个注解通常标注在启动类上,作用看起来和@SpringBootApplication一样,我们姑且猜测他的职能也和@SpringBootApplication一样是负责开启Feign功能以及扫描相关类,我们直接进入@EnableFeignClients来探究源码。

java">@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {// 根据包路径扫描其下的FeignClient,类似于basePackages的简写String[] value() default {};// 根据包路径扫描其下的FeignClientString[] basePackages() default {};// 指定标记的接口来扫描包Class<?>[] basePackageClasses() default {};// Feign客户端默认的全局配置类Class<?>[] defaultConfiguration() default {};// 指定@FeignClient注解的类,直接使用这几个@FeignClient,此时会ban掉类路径扫描Class<?>[] clients() default {};
}

可以通过源码和注释看出来,@EnableFeignClients的作用是用来扫描@FeignClient标注的类和指定全局配置类,值得注意的是在该注解最上方还有一个

@Import({FeignClientsRegistrar.class})

在我们使用@EnableFeignClientsFeignClientsRegistrar也会被引入至SpringBean容器的上下文。从名称可以看出这是FeignClient的注册类,我们再大胆猜测一下,FeignClientsRegistrar该类的职能应该是扫描和注册FeignClients,他肯定是接下来我们源码阅读的关接类之一,再次我们现在这里将其Mark一下,稍后回来。

②,@FeignClient

对于这个接口我们通常是标记在一个远程调用RPC的Interface类上面,但是其作用是用来标记还是用来声明呢,这个作用其实光从名称很难理解,我们可以看看作者留给我们的注释来熟悉一二:

注解用于声明某接口应创建为 REST 客户端(例如,用于自动注入到其他组件中)。如果 SC LoadBalancer 可用,它将用于对后端请求进行负载均衡,且负载均衡器可以使用与 Feign 客户端相同的名称(即 value)进行配置。

看完注释我们再结合代码来进行理解:

java">public @interface FeignClient {@AliasFor("name")// 服务名称 (name的简化版)String value() default "";// 接口生成的动态代理的bean IdString contextId() default "";@AliasFor("value")// 服务名称 (name的简化版)String name() default "";String[] qualifiers() default {};// 服务的url,对于开启了Loadbalance的服务无需使用url,如果没有开启则需要一个绝对的地址String url() default "";// 如果为false则404时默认抛出 FeignException异常,为true则抛出404异常boolean decode404() default false;// Feign客户端本身的配置类,可以对 feign.codec.Decoder, feign.codec.Encoder, feign.Contract.等等进行自定义Class<?>[] configuration() default {};// 失败回调方法Class<?> fallback() default void.class;// 用于生成fallback类实例Class<?> fallbackFactory() default void.class;// 定义当前FeignClient的统一前缀String path() default "";boolean primary() default true;}

看完源码和注释后,我们可以总结出FeignClient的功能:

用来声明FeignClient的一些基本属性,例如:bean name,url,服务名称,path等等

对FeignClient的一些核心功能组件进行配置,核心配置组件如下图所示(截图取自 bojiangzhou 大佬)

image

③ , Feign客户端配置和全局配置

看到上面两个主即我们可以发现,不管是@EnableFeignClients@FeignClient 都有配置项选择,那对于配置项我们该怎么使用呢

1,yaml配置项

feign:client:config:# 默认全局配置default:# 指定客户端名称genius-client:

2,代码配置项

java">public class GeniusFeignConfiguration {@Beanpublic Retryer feignRetryer() {return new Retryer.Default();}
}
java">@FeignClient(value = "genius-client", configuration = GeniusFeignConfiguration.class)
public interface GeniusFeignClient{}

可以看到对于客户端配置和全局配置的使用都很简单方便,但是值得注意的一点是这两个的配置项使用优先级是不同,这个我们后续再谈。

本章导图

image

二,FeignClientsRegister

我们重新回到之前mark的FeignClientsRegister先来看看整个类的实现

java">class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware{private ResourceLoader resourceLoader;private Environment environment;
}

可以看到FeignClientsRegister实现了三个接口

  • ResourceLoaderAware:注入资源加载器 ResourceLoader
  • EnvironmentAware:注入环境 Environment
  • ImportBeanDefinitionRegistrar:注册并注入BeanDefinition

BeanDefinition 实际上是Spring bean的元数据,它保存了Bean的很多属性,我们通常注册BeanDefinition的方式有 @Component,@Bean等。而实现ImportBeanDefinitionRegistrar也是一种注册BeanDefinition 的方式,它通过registerBeanDefinitions 方法来实现BeanDefinition 的注册。

通过这三个实现的接口,我们也找到了核心函数 registerBeanDefinitions,我们来看看他的代码:

java">/*** @param metadata @EnableFeignClients 注解的元数据* @param registry BeanDefinition 注册器*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 注册默认配置项registerDefaultConfiguration(metadata, registry);// 注册FeignClientsregisterFeignClients(metadata, registry);
}

可以看出整个BeanDefinitions的注册分为两个步骤:注册默认配置项注册FeignClients

① 注册默认配置项

这一步是将@EnableFeignClients上的defaultConfiguration内容,给注册为BeanDefinitions,如果不存在则不注册

java">private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 获取注解上的属性信息Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);// defaultConfiguration不为空则注入自定义默认配置项if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {String name;// 查看注解是不是标记在内部类上if (metadata.hasEnclosingClass()) {name = "default." + metadata.getEnclosingClassName();}else {name = "default." + metadata.getClassName();}// 注册自定义默认配置项registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));}
}

② 注册FeignClient

registerFeignClients方法 分为两大部分 扫描包注册FeignClient 我们接下来进入源码一一介绍:

1,扫描包找到所有FeignClient,放入候选队列

java">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");
// 不存在则启用扫描器,扫描包下的FeignClient
if (clients == null || clients.length == 0) {ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);// 设置扫描注解类型为 FeignClient.classscanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));// 获取注解中所有待扫描的包路径,value,basePackages,basePackageClasses,都不存在则添加EnableFeignClients标记对象所在的包路径Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {// 使用扫描器扫描并放入候选队列candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}
}
else {for (Class<?> clazz : clients) {// 将clients项放入候选队列candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));}
}

从上述我们可以明白Feigjn是如何获取项目中的所有FeignClient:

  • clients值不存在时,扫描包路径:value,basePackages,basePackageClasses 或者 (前面配置项都不存在时)EnableFeignClients标记对象所在的包路径
  • clients值存在时,直接使用clients中的值

2,校验并注册候选队列中的所有FeignClient标记的接口

java">for (BeanDefinition candidateComponent : candidateComponents) {// 对象是否是注解Beanif (candidateComponent instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition 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());// 获取Client配置名称,优先级如下:contextId, valueId, name, serviceIdString name = getClientName(attributes);// 注册Client服务配置,名称为Client配置名称registerClientConfiguration(registry, name, attributes.get("configuration"));// 注册FeignClientregisterFeignClient(registry, annotationMetadata, attributes);}
}

经过简单的验证和服务配置注册后我们来到核心方法registerFeignClient(registry, annotationMetadata, attributes)

3,registerFeignClient——注册FeignClient核心方法

java">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);// 获取名称 优先级 serviceId(弃用),name,value,并检验 http://+name是否合法String name = getName(attributes);// 构建FeignClientFactoryBean 工厂,职责是Feign创建代理类的工厂FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(isClientRefreshEnabled());// 设置构建Url,Path,404,fallback和fallbackFactory并返回代理类的supplier,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);// 校验fallback和fallbackFactory是否争取(放在这里校验是否有点,早期版本是放在前面的,不太明白为什么设置写完了supplier才校验)validate(attributes);AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);// 设置优先级boolean primary = (Boolean) attributes.get("primary");beanDefinition.setPrimary(primary);// 设置bean别名String[] qualifiers = getQualifiers(attributes);if (ObjectUtils.isEmpty(qualifiers)) {qualifiers = new String[] { contextId + "FeignClient" };}// 将信息放入BeanDefinitionHolderBeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);// 注册BeanBeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);registerOptionsBeanDefinition(registry, contextId);}

可以看到这里有个很重要的类 FeignClientFactoryBean,它实际上是实现了FactoryBean 接口,这个接口是Spring提供的一个工厂接口

java">public interface FactoryBean<T> {@NullableT getObject() throws Exception;
}

它里面提供了一个方法,用于获取工厂中所提供的Bean,当你实现并注册这个类时,获取该Bean名称获取的实际上是调用了 factory.getObject() 来获取其中的类对象。

FeignClientFactoryBean 它是创建FeignClient 代理类的工厂,他在后续的代理类生成中至关重要,我们在后面一章中来详细介绍他。

本章导图

image

三,FeignClientFactoryBean

在上一节我们知道FeignClientFactoryBean实现了FactoryBean,通过getObject()方法生成FeignClient,我们来详细看看他的方法实现

java">@Override
public Object getObject() {return getTarget();
}<T> T getTarget() {//1,配置Feign.BuilderFeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);//2.1,未设置url,则创建loadBalance Feignif (!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();//返回具备负载均衡能力的Targetreturn (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));}//2.2,设置了url,则创建具体的 FeignClientif (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) {client = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 创建动态代理对象TargetTargeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}

我们可以看到FeignClientFactoryBean创建FeignClient对象分为两个步骤:构建Builder和创建不同的代理对象

1,Feign.Builder构建和设置配置项

在构建Feign.Builder前,他会先获取整个Feign配置的上下文环境,也就是FeignContext。之后他便会定制Encoder,Dcoder,Contract等组件的Feign Builder模板,通过一些配置代码和文件进行Builder的设置。

java">protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(type);// 定制Encoder,Dcoder,Contract等组件的Feign Builder模板Feign.Builder builder = get(context, Feign.Builder.class).logger(logger).encoder(get(context, Encoder.class)).decoder(get(context, Decoder.class)).contract(get(context, Contract.class));// 通过配置代码和配置项初始化Feign.BuilderconfigureFeign(context, builder);applyBuildCustomizers(context, builder);return builder;}

通过下面我们可以知道整个FeignClient的一个配置优先级如下:

自定义服务配置项 > 默认配置项 > 代码配置Config配置

java">protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class): applicationContext.getBean(FeignClientProperties.class);FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());if (properties != null && inheritParentContext) {if (properties.isDefaultToProperties()) {// 低优先级,代码配置Config配置configureUsingConfiguration(context, builder);// 中优先级,默认配置项 feign.client.defaultconfigureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);// 高优先级,自定义服务配置项 feign.client.<clientName>configureUsingProperties(properties.getConfig().get(contextId), builder);}//.......}else {//通过开关优先使用代码配置项configureUsingConfiguration(context, builder);}
}

2.1,创建loadBalance Feign

在了解LoadBalance时我们先了解一下Client接口,Client接口它有许多HttpClient实现类,他的功能就是将request发出然后将返回值封装进入Response中

java">public interface Client {Response execute(Request var1, Request.Options var2) throws IOException;...
}

他的实现类如下,他会通过一些自动配置类(10.12移除了自动配置类,需要自行构建Bean),来使用其他框架的HttpClient,例如OkHttp,ApacheHttp等等,他有一个默认实现的即Client.Default

image

可以看到Client.Default中包含了一些SSL和域名校验的配置,可以通过自行配置Default来构造Feign Https链路。

java">public static class Default implements Client {private final SSLSocketFactory sslContextFactory;private final HostnameVerifier hostnameVerifier;private final boolean disableRequestBuffering;public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {this.sslContextFactory = sslContextFactory;this.hostnameVerifier = hostnameVerifier;this.disableRequestBuffering = true;}
}

再回到loadbalance方法我们可以发现,Client或根据当前使用HttpClient框架选择注入不同的RibbonClient,并将client放入Fegin.Builder中来生成代理对象Targeter

java">protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {// 获取ClientClient 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?");
}

四,生成代理对象

我们可以看到DefaultTargeter 会使用 FeignBuilder的target()方法生成代理对象

java">class DefaultTargeter implements Targeter {@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,Target.HardCodedTarget<T> target) {return feign.target(target);}}

在进行Feign的一系列组件配置后,创建ReflectiveFeign来进行动态代理Feign创建

java">public <T> T target(Target<T> target) {return this.build().newInstance(target);
}public Feign build() {// Feign Http调用客户端,默认为 Client.DefaultClient client = (Client)Capability.enrich(this.client, this.capabilities);// 重试器Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);// Feign 请求拦截器List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {return (RequestInterceptor)Capability.enrich(ri, this.capabilities);}).collect(Collectors.toList());// 日志组件,默认slf4jLogger logger = (Logger)Capability.enrich(this.logger, this.capabilities);// 接口协议Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);// 配置类Request.Options options = (Request.Options)Capability.enrich(this.options, this.capabilities);// 编码器Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);// 解码器Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);// 创建 InvocationHandler 的工厂类InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);// 接口方法处理器工厂SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);// 解析 springmvc 注解 ReflectiveFeign.ParseHandlersByName handlersByName = new ReflectiveFeign.ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);// ✨ 动态创建Feign对象return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

ReflectiveFeign.newInstance()会通过InvocationHandlerFactory.create()来进行动态代理创建InvocationHandler

java">public <T> T newInstance(Target<T> target) {//通过contract(即SpringMvcContract)将FeignClient接口转换成MethodHandlerMap<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 handler = factory.create(target, methodToHandler);// 用 Proxy 创建动态代理,动态代理对象就是 SynchronousMethodHandlerT proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;
}

他的默认实现是InvocationHandlerFactory.Default然后创建一个ReflectiveFeign.FeignInvocationHandler

java">public static final class Default implements InvocationHandlerFactory {public Default() {}public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);}
}

五,一图流总结

🎉特别感谢( bojiangzhou 大佬)🎉

image


http://www.ppmy.cn/news/1536912.html

相关文章

K8sGPT 实战:智能化 Kubernetes 集群诊断与问题解决

引言 在复杂的 Kubernetes 环境中,快速识别和解决问题是一项挑战。K8sGPT 作为一个创新的工具,将人工智能的力量引入到 Kubernetes 运维中,为管理员提供了智能化的诊断和问题解决方案。本文将深入探讨 K8sGPT 的实际应用,通过实战案例展示其如何提高 Kubernetes 集群的运维…

k8s的pod管理及优化

1. Pod的基本管理命令 创建Pod&#xff1a;可以使用kubectl apply -f pod.yaml或kubectl run nginx --imagenginx来创建Pod。查看Pod&#xff1a;使用kubectl get pods命令可以查看当前集群中的所有Pod。查看日志&#xff1a;使用kubectl logs <pod-name>可以查看指定Po…

Python 与 Pycharm 的简易安装教程,包含Pycharm的修改

一. 官方网站 Python网址&#xff1a;python唯一的官方网址。 Pycharm网址&#xff1a;Pycharm的官方网址。 二. python安装步骤 滑动到红色框内 Downloads 导航栏。 红色框是选择适合自己电脑系统和版本的部分&#xff0c;蓝色框是选择系统的部分&#xff0c;黄色框是版本号。…

减少重复的请求之promise缓存池(闭包版) —— 缓存promise,多次promise等待并返回第一个promise的结果

减少重复的请求之promise缓存池 —— 缓存promise&#xff0c;多次promise等待并返回第一个promise的结果 背景简介 当一个业务组件初始化调用了接口&#xff0c;统一个页面多吃使用同一个组件&#xff0c;将会请求大量重复的接口 如果将promise当作一个普通的对象&#xff0…

大数据毕业设计选题推荐-B站热门视频数据分析-Python数据可视化-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

【汇编语言】寄存器(CPU工作原理)(二)—— 汇编指令的基础操作

文章目录 前言正文——&#xff08;一气呵成解决本文内容&#xff09;结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅从课程的角度出发就太片面了&#xff0c;其实学习汇编语言可以深入理解计算机底…

平时使用的正则总结

1、将某一个字符串的后缀名后面加上“!400_500” 使用场景是将minio拿过来的图片压缩尺寸从而压缩其大小&#xff0c;加快渲染的速度。需要在图片的后缀名后面加上尺寸如下&#xff1a; const str //storage-test.test.shiqiao.com/gateway/common/isopen/2024/10/09/e708e9…

基于springboot+vue人脸识别的考勤管理系统(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…