【Spring源码解读一】IoC容器之AnnotationConfigApplicationContext

news2024/11/27 23:52:53

        根据AnnotationConfigApplicationContext类去阅读其将Bean对象交给IoC容器管理的过程。以下这三个代码块是将配置类注册进IoC容器的例子。下面是关于这个类的继承与实现的类图关系树。

public class Test {
	public static void main(String[] args) {
		// 配置类注册进IoC容器
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
		User user = applicationContext.getBean(User.class);
		System.out.println(user.introduce("zhangSan",18));
	}
}
@Configuration
public class SpringConfig {

	@Bean
	public User user() {
		return new User();
	}
}
public class User {
	private String name;
	private Integer age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String introduce(String name, Integer age) {
		return "name:" + name + ",age:" + age;
	}
}

        首先在进入AnnotationConfigApplicationContext类中会先进入到其父类的静态代码块中,这个问题不大,不用刻意去在意。

static {
    // 避免急切的出现类加载器问题导致WebLogic 8.1中的应用程序关闭
    ContextClosedEvent.class.getName();
}

AnnotationConfigApplicationContext构造方法

        可以看到简简单单写了三个方法:this()、register(componentClasses)、refresh()。但在内部却有着十分多的代码量。这里先小小的记录以下这三个方法的作用:

1、this()方法是在AnnotatedBeanDefinitionReader中加载BeanDefinition,也就是Bean描述。还有在ClassPathBeanDefinitionScanner中扫描被@Component、@ManagedBean、@Named注解的类。

2、register()方法是将Bean描述包装成BeanDefinitionHolder再将其BeanDefinitionHolder中的Bean描述注册到Map<String, BeanDefinition> beanDefinitionMap中。

3、refresh()方法主要是刷新IoC容器的十二大步骤,其实也不止十二个步骤。

/**
 * 创建一个新的AnnotationConfigApplicationContext,从给定的组件类派生bean定义并自动刷新上下文。
 * @param componentClasses one or more component classes &mdash; for example,
 * {@link Configuration @Configuration} classes
 */
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
	this();
	register(componentClasses);
	refresh();
}

        这里需要注意的是在进入这个AnnotationConfigApplicationContext的this()无参构造时,由于此类AnnotationConfigApplicationContext继承了GenericApplicationContext,所以会先执行父类的无参构造方法。从以下代码可以看到创建了一个DefaultListableBeanFactory对象,这个对象将会在下面的unwrapDefaultListableBeanFactory中就有这个对象的判断。

public GenericApplicationContext() {
	this.beanFactory = new DefaultListableBeanFactory();
}

this()

        从这个方法调用另一个无参构造如下:一个是AnnotatedBeanDefinitionReader读取器与另一个ClassPathBeanDefinitionScanner扫描器。以下主要讲这两个中的作用。

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

	private final AnnotatedBeanDefinitionReader reader;

	private final ClassPathBeanDefinitionScanner scanner;

	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
    
    // 其它源码先省略
}

AnnotatedBeanDefinitionReader

         从下面源码可以看到从一个参数的有参构造方法中调用了另一个有两个参数的有参构造方法。从前面的源码中也可以知道传来的BeanDefinitionRegistry也就是AnnotationConfigApplicationContext类,那么第二个参数是用getOrCreateEnvironment()方法中获取到的,单从英语角度来说:可以猜测是创建一个环境,那么是什么环境呢,接着往里面看。

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
	this(registry, getOrCreateEnvironment(registry));
}

/**
 * 使用给定的{@link Environment}为给定的注册表创建新的{@code AnnotatedBeanDefinitionReader}
 */
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

getOrCreateEnvironment(registry)

         从下面源码可以看到传来的这个BeanDefinitionRegistory去判断是否为EnvironmentCapable,如果是就返回这个环境,如果不是就返回一个标准环境。

private static Environment getOrCreateEnvironment(BeanDefinitionRegistry registry) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	if (registry instanceof EnvironmentCapable) {
		return ((EnvironmentCapable) registry).getEnvironment();
	}
	return new StandardEnvironment();
}

new ConditionEvalator(register,environment,null)

        接下来再来看看第二个有参构造的这个ConditionEvalator类是做什么的。于是debug去查看。看完后我只能说 我看不懂。所以就先记录以下Spring配置的环境信息。大概是推断是传来的BeanDefinitionFactory是属于哪种BeanFactory,以及环境、资源加载器、类加载器属于哪种。

AnnotationConfigUtils.registerAnnotationConfigProcessors

        进入registerAnnotationConfigProcessors()这个方法中会调用本类的另一个重载方法,在这个重载方法中,第一个会进入到unwrapDefaultListableBeanFactory()方法,并且将BeanDefinfitionRegistry传进去。在这个registerAnnotationConfigProcessors()方法中主要是注册各种处理器。

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
	registerAnnotationConfigProcessors(registry, null);
}

/**
 * 在给定的注册表中注册所有相关的注释后处理程序。
 * @param registry 要操作的注册
 * @param source 配置源元素(已提取)
 * 触发此注册的。可能是{@code null}。
 * @return 一组BeanDefinitionHolders,包含此调用实际注册的所有bean定义
 */
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
		BeanDefinitionRegistry registry, @Nullable Object source) {

	DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
	if (beanFactory != null) {
		if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
            // 注册了实现Order接口的排序器
			beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
		}
		if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
            //设置@AutoWired的候选的解析器
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
		}
	}

    // 存放Bean描述(BeanDefinitionHolder)的包装
	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

	/**
	 * 注册解析我们配置类的后置处理器ConfigurationClassPostProcessor
	 * org.springframework.context.annotation.internalConfigurationAnnotationProcessor
	 */
	if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	/**
	 * 注册处理@Autowired 注解的处理器AutowiredAnnotationBeanPostProcessor
	 * org.springframework.context.annotation.internalAutowiredAnnotationProcessor
	 */
	if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	/**
	 * 注册处理JSR规范的注解处理器CommonAnnotationBeanPostProcessor
	 * org.springframework.context.annotation.internalCommonAnnotationProcessor
	 */
	// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
	if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	/**
	 * 处理jpa注解的处理器
	 * org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
	 */
	// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
	if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		try {
			def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
					AnnotationConfigUtils.class.getClassLoader()));
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException("Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
		}
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	/**
	 * 处理监听方法的注解解析器EventListenerMethodProcessor: org.springframework.context.event.internalEventListenerProcessor
	 */
	if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
	}

	/**
	 * 注册事件监听器工厂: org.springframework.context.event.internalEventListenerFactory
	 */
	if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
	}
	return beanDefs;
}

unwrapDefaultListableBeanFactory

        在这个方法中的DefaultListableBeanFactory就是最前面讲的AnnotationConfigApplicationContext进入this()方法的时候,会先进其父类获取DefaultListableBeanFactory对象。

@Nullable
private static DefaultListableBeanFactory unwrapDefaultListableBeanFactory(BeanDefinitionRegistry registry) {
	// 判断这个配置类是DefaultListableBeanFactory的子类还是GenericApplicationContext的子类
	// 如果是DefaultListableBeanFactory则返回其本身 如果是GenericApplicationContext的子类则返回DefaultListableBeanFactory
	if (registry instanceof DefaultListableBeanFactory) {
		return (DefaultListableBeanFactory) registry;
	} else if (registry instanceof GenericApplicationContext) {
		return ((GenericApplicationContext) registry).getDefaultListableBeanFactory();
	} else {
		return null;
	}
}

        Ok,看到这里,应该重新梳理以下关于AnnotationConfigApplication读取器这个对象中的AnnotationBeanDefinitionReader主要就是对Bean的描述(BeanDefinition)、获取当前的执行环境以及注册各种处理器。

ClassPathBeanDefinitionScanner

        以下就是在这个类中的重载有参构造中传递的顺序。主要是看最后一个有参构造函数

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
	this(registry, true);
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
    this(registry, useDefaultFilters, getOrCreateEnvironment(registry)); // 第三个参数在上面有笔记
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment) {
    this(registry, useDefaultFilters, environment,
         (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;// BeanDefinitionRegistry
    if (useDefaultFilters) registerDefaultFilters(); // 从前面传来的是true 所以会进入注册过滤器方法 下面就是方法
    setEnvironment(environment); // 设置环境
    setResourceLoader(resourceLoader); // 设置资源加载器(由前判断传来的BeanDefinitionRegistry)
}

        由于在这些有参构造中传递的第二个参数为true,即useDefaultFilters为true,所以就会进入到registerDefaultFilters()方法,从英文的理解可以猜测这个方法是注册默认的过滤器。此方法内就是寻找注解的类型,找到了就添加进includeFilters链表中,但这里我更在意的是TypeFilter类型。

public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {

	private final List<TypeFilter> includeFilters = new LinkedList<>();

    protected void registerDefaultFilters() {
        // 如果一个类是被 @Component 注解的就可以被扫描
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
        // 获取 ClassPathScanningCandidateComponentProvider 的类加载器
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			// 扫描被 @ManagedBean 注解的类
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		} catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			// 扫描被 @Named 注解的类
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		} catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}
}

        进入了源码发现它是个接口,被函数式接口注解@FunctionalInterface所注解。此内部有个match()方法。

@FunctionalInterface
public interface TypeFilter {

	/**
	 * 确定此筛选器是否与给定元数据所描述的类匹配。
	 * @param metadataReader 目标类的元数据读取器
	 * @param metadataReaderFactory 取元数据读取器的工厂
	 * 对于其他类(如超类和接口)
	 * @return 此筛选器是否匹配
	 * @throws IOException in case of I/O failure when reading metadata
	 */
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;

}

        Ok,以上就是关于ClassPathBeanDefinitionScanner扫描器对象的源码分析,总结以下就是设置环境与资源加载器,并且最主要的是获取被@Component注解的类。

        上面就是对于this()中的源码,一个读取器AnnotatedBeanDefinitionReader与一个扫描器ClassPathBeanDefinitionScanner的源码解读。如果有问题可以在评论区相互探讨。笔者还是个正在成长的小白。

register(componentClasses)

        这些方法还是一样在AnnotatedBeanDefinitionReader对象中,可以看英文意思:registerBean()与doRegisterBean(),可以看出来就是注册Bean对象的方法。可以看到就是将进来的配置类包装成BeanDefinition,再将BeanDefinition包装成BeanDefinitionHolder,再将BeanName与BeanDefinition以键值对的形式放入Map中,此Map为beanDefinitionMap

public void register(Class<?>... componentClasses) {
    Assert.notEmpty(componentClasses, "At least one component class must be specified");
    this.reader.register(componentClasses);
}
public void register(Class<?>... componentClasses) {
    for (Class<?> componentClass : componentClasses) {
        registerBean(componentClass);
    }
}
public void registerBean(Class<?> beanClass) {
    doRegisterBean(beanClass, null, null, null, null);
}
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
			@Nullable BeanDefinitionCustomizer[] customizers) {
    // 将传入的配置类包装成BeanDefinition
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    //...中间部分省略
    // 在此方法的内部扫描此配置类是否有@Lazy、@Primary、@DependsOn、@Role、@Description这五个注解
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    //...中间部分省略
    // BeanDefinition -> BeanDefinitionHolder 将BeanDefinition包装成BeanDefinitionHolder
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    // 注册BeanDefinitionHolder的BeanDefinition到Map<String, BeanDefinition> beanDefinition
    // 其中Map中的String其实就是BeanName
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

BeanDefinitionReaderUtils.registerBeanDefinition

        下面第一个registerBeanDefinition()方法的第一个代码块是在BeanDefinitionReaderUtils类中的,主要是做的任务就是提取出beanName然后再执行另一个registerBeanDefinition()方法,而这个方法是在DefaultListableBeanFactory中,我放到下面的第二个代码块中。

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    // 注册BeanDefinitionHolder的BeanDefinition到Map<String, BeanDefinition> beanDefinition
    // 其中String其实就是BeanName
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

DefaultListableBeanFactory.registerBeanDefinition

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (existingDefinition != null) {
        //...省略
    } else {
        if (hasBeanCreationStarted()) {
            //...省略
        } else {
            // Still in startup registration phase
            // 注册BeanDefinitionHolder的BeanDefinition到Map<String, BeanDefinition> beanDefinition
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            removeManualSingletonName(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }
}

        Ok,以上就是register()方法的源码解读了,我认为在这里面主要是将配置类包装成BeanDefinition再包装成BeanDefinitionHolder,然后再将BeanDefinitionHolder中的BeanDefinition放入beanDefinitionMap中。其中key是beanName,就是配置类的类名,value就是Bean的描述。

        第三个refresh()方法我放到下一个博文中再详细说明。这篇已经1.5w字了,有点多。

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

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

相关文章

解决Ubuntu16中安装opencv后找不到vtk库的问题

最近一个项目中要用到OpenCV的VTK库&#xff0c;但引入头文件#include <opencv2/viz.hpp>时却说找不到这个库&#xff0c;网上搜了下说在编译opencv源码的时候&#xff0c;需要加上编译VTK库的选项&#xff0c;于是重新下载、编译、安装了源码&#xff0c;在cmake时加上了…

最流行的AI绘图工具Midjourney,你不得不知道的使用技巧

​关注文章下方公众号&#xff0c;可免费获取AIGC最新学习资料 本文字数&#xff1a;1500&#xff0c;阅读时长大约&#xff1a;10分钟 Midjourney成为了最受欢迎的生成式AI工具之一。它的使用很简单。输入一些文本&#xff0c;Midjourney背后的大脑&#xff08;或计算机&#…

Linux 权限

目录 一、 从问题开始 问题一: 什么叫shell? 问题二: 为什么不能直接使用kernel呢? 问题三: shell 与bash 有什么不同吗? 二、 Linux权限 0x01 Linux用户 0x02 切换用户命令 0x03 sudo命令 0x04 权限的相关概念 0x05 chmod 0x06 chown 0x07 chgrp 0x08 文件权…

重磅!软著申请不需要邮寄纸质材料啦,附软著申请流程。

重磅&#xff01;软著申请不需要邮寄纸质材料啦&#xff0c;附软著申请流程。 最新消息申请流程一&#xff0c;准备申请材料二&#xff0c;申请人填写申请表三&#xff0c;提交申请材料四&#xff0c;补正五&#xff0c;审查六&#xff0c;发布公告七&#xff0c;接受异议八&am…

力扣---二叉树OJ题(多种题型二叉树)

文章目录 前言&#x1f31f;一、剑指 Offer 55 - I. 二叉树的深度&#x1f30f;1.1 链接&#xff1a;&#x1f30f;1.2 代码一&#xff1a;&#x1f30f;1.3 代码二&#xff1a;&#x1f30f;1.4 流程图&#xff1a; &#x1f31f;二、100. 相同的树&#x1f30f;2.1 链接&…

超强实用!利用xfsdump和xfsrestore打造无懈可击的数据备份与恢复策略

前言 上次我们分析了EXT文件系统的恢复方式&#xff0c;借助于extundelete工具仅可以恢复EXT类型的文件&#xff0c;但无法恢复CentOS 7系统&#xff0c;因为centos7默认采用xfs类型的文件。 xfs文件系统恢复工具有以下几种&#xff1a; xfsprogs&#xff1a;xfs文件系统扩展…

HTB MonitorsTwo

MonitorsTwo HTB MonitorsTwo 老规矩信息收集了&#xff1a; NMAP信息收集 ┌──(kali㉿kali)-[~/桌面] └─$ sudo nmap --min-rate 1000 10.10.11.211 Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-19 09:18 CST Nmap scan report for 10.10.11.211 Host is up…

Python入门(十六)函数(四)

函数&#xff08;四&#xff09; 1.传递列表1.1 在函数中修改列表 2.传递任意数量的实参2.1 结合使用位置实参和任意数量实参2.2 使用任意数量的关键字实参 作者&#xff1a;Xiou 1.传递列表 我们经常会发现&#xff0c;向函数传递列表很有用&#xff0c;其中包含的可能是名字…

设计模式-模板方法模式

模板方法模式 问题背景解决方案&#xff1a;模板方法模式基本介绍解决问题代码示例运行结果 钩子方法注意事项和细节 问题背景 豆浆的制作&#xff1a; 1&#xff09;制作豆浆的流程&#xff1a;选材—>添加配料—>浸泡—>放到豆浆机打碎 2&#xff09;通过添加不同…

高可用性和双机热备浅析

在用户眼里&#xff0c;业务需要永远正常对外提供服务&#xff0c;这就要求应用系统的高可用&#xff08;High availability&#xff0c;即 HA&#xff09;。高可用主要是针对架构而言&#xff0c;第一步一般会采用分层的思想将一个庞大的应用系统拆分成应用层、中间件、数据存…

SpringBoot+MyBatis-plus实现CRUD (踩坑总结!!!)

一、创建项目&#xff0c;引入相应的依赖 (项目源码在文末) &#xff08;不要选Module !!!!&#xff09; <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version&g…

linux常见的二十多个指令

目录 一、指令的概念 二、28个常见的指令 ⭐2.1 ls指令 ⭐2.2 pwd指令 ⭐2.3 cd指令 ⭐2.4tree指令 ⭐2.5 mkdir指令 ⭐2.6 touch指令 ⭐2.7 rmdir指令 ⭐2.8 rm指令 ⭐2.9 clear指令 ⭐2.10 man指令 ⭐2.11 cp指令 ⭐2.12 mv指令 ⭐2.13 cat指令&#xff08;适…

正规文法、正规表达式、有限自动机及其之间的转换(笔记)

The Equivalent Transforming among RG, RE and FA 正规文法 A Grammar G is a quadruple (四元组):G (VN, VT, S, P ) Where, VN is a finite set of nonterminals.VT is a finite set of terminals.S is the start symbol, S ∈ \in ∈ VN.P is a finite set of product…

.Net 使用OpenAI开源语音识别模型Whisper

.Net 使用OpenAI开源语音识别模型 Whisper 前言 Open AI在2022年9月21日开源了号称其英文语音辨识能力已达到人类水准的 Whisper 神经网络&#xff0c;且它亦支持其它98种语言的自动语音辨识。 Whisper系统所提供的自动语音辨识&#xff08;Automatic Speech Recognition&…

python基础知识(四):input语句、if语句和pass语句

目录 1. input语句2. 强制转换3. if语句4. pass语句 1. input语句 input语句是程序获取从键盘输入的内容&#xff0c;会把输入的内容自动转换成字符串。 使用方法: 变量名 input(“提示语”) 例如 language input("你最爱什么语言?") print(language)这两行代码…

RK3588平台开发系列讲解(项目篇)常见模型结构

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、DNN二、CNN三、RNN沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 AI 模型常采用人工神经网络来模拟人脑神经的记忆和处理信号的能力。常见的人工神经网络类型有深度神经网络(Deep Neural Network,DNN)…

Vue中组件的几个重要点

1 单词命名组件名称 vue推荐的命名组件名称有以下几种&#xff1a; 首先看下组件有几个单词构成 单个单词 如果只有一个单词&#xff0c;那么建议全部小写&#xff0c;用的时候也是全部小写的&#xff0c;或者首字母大写 有人喜欢哪怕只有一个单词也首字母大写&#xff0c;…

读论文-GPRAR

论文&#xff1a;GPRAR: Graph Convolutional Network based Pose Reconstruction and Action Recognition for Human Trajectory Prediction&#xff08;2016&#xff09; 摘要 高精度的预测对于自动驾驶等各种应用至关重要。现有的预测模型在现实世界中很容易出错&#xff0…

linux【网络编程】之HTTP协议

一文了解应用层协议&#xff1a;HTTP协议 一、HTTP协议二、URL2.1 urlencode和urldecode 三、HTTP协议格式3.1 HTTP请求方法3.2 HTTP状态码3.3 HTTP响应报头 四、结合代码理解HTTP通信流程五、长连接六、http会话保持七、postman和fiddler 一、HTTP协议 在上篇文章中我们模拟了…

YOLOV5 + PYQT5双目测距

YOLOV5 PYQT5双目测距 1. 测距源码2. 测距原理3. PYQT环境配置4. 实验结果 1. 测距源码 详见文章 YOLOV5 双目测距&#xff08;python&#xff09; 2. 测距原理 如果想了解双目测距原理&#xff0c;请移步该文章 双目三维测距&#xff08;python&#xff09; 3. PYQT环境…