1, Introduction
Traditional SSM framework development requires a variety of configuration files, application xml,springmvc.xml,web.xml and so on, and then compile and package the written program, and throw it into tomcat's webapp for deployment and startup. However, after the development of the follow-up framework, it can basically achieve zero configuration file, and there is no need to install tomcat separately for deployment. The implementation principle is the java config provided by Spring and the built-in tomcat. Of course, there is already a framework to integrate them, namely Springboot. What should we do if we don't use Springboot?
2, Example
1. Add jar package
compile("org.apache.tomcat.embed:tomcat-embed-core:9.0.12") compile("org.apache.tomcat.embed:tomcat-embed-jasper:9.0.12") //spring Relevant are omitted and added by yourself
2. Write mvc configuration class
@Configuration @ComponentScan("com.luban.mybatis") public class AppConfig extends WebMvcConfigurationSupport { }
3. Add custom initialization class
public class MyWebApplicationInitialier implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { //use java Annotation, to initialize spring Context 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 method
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(); //force Tomcat server Wait, avoid main Close after thread execution tomcat.getServer().await(); } }
5. Control layer
@RestController @RequestMapping("/test") public class TestController { @RequestMapping("/get") public String testGet() { return "ok"; } }
III. source code analysis
1. tomcat starts SPI mechanism
When tomcat starts, resources \ meta-inf \ services \ javax. XML will be loaded when the StandardContext class is initialized servlet. The implementation class configured in the servletcontainerinitializer file and calls its onStartup method
org.springframework.web.SpringServletContainerInitializer
Initialize StandardContext and call startInternal method
protected synchronized void startInternal() throws LifecycleException { ··· try { ··· // Lifecycle events fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); ··· // call ServletContainerInitializers Implementation class onStartup Method, and initializers of value value set Set sum servlet Context passed in 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. Trigger lifecycle events
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() { ··· // find ServletContainerInitializer All implementation classes // put to initializerClassMap In the middle, key Is an implementation class, value It's a set Collection for storage@HandlesTypes Implementation class of annotation if (ok) { processServletContainerInitializers(); } ··· if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) { // From all jar Find all in the package ServletContainerInitializer Used on the implementation class@HandlesTypes annotation // of value The implementation class of the interface corresponding to the value is put into initializerClassMap of value In value if (ok) { processAnnotations( orderedFragments, webXml.isMetadataComplete(), javaClassCache); } } // Take what you found ServletContainerInitializer Configuration put StandardContext Properties of initializers in 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()); } } } }
Find the implementation classes for all servletcontainerinitializers
protected void processServletContainerInitializers() { List<ServletContainerInitializer> detectedScis; try { //Find and load ServletContainerInitializer Implementation class of 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) { //deposit ServletContainerInitializer Implementation class, initialization value value initializerClassMap.put(sci, new HashSet<Class<?>>()); ··· } }
Add to the initializers property of StandardContext
//StandardContext public void addServletContainerInitializer( ServletContainerInitializer sci, Set<Class<?>> classes) { initializers.put(sci, classes); }
2. Call the onStartup method of all implementation classes of ServletContainerInitializer
@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) { //call HandlesTypes Annotation of all implementation classes onStartup method initializer.onStartup(servletContext); } } }
Here, the onStartup method of our custom MyWebApplicationInitialier implementation class will be called, and the servlet context will be passed in. It will not be parsed. It is all the initialization content of the spring container, which has been explained before.
4, Embedded tomcat principle of spring boot
springboot starts with a main method that calls springapplication Run (gtripapplication. Class, args) method. Let's look at this method directly.
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 { //Calling the parent class refresh method super.refresh(); } catch (RuntimeException ex) { WebServer webServer = this.webServer; if (webServer != null) { webServer.stop(); } throw ex; } } //AbstractApplicationContext public void refresh() throws BeansException, IllegalStateException { ··· //Call methods of subclasses onRefresh(); ··· } //ServletWebServerApplicationContext protected void onRefresh() { super.onRefresh(); try { //establish web container createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
I won't talk much about the refresh method of the parent class. I've talked about the source code of spring before. In the onRefresh method, the subclass context rewrites this method and creates a web container.
private void createWebServer() { WebServer webServer = this.webServer; //If using jar When the package is started, what you get must be null ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { //What you get is ServletWebServerFactory Type bean,It's a factory ServletWebServerFactory factory = getWebServerFactory(); //Create and launch web container 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(); }
Here, a lambda expression getSelfInitializer() is put in advance, which will be executed later
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { //Returns a lambda expression 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); } }
Continue creating webServer
public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } //Create instance Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); //instantiation 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); }
//Key code for initializing DispatcherServlet prepareContext(tomcat.getHost(), initializers); //start-up 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(); } }); // start-up 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 } // A daemon thread was started to prevent the thread from stopping startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
Key code 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); ``` } //Realized ServletContainerInitializer Interface, then create StandardContext When you're ready, you'll call him onStartup method 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) { //Placed before execution lambda Expressional onStartup method 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; } }
You can see that the lambda expression added before is finally encapsulated in TomcatStarter, which implements the ServletContainerInitializer interface. Finally, when creating the StandardContext, the onStartup method of the lambda expression will be called.
//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(); //hold DispatcherServlet Put into tomcat context return servletContext.addServlet(name, this.servlet); }
Call the start method of tomcat, and then detonate inward to start a sub container.