【源码阅读】SpringBoot-v2.2.0启动过程以及细节
启动流程
图如下:
以上流程图源文件(可导入https://www.processon.com):https://github.com/Mysakura/DataFiles
相关Event(org.springframework.boot.context.event.SpringApplicationEvent的子类),这些Event是很好的标志,告诉我们程序执行到哪一步了,如下
ApplicationStartingEvent:在Environment和ApplicationContext可用之前 & 在ApplicationListener注册之后发布。
ApplicationContextInitializedEvent:在bean定义加载之前 & ApplicationContextInitializers被调用之后 & ApplicationContext开始准备之后发布
ApplicationPreparedEvent:在ApplicationContext完全准备好并且没有刷新之前发布,此时bean定义即将加载,Environment已经准备好被使用。
ApplicationStartedEvent:在ApplicationContext刷新之后,调用ApplicationRunner和CommandLineRunner之前发布
ApplicationReadyEvent:应用已经准备好接受请求时发布。
第一步,先进入启动类
- @SpringBootApplication
- public class DemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(DemoApplication.class, args);
- }
- }
第二步,执行run方法,实际上执行的是【org.springframework.boot.SpringApplication#run(java.lang.Class<?>[], java.lang.String[])】
- public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
- return run(new Class<?>[] { primarySource }, args);
- }
- public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
- // 初始化SpringApplication,然后执行run方法
- return new SpringApplication(primarySources).run(args);
- }
1. new SpringApplication【org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...)】
- public SpringApplication(Class<?>... primarySources) {
- this(null, primarySources);
- }
- // 初始化一些成员变量:
- public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
- this.resourceLoader = resourceLoader;
- Assert.notNull(primarySources, "PrimarySources must not be null");
- // 初始化primarySources
- this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
- // 判断当前应用是一个基于Servlet或者reactive的web应用还是一个非web应用
- this.webApplicationType = WebApplicationType.deduceFromClasspath();
- // 初始化一系列ApplicationContextInitializer
- setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
- // 初始化一系列ApplicationListener,这些是针对SpringBoot事件的监听器
- setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
- // 配置主要应用启动类
- this.mainApplicationClass = deduceMainApplicationClass();
- }
2. run【org.springframework.boot.SpringApplication#run(java.lang.String...)】
- public ConfigurableApplicationContext run(String... args) {
- // 一个计算耗时小工具:A
- StopWatch stopWatch = new StopWatch();
- stopWatch.start();
- ConfigurableApplicationContext context = null;
- Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
- configureHeadlessProperty();
- // 获取监听器,SpringApplicationRunListener的作用是发布SpringBoot运行期间的事件:B
- SpringApplicationRunListeners listeners = getRunListeners(args);
- // 监听器发布事件:C
- listeners.starting();
- try {
- // 创建默认应用参数
- ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
- // 准备环境:D
- ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
- // 处理要忽略的bean信息
- configureIgnoreBeanInfo(environment);
- // 打印Banner
- Banner printedBanner = printBanner(environment);
- // 获取应用上下文:E
- context = createApplicationContext();
- // 获取异常报告实例列表
- exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
- new Class[] { ConfigurableApplicationContext.class }, context);
- // 准备上下文:F
- prepareContext(context, environment, listeners, applicationArguments, printedBanner);
- // 刷新上下文:G
- refreshContext(context);
- // 留给子类实现,在刷新之后做一下额外的处理
- afterRefresh(context, applicationArguments);
- // 计时器计时完毕
- stopWatch.stop();
- if (this.logStartupInfo) {
- new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
- }
- // 发布ApplicationStartedEvent事件:H
- listeners.started(context);
- // 调用Runners:I
- callRunners(context, applicationArguments);
- }
- catch (Throwable ex) {
- handleRunFailure(context, ex, exceptionReporters, listeners);
- throw new IllegalStateException(ex);
- }
- try {
- // 发布ApplicationReadyEvent事件:J
- listeners.running(context);
- }
- catch (Throwable ex) {
- handleRunFailure(context, ex, exceptionReporters, null);
- throw new IllegalStateException(ex);
- }
- return context;
- }
A:计算耗时小工具(运行如下测试代码)
- StopWatch stopWatch = new StopWatch("Test ABC");
- stopWatch.start("Task A");
- for (int i = 0; i < 10; i++){ }
- stopWatch.stop();
- stopWatch.start("Task B");
- for (int i = 0; i < 5; i++){ }
- stopWatch.stop();
- stopWatch.start("Task C");
- for (int i = 0; i < 15; i++){ }
- stopWatch.stop();
- System.out.println(stopWatch.prettyPrint());
输出:
B: 获取监听器*
- private SpringApplicationRunListeners getRunListeners(String[] args) {
- Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
- // 返回一个SpringApplicationRunListener的集合包装类
- return new SpringApplicationRunListeners(logger,
- getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
- }
- private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
- ClassLoader classLoader = getClassLoader();
- // 使用Set集合确保name是唯一的;调用SpringFactoriesLoader.loadFactoryNames,使用给定的类加载器加载给定类型工厂实现的完全限定类名
- Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
- // 根据给的Class、类加载器、完全限定类型列表已经相关参数,根据反射创建实例列表
- List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
- AnnotationAwareOrderComparator.sort(instances);
- return instances;
- }
- @SuppressWarnings("unchecked")
- private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
- ClassLoader classLoader, Object[] args, Set<String> names) {
- List<T> instances = new ArrayList<>(names.size());
- for (String name : names) {
- try {
- Class<?> instanceClass = ClassUtils.forName(name, classLoader);
- Assert.isAssignable(type, instanceClass);
- Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
- T instance = (T) BeanUtils.instantiateClass(constructor, args);
- instances.add(instance);
- }
- catch (Throwable ex) {
- throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
- }
- }
- return instances;
- }
C:发布【ApplicationStartingEvent】事件
- public void starting() {
- this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
- }
D:准备环境*
- private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
- ApplicationArguments applicationArguments) {
- // 根据webApplicationType,决定创建StandardServletEnvironment还是StandardReactiveWebEnvironment还是StandardEnvironment
- ConfigurableEnvironment environment = getOrCreateEnvironment();
- // 配置属性文件和激活的环境(spring.profiles.active所配)
- configureEnvironment(environment, applicationArguments.getSourceArgs());
- // 将ConfigurationPropertySource附加到指定环境下
- ConfigurationPropertySources.attach(environment);
- // 发布ApplicationEnvironmentPreparedEvent事件
- listeners.environmentPrepared(environment);
- // 绑定环境到应用
- bindToSpringApplication(environment);
- if (!this.isCustomEnvironment) {
- environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
- deduceEnvironmentClass());
- }
- ConfigurationPropertySources.attach(environment);
- return environment;
- }
E:获取应用上下文
- // 根据应用类型创建不同的上下文
- protected ConfigurableApplicationContext createApplicationContext() {
- Class<?> contextClass = this.applicationContextClass;
- if (contextClass == null) {
- try {
- switch (this.webApplicationType) {
- case SERVLET:
- contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
- break;
- case REACTIVE:
- contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
- break;
- default:
- contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
- }
- }
- catch (ClassNotFoundException ex) {
- throw new IllegalStateException(
- "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
- }
- }
- return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
- }
F:准备上下文*
- private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
- SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
- // 设置environment
- context.setEnvironment(environment);
- // 相关额外的处理
- postProcessApplicationContext(context);
- applyInitializers(context);
- // 派发ApplicationContextInitializedEvent事件,表示应用正在启动,上下文正在准备并且一系列Initializer已经调用
- listeners.contextPrepared(context);
- if (this.logStartupInfo) {
- logStartupInfo(context.getParent() == null);
- logStartupProfileInfo(context);
- }
- // Add boot specific singleton beans
- ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
- beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
- if (printedBanner != null) {
- beanFactory.registerSingleton("springBootBanner", printedBanner);
- }
- if (beanFactory instanceof DefaultListableBeanFactory) {
- ((DefaultListableBeanFactory) beanFactory)
- .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
- }
- if (this.lazyInitialization) {
- context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
- }
- // Load the sources
- Set<Object> sources = getAllSources();
- Assert.notEmpty(sources, "Sources must not be empty");
- load(context, sources.toArray(new Object[0]));
- // 监听上下文,派发ApplicationPreparedEvent事件,表示应用正在启动,上下文已经准备好刷新了
- listeners.contextLoaded(context);
- }
- protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
- if (this.beanNameGenerator != null) {
- // 注册注解工具类
- context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
- this.beanNameGenerator);
- }
- if (this.resourceLoader != null) {
- if (context instanceof GenericApplicationContext) {
- // 设置resourceLoader
- ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
- }
- if (context instanceof DefaultResourceLoader) {
- ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
- }
- }
- if (this.addConversionService) {
- // 设置ConversionService
- context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
- }
- }
G:刷新上下文(容器的初始化)【org.springframework.context.support.AbstractApplicationContext#refresh】*
- private void refreshContext(ConfigurableApplicationContext context) {
- refresh(context);
- if (this.registerShutdownHook) {
- try {
- context.registerShutdownHook();
- }
- catch (AccessControlException ex) {
- // Not allowed in some environments.
- }
- }
- }
- protected void refresh(ApplicationContext applicationContext) {
- Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
- ((AbstractApplicationContext) applicationContext).refresh();
- }
- public void refresh() throws BeansException, IllegalStateException {
- synchronized (this.startupShutdownMonitor) {
- // Prepare this context for refreshing.
- // 为上下文刷新做准备
- prepareRefresh();
- // Tell the subclass to refresh the internal bean factory.
- // 告诉子类刷新内部bean工厂
- ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
- // Prepare the bean factory for use in this context.
- // 准备在这个上下文要用的bean工厂
- prepareBeanFactory(beanFactory);
- try {
- // Allows post-processing of the bean factory in context subclasses.
- // 允许在上下文子类中对bean工厂进行后续处理
- postProcessBeanFactory(beanFactory);
- // Invoke factory processors registered as beans in the context.
- // 执行上下文中已注册的bean工厂后置处理器
- invokeBeanFactoryPostProcessors(beanFactory);
- // Register bean processors that intercept bean creation.
- // 注册拦截bean创建过程的后置处理器
- registerBeanPostProcessors(beanFactory);
- // Initialize message source for this context.
- // 初始化此上下文的消息源
- initMessageSource();
- // Initialize event multicaster for this context.
- // 初始化事件派发器
- initApplicationEventMulticaster();
- // Initialize other special beans in specific context subclasses.
- // 初始化子类实现的其它特殊bean
- onRefresh();
- // Check for listener beans and register them.
- // 检查并注册侦听器bean
- registerListeners();
- // Instantiate all remaining (non-lazy-init) singletons.
- // 实例化所有剩余的(非延迟初始化)单实例bean
- finishBeanFactoryInitialization(beanFactory);
- // Last step: publish corresponding event.
- // 最后,发布对应的事件
- finishRefresh();
- }
- catch (BeansException ex) {
- if (logger.isWarnEnabled()) {
- logger.warn("Exception encountered during context initialization - " +
- "cancelling refresh attempt: " + ex);
- }
- // Destroy already created singletons to avoid dangling resources.
- destroyBeans();
- // Reset 'active' flag.
- cancelRefresh(ex);
- // Propagate exception to caller.
- throw ex;
- }
- finally {
- // Reset common introspection caches in Spring's core, since we
- // might not ever need metadata for singleton beans anymore...
- resetCommonCaches();
- }
- }
- }
H:发布ApplicationStartedEvent事件(listeners.started(context);)【org.springframework.boot.context.event.EventPublishingRunListener#started】
- public void started(ConfigurableApplicationContext context) {
- context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
- }
I:调用Runners
- private void callRunners(ApplicationContext context, ApplicationArguments args) {
- List<Object> runners = new ArrayList<>();
- // 获取所有实现ApplicationRunner接口的子类
- runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
- // 获取所有实现CommandLineRunner接口的子类
- runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
- AnnotationAwareOrderComparator.sort(runners);
- // 循环遍历
- for (Object runner : new LinkedHashSet<>(runners)) {
- // 如果是ApplicationRunner的实现类
- if (runner instanceof ApplicationRunner) {
- callRunner((ApplicationRunner) runner, args);
- }
- // 如果是CommandLineRunner的实现类
- if (runner instanceof CommandLineRunner) {
- callRunner((CommandLineRunner) runner, args);
- }
- }
- }
- private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
- try {
- (runner).run(args);
- }
- catch (Exception ex) {
- throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
- }
- }
- private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
- try {
- (runner).run(args.getSourceArgs());
- }
- catch (Exception ex) {
- throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
- }
- }
那么这两个类有啥作用呢
首先,SpringBoot并没有默认的实现类,那么肯定是给我们实现的;然后这个方法的执行时机是在spring容器启动完毕的时候,那么作用肯定是在容器启动之后做一些其它自定义的处理。
这两个接口都只有一个方法,就是run方法。类注解上写的说明:
当SpringApplication中包含了该接口的实现类,在callRunners的时候调用其中的run方法;可以在同一个应用上下文定义多个bean,也可以通过实现Order接口或者使用@Order注解来排序。
这两个类的唯一区别就是run方法的参数类型不同:
org.springframework.boot.CommandLineRunner#run(String... args)
org.springframework.boot.ApplicationRunner#run(ApplicationArguments args)
J:发布ApplicationReadyEvent事件(listeners.running(context);)【org.springframework.boot.context.event.EventPublishingRunListener#running】
- public void running(ConfigurableApplicationContext context) {
- context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
- }
到此一个大概的流程已经走完,而我还想看一下具体的东西。
--------------------------------------------------------------------------------------------------------------------------------------------------------
进一步深入
问题0:我看有好几个地方都用到了同一个方法:org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>)
- private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
- ClassLoader classLoader = getClassLoader();
- // 加载工程名称列表,通过Set集合保证唯一①
- Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
- // 根据名称,创建相关实例②
- List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
- // 排序后返回
- AnnotationAwareOrderComparator.sort(instances);
- return instances;
- }
来看①
- public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
- String factoryTypeName = factoryType.getName();
- return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
- }
- private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
- // 查看缓存有么有
- MultiValueMap<String, String> result = cache.get(classLoader);
- if (result != null) {
- return result;
- }
- try {
- // 获取spring.factories资源路径
- Enumeration<URL> urls = (classLoader != null ?
- classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
- ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
- result = new LinkedMultiValueMap<>();
- while (urls.hasMoreElements()) {
- URL url = urls.nextElement();
- UrlResource resource = new UrlResource(url);
- // 加载资源
- Properties properties = PropertiesLoaderUtils.loadProperties(resource);
- for (Map.Entry<?, ?> entry : properties.entrySet()) {
- String factoryTypeName = ((String) entry.getKey()).trim();
- for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
- // key为工厂类型接口名称,值为一系列子类实现
- result.add(factoryTypeName, factoryImplementationName.trim());
- }
- }
- }
- // 放入缓存并返回
- cache.put(classLoader, result);
- return result;
- }
- catch (IOException ex) {
- throw new IllegalArgumentException("Unable to load factories from location [" +
- FACTORIES_RESOURCE_LOCATION + "]", ex);
- }
- }
具体来看SpringBoot的spring.factories里面的内容:是一系列的组件以及具体的实现列表
通过把这些配置事先读取并放进缓存,我们就可以随时根据工厂接口类型获取对应的子类名称列表。
再来看②
- private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
- ClassLoader classLoader, Object[] args, Set<String> names) {
- List<T> instances = new ArrayList<>(names.size());
- // 遍历子类名称,通过反射进行实例化
- for (String name : names) {
- try {
- Class<?> instanceClass = ClassUtils.forName(name, classLoader);
- Assert.isAssignable(type, instanceClass);
- Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
- T instance = (T) BeanUtils.instantiateClass(constructor, args);
- instances.add(instance);
- }
- catch (Throwable ex) {
- throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
- }
- }
- return instances;
- }
一句话作用:这个方法的作用就是找到spring.factories读取一系列组件的名称,通过传入的Class类型获取对应的子类名称列表进行实例化并返回。
问题1:那一系列Initializer都有哪些,有什么作用【setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));】
0:报告常见的错误配置警告
1:配置ApplicationContext ID
2:执行环境属性【context.initializer.classes】指定的初始化器,也就是给用户一个机会去指定自己实现的ApplicationContextInitializer从而对应用程序做一些初始化工作
3:将RSocketServer实际监听的端口写入Environment环境属性中。这样属性local.rsocket.server.port就可以直接通过@Value注入到测试中,或者通过Environment获取。
4:将WebServer实际监听的端口写入Environment环境属性中。这样属性local.server.port就可以直接通过@Value注入到测试中,或者通过Environment获取。
5:创建一个ConfigurationClassPostProcessor 和 Spring Boot共用的CachingMetadataReaderFactory对象
6:将ConditionEvaluationReport写入日志。
问题2:那一系列Listeners有哪些,有什么作用【setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));】
0:一旦装载了上下文,就清空缓存。
1:在其父应用程序上下文关闭时关闭该应用程序上下文
2:如果系统文件编码与environment里设置的不一致,则停止启动应用程序。默认情况下么有影响,别手贱乱设置。
3:根据属性{spring.output.ansi.enabled}的值配置{AnsiOutput}
4:通过读取已知位置属性文件里的属性来配置上下文的environment,默认加载application.properties或者application.yml里的配置。
5:执行配置在context.listener.classes环境变量里的Listener
6:通过在{DEBUG}级别记录线程上下文类加载器(TCCL)的类路径来对ApplicationEnvironmentPreparedEvent和ApplicationFailedEvent事件做出反应
7:一个配置{LoggingSystem}的ApplicationListener。如果环境包含{logging.config}属性,它将用于引导日志系统,否则将使用默认配置。无论如何,如果环境包含{logging.level},那么日志级别将被定制。
8:将liquibase {ServiceLocator}替换成和SpringBoot可执行归档一起工作的版本。
9:在耗时任务的后台线程中触发早期初始化
问题3:SpringApplicationRunListeners listeners = getRunListeners(args);怎么又出来一个listeners?
- private SpringApplicationRunListeners getRunListeners(String[] args) {
- Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
- // 可以看出,通过getSpringFactoriesInstances读取对应的names并实例化返回,然后封装在SpringApplicationRunListeners
- return new SpringApplicationRunListeners(logger,
- getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
- }
在这里实例化的是:
问题4:listeners.starting();是启动监听器吗?
- void starting() {
- // 遍历内部的listener,执行starting方法
- for (SpringApplicationRunListener listener : this.listeners) {
- listener.starting();
- }
- }
- // starting方法,实际上是广播ApplicationStartingEvent事件
- public void starting() {
- this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
- }
广播事件,有哪些收听者呢?
- public void multicastEvent(ApplicationEvent event) {
- multicastEvent(event, resolveDefaultEventType(event));
- }
- @Override
- public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
- ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
- Executor executor = getTaskExecutor();
- // 典型的观察者模式
- for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
- if (executor != null) {
- executor.execute(() -> invokeListener(listener, event));
- }
- else {
- invokeListener(listener, event);
- }
- }
- }
收听者就是我们之前第一步实例化的listener
问题5:Bean在哪里实例化的
先创建bean定义
- 进入org.springframework.boot.SpringApplication#run(java.lang.String...)
- 进入refreshContext(context);方法
- 最终进入org.springframework.context.support.AbstractApplicationContext#refresh
- 进入invokeBeanFactoryPostProcessors(beanFactory);
- 进入PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
- 进入invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
- 里面是一个循环,进入postProcessor.postProcessBeanDefinitionRegistry(registry);
- 进入processConfigBeanDefinitions(registry);
- 进入parser.parse(candidates);
- 里面是一个遍历BeanDefinitionHolder的过程,获取BeanDefinition。根据BeanDefinition实现的接口执行不同的方法,但是最终都进入org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
- 进入sourceClass = doProcessConfigurationClass(configClass, sourceClass);
到此慢慢接近了真相
- protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
- throws IOException {
- if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
- // Recursively process any member (nested) classes first
- processMemberClasses(configClass, sourceClass);
- }
- // 处理@PropertySource标记的类
- for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
- sourceClass.getMetadata(), PropertySources.class,
- org.springframework.context.annotation.PropertySource.class)) {
- if (this.environment instanceof ConfigurableEnvironment) {
- processPropertySource(propertySource);
- }
- else {
- logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
- "]. Reason: Environment must implement ConfigurableEnvironment");
- }
- }
- // 处理@ComponentScan标记的类
- Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
- sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
- if (!componentScans.isEmpty() &&
- !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
- for (AnnotationAttributes componentScan : componentScans) {
- // 立即执行扫描 A
- Set<BeanDefinitionHolder> scannedBeanDefinitions =
- this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
- // 检查已扫描的定义集合以获得更多的配置类,并在需要时进行递归解析
- for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
- BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
- if (bdCand == null) {
- bdCand = holder.getBeanDefinition();
- }
- if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
- parse(bdCand.getBeanClassName(), holder.getBeanName());
- }
- }
- }
- }
- // 处理@Import标记的类
- processImports(configClass, sourceClass, getImports(sourceClass), true);
- // 处理@ImportResource标记的类
- AnnotationAttributes importResource =
- AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
- if (importResource != null) {
- String[] resources = importResource.getStringArray("locations");
- Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
- for (String resource : resources) {
- String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
- configClass.addImportedResource(resolvedResource, readerClass);
- }
- }
- // 处理单个@Bean标记的方法
- Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
- for (MethodMetadata methodMetadata : beanMethods) {
- configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
- }
- // 处理接口里的默认方法
- processInterfaces(configClass, sourceClass);
- // 处理父类, 如果有的话
- if (sourceClass.getMetadata().hasSuperClass()) {
- String superclass = sourceClass.getMetadata().getSuperClassName();
- if (superclass != null && !superclass.startsWith("java") &&
- !this.knownSuperclasses.containsKey(superclass)) {
- this.knownSuperclasses.put(superclass, configClass);
- // Superclass found, return its annotation metadata and recurse
- return sourceClass.getSuperClass();
- }
- }
- // 没有父类,处理完成
- return null;
- }
我们来看一下扫描过程,进入注释A的那段代码:this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
这个方法主要读取相关的属性,最后的方法是处理过程。其中一段代码,判断basePackages是否为空,如果为空,则默认扫描启动类所在的包
现在进入最后的doScan方法。
- protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
- Assert.notEmpty(basePackages, "At least one base package must be specified");
- Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
- for (String basePackage : basePackages) {
- // 具体扫描包,组装成BeanDefinition返回
- Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
- for (BeanDefinition candidate : candidates) {
- ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
- candidate.setScope(scopeMetadata.getScopeName());
- String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
- if (candidate instanceof AbstractBeanDefinition) {
- postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
- }
- if (candidate instanceof AnnotatedBeanDefinition) {
- AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
- }
- if (checkCandidate(beanName, candidate)) {
- BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
- definitionHolder =
- AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
- beanDefinitions.add(definitionHolder);
- // 注册bean定义,也就是注册到beanFactory里(this.registry的类型是org.springframework.beans.factory.support.DefaultListableBeanFactory)
- registerBeanDefinition(definitionHolder, this.registry);
- }
- }
- }
- return beanDefinitions;
- }
进入注册bean的方法(最终进入org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition)
- public static void registerBeanDefinition(
- BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
- throws BeanDefinitionStoreException {
- // 在主名称下注册bean定义
- String beanName = definitionHolder.getBeanName();
- // 具体注册方法
- registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
- // 如果有的话,为bean名称注册别名
- String[] aliases = definitionHolder.getAliases();
- if (aliases != null) {
- for (String alias : aliases) {
- registry.registerAlias(beanName, alias);
- }
- }
- }
进入具体注册方法(org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition)
实际上是把bean定义放进了一个Map里面。到此为止,bean定义已经注册到beanFactory里了。但是这个时候还没有实例化,仅仅是拿到了bean定义。
实例化
刚才我们走的是invokeBeanFactoryPostProcessors(beanFactory);,现在走 finishBeanFactoryInitialization(beanFactory);
- protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
- // 为上下文初始化conversion service
- if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
- beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
- beanFactory.setConversionService(
- beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
- }
- // 如果之前没有任何bean后置处理器(例如PropertyPlaceholderConfigurer bean)注册,则注册一个默认的嵌入式值解析器
- // 此时,主要用于解析注解属性值。
- if (!beanFactory.hasEmbeddedValueResolver()) {
- beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
- }
- // 尽早初始化LoadTimeWeaverAware bean,以便尽早注册它们的转换器
- String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
- for (String weaverAwareName : weaverAwareNames) {
- getBean(weaverAwareName);
- }
- // 停止使用临时类加载器进行类型匹配。
- beanFactory.setTempClassLoader(null);
- // 允许缓存所有的bean定义元数据,不希望有进一步的更改。
- beanFactory.freezeConfiguration();
- // 实例化所有剩余的(非懒加载)单例。
- beanFactory.preInstantiateSingletons();
- }
进入最后一行
- public void preInstantiateSingletons() throws BeansException {
- if (logger.isTraceEnabled()) {
- logger.trace("Pre-instantiating singletons in " + this);
- }
- // 遍历一个副本以允许init方法,而init方法反过来注册新的bean定义。
- // 虽然这可能不是常规的工厂引导的一部分,但它在其他方面也可以正常工作。
- List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
- // 触发所有非懒加载bean的初始化
- for (String beanName : beanNames) {
- RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
- if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
- 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) {
- getBean(beanName);
- }
- }
- }
- else {
- // 实例化bean,实际进入的是org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法,里面包含了具体实例化的过程
- getBean(beanName);
- }
- }
- }
- // 为所有适用的bean触发初始化后回调
- 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();
- }
- }
- }
- }
最后看一下具体实例化的过程【org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean】
- // Create bean instance.
- if (mbd.isSingleton()) {
- sharedInstance = getSingleton(beanName, () -> {
- try {
- return createBean(beanName, mbd, args);
- }
- catch (BeansException ex) {
- // Explicitly remove instance from singleton cache: It might have been put there
- // eagerly by the creation process, to allow for circular reference resolution.
- // Also remove any beans that received a temporary reference to the bean.
- destroySingleton(beanName);
- throw ex;
- }
- });
- bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
- }
进入createBean【org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])】
- try {
- Object beanInstance = doCreateBean(beanName, mbdToUse, args);
- if (logger.isTraceEnabled()) {
- logger.trace("Finished creating instance of bean '" + beanName + "'");
- }
- return beanInstance;
- }
- catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
- // A previously detected exception with proper bean creation context already,
- // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
- throw ex;
- }
- catch (Throwable ex) {
- throw new BeanCreationException(
- mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
- }
进入doCreateBean【org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean】
- // spring把实例化的类都放在了包装类里
- BeanWrapper instanceWrapper = null;
- if (mbd.isSingleton()) {
- instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
- }
- if (instanceWrapper == null) {
- instanceWrapper = createBeanInstance(beanName, mbd, args);
- }
- final Object bean = instanceWrapper.getWrappedInstance();
- Class<?> beanType = instanceWrapper.getWrappedClass();
- if (beanType != NullBean.class) {
- mbd.resolvedTargetType = beanType;
- }
进入createBeanInstance【org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance】
- protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
- // 确保此时bean类已经被解析。
- Class<?> beanClass = resolveBeanClass(mbd, beanName);
- if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
- throw new BeanCreationException(mbd.getResourceDescription(), beanName,
- "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
- }
- Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
- if (instanceSupplier != null) {
- return obtainFromSupplier(instanceSupplier, beanName);
- }
- if (mbd.getFactoryMethodName() != null) {
- return instantiateUsingFactoryMethod(beanName, mbd, args);
- }
- // Shortcut when re-creating the same bean...
- boolean resolved = false;
- boolean autowireNecessary = false;
- if (args == null) {
- synchronized (mbd.constructorArgumentLock) {
- if (mbd.resolvedConstructorOrFactoryMethod != null) {
- resolved = true;
- autowireNecessary = mbd.constructorArgumentsResolved;
- }
- }
- }
- if (resolved) {
- if (autowireNecessary) {
- return autowireConstructor(beanName, mbd, null, null);
- }
- else {
- return instantiateBean(beanName, mbd);
- }
- }
- // 为自动装配准备的候选构造函数
- Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
- if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
- mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
- return autowireConstructor(beanName, mbd, ctors, args);
- }
- // 为默认构造函数准备的首选构造函数
- ctors = mbd.getPreferredConstructors();
- if (ctors != null) {
- return autowireConstructor(beanName, mbd, ctors, null);
- }
- // 不需要特殊处理:只需使用无参构造函数。
- return instantiateBean(beanName, mbd);
- }
因为本例只有一个Controller,所以运行到了最后一行:
进入instantiateBean【org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#instantiateBean】
- protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
- try {
- Object beanInstance;
- final BeanFactory parent = this;
- if (System.getSecurityManager() != null) {
- beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
- getInstantiationStrategy().instantiate(mbd, beanName, parent),
- getAccessControlContext());
- }
- else {
- // 获取instantiationStrategy策略,并调用instanttiate方法进行实例化
- beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
- }
- // 包装
- BeanWrapper bw = new BeanWrapperImpl(beanInstance);
- initBeanWrapper(bw);
- return bw;
- }
- catch (Throwable ex) {
- throw new BeanCreationException(
- mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
- }
- }
进入instantiate【org.springframework.beans.factory.support.SimpleInstantiationStrategy#instantiate(org.springframework.beans.factory.support.RootBeanDefinition, java.lang.String, org.springframework.beans.factory.BeanFactory)】
- public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
- // Don't override the class with CGLIB if no overrides.
- if (!bd.hasMethodOverrides()) {
- Constructor<?> constructorToUse;
- synchronized (bd.constructorArgumentLock) {
- constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
- if (constructorToUse == null) {
- // 获取Class
- final Class<?> clazz = bd.getBeanClass();
- if (clazz.isInterface()) {
- throw new BeanInstantiationException(clazz, "Specified class is an interface");
- }
- try {
- if (System.getSecurityManager() != null) {
- constructorToUse = AccessController.doPrivileged(
- (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
- }
- else {
- // 获取已声明的构造方法
- constructorToUse = clazz.getDeclaredConstructor();
- }
- bd.resolvedConstructorOrFactoryMethod = constructorToUse;
- }
- catch (Throwable ex) {
- throw new BeanInstantiationException(clazz, "No default constructor found", ex);
- }
- }
- }
- // 通过工具类实例化
- return BeanUtils.instantiateClass(constructorToUse);
- }
- else {
- // Must generate CGLIB subclass.
- return instantiateWithMethodInjection(bd, beanName, owner);
- }
- }
最后一步,看下如何实例化的【org.springframework.beans.BeanUtils#instantiateClass(java.lang.reflect.Constructor<T>, java.lang.Object...)】
- public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
- Assert.notNull(ctor, "Constructor must not be null");
- try {
- // 反射工具类:使构造方法可访问
- ReflectionUtils.makeAccessible(ctor);
- if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
- return KotlinDelegate.instantiateClass(ctor, args);
- }
- else {
- // 获取参数类型列表
- Class<?>[] parameterTypes = ctor.getParameterTypes();
- Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
- Object[] argsWithDefaultValues = new Object[args.length];
- for (int i = 0 ; i < args.length; i++) {
- if (args[i] == null) {
- Class<?> parameterType = parameterTypes[i];
- argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
- }
- else {
- argsWithDefaultValues[i] = args[i];
- }
- }
- // 很熟悉的反射代码【java.lang.reflect.Constructor#newInstance】
- return ctor.newInstance(argsWithDefaultValues);
- }
- }
- catch (InstantiationException ex) {
- throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
- }
- catch (IllegalAccessException ex) {
- throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
- }
- catch (IllegalArgumentException ex) {
- throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
- }
- catch (InvocationTargetException ex) {
- throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
- }
- }
总结:
- // 在刷新上下文方法里
- org.springframework.context.support.AbstractApplicationContext#refresh
- // 获取并注册bean定义(包含了注解扫描等过程)
- postProcessBeanFactory(beanFactory);
- // 实例化所有非懒加载的bean(本质用了反射实现了实例化bean)
- finishBeanFactoryInitialization(beanFactory);
【源码阅读】SpringBoot-v2.2.0启动过程以及细节的更多相关文章
- Netty源码阅读(一) ServerBootstrap启动
Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...
- 曹工说Redis源码(3)-- redis server 启动过程完整解析(中)
文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...
- 曹工说Redis源码(5)-- redis server 启动过程解析,以及EventLoop每次处理事件前的前置工作解析(下)
曹工说Redis源码(5)-- redis server 启动过程解析,eventLoop处理事件前的准备工作(下) 文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis ...
- [源码分析] 消息队列 Kombu 之 启动过程
[源码分析] 消息队列 Kombu 之 启动过程 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 是 ...
- Yii2.0源码阅读-一次请求的完整过程
Yii2.0框架源码阅读,从请求发起,到结束的运行步骤 其实最初阅读是从yii\web\UrlManager这个类开始看起,不断的寻找这个类中方法的调用者,最终回到了yii\web\Applicati ...
- 源码阅读之mongoengine(0)
最近工作上用到了mongodb,之前只是草草了解了一下.对于NoSQL的了解也不是太多.所以想趁机多学习一下. 工作的项目直接用了pymongo来操作直接操作mongodb.对于用惯了Djongo O ...
- Spark源码分析(一)-Standalone启动过程
原创文章,转载请注明: 转载自http://www.cnblogs.com/tovin/p/3858065.html 为了更深入的了解spark,现开始对spark源码进行分析,本系列文章以spark ...
- 曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充
文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...
- flume【源码分析】分析Flume的启动过程
h2 { color: #fff; background-color: #7CCD7C; padding: 3px; margin: 10px 0px } h3 { color: #fff; back ...
随机推荐
- 本地eyoucms搬家
1.后台数据备份 2.删除install 里面的install.lock 3.清理缓存文件 data - runtime-删除所有文件: 4.项目中的文件全部压缩 即打包完毕:最后再把打包的文件放置到 ...
- Vue-Cli 指南
构建项目: vue create 文件夹名称 运行项目:(README文件查询) npm run serve
- javascript实现blob加密视频源地址
一.HTML代码: <video id="my-video" class="video-js" playsinline controls preload= ...
- 基于 ECharts 封装甘特图并实现自动滚屏
项目中需要用到甘特图组件,之前的图表一直基于 EChart 开发,但 EChart 本身没有甘特图组件,需要自行封装 经过一番鏖战,终于完成了... 我在工程中参考 v-chart 封装了一套图表组件 ...
- 在知识爆炸的年代如何学习,避免成为PPT架构师
计算机的发展大体遵循摩尔定律,IT要学的东西越来越多,感觉无从下手 然后发现许多人,专门喜欢说这些名词概念装高大上,脱离一线开发,技术跟风盲目崇拜新的骚东西,比如docker,k8s,微服务,open ...
- shell之命令代换,将当前路径存放在变量中,然后使用变量
重要的 命令代换`` 反引号 shell先执行该命令,然后将命令的结果存放在 变量中 例如 var=`pwd` echo $var 也可以用其$()替换 var=$(date) echo $var 删 ...
- iview admin动态路由实现
参考 https://blog.csdn.net/weixin_41538490/article/details/93749942
- leetcode4 Median of Two Sorted Arrays学习记录
学习了扁扁熊的题解:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/4-xun-zhao-liang-ge- ...
- yandexbot ip列表整理做俄罗斯市场的站长可以关注一下
这段时间ytkah在负责一个客户的网站,主要做俄罗斯市场,当然是要研究Yandex了,首先是要知道yandexbot的ip有哪些,本文通过分析这个站从2018.12.02到2019.05.21这段时间 ...
- Python实战之ATM+购物车
ATM + 购物车 需求分析 ''' - 额度 15000或自定义 - 实现购物商城,买东西加入 购物车,调用信用卡接口结账 - 可以提现,手续费5% - 支持多账户登录 - 支持账户间转账 - 记录 ...