一、引言
传统的SSM框架开发需要多种配置文件,application.xml、springmvc.xml、web.xml等等,然后对写好的程序进行编译、打包,丢到tomcat的webapp下部署、启动。但是经过后续框架的发展,基本可以做到零配置文件,也不需要单独安装tomcat进行部署。实现的原理就是Spring提供的java config,以及内置的tomcat,当然目前已经存在了一种把他们整合在一起的框架了,就是Springboot,如果我们不用Springboot,该怎么做呢?
二、 示例
1、添加jar包
compile("org.apache.tomcat.embed:tomcat-embed-core:9.0.12") compile("org.apache.tomcat.embed:tomcat-embed-jasper:9.0.12") //spring相关的就省略了,自行添加
2、编写mvc配置类
@Configuration @ComponentScan("com.luban.mybatis") public class AppConfig extends WebMvcConfigurationSupport { }
3、添加自定义初始化类
public class MyWebApplicationInitialier implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { //用java注解的方式,去初始化spring上下文环境 AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(AppConfig.class); System.out.println("init context"); DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); } }
4、main方法
public class AppTest { public static void main(String[] args) throws LifecycleException, ClassNotFoundException, IllegalAccessException, InstantiationException { Tomcat tomcat = new Tomcat(); tomcat.setPort(8081); tomcat.addWebapp("/", "C:\\workspace\\software\\apache-tomcat-9.0.55"); tomcat.start(); //强制Tomcat server等待,避免main线程执行结束后关闭 tomcat.getServer().await(); } }
5、控制层
@RestController @RequestMapping("/test") public class TestController { @RequestMapping("/get") public String testGet() { return "ok"; } }
三、源码解析
1、tomcat启动SPI机制
tomcat启动时,在初始化StandardContext类时,会加载resources\META-INF\services\javax.servlet.ServletContainerInitializer文件中配置的实现类,并调用其onStartup方法
org.springframework.web.SpringServletContainerInitializer
初始化StandardContext,调用startInternal方法
protected synchronized void startInternal() throws LifecycleException { ··· try { ··· // 生命周期事件 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); ··· // 调用 ServletContainerInitializers 实现类的onStartup方法,并将initializers的value值set集合和servlet上下文传进去 for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) { try { entry.getKey().onStartup(entry.getValue(), getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } ··· } finally { ··· } ··· }
1、触发生命周期事件
protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } } //ContextConfig public void lifecycleEvent(LifecycleEvent event) { ``` // Process the event that has occurred if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart(); } ``` } protected synchronized void configureStart() { ``` webConfig(); ``` }
protected void webConfig() { ··· // 找到 ServletContainerInitializer 所有的实现类 // 放到initializerClassMap中,key是实现类,value是一个set集合用于存储@HandlesTypes注解的实现类 if (ok) { processServletContainerInitializers(); } ··· if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) { // 从所有jar包中找出所有ServletContainerInitializer实现类上使用了@HandlesTypes注解 // 的value值对应的接口的实现类,放入到initializerClassMap的value值中 if (ok) { processAnnotations( orderedFragments, webXml.isMetadataComplete(), javaClassCache); } } // 把找到的ServletContainerInitializer配置放入StandardContext的属性initializers中 if (ok) { for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } } }
找到所有ServletContainerInitializer的实现类
protected void processServletContainerInitializers() { List<ServletContainerInitializer> detectedScis; try { //找到并加载ServletContainerInitializer的实现类 WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context); detectedScis = loader.load(ServletContainerInitializer.class); } catch (IOException e) { log.error(sm.getString( "contextConfig.servletContainerInitializerFail", context.getName()), e); ok = false; return; } for (ServletContainerInitializer sci : detectedScis) { //存放ServletContainerInitializer的实现类,初始化value值 initializerClassMap.put(sci, new HashSet<Class<?>>()); ··· } }
添加到StandardContext的initializers属性中去
//StandardContext public void addServletContainerInitializer( ServletContainerInitializer sci, Set<Class<?>> classes) { initializers.put(sci, classes); }
2、调用ServletContainerInitializer所有实现类的onStartup方法
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); for (WebApplicationInitializer initializer : initializers) { //调用HandlesTypes注解的所有实现类的onStartup方法 initializer.onStartup(servletContext); } } }
这里就会调用到我们自定义的MyWebApplicationInitialier实现类的onStartup方法,并且传入了servlet的上下文,具体就不再解析了,都是spring容器的初始化内容,以前也都讲解过了。
四、spring-boot的内嵌tomcat原理
springboot的启动使用的是一个main方法,调用的是SpringApplication.run(GtripApplication.class, args)方法,我们直接来看这个方法。
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); } public ConfigurableApplicationContext run(String... args) { ``` refreshContext(context); ··· return context; } private void refreshContext(ConfigurableApplicationContext context) { if (this.registerShutdownHook) { shutdownHook.registerApplicationContext(context); } refresh(context); } protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); refresh((ConfigurableApplicationContext) applicationContext); } protected void refresh(ConfigurableApplicationContext applicationContext) { applicationContext.refresh(); } //ServletWebServerApplicationContext public final void refresh() throws BeansException, IllegalStateException { try { //调用父类的refresh方法 super.refresh(); } catch (RuntimeException ex) { WebServer webServer = this.webServer; if (webServer != null) { webServer.stop(); } throw ex; } } //AbstractApplicationContext public void refresh() throws BeansException, IllegalStateException { ··· //调用子类的方法 onRefresh(); ··· } //ServletWebServerApplicationContext protected void onRefresh() { super.onRefresh(); try { //创建web容器 createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
父类的refresh方法就不多讲了,之前已经讲过了spring的源码,在onRefresh方法里,子类上下文对这个方法进行了重写,创建了web容器。
private void createWebServer() { WebServer webServer = this.webServer; //如果使用的是jar包启动,获取的一定是null ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { //获取到的是ServletWebServerFactory类型的bean,是一个工厂 ServletWebServerFactory factory = getWebServerFactory(); //创建并启动web容器 this.webServer = factory.getWebServer(getSelfInitializer()); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
这里提前放入了一段lambda表达式getSelfInitializer(),后续会执行
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { //返回的是一个lambda表达式 return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }
继续创建webServer
public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } //创建实例 Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); //实例化connector Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); //Engine configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); }
//初始化DispatcherServlet的关键代码 prepareContext(tomcat.getHost(), initializers); //启动tomcat return getTomcatWebServer(tomcat); } protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); } public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null; initialize(); } private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // 启动tomcat this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // 启动了一个守护线程,防止线程停止 startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
关键代码prepareContext(tomcat.getHost(), initializers)
protected void prepareContext(Host host, ServletContextInitializer[] initializers) { ``` ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); host.addChild(context); configureContext(context, initializersToUse); ``` } protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); ``` } //实现了ServletContainerInitializer接口,那么创建StandardContext的时候,就会调用他的onStartup方法 class TomcatStarter implements ServletContainerInitializer { private static final Log logger = LogFactory.getLog(TomcatStarter.class); private final ServletContextInitializer[] initializers; private volatile Exception startUpException; TomcatStarter(ServletContextInitializer[] initializers) { this.initializers = initializers; } @Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { //执行之前放入的lambda表达式的onStartup方法 initializer.onStartup(servletContext); } } catch (Exception ex) { this.startUpException = ex; // Prevent Tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.isErrorEnabled()) { logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: " + ex.getMessage()); } } } Exception getStartUpException() { return this.startUpException; } }
可以看到之前加入的lambda表达式,最终被封装到了TomcatStarter,它实现了ServletContainerInitializer接口,那么最后在创建StandardContext的时候,会调用lambda表达式的onStartup方法。
//RegistrationBean public final void onStartup(ServletContext servletContext) throws ServletException { String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } register(description, servletContext); } //DynamicRegistrationBean protected final void register(String description, ServletContext servletContext) { D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } configure(registration); } //ServletRegistrationBean protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); //把DispatcherServlet放入到tomcat上下文 return servletContext.addServlet(name, this.servlet); }
调用tomcat的start方法,然后就是后续的向内引爆,启动一个一个子容器。