【OpenFeign】【源码+图解】【五】创建FeignClient接口的代理(上)

news2024/11/18 5:54:50

【OpenFeign】【源码+图解】【四】FeignClient实例工具类ReflectiveFeign

目录

  • 6. 创建FeignClient接口的代理
    • 6.1 收集方法的元数据
      • 6.1.1 方法上基础信息
      • 6.1.2 方法所在类的注解信息
      • 6.1.3 方法上的注解信息
        • 6.1.3.1 uri
        • 6.1.3.2 produces
        • 6.1.3.3 consumes
        • 6.1.3.4 headers
          • @RequestMapping
          • @RequestHeader
          • RequestInterceptor
      • 6.1.4 参数上的注解信息
        • 6.1.4.1 参数类型
        • 6.1.4.2 参数上的Annotations
          • 6.1.4.2.1 AnnotatedParameterContext
          • 6.1.4.2.2 AnnotatedParameterProcessor
          • 6.1.4.2.3 ConversionService&Param.Expander
        • 6.1.4.3 URI

6. 创建FeignClient接口的代理

上一节讲到了ReflectiveFeign.newInstance()

public class ReflectiveFeign extends Feign {
    @Override
    public <T> T newInstance(Target<T> target) {
        // 1、创建SynchronousMethodHandler(6.3节)
        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()) {
            // 过滤父类Object对象的方法
            if (method.getDeclaringClass() == Object.class) {
                continue;
            } else if (Util.isDefault(method)) {
                // 如果是default方法则绑定DefaultMethodHandler
                DefaultMethodHandler handler = new DefaultMethodHandler(method);
                defaultMethodHandlers.add(handler);
                methodToHandler.put(method, handler);
            } else {
                // 2、其他的则将SynchronousMethodHandler与ProductFeignClient的方法绑定
                methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
            }
        }
        // 3、创建FeignInvocationHandler(6.4节)
        InvocationHandler handler = factory.create(target, methodToHandler);
        // 4、创建ProductFeignClient的代理。
        // 当调用ProductFeignClient的方法时由proxy代理,交给FeignInvocationHandler处理
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                                             new Class<?>[] {target.type()}, handler);

        for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
            defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
    }
}

创建SynchronousMethodHandler需要先收集FeignClient方法上的元信息以及创建RequestTemplate.Factory,所以接下来会分四步分析:

  1. 创建MethodMetadata,收集方法上的元数据
  2. 创建RequestTemplate.Factory
  3. 创建SynchronousMethodHandler
  4. 创建FeignInvocationHandler

6.1 收集方法的元数据

MethodMetadata是由Contract创建的,先看下其类图

在这里插入图片描述

它的作用是解析并检验FeignClient接口的方法,默认的Contract是SpringMvcContract,接下来就以它的parseAndValidateMetadata方法开始

abstract class BaseContract implements Contract {
    @Override
    public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
        // ......
        final Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
        for (final Method method : targetType.getMethods()) {
            // ......
            // 创建MethodMetadata,获取方法上的信息
            final MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
            if (result.containsKey(metadata.configKey())) {
                // 如果已经处理过,则覆盖原值并返回新类型
                MethodMetadata existingMetadata = result.get(metadata.configKey());
                Type existingReturnType = existingMetadata.returnType();
                Type overridingReturnType = metadata.returnType();
                Type resolvedType = Types.resolveReturnType(existingReturnType, overridingReturnType);
                if (resolvedType.equals(overridingReturnType)) {
                    result.put(metadata.configKey(), metadata);
                }
                continue;
            }
            result.put(metadata.configKey(), metadata);
        }
        return new ArrayList<>(result.values());
    }
}

接下来着重分析parseAndValidateMetadata(targetType, method),分四部分分析该方法:

  1. 基础信息
  2. 类级别的Annotation
  3. method上的Annotation
  4. 参数Parameters

流程图如下

在这里插入图片描述

6.1.1 方法上基础信息

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    @Override
    public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        // 1、记录处理过的method
        processedMethods.put(Feign.configKey(targetType, method), method);
        return super.parseAndValidateMetadata(targetType, method);
    }
}

abstract class BaseContract implements Contract {
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        // 2、
        final MethodMetadata data = new MethodMetadata();
        data.targetType(targetType); // 接口类型
        data.method(method); // 方法
        data.returnType(
            Types.resolve(targetType, targetType, method.getGenericReturnType())); // 返回类型
        data.configKey(Feign.configKey(targetType, method)); // configKey
        if (AlwaysEncodeBodyContract.class.isAssignableFrom(this.getClass())) {
            data.alwaysEncodeBody(true); // 如果是AlwaysEncodeBodyContract类则设置此值,默认是SpringMvcContract
        }
        ......
    }
}

这里主要讲一下metadata.configKey()

举个例子,假设有下面的方法

@FeignClient("product")
public interface ProductFeignClient extends SecondInterface{
    
    @GetMapping("/test")
    public void configKey(String key, List<String> ids);
}

则它的configKeyProductFeignClient#configKey(String,List)),即类名+方法名+#+(参数类型))

6.1.2 方法所在类的注解信息

abstract class BaseContract implements Contract {
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        ......
        if (targetType.getInterfaces().length == 1) {
            // 1、如果继承了其他Interface,先处理父类Interface
            processAnnotationOnClass(data, targetType.getInterfaces()[0]);
        }
        // 2、再处理自己的
        processAnnotationOnClass(data, targetType);
        ......
    }
}

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
        RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
        // Interface上不能有@RequestMapping以及被它注解的,如@GetMapping等
        if (classAnnotation != null) {
            throw new IllegalArgumentException("@RequestMapping annotation not allowed on @FeignClient interfaces");
        }
        CollectionFormat collectionFormat = findMergedAnnotation(clz, CollectionFormat.class);
        if (collectionFormat != null) {
            // 如果Interface上有@CollectionFormat,则设置它的值,主要用来设置查询参数集合的分隔符号
            data.template().collectionFormat(collectionFormat.value());
        }
    }
}

6.1.3 方法上的注解信息

abstract class BaseContract implements Contract {
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        ......
        for (final Annotation methodAnnotation : method.getAnnotations()) {
            // 调用SpringMvcContract的processAnnotationOnMethod
            processAnnotationOnMethod(data, methodAnnotation, method);
        }
        ......
    }
}

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    @Override
	protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
        // 如果方法上有@CollectionFormat会覆盖类级别的
		if (CollectionFormat.class.isInstance(methodAnnotation)) {
			CollectionFormat collectionFormat = findMergedAnnotation(method, CollectionFormat.class);
			data.template().collectionFormat(collectionFormat.value());
		}
        // 如果没有@RequestMapping等方法注解则忽略,直接返回
		if (!RequestMapping.class.isInstance(methodAnnotation)
				&& !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
			return;
		}
        // 设置HttpMethod
		RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
		RequestMethod[] methods = methodMapping.method();
		......
		data.template().method(Request.HttpMethod.valueOf(methods[0].name()));
		// 设置uri
        if (methodMapping.value().length > 0) {
            String pathValue = emptyToNull(methodMapping.value()[0]);
            if (pathValue != null) {
                // 将path中的${}变量替换成yml中的值
                pathValue = resolve(pathValue);
                // 见6.1.1.3.1 
                data.template().uri(pathValue, true);
                if (data.template().decodeSlash() != decodeSlash) {
                    data.template().decodeSlash(decodeSlash);
                }
            }
        }
		// produces, 见6.1.1.3.2 
		parseProduces(data, method, methodMapping);
		// consumes, 见6.1.1.3.3 
		parseConsumes(data, method, methodMapping);
		// headers, 见6.1.1.3.4 
		parseHeaders(data, method, methodMapping);
		data.indexToExpander(new LinkedHashMap<>());
	}
}

6.1.3.1 uri

data.template().uri(pathValue, true)具体代码不分析,看流程图

在这里插入图片描述

6.1.3.2 produces

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    private void parseProduces(MethodMetadata md, Method method, RequestMapping annotation) {
		String[] serverProduces = annotation.produces();
        // 只设置第一个
		String clientAccepts = serverProduces.length == 0 ? null : emptyToNull(serverProduces[0]);
		if (clientAccepts != null) {
            // 设置Accept的值为clientAccepts
			md.template().header(ACCEPT, clientAccepts);
		}
	}
}

6.1.3.3 consumes

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    private void parseConsumes(MethodMetadata md, Method method, RequestMapping annotation) {
		String[] serverConsumes = annotation.consumes();
        // 只设置第一个
		String clientProduces = serverConsumes.length == 0 ? null : emptyToNull(serverConsumes[0]);
		if (clientProduces != null) {
            // 设置Content-Type的值为clientProduces
			md.template().header(CONTENT_TYPE, clientProduces);
		}
	}
}

6.1.3.4 headers

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    private void parseHeaders(MethodMetadata md, Method method, RequestMapping annotation) {
		if (annotation.headers() != null && annotation.headers().length > 0) {
            // headers可设置多个
			for (String header : annotation.headers()) {
				int index = header.indexOf('=');
				if (!header.contains("!=") && index >= 0) {
                    // ${}使用yml文件中的配置
					md.template().header(resolve(header.substring(0, index)),
							resolve(header.substring(index + 1).trim()));
				}
			}
		}
	}
}

对于headers的值可以通过三种方式获取:@RequestMapping@RequestHeaderRequestInterceptor

@RequestMapping

可以从yml配置读取值

custom-headers: lanna
@FeignClient("product")
public interface ProductFeignClient extends SecondInterface{
    
    @GetMapping(value = "/product", headers = { "author=${custom-headers}", "id=8001"})
    public String getProduct(@RequestParam("ids") List<String> ids);
}
@RequestHeader
@FeignClient("product")
public interface ProductFeignClient extends SecondInterface{

    @GetMapping(value = "/product")
    public String getProduct(@RequestParam("ids") List<String> ids, @RequestHeader("author") String author);
}
RequestInterceptor

实现RequestInterceptor

@Configuration
public class MyRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        template.header("author", "lanna");
    }

}

配置到FeignClient

public class MyConfiguration { 
    @Bean
    MyRequestInterceptor requestInterceptor() {
        return new MyRequestInterceptor();
    }
}

// 配置MyRequestInterceptor
@FeignClient(value = "product", configuration = MyConfiguration.class)
public interface ProductFeignClient extends SecondInterface{

    @GetMapping(value = "/product")
    public String getProduct(@RequestParam("ids") List<String> ids);
}

对于produces和consumes的值也可采用相同的方法

6.1.4 参数上的注解信息

abstract class BaseContract implements Contract {
    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        ......
        // 1、对于parameterTypes、genericParameterTypes的区别见6.1.1.4.1 
        final Class<?>[] parameterTypes = method.getParameterTypes();
        final Type[] genericParameterTypes = method.getGenericParameterTypes();
        final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        final int count = parameterAnnotations.length;
        for (int i = 0; i < count; i++) {
            boolean isHttpAnnotation = false;
            if (parameterAnnotations[i] != null) {
                // 2、processAnnotationsOnParameter
                isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
            }

            if (isHttpAnnotation) {
                // 这里使用BitSet的形式保存ignoreParamater的配置
                data.ignoreParamater(i);
            }

            if (parameterTypes[i] == URI.class) {
                // 3、如果是URI类,则请求的路径则使用它
                data.urlIndex(i);
            } else if (!isHttpAnnotation
                       && !Request.Options.class.isAssignableFrom(parameterTypes[i])) {
                if (data.isAlreadyProcessed(i)) {
                    checkState(data.formParams().isEmpty() || data.bodyIndex() == null,
                               "Body parameters cannot be used with form parameters.%s", data.warnings());
                } else if (!data.alwaysEncodeBody()) {
                    checkState(data.formParams().isEmpty(),
                               "Body parameters cannot be used with form parameters.%s", data.warnings());
                    checkState(data.bodyIndex() == null,
                               "Method has too many Body parameters: %s%s", method, data.warnings());
                    // 设置bodyIndex和bodyType,用于加密
                    data.bodyIndex(i);
                    data.bodyType(
                        Types.resolve(targetType, targetType, genericParameterTypes[i]));
                }
            }
        }
        ......
        return data;
    }
}

6.1.4.1 参数类型

假设有一个方法如下

@FeignClient(value = "product")
public interface ProductFeignClient extends SecondInterface{

    @GetMapping(value = "/product")
    public String getProduct(@RequestParam("ids") List<String> ids, @RequestParam("status") Object status);
}

则parameterTypes、genericParameterTypes的区别如下,注意List

在这里插入图片描述

6.1.4.2 参数上的Annotations

在分析SpringMvcContract的processAnnotationsOnParameter方法前必须先了解下面这4个类:

  1. AnnotatedParameterContext参数容器(见6.1.1.4.2.1)
  2. AnnotatedParameterProcessor参数处理器(见6.1.1.4.2.2)
  3. ConversionService参数类型转换器(见6.1.1.4.2.3)
  4. Param.Expander参数转换器(见6.1.1.4.2.3)

在这里插入图片描述

通过类图大体理清楚了SpringMvcContract与各个参数的关系,那么接下来就要弄清楚这些参数的赋值过程,SpringMvcContract是通过FeignClientsConfiguration文件注入的,分析下SpringMvcContract的构造函数

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
			ConversionService conversionService, boolean decodeSlash) {
        // AnnotatedParameterProcessor的7个实现类都会添加进来
		List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
		processors.addAll(annotatedParameterProcessors);
        // 将processors转成Map,key为AnnotatedParameterProcessor.ANNOTATION,value为processor
		annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
        // DefaultFormattingConversionService
		this.conversionService = conversionService;
		convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
        // feign.client.decodeSlash,默认true
		this.decodeSlash = decodeSlash;
	}
}

这样SpringMvcContract就创建好了,接下来先逐个介绍前面那4个类的功能

6.1.4.2.1 AnnotatedParameterContext

在这里插入图片描述

因为AnnotatedParameterContext的实现类只有一个,所以我们值分析SimpleAnnotatedParameterContext

private class SimpleAnnotatedParameterContext implements AnnotatedParameterProcessor.AnnotatedParameterContext {

    private final MethodMetadata methodMetadata;

    private final int parameterIndex;
    
    // 1、 向methodMetadata添加参数名字
    @Override
    public void setParameterName(String name) {
        nameParam(methodMetadata, name, parameterIndex);
    }

    // 2、向methodMetadata添加模板参数名字
    @Override
    public Collection<String> setTemplateParameter(String name, Collection<String> rest) {
        return addTemplateParameter(rest, name);
    }

}
6.1.4.2.2 AnnotatedParameterProcessor

在这里插入图片描述

其7个实现类的功能如下

在这里插入图片描述

6.1.4.2.3 ConversionService&Param.Expander

从前面的类图中可以看出ConversionService主要是作为ConvertingExpanderFactory的参数使用,所以我们只看ConvertingExpanderFactory

private static class ConvertingExpanderFactory {

    private final ConversionService conversionService;

    ConvertingExpanderFactory(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    Param.Expander getExpander(TypeDescriptor typeDescriptor) {
        // 返回Param.Expander。该Param.Expander的作用就是利用conversionService将类型为typeDescriptor的value转换成STRING_TYPE_DESCRIPTOR
        return value -> {
            Object converted = conversionService.convert(value, typeDescriptor, STRING_TYPE_DESCRIPTOR);
            return (String) converted;
        };
    }

}

经过前面的介绍就可以很容易分析SpringMvcContract的processAnnotationsOnParameter方法了

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    @Override
    protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
        boolean isHttpAnnotation = false;

        // 1、创建参数容器
        AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data,
                                                                                                            paramIndex);
        // 获取方法
        Method method = processedMethods.get(data.configKey());
        // 遍历annotation
        for (Annotation parameterAnnotation : annotations) {
            // 2、annotation参数处理器
            AnnotatedParameterProcessor processor = annotatedArgumentProcessors
                .get(parameterAnnotation.annotationType());
            // 处理器processor存在则处理annotation参数值
            if (processor != null) {
                Annotation processParameterAnnotation;
                // 给@AliasFor的参数赋值
                processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation,
                                                                                              method, paramIndex);
                // 设置isHttpAnnotation,如果是AnnotatedParameterProcessor七个实现类中的ANNOTATION之一,则isHttpAnnotation=true
                isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method);
            }
        }

        if (!isMultipartFormData(data) && isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {
            // 3、创建类型描述符
            TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);
            // 4、如果能转化为String,则添加参数转换器
            if (conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) {
                // 5、创建转换为String的参数转换器
                Param.Expander expander = convertingExpanderFactory.getExpander(typeDescriptor);
                if (expander != null) {
                    // 设置该参数的转换器。将复杂类型转换成String
                    data.indexToExpander().put(paramIndex, expander);
                }
            }
        }
        return isHttpAnnotation;
    }
}

6.1.4.3 URI

修改下Controller和FeignClient

@RestController
@RequestMapping("/feign")
public class ProductController {
    @Autowired
    private ProductFeignClient productFacade = null;

    @GetMapping("/product")
    public String getProduct() throws URISyntaxException {
        // 赋值的URI必须是绝对路径
        return productFacade.getProduct(null, new URI("http://product/product"));
    }
}

@FeignClient(value = "product")
public interface ProductFeignClient extends SecondInterface{

    // 为了测试URI我们把@GetMapping中的value值去掉
    @GetMapping
    public String getProduct(@RequestParam("ids") List<String> ids, URI uri);
}

重新启动后发起请求,依然能拿到结果。证明我们可以直接传参URI类型赋值请求URL。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/113065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

认识 TEE OS

了解 TEE OS 关于本文&#xff0c;是一篇会议纪要&#xff0c;会议主题是《从 Linux Kernel 角度看 TEE》&#xff0c;主讲人是周贺贺。它适用于嵌入式系统开发/驱动开发/内核设计/安全业务设计从业者&#xff0c;目的是让自己掌握 TEE 基本概念&#xff0c;知道大系统软件架构…

目标检测之YOLOv1算法分析

网络结构 卷积层池化层全连接层 输入448∗448448*448448∗448大小的图片 输出7∗7∗307*7*307∗7∗30的张量 30维张量包括20个对象的概率&#xff0c;2个bounding box的置信度及其位置&#xff08;一个bounding box位置信息需要四个值&#xff0c;两个bounding box总需要8个…

【C/C++】排序讲解,C语言实现各种排序

这篇文章会从思路到实现到分析时间空间复杂度&#xff0c;一次性搞懂各种排序 有帮助的话点个赞收藏一下不迷路啊 如果对时间空间复杂度还不熟悉的请去看 时间 空间复杂度 本文章不会讲堆排序&#xff0c;这部分内容会马上单写一篇博客介绍&#xff0c;和堆的和一些更复杂的问题…

C++基础之核心3

C核心编程 本阶段主要针对C面向对象编程技术做详细讲解&#xff0c;探讨C中的核心和精髓。 1 内存分区模型 C程序在执行时&#xff0c;将内存大方向划分为4个区域 代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的全局区&#xff1a;存放全局变量…

海格里斯HEGERLS标准解析|夹抱式四向穿梭车医用行业现代物流解决方案

众所周知&#xff0c;随着当前电商、医用、新零售等领域的快速发展&#xff0c;各大中小企业对于存储的要求越来越高&#xff0c;为让仓储货架、仓储设备、仓储配件等更具有行业的适配性&#xff0c;传统固定的穿梭车已不能满足对不同尺寸料箱的处理。为此&#xff0c;河北沃克…

二叉树题型

目录 二叉数遍历迭代法 1.1前序遍历 1.2中序遍历 1.3后续遍历 二叉树最小深度 二叉树所有路径 中序后序构造二叉树 验证二叉搜素树 二叉数遍历迭代法 1.1前序遍历 前序遍历顺序&#xff1a;根—左—右&#xff1b; 解法1&#xff1a;用栈来进行中间过程处理&#xf…

Babel和devServer | Webpack

文章目录Babel和devServerbabelbabel命令行使用babel-loaderVue源码的打包VSCode对SFC文件的支持vue-loaderdevServerBabel和devServer babel babel命令行使用 babel-loader Vue源码的打包 VSCode对SFC文件的支持 vue-loader devServer

一键可以轻松替换人物背景图,效果出乎意料的好(附 Python 代码)

最近发现 BackgroundMattingV2 项目的一些使用上的小缺陷&#xff0c;但是他却可以做到头发丝精细的抠图效果。我将项目稍微魔改了一下&#xff0c;让他在可以选择单一图片的基础上&#xff0c;可以把抠好的图片贴在自定义的背景图上&#xff0c;这样就可以让照片中的人物&…

使用Tomcat时出现Access Error: 404 -- Not Found的解决办法

当时出现这种情况很迷惑&#xff0c;错误提示如下 突然想到大二上学模电的时候安装过multisim&#xff0c;这个软件的某些不必要的服务占用了8080端口&#xff08;恼&#xff09;&#xff0c;出现这种情况应该是和Tomcat默认的端口冲突了。 于是就有了解决思路&#xff1a; …

stm32f407VET6 系统学习 day03 通用同步异步收发器

1.同步串行通信 同步通信发送端和接收端必须用共同的时钟源才能保持它们之间的准确同步。同步传输时&#xff0c;每个字 符没有起始位和停止位&#xff0c;它不是用起始位来标志字符的开始&#xff0c;而是用一串特定的二进制序列&#xff0c;称为 同步字符&#xff0c;去通知接…

[思维模式-14]:《复盘》-2- “知”篇 - 复盘之道

目录 前言&#xff1a; 一、U型学习法&#xff1a;复盘的学习机理 &#xff08;1&#xff09; 回顾、评估 &#xff08;2&#xff09;分析、反思 &#xff08;3&#xff09;萃取、提炼 &#xff08;4&#xff09;转化、应用 二、复盘与PDCA既有区别&#xff0c;也有联系…

solr 安装和使用

Solr是基于ApacheLucene构建的流行、快速、开源的企业搜索平台 Solr具有高度可靠性、可扩展性和容错性&#xff0c;提供分布式索引、复制和负载平衡查询、自动故障切换和恢复、集中配置等功能。Solr为许多世界上最大的互联网站点提供搜索和导航功能 环境准备 linux centos7 ja…

如何用iDesktop快速制作一幅研究区概况图

目录前言数据准备成果展示制作步骤前言 研究区概况图能直观展示研究区域的地理位置&#xff0c;在许多研究展示与论文撰写中必不可少。本文将以成都市为例&#xff0c;利用SuperMap桌面产品iDesktop快速制作一幅研究区概况图。 数据准备 四川省行政区划矢量数据&#xff08;…

【语音处理】LQ/QR噪声估计器研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

【Javascript】循环,函数,调用栈,闭包,递归

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录函数作用域例子调用栈可选参数闭包递归函数 形参不需要定义var&#xff0c;函数可以付给一个变量…

Android Qcom USB Driver学习(八)

该系列文章总目录链接与各部分简介&#xff1a; Android Qcom USB Driver学习(零) 因为要看usb charging的问题&#xff0c;所以需要补充一下battery的相关知识&#xff0c;算是入门吧 BAT SCH (1)VBATT_VSNS_P (2)BAT_THERM (3)I2C_SDA (4)I2C_SCL (5)VBATT_VSNS_M (1)BATT…

【问题分析】解决java中epoll依赖缺失问题

【问题分析】解决java中epoll依赖缺失问题一、前言二、问题描述三、问题分析四、解决方法五、总结一、前言 在学习使用lettuce框架实现UNIX域套接字unix domain socket连接redis时&#xff0c;遇到了一个问题&#xff0c;提示java.lang.IllegalStateException: A unix domain …

Java Stream后续来了,汇总一些项目开发中高频使用的 Stream操作

不过讲解这些操作时用的都是非常简单的例子&#xff0c;流操作的数据也都是简单类型的&#xff0c;主要的目的是让大家能更快速地理解 Stream 的各种操作应用在数据上后&#xff0c;都有什么效果。 在现实场景中实际做项目的时候&#xff0c;我们使用Stream操作的数据大多数情…

OpenCV颜色识别

颜色分辨 单个颜色识别 代码 import cv2 import numpy as npdef color(lower, upper, name):Img cv2.imread(image/origin/all.png) # 读入一幅图像kernel_3 np.ones((3, 3), np.uint8) # 3x3的卷积核if Img is not None: # 判断图片是否读入HSV cv2.cvtColor(Img, cv2…

maven中profiles使用详解,多环境开发配置文件(开发,测试,生产)+ pom中resources部分标签介绍

一.maven中profiles使用详解&#xff08;仅供参考&#xff09; 使用的场景 常常遇到一些项目中多环境切换的问题。比如在开发过程中用到开发环境&#xff0c;在测试中使用测试环境&#xff0c;在生产中用生产环境的情况。springboot中提供了 spring.profile.active的方式来实…