在Servlet 3.0環境中,容器(加載運行webapp的軟件,如Tomcat)會在類路徑中查找實現==javax.servlet.ServletContainerInitializer==接口的類(這一行爲本質上是Java EE標準和協定所要求的,Tomcat是基於該協定的一種實現),若是能發現的話,就會用它來配置Servlet容器。html
Spring提供了這個接口的實現,名爲SpringServletContainerInitializer,所以一個引入的SringMVC的web項目在沒有其它設置的狀況下會被Tomcat找到SpringServletContainerInitializer。java
SpringServletContainerInitializerreact
==SpringServletContainerInitializer==又會查找實現==WebApplicationInitializer==接口的類並調用其onStartup(ServletContext servletContext)
方法,其中ServletContext對象由其負責將服務器生成的惟一的ServletContext實例傳入。git
WebApplicationInitializergithub
Interface to be implemented in Servlet 3.0+ environments in order to configure theServletContext
programmatically -- as opposed to (or possibly in conjunction with) the traditionalweb.xml
-based approach.
ServletContext web
Defines a set of methods that a servlet uses to communicate with its servlet container, for example,算法
到目前位置,咱們已經可使用SpringMVC來增設Servlet了,雖然這看起來並不美觀也不簡便。代碼以下所示。spring
package spittr.config; import org.springframework.web.WebApplicationInitializer; import spittr.web.AServlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; public class SpittrWebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { //增長一個Servelt 其中AServlet是Servlet接口的實現類,個人實現直接繼承了HttpServlet ServletRegistration.Dynamic aServlet = servletContext.addServlet("AServlet", AServlet.class); //爲AServlet增設映射路徑,其做用等同於@WebServlet(urlPatterns={"/AServlet"}) aServlet.addMapping(new String[]{"/AServlet"}); } }
package spittr.web; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.write("我收到了你的GET"); } }
如今咱們能夠向瀏覽器直接訪問AServletapi
然而,這樣的實如今美觀和便利上遠遠不如使用Servlet3.0引入和更新的@WebServlet等機制。瀏覽器
而且徹底沒有涉及Spring和Spring MVC,只是按照Servlet3.0的標準的一種添加Servlet的方式罷了。
那麼接下來咱們就要開始引入Spring和Spring MVC了。
第一步確定是引入Spring,也即引入一個Spring的容器。
這很簡單,在onStartup中實例化一個ApplicationContext的實例便可。查詢ApplicationContext的javadoc,看到目前全部的ApplicationContext實現類:
All Known Implementing Classes:
AbstractApplicationContext, AbstractRefreshableApplicationContext, AbstractRefreshableConfigApplicationContext, AbstractRefreshableWebApplicationContext, AbstractXmlApplicationContext, AnnotationConfigApplicationContext, AnnotationConfigWebApplicationContext, ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, GenericApplicationContext, GenericGroovyApplicationContext, GenericWebApplicationContext, GenericXmlApplicationContext, GroovyWebApplicationContext, ResourceAdapterApplicationContext, StaticApplicationContext, StaticWebApplicationContext, XmlWebApplicationContext
而咱們打算使用基於Java代碼的配置並開啓基於註解的自動掃描,同時應用場景爲webapp,因此應該使用AnnotationConfigWebApplicationContext
實現類。
綜上所述,能夠獲得以下代碼:
package spittr.config; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.web.WebApplicationInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; public class SpittrWebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(AppConfig.class); } }
@Configuration @ComponentScan("spittr.web") public class AppConfig { }
至此,咱們已經在這個webapp中集成了Spring容器,從理論上講,咱們應該能夠對一個Servlet標註@Controller後使其自動被註冊和使用。可是因爲@RequestMapping咱們還不知道能不能用,實際上沒法對其進行測試(由於即使將服務器註冊到了Spring容器中,咱們也沒法爲它配置映射路徑)。
那麼如今就該去解決@RequestMapping了。
@RequestMapping javadoc這一註解作了以下解讀
Annotation for mapping web requests onto methods in request-handling classes with flexible method signatures.
Both Spring MVC and Spring WebFlux support this annotation through a
RequestMappingHandlerMapping
andRequestMappingHandlerAdapter
in their respective modules and package structure. For the exact list of supported handler method arguments and return types in each, please use the reference documentation links below:
- Spring MVC Method Arguments and Return Values
- Spring WebFlux Method Arguments and Return Values
Note: This annotation can be used both at the class and at the method level. In most cases, at the method level applications will prefer to use one of the HTTP method specific variants
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
, or@PatchMapping
.NOTE: When using controller interfaces (e.g. for AOP proxying), make sure to consistently put all your mapping annotations - such as
@RequestMapping
and@SessionAttributes
- on the controller interface rather than on the implementation class.
其中最重要的在第二段,它說明了Spring MVC經過使用RequestMappingHandlerMapping
和RequestMappingHandlerAdapter
得以支持@RequestMappin註解。
javadoc:RequestMappingHandlerMapping
javadoc:RequestMappingHandlerAdapter
能夠發現,這兩個類都是能夠被實例化的,且構造器不須要參數。
既然如此,咱們能夠試着在AppConfig中配置這兩個類。
@Configuration @ComponentScan("spittr.web") public class AppConfig { @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){ return new RequestMappingHandlerAdapter(); } @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping(){ return new RequestMappingHandlerMapping(); } }
而後使用帶@Controller和@RequestMapping的類
package spittr.web; @Controller @RequestMapping("/BServlet") public class BServlet{ @RequestMapping(method = RequestMethod.GET) public void doGet() { System.out.println("BServlet:我收到了你的GET"); } }
不過測試結果是糟糕的,咱們沒有如願實現訪問BServlet。
失敗的緣由沒有官方文檔直接告知,但結合以後進一步的學習,不難猜想理由應該是:咱們AppConfig的Spring-beans容器其實沒有和Servlet容器結合起來。咱們只是在onStartUp
方法中實例化了一個Spring-beans容器,甚至能夠認爲在方法的生命週期結束以後,這個實例就直接沒了。如若真的如此,咱們就連實際上把Spring集成到這個WebApp中都沒有作到,怎麼可能作到開啓Spring MVC註解呢。
事已至此,就只能閱讀官方文檔了。官方文檔
開門見山地:
Spring MVC, as many other web frameworks, is designed around the front controller pattern where a centralServlet
, theDispatcherServlet
, provides a shared algorithm for request processing, while actual work is performed by configurable delegate components. This model is flexible and supports diverse workflows.
→Spring MVC圍繞一個前線控制器模式(front controller pattern)而設計,在這種模式下一個核心Servlet,也就是DispatchereServlet(由Spring實現的Servlet類),會爲處理客戶端請求提供了算法,而真正的工做(處理請求)由可配置的代理組件來執行。
所以能夠認爲,要充分利用SpringMVC,必然要加載SpringMVC自行實現的Servlet類:org.springframework.web.servlet.DispatcherServlet
。
org.springframework.web.servlet.DispatcherServlet
官方文檔給出了一段初始化代碼:
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletCxt) { // Load Spring web application configuration AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); //AppConfig是自定義的帶@Configuration註解的類 ac.register(AppConfig.class); ac.refresh(); // Create and register the DispatcherServlet // 將Spring容器與DispatcherServlet綁定 DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); } }
這段代碼的前半部分,咱們是很熟悉的。第三章就作過。
這段代碼的後半部分其實沒有什麼新意,但下半部分的第一行很是關鍵
DispatcherServlet servlet = new DispatcherServlet(ac);
接受一個AnnotationConfigWebApplicationContext
做爲構造器參數!這實際上解決了咱們在第四章測試失敗後反思的可能的疑惑——咱們配置的Spring容器實際上並無和tomcat融合起來。
那麼如今,將官方代碼中的ac換成咱們本身的,是否是就能成功了呢?不妨一試
public class SpittrWebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); ac.register(AppConfig.class); DispatcherServlet dispatcher = new DispatcherServlet(ac); ServletRegistration.Dynamic d = servletContext.addServlet("dispatcher", dispatcher); d.setLoadOnStartup(1); d.addMapping("/"); } } /* AppConfig BServlet 相較以前徹底沒有變化,因此不展現 */
結果是喜人的,咱們嘗試成功了。能夠看到輸出BServlet:我收到了你的GET
官方文檔進一步說明:
TheDispatcherServlet
, as anyServlet
, needs to be declared and mapped according to the Servlet specification by using Java configuration or inweb.xml
. In turn, theDispatcherServlet
uses Spring configuration to discover the delegate components it needs for request mapping, view resolution, exception handling, and more.The following example of the Java configuration registers and initializes the
DispatcherServlet
, which is auto-detected by the Servlet container (see Servlet Config):
這段話應該分紅這兩個部分:
TheDispatcherServlet
, as anyServlet
, needs to be declared and mapped according to the Servlet specification by using Java configuration or inweb.xml
.The following example of the Java configuration registers and initializes theDispatcherServlet
, which is auto-detected by the Servlet container (see Servlet Config):
這一部分上來先說,DispatcherServlet就像任何Servlet同樣,也是須要作好聲明和映射的。下面的代碼介紹了使用Servlet container提供的自動探測註冊功能來註冊和初始化DispatcherServlet。這裏所謂的Servlet container的自動探測,其實就是指以前提到的1,2兩個階段。
In turn, the
DispatcherServlet
uses Spring configuration to discover the delegate components it needs for request mapping, view resolution, exception handling,
and more.
這一部分說,DispatcherServlet被配置註冊好以後,也能夠反過來使用Spring配置來發現和委派爲它爲請求映射,視圖渲染,異常處理所須要的組件。
那麼,DispatcherServlet要如何反過來配置它本身的組件呢?帶着這一疑問,咱們繼續往下看。
官方文檔緊接着提到了一個WebApplicationInitializer
的Spring實現類AbstractAnnotationConfigDispatcherServletInitializer
,它能夠避免直接使用ServletContext(它本身已經用了),經過重寫特定的方法完成配置。
In addition to using the ServletContext API directly, you can also extend
AbstractAnnotationConfigDispatcherServletInitializer
and override specific methods (see the example under
Context Hierarchy).
跟隨Context Hierarchy超連接一探究竟。先放上example code:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { App1Config.class }; } @Override protected String[] getServletMappings() { return new String[] { "/app1/*" }; } }
再看文字說明
DispatcherServlet
expects aWebApplicationContext
(an extension of a plainApplicationContext
) for its own configuration.
DispatcherServlet爲它本身的配置須要一個WebApplicationContext(ApplicationContext的子接口)即一個Spring容器的配置實現類。
WebApplicationContext
has a link to theServletContext
and theServlet
with which it is associated. It is also bound to theServletContext
such that applications can use static methods onRequestContextUtils
to look up theWebApplicationContext
if they need access to it.
一個Spring容器與ServletContext和與它共生的Servlet又關聯。這個Spring容器由於綁定ServletContext,因此也能夠經過類RequestContextUtils的靜態方法去獲得。
For many applications, having a singleWebApplicationContext
is simple and suffices. It is also possible to have a context hierarchy where one rootWebApplicationContext
is shared across multipleDispatcherServlet
(or otherServlet
) instances, each with its own childWebApplicationContext
configuration. See Additional Capabilities of theApplicationContext
for more on the context hierarchy feature.
絕大部分應用來講,一個Spring容器就夠用了。但也能夠有一個有層級的容器結構——一個根Spring容器在多個(所有)Servlet實例中共享,同時每一個Servlet實例也有本身的WebApplicationContext配置。
Java EE和Servlet3.0標準的Servlet接口實際上是不支持Servlet實例共生一個ApplicationContext的,由於後者畢竟是Spring的專屬。因此這裏的Servlet實例考慮爲像DispatcherServlet這樣由Spring實現並提供的類,而不包括用戶自定義的符合Java EE和Servlet3.0標準的Servlet接口的Servlet。
The rootWebApplicationContext
typically contains infrastructure beans, such as data repositories and business services that need to be shared across multipleServlet
instances. Those beans are effectively inherited and can be overridden (that is, re-declared) in the Servlet-specific childWebApplicationContext
, which typically contains beans local to the givenServlet
.
在層級話的Spring容器結構中,根Spring容器一般包含基礎設施的組件,好比數據持久化層,商業服務層這種須要在各類Servlet中共享的組件。這些組件可以被有效地繼承地同時,也能夠被在Servlet相關的子Spring容器中被從新配置,使得組件能夠針對給定的Servlet因地制宜。
到這裏再回看代碼。
protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; }
顯然,這裏的RootConfig.class是用戶自定義的帶@Configuration註解的Spring容器配置類,用以實現根Spring容器。
@Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { App1Config.class }; }
這個就是AbstractAnnotationConfigDispatcherServletInitializer
默認實現的那個DispatcherServlet
的伴生Spring容器配置。
protected String[] getServletMappings() { return new String[] { "/app1/*" }; }
這個則是肯定AbstractAnnotationConfigDispatcherServletInitializer
默認實現的那個DispatcherServlet
所要管理的request URI映射。
至此1.1.1 Context Hierarchy結束,咱們以前就是根據超連接跳到這一章節的,這一章節結束後,咱們返回以前的位置繼續閱讀文檔。
發現緊接着就又是1.1.1 Context Hierarchy,直接跳過讀下一章。
1.1.2. Special Bean Types
The
DispatcherServlet
delegates special beans to process requests and render the appropriate responses. By 「special beans」 we mean Spring-managedObject
instances that implement framework contracts. Those usually come with built-in contracts, but you can customize their properties and extend or replace them.1.1.3. Web MVC Config
Applications can declare the infrastructure beans listed in Special Bean Types that are required to process requests. The
DispatcherServlet
checks theWebApplicationContext
for each special bean. If there are no matching bean types, it falls back on the default types listed inDispatcherServlet.properties
.In most cases, the MVC Config is the best starting point. It declares the required beans in either Java or XML and provides a higher-level configuration callback API to customize it.
這兩個部分回答了咱們的問題——DispatcherServlet要如何反過來配置它本身的組件——DispatcherServlet將會搜索它能夠訪問的WebApplicationContext(這包括根Spring容器和它本身伴生的子Spring容器)來查找每一個special bean——即被委派來處理請求渲染迴應等工做的組件——的設置。若是沒有的話,它將使用默認的,保存在DispatcherServlet.properties中的設定。
很好理解的,咱們以前所寫的AppConfig中的兩個Bean,它們是那麼的基礎——由Spring提供和實現,咱們只是new出來什麼自定義也沒有——以致於使用DispatcherServlet的默認配置也不會更糟糕。因此咱們去掉以前的AppConfig的配置,僅僅留下一個空的AppConfig,其它代碼不變。
再次測試,仍然可以收到BServlet的輸出。
到這裏,對於如何將Spring和Spring MVC集成到一個WebApp中的過程以及爲何能夠集成進來已經分析得差很少了。
更進一步得學習Spring MVC,就繼續仔細閱讀官方文檔吧!