@RefreshScope注解底层源码分析

2023-11-15

写在前面:

最近在研究Spring Cloud和Spring Cloud Alibaba源码,在看到Nacos的配置中心的时候,有注意到自动刷新配置的玩法,底层实现依靠@RefreshScope注解。那么为什么要写这篇文章呢?笔者认为@RefreshScope注解源码实现跨度特别大,从Spring Cloud Alibaba 到Spring Cloud 到 Spring Boot 再到Spring,笔者认为能够理解它的源码实现的话对Spring全家桶的理解又能上升一个档次~

正文:

版本如果下:

Spring:5.3.23
Spring Boot:2.6.3
Spring Cloud:3.1.4
Spring Cloud Alibaba:2021.0.4.0
Nacos:2.0.4

先会用,再深入源码,所以我们需要从案例出发。

@RestController
@RefreshScope
public class ConsumerController {

    @Value("${consumer.value:moren}")
    private String value;

    @RequestMapping("/consumer")
    public String consumer(){
        return value;
    }
}

 

 能够正常使用Nacos服务端的配置数据,所以,我们发现在类上存在@RefreshScope注解,所以我们的重心看往@RefreshScope注解。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {

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

}

此时,很清楚的看到@RefreshScope注解聚合了@Scope注解,并且赋予了"refresh". 此时,从Spring惯用玩法,我们需要找到Spring何时解析的@Scope注解,何时解析的@RefreshScope注解。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {   
                // 解析当前类上是否存在@scope注解
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				
				…………

				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    // 生成Scope代理类
					definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}


	@Override
	public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
		if (definition instanceof AnnotatedBeanDefinition) {
			AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
			AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
					annDef.getMetadata(), this.scopeAnnotationType);
			
		}
		return metadata;
	}

protected Class<? extends Annotation> scopeAnnotationType = Scope.class;

上面代码,是解析@ComponentScan时,扫描指定路径包的@Component注解类,而我们的@RestController也是一个@Component,并且我们的@RestController类上还有@RefreshScope注解,而@RefreshScope注解又存在@Scope注解。所以,接下来我们分析如何做Scope的代理。

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
			BeanDefinitionRegistry registry, boolean proxyTargetClass) {
        // 拿到原类的名字,和对应的BeanDefinition
		String originalBeanName = definition.getBeanName();
		BeanDefinition targetDefinition = definition.getBeanDefinition();
		String targetBeanName = getTargetBeanName(originalBeanName);

        // 手动创建Scope代理类的BeanDefinition
        // 设置BeanDefinition的BeanClass为ScopedProxyFactoryBean
		RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
		proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
		proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
		proxyDefinition.setSource(definition.getSource());
		proxyDefinition.setRole(targetDefinition.getRole());

        …………
        
        // 将代理类设置为自动注入,并且优先级最高。        
        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
		proxyDefinition.setPrimary(targetDefinition.isPrimary());
		
		// 将原类设置为不能自动注入
		targetDefinition.setAutowireCandidate(false);
		targetDefinition.setPrimary(false);

		// 将原类的BeanDefinition注册到工厂中
		registry.registerBeanDefinition(targetBeanName, targetDefinition);
		return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
	}

public static String getTargetBeanName(String originalBeanName) {
	return TARGET_NAME_PREFIX + originalBeanName;
}

private static final String TARGET_NAME_PREFIX = "scopedTarget.";

这里就是非常关键的部分, 有没有感觉是在"偷天换日"。手动创建BeanDefintion,把ScopedProxyFactoryBean作为BeanClass,并且把原类的name作为手动创建BeanDefintion的name。把原类的BeanDefinition的name加上前缀scopedTarget.   相信Spring底子好的读者很容易看明白。下面是改变的流程图。

那么,说了这么多的意义在哪里呢?我们的主题不是动态刷新么,怎么连注册中心的影子都没见到?年轻人,不要着急,文章开头就说了本篇文章跨度很大~

此时,我们需要从Spring Cloud规范包入手,相信各位读者知道,Spring-Cloud-Commons和Spring-Cloud-Context 这两个Spring Cloud规范包。从他们的spring.factories自动装配包可以得知以下信息。

@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
	return new RefreshScope();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapEnabled
public LegacyContextRefresher legacyContextRefresher(ConfigurableApplicationContext context, RefreshScope scope,
			RefreshProperties properties) {
	return new LegacyContextRefresher(context, scope, properties);
}

@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
	return new RefreshEventListener(contextRefresher);
}

从spring.factroies自动装配规范文件中我们能看到RefreshAutoConfiguration类,从类名也能获取到很多信息。再从其中的@Bean中可以看到 RefreshScope 、 LegacyContextRefresher 、 RefreshEventListener三个类。那么下面从笔者的解释和源码深入理解这三个类~!

  • RefreshScope:扩展@Scope注解,并且CRUD名字为refresh的@Scope注解类,之前介绍的@RefreshScope注解中存在value为refresh的@Scope注解。
  • LegacyContextRefresher:用来刷新Environment。
  • RefreshEventListener:用来监听ApplicationReadyEvent和RefreshEvent事件。
public class RefreshScope extends GenericScope
		implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered 

// RefreshScope的父类
// 实现了BeanFactoryPostProcessor扩展接口
public class GenericScope
		implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean 


@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	this.beanFactory = beanFactory;
    // 把当前Scope注册到BeanFacotry中。
	beanFactory.registerScope(this.name, this);
	setSerializationId(beanFactory);
}

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	for (String name : registry.getBeanDefinitionNames()) {
		BeanDefinition definition = registry.getBeanDefinition(name);
		if (definition instanceof RootBeanDefinition) {
			RootBeanDefinition root = (RootBeanDefinition) definition;
			if (root.getDecoratedDefinition() != null && root.hasBeanClass()
					&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
				if(getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) {
                    // 再一次的偷天换日
					root.setBeanClass(LockedScopedProxyFactoryBean.class);
					root.getConstructorArgumentValues().addGenericArgumentValue(this);
					root.setSynthetic(true);
				}
			}
		}
	}
}

这里注意到RefreshScope自动注入的类,这里需要区分@RefreshScope注解和RefreshScope类。它的父类GenericScope实现了BeanFactroyPostProcessor。在postProcessBeanDefinitionRegistry回掉中可以清楚的看到再一次上演了"偷天换日",把ScopedProxyFactoryBean换成了LockedScopedProxyFactoryBean。并且在postProcessBeanFactory回掉中把RefreshScope作为Scope注册到BeanFacotry工厂中。

// SmartApplicationListener子类监听器
public class RefreshEventListener implements SmartApplicationListener 

// 事件回掉。
@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationReadyEvent) {
		handle((ApplicationReadyEvent) event);
	}
    // 一定要注意到RefreshEvent事件,非常非常非常重要!!!
    // 后续我们只需要找到哪里发出的这个事件即可。
	else if (event instanceof RefreshEvent) {
		handle((RefreshEvent) event);
	}
}

public void handle(RefreshEvent event) {
	if (this.ready.get()) { 
		Set<String> keys = this.refresh.refresh();
	}
}

public synchronized Set<String> refresh() {
    // 刷新Environment上下文。而我们知道配置数据是放在Environment中的。
	Set<String> keys = refreshEnvironment();
    // 调用scope的refreshAll,从方法就可以知道,要刷新所有的数据
	this.scope.refreshAll();
	return keys;
}
  1. RefreshEventListener作为SmartApplicationListener的子类实现onApplicationEvent方法
  2. 监听ApplicationReadyEvent和RefreshEvent事件
  3. 调用ContextRefresh类(LegacyContextRefresher)的refresh方法
  4. refreshEnvironment方法刷新Environment,返回发生改变的配置数据
  5. 调用RefreshScope类的refreshAll方法,把整个RefreshScope中存放的Bean给destroy。
public synchronized Set<String> refreshEnvironment() {
    // 拿到更新前的Environment
	Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
    // 更新Environment。
	updateEnvironment();
    // 对比更新后和更新前,返回发生变化的数据。
	Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
	this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
	return keys;
}

@Override
protected void updateEnvironment() {
	addConfigFilesToEnvironment();
}

ConfigurableApplicationContext addConfigFilesToEnvironment() {
	ConfigurableApplicationContext capture = null;
	try {
		StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());

		…………

		// 创建一个Spring Boot的启动器,目的:为了创建出Spring的上下文(这样整个上下文刷新就可以得到最新的environment)。
		// 这里要注意,刷新Spring上下文的environment是手动放入的,
		// 也即重新刷新Spring上下文的环境变量会加载到手动创建的environment中
		SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Banner.Mode.OFF)
		.web(WebApplicationType.NONE).environment(environment);
		builder.application().setListeners(
			Arrays.asList(new BootstrapApplicationListener(), new BootstrapConfigFileApplicationListener()));
		capture = builder.run();
		
		MutablePropertySources target = getContext().getEnvironment().getPropertySources();
		String targetName = null;
		// 把再次刷新Spring上下文environment得到的数据赋值到原有的environment环境变量中(这不就完成了配置数据的刷新么)
		for (PropertySource<?> source : environment.getPropertySources()) {
			if (!this.standardSources.contains(name)) {
				if (target.contains(name)) {
					target.replace(name, source);
				}
				else {
					if (targetName != null) {
						target.addAfter(targetName, source);
							// update targetName to preserve ordering
						targetName = name;
					}
					else {
							// targetName was null so we are at the start of the list
						target.addFirst(source);
						targetName = name;
					}
				}
			}
		}
	}
	// finally中把临时创建出的Application上下文给关闭。
	finally {
		ConfigurableApplicationContext closeable = capture;
		while (closeable != null) {
			closeable.close();
			if (closeable.getParent() instanceof ConfigurableApplicationContext) {
				closeable = (ConfigurableApplicationContext) closeable.getParent();
			}
			else {
				break;
			}
		}
	}
	return capture;
}

这里就是重点所在。一言以蔽之:手动创建Environment对象,然后重新走一遍上下文刷新,这样可以得到最新的配置文件,然后把手动创建Environment对象赋值给当前旧的上下文,这样就完成了动态刷新配置。详细流程如下:

  • 创建出Environment对象。
  • 创建SpringApplicationBuilder对象,也即Spring Boot的启动器(手动传入创建出的Environment对象,这样刷新上下文的时候使用的是这里创建的Environment对象)
  • 调用SpringApplication的run方法,刷新Spring Boot和Spring上下文(刷新完成后返回Spring上下文)。
  • 把刷新Spring Boot和Spring上下文得到的Environment对象的属性拷贝到当前Spring上下文中
  • close掉刷新完成后返回Spring上下文的。
public void refreshAll() {
	super.destroy();
	this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

@Override
public void destroy() {
	List<Throwable> errors = new ArrayList<Throwable>();
    // 清空缓存
	Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
    // 调用摧毁的回掉函数
	for (BeanLifecycleWrapper wrapper : wrappers) {
		try {
			Lock lock = this.locks.get(wrapper.getName()).writeLock();
			lock.lock();
			try {
				wrapper.destroy();
			}
			finally {
				lock.unlock();
			}
		}
		catch (RuntimeException e) {
			errors.add(e);
		}
	}
}

这里就是把RefreshScope中所有的缓存的给clear,并且回掉Bean的destroy方法。这里我必须再次强调RefreshScope的作用,就是CRUD 类上标有value为"refresh"的@Scope注解(@RefreshScope不就是么,所以我们的ConsumerController这个Bean就是交给RefreshScope管理,可能到这里笔者有点懵逼,为什么ConsumerController交给RefreshScope管理?他不是Spring的Bean么,不是要进入三级缓存中么???那么下面就是为了解答这个问题。)

我们看到getBean的doGetBean方法创建Bean的流程中。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {

    // 创建singleton的bean
	if (mbd.isSingleton()) {
        …………
	}
    // 创建Prototype的bean
	else if (mbd.isPrototype()) {
		…………
	}
    // 创建其他Scope作用域的bean
	else {
		String scopeName = mbd.getScope();
		if (!StringUtils.hasLength(scopeName)) {
			throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
		}
        // 根据name拿到对应的Scope。
		Scope scope = this.scopes.get(scopeName);
		if (scope == null) {
			throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
		}
		try {
            // 调用scope的get方法,所以对应的scope可以缓存。
			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);
		}
	}
}

在else代码块中会去处理不同Scope作用域的Bean,而在RefreshScope的父类GenericScope中的postProcessBeanFactory回掉方法中(前面有介绍)会把RefreshScope作为一个Scope注册到BeanFactory中(所以上文的refreshAll方法把RefreshScope的缓存全部clear掉了,下次就会去createBean,就会重新走一遍Spring创建Bean的过程,而环境变量已经更改了,@Value注解的注入就会注入到新的环境变量中的配置数据)。

这里把RefreshScope的缓存全部clear掉了,那么总要有一个地方每次都来拿一遍数据(这样缓存在就拿缓存的,缓存不在(缓存不在就代表被clear了,而clear掉了代表有地方发生了RefreshEvent事件,执行了refreshAll方法和refreshEnvironment,缓存被清除了,环境变量被更改了)就重新createBean,拿到最新的环境变量)

此时,我们是不是忘了,我们的ConsumerController被代理了呢?上文介绍RefreshScope的父类GenericScope中postProcessBeanDefinitionRegistry方法注册了LockedScopedProxyFactoryBean。

public static class LockedScopedProxyFactoryBean<S extends GenericScope> extends ScopedProxyFactoryBean
			implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
	    // 反射的Method
        Method method = invocation.getMethod();
	
	    …………

	    try {
		    if (proxy instanceof Advised) {
			    Advised advised = (Advised) proxy;
			    ReflectionUtils.makeAccessible(method);
			    // advised.getTargetSource().getTarget()方法会去getBean
			    return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(),
				invocation.getArguments());
		    }
		    return invocation.proceed();
	    }
    }
}
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

	@Override
	public Object getTarget() throws Exception {
		// 看到熟悉的getBean方法了。
		return getBeanFactory().getBean(getTargetBeanName());
	}

}

LockedScopedProxyFactoryBean实现了MethodInterceptor接口,所以只要调用ConsumerController类中的方法就会走到LockedScopedProxyFactoryBean的invoke方法。恰好在invoke方法中会去调用BeanFactory的getBean方法。而getBean再到doGetBean,再到上面介绍的else代码块中,就走到RefreshScope类中的get方法了。

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
    // 拿缓存,如果缓存中没有就创建
	BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
	this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
	try {
        // 拿缓存,如果缓存中没有就回掉createBean方法。
		return value.getBean();
	}
	catch (RuntimeException e) {
		this.errors.put(name, e);
		throw e;
	}
}

看到get方法其实就恍然大悟了,因为在LockedScopedProxyFactoryBean的invoke方法中会调用getBean,getBean调用doGetBean中会调用scope.get方法,而在get方法中会去拿缓存,如果没有缓存就会创建一个新的,而新的就会去执行createBean方法创建一个新的Bean出来。恰好在这之前缓存已经被清除了,环境变量更新了。最终createBean方法创建的时候@Value注入的就是最新的环境变量中的配置数据。

所以,RefreshScope类+@RefreshScope注解控制了Bean的创建,RefreshEvent事件控制了缓存的clear和环境变量的更新。但是我们似乎还没有闭环RefreshEvent事件在哪里发出的。

public class NacosContextRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {

	private void registerNacosListener(final String groupKey, final String dataKey) {
		// 一个group、dataid对应一组事件。
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		// 创建一个Nacos事件监听器
		Listener listener = listenerMap.computeIfAbsent(key,
			lst -> new AbstractSharedListener() {
				@Override
				public void innerReceive(String dataId, String group,
					String configInfo) {
	
					// 这里发送了RefreshEvent事件。
					applicationContext.publishEvent(
						new RefreshEvent(this, null, "Refresh Nacos config"));
				}
			});
		try {
			// 添加Nacos的内部事件
			configService.addListener(dataKey, groupKey, listener);
		}
	}
}

对于Nacos的配置中心代码不过细讲,我们能够知道RefreshEvent事件是从Nacos内部发出的即可

总结:

确实跨度非常大,设计到Spring全家桶的各个部分的源码,可能对于一些读者来说欠缺一小部分的前置知识源码就没办法继续读下去了,但是笔者也没办法把所有细节和所有需要的前置知识都写在此篇文章中~  笔者能做到的是把整个源码闭环,也欢迎大家积极评论~!

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

@RefreshScope注解底层源码分析 的相关文章

  • java中监视目录变化

    我正在使用 WatchService 来监视目录中的更改 特别是目录中新文件的创建 下面是我的代码 package watcher import java nio file import static java nio file Stand
  • 有没有创建 Cron 表达式的 Java 代码? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我需要一个 Java 代码来根据用户输入创建一个 cron 表达式 用户输入是时间 频率和执行次数 只需从评论中添加 自己创建 即可
  • TreeMap 删除所有大于某个键的键

    在项目中 我需要删除键值大于某个键的所有对象 键类型为Date 如果重要的话 据我所知TreeMapJava中实现的是红黑树 它是一种二叉搜索树 所以我应该得到O n 删除子树时 但除了制作尾部视图并一一删除之外 我找不到任何方法可以做到这
  • 如何在 JavaFX 中连接可观察列表?

    我所说的串联是指获得一个新列表 该列表侦听所有串联部分的更改 方法的目的是什么FXCollections concat ObservableList
  • eclipse行号状态行贡献项是如何实现的?

    我需要更新状态行编辑器特定的信息 我已经有了自己的实现 但我想看看 eclipse 贡献项是如何实现的 它显示状态行中的行号 列位置 谁能指点一下 哪里可以找到源代码 提前致谢 亚历克斯 G 我一直在研究它 它非常复杂 我不确定我是否了解完
  • 如何在 JPQL 或 HQL 中进行限制查询?

    在 Hibernate 3 中 有没有办法在 HQL 中执行相当于以下 MySQL 限制的操作 select from a table order by a table column desc limit 0 20 如果可能的话 我不想使用
  • 如何在 Java 中向时间戳添加/减去时区偏移量?

    我正在使用 JDK 8 并且玩过ZonedDateTime and Timestamp很多 但我仍然无法解决我面临的问题 假设我得到了格式化的Timestamp在格林威治标准时间 UTC 我的服务器位于某处 假设它设置为Asia Calcu
  • Mockito 使用 @Mock 时将 Null 值注入到 Spring bean 中?

    由于我是 Spring Test MVC 的新手 我不明白这个问题 我从以下代码中获取了http markchensblog blogspot in search label Spring http markchensblog blogsp
  • 如何仅从 Firestore 获取最新更新的数据?

    在 Firestore 上发现任何更改时始终获取整个文档 如何只获取最近更新的数据 这是我的数据 我需要在第一次加载时在聊天中按对象顺序 例如 2018 09 17 30 40 msg和sendby 并且如果数据更新则仅获取新的msg和se
  • Java Applet 中的 Apache FOP - 未找到数据的 ImagePreloader

    我正在研究成熟商业产品中的一个问题 简而言之 我们使用 Apache POI 库的一部分来读取 Word DOC 或 DOCX 文件 并将其转换为 XSL FO 以便我们可以进行标记替换 然后 我们使用嵌入到 Java 程序中的 FOP 将
  • 从jar中获取资源

    我有包含文件的 jar myJar res endingRule txt myJar wordcalculator merger Marge class 在 Marge java 中我有代码 private static final Str
  • 提高 PostgreSQL 1 亿数据左连接查询性能

    我在用Postgresql 9 2 version Windows 7 64 bit RAM 6GB 这是一个Java企业项目 我必须在我的页面中显示订单相关信息 有三个表通过左连接连接在一起 Tables TV HD 389772 行 T
  • Jetty、websocket、java.lang.RuntimeException:无法加载平台配置器

    我尝试在 Endpoint 中获取 http 会话 我遵循了这个建议https stackoverflow com a 17994303 https stackoverflow com a 17994303 这就是我这样做的原因 publi
  • JDBC 时间戳和日期 GMT 问题

    我有一个 JDBC 日期列 如果我使用 getDate 则会得到 date 仅部分2009 年 10 月 2 日但如果我使用 getTimestamp 我会得到完整的 date 2009 年 10 月 2 日 13 56 78 890 这正
  • 不可变的最终变量应该始终是静态的吗? [复制]

    这个问题在这里已经有答案了 在java中 如果一个变量是不可变的并且是final的 那么它应该是一个静态类变量吗 我问这个问题是因为每次类的实例使用它时创建一个新对象似乎很浪费 因为无论如何它总是相同的 Example 每次调用方法时都会创
  • 将 JavaFX FXML 对象分组在一起

    非常具有描述性和信息性的答案将从我这里获得价值 50 声望的赏金 我正在 JavaFX 中开发一个应用程序 对于视图 我使用 FXML
  • 手动设置Android Studio的JDK路径

    如何为 Android Studio 使用自定义 JDK 路径 我不想弄乱 PATH 因为我没有管理员权限 是否有某个配置设置文件允许我进行设置 如果您查看项目设置 您可以从那里访问 jdk 在标准 Windows 键盘映射上 您可以在项目
  • Log4j2 ThreadContext 映射不适用于parallelStream()

    我有以下示例代码 public class Test static System setProperty isThreadContextMapInheritable true private static final Logger LOGG
  • 抛出 Java 异常时是否会生成堆栈跟踪?

    这是假设我们不调用 printstacktrace 方法 只是抛出和捕获 我们正在考虑这样做是为了解决一些性能瓶颈 不 堆栈跟踪是在构造异常对象时生成的 而不是在抛出异常对象时生成的 Throwable 构造函数调用 fillInStack
  • java'assert'和'if(){}else exit;'之间的区别

    java和java有什么区别assert and if else exit 我可以用吗if else exit代替assert 也许有点谷歌 您应该记住的主要事情是 if else 语句应该用于程序流程控制 而assert 关键字应该仅用于

随机推荐

  • 国产开源大模型: 百亿参数“伶荔”,填补中文基础模型空白!

    Datawhale开源 团队 深圳大学沈琳琳教授团队 Linly 伶荔说 中文语言大模型来啦 大数据系统计算技术国家工程实验室副主任 深圳大学计算机与软件学院沈琳琳教授团队主持的人工智能项目 伶荔 Linly 于今天隆重推出 伶荔说 系列中
  • HashMap源码分析

    目录 hashmap1 8源码大纲 那么问题来了 hashmap的数据结构 为什么扩容长度必须是2的指数次幂也就是2的n次方 为什么加载因子是0 75 为什么数组转链表阈值是8 key能否为空 hashmap为什么线程不安全 hashmap
  • js与移动端交互

    1 js 调用移动端ios与android方法 2 移动端ios与android调用js方法 3 demo如下 div div
  • 使用vlc显示海康网络摄像机的视频

    通过博主的另外一篇博客https blog csdn net u014552102 article details 86700057 配置完海康网络摄像机后 我们就可以使用vlc显示摄像机的视频了 在下图所示的浏览器页面中 我们可以知道摄像
  • redis集群主从复制bug:从机出现master_link_status:down提示,显示主机是down的状态,主机显示没有从机挂载

    bug 从机出现master link status down 原因分析 这里主要是因为redis设置了密码 可以在redis conf文件里面配从不配主 也就是 将master和slave的密码配置相同 然后将slave的配置文件中的ma
  • matlab的一些基本矩阵函数总结

    单位矩阵的生成 A eye 3 3 生成一个3 3的单位矩阵 随机矩阵的生成 A rand 4 5 生成一个4 5的随机矩阵 对角矩阵的生成 d diag A 若A是一个矩阵 则d为取A对角线元素组成的一个向量 如果A为一个向量 则d是一个
  • 归并排序与基数排序

    你好 我是史丰源 欢迎你的来访 希望我的博客能给你带来一些帮助 我的Gitee 代码仓库 归并排序与基数排序 归并排序 概念 来自Wikipedia 实现算法 来自Wikipedia 基数排序 概念 来自Wikipedia 举例 来自Wik
  • 第十七讲:神州三层交换机DHCP服务器配置

    DHCP是基于Client Server模式的协议 DHCP客户机向DHCP服务器索取网络地址及配置参数 服务器为客户机提供网络地址及配置参数 当DHCP客户机和DHCP服务器不在同一子网时 需要由DHCP中继为DHCP客户机和DHCP服务
  • 2021年南京市高考成绩查询,2021年南京各高中高考成绩排名及放榜最新消息

    一 2020年南京各高中高考成绩排名及放榜最新消息 南师附中 理科最高分431分 裸分400分及以上147人 据统计 南师附中在本届已有9位同学2019年被中国科技大学创新班提前录取 5位同学被清华 北大保送的情况下 共563人参加高考 理
  • java中的流的分类

    java中的流的分类 按照流是否直接与特定的地方 如磁盘 内存 设备等 相连 分为节点流和处理流两类 节点流 可以从或向一个特定的地方 节点 读写数据 如FileReader 处理流 是对一个已存在的流的连接和封装 通过所封装的流的功能调用
  • 它来了!Flutter3.0新特性全接触

    点击上方蓝字关注我 知识会给你力量 又到了Flutter稳定版发布的时候了 我们非常自豪地宣布Flutter 3 仅仅三个月前 我们宣布Flutter支持Windows 今天 我们很高兴地宣布 除了Windows之外 Flutter现在在m
  • svpwm之先把电机转起来

    学习FOC一段时间 怎是没有长进 一直看书 FOC框架比较复杂 我在想可不可以输出一个固定频率的SVPWM先把电机转动起来 FOC框架如上图 我先实现SVPWM部分 如下图框选的部分 生成7段式SVPWM 1 硬件平台选择 硬件平台 MCU
  • 解决在本地连接不上阿里云服务器mysql服务的问题

    先得在阿里云上把3306端口添加到安全组 首先先进入mysql的服务 选择mysql这个库 然后查看user用户的host属性 会发现其host属性值是localhost 意思是只准许本地的连接访问 此时我们要对他修改为谁都可以访问的 修改
  • Albumentations 对 PIL 图像进行数据增强

    要使用 Albumentations 对 PIL 图像进行数据增强 你需要将 PIL 图像转换为 NumPy 数组 并使用 Albumentations 库中的转换函数来进行数据增强 以下是一个示例代码 import albumentati
  • dhcp协议配置练习

    路由器配置 配置接口ip地址 配置地址池 开启dhcp全局映射 结果 Pc1 Pc2 Pc3 Pc4
  • postman中post请求正常,但是利用postman生成C#后台http模拟代码之后调用失败问题记录

    postman中post请求正常 但是利用postman中code功能生成C 后台代码之后 填入C 后台失败 postman中code生成的代码引用的是RestSharp Restful Client开发 RestSharp帮助类 post
  • vue特性 is ref

    is属性 使用is标签解决页面中出现的小bug 例如下面的例子 div table tbody tbody table div
  • Linux INPUT 子系统实验

    目录 input 子系统 input 子系统简 input 驱动编写流程 input event 结构体 硬件原理图分析 实验程序编写 修改设备树文件 按键input 驱动程序编写 编写测试APP 运行测试 编译驱动程序和测试APP 运行测
  • springBoot打印请求信息日志,如请求头,请求体,请求路径等

    背景 和前端联调 前端总是说接口对了呀 后端说 没有进我的方法呀 后端加日志拦截所以请求 过程 springmvc代码 包装类中报错getReader has already been called for this request 代码里
  • @RefreshScope注解底层源码分析

    写在前面 最近在研究Spring Cloud和Spring Cloud Alibaba源码 在看到Nacos的配置中心的时候 有注意到自动刷新配置的玩法 底层实现依靠 RefreshScope注解 那么为什么要写这篇文章呢 笔者认为 Ref