Spring Ioc 容器启动流程

news2024/11/25 22:35:39

Spring容器的启动流程

本文基于 Spring 5.3.23

基于XML文件

public void test() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = applicationContext.getBean("user", User.class);
}

我们进入 ClassPathXmlApplicationContext() 源码

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[] {configLocation}, true, null);
}

显示的是调用另外一个方法

public ClassPathXmlApplicationContext(
       String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
       throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
       refresh();
    }
}

我们可以看到,这个方法就只运行了三行代码:我们逐个分析

super(parent) parent 为 null。我们进入源码看看到底做了什么

super(parent)源码分析

public AbstractXmlApplicationContext(@Nullable ApplicationContext parent) {
		super(parent);
}
public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) {
		super(parent);
}
public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) {
		super(parent);
}
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
		this();
		setParent(parent);
}

我们发现它一直向上调用父类的方法,一直到

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
		this();
		setParent(parent);
}

我们来看看这个 this()函数做了什么

this()源码分析

public AbstractApplicationContext() {
		this.resourcePatternResolver = getResourcePatternResolver();
}

大致就是 给 AbstractApplicationContext这个抽象类的 resourcePatternResolver属性赋值。

我们来看看 getResourcePatternResolver();做了什么

getResourcePatternResolver();源码分析

protected ResourcePatternResolver getResourcePatternResolver() {
		return new PathMatchingResourcePatternResolver(this);
}

大致就是 创建了一个 PathMatchingResourcePatternResolver类的实例对象并返回。

这里 提供了一个参数 this 让我们看看getResourcePatternResolver()函数所在的类是什么样子的。

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext{}

继承了一个 AbstractApplicationContext类,实现了ConfigurableApplicationContext接口。很好,先到这里,放我们进去

PathMatchingResourcePatternResolver()源码,看看这个 this到底代表了什么。

PathMatchingResourcePatternResolver()源码分析

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
		Assert.notNull(resourceLoader, "ResourceLoader must not be null");
		this.resourceLoader = resourceLoader;
}

看到这我们应该明白了 this 就是代表一个 resourceLoader。毕竟 AbstractApplicationContext类继承了 DefaultResourceLoader类。到这里 this()就到头了,大概就是初始化了一个 资源加载器resourceLoader


public AbstractApplicationContext(@Nullable ApplicationContext parent) {
		this();
		setParent(parent);
}

接下来我们来看看setParent(parent)做了什么

setParent(parent)源码分析

public void setParent(@Nullable ApplicationContext parent) {
	this.parent = parent; //赋值
	if (parent != null) {
        //获取 parent 对象的环境
		Environment parentEnvironment = parent.getEnvironment();
        //如果这个环境是 ConfigurableEnvironment 的实例
		if (parentEnvironment instanceof ConfigurableEnvironment) {
            //将 parentEnvironment 强制转换为 父环境,同时,将父环境的配置合并到当前环境
			getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
		}
	}
}
//简而言之,这段代码的作用是设置当前对象的父应用程序上下文,并在父上下文存在且为可配置环境时,将其环境的配置合并到当前环境中。

emmm,我们一路传递上来的 parent 好像是 null 呀。那个这好像就没有发挥什么作用。

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
		this();
		setParent(parent);
}

这个代码就解析完了。就大概初始化了资源解析器。。


public ClassPathXmlApplicationContext(
       String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
       throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
       refresh();
    }
}

接下来分析 setConfigLocations(configLocations);做了什么

setConfigLocations(configLocations);源码分析

我们要先知道 String[] configLocations 这个数组里就一个String对象 applicationContext.xml

public void setConfigLocations(@Nullable String... locations) {
		if (locations != null) {//地址为不空(其实就是配置文件)
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length]; //根据文件的数量
			for (int i = 0; i < locations.length; i++) {
                // configLocation 也是一个 String 类型的数组
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
}

我们来看看 configLocations[i]中具体存的什么东西

this.configLocations[i] = resolvePath(locations[i]).trim();

resolvePath()源码分析

protected String resolvePath(String path) {
		return getEnvironment().resolveRequiredPlaceholders(path);
}

我们看看 getEnvironment()做了什么

public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			this.environment = createEnvironment();
		}
		return this.environment;
	}

创建了一个 ConfigurableEnvironment接口类型的对象。

protected ConfigurableEnvironment createEnvironment() {
		return new StandardEnvironment();
}

实际是 StandardEnvironment类型的对象。

然后用我们创建的对象去调用 resolveRequiredPlaceholders()函数,此时 path 仍旧是 applicationContext.xml

我们去resolveRequiredPlaceholders()源码看看

String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

一个接口的方法,还是要去实现类看看。

在这里插入图片描述

我们去 第一个 AbstractEnvironment类中去看看

public abstract class AbstractEnvironment implements ConfigurableEnvironment {}

这不巧了吗,我们刚生成了一个 ConfigurableEnvironment接口类型的对象,

我们来看看 AbstractEnvironment类中 resolveRequiredPlaceholders()干了什么

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
		return this.propertyResolver.resolveRequiredPlaceholders(text);
}

继续深入。

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
       this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}

很好,已经看不懂了。。。。。。。。。。谁来九九我。

学个单词 Placeholder:占位符。

那么这个 doResolvePlaceholders()这个函数大致做了什么。我们可以大胆的猜一下。text就是我们传过来的配置文件的文件名。

那么这个函数大概就是 对我们的文件名 进行了 一些 操作,比如处理处理一下占位符啥啥的。

好了,我不会了。就先到这里

this.configLocations[i] = resolvePath(locations[i]).trim();

这行代码大概就是对我们的文件名做了点操作。我太菜了,只能看到这里。

感兴趣的 spring setConfigLocations方法分析_blueskygotohz的博客-CSDN博客文章走起


public ClassPathXmlApplicationContext(
       String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
       throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
       refresh();
    }
}

我们传递的 refresh为 true 也就是 一定会运行 refresh()函数。这也是 spring中最重要的一个函数。

refresh()源码分析

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {//先加个锁
       StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");//1
       // Prepare this context for refreshing.
        //准备此上下文以进行刷新
       prepareRefresh();//2
        
       // Tell the subclass to refresh the internal bean factory.
        //告诉子类刷新内部 Bean 工厂。
       ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//3
        
       // Prepare the bean factory for use in this context.
        //准备 Bean 工厂以在此上下文中使用。
       prepareBeanFactory(beanFactory);//4

       try {
          // Allows post-processing of the bean factory in context subclasses.
           //允许在上下文子类中对 Bean 工厂进行后处理。
          postProcessBeanFactory(beanFactory);//5

           //不知道是个什么东西
          StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
           
          // Invoke factory processors registered as beans in the context.
           //调用在上下文中注册为 Bean 的工厂处理器。
          invokeBeanFactoryPostProcessors(beanFactory); // 6

          // Register bean processors that intercept bean creation.
           //注册截获 Bean 创建的 Bean 处理器。
          registerBeanPostProcessors(beanFactory);//7
          beanPostProcess.end();

          // Initialize message source for this context.
           //初始化此上下文的消息源。
          initMessageSource();//8

          // Initialize event multicaster for this context
         //初始化此上下文的事件多播器。
          initApplicationEventMulticaster();//9

          // Initialize other special beans in specific context subclasses.
           //在特定上下文子类中初始化其他特殊 bean。
          onRefresh();// 可以提供子类重写这个方法,在容器刷新时,做些其它操作

          // Check for listener beans and register them.
           //检查侦听器 Bean 并注册它们。
          registerListeners();//10

          // Instantiate all remaining (non-lazy-init) singletons.
           //实例化所有剩余的(非惰性初始化)单例。
          finishBeanFactoryInitialization(beanFactory);//11

          // Last step: publish corresponding event.
           //最后一步:发布相应事件
          finishRefresh();
       }

       catch (BeansException ex) {
          ...
       }

       finally {
          ....
       }
    }
}

我滴妈,这么多。一点一点看吧。。注意注意 refresh()函数 是在 AbstractApplicationContext类中,其实就是 应用上下文容器

1、

StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

不知道干了点啥,不看了

2、 prepareRefresh();

做了一些简单的准备工作

protected void prepareRefresh() {
		// Switch to active.
		this.startupDate = System.currentTimeMillis(); //开始时间
		this.closed.set(false);//设置当前容器未关闭
		this.active.set(true);//设置当前容器已激活

    	//打印容器刷新日志
		if (logger.isDebugEnabled()) {
			if (logger.isTraceEnabled()) {
				logger.trace("Refreshing " + this);
			}
			else {
				logger.debug("Refreshing " + getDisplayName());
			}
		}

		//初始化一下属性(该方法默认是空的,是提供给子类来实现的,
		//假设我们有些工作需要在初始化bean以前就要加载设置等,可以通过重写这个方法来完成)
		initPropertySources();

		//校验设置的属性是否合法 
    	//同样是交给子类去实现
		getEnvironment().validateRequiredProperties();

		//初始化 早期应用监听器
		if (this.earlyApplicationListeners == null) {
			this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
		}
		else {
			// Reset local application listeners to pre-refresh state.
			this.applicationListeners.clear();
			this.applicationListeners.addAll(this.earlyApplicationListeners);
		}

		//初始化一个集合属性,提供用来保存后面创建的事件,如果有事件发生会放入这个集合中
    	//初始化早期应用事件
		this.earlyApplicationEvents = new LinkedHashSet<>();
	}

3、获取一个 beanFactory

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		return getBeanFactory();
}

refreshBeanFactory(): 下面是子类的一个实现

protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) { //已经有工厂了
       destroyBeans();//破坏所有的 bean
       closeBeanFactory();//关闭这个工厂
    }
    try {
       DefaultListableBeanFactory beanFactory = createBeanFactory(); //创建一个工厂
        //设置bean工厂的序列化 id 
       beanFactory.setSerializationId(getId());
        //自定义bean工厂
       customizeBeanFactory(beanFactory);
        //加载bean定义信息
       loadBeanDefinitions(beanFactory);
       this.beanFactory = beanFactory;
    }
    catch (IOException ex) {
       throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

getBeanFactory()

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
    DefaultListableBeanFactory beanFactory = this.beanFactory;//这是我们刚才生成的
    if (beanFactory == null) {
        throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                "call 'refresh' before accessing beans via the ApplicationContext");
    }
    return beanFactory;//返回去
}
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

这行代码就是获取一个全新的 beanFactory

4、 prepareBeanFactory(beanFactory) : 大致就是做了一些预处理

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // Tell the internal bean factory to use the context's class loader etc.
    beanFactory.setBeanClassLoader(getClassLoader());//设置一个 bean 加载器
    
    //表达式语言解析器
    if (!shouldIgnoreSpel) {
        beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
    }
    beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

    // Configure the bean factory with context callbacks.
    //设置添加一个 ApplicationContextAwareProcessor 后置处理器
    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    
    //设置忽略的自动装配的接口,就是设置这些接口的实现类不能通过这些接口实现自动注入
    beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
    beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
    beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
    beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationStartupAware.class);

    //注册可以解析的自动装配
    //假设想要使用@Autowired 注解将Spring提供的 BeanFactory 装配到自己创建的某个类的属性上,就要在此处设置
    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
    beanFactory.registerResolvableDependency(ResourceLoader.class, this);
    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
    beanFactory.registerResolvableDependency(ApplicationContext.class, this);

   //设置添加一个ApplicationListenerDetector后置处理器
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

    //添加编译时支持AspectJ
    if (!NativeDetector.inNativeImage() && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        // Set a temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }

    // Register default environment beans.
    //注册默认的环境 bean 
    //且是单例模式
    if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
        //注册了一个 Environment,该对象中存了一下我们默认的属性
        beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
        //向beanFactory中注册了系统属性属性(一个Map集合)
        beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
        //向beanFactory中注册环境变量等相关信息(一个Map集合)
        beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
    }
    if (!beanFactory.containsLocalBean(APPLICATION_STARTUP_BEAN_NAME)) {
        //向beanFactory中注册 ApplicationStartup 对象。
        beanFactory.registerSingleton(APPLICATION_STARTUP_BEAN_NAME, getApplicationStartup());
    }
}

5、postProcessBeanFactory(beanFactory);

该方法默认是空的,为子类通过的,假设后续开发中,在 beanFactory 创建并且有准备完成后需要执行某些操作,可以提供子类重写这个方法来实现

以上可以看为beanFactory 的创建及预准备工作===========

6、 invokeBeanFactoryPostProcessors(beanFactory)

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    //执行beanFactory后置处理器中的方法,该方法中获取所有BeanFactoryPostProcessor,遍历判断
	//对不同的BeanFactoryPostProcessor进行排序,因为先后执行的顺序不同,
	//PriorityOrdered<BeanDefinitionRegistryPostProcessor>BeanFactoryPostProcessor
	//然后执行后置处理器中定义的初始化 beanFactory 后要执行的方法
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
       beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
       beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

ok 卡! 在看下面的代码之前,了解点知识

什么是后处理(Post-processing)?

在Spring框架中,后处理(Post-processing)是指在应用程序上下文加载和初始化Bean之后,对这些Bean进行进一步的修改或处理的过程。后处理器是实现了相应接口的组件,它们可以在Spring容器生命周期的不同阶段进行自定义的操作。

在Spring中,有多个后处理器接口可供使用,包括:

  1. BeanPostProcessor:该接口定义了在Bean实例化和依赖注入完成之后,在初始化前后对Bean进行修改的方法。可以通过实现该接口来定制Bean的初始化逻辑,例如添加额外的初始化操作、修改属性值等。
  2. BeanFactoryPostProcessor:该接口允许在应用程序上下文创建所有单例Bean之前对Bean工厂进行修改。可以通过实现该接口来修改Bean工厂的配置,例如注册自定义编辑器、添加额外的Bean定义等。
  3. BeanDefinitionRegistryPostProcessor:该接口在Bean定义被加载到应用程序上下文之前,允许对Bean定义进行修改或添加新的Bean定义。可以通过实现该接口来动态注册Bean定义,修改Bean定义的属性等。

通过实现这些后处理器接口,开发人员可以对Spring容器中的Bean进行自定义操作,以满足特定的需求。后处理器在Spring的扩展机制中起到了重要的作用,使得开发者能够更加灵活地定制和扩展应用程序上下文的行为。

invokeBeanFactoryPostProcessors()

public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
		Set<String> processedBeans = new HashSet<>();
    /**
      *1.判断beanFactory是否为BeanDefinitionRegistry,beanFactory为DefaultListableBeanFactory,而				         *DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,因此这边为true
    */
		if (beanFactory instanceof BeanDefinitionRegistry) {
			BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            // regular: 常规  用来存储常规的BeanFactoryPostProcessor
			List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
            // 用于存放BeanDefinitionRegistryPostProcessor
			List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

            // 2.首先处理入参中的beanFactoryPostProcessors
            // 遍历所有的beanFactoryPostProcessors, 将BeanDefinitionRegistryPostProcessor和普通      BeanFactoryPostProcessor区分开

			for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
				if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    // 如果是BeanDefinitionRegistryPostProcessor
					BeanDefinitionRegistryPostProcessor registryProcessor =
							(BeanDefinitionRegistryPostProcessor) postProcessor;
                    //直接执行BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法
					registryProcessor.postProcessBeanDefinitionRegistry(registry);
                    //添加到registryProcessors(用于最后执行postProcessBeanFactory方法)
					registryProcessors.add(registryProcessor);
				}
				else {
                    //加到regularPostProcessors(用于最后执行postProcessBeanFactory方法)
					regularPostProcessors.add(postProcessor);
				}
			}

			// 用于保存本次要执行的BeanDefinitionRegistryPostProcessor
			List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

			// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
            //找出所有实现BeanDefinitionRegistryPostProcessor接口的Bean的beanName
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            //遍历postProcessorNames
			for (String ppName : postProcessorNames) {
                // 校验是否实现了PriorityOrdered接口
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    //获取ppName对应的bean实例, 添加到currentRegistryProcessors中,
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    //将要被执行的加入processedBeans,避免后续重复执行
					processedBeans.add(ppName);
				}
			}
             // 进行排序(根据是否实现PriorityOrdered、Ordered接口和order值来排序)
			sortPostProcessors(currentRegistryProcessors, beanFactory);
            //添加到registryProcessors(用于最后执行postProcessBeanFactory方法)
			registryProcessors.addAll(currentRegistryProcessors);
            //遍历currentRegistryProcessors, 执行postProcessBeanDefinitionRegistry方法
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
            //执行完毕后, 清空currentRegistryProcessors
			currentRegistryProcessors.clear();

			// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
            //找出所有实现BeanDefinitionRegistryPostProcessor接口的类, 这边重复查找是因为执行完上面的BeanDefinitionRegistryPostProcessor,
            //可能会新增了其他的BeanDefinitionRegistryPostProcessor, 因此需要重新查找
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
                // 校验是否实现了Ordered接口,并且还未执行过
				if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
            //遍历currentRegistryProcessors, 执行postProcessBeanDefinitionRegistry方法
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
			currentRegistryProcessors.clear();

			// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
            //最后, 调用所有剩下的BeanDefinitionRegistryPostProcessors
			boolean reiterate = true;
			while (reiterate) {
				reiterate = false;
                //找出所有实现BeanDefinitionRegistryPostProcessor接口的类
				postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
				for (String ppName : postProcessorNames) {
                    //跳过已经执行过的
					if (!processedBeans.contains(ppName)) {
						currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
						processedBeans.add(ppName);
                        //如果有BeanDefinitionRegistryPostProcessor被执行, 则有可能会产生新的BeanDefinitionRegistryPostProcessor,
                    // 因此这边将reiterate赋值为true, 代表需要再循环查找一次
						reiterate = true;
					}
				}
				sortPostProcessors(currentRegistryProcessors, beanFactory);
				registryProcessors.addAll(currentRegistryProcessors);
                //遍历currentRegistryProcessors, 执行postProcessBeanDefinitionRegistry方法
				invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
				currentRegistryProcessors.clear();
			}

			// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
            //调用所有BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法(BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor)
			invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
            //调用入参beanFactoryPostProcessors中的普通BeanFactoryPostProcessor的postProcessBeanFactory方法
			invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
		}

		else {
			// Invoke factory processors registered with the context instance.
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}
    
    // 到这里 , 入参beanFactoryPostProcessors和容器中的所有BeanDefinitionRegistryPostProcessor已经全部处理完毕,
    // 下面开始处理容器中的所有BeanFactoryPostProcessor

		//找出所有实现BeanFactoryPostProcessor接口的类
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);


    //用于存放实现了PriorityOrdered接口的BeanFactoryPostProcessor
		List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    //用于存放实现了Ordered接口的BeanFactoryPostProcessor的beanName
		List<String> orderedPostProcessorNames = new ArrayList<>();
    //用于存放普通BeanFactoryPostProcessor的beanName
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    //遍历postProcessorNames, 将BeanFactoryPostProcessor按实现PriorityOrdered、实现Ordered接口、普通三种区分开
		for (String ppName : postProcessorNames) {
            //跳过已经执行过的
			if (processedBeans.contains(ppName)) {
				// skip - already processed in first phase above
			}
			else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                //添加实现了PriorityOrdered接口的BeanFactoryPostProcessor
				priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
			}
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                //添加实现了Ordered接口的BeanFactoryPostProcessor的beanName
				orderedPostProcessorNames.add(ppName);
			}
			else {
                 //添加剩下的普通BeanFactoryPostProcessor的beanName
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		
    	//调用所有实现PriorityOrdered接口的BeanFactoryPostProcessor
    	//对priorityOrderedPostProcessors排序
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    	//遍历priorityOrderedPostProcessors, 执行postProcessBeanFactory方法
		invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

		// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
    	//调用所有实现Ordered接口的BeanFactoryPostProcessor
		List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String postProcessorName : orderedPostProcessorNames) {
            //获取postProcessorName对应的bean实例, 添加到orderedPostProcessors, 准备执行
			orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
    	//对orderedPostProcessors排序
		sortPostProcessors(orderedPostProcessors, beanFactory);
    	//遍历orderedPostProcessors, 执行postProcessBeanFactory方法
		invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

		// Finally, invoke all other BeanFactoryPostProcessors.
    	//调用所有剩下的BeanFactoryPostProcessor
		List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String postProcessorName : nonOrderedPostProcessorNames) {
            //获取postProcessorName对应的bean实例, 添加到nonOrderedPostProcessors, 准备执行
			nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
    	//遍历nonOrderedPostProcessors, 执行postProcessBeanFactory方法
		invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

		// Clear cached merged bean definitions since the post-processors might have
		// modified the original metadata, e.g. replacing placeholders in values...
    	//清除元数据缓存(mergedBeanDefinitions、allBeanNamesByType、singletonBeanNamesByType
    	//因为后处理器可能已经修改了原始元数据,例如, 替换值中的占位符...
		beanFactory.clearMetadataCache();
	}

在这里插入图片描述

7、registerBeanPostProcessors(beanFactory)

protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
public static void registerBeanPostProcessors(
			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
		//根据类型获取 beanFactory 中所有的 BeanPostProcessor 名字
		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
     	//添加了一个后置处理器,通过添加的这个后置处理器检查 前获取的这些后置处理器 
    //BeanPostProcessorChecker 是一个内部类,继承了BeanPostProcessor接口
		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

		//创建两个集合用来存放不同的BeanPostProcessor,通过这两个集合对不同的BeanPostProcessor 进行排序
		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
				priorityOrderedPostProcessors.add(pp);
				if (pp instanceof MergedBeanDefinitionPostProcessor) {
					internalPostProcessors.add(pp);
				}
			}
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
				orderedPostProcessorNames.add(ppName);
			}
			else {
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// 首先对 priorityOrderedPostProcessors 中的进行排序
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    	//注册,也就是创建BeanPostProcessor设置到beanFactory中
		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

		// Next, register the BeanPostProcessors that implement Ordered.
		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String ppName : orderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			orderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
    	//对orderedPostProcessors集合中的后置处理器排序
		sortPostProcessors(orderedPostProcessors, beanFactory);
    	//注册
		registerBeanPostProcessors(beanFactory, orderedPostProcessors);

		// Now, register all regular BeanPostProcessors.
		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String ppName : nonOrderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			nonOrderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
    	//注册nonOrderedPostProcessors集合中的后置处理器
		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

		//对internalPostProcessors集合中的后置处理器进行排序
		sortPostProcessors(internalPostProcessors, beanFactory);
    	//注册internalPostProcessors集合中的后置处理器
		registerBeanPostProcessors(beanFactory, internalPostProcessors);

		//又添加了一个后置处理器ApplicationListenerDetector,
		//该后置处理器用来判断是否是某个监听器,如果是添加到容器中
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
	}

在这里插入图片描述

8、 initMessageSource();

在 SpringMVC 中用来做国际化功能,消息解析,消息绑定,MessageSource中有getMessage()方法,一般用来取出国际化配置文件中的key的值,能按照区域信息去获取

protected void initMessageSource() {
    //获取beanFactory
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    //判断beanFactory中是否有一个id为"messageSource" 的 bean
    if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
        //如果有通过id获取这个bean,赋值给 messageSource  属性
       this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
       // Make MessageSource aware of parent MessageSource.
       if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
          HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
          if (hms.getParentMessageSource() == null) {
             // Only set parent context as parent MessageSource if no parent MessageSource
             // registered already.
             hms.setParentMessageSource(getInternalParentMessageSource());
          }
       }
       if (logger.isTraceEnabled()) {
          logger.trace("Using MessageSource [" + this.messageSource + "]");
       }
    }
    else {
       //如果没有创建一个MessageSource组件
       DelegatingMessageSource dms = new DelegatingMessageSource();
       //将这个组件注册到容器中(以后获取国际化配置文件的相关信息,可以通过@Autowired在Spring
       //容器中直接获取装配到自己的类的属性上,然后调用MessageSource的方法)
       dms.setParentMessageSource(getInternalParentMessageSource());
       this.messageSource = dms;
       beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
       if (logger.isTraceEnabled()) {
          logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
       }
    }
}

9、initApplicationEventMulticaster()

在这里插入图片描述

protected void initApplicationEventMulticaster() {
    //获取beanFactory
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    //获取beanFactory中id为"applicationEventMulticaster"的事件派发器
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
       this.applicationEventMulticaster =
             beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
       if (logger.isTraceEnabled()) {
          logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
       }
    }
    else {
        //如果没有创建一个 SimpleApplicationEventMulticaster 事件派发器,
       this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        //注册到 Spring 容器中
       beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
       if (logger.isTraceEnabled()) {
          logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
                "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
       }
    }
}

10、registerListeners()

protected void registerListeners() {
		// Register statically specified listeners first.
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}

		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let post-processors apply to them!
    	//在容器中拿到所有的监听器的名字
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    	//遍历,将每个监听器添加到事件派发器中
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		// Publish early application events now that we finally have a multicaster...
    	//获取早期设置的事件(派发之前的事件)
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
    	//如果有
		if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                将早期的事件派发出去
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}

在这里插入图片描述

11、finishBeanFactoryInitialization(beanFactory)

涉及到bean的生命周期,看这篇文章(正在创作)

在这里插入图片描述

12、finishRefresh()

protected void finishRefresh() {
    // Clear context-level resource caches (such as ASM metadata from scanning).
    clearResourceCaches();

    // Initialize lifecycle processor for this context.
    initLifecycleProcessor();

    // Propagate refresh to lifecycle processor first.
    getLifecycleProcessor().onRefresh();

    // Publish the final event.
    publishEvent(new ContextRefreshedEvent(this));

    // Participate in LiveBeansView MBean, if active.
    if (!NativeDetector.inNativeImage()) {
       LiveBeansView.registerApplicationContext(this);
    }
}

在这里插入图片描述

emmmm 都是什么玩意,服了,不看了


总结

Spring中的Ioc 容器启动主要是在 AbstractApplicationContext # refresh() 方实现的。大致过程如下:

1、首先会执行一些容器刷新前的准备工作,如设置容器启动时间、一些状态标志位等

2、创建容器对象、其实就是实例化DefaultListableBeanFactory对象,这一步包含了bean定义信息的解析,解析后的属性都封装到DefaultListableBeanFactory的成员属性中了,如我们常见的beanDefinitionMapbeanDefinitionNames;

3、准备bean工厂,实际就是设置 beanFactory的一些属性

4、Spring提供了 postProcessorBeanFactory()方法给我们去扩展、例如我们可以注册一些特殊的BeanPostProcessor后置处理器等操作

5、执行BeanFactoryPostProcessor后置处理器的postProcessBeanFactory()增强方法,使三个不同的集合分别存放实现了PriorityOrdered接口、实现了Ordered接口、普通的BeanFactoryPostProcessor,经过排序后之执行BeanFactoryPostProcessor的回调postProcessorBeanFactory

6、第六步,注册BeanPostProcessor后置处理器,注意,这里还不会执行BeanPostProcessor对应的增强方法;同样的,使用三个不同的集合分别存放实现了PriorityOrdered接口、实现了Ordered接口、普通的BeanPostProcessor,依次调用beanFactory.addBeanPostProcessor(postProcessor)方法往bean工厂中添加BeanPostProcessor;

7、为上下文初始化MessageResource,即国际化处理

8、初始化事件多播器,即ApplicationEventMulticaster,为后面的事件发布-监听做准备

9、提供了一个模板方法onRefresh(),留给子类初始化其他的bean

10、注册Listener监听器

11、最关键的一步,这里会实例化所有剩下的非懒加载的单例Bean,Bean的生命周期也是从这里开始的。

12、完成上下文刷新工作,如清除一些缓存、发布容器刷新完成事件等。

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

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

相关文章

CCF ChinaSoft 2023 论坛巡礼 | 云计算标准化论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

可视化 | echarts中国地图散点图

改编自echarts添加地图散点 &#x1f4da;改编点 roam: false&#xff1a;不允许放缩拖动 地图颜色修改 geo: {show: true,top: 15%,map: name,label: {normal: {show: false},emphasis: {show: true,color: "#fff",}},roam: false,itemStyle: {normal: {areaColo…

SAP ABAP基础语法-Excel上传(十)

EXCEL BDS模板上传及赋值 上传模板事务代码&#xff1a;OAER l 功能代码&#xff1a;向EXCEL模板中写入数据示例代码如下 REPORT ZEXCEL_DOI. “doi type pools TYPE-POOLS: soi. *SAP Desktop Office Integration Interfaces DATA: container TYPE REF TO cl_gui_custom_c…

Elasticsearch 集群状态详解

cluster state 返回结果详解 GET /_cluster/statehttps://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-state.html详细信息如下&#xff1a; {"cluster_name": "business-log","cluster_uuid": "ArYy-qmCTbCQTDUI8o…

ClickHouse Keeper: Coordination without the drawbacks没有缺点的分布式协作系统

ClickHouse Keeper 介绍 现代分布式系统需要一个共享和可靠的信息存储库和共识系统来协调和同步分布式操作。对于ClickHouse来说&#xff0c;ZooKeeper最初是被选中的。它的广泛使用是可靠的&#xff0c;提供了简单而强大的API&#xff0c;并提供了合理的性能。 然而&#xf…

[工业自动化-8]:西门子S7-15xxx编程 - PLC主站 - CPU模块

目录 前言&#xff1a; 一、概述 二、CPU操作和显示 三、安装 四、CPU的选择 前言&#xff1a; 一、概述 西门子S7-1500系列是一系列高性能工业自动化控制器&#xff0c;广泛应用于制造业、自动化生产、物流等领域。这个系列的控制器是设计用来满足高性能、高效能要求的复…

板刷codeforces 1000分

练习 1.Problem - 1A - Codeforces AC代码: #include <bits/stdc.h> #define endl \n #define int long long using namespace std; int n,m,a; void solve() {cin>>n>>m>>a;cout<<(n/a(n%a!0))*(m/a(m%a!0))<<endl; } signed main() {…

《童年》 思维导图

《童年》是高尔基自传体小说三部曲中的第一部&#xff0c;讲述的是高尔基幼年丧父、母亲改嫁&#xff0c;他跟随日渐破落的小染坊主外公以及外婆生活的童年经历。小说通过一个儿童天真无邪的眼光&#xff0c;向读者生动地展示了19世纪中叶俄罗斯社会底层人民的生活状态&#xf…

Apache APISIX 的 Admin API 默认访问令牌漏洞(CVE-2020-13945)漏洞复现

漏洞描述 Apache APISIX 是一个动态、实时、高性能的 API 网关。Apache APISIX 有一个默认的内置 API 令牌&#xff0c;可用于访问所有 admin API&#xff0c;通过 2.x 版本中添加的参数导致远程执行 LUA 代码。 漏洞环境及利用 启动docker环境 访问9080端口 通过 admin api…

Centos7安装配置中文输入法

Centos7安装配置中文输入法 在安装CentOS时&#xff0c;我们为了方便使用&#xff0c;语言选择了中文&#xff0c;但是我们发现&#xff0c;在Linux命令行或者是浏览器中输入时&#xff0c;我们只能输入英文&#xff0c;无法输入汉字。 来&#xff0c;跟随脚步&#xff0c;设…

【java】【MyBatisPlus】【四】【完】MyBatisPlus一些实战总结(枚举、翻页、sql、组合条件、自增主键、逻辑删除)

目录 一、枚举 1、数据库type字段是Integer 类型枚举 2、创建一个该字段的枚举类 TypeEnum 3、修改实体类 4、配置文件新增mybatis-plus的配置 5、检验&#xff1a; 5.1 查询显示 5.3 库里验证 二、自增主键不是id字段处理 三、逻辑删除字段不是delete字段处理 1、实…

【Linux网络】2分钟学习centos7永久修改网卡名称

目录 第一步&#xff0c;先查看网卡名称 第二步&#xff1a;先修改配置文件/etc/default/grub&#xff0c;添加net.ifnemes0 第三步&#xff1a;重新加载内核配置grub2-mkconfig -o /boot/grub2/grub.cfg 第四步&#xff1a;重启电脑 第五步&#xff1a;查看网卡名称&…

No module named ‘importlib.metadata‘

解决办法 参考博客 https://wenku.csdn.net/answer/45a1563cc02e9592dd1d1d28fe7b88e7 pip install importlib_metadata

基于工业智能网关的汽车充电桩安全监测方案

近年来&#xff0c;我国新能源汽车产业得到快速发展&#xff0c;电动车产量和销量都在持续增长&#xff0c;不仅国内市场竞争激烈&#xff0c;而且也远销海外&#xff0c;成为新的经济增长点。但与此同时&#xff0c;充电设施的运营却面临着安全和效率的双重挑战。 当前的充电桩…

python特殊循环队列_队中元素个数代替队尾指针

对于循环队列来说&#xff0c;如果知道队头指针和队中元素个数&#xff0c;则可以计算出队尾指针。也就是说&#xff0c;可以用队中元素个数代替队尾指针。设计出这种循环队列的判队空、进队、出队和取队头元素的算法。 本例的循环队列包含data 数组、队头指针 front和队中元素…

猫头虎博主第7期赠书活动:《计算机考研精炼1000题》

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

mac M2 pytorch_geometric安装

我目前的环境是mac M2&#xff0c;我在base环境中安装了pytorch_geometric,仅仅做测试用的&#xff0c;不做真正跑代码的测试 首先我的base环境的设置如下&#xff1a; pip install pyg_lib torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.…

Android Camera App启动流程解析

前言&#xff1a;做了7年的camera app开发&#xff0c;给自己一个总结&#xff0c;算是对camera的一次告白吧。Camera被大家誉为手机的眼睛&#xff0c;是现在各大手机厂商的卖点&#xff0c;也是各大厂商重点发力的地方。Camera的重要性我就不在这里赘述了&#xff0c;让我们进…

计网自顶向下(Web服务器+UDPping+邮件客户端)

目录 &#x1f416;前言 &#x1f33c;Web服务器(作业1) &#x1f333;过程 &#x1f333;解释 &#x1f525;代码 &#x1f33c;UDPping程序(作业2) &#x1f333;过程 &#x1f333;解释 Client Server 整体逻辑 &#x1f525;代码 &#x1f33c;邮件客户端(作业…

粤嵌实训医疗项目(小组开发)--day05

目录 一、医生功能模块 ------------前端实现------------ ------------后端接口------------ 功能一&#xff1a;分页查询医生基础信息&#xff08;介绍MybatisPlus如何使用分页&#xff09; 功能二&#xff1a;根据搜索栏名称查找对应医生&#xff08;讲解自定义查询集&…