Spring Boot启动时判断是否为web项目

spring boot项目中如果我们引入web的starter在启动的时候会自动启动web容器,很好奇spring boot是怎么做到的?

读了读源码发现spring boot就是根据已经加载的类中是否有servlet相关的类来决定启动哪种ApplicationContext。

实现方式: 基于2.1.5.RELEASE

  • 1
@SpringBootApplication
public class BootStudyApplication {

    public static void main(String[] args) {
        System.out.println(System.getProperty("java.class.path"));
        SpringApplication.run(BootStudyApplication.class, args);
    }

}

在启动类中使用SpringApplication.run()方法来启动项目,跟代码

  • 2
    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);
    }

依次调用上面两个方法,在下面一个方法中调用SpringApplication的构造函数。

就是在这个构造函数中完成了项目类型的推断。

构造函数代码:

  • 3
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    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();
}

这里是调用了 WebApplicationType.deduceFromClasspath(); 方法来推断类型 。

根据方法名也可以大致看出来:从classpath中推断

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";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

private WebApplicationType() {
}

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (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;
    }
}

我这里是反编译后的代码deduceFromClasspath方法中ClassUtils.isPresent()里面的第一个参数其实是上面静态变量。这里直接显示的值。

这里就是来判断这几个类是否加载了。如果加载了就返回SERVLET。没的话放回REACTIVE或者NONE。

这里REACTIVE就是指普通boot项目。

下面就是看ClassUtils.isPresent()是怎么判断是否加载的。

public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        forName(className, classLoader);
        return true;
    } catch (IllegalAccessError var3) {
        throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
    } catch (Throwable var4) {
        return false;
    }
}

这里核心是forName方法。如果类已加载返回true。如果抛出异常则说明不存在。

继续forName方法。

public static Class<?> forName(String name, @Nullable ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
    Assert.notNull(name, "Name must not be null");
    Class<?> clazz = resolvePrimitiveClassName(name);
    if (clazz == null) {
        clazz = (Class)commonClassCache.get(name);
    }

    if (clazz != null) {
        return clazz;
    } else {
        Class elementClass;
        String elementName;
        
        //判断各种类型的类
        if (name.endsWith("[]")) {
            elementName = name.substring(0, name.length() - "[]".length());
            elementClass = forName(elementName, classLoader);
            return Array.newInstance(elementClass, 0).getClass();
        } else if (name.startsWith("[L") && name.endsWith(";")) {
            elementName = name.substring("[L".length(), name.length() - 1);
            elementClass = forName(elementName, classLoader);
            return Array.newInstance(elementClass, 0).getClass();
        } else if (name.startsWith("[")) {
            elementName = name.substring("[".length());
            elementClass = forName(elementName, classLoader);
            return Array.newInstance(elementClass, 0).getClass();
        } else {
            ClassLoader clToUse = classLoader;
            if (classLoader == null) {
                clToUse = getDefaultClassLoader();
            }
			//重点在这,尝试根据类名加载类。抛出异常再尝试内部类。如果都抛出异常,异常会抛出到刚刚的isPresent方法。
            try {
                return Class.forName(name, false, clToUse);
            } catch (ClassNotFoundException var9) {
                int lastDotIndex = name.lastIndexOf(46);
                if (lastDotIndex != -1) {
                    String innerClassName = name.substring(0, lastDotIndex) + '$' + name.substring(lastDotIndex + 1);

                    try {
                        return Class.forName(innerClassName, false, clToUse);
                    } catch (ClassNotFoundException var8) {
                    }
                }

                throw var9;
            }
        }
    }
}

这里如果加载成功,说明这个类在项目中存在,即认为这是个web项目。

回到刚刚的构造方法,这个时候webApplicationType就有了值。

在步骤2中new完成之后,调用run方法时,里面会根据webApplicationType来创建不同的ApplicationContext。

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        /** 这里 **/
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            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);
}

至此推演boot时哪种类型结束 。

至于org.springframework.web.servlet.DispatcherServlet会被加载,是在spring boot启动时org.springframework.boot.loader.JarLauncher这个类加载器会把依赖的类都加载进去。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值