spring 源码分析

1 IOC 源码解析

BeanDefinition: bean的定义。里面会有beanClass、beanName、scope等属性

  • beanClass:通过Class.forName生成的Class 对象
  • beanName:context.getBean(“account”),acount就是beanName
  • scope: 作用区分单例bean、原型bean

BeanFactory: 生产bean的工厂

可以把IOC过程分成四个状态:概念态、定义态、纯净态、成熟态。
概念态:只是通过注解或配置文件声明的bean。
定义态:spring 容器通过读取配置文件和注解,把类的定义信息装载到BeanDefinition中。
纯净态:beanFactory通过反射和BeanDefinition生成对象,还没有进行依赖注入。
成熟态:对象在进行依赖注入以及后置处理器处理后就是成熟态。

1.1 通过配置文件进行加载

public class DemoApplication {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(context.getBean("account"));
    }

}

1.2 进入AnnotationConfigApplicationContext

    public AnnotationConfigApplicationContext(String... basePackages) {
        this();
        //包扫描
        this.scan(basePackages);
        //容器刷新
        this.refresh();
    }

1.3 进入核心方法refresh的源码

refresh() 方法是 Spring 上下文启动的核心方法。它包含了多个步骤,这些步骤确保了 Spring 容器被正确初始化和配置。

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符。
            this.prepareRefresh();
            //生成beanFactory
            //把通过注解或配置文件声明的bean,解析成BeanDefinition,注册到BeanFactory中。
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            //设置bean的类加载器,添加几个BeanPostProcessor,手动注册几个特殊的 bean
            this.prepareBeanFactory(beanFactory);

            try {
 
         		//具体的子类可以在这步的时候添加一些特殊的BeanFactoryPostProcessor的实现类
         		//可以对BeanFactory进行一些处理和BeanPostProcessor相似
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                
                //调用BeanFactoryPostProcessor各个实现类的postProcessBeanFactory(factory)方法
                this.invokeBeanFactoryPostProcessors(beanFactory);
                
  				//注册BeanPostProcessor的实现类
         		//此接口两个方法:postProcessBeforeInitialization和postProcessAfterInitialization
         		//两个方法分别在Bean初始化之前和初始化之后得到执行,这里仅仅是注册。
                this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                
                //初始化 MessageSource,用于国际化支持。
                this.initMessageSource();
                
                //初始化事件发布器,用于事件发布和监听。
                this.initApplicationEventMulticaster();
                
                //模板方法,允许子类在容器刷新时进行特定的初始化操作。
                this.onRefresh();
                //注册事件监听器。
                this.registerListeners();
                
                //重点!!!
                //完成BeanFactory的初始化,包括实例化所有的非懒加载的单例bean。
                this.finishBeanFactoryInitialization(beanFactory);
                //完成刷新的最后步骤,比如发布上下文刷新事件。
                this.finishRefresh();
            } catch (BeansException var10) {
                BeansException ex = var10;
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);
                }

                this.destroyBeans();
                this.cancelRefresh(ex);
                throw ex;
            } finally {
                this.resetCommonCaches();
                contextRefresh.end();
            }

        }
    }

(1)【1.准备刷新 】prepareRefresh();
主要是一些准备工作设置其启动日期和活动标志以及执行一些属性的初始化。(不是很重要的方法)

(2)【2.初始化 BeanFactory 】obtainFreshBeanFactory();(重点方法)

    <1>如果有旧的BeanFactory就删除并创建新的BeanFactory

    <2>解析所有的Spring配置文件,将配置文件中定义的bean封装成BeanDefinition,加载到BeanFactory中(这里只注册,不会进行Bean的实例化)

(3)【3.bean工厂前置操作】prepareBeanFactory(beanFactory);

    配置 beanFactory 的标准上下文特征,例如上下文的 ClassLoader、后置处理器等。

(4)【4.bean工厂后置操作】postProcessBeanFactory(beanFactory);

    空方法,如果子类需要,自己去实现 

(5)【5.调用bean工厂后置处理器】invokeBeanFactoryPostProcessors(beanFactory);

    实例化和调用所有BeanFactoryPostProcessor,完成类的扫描、解析和注册

    BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它。

(6)【6.注册bean后置处理器】registerBeanPostProcessors(beanFactory);

    将所有实现了 BeanPostProcessor 接口的类注册到 BeanFactory 中。

(7)【7.初始化消息源】initMessageSource();

    初始化MessageSource组件(做国际化功能;消息绑定,消息解析)

(8)【8.初始化事件广播器】initApplicationEventMulticaster();

    初始化应用的事件广播器 ApplicationEventMulticaster。

(9)【9.刷新:拓展方法】onRefresh();

    空方法,模板设计模式;子类重写该方法并在容器刷新的时候自定义逻辑;

    例:springBoot在onRefresh() 完成内置Tomcat的创建及启动

(10)【10.注册监听器】registerListeners();

    向事件分发器注册硬编码设置的applicationListener,向事件分发器注册一个IOC中的事件监听器(并不实例化)

(11)【11.实例化所有非懒加载的单例bean】finishBeanFactoryInitialization(beanFactory);

    是整个Spring IoC核心中的核心

(12)【12.结束refresh操作】finishRefresh();

    发布事件,ApplicationContext 初始化完成

1.4 核心方法源码解析

1.4.1 obtainFreshBeanFactory

这一步上面简单介绍过了,作用是把配置文件解析成一个个BeanBeanDefinition,并且注册到BeanFactory中,源码:

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        this.refreshBeanFactory();
        return this.getBeanFactory();
    }

我们进入refreshBeanFactory()方法

@Override
protected final void refreshBeanFactory() throws BeansException {
    //如果存在旧的BeanFactory就删除
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
        //创建新的BeanFactory
		DefaultListableBeanFactory beanFactory = createBeanFactory();
        //序列化
		beanFactory.setSerializationId(getId());
        //是否允许 Bean 覆盖、是否允许循环引用
		customizeBeanFactory(beanFactory);
        //根据配置,加载各个Bean,然后放到 BeanFactory 中
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

这个方法的作用是关闭旧的 BeanFactory (如果有),创建新的 BeanFactory,加载 Bean 定义、注册 Bean 等。

1.4.2 实例化所有非懒加载的单例bean

finishBeanFactoryInitialization(beanFactory)
在这个方法中,我们主要做了以下操作:
(1)将之前解析的 BeanDefinition 进一步处理,将有父 BeanDefinition 的进行合并,获得MergedBeanDefinition
(2)尝试从缓存获取 bean 实例
(3)处理特殊的 bean —— FactoryBean 的创建
(4)创建 bean 实例
(5)循环引用的处理
(6)bean 实例属性填充
(7)bean 实例的初始化
(8)BeanPostProcessor 的各种扩展应用
这个方法解析的结束,也标志着Spring IoC重要内容基本都已经解析完毕

finishBeanFactoryInitialization(beanFactory)点进去看源码

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
	// 5.实例化所有剩余(非懒加载)单例对象
	beanFactory.preInstantiateSingletons();	
}

reInstantiateSingletons();点进去看源码

@Override
public void preInstantiateSingletons() throws BeansException {
	List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
	for (String beanName : beanNames) {
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            //判断beanName对应的bean是否为FactoryBean
			if (isFactoryBean(beanName)) {
				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
				if (bean instanceof FactoryBean) {
					final FactoryBean<?> factory = (FactoryBean<?>) bean;
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
										((SmartFactoryBean<?>) factory)::isEagerInit,
								getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						//如果希望急切的初始化,则通过beanName获取bean实例
						getBean(beanName);
					}
				}
			}
			else {
				//如果beanName对应的bean不是FactoryBean,只是普通Bean,通过beanName获取bean实例
				getBean(beanName);
			}
		}
	}
	//遍历beanNames,触发所有SmartInitializingSingleton的后初始化回调
	for (String beanName : beanNames) {
		Object singletonInstance = getSingleton(beanName);
		if (singletonInstance instanceof SmartInitializingSingleton) {
			final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
					smartSingleton.afterSingletonsInstantiated();
					return null;
				}, getAccessControlContext());
			}
			else {
				smartSingleton.afterSingletonsInstantiated();
			}
		}
	}
}

getBean();点进去看源码

@Override
public Object getBean(String name) throws BeansException {
    // 获取name对应的bean实例,如果不存在,则创建一个
    return doGetBean(name, null, null, false);
}

doGetBean();点进去看源码

            

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
 
	// 1.解析beanName,主要是解析别名、去掉FactoryBean的前缀“&”		
	final String beanName = transformedBeanName(name);
	Object bean;
	// 2.尝试从缓存中获取beanName对应的实例
	Object sharedInstance = getSingleton(beanName);
	// 3.如果beanName的实例存在于缓存中
	if (sharedInstance != null && args == null) {
		if (logger.isDebugEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		// 3.1 返回beanName对应的实例对象(主要用于FactoryBean的特殊处理,普通Bean会直接返回sharedInstance本身)
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
 
	else {
		// 4.scope为prototype的循环依赖校验:如果beanName已经正在创建Bean实例中,而此时我们又要再一次创建beanName的实例,则代表出现了循环依赖,需要抛出异常。
		if (isPrototypeCurrentlyInCreation(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
 
		// 5.获取parentBeanFactory
		BeanFactory parentBeanFactory = getParentBeanFactory();
		// 5.1 如果parentBeanFactory存在,并且beanName在当前BeanFactory不存在Bean定义,则尝试从parentBeanFactory中获取bean实例
		if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
			// 5.2 将别名解析成真正的beanName
			String nameToLookup = originalBeanName(name);
			if (parentBeanFactory instanceof AbstractBeanFactory) {
				return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
						nameToLookup, requiredType, args, typeCheckOnly);
			}
			// 5.3 尝试在parentBeanFactory中获取bean对象实例
			else if (args != null) {
				// Delegation to parent with explicit args.
				return (T) parentBeanFactory.getBean(nameToLookup, args);
			}
			else {
				// No args -> delegate to standard getBean method.
				return parentBeanFactory.getBean(nameToLookup, requiredType);
			}
		}
		// 6.如果不是仅仅做类型检测,而是创建bean实例,这里要将beanName放到alreadyCreated缓存
		if (!typeCheckOnly) {
			markBeanAsCreated(beanName);
		}
 
		try {
			// 7.根据beanName重新获取MergedBeanDefinition(步骤6将MergedBeanDefinition删除了,这边获取一个新的)
			final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			checkMergedBeanDefinition(mbd, beanName, args);
 
			// 8.拿到当前bean依赖的bean名称集合,在实例化自己之前,需要先实例化自己依赖的bean
			String[] dependsOn = mbd.getDependsOn();
			if (dependsOn != null) {
				for (String dep : dependsOn) {
					// 8.2 检查dep是否依赖于beanName,即检查是否存在循环依赖
					if (isDependent(beanName, dep)) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
					}
					// 8.4 将dep和beanName的依赖关系注册到缓存中
					registerDependentBean(dep, beanName);
					try {
						// 8.5 获取dep对应的bean实例,如果dep还没有创建bean实例,则创建dep的bean实例
						getBean(dep);
					}
					catch (NoSuchBeanDefinitionException ex) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
					}
				}
			}
 
			// 9.针对不同的scope进行bean的创建
			if (mbd.isSingleton()) {
				sharedInstance = getSingleton(beanName, () -> {
					try {
						// 9.1.1 创建Bean实例
						return createBean(beanName, mbd, args);
					}
					catch (BeansException ex) {
						destroySingleton(beanName);
						throw ex;
					}
				});
				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
			}
 
			else if (mbd.isPrototype()) {
				// 9.2 scope为prototype的bean创建
				Object prototypeInstance = null;
				try {
					beforePrototypeCreation(beanName);
					prototypeInstance = createBean(beanName, mbd, args);
				}
				finally {
					afterPrototypeCreation(beanName);
				}
				bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
			}
	// 10.返回创建出来的bean实例对象
	return (T) bean;
}

getSingleton(beanName); 点进去看源码

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 1.从单例对象缓存中获取beanName对应的单例对象
	Object singletonObject = this.singletonObjects.get(beanName);
	// 2.如果单例对象缓存中没有,并且该beanName对应的单例bean正在创建中
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// 3.加锁进行操作
		synchronized (this.singletonObjects) {
			// 4.从早期单例对象缓存中获取单例对象(之所称成为早期单例对象,是因为
			earlySingletonObjects里
			// 的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作)
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 5.如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用
			if (singletonObject == null && allowEarlyReference) {
				// 6.从单例工厂缓存中获取beanName的单例工厂
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					// 7.如果存在单例对象工厂,则通过工厂创建一个单例对象
					singletonObject = singletonFactory.getObject();
					// 8.将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中
					this.earlySingletonObjects.put(beanName, singletonObject);
					// 9.移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实
					例对象,并且放到earlySingletonObjects缓存了,
					// 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects
					缓存拿到,不需要在用到该单例工厂
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	// 10.返回单例对象
	return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
public boolean isSingletonCurrentlyInCreation(String beanName) {
	return this.singletonsCurrentlyInCreation.contains(beanName);
}

这段代码很重要,在正常情况下,该代码很普通,只是正常的检查下我们要拿的 bean 实例是否存在于缓存中,如果有就返回缓存中的 bean 实例,否则就返回 null。
这段代码之所以重要,是因为该段代码是 Spring 解决循环引用的核心代码。解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该bean 实例还未初始化),并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将
ObjectFactory 放到 singletonFactories 缓存),通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,从而避免出现循环引用导致的死循环。这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的bean 实例,但是由于是单例,所以后续初始化完成后,该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。

另外这个代码块中引进了4个重要缓存:
(1)singletonObjects 缓存:beanName -> 单例 bean 对象。
(2)earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。
(3)singletonFactories 缓存:beanName -> ObjectFactory。
【singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “一、二、三级缓存” 的概念】
(4)singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。

createBean(beanName, mbd, args);点进去看源码

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    ...
	try {
        //创建Bean实例(真正创建Bean的方法)
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isDebugEnabled()) {
			logger.debug("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}
    ...
}

doCreateBean(beanName, mbdToUse, args);点进去看源码

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
	......
	Object exposedObject = bean;
	try {
		//对bean进行属性填充
		populateBean(beanName, mbd, instanceWrapper);
		//对bean进行初始化,方法中激活了Aware
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	......
	//完成创建并返回
	return exposedObject;
}

1.5 IOC流程图

在这里插入图片描述

2 Spring的循环依赖

2.1 三级缓存

三级缓存:

singletonObjects:一级缓存

主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例 Bean 实例,这样的 Bean 能够直接提供给用户使用,我们称之为终态 Bean 或叫成熟 Bean。

earlySingletonObjects:二级缓存

主要存放的已经完成初始化,但属性还没自动赋值的 Bean,这些 Bean 还不能提供用户使用,只是用于提前暴露的 Bean 实例,我们把这样的 Bean 称之为临时 Bean 或早期的 Bean(半成品 Bean)。

singletonFactories:三级缓存

存放的是 ObjectFactory 的匿名内部类实例,调用 ObjectFactory.getObject() 最终会调用 getEarlyBeanReference 方法,该方法可以获取提前暴露的单例 Bean 引用。

2.1 流程分析

Spring 通过三级缓存解决了循环依赖,其中,一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象 earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当 A、B 两个类发生循环引用时,在 A 完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果 A 被 AOP 代理,那么,通过这个工厂获取到的就是 A 代理后的对象,如果 A 没有被 AOP 代理,那么,这个工厂获取到的就是 A 实例化的对象。

当 A 进行属性注入时,会去创建 B,同时,B 又依赖了 A,所以,创建 B 的同时又会去调用 getBean(a) 来获取需要的依赖,此时的 getBean(a) 会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到 B 中。紧接着 B 会走完它的生命周期流程,包括初始化、后置处理器等。当 B 创建完后,会将 B 再注入到 A 中,此时, A 再完成它的整个生命周期。

通过上述流程,可以看出 bean 都是需要先可以被实例化才可以的,所以,这也就是为什么构造器依赖可能会失败的原因。

例如,Bean A 的构造器依赖 B,而实例化 A 需要先调用 A 的构造函数,发现依赖 B,那么,需要再去初始化 B,但是,B 也依赖 A,不管 B 是通过构造器注入还是 setter 注入,此时,由于 A 没有被实例化,没有放入三级缓存,所以, B 无法被初始化,所以,spring 会直接报错。反之,如果 A 通过 setter 注入的话,那么,则可以通过构造函数先实例化,放入缓存,然后再填充属性,这样的话不管 B 是通过 setter 还是构造器注入 A,都能在缓存中获取到,于是可以初始化。

2.3 循环依赖的经典面试题

(1)【Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?】

    答:只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题。

(2)【三级缓存中为什么要添加 ObjectFactory 对象,而不是直接保存实例对象?】

    答:假如想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。

(3)【构造器注入注入的循环依赖为什么无法解决?】

    答:因为我们要先用构造函数创建一个 “不完整” 的 bean 实例,如果构造器出现循环依赖,我们连不完整的 bean 实例都构建不出来。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android水波纹效果

Android水波纹效果 需要到水波纹效果的场景还蛮少的。万一刚好你需要呢 一、思路&#xff1a; 自定义组件SpreadView 二、效果图&#xff1a; 看视频更直观点&#xff1a; Android轮子分享-水波纹效果 三、关键代码&#xff1a; public class SpreadView extends View {pr…

用 NotePad++ 运行 Java 程序

安装包 网盘链接 下载得到的安装包: 安装步骤 双击安装包开始安装. 安装完成: 配置编码 用 NotePad 写 Java 程序时, 需要设置编码. 在 设置, 首选项, 新建 中进行设置, 可以对每一个新建的文件起作用. 之前写的文件不起作用. 在文件名处右键, 可以快速打开 CMD 窗口, 且路…

TaskBuilder SQL执行工具

为了方便开发者连接当前任擎服务器上配置的各个数据源对应的数据库进行相关操作&#xff0c;TaskBuilder提供了一个SQL执行工具&#xff0c;点击系统侧边栏里的执行SQL图标 &#xff0c;即可打开该工具&#xff0c;界面如下图所示&#xff1a; 该工具从上至下分为三个区域&a…

学生信息管理系统(简化版)数据库部分

使用Mysql&#xff0c;与navicat工具 下面是mysql创建的代码&#xff0c;可做必要修改 -- 创建学生学籍信息表 CREATE TABLE StudentEnrollment (-- 学号&#xff0c;作为主键student_id VARCHAR(8) NOT NULL,-- 学生姓名stu_name VARCHAR(8) NOT NULL,-- 学生性别gender VARC…

计算机网络之传输层协议TCP

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络之传输层协议TCP 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目…

美畅物联丨观看实时视频对服务器带宽有什么要求?

​随着互联网的迅猛发展&#xff0c;实时视频观看已然成为人们日常生活中不可或缺的一部分。不管是视频会议、在线教育&#xff0c;还是在线娱乐&#xff0c;实时视频都起到了极为重要的作用。不过&#xff0c;实时视频的流畅播放对服务器的带宽有着极高的要求。本文将深入探究…

SWIRL:有望成为2025年顶级AI搜索引擎

现在几乎每家公司都会有内部文档系统&#xff0c;如阿里的语雀、钉钉&#xff0c;字节的飞书&#xff0c;Confluence&#xff0c;印象笔记等等都可以提供给B端在局域网部署。因此&#xff0c;如果能把搜索功能做得高效&#xff0c;就能提高自家产品的竞争力。 想象一下&#xf…

技术栈6:Docker入门 Linux入门指令

目录 1.Linux系统目录结构 2.处理目录的常用命令 3.Docker概述 4.Docker历史 5.Docker基本组成 6.Docker底层原理 7.Docker修改镜像源 8.Docker基本命令 9.Docker创建Nginx实战 10.数据卷 11.本地目录直接挂载* 12.镜像和dockerfile 13.容器互联与自定义网络 14.…

LeetCode - #156 上下翻转二叉树(会员题)

文章目录 前言1. 描述2. 示例3. 答案关于我们 前言 本题为 LeetCode 的高级会员解锁题 我们社区陆续会将顾毅&#xff08;Netflix 增长黑客&#xff0c;《iOS 面试之道》作者&#xff0c;ACE 职业健身教练。&#xff09;的 Swift 算法题题解整理为文字版以方便大家学习与阅读…

汽车零部件设计之——麦弗逊悬架KC特性分析仿真APP介绍

汽车零部件是汽车工业的基石&#xff0c;是构成车辆的基础元素。一辆汽车通常由上万件零部件组成&#xff0c;包括发动机系统、传动系统、制动系统、电子控制系统等&#xff0c;它们共同确保了汽车的安全、可靠性及高效运行。在汽车产业快速发展的今天&#xff0c;汽车零部件需…

STM32串口接收与发送(关于为什么接收不需要中断而发生需要以及HAL_UART_Transmit和HAL_UART_Transmit_IT的区别)

一、HAL_UART_Transmit和HAL_UART_Transmit_IT的区别 1. HAL_UART_Transmit_IT&#xff08;非阻塞模式&#xff09;&#xff1a; HAL_UART_Transmit_IT 是非阻塞的传输函数&#xff0c;也就是说&#xff0c;当你调用 HAL_UART_Transmit_IT 时&#xff0c;它不会等到数据完全发…

学生信息管理系统(简化版)后端接口

目录 allAPI __init__.py是空文件&#xff0c;目的让python知道这个文件夹是个包 ClassInfo.py from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from API.database import get_db, Base from sqlalchemy import Column, String,…

网络安全信息收集(总结)更新

目录 重点&#xff1a; 前言&#xff1a; 又学到了&#xff0c;就是我们什么时候要子域名收集&#xff0c;什么时候收集域名&#xff0c;重点应该放前面 思考&#xff1a; 信息收集分为哪几类&#xff0c;什么是主域名&#xff0c;为什么要收集主域名&#xff0c;为什么要收…

微信小程序从后端获取的图片,展示的时候上下没有完全拼接,有缝隙【已解决】

文章目录 1、index.wxml2、index.js3、detail.detail为什么 .rich-text-style 样式可以生效&#xff1f;1. <rich-text> 组件的特殊性2. 类选择器的作用范围3. 样式优先级4. line-height: 0 的作用5. 为什么直接使用 rich-text 选择器无效&#xff1f; 总结 上下两张图片…

【达梦数据库】存储过程调用实践案例-select

目录 前言创建表插入数据查询表中数据创建存储过程打开dbms_output包输出开关调用存储过程 前言 如果要在存储过程中执行一个SELECT语句并处理其结果&#xff0c;你不能直接使用EXECUTE IMMEDIATE&#xff0c;因为EXECUTE IMMEDIATE主要用于执行那些不返回行的语句&#xff08;…

C语言:指针(第一天)

C语言&#xff1a;指针&#xff08;第一天&#xff09; 预备知识 内存地址 字节&#xff1a;字节是内存的容量单位&#xff0c;英文名byte&#xff0c;一个字节有8位&#xff0c;即1byte8bits地址&#xff1a;系统为了便于区分每一个字节而对他们逐一进行的编号&#xff0c;…

vue3如何实现点击回车,自动登录?

vue3如何实现点击回车&#xff0c;自动登录&#xff1f; 场景&#xff1a;登录账号的时候&#xff0c;可能有的人不习惯直接点击登录&#xff0c;而是通过顺手敲个回车键实现登录 解决办法&#xff1a;上代码 //监听回车 function onKeyUp(e) {//console.log(e)if (e.key En…

LNMP和Discuz论坛

文章目录 LNMP和Discuz论坛1 LNMP搭建1.1 编译安装nginx服务1.1.1 编译安装1.1.2 添加到系统服务 1.2 编译安装MySQL服务1.2.1 准备工作1.2.2 编辑配置文件1.2.3 设置路径环境变量1.2.4 数据库初始化1.2.5 添加mysqld系统服务1.2.6 修改mysql的登录密码 1.3 编译安装PHP服务1.3…

目标跟踪算法:SORT、卡尔曼滤波、匈牙利算法

目录 1 目标检测 2 卡尔曼滤波 3《从放弃到精通&#xff01;卡尔曼滤波从理论到实践》视频简单学习笔记 3.1 入门 3.2 进阶 3.2.1 状态空间表达式 3.2.2 高斯分布 3.3 放弃 3.4 精通 4 匈牙利算法 5 《【运筹学】-指派问题&#xff08;匈牙利算法&#xff09;》视…

Linux DNS之进阶篇bind-chroot企业级部署方式

BIND-chroot 服务是利用 chroot 机制为 BIND 服务创建伪根目录以限制其访问范围&#xff0c;增强安全性&#xff0c;但配置与维护相对较为复杂的一种服务机制。 本章我们将部署chroot模式的DNS服务&#xff0c;以增加安全性 案例要求&#xff1a; 此案例域名为xjh.com www 解析…