Open Feign 源码解析(三) --- 配置体系详解

news2024/11/26 20:21:11

Open Feign 源码解析三 配置体系

配置类

应用级别配置(全局)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) // 注册feign client的bean定义
public @interface EnableFeignClients {

	String[] value() default {};
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
	Class<?>[] defaultConfiguration() default {}; // 默认配置全局有效
	Class<?>[] clients() default {};
}
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = {DefaultConfiguration.class}) // 配置在启动类上
@EnableDiscoveryClient
public class FeignClientMain {
	// ...
}
服务级别配置
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
    // ...
    Class<?>[] configuration() default {}; // 只对服务接口有效
    // ...
}
// 配置在服务接口
@FeignClient(value = "cloud-feign-server", contextId = "order", configuration = OrderConfiguration.class)
public interface OrderService {
	// ...
}

@FeignClient(value = "cloud-feign-server", contextId = "user", configuration = UserConfiguration.class)
public interface UserService {
    // ...
}
配置隔离原理

一句话:通过spring子容器进行隔离,不同的feign client接口对应不同的子容器,里面有自己独立的配置

1) 注册配置类到spring父容器
class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
    /** ImportBeanDefinitionRegistrar的方法 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
    
    /** 注册默认的配置类 */
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        // 获取EableFeignClients注解的信息
		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"));
		}
	}
    
    public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        // ...

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					// 获取BeanDefinition元信息
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                   
                    // 判断是否是接口
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

                    // 通过元信息过去FeignClient注解中的信息
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
                    // 注册服务接口的配置类
					registerClientConfiguration(registry, name, attributes.get("configuration"));
					// 注册一个个的FeignClient的接口转换成BeanDefinition放在Bean定义容器中交给spring
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
    }
    
    // 注意这里不是注册配置类本身 注册的是FeignClientSpecification 但里面封装了配置类
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
        // 注册FeignClientSpecification的bean定义
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientSpecification.class);
		builder.addConstructorArgValue(name);
        // 把配置类通过构造方法传入
		builder.addConstructorArgValue(configuration);
		registry.registerBeanDefinition(
				name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}
}

注意不是直接注册配置类本身,而是 FeignClientSpecification 类

2) 注入配置类到FeignContext
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
		FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {

    // 把所有FeignClientSpecification对象注入到集合里面
	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();

	@Bean
	public FeignContext feignContext() {
		FeignContext context = new FeignContext();
        // 配置就是全局配置和局部配置类封装成的一个个FeignClientSpecification
		context.setConfigurations(this.configurations);
		return context;
	}
}
3) 从FeignContext中获取组件
class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
	// ...
    
    // 使用配置类进行配置
	protected void configureUsingConfiguration(FeignContext context,
			Feign.Builder builder) {
        // 从spring容器获取组件
        Logger.Level level = getOptional(context, Logger.Level.class);
        
        // ...
        
        // 从spring容器获取组件
        Map<String, RequestInterceptor> requestInterceptors = context
				.getInstances(this.contextId, RequestInterceptor.class);
        
        // ...
	}
    
    protected <T> T getOptional(FeignContext context, Class<T> type) {
		return context.getInstance(this.contextId, type);
	}
}
4) 创建子容器加载配置
// FeignContext
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

	public FeignContext() {
        // 传入FeignClients的官方默认配置类
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}
}

// 带名字的上下文工厂
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
    
    public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType; // 传入官方默认配置类
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}
    
    // 存储子容器的Map
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    // value是FeignClientSpecification对象
	private Map<String, C> configurations = new ConcurrentHashMap<>(); 

    // 父容器 通过ApplicationContextAware注入
	private ApplicationContext parent; 

    // 默认配置类是FeignClientsConfiguration
    private Class<?> defaultConfigType; 
    
    /** 把配置的List转为Map */
    public void setConfigurations(List<C> configurations) {
		for (C client : configurations) {
			this.configurations.put(client.getName(), client);
		}
	}
    
    // 从spring父子容器中获取单个对象
    public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return context.getBean(type);
		}
		return null;
	}
    
    // 从spring父子容器中获取多个对象
    public <T> Map<String, T> getInstances(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
		}
		return null;
	}
    
    /** 获取context */
    protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

    /** 创建context */
    protected AnnotationConfigApplicationContext createContext(String name) {
       // 每个接口创建自己的子容器
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
       // 注册属于服务接口的配置类
        if (this.configurations.containsKey(name)) {
          for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
             context.register(configuration);
          }
       }
        // 注册应用全局的配置类
       for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
          if (entry.getKey().startsWith("default.")) {
             for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
             }
          }
       }
       // 注册默认的配置类
       context.register(PropertyPlaceholderAutoConfiguration.class,
             this.defaultConfigType);
        
       context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
             this.propertySourceName,
             Collections.<String, Object>singletonMap(this.propertyName, name)));
        
       // 父容器就是当前应用的spring容器
       if (this.parent != null) {
          context.setParent(this.parent);
          context.setClassLoader(this.parent.getClassLoader());
       }
       context.setDisplayName(generateDisplayName(name));
       context.refresh();
       return context;
    }
}
配置类示意图

parent context type : AnnotationConfigServletWebApplicationContext 不允许bean 定义覆盖

child context type: AnnotationConfigApplicationContext 允许bean 定义覆盖

在这里插入图片描述

问题一:

如果同时添加了全局和服务级别的配置,那会发生什么?

1)启动报错 2)全局配置生效 3)服务级别的配置生效

答案: 2)全局配置生效

allowBeanDefinitionOverriding:true 允许Bean覆盖

在这里插入图片描述

配置文件

application.properties 或 application.yml

feign:
  client:
    defaultToProperties: false
    config: # 对应FeignClientProperties类的config成员变量
      default: # 全局配置默认就是default
        # 日志级别
        logger-level: BASIC
        # 超时时间
        connect-timeout: 10000
      order: # 是feignClient注解的ContextId
        # 日志级别
        logger-level: HEADERS
        # 超时时间
        connect-timeout: 8000
      user:
        # 日志级别
        logger-level: FULL
        # 超时时间
        connect-timeout: 6000

属性绑定Properties类

@ConfigurationProperties("feign.client") // 配置的前缀 feign.client
public class FeignClientProperties {

    // 以配置文件的为准
	private boolean defaultToProperties = true;

    // 默认配置的名称 default
	private String defaultConfig = "default";

    // 可以自定义多个配置 key为配置名称
	private Map<String, FeignClientConfiguration> config = new HashMap<>();
	
	/**
	 * Feign client configuration.
	 */
	public static class FeignClientConfiguration {

		private Logger.Level loggerLevel;  // 日志级别

		private Integer connectTimeout;  // 连接超时

		private Integer readTimeout;  // 读取超时

		private Class<Retryer> retryer;  // 重试

		private Class<ErrorDecoder> errorDecoder; // 错误解码器

		private List<Class<RequestInterceptor>> requestInterceptors;  // 拦截器

		private Boolean decode404;

		private Class<Decoder> decoder; // 解码器

		private Class<Encoder> encoder; // 编码器

		private Class<Contract> contract; // 契约
    }
}

配置类和配置文件的优先级

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    // ...
    
    // 配置 feign
    protected void configureFeign(FeignContext context, Feign.Builder builder) {
        // 从配置文件获取(属性绑定)
		FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);
        
		if (properties != null) {
            // 如果有配置文件有配置
			if (properties.isDefaultToProperties()) {
                // isDefaultToProperties默认为true 即默认以配置文件的配置为准
                // 因此先通过配置类进行配置 然后通过配置文件进行配置
				configureUsingConfiguration(context, builder);
                // 对于配置文件而言 服务级别的配置可以覆盖默认配置
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
			}
			else {
                // isDefaultToProperties如果设置为false 即默认以配置类的配置为准
                // 因此先通过配置文件进行配置 然后通过配置类进行配置
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
            // 如果配置文件没有配置则直接从配置类进行配置
			configureUsingConfiguration(context, builder);
		}
	}

    // 使用配置类进行配置
	protected void configureUsingConfiguration(FeignContext context,
			Feign.Builder builder) {
        // 日志级别
		Logger.Level level = getOptional(context, Logger.Level.class);
		if (level != null) {
			builder.logLevel(level);
		}
        
        // 重试器
		Retryer retryer = getOptional(context, Retryer.class);
		if (retryer != null) {
			builder.retryer(retryer);
		}
        
        // 错误编码
		ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
		if (errorDecoder != null) {
			builder.errorDecoder(errorDecoder);
		}
        
        // 请求参数(连接超时 读取超时等)
		Request.Options options = getOptional(context, Request.Options.class);
		if (options != null) {
			builder.options(options);
		}
        
        // 拦截器
		Map<String, RequestInterceptor> requestInterceptors = context
				.getInstances(this.contextId, RequestInterceptor.class);
		if (requestInterceptors != null) {
			builder.requestInterceptors(requestInterceptors.values());
		}
        
		QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);
		if (queryMapEncoder != null) {
			builder.queryMapEncoder(queryMapEncoder);
		}
        
		if (this.decode404) {
			builder.decode404();
		}
	}

    // 使用配置文件进行配置
	protected void configureUsingProperties(
			FeignClientProperties.FeignClientConfiguration config,
			Feign.Builder builder) {
		if (config == null) {
			return;
		}

        // 日志级别
		if (config.getLoggerLevel() != null) {
			builder.logLevel(config.getLoggerLevel());
		}

        // 请求参数(连接超时 读取超时等)
		if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
			builder.options(new Request.Options(config.getConnectTimeout(),
					config.getReadTimeout()));
		}

        // 重试器
		if (config.getRetryer() != null) {
			Retryer retryer = getOrInstantiate(config.getRetryer());
			builder.retryer(retryer);
		}

        // 错误编码
		if (config.getErrorDecoder() != null) {
			ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
			builder.errorDecoder(errorDecoder);
		}

        // 拦截器
		if (config.getRequestInterceptors() != null
				&& !config.getRequestInterceptors().isEmpty()) {
			for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
				RequestInterceptor interceptor = getOrInstantiate(bean);
				builder.requestInterceptor(interceptor);
			}
		}

		if (config.getDecode404() != null) {
			if (config.getDecode404()) {
				builder.decode404();
			}
		}

        // 编码器
		if (Objects.nonNull(config.getEncoder())) {
			builder.encoder(getOrInstantiate(config.getEncoder()));
		}

        // 解码器
		if (Objects.nonNull(config.getDecoder())) {
			builder.decoder(getOrInstantiate(config.getDecoder()));
		}

        // 契约
		if (Objects.nonNull(config.getContract())) {
			builder.contract(getOrInstantiate(config.getContract()));
		}
	}
    
    private <T> T getOrInstantiate(Class<T> tClass) {
		try {
            // 直接从spring父容器中取
			return this.applicationContext.getBean(tClass);
		}
		catch (NoSuchBeanDefinitionException e) {
			return BeanUtils.instantiateClass(tClass);
		}
	}
    // ...
}

注意: 配置类是全局配置覆盖局部配置 而 配置文件是, 局部配置覆盖全局配置(是反过来的)

具体配置举例讲解

请求拦截器

接口:

public interface RequestInterceptor {
  void apply(RequestTemplate template);
}

调用拦截器:发送请求前

作用:用于修改请求url, header, body等等

final class SynchronousMethodHandler implements MethodHandler {

   Request targetRequest(RequestTemplate template) {
      // 调用拦截器
      for (RequestInterceptor interceptor : requestInterceptors) {
         interceptor.apply(template);
      }
      return target.apply(template);
   }

	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);
        } catch (IOException e) {
          if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
          }
          throw errorExecuting(request, e);
        }
        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
		// ...
    }
}

获取拦截器组件: 从配置类或配置文件

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    
    // 使用配置类进行配置
	protected void configureUsingConfiguration(FeignContext context,
			Feign.Builder builder) {
		// ...
        
        // 从spring容器获取组件
        Map<String, RequestInterceptor> requestInterceptors = context
				.getInstances(this.contextId, RequestInterceptor.class);
        
        // ...
	}
    
    // 使用配置文件进行配置
	protected void configureUsingProperties(
			FeignClientProperties.FeignClientConfiguration config,
			Feign.Builder builder) {
		// ...

        // 拦截器
		if (config.getRequestInterceptors() != null
				&& !config.getRequestInterceptors().isEmpty()) {
			for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
				RequestInterceptor interceptor = getOrInstantiate(bean);
				builder.requestInterceptor(interceptor);
			}
		}
		// ...
	}
}
问题一:

是否需要@Component注解?

  1. 可以: 加了就在父容器里面
  2. 不加, 放在用@Bean把这些放在@EnableFeignClients(defaultConfiguration = {DefaultConfiguration.class}) 这个类上 或 每个对应的FeignClient的configuration中
问题二:

拦截器是全局有效的吗?如果是,可否做到只对某个服务接口有效?

  1. 拦截器可以全局有效, 用@Component放在父容器中, 或者放在 @EnableFeignClients(defaultConfiguration = {DefaultConfiguration.class}) 指定的配置类中 或 yml配置文件中配置
  2. 拦截器可以局部有效: 把对应的配置类放在FeignClient的configuration中 或 配置在yml中
问题三:

拦截器是否可以自定义顺序?

  1. 暂时没发现其他的@Order注解 或 Order方法, 所以不能排序

  2. 但是可以通过BeanDefinition解析过程来决定先后顺序, 因为requestInterceptors是LinkedHashMap

    在这里插入图片描述

    在这里插入图片描述

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

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

相关文章

Python基础语法之学习type()函数

Python基础语法之学习type函数 一、代码二、效果 查看数据类型或者说查看变量存储的数据类型 一、代码 print(type("文本")) print(type(666)) print(type(3.14))二、效果 梦想是生活的指南针&#xff0c;坚持追逐梦想&#xff0c;终将抵达成功的彼岸。不要害怕失败…

Go 数字类型

一、数字类型 1、Golang 数据类型介绍 Go 语言中数据类型分为&#xff1a;基本数据类型和复合数据类型基本数据类型有&#xff1a; 整型、浮点型、布尔型、字符串复合数据类型有&#xff1a; 数组、切片、结构体、函数、map、通道&#xff08;channel&#xff09;、接口 2、…

工厂仓库环境监测,完善仓储管理

仓库&#xff0c;是工厂管理运营的重中之重&#xff0c;需创造一个认为的稳定环境&#xff0c;为物品的存放创造条件&#xff0c;对仓储、办公室、楼宇、电子、机械、化工等场合实施监测、管理&#xff0c;提升仓储管理效率及物品品质。 工厂仓储环境监测系统解决方案&#xff…

【leetcode每日一题】565数组嵌套

思路流程&#xff1a; 思路v1.0 先学会写 s[0] ,用一个ans数组接收元素&#xff0c;每次往ans里添加的时候&#xff0c;先判断一下 这个index会不会超出数组的长度。ans里有没有这个元素。 s[0] 写完&#xff0c;就是用一个for循环&#xff0c;算出所有的 s[i],每次算出来的时…

通过亚马逊云科技云存储服务探索云原生应用的威力

文章作者&#xff1a;Libai 欢迎来到我们关于“使用亚马逊云科技云存储服务构建云原生应用”的文章的第一部分。在本文中&#xff0c;我们将深入探讨云原生应用的世界&#xff0c;并探索亚马逊云科技云存储服务在构建和扩展这些应用中的关键作用。 亚马逊云科技开发者社区为开发…

IDEA插件:Apipost-Helper-2.0

我们在编写完接口代码后需要进行接口调试等操作&#xff0c;一般需要打开额外的调试工具。今天就给大家介绍一款IDEA插件&#xff1a;Apipost-Helper-2.0。用它&#xff0c;代码写完直接编辑器内调试、还支持生成接口文档、接口树等功能&#xff0c;并且完全免费&#xff01;非…

巅峰对决 LlamaIndex 与 OpenAI API大比拼

我们进行了一项详尽的分析&#xff0c;比较了 OpenAI 助手 API 和 LlamaIndex 在 RAG 性能方面的差异。目的是使用Tonic Validate评估各种RAG系统&#xff0c;该系统是一个RAG评估和基准平台&#xff0c;同时使用开源工具tvalmetrics。本文中使用的所有代码和数据都可以在这里找…

Gitee上传代码教程

1. 本地安装git 官网下载太慢&#xff0c;我们也可以使用淘宝镜像下载&#xff1a;CNPM Binaries Mirror 安装成功以后电脑会有Git Bush标识&#xff0c;空白处右键也可查看。 2. 注册gitee账号&#xff08;略&#xff09; 3. 创建远程仓库 4. 上传代码 4.1 在项目文件目录…

扩散模型,快速入门和基于python实现的一个简单例子(复制可直接运行)

提示&#xff1a;内容丰富&#xff0c;收藏本文&#xff0c;以免忘记哦 文章目录 一、扩散模型二、一个简单的迭代式扩散模型的例子温度扩散模型python代码实现差分近似模拟拉普拉斯算子 三、扩散模型和深度学习进行结合简介用python和torch的代码实现 四、扩散模型与生成模型第…

Android Bitmap 模糊效果实现 (二)

文章目录 Android Bitmap 模糊效果实现 (二)使用 Vukan 模糊使用 RenderEffect 模糊使用 GLSL 模糊RS、Vukan、RenderEffect、GLSL 效率对比 Android Bitmap 模糊效果实现 (二) 本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134656140 最新更新地址 https:/…

Java之API(上):Integer

前言&#xff1a; 这一次内容主要是围绕Java开发中的一些常用类&#xff0c;然后主要是去学习这些类里面的方法。 一、高级API&#xff1a; (1)介绍&#xff1a;API指的是应用程序编程接口&#xff0c;API可以让编程变得更加方便简单。Java也提供了大量API供程序开发者使用&…

Vue框架学习笔记——侦听(监视)属性watch:天气案例+immediate+deep深度监听

文章目录 前文提要天气案例描述样例代码呈现效果&#xff1a;事件的响应中可以写一些简单的语句&#xff08;不推荐&#xff09; 侦听&#xff08;监视&#xff09;属性watch结合天气案例的第一种写法&#xff08;New Vue&#xff09;immediate&#xff1a; 侦听&#xff08;监…

【Java数据结构 -- 包装类和泛型】

包装类和泛型 1. 包装类1.1 基本数据类型和对应的包装类1.2 装箱和拆箱1.3 自动装箱和自动拆箱1.4 自动装箱实际上是调用了valueOf&#xff08;&#xff09;1.5 Integer包装类赋值注意点 2 什么是泛型3 引出泛型4 泛型的使用4.1 语法4.2 类型推导 5 裸类型6 泛型如何编译6.1 擦…

2019年8月21日 Go生态洞察:迁移到Go模块

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【从删库到跑路 | MySQL总结篇】表的增删查改(进阶上)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、数据…

数据增强让模型更健壮

在做一些图像分类训练任务时,我们经常会遇到一个很尴尬的情况,那就是: 明明训练数据集中有很多可爱猫咪的照片,但是当我们给训练好的模型输入一张戴着头盔的猫咪进行测试时,模型就不认识了,或者说识别精度很低。 很明显,模型的泛化能力太差,难道戴着头盔的猫咪就不是猫…

坚鹏:贵州银行西南财经大学零售业务数字化转型与场景营销策略

中国银保监会2022年1月正式发布了中国银保监会发布《关于银行业保险业数字化转型的指导意见》&#xff0c;这标准着中国银行业从局部的数字化转型向全面的数字化转型转变&#xff0c;进一步加速了银行数字化转型高潮的到来。 《关于银行业保险业数字化转型的指导意见》提出明确…

一次脚本测试的内存增长问题

问题背景 问题描述&#xff1a;进入应用的视频素材剪辑页面然后退出&#xff0c;脚本循环执行500次&#xff0c;内存增长156M 问题分析 分析增长曲线图 曲线反映了从0到500次脚本执行过程中adb shell dumpsys meminfo抓取内存的增长情况&#xff0c;可以看出是Native内存一直…

JavaScript解构对象

之前介绍了数组解构&#xff0c;本文来介绍一下对象如何解构&#xff1b; 前言 现在我们有这样的一个数组&#xff1a; const restaurant {name: Classico Italiano,location: Via Angelo Tavanti 23, Firenze, Italy,categories: [Italian, Pizzeria, Vegetarian, Organic…

LINUX入门篇【10】---进程篇【2】---进程状态

前言&#xff1a; 有了上一篇关于进程的初步认识和我们的PCB第一个数据段–标识符的讲解&#xff0c;接下来我们将继续讲解PCB的其他数据段&#xff0c;本篇要讲的是进程状态。 进程状态&#xff1a; 就像我们写贪吃蛇的时候&#xff0c;构建的游戏状态来判定游戏结束的方式…