Spring IOC 概述
Spring IOC 概述
IOC(Inversion of Control) 控制反转,也叫 DI(D_ependency injection_) 依赖注入。是一种设计思想。不过我并不同意所谓反转的说法,因为没有谁规定什么方式就是“标准”的,如果我把IOC作为“标准”,IOC就是“标准”自身,何来反转?不过我行文也是沿用官方的说法,使用IOC描述这个技术
IOC其实是一种组装的思想,最简单理解 IOC 的方法,就是我们的组装电脑。
主板,硬盘,显卡,CPU厂商们先定义好一个个插口。
然后主板厂商就在他的主板上面预留位置。比如 A 插口是 留给硬盘的,B插口是留给 CPU的。注意,主板生产完成时,硬盘和CPU并没有在主板上,而是空的,不过主板已经写好好了给 CPU 和 硬盘 供电的 功能。
我们的工作就是根据主板的插口来找件,装配,而不是先买CPU、内存、硬盘再去选主板,这就是IOC ,主板需要插什么接口的,我们就去获得相关的零件,这样整个电脑就可以工作。
首先,我们必须要知道,没有 Spring ,我们照样可以做所谓的 IOC,就是 主物预留插口 -> 按插口找件,组装。其实这在我们生活中随处可见。
那么,我们为什么要使用 Spring 呢,我们组装电脑,就要获得关于这个电脑的所有部件,比如主板、电源、内存等等,我们自己当然不可能凭空造出来,但是我们可以上商城购买,我们只需要关心接口对不对,而不用关心他是怎么生产的。那么,Spring 的角色就是一个商城。
我们可以把 Spring IOC 看成一个全产业链的商城,他会把产业链中的第一层原料开始,每一层都登记进入商城。在这个商城里面,我们可以买到任何层级的零件。之后,我们就可以自由的控制一切:如果我们是厂商,我们可以买到我们想要的零件。如果我们是消费者,想组装电脑,就可以买到主板,硬盘和CPU。如果我们有了机房,就可以直接买到若干个电脑。甚至,我们有一个国家,就可以在上面买到关于这个国家的一切。我们再不用关心东西是怎么来的,我们只管买即可。东西生产由其他人负责。而Spring IOC做的就是整个产业链从头到尾的组装,上架工作。
Spring IOC的好处也是众说纷纭,我也没有找到很有说服力的解释,姑且写一些在下面:
轻松实现面向接口编程,中心管理,按需取用,各个环节完全解耦,比如,网站环境和测试环境差异巨大,也可以轻松切换。
可以监听和控制所管理对象的生命周期,并且执行相关的操作
因为可以控制对象的生命周期,所以可以轻松通过 AOP 进行对象增强
轻松整合整个 Java 生态链,对于开发者来说,整合是轻松友好的。
Spring IOC 用法解析
随着 SpringBoot 的潮流, 我们坚持只使用注解配置 Spring IOC
SpringIOC使用例子
maven 配置依赖
12345 |
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.11.RELEASE</version></dependency> |
spring-context 会自动将 spring-core、spring-beans、spring-aop、spring-expression 这几个基础 jar 包带进来。
定义一个接口 (类比的话,就是主物上面的一个接口)
123 |
public interface MessageService { String getMessage();} |
定义接口实现类(类比的话,就是零件自身),并且使用注解注册到 Spring 商城
1234567 |
@Component("messageService")public class MessageServiceImpl implements MessageService { public String getMessage() { return "hello world"; }} |
使用: (类比的话,就是从商城买东西) (这里没有使用依赖注入(自动装配),下面会介绍)
12345678910 |
// Main.java@ComponentScan // 非常重要,会扫描包下所有注解public class Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(Main.class); MessageService messageService = (MessageService) context.getBean("messageService"); System.out.println(messageService.getMessage()); // 输出 Hello, World }} |
SpringIOC 登记方式
Spring中管理的对象都叫 Bean,就像商城里面的一种商品。Spring 管理的对象默认是单例的,也就是一种商品只有一件,不过可以重复购买(注入)。下面,我们说一下如何上架这些商品。
普通类的对象登记
@Component 通用的登记方式,以下的注解都包含这个注解的功能,并且可能有额外的含义
@Service 通常用于服务层
@Controller 通常用于控制层
@Repository 通常用于数据库仓库层
后面加(“”) 定义 bean 的名字,也可以不定义自动由 Spring 生成。
例子:
1234567 |
@Component("messageService")public class MessageServiceImpl implements MessageService { public String getMessage() { return "hello world"; }} |
工厂创建的对象登记
我们有时候想通过一个工厂的方式,根据传入不同对象,生成不同的对象,并登记到 Spring,我们要这么做
1234567891011121314 |
@Configurationpublic class DataConfig{ @Bean("aSource") // 配置文件来源,通常用 properties 文件定义 public DataSource source(){ DataSource source = new RedisProperties(); source.setHost("localhost"); source.setPassword("123"); return source; } @Bean(name = "redisTemplate") public RedisTemplate redisTemplate(@Qualifier("aSource") DataSource source) { return super.stringRedisTemplate(source); }} |
我们现在就注册到了一个 redisTemplate 的 Bean,是通过我们的配置文件生产的。
其中有这些注解
@Configuration 说明这是个配置类
@Bean 说明这个函数是用来创建 Bean 的
@Qualifier 说明我们引入哪一个 Bean 作为 传入参数。
我们可以写多组这样的函数,就会创造出不同的 Bean
另外还有可能用到的是 @Lazy ,可以让 bean 在用的时候再加载。
SpringIOC 注入方式
当然,除了上面的 getBean 外,Spring还给我们封装了许多方法方便我们买东西:
其中,@Autowired 注解 最常用,意思是按类型装配,如果这个类型的零件只有一个,那么就默认选这一个。如果是有多个,那么还需要我们指定具体是哪一个。
setter
12345678 |
@Componentpublic class Customer { private Person person; @Autowired public void setPerson (Person person) { this.person=person; }} |
filed
123456 |
@Componentpublic class Customer { @Autowired private Person person; private int type;} |
构造函数
12345678 |
@Componentpublic class Customer { private Person person; @Autowired // 甚至可以直接不写 public Customer (Person person) { this.person=person; }} |
如果一个类型对应多个 Bean,使用 @Qualifier 指定
123456789101112131415 |
@Componentpublic class BeanB1 implements BeanInterface { //}@Componentpublic class BeanB2 implements BeanInterface { //}@Componentpublic class BeanA { @Autowired @Qualifier("beanB2") private BeanInterface dependency; ...} |
Spring IOC 生命周期与扩展点
Spring IOC生命周期:
初始化 BeanFactory
创建 BeanFactory
读取 BeanDefinition
通过BeanDefinition,初始化 Bean
供外部调用
BeanFactory销毁
我们可以在 Spring IOC 的生命周期中置入各种我们自定义的逻辑。
单独类实现接口,一般是应用于全局的
Bean 继承 Aware 类,一般是用于该 Bean 获取环境变量
Bean 实现 接口,一般是用于该 Bean
Bean 上 注解,一般是用于该 Bean
初始化 BeanFactory 之后
实现 BeanFactoryPostProcessor 在BeanFactory初始化之后(已经读取了配置但还没初始化Bean),做一些操作,可以读取并修改BeanDefinition
1234567 |
@Componentpublic class LifeCycleControl implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { //.. doSomeThing }} |
初始化 Bean 时
顺序如下:
开始几步分别是:实例化、构造函数、设置属性函数,并且,在这里,其实Spring早就已经把所有属性都注入好了,下面的过程都是 Spring 预留给用户扩展的。
BeanNameAware抽象类 可以 获取到 BeanName,其他几个 Aware 类似
1234567 |
@Componentpublic class TestB extends BeanNameAware { @Override public void setBeanName(String name) { // 会告诉 TestB 他的 BeanName 是什么 }} |
BeanPostProcessor接口 对每一个 Bean 初始化前后进行置入,有多少个 Bean 就会执行多少次
postProcessBeforeInitialization(Object bean, String beanName)
postProcessAfterInitialization(Object bean, String beanName)
@PostConstruct 注解(在Bean上),用户自定义的该Bean的初始化操作
InitializingBean接口的afterPropertiesSet()方法会被调用
init-method 用注解的话,这个一般不用了,一定要用的话,可以用 @Bean(initMethod = “initMethodName”),在配置中心配置,不能在 Bean 上配置。
Spring IOC 构成
Bean
Bean 是 Spring 里面最基本的单位,如果把 Spring 管理的对象看成一群人,那么 Bean 就是每一个人。
在用户看来,bean 就是一个实际的对象,比如
1 |
MessageService messageService = context.getBean(MessageService.class); |
在Spring内部,Bean的本质是一个 BeanName -> BeanDefinition 的键值对 ,即用于搜索的名字,和他的实际定义内容。
比如:
1 |
<bean id="messageService" class="ma.luan.example.MessageServiceImpl"/> |
就定义了一个 Bean,Name 是 messageService ,BeanDefinition 的内容之一是 ma.luan.example.MessageServiceImpl
ApplicationContext:
Spring IOC 的门面,供用户调用,起到统筹全局的作用。背后它从源(Resource)读取配置 ,为每个 bean 生成配置对象(BeanDefinition) 到工厂(BeanFactory)的注册中心(Registry) ,控制工厂管理 Bean (Autowire),代理工厂的 getBean 操作。我们可以使用多种输入方式 Context。
BeanFactory
Spring 的核心,他管理着一个注册中心 Registry,并且负责管理 Bean(加载类,实例化,注入属性等),并且提供 getBean 等操作
BeanFactory: 可以获取一个 Bean 的能力的接口,有getBean方法
ListableBeanFactory,有一次获取多个 Bean 能力的接口,有getBeans方法
HierarchicalBeanFactory,有继承能力的工厂接口,有一个 getParentBeanFactory 方法,可以获取父工厂,先不用管
AutowireCapableBeanFactory 可以自动装配的工厂接口,继承它让我们的工厂可以对 Bean 自动创建,属性进行自动插入的能力。autowireBean、createBean、initializeBean、applyBeanPostProcessorsBeforeInitialization 等等,是工厂最核心的能力
DefaultListableBeanFactory,继承了所有接口,是我们最常使用的标准工厂。他继承并实现了所有的能力
getBean
getBeans
getParentBeanFactory
AutowireCapable
Config (修改设置)
ApplicationContext虽然也继承了 BeanFactory,但是实际上是复用了他的 getBean() 等接口,实际逻辑代码并没有什么继承关系。
BeanRegistry
注册中心,维护着多个 Map,用于登记 Bean 的信息,包括 BeanDefinition 的 Map,还有 存放单例的 singletonObjects 的 Map 等等。
BeanDefinition
BeanDefinition存放我们在配置文件中对某个 Bean 的所有注册信息,和存放该对象的实际单例对象。
比如,
我们在 xml 文件中配置
1 |
<bean id="messageService" class="ma.luan.example.MessageServiceImpl"/> |
那么, BeanRegistry 中 会有
messageService -> BeanDefinition
BeanDefinition里面有关于这个 Bean 的所有配置
ResourceLoader
负责从多个配置源(Resource)读取配置文件,源包括 XML、注解等等,XML有可能来自本地或者网络。
DefinitionReader
从 ResourceLoader 加载到配置资源后,把配置转成(read) Definition 的形式
启动过程分析
简单起见,我们可能还是要用回 XML 配置启动的方法(ClassPathXmlApplicationContext)来分析 (真香),不过其实内部大同小异。
123456 |
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml"); MessageService messageService = context.getBean(MessageService.class); System.out.println(messageService.getMessage()); // 输出 hello world} |
ClassPathXmlApplicationContext 构造方法
123456789 |
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); // 保存XML路径 if (refresh) { refresh(); // 第一次执行会到这里,初始化 }} |
核心启动器:refresh 方法
refresh方法是启动的核心方法,执行了启动的所有操作,后面还会提到余下的部分
123456789101112 |
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 prepareRefresh(); // 创建工厂,读取 XML,把 BeanName -> BeanDefinition 在 BeanFactory 的 Registry 里 注册 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // .... }} |
创建工厂并在 Context 中保存引用
obtainFreshBeanFactory 方法
这个方法干了下面几件事
创建了 beanFactory (DefaultListableBeanFactory),并且把 beanFactory 赋值给 Context 保存
读取 Context 的配置,设置该工厂 是否允许 Bean 覆盖、是否允许循环引用 等
Context 读取加载 BeanDefinition,把 BeanDefinition 注册到 BeanFactory 的 Map 中
配置 DefinitionReader,读取 Resource
从XML读取配置树,转换成 Definition,触发监听事件
创建好之后的 beanFactory 的一部分的截图
创建工厂后的维护操作
主要是在 BeanFactory 里面注册实现了各种接口的Bean,Factory会为每一个特殊的接口类型维护一个列表,以后到达特定的位置,就会遍历这个列表。
比如 实现了 BeanPostProcessors 的接口的 Bean 有 A,B,C,那么到时候初始化 Bean 之前,就会遍历调用A,B,C的 postProcessBeforeInitialization方法,初始化 Bean 之后,就会调用 A,B,C 的postProcessAfterInitialization 方法,具体什么时候调用什么方法,请查看后面写的 Spring Bean 生命周期
1234567891011121314151617181920212223242526272829303132333435 |
@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 主要是设置 BeanFactory 的 ClassLoader prepareBeanFactory(beanFactory); try { // 注册实现了 BeanFactory 的 Bean postProcessBeanFactory(beanFactory); // 调用上面注册的 Bean 的相关方法 (用于 BeanFactory 读取 Definition 之后,初始化之前) invokeBeanFactoryPostProcessors(beanFactory); // 这个比较会用到,检测 注册好的 Bean 里面,实现了 BeanPostProcessors 接口的 Bean // 等下初始化 Bean 的前后,会调用这些所有 Bean 的 相关方法 registerBeanPostProcessors(beanFactory); // 国际化的,用不到 initMessageSource(); // 注册ApplicationEvent接口的Bean,初始化事件广播器 initApplicationEventMulticaster(); // 给子类的钩子,会再注册一些内置 Bean onRefresh(); // 注册实现了 ApplicationListener 接口的 Bean registerListeners(); // 初始化所有的 singleton beans (lazy-init 的除外):下面讲 finishBeanFactoryInitialization(beanFactory); // ... } //... } |
初始化 Bean
Spring 默认 初始化的 Bean对象 都是单例的,采用的是单例注册表的方法。
我们重点关注单例如何实现,怎么解决循环引用
首先,初始化入口在 finishBeanFactoryInitialization(beanFactory)
,就是在 refresh() 方法的尾部。这个方法会进行马上初始化的 bean 进行马上初始化。
因为要兼容 延迟初始化(getBean时候加载) 和 马上初始化,所以最合适的方式就是把加载的逻辑写在 getBean 里边,需要马上加载的时候,提前调用 getBean 即可。
finishBeanFactoryInitialization
核心方法是 preInstantiateSinletons():
对符合条件的所有 beandefinition 里面 的 bean 执行了初始化操作:
12345678910111213141516171819202122 |
public void preInstantiateSingletons() throws BeansException { // this.beanDefinitionNames 保存了所有的 beanNames List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames); // 触发所有的非懒加载的 singleton beans 的初始化操作 for (String beanName : beanNames) { // 非抽象、非懒加载的 singletons。如果配置了 'abstract = true',那是不需要初始化的 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { // FactoryBean 的话,在 beanName 前面加上 ‘&’ 符号。再调用 getBean final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName); } else { // 对于普通的 Bean,只要调用 getBean(beanName) 这个方法就可以进行初始化了 getBean(beanName); } } } } |
getBean
我去掉了部分无关紧要的代码,如果有兴趣可以去看原文件 AbstractBeanFactory
1234567891011121314151617181920212223242526272829303132333435 |
@Overridepublic Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false);} @SuppressWarnings("unchecked")protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // 获取 BeanName final String beanName = transformedBeanName(name); // 这个是返回值 Object bean; // 检查单例是否已经初始化,单例全部注册在 regisrty 的 singletonObjects,多例不会在这里注册 Object sharedInstance = getSingleton(beanName); // 如果注册表中没有这个单例,会返回 null,后面还会讲到这个方法 // 开始创建 bean if (sharedInstance != null) { bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); // 默认非 BeanFactor 时,等同于 bean = sharedInstance } else { // 如果上面为 null,则说明单例未初始化,或者是多例 final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // 这里,实际执行了:this.beanDefinitionMap.get(beanName) // 就是把 BeanDefinition 拿出来了 // 这里有一百多行代码,除了插入各种钩子和特殊情况,其实我们只执行了一行代码 bean = createBean(beanName, mbd, args) } return (T) bean; } |
所以,getBean 就是 配套钩子 + 执行 createBean 方法
创建好的对象会放在 单例注册表 singletonObjects 中,下次再取的时候就从表里取,而不重新创建,从而实现单例模式。
createBean方法
如果单例还未创建,会在此创建 Bean
createBean 方法 主要做了些前置的工作,包括给 AOP 预留的拦截器 (AOP时,返回 Proxy 对象而不是真正的对象)
然后委托给 doCreateBean 方法,主要做了下面这些事:
实例化对象
装配属性
执行 InitializingBean 接口,BeanPostProcessor 接口钩子, init 方法钩子 (生命周期钩子)
我们主要关心一下怎么解决循环引用的问题:
123456789 |
<bean id="circleA" class="entity.CircleA" > <property name="circleB" ref="circleB"/></bean><bean id="circleB" class="entity.CircleB" > <property name="circleC" ref="circleC"/></bean><bean id="circleC" class="entity.CircleC" > <property name="circleA" ref="circleA"/></bean> |
这样就构成了一个循环依赖,而我们默认是单例的,那如何一次性创建三个对象呢?
首先,在实例化对象的时候,先在 singletonFactories 注册一个工厂
1234567 |
addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); }});// this.singletonFactories.put(beanName, singletonFactory); |
circleA 属性注入时,到了 circleB ,会 getBean(circleB) ,然后又会 getBean(circleC)
getBean(circleC) 时,又看到了A ,会调用回 getBean(circleA),在getBean的时候,会调用getSingleton,他会先从工厂取,这个时候A已经在工厂列表了,然后用getObject,就可以拿到A的引用。
123456789101112131415161718192021 |
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); // getBean 默认走这里,从单例注册表拿已经创建好的单例,但是现在A还没创建好 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 单例注册表里面没有,对象正在初始化,符合循环引用的条件 synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // getObject,就是把A的引用拉过来了,A其实还没建好,不过他的引用已经有了 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } // 返回A的引用给C return (singletonObject != NULL_OBJECT ? singletonObject : null); } |
这样就可以解决循环引用的问题
拾遗
Spring中的监听器用法
123456789101112131415161718192021222324252627282930313233 |
// 自定义事件public class MyEvent extends ApplicationEvent { public MyEvent(Object source) { super(source); }}// 自定义 Bean 实现 ApplicationContextAware 接口@Componentpublic class HelloBean implements ApplicationContextAware { private ApplicationContext applicationContext; private String name; public void setApplicationContext(ApplicationContext context) { this.applicationContext = context; } // 当调用 setName 时, 触发事件 public void setName(String name) { this.name = name; applicationContext.publishEvent(new MyEvent(this)); // 这行代码执行完会立即被监听到 } public String getName() { return name; }}// 自定义监听器, 监听上面的事件@Componentpublic class MyApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof MyEvent) { System.out.println(((HelloBean)event.getSource()).getName()); } }} |
Spring IOC中的主要设计模式
单例模式
观察者模式 (Listener)
参考资料
Spring IOC 容器源码分析
Tiny-spring: A tiny IoC container refer to Spring.
Spring4参考手册中文版
Spring Framework Reference Documentation
Spring的扩展点
Spring Bean Life Cycle Tutorial
转载于:https://www.cnblogs.com/endv/p/11257617.html
Spring IOC 概述相关推荐
- Spring(2)——Spring IoC 详解
Spring IoC 概述 IoC:Inverse of Control(控制反转) 读作"反转控制",更好理解,不是什么技术,而是一种设计思想,就是将原本在程序中手动创建对象的控 ...
- Spring IOC 组件概述
IOC 概述 IOC: Inversion of Control(控制反转), 这里其实指的是: 将程序中需要使用的 POJOs, 丢入到容器中, 解析成统一的 BeanDefinition(主要基于 ...
- Spring IOC(一):概述
参考书籍:<Spring技术内幕> 系列文章 Spring IOC(一):概述 Spring IOC(二):初始化 Spring IOC(三):依赖注入 Spring IOC(四):相关特 ...
- Spring IOC和DI概述
一.IOC和DI 1. IOC (Inversionof Control): 其思想是反转资源获取的方向.传统的资源查找方式要求组件向容器发起资源查找请求.作为回应,容器适时的返回资源. 而应用了IO ...
- 1、spring的概述
1.spring的概述 spring是什么 spring的两大核心 spring的发展历程和优势 spring体系结构 spring是什么 Spring 是分层的 Java S ...
- Spring IoC(二)IoC容器的初始化过程
(一)IoC 容器初始化过程概述 1.1简要概述初始化过程 IoC 容器的初始化过程是通过refresh() 方法来启动的,这个方法标识着IoC 容器正式启动.具体来说,这个启动过程包括:BeanDe ...
- Spring IoC(一)IoC容器的设计与实现:BeanFactory与ApplicationContext
在写BeanFactory与ApplicationContext 之前,我想先简单聊一聊Spring IoC 容器,希望能给大家一个参考.如果你对这反面的知识比较了解,可以直接跳过. (一)Sprin ...
- spring ioc原理解析
概述 Spring IOC控制反转,分为两个方面解释: 控制:对象对于内部成员的控制 反转:将之前对象管理自己内部成员,转变为ioc容器管理,目的是接耦 IOC的好处是: 无需手动创建,拿来就用 享受 ...
- [Spring5]Spring框架概述
Spring框架概述 1.Spring是轻量级的开源的JavaEE框架 2.Spring可以解决企业应用开发的复杂性 3.Spring有两个核心部分:IOC和Aop a.IOC:控制反转,把创建对象过 ...
- java 从一个容器获取对象,如何从 Spring IoC 容器中获取对象?
前面几篇文章主要分析了 Spring IoC 容器如何初始化,以及解析和注册我们定义的 bean 信息. 其中,「Spring 中的 IoC 容器」对 Spring 中的容器做了一个概述,「Sprin ...
最新文章
- MIIC:互联网会成基础设施,智能硬件就是互联网硬件
- 读取siftgeo格式文件的matlab程序
- python中uss的用法_使用不同内存ussag管理Python多进程进程进程
- Linux报错./configure: error: C compiler cc is not found
- vue el-upload上传组件限制文件类型:accept属性
- 华为服务器停止响应,windows服务器停止工作
- 处理Excel,填充空白区域
- java中的gridy_JAVA格局管教器.
- 3d打印机 开源资料_3D打印的人类双手,开源课程资料以及更多新闻
- Ubuntu: apt安装clang
- 微信小程序使用组件实现移动端软键盘
- pdf照片显示正常打印时被翻转_2020年广东二级建造师准考证打印常见问题
- MoSE论文中Sequential Synthetic Dataset生成代码(时间序列多任务学习数据集)
- 智力过河游戏c语言,Flash AS代码实现智力过河小游戏
- 计算机毕业设计Java校园闲置物品交换平台系统(源码+系统+mysql数据库+lw文档
- alibaba的druid连接池的监控的两种方法
- python开发和大数据开发工程师_大数据开发工程师的岗位职责
- 算法学习之- 动态规划
- 【XSS漏洞06】神器beEF的安装与简介
- 全志A33移植ubuntu系统记录(1)V1.0(分色排版)