Spring Boot 源码分析(二):启动流程

234 阅读11分钟

Spring Boot主启动类的main方法中调用SpringApplication的静态方法run后,项目直接启动了,这一篇我们就来分析下具体的启动流程。IOC是Spring的两个核心特性之一,Spring Boot项目启动过程中必然会围绕IOC容器的初始化做一些动作。另外,Spring Boot 2.x基于Spring 5.x,Spring Boot 1.x基于Spring 4.x,下面的分析都是基于很多项目常用的Spring Boot 2.x。先来看一下Spring IOC的一些基础API。

1. IOC相关API

1.1 BeanFactory

用IDEA生成BeanFactory接口和相关重要API的类图:

2-1.jpg

BeanFactory是最基本的接口,它的重要子接口有ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory等。ListableBeanFactory具有可列举性,可以把容器里的bean列举出来;HierarchicalBeanFactory使其派生接口具有父子结构。ConfigurableBeanFactory继承了它,因而具有获得了层次性;AutowireCapableBeanFactory支持自动装配,用来注入Spring无法控制其生命周期的bean。

AbstractBeanFactory是BeanFactory最基础的抽象类,通过继承HierarchicalBeanFactory管理BeanFactory的层次性,通过继承AliasRegistry等具有管理bean对象名称的能力,createBean抽象方法, getBean和doGetBean等重要方法都在这里。

AbstractAutowireCapableBeanFactory抽象类,实现了AutowireCapableBeanFactory,具有组件自动装配能力;继承了AbstractBeanFactory,实现了createBean方法,bean对象的真正创建动作、属性赋值和依赖注入,Bean的初始化都在这里。

DefaultListableBeanFactory是BeanFactory的成熟的落地实现类,通过继承ConfigurableListableBeanFactory获得可列举性,通过继承AbstractAutowireCapableBeanFactory获取自动装配和bean对象生命周期管理能力, 通过实现BeanDefinitionRegistry接口获得注册bean定义的能力。

1.2 ApplicationContext

​ 用IDEA生成ApplicationContext接口的父接口类图:

2-2.jpg

​ 类图显示ApplicationContext接口具有加载文件资源、事件发布、国际化支持、容器层级关系支持和可列举性的能力。

​ 用IDEA生成ApplicationContext接口的父接口类图:

2-3.jpg ConfigurableApplicationContext接口继承ApplicationContext,同时加了一些配置上下文的方法。AbstractApplicationContext抽象类,定义和实现了绝大部分应用上下文的功能。AbstractApplicationContext有2个重要子类GenericApplicationContext和AbstractRefreshableApplicationContext。Spring IOC基于XML配置和注解驱动2种方式,Spring Boot已放弃基于XML的方式。基于XML的基于XML的IOC容器可重复刷新,基于注解的不可以。AbstractRefreshableApplicationContext代表XML方式,GenericApplicationContext代表注解方式。所以我们主要分析GenericApplicationContext及其子类。它的子类AnnotationConfigApplicationContext是Spring中最常用的注解驱动IOC容器。

1.3 Environment

包含Profile和Property配置的信息,可实现统一的配置存储、配置解析等。

用IDEA生成Environment接口的父接口类图:

2-4.jpg

​ Environment继承PropertyResolver,PropertyResolver具有获取属性和解析占位符的能力。ConfigurableEnvironment具有指定激活的Profile和获取MutablePropertySources的能力。AbstractEnvironment是所有Environment的落地实现类。

​ 下面的源码分析会发现ApplicationContext创建后Environment才创建,所以Environment伴随ApplicationContext的存在。ApplicationContext同时包含普通bean和Environment,同时Environment还要辅助处理配置属性的解析和注入,配合ApplicationContext工作,所有bean都可能要Environment参与处理,所以Environment覆盖范围比普通bean更大,但在ApplicationContext范围内。

2. springApplication的创建和启动

Spring Boot项目主启动类的main方法会调用springApplication的静态run方法,这个run方法会调用重载的静态run方法:

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

重载方法会创建SpringApplication对象,然后调用这个对象的run方法:

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }
2.1 创建

SpringAplication对应构造方法:

public SpringApplication(Class<?>... primarySources) {
        this((ResourceLoader)null, primarySources);
}

SpringAplication还支持传入特定ResourceLoader以实现自定义资源加载:

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));
    	//判断应用类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	//设置应用初始化器
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    	//设置事件监听器
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    	//确定主启动类
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

2.1 判断应用类型

直接打开枚举WebApplicationType:

public enum WebApplicationType {
    NONE,
    SERVLET,
    REACTIVE;

    private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
	//...
    
    static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, (ClassLoader)null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, (ClassLoader)null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, (ClassLoader)null)) {
            return REACTIVE;
        } else {
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }

            return SERVLET;
        }
    }
    
}

Spring Boot 2.x解决Web场景有WebMvc和WebFlux两种方案。从源码看出:

  • 若WebFlux的核心控制器DispatcherHandler存在,且WebMvc的核心控制器DispatcherServlet不存在,则启用WebFlux环境;
  • 若Servelet类和ConfigurableWebApplicationContext中有任何一个不存在,则启用None环境;
  • 否则启用Servlet环境。
2.2 设置应用初始化器

先看看ApplicationContextInitializer接口,顾名思义它和初始化ApplicationContext有关。我们的分析是基于WebMvc环境,这个环境中对应的实现是ServletContextApplicationContextInitializer:

public class ServletContextApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext>, Ordered {
    private final ServletContext servletContext;

    public ServletContextApplicationContextInitializer(ServletContext servletContext) {
        this(servletContext, false);
    }

    //...

    public void initialize(ConfigurableWebApplicationContext applicationContext) {
        applicationContext.setServletContext(this.servletContext);
        if (this.addApplicationContextAttribute) {
            this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, applicationContext);
        }

    }
}

重写的initialize方法将ApplicationContext和ServletContext互相放到对方的容器中。

回头看this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

明显用了SPI机制加载配进去的所有ApplicationContextInitializer实现类。

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return this.getSpringFactoriesInstances(type, new Class[0]);
    }

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        //Spring SPI
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
2.3 设置事件监听器

Spring本身有完善的事件监听机制,开发时可以编写事件并发布,然后设置监听方法处理。Spring Boot将Spring事件监听机制做了扩展,首先看下事件监听的抽象SpringApplicationRunListener接口。它有下面方法:

  • starting,调用SpringApplication的run方法时立即回调(下面的run方法源码可以看到)。

  • environmentPrepared,Environment构建完成但ApplicationContext创建前回到。

  • contextPrepared,创建ApplicationContext后调用。

  • contextLoaded,ApplicationContext已加载到尚未刷新容器。

  • started,容器已刷新但未调用CommandLineRunners和ApplicationRunners。

  • running,run方法彻底完成前。

  • failed,run方法执行过程抛异常时调用。

    它唯一的实现类EventPublishingRunListener看,每个方法对应一个Event对象:

    • starting:ApplicationStartingEvent
    • environmentPrepared:ApplicationEnvironmentPreparedEvent
    • contextPrepared:ApplicationContextInitializedEvent
    • contextLoaded:ApplicationPreparedEvent
    • started:ApplicationStartedEvent
    • running:ApplicationReadyEvent
    • failed:ApplicationFailedEvent

了解到这个程度可以更好分析下面的源码了。SpringApplication的构造方法中设置事件监听器也是用了SPI机制。

Spring Boot加载解析yaml或properties等配置文件就是依靠一个META-INF/spring.factories中配的一个事件监听器来完成相关动作的,后面接着分析这一点的源码。

2.4 确定主启动类
private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                //遍历方法调用栈,找到有main方法那一层,应类就是主启动类
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } 
		// ...
        return null;
    }
2.2 启动

SpringApplication对象创建完成后就执行run方法启动Spring Boot应用。

public ConfigurableApplicationContext run(String... args) {
        //...
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
    	//获取SpringApplicationRunListeners
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
    	//调用所有SpringApplicationRunListener实现类的starting
        listeners.starting();

        try {
            //封装main方法的args参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //准备环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            //创建空的容器
            context = this.createApplicationContext();
            //容器初始化
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //刷新容器
            this.refreshContext(context);
            //容器刷新后的回调,模板方法供子类重写
            this.afterRefresh(context, applicationArguments);
            
			//调用SpringApplicationRunListener实现类的started(容器已刷新但未调用CommandLineRunners和ApplicationRunners)
            listeners.started(context);
            //调用CommandLineRunners和ApplicationRunners
            this.callRunners(context, applicationArguments);
        } catch (Throwable var9) {
            //执行run抛出异常,调用listeners.failed(context, exception);
            this.handleRunFailure(context, var9, listeners);
            throw new IllegalStateException(var9);
        }

        try {
            //run方法彻底完成时回调
            listeners.running(context);
            return context;
        } catch (Throwable var8) {
            this.handleRunFailure(context, var8, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var8);
        }
    }

run方法是按照IOC容器的创建前准备、创建、刷新、刷新后回调的流程来处理的,上面介绍的SpringApplicationRunListener接口方法的回调会贯穿其中。

2.2.1 创建前准备

先获取SpringApplicationRunListeners并执行starting方法。SpringApplicationRunListeners是个工具类,它组合了SpringApplicationRunListener对象集合,实现了SpringApplicationRunListener的所有接口方法,具体的实现逻辑在SpringApplicationRunListener的唯一实现类EventPublishingRunListener中。进一步看,EventPublishingRunListener中重写方法中会先构造事件对象,然后用它组合的事件广播器SimpleApplicationEventMulticaster广播或用ConfigurableApplicationContext的publishEvent方法发布。

创建前准备环境:

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        //创建环境
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        //配置环境
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach((Environment)environment);
        //SpringApplicationRunListener的回调environmentPrepared
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        //Environment与SpringApplication绑定
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

先创建环境,根据SpringApplication构造方法中确定的Web类型,创建对应Environment子类对象返回。

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        } else {
            switch(this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
            }
        }
    }

再配置环境:

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            //获取ApplicationConversionService实例配置到environment
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService)conversionService);
        }
		//main方法参数封装成PropertySource,配置到environment
        this.configurePropertySources(environment, args);
        //手动编写的激活的Profile配置到environment
        this.configureProfiles(environment, args);
    }

最后Environment与SpringApplication绑定:

    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } //catch ...
    }

Binder将environment中以“spring.main”开头的值映射到SpringApplication中

2.2.2 创建空容器
protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                //根据Web类型找到对应ApplicationContext的实现类
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }
		
    	//用反射创建容器对象
        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }
2.2.3 容器初始化
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
    	//创建容器后的后置处理
        this.postProcessApplicationContext(context);
    	//应用SpringApplication构造方法中设置的初始化器
        this.applyInitializers(context);
        listeners.contextPrepared(context); //调用SpringApplicationRunListener实现类的started

    	//从ConfigurableApplicationContext中获取beanFactory
    	//然后用beanFactory注册main方法参数对应的封装对象,设置是否运行Bean定义覆盖,添加懒加载BeanFactoryPostProcessor
        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());
        }

    	//获取所有配置源
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
    	//加载配置源
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }
  • 容器后置处理

        protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
            //注册Bean的名称生成器
            if (this.beanNameGenerator != null) {
     context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator", this.beanNameGenerator);
            }
    
            //资源加载器设置到容器中
            if (this.resourceLoader != null) {
                if (context instanceof GenericApplicationContext) {
                    ((GenericApplicationContext)context).setResourceLoader(this.resourceLoader);
                }
    
                if (context instanceof DefaultResourceLoader) {
                    ((DefaultResourceLoader)context).setClassLoader(this.resourceLoader.getClassLoader());
                }
            }
    
            //注册类型转换器
            if (this.addConversionService) {
     context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
            }
        }
    
  • 应用初始化器比较简单,取出来遍历,分别调对应的初始化方法

        protected void applyInitializers(ConfigurableApplicationContext context) {
            Iterator var2 = this.getInitializers().iterator();
    
            while(var2.hasNext()) {
                ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
                Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
                Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
                initializer.initialize(context);
            }
    
        }
    
  • 获取所有配置源。

        public Set<Object> getAllSources() {
            Set<Object> allSources = new LinkedHashSet();
            //main方法传入的主类Class对象放在了primarySources
            if (!CollectionUtils.isEmpty(this.primarySources)) {
                allSources.addAll(this.primarySources);
            }
    
            if (!CollectionUtils.isEmpty(this.sources)) {
                allSources.addAll(this.sources);
            }
    
            return Collections.unmodifiableSet(allSources);
        }
    
  • 加载配置源

        protected void load(ApplicationContext context, Object[] sources) {
    
            BeanDefinitionLoader loader = this.createBeanDefinitionLoader(this.getBeanDefinitionRegistry(context), sources);
            
    		//...
            loader.load();
        }
    

    创建的Bean定义加载器,可以加载XML配置中定义的Bean和注解驱动的Bean,也可以获取包路径下被@Component标注的Bean。它组合了XmlBeanDefinitionReader、AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner,从重载的load方法可以看出,具体的加载工作委托给了这几个类来做。

    class BeanDefinitionLoader {
        private final Object[] sources;
        private final AnnotatedBeanDefinitionReader annotatedReader;
        private final XmlBeanDefinitionReader xmlReader;
        private BeanDefinitionReader groovyReader;
        private final ClassPathBeanDefinitionScanner scanner;
        
        int load() {
            int count = 0;
            Object[] var2 = this.sources;
            int var3 = var2.length;
    
            for(int var4 = 0; var4 < var3; ++var4) {
                Object source = var2[var4];
                count += this.load(source);
            }
    
            return count;
        }
    
        //重载的load,根据配置源类型委托不同加载器来做
        private int load(Object source) {
            Assert.notNull(source, "Source must not be null");
            if (source instanceof Class) {
                return this.load((Class)source);
            } else if (source instanceof Resource) {
                return this.load((Resource)source);
            } else if (source instanceof Package) {
                return this.load((Package)source);
            } else if (source instanceof CharSequence) {
                return this.load((CharSequence)source);
            } else {
                throw new IllegalArgumentException("Invalid source type " + source.getClass());
            }
        }
    }
    
    
2.2.4 刷新容器

这里细节更多,提供了很多的扩展点给开发者,后面再分析。

    private void refreshContext(ConfigurableApplicationContext context) {
		//...
        this.refresh((ApplicationContext)context);
    }

	//refreshContext调refresh,实际是调ConfigurableApplicationContext的refresh方法
	//ConfigurableApplicationContext接口的refresh,最终都会调AbstractApplicationContext的refresh
    protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
    }
2.2.5 刷新后回调
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        Iterator var4 = (new LinkedHashSet(runners)).iterator();

        while(var4.hasNext()) {
            Object runner = var4.next();
            if (runner instanceof ApplicationRunner) {
                this.callRunner((ApplicationRunner)runner, args);
            }

            if (runner instanceof CommandLineRunner) {
                this.callRunner((CommandLineRunner)runner, args);
            }
        }

    }

    private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
        try {
            runner.run(args.getSourceArgs());
        } // catch ...
    }

收集容器中所有ApplicationRunner和CommandLineRunner对象,然后分别调用run方法。这里用于在IOC容器完全启动后,SpringApplicationRunListener的事件完成后,留出来的一个扩展点。

@FunctionalInterface
public interface ApplicationRunner {
    void run(ApplicationArguments args) throws Exception;
}

//CommandLineRunner同样也只有一个run方法

在这个扩展点是位于应用启动完成后,所以可以利用它在应用启动后做一些工作。如应用启动后从数据库加载一些配置信息、从数据库加载热点数据到缓存等;应用启动后立即启动向数据同步、日志清理这样的后台任务;应用启动后设置一些默认的角色权限,保证系统正常的功能可用。

如下,在主启动类直接实现ApplicationRunner,并用@Order控制执行顺序(注解中数字越小越先执行):

@SpringBootApplication
public class SpringBootInductionApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootInductionApplication.class, args);
    }

    @Component
    @Order(3)
    class ApplicationRunnerBean implements ApplicationRunner {

        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("ApplicationRunnerBean...");
            System.out.println("Spring Boot成功启动,读库写缓存...");
        }
    }

    @Component
    @Order(2)
    class InductApplicationRunnerBean2 implements ApplicationRunner {

        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("ApplicationRunnerBean2...");
            System.out.println("Spring Boot成功启动,初始化系统常用角色和权限...");
        }
    }

}

执行结果:

2-5.jpg

项目启动后,紧接着回调逻辑也执行了。