模拟实现 Spring IOC(详解)

news2024/12/28 3:58:26

文章目录

  • 前言
  • Spring IoC
    • Spring IoC 概述
    • Spring IoC 技术难点
    • Spring IoC 框架思考
      • 需求分析
  • Spring IoC 技术难点实现
  • Spring IoC 模拟实现
    • Bean工厂模式实现
    • Bean注解的循环依赖基础建立

前言

Spring是一种 Java 开发框架,其主要功能有两个:IoC(DI)和AOP。《模拟实现Spring IOC》是本人的一个编程训练项目,为了提升本人的编程能力、JAVA 编程思想,基于框架的角度出发,对 Spring IoC有一个更深层次的认识, DI (依赖注入)的底层实现逻辑有更深的理解。

博主本人初入 Java 不久,能力有限,只将 Spring IoC 完成到:发现Bean注解的循环依赖;

Spring IoC

Spring IoC 概述

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。

  • 反转:程序本身不创建对象,而变成被动的接收对象。

  • 依赖注入:就是利用 set 方法来进行注入的。

Spring IoC 技术难点

  • 包扫描技术的实现
  • 反射机制的使用
  • 自动注入的实现
  • 循环依赖的处理

Spring IoC 框架思考

一个APP中的类对象,不是直接在代码中初始化的,而是通过自动工具(Spring)完成初始化(对象的生成)。

需求分析

假设某个APP中存在多个类:A、B、C,A 类中存在一个成员,其类型是B,B类中存在一个成员,其类型是C。

A 类:

public class A {
	private B b;
	
	……无参、单参构造,getter,setter

	@Override
	public String toString() {
		return "A [b=" + b + "]";
	}
}

B 类:

public class B {
	private C c;
	
    ……无参、单参构造,getter,setter
    
	@Override
	public String toString() {
		return "B [c=" + c + "]";
	}
}

C 类:

public class C {
	…………
}

那么,A、B、C这三个类的成员,将由一个“Bean工厂”自动生成,且,其中的各成员所需对象也是由这个工厂自动“注入”的;并将这些生成的对象,存储到一个Bean工厂中。

在使用时,由自动工具完成从Bean工厂中取出相关对象的操作。

IoC(DI)所处理的类的成员类型主要是类类型,或者说,非八大基本类型和String类型。

Spring IoC 技术难点实现

  • 包扫描技术的实现

    上述代码中涉及到包扫描的工具使用,这里给出具体实现过程,HB个人博客:包扫描工具实现https://blog.csdn.net/SwaggerHB/article/details/130990812?spm=1001.2014.3001.5501

  • 自动注入的实现

    为了模拟实现 IoC 中的自动注入,博主这里定义了一个**@Autowired 注解**

    • 通过包扫描,获取这个类的每一个成员,判断是否有Autowired注解;
    • 如果没有Autowired注解,放弃;
    • 如果有Autowired注解,对这个成员进行注入;

    具体实现在下文中对@Autowired 注解的建立和使用中。

  • 循环依赖的处理

    • 问题描述:

      如果带有@Bean注解的方法是无参方法,当然可以直接执行,并生成一个Bean;

      如果该方法是带参的(当然,这个参数的值(实参),必须能从BeanFactory中获取),那么,这个方法的反射执行,就必须先从BeanFactory中获取一个Bean,然后才能执行之,并获得一个 Bean。但,对于方法在反射机制的扫描过程,不能完全保证应有的顺序!

      更普遍的现象可能会是:多个@Bean注解的方法都是带参数的,而且,这些参数对应的Bean对象还没有生成,可能会在以后的扫描过程中才逐步生成,那就必须处理这样的问题。

      这个问题就等价于说:多个Bean对象,其生成顺序不定(且都是通过带有@Bean注解的方法生成的),那么,这些方法所需
      参数,就形成了所谓的“依赖关系”。

    • 解决方案:

      对于带参的@Bean注解的方法,需要考察期参数依赖;如果参数依赖都能够满足,那么可以执行这个方法,并得到响应的Bean对象;

      如果相关参数不能得到满足,就需要先将这些方法存储起来;并在每生成一个Bean对象时,扫描所有依赖不满足的方法,更改依赖关系,直到某个方法依赖已经满足,则,执行这个方法!

上述算法虽然可以解决问题,但是,其时间复杂度比较高。为降低时间复杂度,可以考虑按照所需依赖的类型,创建一个Map:
ClassB -> getClassA(), getClassE()
ClassC -> getClassA(), getClassB()
ClassD -> getClassB(), getClassC(), getClassE()
ClassA -> getClassE()

Spring IoC 模拟实现

所需注解定义:

  • Component 注解:

    这个注解的意思是:对于这个类,我是否产生一个 Bean 放到 BeanFactory 里面去。

    //如果某一个类带了 Component 注解,那么就需要生成一个对象放到 BeanFactory 里。
    @Retention(RUNTIME)
    @Target(TYPE)
    public @interface Component {
    	String value() default "";
    }
    
  • Autowired 注解:

    @Retention(RUNTIME)
    @Target({ElementType.FIELD, ElementType.METHOD}) //只用于成员和方法
    public @interface Autowired {
    	String value() default "";
    }
    

    这个注解的意思是:某个成员被自动注入,例如:

    public class B {
    	@Autowired
    	private C c;
    	
    	…………
    }
    

    表明 C 成员应该由自动工具自动完成初始化,也就是说从 BeanFactory 中取出 C 的一个成员完成对他的初始化,即注入

  • Bean 注解:

    上述两个注解:@Component注解、@Autowried注解,这些注解需要书写到类和成员上,才能实现其功能。依靠这两个注解,IoC的基本功能已经可以实现,并且避免了循环依赖造成的无限递归;但是,功能还不完善。

    这时候我们也发现了一个问题,对于已经编译成 .class 的类,我们无法将上述两个注解“书写”进这些类中,因此,对于已经编译成.class的类,是不能执行 IoC 功能的。这就是最大的缺陷!

    为解决上述问题,专门引入一个注解:@Bean注解,@Bean 注解专门处理已生成的类的IoC问题。

    @Retention(RUNTIME)
    @Target(METHOD)			//用于方法
    public @interface Bean {
    }
    

    举个例子,如下:

    	//说明:此处的Timer是自己编写的定时器,用来当作已无法更改其源代码的类。
    	@Bean
    	public Timer getTimer() {
    		Timer timer = new Timer();
    		// 在这个方法中,生成了一个Timer类的对象,而且可以预见:
    		// 在这个方法中,我们可以对timer对象进行更复杂的操作!
    		timer.setDelayTime(1500);
    		// 这种操作对于前面实现的Autowired注入方式是无法实现的!
    		// 这相当于增强了IoC时的逻辑操作!
    		return timer;
    	}
    

    我们则需要在 BeanFactory 的 scan 中,去扫描到带有 @Bean 的方法,执行它,并将执行的结果以返回值类型的名字作为 id,和它的返回值形成BeanDefinition

Bean工厂模式实现

BeanFactory 用来存放众多的 Bean ,对所有的 Bean进行封装。

  • BeanDefinition

    public class BeanDefinition {
    	private Class<?> klass;    //Bean的类型
    	private Object object;     //该类的一个对象
    	private boolean isInject;  //是否被注入的标志
    	
    	…………无参构造、一系列getter,setter
    }
    

    为后续的所要产生 Bean 的类,都产生一个对象,并且封装成 BeanDefinition。

  • BeanFactory

    • 包扫描:扫描所有包下,带有Component注解的类

      public class BeanFactory {
          //单例模式的 beanPool;
      	private static final Map<String, BeanDefinition> beanPool;
      	static {
      		beanPool = new HashMap<>();
      	}
      	
      	public BeanFactory() {
      	}
      	//
      	public static void scanBeanByAnnotation(String packageName) throws Exception {
      		PackageScanner scanner = new PackageScanner()
      				.addClassDealer(new IClassDealer() {
      					@Override
      					public void classDealer(Class<?> klass) {
                              //如果没有带Component注解,返回
      						if (!klass.isAnnotationPresent(Component.class)) {
      							return;
      						}
      						//如果带有Component注解,我们需要生成一个对象,放到BeanPool中去
      						try {
      							Object object = klass.newInstance();
      							BeanDefinition beanDefinition = addBean(klass, object);
      							
      							dealBeanAnnotation(beanDefinition);
      							dealDependencyMethod();
      						} catch (Exception e) {
      							e.printStackTrace();
      						}
      					}
      				});
      		scanner.scanPackage(packageName);
      	}
          
          …………
      }
      
    • 获取封装在BeanFactory中的指定类的对象:

      	@SuppressWarnings("unchecked")
      	public <T> T getBean(String name) {
              //从 beanPool 中根据 name 获取 beanDefinition;
      		BeanDefinition beanDefinition = beanPool.get(name);
      		if (beanDefinition == null) {
      			return null;
      		}
      		
      		if (!beanDefinition.isInject()) {
                  //设置已经被注入
      			beanDefinition.setInject(true);
      			doInject(beanDefinition);
      		}
      		
      		return (T) beanDefinition.getObject();
      	}
      	//重载,从 beanPool 中根据 klass 获取 name;
      	public <T> T getBean(Class<?> klass) {
      		return getBean(klass.getName());
      	}
      
    • 注入操作的实现

      	private void doInject(BeanDefinition bean) {
      		Class<?> klass = bean.getKlass();
      		Object object = bean.getObject();
      		//获取这个类的每一个成员,判断是否有Autowired注解
      		Field[] fields = klass.getDeclaredFields();
      		for (Field field : fields) {
                  //如果没有Autowired注解,放弃
      			if (!field.isAnnotationPresent(Autowired注解.class)) {
      				continue;
      			}
      			//如果有Autowired注解,对这个成员进行注入
      			Class<?> fieldType = field.getType();		//获取这个成员的类型
      			/**
                  *使用下面的代码,Bean对象就是单例的;
      			*/
      			Object fieldValue = getBean(fieldType);		//根据成员的类型,获取在BeanFactory存放的这个成员的对象
                  /**
                  *如果发现用户要求非单例,则需要根据fieldType,再次生成一个对象,
      			*而且还需要进一步完成这个对象的“注入”工作
      			*/
      			
      			field.setAccessible(true);
      			// 上面的操作有违Java编程精神,有违面向对象编程思想。
      			// private权限就是为了保护成员和方法的,而setAccessible()方法彻底使得
      			// 保护权限失去其作用!所以,这种注入方法不提倡!
      			// 建议:对setter方法使用@Autowired注解!
      			try {
      				field.set(object, fieldValue);
      				
      			} catch (IllegalArgumentException e) {
      				e.printStackTrace();
      			} catch (IllegalAccessException e) {
      				e.printStackTrace();
      			}
      		}
      	}
      
    • @Bean注解的处理:

      	private static void dealBeanAnnotation(BeanDefinition beanDefinition) throws Exception {
      		Class<?> klass = beanDefinition.getKlass();
      		Object object = beanDefinition.getObject();
      		
      		Method[] methods = klass.getDeclaredMethods();
      		for (Method method : methods) {
      			if (!method.isAnnotationPresent(Bean.class)) {
      				continue;
      			}
      
      			MethodDependencyPool.addBeanMethodDefinition(object, method);
      		}
      	}
      
    • @Bean注解的循环依赖

      • 将 Bean 封装后,添加到BeanPool中:

        	private static BeanDefinition addBean(Class<?> klass, Object object) {
        		BeanDefinition bean = new BeanDefinition();
        		bean.setKlass(klass);
        		bean.setObject(object);
        		bean.setInject(false);
        		
        		beanPool.put(klass.getName(), bean);
        		
        		MethodDependencyPool.adjustDependency(klass);
        		
        		return bean;
        	}
        
      • 判断beanPool中是否存在这个类的Bean

        	static boolean isBeanExist(Class<?> klass) {
        		return beanPool.containsKey(klass.getName());
        	}
        
      • 反射执行带有@Bean注解的方法:

        	private static void doBeanMethod(BeanMethodDefinition beanMethodDefinition) throws Exception {
        		Object object = beanMethodDefinition.getObject();
        		Method method = beanMethodDefinition.getMethod();
        		Class<?> beanClass = method.getReturnType();
        		
        		Object[] parameterValues = getParameterValues(method);
        		Object bean = method.invoke(object, parameterValues);
        		
        		addBean(beanClass, bean);
        	}
        
      • 处理MethodDependencyPool池子中的方法:

        	private static void dealDependencyMethod() throws Exception {
        		while (MethodDependencyPool.hasNext()) {
        			BeanMethodDefinition beanMethodDefinition = MethodDependencyPool.next();
        			doBeanMethod(beanMethodDefinition);
        		}
        	}
        
      • 获取这个方法的所有参数的值列表:

        	private static Object[] getParameterValues(Method method) {
        		int parameterCount = method.getParameterCount();
        		if (parameterCount <= 0) {
        			return new Object[0];
        		}
        		Object[] parameterValues = new Object[parameterCount];
        		
        		Parameter[] parameters = method.getParameters();
        		for (int index = 0; index < parameterCount; index++) {
        			Parameter parameter = parameters[index];
        			Class<?> parameterType = parameter.getType();
        			BeanDefinition beanDefinition = beanPool.get(parameterType.getName());
        			parameterValues[index] = beanDefinition.getObject();
        		}
        		
        		return parameterValues;
        	}
        

Bean注解的循环依赖基础建立

  • @Bean注解的循环依赖基础数据创建

    • BeanMethodDefinition类的创建:

      public class BeanMethodDefinition {
      	private Object object;
      	private Method method;
      	private List<Class<?>> parameterTypeList;
      	
      	public BeanMethodDefinition() {
      		this.parameterTypeList = new LinkedList<Class<?>>();
      	}
          
          …………一系列getter、setter
      	
      	void addParameterType(Class<?> parameterType) {
      		this.parameterTypeList.add(parameterType);
      	}
      	
      	void removeParameterType(Class<?> parameterType) {
      		if (this.parameterTypeList.contains(parameterType)) {
      			this.parameterTypeList.remove(parameterType);
      		}
      	}
      	
      	boolean isParameterReady() {
      		return this.parameterTypeList.isEmpty();
      	}
      }
      
    • MethodDependencyPool类的创建:

      public class MethodDependencyPool {
      	/**
      	 * 尚未满足依赖关系的Bean方法的列表
      	 */
      	private static final List<BeanMethodDefinition> beanMethodList = new ArrayList<>();
      	/**
      	 * 键:尚未生成的Bean对象类型名称;
      	 * 值:方法列表;其中的每一个方法,都依赖“键”所表示的Bean对象。
      	 */
      	private static final Map<Class<?>, List<BeanMethodDefinition>> dependencyList 
      			= new HashMap<Class<?>, List<BeanMethodDefinition>>();
      	/**
      	 * 已经满足依赖关系的,可以直接执行的方法列表。
      	 */
      	private static final List<BeanMethodDefinition> readyMethodList = new LinkedList<>();
      	
      	MethodDependencyPool() {
      	}
      	
      	/**
      	 * 增加一个存在依赖关系的方法。
      	 * @param object	相关方法在执行时所需的对象
      	 * @param method	方法
      	 */
      	static void addBeanMethodDefinition(Object object, Method method) {
      		BeanMethodDefinition definition = new BeanMethodDefinition();
      		definition.setObject(object);
      		definition.setMethod(method);
      		
      		int parameterCount = method.getParameterCount();
      		if (parameterCount <= 0) {
      			// 对于无参方法,依赖关系已经满足,可以直接加到readyMethodList中。
      			readyMethodList.add(definition);
      			return;
      		}
      		
      		Map<Class<?>, Integer> parameterTypePool = new HashMap<Class<?>, Integer>();
      		Parameter[] parameters = method.getParameters();
      		for (Parameter parameter : parameters) {
      			Class<?> parameterType = parameter.getType();
      			if (!BeanFactory.isBeanExist(parameterType)) {
      				parameterTypePool.put(parameterType, 0);
      			}
      		}
      		
      		if (parameterTypePool.isEmpty()) {
      			readyMethodList.add(definition);
      			return;
      		}
      		
      		for (Class<?> clazz : parameterTypePool.keySet()) {
      			definition.addParameterType(clazz);
      			
      			if (!dependencyList.containsKey(clazz)) {
      				dependencyList.put(clazz, new ArrayList<>());
      			}
      			
      			List<BeanMethodDefinition> methodDependencyList = dependencyList.get(clazz);
      			methodDependencyList.add(definition);
      		}
      	}
      	
      	static boolean hasNext() {
      		return !readyMethodList.isEmpty();
      	}
      	
      	static BeanMethodDefinition next() {
      		return readyMethodList.remove(0);
      	}
      	
      	/**
      	 * 根据新生成的Bean对象,调整方法依赖关系。
      	 * @param klass	新生成的Bean对象的类型
      	 */
      	static void adjustDependency(Class<?> klass) {
      		if (!dependencyList.containsKey(klass)) {
      			return;
      		}
      		
      		List<BeanMethodDefinition> findBeanMethodList = dependencyList.remove(klass);
      		for (BeanMethodDefinition beanMethod : findBeanMethodList) {
      			beanMethod.removeParameterType(klass);
      			if (beanMethod.isParameterReady()) {
      				beanMethodList.remove(beanMethod);
      				readyMethodList.add(beanMethod);
      			}
      		}
      	}
      }
      

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

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

相关文章

什么是高并发?

目录 什么是高井发系统 1.1 什么是高井发 1.2 高井发系统有哪些关键指标 1.2.1 响应时间 1.2.2 吞吐量 1.2.3 每秒请求数(QPS) 1.2.4 每秒事务数 (TPS) 1.2.5 访问量 (PV) 1.2.6 独立访客 (UV) 1.2.7 网络流量 1.3 为什么学习高并发系统 1.32在面试中脱颖而出 什么…

Android:Selector + Layer-lists + Shape 实现 “缺右下角Button“

UI需求&#xff1a;实现"缺右下角的渐变Button"效果 实现方式有两种&#xff1a; 一.UI绘制.9背景图&#xff0c;Selector直接实现 二.使用Shape与Selector、Layer-lists实现 UI给的设计稿里没有Button背景图&#xff0c;我用Shape做完了他告诉我他有做背景图&…

字符串搜索算法:暴力搜索,KMP

目录 前言废话暴力搜索KMP算法 前言废话 最近脑子有点昏昏沉沉&#xff0c;喝点那种红枣泡的白酒居然神奇的好了一些&#xff0c;感觉很舒服。看来喝少量的酒可以让人更清醒&#xff0c;长期喝可能有养生的效果&#xff1f; 写道这里去百度了下&#xff0c;发现红枣还真有养生效…

js中this关键字的作用和如何改变其上下文

一、this 关键字的作用 JavaScript 中的 this 关键字引用了所在函数正在被调用时的对象。在不同的上下文中&#xff0c;this 的指向会发生变化。 在全局上下文中&#xff0c;this 指向全局对象&#xff08;在浏览器中是 window 对象&#xff0c;在 Node.js 中是 global 对象&…

CV | Emotionally Enhanced Talking Face Generation论文详解及代码实现

本博客主要讲解了Emotionally Enhanced Talking Face Generation&#xff08;情感增强的谈话人脸生成&#xff09;论文概括与项目实现&#xff0c;以及代码理解。 Emotionally Enhanced Talking Face Generation Paper :https://arxiv.org/pdf/2303.11548.pdf Code: GitHub - s…

ROS:服务数据(srv)的定义与使用

目录 一、服务模型二、创建功能包三、自定义服务数据3.1定义srv文件3.2在package.xml中添加功能包依赖3.3在CMakeLists.txt中添加编译选项3.4编译生成语言相关文件 四、创建代码并编译运行&#xff08;C&#xff09;4.1创建代码4.2编译4.3运行 一、服务模型 Client发布显示某个…

价值8800元SEO自动化养权重流量站课程分享(升级版)!

本来想做培训收8800&#xff0c;但是我怕大伙骂我&#xff08;说我割韭菜&#xff09;&#xff0c;所以我决定免费把这套自动化批量养站的技术和流程详细给大家分享出来。有些朋友可能是手动养&#xff0c;我觉得这种思路是没错的&#xff0c;但是有点鸡肋&#xff0c;先说下缺…

电子科技大学计算机系统结构复习笔记(三):流水线技术

目录 前言 重点一览 流水线定义 基本概念 流水线分类 流水线特点 流水线时空图 流水线性能分析 流水线特点 经典5段流水线RISC处理器 流水线的三种冒险 冒险分类 停顿流水线 结构冒险 数据冒险 控制冒险 流水线处理机的指令系统 流水线指令系统与格式 流水…

nvm安装并配置环境变量使用nvm安装、切换nodejs

目录 第一章 准备工作 1.1 卸载nodejs 1.2 安装nvm 第二章 nvm环境配置 第三章 nodejs安装以及环境配置 3.1 会用nvm常用命令 3.2 nodejs安装 3.3 node环境配置 3.4 遇到的问题 第一章 准备工作 1.1 卸载nodejs 找到自己对应的nodejs文件所在路径 where node 通过控…

Python 异常类型捕获( try ... except 用法浅析)——Don‘t bare except (不要让 except 裸奔)

不要让 except 裸奔&#xff01;裸奔很爽&#xff0c;但有隐忧。 (本笔记适合学完 Python 五大基本数据类型&#xff0c;有了些 Python 基础的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程…

大模型时代的来临:AI如何改变人类生活和经济?

大模型时代的来临&#xff1a;AI如何改变人类生活和经济&#xff1f; 第三次AI浪潮之下&#xff0c;人类面临着前所未有的机遇和挑战。随着人工智能的快速发展&#xff0c;我们开始高度重视其可能带来的负面影响。 最近&#xff0c;AI领域再次引起了全球范围内的关注&#xff0…

机器视觉_HALCON_图像采集接口编程手册_1.第一章节介绍

文章目录 一、前言二、图像采集接口编程第一章2.1 HALCON的通用图像采集接口2.2 图像采集基础2.3 同步抓取 vs. 异步抓取⭐2.4 缓冲策略⭐2.5 A/D转换和多路复用2.6 HALCON图像采集算子⭐2.6.1 open_framgrabber2.6.2 close_framegrabber2.6.3 info_framegrabber2.6.4 grab_ima…

chatgpt赋能python:Python均值函数介绍

Python均值函数介绍 Python是一种高级编程语言&#xff0c;非常适合数据处理和分析。在数据分析中&#xff0c;均值通常被用来代表一组数据的平均水平。Python提供了多种方式来计算均值&#xff0c;其中最常用的是使用均值函数来计算。 什么是均值函数&#xff1f; 均值函数…

高通 Camera HAL3:添加一个VendorTag

一.概述 MetadataTag在CamX中有两种体现&#xff0c;可以是预定义的AndroidTag或是自定义VendorTag VendorTag在HAL中定义&#xff0c;用来支持Camx和Chi所需的额外metadata VendorTag类型有三种&#xff1a; hwVendorTagInfocomponentvendortaginfocoreVendorTagInfo 根据不…

「HTML和CSS入门指南」img 标签详解

什么是 img 标签? 在 HTML 中,img 标签用于插入图像。它是一个独立的标签,没有结束标记,并且可以设置多种属性来改变图片的大小、位置、样式等。使用 img 标记可以帮助您更好地展示您的内容,并让浏览器更快地加载网页。 img 标签的基本语法 以下是 img 标签的基本语法: …

卡尔曼滤波与组合导航原理笔记(一)卡尔曼滤波方程的推导 第二部分

文章目录 三、卡尔曼滤波1、随机系统状态空间模型2、状态预测3、状态量测4、增益矩阵K与状态估计5、Kalman滤波公式汇总6、Kalman滤波流程图1.划分为左右两部分&#xff08;一阶矩和二阶矩&#xff09;2.划分为上下两部分&#xff08;时间更新、量测更新&#xff09; 7、Kalman…

ESP8266开发阶段无线WIFI本地烧录升级 -- FOTA

【本文发布于https://blog.csdn.net/Stack_/article/details/130448713&#xff0c;未经允许不得转载&#xff0c;转载须注明出处】 前言 因为正在DIY一个WiFi计量插座&#xff0c;采用非隔离的方案&#xff0c;烧录时要拔掉220V插头&#xff0c;测试时要拔掉USB线&#xff0c;…

php获取文件的权限信息(获取权限信息、返回字符串涵义、二进制的转换方式、权限修改)

php获取文件的权限信息 说明1.获取文件的权限信息2.返回文件权限字符的解读3.转为二进制权限4.修改权限 说明 &#xff08;图片来源于网络&#xff09; 文件权限是指文件或目录对用户和其他进程的访问许可。在 Unix 和 Linux 系统中&#xff0c;文件和目录都有三个权限&#x…

高通 Camera HAL3:CamX、Chi-CDK 详解

网上关于高通CameraHAL3的介绍文档不多&#xff0c;之前做高通CameraHAL3的一些收集、总结&#xff0c;杂乱了一点&#xff0c;将就着看吧。 一.初步认知 高通CameraHAL3的架构很庞大&#xff0c;代码量也很巨大。 先对CamX、Chi-CDK的关键术语、目录等有个初步认知 1.1 术…

Servlet与Mybatis-2

过滤器 过滤器是一种代码重用的技术&#xff0c;它可以改变 HTTP 请求的内容&#xff0c;响应&#xff0c;及 header 信息。过滤器通常不产生响应或像 servlet 那样对请求作出响应&#xff0c;而是修改或调整到资源的请求&#xff0c;修改或调整来自资源的响应。 作用&#x…