您好,登錄后才能下訂單哦!
這篇文章主要講解了“web.xml SpringBoot打包可執行Jar運行SpringMVC的方法是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“web.xml SpringBoot打包可執行Jar運行SpringMVC的方法是什么”吧!
本文使用的Spring版本為Spring6,SpringBoot版本為3,JDK為17,可能會和之前有細微不同,但整體流程差不太大。
如果部署應用到tomcat webapps目錄下面啟動,則需要在項目中配置web.xml文件
配置Spring應用上下文
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/application-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
context-param標簽是用于在Web應用程序的上下文范圍內設置初始化參數。這些參數可以在整個Web應用程序中使用,并且可以通過ServletContext對象的getInitParameter()方法獲取。
ContextLoaderListener實現了ServletContextListener接口,這個接口是tomcat留給應用程序初始化上下文環境的接口,用于在Web應用程序啟動時加載ApplicationContext。
ServletContextListener有兩個默認方法
// 在所有的servlet和filter初始化之前被調用 default public void contextInitialized(ServletContextEvent sce) { } // 在所有的servlet和filter銷毀之后被調用 default public void contextDestroyed(ServletContextEvent sce) { }
ContextLoaderListener還繼承了ContextLoader類,所有的context操作都在此類進行。
ContextLoaderListener實現contextInitialized方法,然后調用父類ContextLoader的initWebApplicationContext方法,把ServletContext傳進去。
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
initWebApplicationContext方法關鍵代碼
... if (this.context == null) { // 創建ApplicationContext this.context = createWebApplicationContext(servletContext); } ... // 刷新ApplicationContext configureAndRefreshWebApplicationContext(cwac, servletContext); ... // 將當前ApplicationContext添加到ServletContext的屬性中,后面有用再說 // String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ...
在createWebApplicationContext方法中,先調用determineContextClass方法確定使用哪個ApplicationContext,找到之后,實例化。
determineContextClass這個方法,主要是確定使用的ApplicationContext,首先從web.xml中加載,如果用戶有定義,直接使用用戶自定義的。
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
web.xml中配置如下,
<context-param> <param-name>contextClass</param-name> <param-value>com.xxx.XxxContext</param-value> </context-param>
如果沒有配置,則使用Spring默認的XmlWebApplicationContext類。
這個類在ContextLoader同路徑包下面的ContextLoader.properties文件中定義。
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
configureAndRefreshWebApplicationContext關鍵代碼
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc) { // ... // 獲取web.xml中配置的contextConfigLocation參數 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // ... // 刷新上下文 wac.refresh(); }
至此Tomcat已經啟動Spring環境了,后續就是Spring的初始化流程,這里不再敘述。
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
此處的contextConfigLocation屬于DispatcherServlet的父類FrameworkServlet,主要用來加載SpringMVC相關的配置,示例如下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <!-- 掃描控制器和其他組件 --> <context:component-scan base-package="com.example.controller" /> <!-- 配置視圖解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 啟用Spring MVC注解支持 --> <mvc:annotation-driven /> </beans>
可以看到DispatcherServlet實現了Servlet接口,Servlet接口中有init方法,SpringMVC的配置就是在初始化的時候被加載的。
關鍵代碼在HttpServletBean.init()和FrameworkServlet.initServletBean()方法中。
public final void init() throws ServletException { // Set bean properties from init parameters. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. initServletBean(); }
protected final void initServletBean() throws ServletException { ... // 在這里初始化ApplicationContext this.webApplicationContext = initWebApplicationContext(); // 初始化servlet initFrameworkServlet(); }
protected WebApplicationContext initWebApplicationContext() { // 此處獲取根容器,就是Spring初始化的XmlWebApplicationContext, // 在上面把它添加到了ServletContext的屬性中,標記根容器,這里把它獲取出來 // String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; // servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // 此時webApplicationContext還是null,因為DispatchServlet是被tomcat創建的,需要無參構造器 // 構造器中沒有設置webApplicationContext的代碼,所以此時webApplicationContext還是null // 注意:在SpringBoot使用嵌入式Tomcat時,這個webApplicationContext不為null,因為FrameworkServlet還 // 實現了ApplicationContextAware接口,所以當SpringBoot的上下文準備好之后,會回調setApplicationContext方法 // 注入ApplicationContext,后面在細說 if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id // 此處主要是獲取web.xml配置的WebApplicationContext // 可以通過設置參數contextAttribute來設置加載SpringMVC的ApplicationContext // 比如下面這樣。除非項目中有多個WebApplicationContext,需要使用其他WebApplicationContext才會用到 // 一般都是null // <context-param> // <param-name>contextAttribute</param-name> // <param-value>myWebApplicationContext</param-value> // </context-param> wac = findWebApplicationContext(); } if (wac == null) { // 現在進入到創建SpringMVC的ApplicationContext流程 // 也就是加載contextConfigLocation定義的xml文件 // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. synchronized (this.onRefreshMonitor) { // 初始化策略對象 // 比如:HandlerMapping,HandlerAdapter,ViewResolver等等 onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { // SpringMVC所使用的contextClass,可以在<servlet>標簽下設置 // <init-param> // <param-name>contextClass</param-name> // <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value> // </init-param> // 默認為XmlWebApplicationContext Class<?> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } // 實例化ApplicationContext ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); // 設置環境參數 wac.setEnvironment(getEnvironment()); // 設置父容器為Spring的ApplicationContext wac.setParent(parent); // 獲取SpringMVC的contextConfigLocation文件 String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } // 配置并刷新ApplicationContext configureAndRefreshWebApplicationContext(wac); return wac; }
DispatchServlet初始化完成
為什么需要父子容器
父子容器的作用主要是劃分框架邊界和實現bean的復用。
在J2EE三層架構中,在service層我們一般使用Spring框架,而在web層則有多種選擇,如Spring MVC、Struts等。為了讓web層能夠使用service層的bean,我們需要將service層的容器作為web層容器的父容器,這樣就可以實現框架的整合。
父子容器的作用在于,當我們嘗試從子容器(Servlet WebApplicationContext)中獲取一個bean時,如果找不到,則會委派給父容器(Root WebApplicationContext)進行查找。這樣可以避免在多個子容器中重復定義相同的bean,提高了代碼的復用性和可維護性。
請求先進入doService,然后調用doDispatch進行處理。
doDispatch關鍵代碼
... // 首先根據當前請求HttpServletRequest,遍歷所有的HandlerMapping執行handle方法,返回可用的HandlerExecutionChain對象。 mappedHandler = getHandler(processedRequest); // 然后根據handler獲取支持的適配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 執行HandlerInterceptor.preHandle,在controller的方法被調用前執行 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 執行controller方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 執行HandlerInterceptor.postHandle,在controller的方法被調用后執行 mappedHandler.applyPostHandle(processedRequest, response, mv); // 渲染結果到視圖 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
HandlerMapping是request與handler object之間的映射,它能根據request找到對應的handler。handler object可以是任意類型,比如@Controller注解的類,或者實現了Controller接口的類,或者實現了HttpRequestHandler接口的類等。
HandlerExecutionChain是handler執行鏈,它包裝了handler object和一組HandlerInterceptor。HandlerInterceptor是攔截器,它可以在handler執行前后進行一些額外的操作,比如權限檢查,日志記錄等。
HandlerAdapter是handler的適配器,它能處理不同類型的handler object,并調用其對應的方法,返回ModelAndView對象。HandlerAdapter可以根據handler object的類型,進行參數綁定,返回值處理等操作。
HandlerInterceptor使用
定義一個攔截器類,實現HandlerInterceptor接口或者繼承HandlerInterceptorAdapter類,重寫preHandle,postHandle和afterCompletion三個方法。
在preHandle方法中,可以獲取請求和響應對象,進行預處理,比如檢查請求頭中的token,或者判斷請求的url是否有權限訪問等。如果返回true,則繼續執行后續的攔截器或者處理器;如果返回false,則中斷請求,不再執行后續的攔截器或者處理器。
在postHandle方法中,可以獲取請求和響應對象,以及處理器返回的ModelAndView對象,進行后處理,比如修改模型數據或者視圖信息等。這個方法只有在preHandle返回true且處理器成功執行后才會調用。
在afterCompletion方法中,可以獲取請求和響應對象,以及處理器拋出的異常對象(如果有的話),進行清理資源或者異常處理等。這個方法只有在preHandle返回true后才會調用,無論處理器是否成功執行。
在SpringMVC的配置文件中,注冊攔截器類,并指定攔截的url模式。可以注冊多個攔截器,并指定順序。攔截器會按照順序執行preHandle方法,然后按照逆序執行postHandle和afterCompletion方法。
HandlerInterceptor和Filter的區別
HandlerInterceptor是基于Java反射機制的,而Filter是基于函數回調的。HandlerInterceptor可以利用Spring的AOP技術,實現更靈活的攔截邏輯,而Filter只能在請求前后進行簡單的處理。
HandlerInterceptor不依賴于Servlet容器,而Filter依賴于Servlet容器。HandlerInterceptor是SpringMVC框架提供的,可以在任何情況下使用,而Filter是Servlet規范的一部分,只能在Web應用中使用。
HandlerInterceptor的執行由SpringMVC框架控制,而Filter的執行由Servlet容器控制。HandlerInterceptor可以通過IoC容器來管理,可以注入其他的Bean,而Filter則需要在web.xml中配置,或者使用@WebFilter注解,并且需要@ServletComponentScan掃描。
HandlerInterceptor只能攔截DispatcherServlet處理的請求,而Filter可以攔截任何請求。HandlerInterceptor只能對Controller方法進行攔截,而Filter可以對靜態資源、JSP頁面等進行攔截。
HandlerInterceptor有三個方法:preHandle,postHandle和afterCompletion,分別在請求處理前后和視圖渲染前后執行,而Filter只有一個方法:doFilter,在請求處理前后執行。
對于被controller方法,使用的適配器是RequestMappingHandlerAdapter,在handlerAdapter.handle方法執行時,會去執行對應的controller方法,處理controller方法返回的結果。
invocableMethod.invokeAndHandle(webRequest, mavContainer);
ServletInvocableHandlerMethod.invokeAndHandle
// 執行controller方法 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ... // 處理返回數據,會判斷是不是有@ResponseBody注解,如果有,會使用RequestResponseBodyMethodProcessor來處理返回值 // 然后會解析請求頭等等,判斷應該返回什么類型的數據,然后使用對應的HttpMessageConverter寫入輸出流 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
SpringBoot使用嵌入式Servlet容器啟動應用,有Tomcat,Jetty,Undertow。
SpringBoot默認使用Tomcat,可以在配置文件中看出。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
web模塊自動引入了tomcat
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency>
如果不使用Tomcat可以排除,引入其他服務器。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 剔除Tomcat --> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!-- 使用jetty --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
如果沒有排除Tomcat,直接引入其他服務器,比如下面。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 沒有排除Tomcat --> </dependency> <!-- 引入jetty --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
如果項目中同時引入了Tomcat和其他服務器的依賴,那么SpringBoot會按照以下順序來選擇啟動的服務器。
Tomcat > Jetty > Undertow
也就是說,如果有Tomcat,就優先使用Tomcat,如果沒有Tomcat,就看有沒有Jetty,如果有Jetty,就使用Jetty,以此類推。這個順序是在SpringBoot的ServletWebServerFactoryConfiguration類中定義的。
// 只展示必要代碼 class ServletWebServerFactoryConfiguration { // 當Servlet、Tomcat、UpgradeProtocol類在類路徑存在時 // 并且ServletWebServerFactory類存在,則會創建tomcatServletWebServerFactory bean。 @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) static class EmbeddedTomcat { @Bean TomcatServletWebServerFactory tomcatServletWebServerFactory( ... 代碼省略 } } // 當Servlet、Server、WebAppContext類在類路徑存在時 // 并且ServletWebServerFactory類型的Bean不存在時,則會創建JettyServletWebServerFactory bean。 // ServletWebServerFactory是TomcatServletWebServerFactory、JettyServletWebServerFactory、 // UndertowServletWebServerFactory的父類 // 所以如果Tomcat被引入,上面的tomcatServletWebServerFactory就會被創建,這里的條件就不滿足,不會被創建。 @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) static class EmbeddedJetty { @Bean JettyServletWebServerFactory JettyServletWebServerFactory( ... 代碼省略 } } // 分析同上 @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) static class EmbeddedUndertow { @Bean UndertowServletWebServerFactory undertowServletWebServerFactory( ... 代碼省略 } }
下面繼續以Tomcat為例
Tomcat是在Spring容器啟動的時候啟動的
SpringApplication.run
方法
首先創建一個ConfigurableApplicationContext對象,并調用其refresh()方法,這個對象一般是AnnotationConfigServletWebServerApplicationContext。
context = createApplicationContext(); -> refreshContext(context); -> refresh(context); -> applicationContext.refresh();
refresh()方法會調用其父類ServletWebServerApplicationContext的refresh()方法,在父類的refresh()中再次調用父類AbstractApplicationContext的refresh()方法,主要在onRefresh階段,會進行服務器的配置。
... refresh()代碼簡略 // 這里會初始化Tomcat配置 onRefresh(); // 這里會啟動Tomcat finishRefresh(); ...
回到ServletWebServerApplicationContext類的onRefresh()方法,會調用createWebServer()方法,創建web服務器。
protected void onRefresh() { super.onRefresh(); try { // 創建服務器 createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
private void createWebServer() { ... 代碼簡略 // 獲取工廠類,這里獲取的就是在配置類中生效的那一個,這里為TomcatServletWebServerFactory ServletWebServerFactory factory = getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); // 獲取服務器 this.webServer = factory.getWebServer(getSelfInitializer()); }
TomcatServletWebServerFactory.getWebServer
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()); for (LifecycleListener listener : this.serverLifecycleListeners) { tomcat.getServer().addLifecycleListener(listener); } // 設置Connector,對應與Tomcat Server.xml 中的<Connector></Connector> Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); // 對應于Server.xml 中 // <Service name="Catalina"> // <Connector port="8080" protocol="HTTP/1.1" // connectionTimeout="20000" // redirectPort="8443" relaxedQueryChars="[|]"/> // </Service> 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); } // 準備好Context組件 prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); }
// 創建Tomcat服務器 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); }
至此,Tomcat配置已經初始化完成,準備啟動。
在finishRefresh()方法中,會啟動Tomcat
getLifecycleProcessor().onRefresh(); > DefaultLifecycleProcessor.startBeans(true); > LifecycleGroup::start > doStart(this.lifecycleBeans, member.name, this.autoStartupOnly); > bean.start(); > WebServerStartStopLifecycle.start > TomcatWebServer.start();
private void startBeans(boolean autoStartupOnly) { Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans(); Map<Integer, LifecycleGroup> phases = new TreeMap<>(); lifecycleBeans.forEach((beanName, bean) -> { if (!autoStartupOnly || (bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup())) { int phase = getPhase(bean); phases.computeIfAbsent( phase, p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly) ).add(beanName, bean); } }); if (!phases.isEmpty()) { phases.values().forEach(LifecycleGroup::start); } }
public void start() { this.webServer.start(); this.running = true; this.applicationContext .publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext)); }
在prepareContext方法中,有一個方法configureContext
configureContext(context, initializersToUse);
configureContext方法,在這里面創建了一個TomcatStarter對象,這個類實現了ServletContainerInitializer接口,所以在容器啟動過程中會被調用。
TomcatStarter starter = new TomcatStarter(initializers); context.addServletContainerInitializer(starter, NO_CLASSES);
initializers是Spring自己定義的初始化接口ServletContextInitializer,傳入TomcatStarter之后,在onStartup方法中循環調用onStartup方法。
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } ... }
需要注意的是,這里的initializers有些傳過來的時候是一個函數式接口,在上面的factory.getWebServer(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); } }
這里遍歷所有的ServletContextInitializer,然后調用它的onStartup方法。
其中有一個DispatcherServletRegistrationBean,這個類實現了ServletContextInitializer接口,主要是用來添加DispatchServlet。
DispatcherServletAutoConfiguration配置類中有DispatcherServlet,DispatcherServletRegistrationBean兩個Bean。
protected static class DispatcherServletRegistrationConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { // 創建DispatcherServletRegistrationBean,并把dispatcherServlet傳進去 DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } } protected static class DispatcherServletConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { // 創建DispatcherServlet DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; } }
ServletContextInitializer.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是一個抽象方法,由子類DynamicRegistrationBean實現 register(description, servletContext); } protected abstract void register(String description, ServletContext servletContext);
DynamicRegistrationBean.register
protected final void register(String description, ServletContext servletContext) { // addRegistration是一個抽象方法,由子類ServletRegistrationBean實現 D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } // Servlet被添加到Context后,這里對Servlet進行配置,如攔截路徑 configure(registration); } protected abstract D addRegistration(String description, ServletContext servletContext);
ServletRegistrationBean.addRegistration,作用類似下面
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet>
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); // 添加Servlet到Context中,這里的servlet就是DispatchServlet。 return servletContext.addServlet(name, this.servlet); }
ServletRegistrationBean.configure,作用類似下面
<servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
protected void configure(ServletRegistration.Dynamic registration) { super.configure(registration); String[] urlMapping = StringUtils.toStringArray(this.urlMappings); if (urlMapping.length == 0 && this.alwaysMapUrl) { // DEFAULT_MAPPINGS默是“/” urlMapping = DEFAULT_MAPPINGS; } if (!ObjectUtils.isEmpty(urlMapping)) { // 設置mapping registration.addMapping(urlMapping); } registration.setLoadOnStartup(this.loadOnStartup); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } }
至此,DispatchServlet已配置好,后續流程和web.xml配置調用流程基本相同。
protected WebApplicationContext initWebApplicationContext() { // 此處獲取根容器,就是Spring初始化的XmlWebApplicationContext, // 在上面把它添加到了ServletContext的屬性中,標記根容器,這里把它獲取出來 // String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; // servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析============ // 同樣是獲取根容器,不過一般為AnnotationConfigServletWebServerApplicationContext WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // 此時webApplicationContext還是null,因為DispatchServlet是被tomcat創建的,需要無參構造器 // 構造器中沒有設置webApplicationContext的代碼,所以此時webApplicationContext還是null // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析============ // 注意:在SpringBoot使用嵌入式Tomcat時,這個webApplicationContext不為null,因為FrameworkServlet還 // 實現了ApplicationContextAware接口,所以當SpringBoot的上下文準備好之后,會回調setApplicationContext方法 // 注入ApplicationContext,后面在細說 if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id // 此處主要是獲取web.xml配置的WebApplicationContext // 可以通過設置參數contextAttribute來設置加載SpringMVC的ApplicationContext // 比如下面這樣。除非項目中有多個WebApplicationContext,需要使用其他WebApplicationContext才會用到 // 一般都是null // <context-param> // <param-name>contextAttribute</param-name> // <param-value>myWebApplicationContext</param-value> // </context-param> // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析 // 因為wac此時不為null,這里不會進入 wac = findWebApplicationContext(); } if (wac == null) { // 現在進入到創建SpringMVC的ApplicationContext流程 // 也就是加載contextConfigLocation定義的xml文件 // ===========上面為使用web.xml時的分析,下面為SpringBoot嵌入式Tomcat分析 // 因為wac此時不為null,這里不會進入,所以沒有SpringMVC的容器,也就是沒有父子容器之分,SpringBoot項目中只有一個容器 // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. synchronized (this.onRefreshMonitor) { // 初始化策略對象 // 比如:HandlerMapping,HandlerAdapter,ViewResolver等等 onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
感謝各位的閱讀,以上就是“web.xml SpringBoot打包可執行Jar運行SpringMVC的方法是什么”的內容了,經過本文的學習后,相信大家對web.xml SpringBoot打包可執行Jar運行SpringMVC的方法是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。