@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工作原理 的相关文章

  • 无论线程如何,对象是否总是能看到其最新的内部状态?

    假设我有一个带有简单整数计数变量的可运行对象 每次可运行对象运行时该变量都会递增 该对象的一个 实例被提交以在计划的执行程序服务中定期运行 class Counter implements Runnable private int coun
  • 在 String 值之后打印 int 值

    我有以下示例代码 int pay 80 int bonus 65 System out println pay bonus bonus pay 有人可以向我解释一下为什么我得到以下输出 145 6580 您的代码正在从左到右解释表达式 pa
  • JAVA 中的 Composer 相当于什么? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我目前从 PHP 转向 java 有没有类似的工具composer https getcomposer org 在 PHP 中用于 JAV
  • 了解 netty 通道缓冲区和水印

    我正在尝试了解网络缓冲区和水印 作为一个测试用例 我有一个 netty 服务器 它向客户端写入数据 客户端被阻止 基本上每次读取之间有 10 秒的睡眠时间 在正常 I O 下 如果接收方被阻塞 TCP 发送方将受到限制 由于流量控制 发送速
  • 如何在 Android 中的 Chrome 或 Firefox 等特定浏览器的 Web 视图中加载应用程序

    我是 Android 新手 我正在做一个应用程序 我需要在平板电脑上的 Web 视图中加载现有的应用程序 在平板电脑中 当我使用 Web 视图加载应用程序时 我的应用程序将加载到默认浏览器中 如何在平板电脑上的 Web 视图中的特定浏览器
  • 垂直 ViewPager 中的动画

    我需要垂直制作这个动画ViewPager https www youtube com watch v wuE 4jjnp3g https www youtube com watch v wuE 4jjnp3g 这是我到目前为止所尝试的 vi
  • 如何将本机数据库运算符 (postgres ~) 与 JPA 标准生成器一起使用?

    我使用 JPA 2 0 标准构建以下查询 简化 select n from notif n where n message b la 我正在使用 postgresql 数据库 我真的需要 运算符 而不是像 我可以使用与 CriteriaBu
  • Java Junit 测试 HTTP POST 请求

    我需要测试以下方法而不改变方法本身 该方法向服务器发出 POST 方法 但我需要制作一个独立于服务器的测试用例 在将其重定向到本地文件之前 我测试了类似的方法 但为此我将协议指定为文件 主机名指定为 localhost 端口指定为 1 我的
  • 使用 kryo 注册课程的策略

    我最近发现了 kryonet 库 它非常棒并且非常适合我的需求 然而 我遇到的一个问题是制定一种好的策略来注册所有可以转移的类 我知道我可以在每个对象中编写一个静态方法 该方法将返回它使用的所有类的列表 但我真的不想这样做 为了我自己的时间
  • Java 中如何验证字符串的格式是否正确

    我目前正在用 Java 编写一个验证方法来检查字符串是否是要更改为日期的几种不同格式之一 我希望它接受的格式如下 MM DD YY M DD YY MM D YY 和 M D YY 我正在测试第一种格式 每次它都告诉我它无效 即使我输入了有
  • 如何在Netbeans中设置JList的ListModel?

    我在 Netbeans IDE 的帮助下设计了一个 Swing GUI 该 GUI 包含一个 JList 默认情况下 它使用 QAbstractListModel 将其作为 JList 构造函数中的参数传递以创建该 JList 我想在 Ne
  • 避免 @Secured 注释的重复值

    我正在尝试使用以下方法来保护我的服务方法 Secured如下 public interface IUserService Secured ROLE ROLE1 ROLE ROLE2 ResponseEntity saveUser Creat
  • 在Java中如何将字节数组转换为十六进制?

    我有一个字节数组 我希望该数组的每个字节字符串转换为其相应的十六进制值 Java中有没有将字节数组转换为十六进制的函数 byte bytes 1 0 1 2 3 StringBuilder sb new StringBuilder for
  • Time.valueOf 方法返回错误值

    我使用 Time valueOf 方法将字符串 09 00 00 转换为 Time 对象 如下所示 Time valueOf LocalTime parse 09 00 00 当我调用 getTime 来显示我得到的值时 28800000
  • Hibernate 标准接受 %% 值

    我正在使用下面的 Hibernate 代码来过滤workFlowName crt add Restrictions like workFlowName workFlow MatchMode ANYWHERE crt is the crite
  • 在java中执行匿名pl/sql块并获取结果集

    我想执行匿名 PL SQL 并需要获取结果集对象 我得到了可以通过在 PL SQL 块内使用游标来完成的代码 但 PL SQL 块本身将以文本形式来自数据库 所以我无法编辑该 PL SQL 块 并且它只会返回两个值 其列名始终相同 它将返回
  • java Web应用程序中的日期转换

    String date1 13 03 2014 16 56 46 AEDT SimpleDateFormat sdf new SimpleDateFormat dd MM yyyy HH mm ss z sdf setTimeZone Ti
  • Java时区混乱

    我正在运行 Tomcat 应用程序 并且需要显示一些时间值 不幸的是 时间快到了 还有一个小时的休息时间 我调查了一下 发现我的默认时区被设置为 sun util calendar ZoneInfo id GMT 08 00 offset
  • 如何在J2ME中获取数字的幂[重复]

    这个问题在这里已经有答案了 可能的重复 J2ME power double double 数学函数实现 https stackoverflow com questions 2076913 j2me powerdouble double ma
  • Android ClassNotFoundException:在路径上找不到类

    10 22 15 29 40 897 E AndroidRuntime 2561 FATAL EXCEPTION main 10 22 15 29 40 897 E AndroidRuntime 2561 java lang Runtime

随机推荐

  • 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是如何触发的