春のブートソースコード解析プロセスとSpring MVCの統合を開始

推奨されるオープンソースプロジェクト

ペッパーメトリックは私の同僚と私は、オープンソース・ツール(開発github.com/zrbcool/pep ... jedis / MyBatisの/のHttpServlet /ダボ/ motan操作するパフォーマンス統計情報、および公開するタイミングプロメテウス他の主流のデータベースの互換性データが収集されました)、 grafanaショーの動向によって。そのプラグインアーキテクチャの非常にユーザーフレンドリー拡張機能やその他の集積のオープンソースコンポーネントです。
歓迎のプロジェクトを改善するために一緒にPRを提出するために、開発者になることながら、星を与えてください。

シンプルな春ブーツのWebプロジェクトからおしゃべり

私たちは、スプリングブーツで、このクラスのブートマスターを作成し、Webプロジェクトは非常に簡単です書き込み継承春ブート親と春ブート・スターター・ウェブの導入に依存しPOMことを知っている、とあなたはコントローラーを書く行くことができ、かつ非常にこのように、シンプル:

@SpringBootApplication
public class SampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}
// 然后再写一个Controller声明一个Rest服务
@RestController
@RequestMapping("/perf")
public class PerfController {
    @RequestMapping("/trace")
    public Object trace() {
        Object result = yourLogic();
        return result;
    }
}
复制代码

話SpringApplication.run

しかし、我々は最終的には、スプリング・ブーツの後ろにそれについて考えた私たちの仕事は非常にシンプルにするもの、どのようにそれは春になり、春、MVC、Tomcatはそれを統合するのですか?次は全体の初期化プロセスを分析するために、ビューのプロジェクトのポイントを開始します。

PS:次のコード分析プロセスは、一連のプロセスに焦点を当てて、変数への呼び出しを、著者はこの変数の特定の実装に直接与えられます、読者が混同されるかもしれないが、停止しない、の著者によって付与された最初の選択プロセスが初期化され、主要な変数ごとに1つの解釈ずつ実装されるの後ろの思考プロセスは、仕上げを撫で。

SpringApplication.runからいえば、次のようにメソッドが定義されています

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();//1)
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);//2)
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}
复制代码

このrunメソッドを打破してみましょう
)1を見て、コンテキスト= createApplicationContext()
このメソッドの内部で、春の主なコンテナを作成するための責任があるが、特定のプロジェクトの依存関係に基づいて動的に実装することを選択するための実行時クラス、それはWebプロジェクトの場合と同様に、AnnotationConfigServletWebServerApplicationContextを選択します選択のためのルールとの理由から、ここに戻って(:ドアの時間と空間に専念されますが、無視だServletWebServerApplicationContext)。
次に、我々は焦点2)refreshContext(コンテキスト)メソッドを見て
のApplicationContext).refresh(そのメソッド内では、最終的に((AbstractApplicationContext呼び出す) ) 法、我々が拡大するために、このメソッドを置きます

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try {
            postProcessBeanFactory(beanFactory);
            invokeBeanFactoryPostProcessors(beanFactory);
            registerBeanPostProcessors(beanFactory);
            initMessageSource();
            initApplicationEventMulticaster();
            onRefresh();//3)
            registerListeners();
            finishBeanFactoryInitialization(beanFactory);
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
        finally {
            resetCommonCaches();
        }
    }
}
复制代码

実際には、ここで私たちは春・コンテキストのパッケージを呼び出す必要があり、実際には、リフレッシュ中のスプリング・ブートの関係、そしてこの事実()SpringApplicationContext標準開始の標準的な部分であると、私たちは春の分解のプロセスを開始していないことは何もありません、私たちは、Tomcat、春-MVCをバインドするだけ心配部分です。
サブクラスServletWebServerApplicationContext AnnotationConfigServletWebServerApplicationContextであるので、フローはServletWebServerApplicationContext OnRefresh()メソッドを進めるように、ダイレクト3)onRefresh()メソッドを参照してください

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();//4)
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
复制代码

あなたは、4)createWebServer()は、私たちの鍵であることがわかります

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();//5)
        this.webServer = factory.getWebServer(getSelfInitializer());//6)
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}
复制代码

前記
5)ServletWebServerFactory getWebServerFactory工場=()。
上記で得られた語句は、特定の実装TomcatServletWebServerFactory(ワープゲートである:TomcatServletWebServerFactory)6)this.webServer = factory.getWebServer(getSelfInitializer());
6)getSelfInitializerを(ルック)方法:

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    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);
    }
}
复制代码

平均このビットは、我々はそれが何であるかの定義を見て、この方法はorg.springframework.boot.web.servlet.ServletContextInitializerを返すように定義され、リターンこの:: selfInitializeです

@FunctionalInterface
public interface ServletContextInitializer {
	void onStartup(ServletContext servletContext) throws ServletException;
}
复制代码

@FunctionalInterfaceのjava8は、機能ラムダselfInitializeに後の工程で呼び出されるこのインタフェースロジックに支持されています。
this.webServer = factory.getWebServer(...)で6)を参照し続け、我々が達成するために見て:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);//7)
    return getTomcatWebServer(tomcat);
}
复制代码

あなたは)最も重要なライン7、Tomcatインスタンス内の内部はwebServer実装として作成され、その後、Tomcatのコネクタサービス容器に注入し、その後、デフォルトのホストコンテナオートデプロイプロパティおよびその他のTomcatの初期化を設定見ることができ
、我々は見て:

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    ...//省略我们不关注的部分代码
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);//8)
    host.addChild(context);//将context加入host作为host的子容器
    configureContext(context, initializersToUse);//9)
    postProcessContext(context);
}
复制代码

私たちは、私たちが見て、configureContext法)、その後9を呼び出して、すべてのServletContextInitializerが実現し、配列に結合して下さい(文脈)コンテキストは、サブホストのホスト・コンテナとして追加した後、8されます)、それはhost.addChildを呼び出すことがわかります。

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    TomcatStarter starter = new TomcatStarter(initializers);//10)
    if (context instanceof TomcatEmbeddedContext) {
        TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
        embeddedContext.setStarter(starter);
        embeddedContext.setFailCtxIfServletStartFails(true);
    }
    context.addServletContainerInitializer(starter, NO_CLASSES);//11)
    ...//忽略
}
复制代码

10)コンテナはTomcatプロセスがこのTomcatStarterインスタンスに呼び出される開始するようTomcatStarterオブジェクトとスタータ)11を参照して、conainerInitializerリストコンテキストを追加作成しました。
私たちは、何が行われたかを見てTomcatStarter

class TomcatStarter implements ServletContainerInitializer {
    ...
	private final ServletContextInitializer[] initializers;
	...
	TomcatStarter(ServletContextInitializer[] initializers) {
		this.initializers = initializers;
	}
    ...
	@Override
	public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
		try {
			for (ServletContextInitializer initializer : this.initializers) {
				initializer.onStartup(servletContext);
			}
		}
		catch (Exception ex) {
			this.startUpException = ex;
			if (logger.isErrorEnabled()) {
				logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
						+ ex.getMessage());
			}
		}
	}
    ...
}
复制代码

私たちは、イベントコンテキストの起動をフックするTomcatStarter相当を参照して、すべての初期化子を注入しonStartupメソッドを呼び出すことができ、デジャヴはそうではありませんか?いえ、以前の@FunctionalInterface機能インタフェース、前述の特定のコンテンツの初期化子onStartupそして我々の深い表情

//ServletWebServerApplicationContext类当中
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    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);
    }
}
复制代码

我々は、すべてがそれぞれのServletContextInitializer getServletContextInitializerBeansための方法を()onStartup呼び出すことがわかります

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
}
复制代码

新しいServletContextInitializerBeans(getBeanFactory())を見てください何をしました

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
        Class<? extends ServletContextInitializer>... initializerTypes) {
    this.initializers = new LinkedMultiValueMap<>();
    this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
            : Collections.singletonList(ServletContextInitializer.class);
    addServletContextInitializerBeans(beanFactory);
    addAdaptableBeans(beanFactory);
    List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
            .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
            .collect(Collectors.toList());
    this.sortedList = Collections.unmodifiableList(sortedInitializers);
    logMappings(this.initializers);
}
复制代码

:それは、集積部ServletRegistrationBean、注入プロセスServletRegistrationBeanリファレンスに関して本明細書で春に実装たBeanFactoryから全てServletContextInitializer容器を、取得分かるワープゲート:Dispatcherservletregistrationbean

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
    for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
        for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
                initializerType)) {
            addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
        }
    }
}
private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
        ListableBeanFactory beanFactory) {
    if (initializer instanceof ServletRegistrationBean) {
        Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
        addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
    }
    else if (initializer instanceof FilterRegistrationBean) {
        Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
        addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
    }
    else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
        String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
        addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
    }
    else if (initializer instanceof ServletListenerRegistrationBean) {
        EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
        addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
    }
    else {
        addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
                initializer);
    }
}
复制代码

そして、避けるだろうプロセスは、我々は最終的にサーブレットコンテナをインターセプトすべての要求に注入DispatchServletますservletContext.addServletの標準Servlet3.0に呼び出しますonStartup方法のServletRegistrationBeanに呼び出します。
以下のコードを参照してください。

//RegistrationBean
@Override
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
@Override
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
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}
复制代码

これまでのところ、すべての統合が完了し、完了するために、Tomcatのプロセスを開始します。

私は物語を終了しませんでした:個々のコンポーネントが依存して初期化されていますか

TomcatServletWebServerFactory

いくつかの構成では、ばねブート自動構成/ META-INF / spring.factories:

...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
...
复制代码

その後、我々はServletWebServerFactoryAutoConfigurationクラスを見て

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    ...
}
复制代码

ServletWebServerFactoryConfiguration.EmbeddedTomcat.class、綿密な表情でを紹介@import一部

@Configuration
class ServletWebServerFactoryConfiguration {
	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {
		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
			return new TomcatServletWebServerFactory();
		}
	}
	...
}
复制代码

ワンピース春ブーツは、現在の実行環境は、TomcatServletWebServerFactory、後で春のコンテナ管理が便利な追加Beanインスタンスを満たすために作成された場合、TomcatのJARパッケージが含まれている@ConditionalOnClass裁判官によれば、条件を満たしています。

ServletWebServerApplicationContext

実際の開始は、AnnotationConfigServletWebServerApplicationContextは、我々はSpringApplicationクラスを見て、実際にSpringApplication ApplicationContextの実行時に、状況に応じて使用するかを決めるそのサブクラスを開始しました

ビューcreateApplicationContext()メソッド
This.webApplicationTypeは、これはどこの値だのですか?我々は、この工法を見て

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码

WebApplicationType.deduceFromClasspath()が自動的に値を識別するために使用され、達成を見て:

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}
复制代码

あなたはそれが裁判官に関連があるかどうかのサーブレットクラスのクラスローダを決定するので、裁判官が実行されていることであることがわかります。

DispatcherServletRegistrationBean

DispatcherServletRegistrationBeanが私たちのDispatcherServletを確保するための鍵は、Servletコンテナに注入され、実際に、我々はそれが初期化する方法で見ている
バネのブート自動構成を/メタ- INF / spring.factories 一部の構成では:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
复制代码

実装を見てください

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
	@Configuration
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		private final WebMvcProperties webMvcProperties;

		private final MultipartConfigElement multipartConfig;

		public DispatcherServletRegistrationConfiguration(WebMvcProperties webMvcProperties,
				ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
			this.webMvcProperties = webMvcProperties;
			this.multipartConfig = multipartConfigProvider.getIfAvailable();
		}

		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					this.webMvcProperties.getServlet().getPath());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
			if (this.multipartConfig != null) {
				registration.setMultipartConfig(this.multipartConfig);
			}
			return registration;
		}

	}
}
复制代码

その継承を見て、容器はDispatcherServletRegistrationBean Beanインスタンスのばねとして登録されていることがわかります。


ServletRegistrationBeanクラスの親クラスには、次のメソッドがあります。

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}
复制代码

これはServletContext.addServlet方法DispatchServletサーブレット方法Servlet3.0に登録されたサーブレット・コンテナに追加呼び出します。
そして、あなたはそれを呼び出すための時間が何であるか、addRegistrationを頼むかもしれませんか?相続によると、親クラスがあり、その親を表示RegistrationBean

@Override
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のサブクラスを達成するためのメソッド呼び出しです

@Override
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);
}
复制代码

addRegistrationテンプレート方法はaddRegistration ServletRegistrationBeanの前で主な流れは、について話したときの方法はここではそれらを繰り返さない、と言うに呼ばれている)onStartupメソッドはSpringApplication.runを(流れ、それを達成するための方法を達成されている
意志ので、 DispatchServletはTomcatの、DispatchServletテンプレートメソッドデザインパターン、異なる処理に割り当てられた特定のリクエストハンドラと統合され、これは後に本明細書で言及され、これは主にスプリングブートスプリングMVCとTomcatの統合された部分の原理に焦点を当てます。

おすすめ

転載: juejin.im/post/5d649ca4518825168d37c43d
おすすめ