美文网首页
Springboot源码跟读1--SpringApplicati

Springboot源码跟读1--SpringApplicati

作者: 安中古天乐 | 来源:发表于2020-04-15 16:51 被阅读0次

    使用Springboot框架进行应用开发,方便得一P,但是不能仅满足于会用,而要了解其背后的运行机制,这样才能在框架出现报错时,不慌不乱,谈笑间消灭掉Bug。

    而源代码是框架最好的导师,不废话,开讲!

    要启动Springboot应用,需要配置启动类,如下:

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

    可以看到,关键在于SpringApplication的run方法:

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

    本质上是将启动类的Class包装成Class[]作为primarySources,然后传给SpringApplication的构造函数创建其实例,最后再调用SpringApplication实例的run方法完成应用的启动。

    由于Springboot Application启动涉及的东西太多,我们分多篇来讲。第1篇先讲一讲SpringApplication的创建过程。

    点进去SpringApplication的构造函数:

    public SpringApplication(Class... primarySources) {
        this((ResourceLoader)null, primarySources);
    }
    
    public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = this.deduceWebApplicationType();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }
    

    需要关注的有deduceWebApplicationType、setInitializers、setListeners、mainApplicationClass这4个方法。

    deduceWebApplicationType

    该方法主要用于推断应用类型。

    private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null)) {
            return WebApplicationType.REACTIVE;
        } else {
            String[] var1 = WEB_ENVIRONMENT_CLASSES;
            int var2 = var1.length;
    
            for(int var3 = 0; var3 < var2; ++var3) {
                String className = var1[var3];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return WebApplicationType.NONE;
                }
            }
    
            return WebApplicationType.SERVLET;
        }
    }
    
    • 如果Classpath中存在org.springframework.web.reactive.DispatcherHandler的继承类且不存在org.springframework.web.servlet.DispatcherServlet的继承类,则返回该应用类型为REACTIVE;
    • 继续判断Classpath中javax.servlet.Servlet或org.springframework.web.context.ConfigurableWebApplicationContext的继承类是否存在,只要1个不存在,则返回该应用类型为NONE;
    • 否则返回该应用类型为SERVLET。

    deduceMainApplicationClass

    该方法主要用于推断启动类Class。

    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];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
            ;
        }
    
        return null;
    }
    

    基本原理是创建1个RuntimeException实例,然后获取其堆栈信息,遍历各栈帧的方法名,若该栈帧的方法名为"main",则返回该栈帧所在的Class。

    setInitializers和setListeners

    2种方法分别用于初始化所有ApplicationContextInitializer和ApplicationListener的实例,底层均调用的getSpringFactoriesInstances方法,下面具体看看getSpringFactoriesInstances的实现:

    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) {
        // 获取Class加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 获取该Type所有继承类的Class Name
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 根据Class Name通过反射创建各继承类的实例
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 根据@Order注解排序后返回
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    

    先看一下loadFactoryNames方法:

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
    
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
    
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
    
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                        result.addAll((String)entry.getKey(), factoryClassNames);
                    }
                }
    
                cache.put(classLoader, result);
                return result;
            } catch (IOException var9) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
            }
        }
    }
    

    loadFactoryNames方法先从缓存中获取,若缓存中不存在,则扫描ClassPath中所有的"META-INF/spring.factories"加载,更新到缓存,然后返回结果。

    拿到各继承类的className信息后,就可以通过createSpringFactoriesInstances方法来构建实例。

    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList(names.size());
        Iterator var7 = names.iterator();
    
        while(var7.hasNext()) {
            String name = (String)var7.next();
    
            try {
                // 通过name加载Class
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                // 获取Class的构造器
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                // 反射创建该Class的实例
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }
    
        return instances;
    }
    

    Tip: 这里有2个工具类:ClassUtils和BeanUtils,有兴趣的可以看一下。

    实践一把,构建一个空Springboot的Web项目,然后在SpringApplication的run方法开头打个断点,Debug运行:

    1.jpg 2.jpg 3.jpg

    可以看到,SpringApplication已创建,各项属性也已初始化。

    OK,SpringApplication创建的源码已跟完,下篇开始run方法的源码跟读。

    相关文章

      网友评论

          本文标题:Springboot源码跟读1--SpringApplicati

          本文链接:https://www.haomeiwen.com/subject/fvzcvhtx.html