@RefreshScope工作原理

2023-11-01

本文主要从两个层次来分析@RefreshScope;

1.加了@RefreshScope注解的bean是如何注入到IOC容器中的;

2.触发@RefreshScope后IOC容器是如何工作的。

注:本文不讨论@RefreshScope是如何触发的,springCloud只是提供了一个规范,每种框架的触发原理机制不同,说实话我也不是很明白,等弄懂了再来写

一、@RefreshScope是如何完成bean的实列化的

首先我们需要了解,我们在不声明bean的作用域时,bean默认是单列的,原因是因为在bean的merge也就是合并时,会自己声明其为单列,当我们加上注解@RefreshScope是,其scope就是refresh,不是singleton

// Set default singleton scope, if not configured before.
if (!StringUtils.hasLength(mbd.getScope())) {
   mbd.setScope(SCOPE_SINGLETON);
}

一般的bean都是通过ClassPathBeanDefinitionScanner#doScan(String... basePackages)先扫描出来加载成beanDefinition,如图所示当加载到加了@RefreshScope的bean时

 这里有行致关重要的代码

AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);

static BeanDefinitionHolder applyScopedProxyMode(
			ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

		ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
		if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
			return definition;
		}
		boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
		return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
	}

可以看到当普通bean没有加上@RefreshScope注解时,代码会直接进入if然后return不会执行后面的代码,那么我们需要看一下 ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);的代码逻辑,这时代码会掉到org.springframework.aop.scope.ScopedProxyUtils#createScopedProxy,这里的代码至关重要

       String originalBeanName = definition.getBeanName();
        BeanDefinition targetDefinition = definition.getBeanDefinition();
        String targetBeanName = getTargetBeanName(originalBeanName);
        RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
        proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
        proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
        proxyDefinition.setSource(definition.getSource());
        proxyDefinition.setRole(targetDefinition.getRole());
        proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
        if (proxyTargetClass) {
            targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
        } else {
            proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
        }

        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
        proxyDefinition.setPrimary(targetDefinition.isPrimary());
        if (targetDefinition instanceof AbstractBeanDefinition) {
            proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition)targetDefinition);
        }

        targetDefinition.setAutowireCandidate(false);
        targetDefinition.setPrimary(false);
        registry.registerBeanDefinition(targetBeanName, targetDefinition);
        return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());

   String targetBeanName = getTargetBeanName(originalBeanName);

//这行代码会生成一个新的beanNamme为scopedTarget.***,

此时 创建了一个新的代理bd,

RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);但是此时bd的类型为ScopedProxyFactoryBean。注意这个proxyDefinition从头到尾没有设置它的作用域scope,也就是说这个bd是一个单列的。最后看这个

 registry.registerBeanDefinition(targetBeanName, targetDefinition);这里将targetDefinition也就是原来的bd,但是名字是新生成的scopedTarget.***不是原来的名字,最后将新生成的bd--proxyDefinition(单列) 返回。返回后又会调用
registerBeanDefinition(definitionHolder, this.registry);此时注入容器的bd用的是原来的beanName,但是bd对象换成了proxyDefinition。这里有点绕需要仔细体会。

 将beanDefinition注入到容器后,接着就是bean的实列化过程,但在实列化前,@Refrescope还会干一些事。
由于整合了springcloud,springboot会自动注入一个类RefreshScope,注意这个是类不是注解,这个类干了什么事呢,首先看这个类的关系图

 这个类实现了BeanDefinitionRegistryPostProcessor,那么会在bean实列化前执行一个方法

	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);
						// surprising that a scoped proxy bean definition is not already
						// marked as synthetic?
						root.setSynthetic(true);
					}
				}
			}
		}
	}

     root.setBeanClass(LockedScopedProxyFactoryBean.class);可以看到这里又替换了bd的beanClass,这个被替换的bd就是我们前面生成的proxyDefinition。这个类后面再将看名字proxy肯定是和代理相关得。

在我们实列化proxyDefinition这个bd时实际上是实列化LockedScopedProxyFactoryBean这个类,这个类实现了两个接口ScopedProxyFactoryBean,MethodInterceptor。MethodInterceptor是和切面相关的。又因为ScopedProxyFactoryBean实现了BeanFactoryAware接口,所有bean的初始化过程中回调会setBeanFactory()方法,这里很简单就是创建了一个代理对象,子类通过如下代码添加切面,也就是会执行this的invoke方法

Advised advised = (Advised) proxy;
advised.addAdvice(0, this);

还有就是ScopedProxyFactoryBean实现了FactoryBean接口,意思当我们通过byType获取bean时,实际上返回的就是我们的代理对象,这个代理对象会继承我们的原始类型
 

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


//这个就是原始类型
   public Class<?> getObjectType() {
        return this.proxy != null ? this.proxy.getClass() : this.scopedTargetSource.getTargetClass();
    }

那么我们看一下invoke

	@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			Method method = invocation.getMethod();
			if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method)
					|| AopUtils.isHashCodeMethod(method) || isScopedObjectGetTargetObject(method)) {
				return invocation.proceed();
			}
			Object proxy = getObject();
			ReadWriteLock readWriteLock = this.scope.getLock(this.targetBeanName);
			if (readWriteLock == null) {
				if (logger.isDebugEnabled()) {
					logger.debug("For bean with name [" + this.targetBeanName
							+ "] there is no read write lock. Will create a new one to avoid NPE");
				}
				readWriteLock = new ReentrantReadWriteLock();
			}
			Lock lock = readWriteLock.readLock();
			lock.lock();
			try {
				if (proxy instanceof Advised) {
					Advised advised = (Advised) proxy;
					ReflectionUtils.makeAccessible(method);
					return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(),
							invocation.getArguments());
				}
				return invocation.proceed();
			}
			// see gh-349. Throw the original exception rather than the
			// UndeclaredThrowableException
			catch (UndeclaredThrowableException e) {
				throw e.getUndeclaredThrowable();
			}
			finally {
				lock.unlock();
			}
		}

的真实逻辑。

ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(),
      invocation.getArguments());

这行代码就是反射调用目标对象的方法,那么目标对象是如何获取的

这个targetSource就是父类的

private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();这个getTarget就是从
public Object getTarget() throws Exception {
    return this.getBeanFactory().getBean(this.getTargetBeanName());
}

原来是从bean容器获取的这个getTargetBeanName就是加了scopedTarget.***的名字是原始的beanDefinition,但这个的scope是refresh。当作用域是refresh时spring是如何实列化的呢?主要是在这段代码中org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

	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);
					}

这里看到首先是通过scopes.get(),其实这里也能猜到 这个获取到的scope类型一定是RefreshScope,那么他是在那里注册的呢,其实这种代码 只需要找到在那里put的就可以顺藤摸瓜找到下面去,

 因为GenericScope是一个beanFactoryPostprocess,在bean实列化前就会执行postProcessBeanFactory将自己注册放scopes中去

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 + "]");
			}
		}
	}

接着看获取到scope后的处理流程
 

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


    ###get方法
	public Object get(String name, ObjectFactory<?> objectFactory) {
		BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
		this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
		try {
			return value.getBean();
		}
		catch (RuntimeException e) {
			this.errors.put(name, e);
			throw e;
		}
	}


   ##value.getBean()
	public Object getBean() {
			if (this.bean == null) {
				synchronized (this.name) {
					if (this.bean == null) {
						this.bean = this.objectFactory.getObject();
					}
				}
			}
			return this.bean;
		}

可以看到这里很明显是先从缓存中获取,若获取不到则调用objectFactory.getObject()实际上就是调用createBean(),创建一个新的bean。这就是scope的流程。

我们总结一下上面的流程,

1.如果用@RefreshScope修饰的单列bean,会往beanFactory中注入2个beanDefinition;第一个是普通的bd,class对象就是改bean本来的class,但这个bd的scope是refresh,beanName=scopedTarget.***;第二个bd是的class是LockedScopedProxyFactoryBean.class这是一个factoryBean,最终通过getObject返回的是一个代理对象,scope为单列singleton,beanName=***,当我们通过依赖注入或者getBean时返回的是一个单列的代理对象。

2.通过代理对象调用目标方法时执行的是

 ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(),
      invocation.getArguments());

执行流程:

 当我们想修改配置文件后,我们只需要清除缓存中的bean,这样就会重新调用createBean()生成一个新bean,@RefreshSocpe触发后也是这种工作原理,请看调用流程

 最终将cache全部清除,清除后会触发bean的destory逻辑。最终发布事件RefreshScopeRefreshedEvent。

这里需要注意一个点当我们加了@Scheduled注解bean被销毁了,并不会立马重建,只有当真正调用目标方法时,此时从缓存中获取不到时,才会从容器中去获取getBean(),此时bean才会真正创建。所以解决这个问题时我们可以通过监听RefreshScopeRefreshedEvent事件,当事件触发时,在调用getBean(name),需要注意这个name必须是加了scopedTarget.前缀的name,或者调用一下目标方法也能触发

。加了scopedTarget.前缀的bean是不允许进行属性注入的

targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);

所以当通过getBean(Class)是获取不到目标类型的bean的

  

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

@RefreshScope工作原理 的相关文章

  • 如何克服原语按值传递的事实

    我有一段很长的代码来计算两个值 doubles 对我来说 我在几个地方使用了这段代码 为了坚持 DRY 原则 我应该将这段代码重构为一个很好的单元测试方法 但是我不能让它返回两个双精度数 而双精度数是原始的 因此不能按值传递和操作 我能想到
  • Java - 为什么不允许 Enum 作为注释成员?

    It says 原始 String Class an Enum 另一个注释 上述任何一个的数组 只有这些类型才是合法的 Annotation 成员 为什么泛型 Enum 不能成为 Annotation 的成员 例如 Retention Re
  • 如何使用 Java 中的 Web 服务(例如 Axis2)发送复杂对象的数组或集合?

    我对 SOAP Web 服务还比较陌生 虽然我完成了一些较小的 Web 服务项目 但我偶然从来不需要返回 或用作参数 复杂 对象的数组或集合 当我尝试这样做时 根据我的 SOAP 绑定风格 我会得到不同的奇怪行为 当我使用RPC 文字 我可
  • 如何创建一个显示 Spinners 的 x 和 y 值的表格?

    我想创建一个位于图表右侧的表格 其中显示 2 列 x 和 y 值已输入到xSpin and ySpin旋转器 我已经画了一张我想要桌子放置的位置的图 我尝试过在网格窗格布局中使用文本框来创建表格并将值直接输入到文本框网格中 但是我无法将它们
  • 是什么决定了从 lambda 创建哪个函数式接口?

    请考虑这个例子 import java util function Consumer public class Example public static void main String args Example example new
  • SAML 服务提供商 Spring Security

    当使用预先配置的服务提供者元数据时 在 Spring Security 中 是否应该有 2 个用于扩展元数据委托的 bean 定义 一份用于 IDP 元数据 一份用于 SP 元数据
  • Hazelcast 分布式锁与 iMap

    我们目前使用 Hazelcast 3 1 5 我有一个简单的分布式锁定机制 应该可以跨多个 JVM 节点提供线程安全性 代码非常简单 private static HazelcastInstance hInst getHazelcastIn
  • hibernate锁等待超时超时;

    我正在使用 Hibernate 尝试模拟对数据库中同一行的 2 个并发更新 编辑 我将 em1 getTransaction commit 移至 em1 flush 之后我没有收到任何 StaleObjectException 两个事务已成
  • 将 SignedHash 插入 PDF 中以进行外部签名过程 -workingSample

    遵循电子书第 4 3 3 节 PDF 文档的数字签名 https jira nuxeo com secure attachment 49931 digitalsignatures20130304 pdf 我正在尝试创建一个工作示例 其中 客
  • 很好地处理数据库约束错误

    再一次 它应该很简单 我的任务是在我们的应用程序的域对象中放置一个具有唯一约束的特定字段 这本身并不是一个很大的挑战 我刚刚做了以下事情 public class Location more fields Column unique tru
  • 普罗米修斯指标 - 未找到

    我有 Spring Boot 应用程序 并且正在使用 vertx 我想监控服务和 jvm 为此我选择了 Prometheus 这是我的监控配置类 Configuration public class MonitoringConfig Bea
  • IntelliJ - 调试模式 - 在程序内存中搜索文本

    我正在与无证的第三方库合作 我知道有一定的String存储在库深处的某个字段中的某处 我可以预测的动态值 但我想从库的 API 中获取它 有没有一种方法可以通过以下方式进行搜索 类似于全文搜索 full程序内存处于调试模式并在某个断点处停止
  • 欧洲中部时间 14 日 3 月 30 日星期五 00:00:00 至 日/月/年

    我尝试解析格式日期Fri Mar 30 00 00 00 CET 14至 日 月 年 这是我的代码 SimpleDateFormat formatter new SimpleDateFormat dd MM yyyy System out
  • 在 Spring 中重构这个的最佳方法?

    private final ExecutorService executorParsers Executors newFixedThreadPool 10 public void parse List
  • 如何在JSTL中调​​用java方法? [复制]

    这个问题在这里已经有答案了 这可能是重复的问题 我只想调用不是 getter 或 setter 方法的方法例如 xyz 类的 makeCall someObj stringvalue Java类 Class XYZ public Strin
  • 我可以创建自定义 java.* 包吗?

    我可以创建一个与预定义包同名的自己的包吗在Java中 比如java lang 如果是这样 结果会怎样 这难道不能让我访问该包的受保护的成员 如果不是 是什么阻止我这样做 No java lang被禁止 安全管理器不允许 自定义 类java
  • spring中如何使用jackson代替JdkSerializationRedisSerializer

    我在我的一个 Java 应用程序中使用 Redis 并且正在序列化要存储在 Redis 中的对象列表 但是 我注意到使用 RedisTemplate 会使用 JdkSerializationRedisSerializer 相反 我想使用 J
  • Trie 数据结构 - Java [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 是否有任何库或文档 链接提供了在 java 中实现 Trie 数据结构的更多信息 任何帮助都会很棒 Thanks 你可以阅读Java特里树
  • 在 RESTful Web 服务中实现注销

    我正在开发一个需要注销服务的移动应用程序 登录服务是通过数据库验证来完成的 现在我陷入了注销状态 退一步 您没有提供有关如何在应用程序中执行身份验证的详细信息 并且很难猜测您在做什么 但是 需要注意的是 在 REST 应用程序中 不能有会话
  • 如何修复:“无法解析类型 java.lang.CharSequence。它是从所需的 .class 文件间接引用的”消息? [复制]

    这个问题在这里已经有答案了 我正在尝试使用这个字符串 amountStr amountStr replace replace replace 但我收到一条错误消息 我知道我收到的错误消息是因为我刚刚发布的字符串已过时 所以我想知道该字符串的

随机推荐

  • webstorm中怎么搜索文件

    Ctrl N 文件搜索 Ctrl SHIFT R 关键字搜索
  • windows搭建ftp服务器、抓取虚拟机数据包、局域网流量监听

    先保证三台主机在同一局域网下 可以相互ping通 控制面板 gt 程序 gt 程序和功能 gt 启用或关闭windows功能 Web管理工具也要选上 进入管理工具 配置登录用户的权限 访问成功 使用kali登录ftp服务器 用户名 anon
  • MySQL服务器断电无法启动处理过程

    问题描述 2021 09 14 09 02 42 f24 InnoDB Operating system error number 1117 in a file operation InnoDB Some operating system
  • SpringBoot之统一返回格式与统一异常处理

    文章目录 导入Jar包 配置统一结果返回 配置全局异常处理 效果测试 在任何接口返回数据时 正确的返回格式 code 状态码 data 数据 message 接口响应信息 一般接口需要的就是这三个数据 code 200 data succe
  • WEB项目中出现The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in ei问题的解决方法

    web项目出现如上问题 据查是版本问题 JSTL 1 0 的声明是 JSTL1 1 的声明是 项目中 已经是 jstl 1 2 版本了 页面中也全部是用这种方式 javaee5之后就只有 jstl jar 这一个jar包了 没有standa
  • FLASH:一种高效的Transformer设计

    背景 近年来 Transformer凭借其优秀的设计 在文本 图像 语音等方向大杀四方 但是由于其attention的二次复杂度限制了其在长序列上的应用 本文提出了一种快 速度快 省 省显存 的模型FLASH Fast Linear Att
  • Allegro 干货知识分享--如何在Allegro中添加泪滴

    背景介绍 有时候在PCB绘制完成后需要对PCB进行添加泪滴的操作 添加泪滴的作用主要是 信号传输时平滑阻抗 减少阻抗的急剧跳变 避免高频信号传输时由于线宽突然变小而造成反射 焊接时可以保护焊盘 避免多次焊接时焊盘的脱落 生产时可以避免蚀刻不
  • Java与C#比较,哪个语言更是适合你?

    Java与C 比较 哪个语言更是适合你 先来说一说Java和c 的一些语言细节上的区别 第1个方面是数据类型方面 c 支持nullable数据类型 而Java不支持 c 支持指针类型 而Java不支持 c 支持无符号整形型 而Java不支持
  • 给一串字符串,打乱字符串顺序

    import java util Random public class Pratice 给一串字符串 打乱字符串顺序 修改字符串有两个思路 1 subString 2 字符数组 public static void main String
  • 4.0寸86盒显示屏调试(三)

    读取了个把星期也没读取出正确的ID号 最终放弃了读取 考虑是不是液晶屏根本没有输出功能 在使用SPI驱动以后 也没有显示白屏或者任何可以让人感觉驱动正确的现象 最终也放弃了在SPI上搞出个现象 转而使用RGB协议直驱 但还是没有结果 最后还
  • 百奥赛图财报解读:CRO业务枝繁叶茂,“千鼠万抗”遍地生花

    命运对勇士说 你无法抵御风暴 勇士回应 我就是风暴 这段对话是对中国创新药行业最好的诠释 回顾中国创新药近十年高速发展期 上千家创新药公司先后诞生 行业被资本推动一路 狂飙 根据医药魔方数据 创新药一级市场报道的融资额从2013年的36亿元
  • 计算机毕业设计-基于SSM的音乐播放器管理系统

    项目摘要 随着社会的发展 计算机的优势和普及使得音乐播放器管理系统的开发成为必需 音乐播放器管理系统主要是借助计算机 通过对首页 音乐推荐 付费音乐 论坛信息 个人中心 后台管理等信息进行管理 减少管理员的工作 同时也方便广大用户对个人所需
  • openGL增强表面细节--高度贴图

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一 高度贴图原理 二 代码实现 1 c 主程序 2 着色器程序 运行效果 源码下载 前言 现在我们扩展法线贴图的概念 从纹理图像用于扰动法向量到扰乱顶点位置本身 实 际上
  • 深度学习环境配置5——windows下的torch-cpu=1.2.0环境配置

    深度学习环境配置5 windows下的torch cpu 1 2 0环境配置 注意事项 一 2021 10 8更新 学习前言 各个版本pytorch的配置教程 环境内容 环境配置 一 Anaconda安装 1 Anaconda的下载 2 A
  • SystemServer启动服务

    一 启动流程 SystemServer的在Android体系中所处的地位 SystemServer由Zygote fork生成的 进程名为system server 该进程承载着framework的核心服务 startSystemServe
  • 超强语义分割算法!基于语义流的快速而准确的场景解析

    论文地址 https arxiv org abs 2002 10120 代码地址 https github com donnyyou torchcv 该论文提出了一种有效且快速的场景解析方法 通常 提高场景解析或语义分割性能的常用方法是获得
  • C语言图形库——EasyX基本贴图

    在C语言的学习过程中 接触最多的就是黑乎乎的DOS窗口 这也是在消磨学习者的兴趣 学到最后可能还不知道C语言到底能做什么 难道就是输入输出数据吗 当然不是 C的用处很广泛 这里不做讨论 我们能不能用C语言做些好玩的东西 当然可以 我们可以做
  • 玩转oled屏(基于SPI协议)

    玩转OLED屏 一 简介 一 SPI协议简介 二 OLED简介 二 OLED滚动显示长字符 一 常用OLED滚屏命令 1 水平左 右移 2 垂直和水平移动 二 取字模 三 OLED屏滑动演示 三 OLED显示温湿度 总结 一 简介 一 SP
  • 爬虫漫游指南:加速乐__jsl_clearance破解

    爬虫漫游指南 JS破解之加速乐 本文会介绍加速乐cookie中的 jsl clearance的生成方式 纯粹技术讨论 如果侵害到任何人的利益 请联系本人邮箱yu haojia foxmail com 会立刻删除 如何识别加速乐 使用加速乐的
  • @RefreshScope工作原理

    本文主要从两个层次来分析 RefreshScope 1 加了 RefreshScope注解的bean是如何注入到IOC容器中的 2 触发 RefreshScope后IOC容器是如何工作的 注 本文不讨论 RefreshScope是如何触发的