nacos动态配置刷新机制原理

news2024/11/25 14:22:31

nacos动态配置刷新机制原理

项目里面许多业务场景以及灵活配置的需求经常要用到动态配置。一般就是apollo和nacos两种选型。

nacos动态刷新导致的bug

nacos一般为了实现动态配置一般会加入@RefreshScope注解进行实现,例如下面的代码加入了@RefreshScope想要实现跨域的动态配置,例如跨域白名单等。

@slf4j
@configuration
@RefreshScope
public class FilterConfiguration {
@value("${jlpay.business.filter.allowCredentials:true}")
private boolean allowCredentials;
@value("${jlpay.business.filter.allowedHeader:}")
private String allowedHeader;
@value("${jlpay.business.filter.allowedMethod:}")
private String allowedMethod;
@value("${jlpay.business.filter.allowedOrigin:*}")
private String allowedOrigin;
@value("${jlpay.business.filter.corsPath:/**}")
private String corsPath;

/**
 * 跨域请求配置
 * @return
 */
private CorsConfiguration buildCorsConfig() {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowCredentials(allowCredentials);
    corsConfig.addAllowedHeader(allowedHeader);
    corsConfig.addAllowedMethod(allowedMethod);
    //指定域名拦截配置
    if (!StringUtils.isEmpty(allowedOrigin) && !CorsConfiguration.ALL.equals(allowedOrigin)) {
        String[] originArr = allowedOrigin.split(",");
        for (String origin : originArr) {
            corsConfig.addAllowedOrigin(origin);
        }
    } else {
        corsConfig.addAllowedOrigin(CorsConfiguration.ALL);
    }

    return corsConfig;
}

/**
 * 跨域请求过滤器配置
 */
@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
    //注:暂定所有接口均允许跨域,建议针对具体接口配置允许跨域
    configSource.registerCorsConfiguration(corsPath, buildCorsConfig());

    log.info("跨域请求过滤器配置:{}", JSONObject.toJSONString(configSource.getCorsConfigurations()));
    return new CorsFilter(configSource);
}

但是发现这个注解后直接导致了跨域配置失效,前端访问我接口的时候报了跨域错误。问题的根因在于serverletContext没有注入生成的filterbean,为什么没有注入,是serverletContext在初始化的时候去找符合的filer类,是通过getBeanNamesForType获取的注入的filter类名称,按道理根据filter实现统一接口类是可以获取到的,但是这个方法isFactoryBean 判定了CorsFilter是factorybean,因此,CorsFilter没有被获取到加入到filter链中。所以@RefreshScope注解影响了CorsFilter bean的生成 方式。下面仔细看下@RefreshScope的原理。

@Override
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
   String beanName = transformedBeanName(name);
   Object beanInstance = getSingleton(beanName, false);
   if (beanInstance != null) {
      return (beanInstance instanceof FactoryBean);
   }
   // No singleton instance found -> check bean definition.
   if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory cbf) {
      // No bean definition found in this factory -> delegate to parent.
      return cbf.isFactoryBean(name);
   }
   return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}

因为@RefreshScope 导致FilterConfiguration 变成了一个facotorybean, 导致无法加载了serverletContext的filter链中。 所以解决防范是建议使用ConfigurationProperties bean 方式使用,@RefreshScope并不是一个很好的使用方式。

RefreshScope动态刷新原理

RefreshScope注解的Bean可以在运行时刷新,并且使用它们的任何组件都将在下一个方法调用前获得一个新实例,该实例将完全初始化并注入所有依赖项。

public @interface RefreshScope {

  /**
	 * @see Scope#proxyMode()
	 * @return proxy mode
	 */
  ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

RefreshScope用到的是spring bean的scope模式能力。scope模式在获取bean时和单例的Bean以及多例的bean不太一样。scope 里自定义了一个scope范围来隔离不同的scope,在获取bean时会优先从实现了Scope接口的类中获取,简单来说,scope模式的bean的获取方式通过scope接口实现类获取,是一种代理模式。

//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 
// Create bean instance.
   if (mbd.isSingleton()) {
      sharedInstance = getSingleton(beanName, () -> {
         try {
            return createBean(beanName, mbd, args);
         }
          //......
   }

   else if (mbd.isPrototype()) {
      // It's a prototype -> create a new instance.
      Object prototypeInstance = null;
      try {
         beforePrototypeCreation(beanName);
         prototypeInstance = createBean(beanName, mbd, args);
      }
       //......
   }

   else {
      String scopeName = mbd.getScope();
      if (!StringUtils.hasLength(scopeName)) {
         throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
      }
      Scope scope = this.scopes.get(scopeName);
      if (scope == null) {
         throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
      }
      try {
         Object scopedInstance = scope.get(beanName, () -> {
            beforePrototypeCreation(beanName);
            try {
               return createBean(beanName, mbd, args);
            }
            finally {
               afterPrototypeCreation(beanName);
            }
         });
         beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
      }
      catch (IllegalStateException ex) {
         throw new ScopeNotActiveException(beanName, scopeName, ex);
      }
   }
}
catch (BeansException ex) {
	//......
}
finally {
	//......
}

RefreshScope 就是其中一个代理类实现,scope.get由genericScope实现。

在这里插入图片描述
GenericScope是一个beanFactoryPostprocess,在bean实列化前就会执行postProcessBeanFactory将自己注册放scopes中去,这样前面的Bean获取单例就会从对应scopeName中获取scope,而scope是一个保存了代理类的map,最终执行的代理类的Invoke方法。

//postProcessBeanFactory会在实例化执行
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
		beanFactory.registerScope(this.name, this);
		setSerializationId(beanFactory);
	}

 
public void registerScope(String scopeName, Scope scope) {
		Assert.notNull(scopeName, "Scope identifier must not be null");
		Assert.notNull(scope, "Scope must not be null");
		if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
			throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
		}
		Scope previous = this.scopes.put(scopeName, scope);
		if (previous != null && previous != scope) {
			if (logger.isDebugEnabled()) {
				logger.debug("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]");
			}
		}
		else {
			if (logger.isTraceEnabled()) {
				logger.trace("Registering scope '" + scopeName + "' with implementation [" + scope + "]");
			}
		}
	}

这个代理类RefreshScope 注解生成的是LockedScopedProxyFactoryBean,下面看看代理类是怎么生成的以及invoke代理的方法。

RefreshScope 注解类代理类注册容器

RefreshScope 注解的类 在生成Bean 定义时会生成两份定义,一份是原来的类的定义,另一份时代理类LockedScopedProxyFactoryBean的定义,且LockedScopedProxyFactoryBean时一个factorybean,通过getObject方法获取实际的proxy对象,实际的proxy对象通过getBeanFactory().getBean(getTargetBeanName()),即ioc容器中获取原始bean。

代理类定义

springboot bean 加载过程是利用@ComponentScan会扫描并注册包下带有@Componet注解的类为Bean(@Configuration 实际上也是一个@Componet),达到一个注解驱动注册Bean的效果。扫描Bean并注册为BeanDefinition这一过程是由ClassPathBeanDefinitionScanner类的doScan方法去做的。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  // 扫描basePackages所在的包下的所有的类,带@Componet的类都会被注册为BeanDefinition
  for (String basePackage : basePackages) {
    	//...
      if (checkCandidate(beanName, candidate)) {
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        // 这里就是Scope的Bean的统一处理,是一个改变BeanDefinition的回调机会
          // 调用createScopedProxy创建一个scope 代理
        definitionHolder =
          AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        beanDefinitions.add(definitionHolder);
        // 注册BeanDefinition到IOC容器中
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

注意AnnotationConfigUtils.applyScopedProxyMode方法,这里提供了一个代理bean定义的方式,若具体是在 创建bean时会判断类是否带@refreshScope注解,然后创建ScopedProxyFactoryBean bean工厂用来创建代理bean。

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
                                                     BeanDefinitionRegistry registry, boolean proxyTargetClass) {

  // ...

  // Create a scoped proxy definition for the original bean name,
  // "hiding" the target bean in an internal target definition.
  // 重点,这里构造函数中将beanClass设置为了ScopedProxyFactoryBean.class
  RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
  // targetDefinition是被代理的原生Bean
  proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
  
  // ...

  // Return the scoped proxy definition as primary bean definition
  // (potentially an inner bean).
  return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}


postProcessBeanDefinitionRegistry回调方法中会针对刚刚那个beanClass为ScopedProxyFactoryBean.class的BeanDefinition进行一个增强处理,最终生成的代理类定义的是LockedScopedProxyFactoryBean。

// GenericScope.class
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
  throws BeansException {
  // 获取所有BeanDefinition的名称
  for (String name : registry.getBeanDefinitionNames()) {
    BeanDefinition definition = registry.getBeanDefinition(name);
    // 针对RootBeanDefinition这个BeanDefinition来做,这和上面的逻辑吻合
    if (definition instanceof RootBeanDefinition) {
      RootBeanDefinition root = (RootBeanDefinition) definition;
      // 判断BeanClass == ScopedProxyFactoryBean.class
      if (root.getDecoratedDefinition() != null && root.hasBeanClass()
          && root.getBeanClass() == ScopedProxyFactoryBean.class) {
        if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()
                             .getScope())) {
          // 将BeanClass换为LockedScopedProxyFactoryBean
          root.setBeanClass(LockedScopedProxyFactoryBean.class);
          root.getConstructorArgumentValues().addGenericArgumentValue(this);
          // surprising that a scoped proxy bean definition is not already
          // marked as synthetic?
          root.setSynthetic(true);
        }
      }
    }
  }
}

代理类创建

LockedScopedProxyFactoryBean的父类ScopedProxyFactoryBean 实现了FactoryBean,FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象。proxy就是生成的实际代理对象。

public Object getObject() {
  if (this.proxy == null) {
    throw new FactoryBeanNotInitializedException();
  }
  return this.proxy;
}

在这里插入图片描述

proxy是通过 ScopedProxyFactoryBean 生成的,该类实现了 BeanFactoryAware(该接口用于知道创建Bean的beanfactory,即ioc容器),因此setBeanFactory会在比较早的时机被回调用来生成proxy.

public void setBeanFactory(BeanFactory beanFactory) {
  
  // ...
  
	// 这里是一个比较关键的点,scopedTargetSource变量是一个SimpleBeanTargetSource
  // scopedTargetSource中保存了IOC容器
  this.scopedTargetSource.setBeanFactory(beanFactory);

  // 创建动态代理前,将动态代理的信息都保存到ProxyFactory中
  ProxyFactory pf = new ProxyFactory();
  pf.copyFrom(this);
  // 注意,这里的TargetSource就是刚刚说的scopedTargetSource
  pf.setTargetSource(this.scopedTargetSource);
  
	// ...

  this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}

proxy的代理方法如下, 代理实现是CGLib动态代理都会实现一个MethodInterceptor,被代理的类的每一个方法调用实质上都是在调用MethodInterceptor的intercept方法。

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  // 反复强调的TargetSource
  TargetSource targetSource = this.advised.getTargetSource();
  try {
    // ...
    // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
    // 重点,这里调用了targetSource的getTarget
    // target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
    target = targetSource.getTarget();
   // ...
    if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
      // ...
      // target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
      retVal = methodProxy.invoke(target, argsToUse);
    }
    else {
      // We need to create a method invocation...
      // target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
      retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
    }
    // target变量就是被代理的类,调用实际方法的时候反射调用其对应方法
    retVal = processReturnType(proxy, target, method, retVal);
    return retVal;
  }
  // ...
}

前面讲到TargetSource的实现类是SimpleBeanTargetSource:通过bean容器获取代理的bean.

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

  @Override
  public Object getTarget() throws Exception {
    // 从IOC中getBean
    return getBeanFactory().getBean(getTargetBeanName());
  }
}

bean注册

this.registry 的类是实现了BeanDefinitionRegistry 是一个接口,它定义了关于 BeanDefinition 的注册、移除、查询等一系列的操作。该接口有三个实现类:DefaultListableBeanFactory、GenericApplicationContext、SimpleBeanDefinitionRegistry。BeanDefinitionRegistry 集成了SingletonBeanRegistry ,接口的核心实现类是 DefaultSingletonBeanRegistry(该类存储 Bean 之间的依赖关系、存储 Bean 的包含关系(外部类包含内部类)、获取 Bean 所处的状态(正在创建、创建完毕等)、回调销毁 Bean 时触发的 destroy 方法等。)用来注册bean到ioc容器。

执行完 registerBeanDefinition 方法后,Bean 的名称和对应的 BeanDefinition 就被放入了容器中,后续获取 Bean 也是从这个容器中获取。

在这里插入图片描述

	/**
	 * 注册Bean基于给定的bean factory.
	 * @param definitionHolder bean定义类
	 * @param registry bean factory注册
	 * @throws BeanDefinitionStoreException if registration failed
	 */
	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// 注册bean 定义
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// 证据Bean 别名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}
	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
        //......

			if (hasBeanCreationStarted()) {
				// 双重判断,将bean定义放入beanDefinitionMap (ioc容器)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName);
				}
			}
               //......

动态配置刷新

前面讲到通过beanfactory会创建以及获取bean, 这里scope.get获取的Scope对象为RefreshScope,创建Bean还是由IOC来做(createBean方法),但是获取Bean,都由RefreshScope对象的get方法去获取,其get方法在父类GenericScope中实现。scop利用hash map管理的ioc创建的bean

Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});

public Object get(String name, ObjectFactory<?> objectFactory) {
  // 将原始Bean缓存下来
  BeanLifecycleWrapper value = this.cache.put(name,
                                              new BeanLifecycleWrapper(name, objectFactory));
  this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
  try {
    // 获取原始类bean
    return value.getBean();
  }
  catch (RuntimeException e) {
    this.errors.put(name, e);
    throw e;
  }
}
#BeanLifecycleWrapper getbean
public Object getBean() {
  // 因爲是新的BeanLifecycleWrapper實例,這裏必定爲null
  if (this.bean == null) {
    synchronized (this.name) {
      if (this.bean == null) {
        // 調用IOC容器的createBean,再建立一個Bean出來
        this.bean = this.objectFactory.getObject();
      }
    }
  }
  return this.bean;
}

BeanLifecycleWrapper生成了一个原始的包装类,其中getBean是返回前面说到的原始类的bean。那么原始类的beab和代理类的bean的关系就出来了。

代理类bean会被其他bean引用,代理原始bean进行执行,并且原始bean可以看出是由GenericScope来管理生命周期。所以当配置发生变化时,只需要将scop缓存的bean移除且将原始bean值设置为null,既可以让代理类重新生成新的原始bean,此时获取的配置也是最新的。

// 配置发生变化时调用

public synchronized Set<String> refresh() {
	Set<String> keys = refreshEnvironment();
	this.scope.refreshAll();
	return keys;
}
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
	super.destroy();
	this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
//进行对象获取,如果没有就创建并放入缓存
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
	BeanLifecycleWrapper value = this.cache.put(name,
			new BeanLifecycleWrapper(name, objectFactory));
	locks.putIfAbsent(name, new ReentrantReadWriteLock());
	try {
		return value.getBean();
	}
	catch (RuntimeException e) {
		this.errors.put(name, e);
		throw e;
	}
}
//进行缓存的数据清理
@Override
public void destroy() {
	List<Throwable> errors = new ArrayList<Throwable>();
	Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
	for (BeanLifecycleWrapper wrapper : wrappers) {
		try {
			Lock lock = locks.get(wrapper.getName()).writeLock();
			lock.lock();
			try {
				wrapper.destroy();
			}
			finally {
				lock.unlock();
			}
		}
		catch (RuntimeException e) {
			errors.add(e);
		}
	}
	if (!errors.isEmpty()) {
		throw wrapIfNecessary(errors.get(0));
	}
	this.errors.clear();
}

总结

@refreshScope注解用了两种代理的模式去实现动态刷新bean,一个是scope代理原始bean的生成和小伙,另一个是aop模式代理原始bean,去获取修改配置后新配置值生成的bean。值得注意的是@RefreshScope会使得bean自动销毁和生成,可能会导致一些莫名奇妙的问题,例如常见的@scheduled注解失效、线程池bean不断被创建等等,因此实际生产中最好控制动态变量用ConfigurationProperties 注解生产,该注解是通过设置变量值的方式进行配置的修改,更加的安全。

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

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

相关文章

智能家居监控管理系统项目需求分析

目录 一、引言 1、目的 2、背景 二、项目系统概述 1、项目产品概述 2、项目功能架构 3、项目市场需求 三、项目硬件需求 1、核心开发板 2、实时检测模块 3、实时信息交互模块 4、用户安全登录模块 5、开发板可扩展模块 6、硬件之间连接设备 四、项…

网络安全进阶学习第二十课——CTF之文件操作与隐写

文章目录 一、文件类型识别1、File命令2、Winhex3、文件头残缺/错误 二、文件分离操作1、Binwalk工具2、Foremost3、dd4、Winhex 三、文件合并操作1、Linux下的文件合并2、Windowsa下的文件合并 四、文件内容隐写Winhex 五、图片文件隐写1、图片混合2、LSB(最低有效位 Least Si…

slickEdit 2022 (v27.0.2)Ubuntu安装以及破解

1去官网下载安装包 SlickEdit 也可以从我这里下载源码包 https://download.csdn.net/download/m0_38012470/88343180 2.解压压缩包并进入根目录 3.sudo ./vsinst 4按住回车不松手一直到显示需要你输入yes的时候 5.一路通过需要输入Y的时候就输入 6.一直到弹出对话框关闭…

c++11的一些新特性

c11 1. {}初始化2. 范围for循环3. final与override4. 右值引用4.1 左值引用和右值引用4.2 左值引用与右值引用比较 5. lambda表达式6. 声明6.1 auto6.2 decltype6.3 nullptr 7. 可变参数模版 1. {}初始化 在C中&#xff0c;使用花括号初始化的方式被称为列表初始化。列表初始化…

Windows C++ 环境下 eigen、osqp、osqp-eigen安装教程

本文是Windows环境下安装eigen、osqp、osqp-eigen的一个简单教程。 osqp是用于二次规划的一种求解器&#xff0c;提供包括C、Matlab、Python等在内的接口&#xff0c;但是不包含C接口。为了能在C 中使用osqp&#xff0c;可以使用osqp-eigen接口进行调用。 第一步&#xff1a;…

第二章 进程与线程 七、处理机调度(概念、层次)

目录 一、基本概念 二、三个层次 1、高级调度&#xff08;作业调度&#xff09; 2、低级调度&#xff08;进程调度/处理机调度&#xff09; 3、中级调度&#xff08;内存调度&#xff09; 三、三次调度的联系、对比 四、七状态模型 五、总结 一、基本概念 当有一堆任务…

利用群论来研究魔方

文章灵感来源于&#xff1a; 魔方与群论&#xff08;二&#xff09;&#xff08;交换子牛啤&#xff01;&#xff09; - 知乎并参考了&#xff1a;https://www.gap-system.org/Doc/Examples/rubik.html使用了这里的小程序&#xff1a;Cubie 先汇制一张&#xff0c;魔方图 ----…

MySQL数据库upsert使用

本文翻译自&#xff1a;MySQL UPSERT - javatpoint&#xff0c;并附带自己的一些理解和使用经验. MySQL UPSERT UPSERT是数据库管理系统管理数据库的基本功能之一&#xff0c;它允许数据库操作语言在表中插入一条新的数据或更新已有的数据。UPSERT是一个原子操作&#xff0c;…

手刻 Deep Learning -第壹章-PyTorch入门教学-基础概念与再探线性回归

一、前言 本章会需要 微分、线性回归与矩阵的基本观念 这次我们要来做 PyTorch 的简单教学&#xff0c;我们先从简单的计算与自动导数&#xff08; auto grad / 微分 &#xff09;开始&#xff0c;使用优化器与误差计算&#xff0c;然后使用 PyTorch 做线性回归&#xff0c;还有…

office mac苹果办公软件安装包安装教程详解

软件下载 软件&#xff1a;mac office版本&#xff1a;2021语言&#xff1a;简体中文大小&#xff1a;4.27G安装环境&#xff1a;mac硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道 百度网盘 https://pan.baidu.com/s/1WGSB-icELUxweFkI8iIbzA 首先&#…

恒源云GPU使用tensorboard || 以OpenMMLab系列为例 || 定时复制可视化日志

序言 在训练过程中使用可视化工具向来是很有效的。相比于shell中的输出&#xff0c;可视化能够更好地向我们展现在训练过程中各项指标的变化。 但是&#xff0c;由于深度学习所需要的设备性能要求较高&#xff0c;我们常常使用云GPU进行训练。但是一些云平台的可视化工具让人摸…

2023国赛B题:多波束测线问题 评阅要点完整分析

本文所有分析仅代表个人观点&#xff0c;不代表官方&#xff0c;仅供参考 制作人&#xff1a;川川徒弟 demoo CSDN&#xff1a;川川菜鸟公众号&#xff1a;川川带你学AI 全文采用非编程做法  需要工具&#xff1a; geogebra、matlab工具箱   注&#xff1a; 本文全文不考虑…

02 java ---- Android 基础app开发

目录 相对布局 显示一个美女 显示两个美女 安卓APP启动过程 安卓布局控件 常用布局之相对布局 常用布局之相对布局 padding和margin 按键美化 常用布局之线性布局 安卓按键响应的几种方式 直接设置按键的onClick绑定的函数 自定义类实现按键监听事件的接口 匿名内…

字节一面:说说var、let、const之间的区别

前言 最近博主在字节面试中遇到这样一个面试题&#xff0c;这个问题也是前端面试的高频问题&#xff0c;作为一名前端开发工程师&#xff0c;熟练掌握js是我们的必备技能&#xff0c;var、let、const之间的区别我们也得熟练掌握&#xff0c;博主在这给大家细细道来。 &#x1f…

Linux驱动中断与时间篇——高精度定时器hrtimer

文章目录 前言相关接口使用示例单次定时循环定时 前言 低分辨率定时器是用jiffies来定时的&#xff0c;所以会受到HZ影响&#xff0c;如果HZ为200&#xff0c;代表每秒种产生200次中断&#xff0c;那一个jiffies就需要5毫秒&#xff0c;所以精度为5毫秒。 如果精度需要达到纳秒…

如何实现一个简单的Promise/A+规范的Promise库?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ Promise/A规范的Promise⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚…

uni-app:通过ECharts实现数据可视化-如何引入项目

效果 引入文件位置 代码 <template><view id"myChart"></view> </template> <script> import echarts from /static/js/echarts.js // 引入文件 export default {mounted() {// 初始化EChartsconst myChart echarts.init(document…

【JVM 内存结构丨堆】

堆 定义内存分配特点:分代结构对象分配过程Full GC /Major GC 触发条件引用方式堆参数堆内存实例 主页传送门&#xff1a;&#x1f4c0; 传送 定义 JVM&#xff08;Java Virtual Machine&#xff09;堆是Java应用程序运行时内存管理的重要组成部分之一。堆内存用于存储Java对象…

如何区分和选择EML、DML两种激光器

EML&#xff08;External Cavity Laser&#xff09;和DML&#xff08;Distributed Feedback Laser&#xff09;两种激光器在光模块中都扮演着重要的角色&#xff0c;用于光通信和其他光电子应用。本文跟随易天光通信来了解一下它们在光模块中的应用吧&#xff01; 一、什么是E…

excel中的引用与查找函数篇3

1、INDEX(array,row_num,[col_num])&#xff1a;获取指定范围中指定行号和列号对应的数据 index(查询范围,行号,列号) 行号和列号是相对选中查询范围来写的&#xff1a;分别把第二行第三列的数据和第四行第二列的数据查找出来。 数据是单行或单列&#xff0c;后面只需要给一个参…