該案例的github地址:https://github.com/zhouyanger/demo/tree/master/springmvc-noxml-demo
1.介紹
之前搭建SpringMvc項目要配置一系列的配置文件,比如web.xml,applicationContext.xml,dispatcher.xml。Spring 3.X之后推出了基於JavaConfig方式以及注解的形式的配置。在一定程度上簡化了Spring項目的配置。近幾年特別火的SpringBoot,大大的簡化了創建項目,基本不需要配置配置文件,就可以快速的創建一個項目。其中一個重要的原因就是采用JavaConfig和注解幫我們做了很多配置的事。今天演示下如何通過JavaConfig和注解方式快速創建一個Springmvc項目,為以后深入學習SpringBoot打點基礎。
(1) Spring MVC對 request 做了什么?
當一個 Request 離開瀏覽器(下圖標號1)時,攜帶了用戶的請求信息,包括url以及表單等,它到達的第一站就是 DispatcherServlet 。像大多數基於 Java 的 web 框架一樣,Spring MVC 首先通過一個前端控制器處理,把 Request 交給其他組件處理(類似於前台姐姐把客戶帶到要去的各種部門),在 Spring MVC 里面,充當前端控制器這一角色的就是 DispatcherServlet 。
DispatcherServlet 目的是把 Request 送到其他的 Controller,對 Request 作進一步處理,但是一般情況下程序中會有很多的 Controller,它們各自負責處理不同的 Request ,於是 DispatcherServlet 需要一些協助來決定一個 Request 應該送到哪個 Controller 。因此 DispatcherServlet 會詢問 Handler mappings (上圖標號2),一個 Request 應該何去何從,Handler mappings 謝邀之后不敢怠慢,通過驗看 Request 攜帶的 URL 進行抉擇。
Handler mappings 的結果出來之后,DispatcherServlet 歡天喜地地把 Request 送給對應的 Controller (如上圖標號3)。到達之后,Request 卸下重擔(攜帶着的由用戶提交的信息)並耐心地等待 Controller 處理這些信息(而實際上,一個機智的Controller 自己幾乎不做任何處理,而是把活兒交給其他負責業務邏輯的 service 對象)。
Controller 處理完后得到的結果往往需要返回給用戶並在瀏覽器中顯示,這個結果被稱作 model, 它比較粗糙,需要補充額外信息轉換成用戶友好型的格式,例如 HTML 。出於這個目的呢,這一 model 需要交給一個 view , 典型的有 JavaServer Page (JSP)。
所以,Controller 最后需要做的就是把 model 數據打包並且確定一個到時候用來渲染這個 model 的 view,然后把 Request、model 數據以及 view 的名字一塊發送回 DispatcherServlet(如上圖標號4)。
因此 Controller 實現了與特定 view 的解耦, DispatcherServlet 拿到的 view 名也並不直接確定這個 view 是 JSP,DispatcherServlet 只知道憑借這一 view 名找到的 view 可以處理手頭上的 model 數據,於是它把 view 名給 view resolver 來幫它找到對應的 view(如上圖標號5) 。
DispatcherServlet 終於得到了渲染 model 數據的 view,Request 的旅程也即將走到盡頭, 它的最后一站是在 view 的實現部分(上圖標號6),而view 的實現典型的有 JSP ;這一步使用 model 數據進行輸出結果的渲染,之后通過響應對象把渲染結果送回到客戶端。
(2) Spring MVC 入門工程實現的准備工作;
需要的工具
Eclipse IDE for Java EE Developers ;
Apache Tomcat ;
Spring framework jar包 ;
以及其他一些依賴包:commons-logging-1.1.3.jar,jstl-1.2.jar,log4j-1.2.17.jar
建立 maven 工程
完了之后,導入相應的mvcjar包,這和之前的xml配置的jar包一樣
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zy</groupId> <artifactId>springmvc-noxml-demo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>springmvc-noxml-demo Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <!--spring 版本號--> <spring.version>5.1.6.RELEASE</spring.version> <!--mybatis 版本號--> <mybatis.version>3.2.4</mybatis.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--上邊介紹的的版本號等等 將下面的代碼放到dependencies標簽中即可--> <!--spring 核心包--> <!-- spring start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!-- spring end --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--日志--> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.8.0-alpha0</version> <scope>test</scope> </dependency> <!--j2ee相關包 servlet、jsp、jstl--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <!--mybatis核心包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <!--mybatis/spring 包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency> <!--MySQL 驅動包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency> </dependencies> <build> <finalName>springmvc-noxml-demo</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
這一切做完應該就可以開始寫代碼了…
(3) Spring MVC的Java配置;
經過開頭那幅圖的一通介紹,看起來 Spring MVC 需要的配置會很復雜…相對來說,以前確實很復雜(用 xml 配置),然而現在只需簡單幾步就能搞定。
配置 DispatcherServlet
DispatcherServlet 是 Spring MVC 的核心組件,它是一個 request 首先到達的地方,負責 request 在其他各個組件間的傳遞加工,在過去,像DispatcherServlet 這樣的 servlets 是使用 web.xml 文件配置的,當然現在也可以這樣做….但是由於時代的進步,基於 Servlet 3 和 Spring 3.1 的一些新特性,我們可以用更簡單的方式來配置,即使用 Java 代碼。
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 指定spring容器的配置類 * @return */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{MySpringConfig.class}; } /** * springmvc容器的配置類 * @return */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{MyServletConfig.class}; } /** * 配置攔截請求路徑 * @return */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
或許建工程的時候你就很在意 spittr 是干嘛的,但還是那句話,下文有介紹…先看代碼…代碼里是一個叫做 SpittrWebAppInitializer 的類,在spittr.config包里,顧名思義,它是起一個初始化配置的作用,並且相當於一個程序的入口類,在整個程序啟動時加載。
首先需要介紹的就是這個名字很拉風的類,即被 SpittrWebAppInitializer 繼承的 AbstractAnnotationConfigDispatcherServletInitializer ,乍看一頭霧水…..哥們你干嘛的….簡單來說,它自動被加載,負責應用程序中 servlet 上下文中的 DispatcherServlet 和 Spring 其他上下文的配置。
關於 AbstractAnnotationConfigDispatcherServletInitializer
各位好,如果以上介紹不過癮,這里是復雜來說…
在 Servlet 3.0 環境下,Servlet 容器會在 classpath 下搜索實現了 javax.servlet
.ServletContainerInitializer 接口的任何類,找到之后用它來初始化 Servlet 容器。Spring 實現了以上接口,實現類叫做 SpringServletContainerInitializer, 它會依次搜尋實現了 WebApplicationInitializer的任何類,並委派這個類實現配置。之后,Spring 3.2 開始引入一個簡易的 WebApplicationInitializer 實現類,這就是 AbstractAnnotationConfigDispatcherServletInitializer。
所以 SpittrWebAppInitializer 繼承 AbstractAnnotationConfigDispatcherServletInitializer之后,也就是間接實現了 WebApplicationInitializer,在 Servlet 3.0 容器中,它會被自動搜索到,被用來配置 servlet 上下文。
當然可以直接實現 WebApplicationInitializer,但作為入門來說,AbstractAnnotationConfigDispatcherServletInitializer 是一個不錯的選擇。
回到代碼,SpittrWebAppInitializer 需要重寫3個方法,第一個,getServletMappings(),為 DispatcherServlet 提供一個或更多的Servlet 映射;這里是被映射到 /,指示它為默認的 servlet,用來操作所有來到程序的 Request。為了理解另外兩個方法的作用,需要先明白 DispatcherServlet 和一個叫做 ContextLoaderListener 的 servlet 監聽器之間的關系。
A TALE OF TWO APPLICATION CONTEXTS1
DispatcherServlet 開始啟動時,會產生一個 Spring 應用程序上下文,把它和配置文件中聲明的 bean 或者類一起加載進來。通過getServletConfigClasses() 方法,設置 DispatcherServlet 通過 WebConfig 配置類來完成 Spring 上下文和 bean 的加載。
但是在 Spring web 程序中,往往還有另外一個應用程序上下文,它是由 ContextLoaderListener 產生的。通過調用 getRootConfigClasses()方法返回的類就是用來配置 ContextLoaderListener 產生的上下文。
其中,DispatcherServlet 是用來加載涉及 web 功能的 beans,例如 controllers, view resolvers, 和 handler mappings;而 ContextLoaderListener 則是用來載入程序中其余的 beans,例如一些中間層和數據層組件,完成的是程序后端功能。
於是,我們知道,AbstractAnnotationConfigDispatcherServletInitializer 產生了一個 DispatcherServlet 和一個 ContextLoaderListener,以上代碼清單1通過兩個方法分別得到兩個上下文的配置類(使用@Configuration注解)。和 web.xml 的配置方法不一樣的是,這種方法只適用於使用了 Servlet 3.0 的服務器,例如 Apache Tomcat 7 或 7+。不過 Servlet 3.0 規范在 2009 年 12 月形成最終版本, 幾乎不用擔心遇到不支持 Servlet 3.0 的 servlet 容器 。
但是,如果你的服務器實在是不能支持 Servlet 3.0,那么 Java 配置的方法不適用於你了,你適合使用 web.xml 的配置方法,在原作者書中,第七章有介紹,不過本文不討論這種方法,因為這種方法已經很成熟…網上一搜一大堆,請自行搜索….
啟用 Spring MVC 功能
基於 Java 的方式超級簡單,你的配置類只需要帶有一個@EnableWebMvc注解,就像這樣子
/** * 有關springmvc的配置都在這里面,可以重寫其很多方法,比如攔截器,編碼等 */ @Configuration @EnableWebMvc @ComponentScan("com.zy.controller") public class MyServletConfig implements WebMvcConfigurer { //配置視圖解析器 @Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } }
@EnableWebMvc引入了這樣一個類DelegatingWebMvcConfiguration,通過@Bean注冊了和<mvc:annotation-driven/>一樣的組件,RequestMappingHandlerMapping、RequestMappingHandlerAdatper、HandlerExceptionResolver等等,只要有個Spring管理的bean繼承WebMvcConfigurer或WebMvcConfigurerAdapter,重寫方法即可自定義<mvc:annotation-driven/>.
其實@EnableWebMvc == @Import({DelegatingWebMvcConfiguration.class})
是不是很簡潔?這樣就可以開始使用 Spring MVC 框架了,但是各種組件還沒配置:
-
View Resolver:雖然 Spring 會默認使用 BeanNameViewResolver,它會搜索 ID 和 view 名一致並且實現了 view 接口的 bean。
-
Controller:要讓 Spring 能夠加載各種 Controllers,必須對它們進行顯式聲明,然后啟用 Component-scanning 功能自動搜索。
-
完善 Request 的映射: 按照之前的配置,對於一些靜態資源(圖片、樣式表等)也是映射到默認的 Servlet,而這顯然是不合理的。
首先可以發現的是,代碼中添加了@ComponentScan注解,因此spitter.web 包將會被掃描以搜索 Controller。
然后,代碼添加了一個 ViewResolver bean, 具體來說是一個 InternalResourceViewResolver,再具體的話這里不再討論,請查閱其他資料….或參看原書第六章,就這里而言,只需要知道它是用來查找 view(JSP)的,並且會對 view 的名字使用前綴和后綴進行包裹就行了 ( 舉個栗子,在上述代碼中,名字為 home 的 view 將被轉換為 /WEB-INF/views/home.jsp )。
最后, WebConfig 類繼承 WebMvcConfigurerAdapter 並重寫了它的 configureDefaultServletHandling() 方法,通過調用所給的DefaultServletHandlerConfigurer 對象的 enable() 方法,告訴DispatcherServlet 轉發對靜態資源的 Request 到 Servlet 容器的默認Servlet, 而不是自己處理。
搞定了 WebConfig ,接下來搞定MySpringConfig,由於這里集中討論 Spring 的 web 開發,所以簡單貼出MySpringConfig的代碼,這相當於配置application.xml,用來配置數據庫等等,不做額外解釋了:
@Configuration @ComponentScan(value = "com.zy",excludeFilters = {@Filter(type = FilterType.ANNOTATION,classes = {Controller.class})}) public class MySpringConfig { }
Spittr 的介紹
是的,終於到了這里。
之前所做的已經足夠了,現在可以開始去構思一個 web 程序的實現了,按照原作者的說法,原作者打算做一個簡易的 Twitter,於是,對 Twitter 和 Spring 各取一半就有了 Spitter,為了逼格更高一點,參考熱門的 Flickr,扔掉一個 e 就有了 Spittr。Spittr 的用戶叫做 spitters,他們發布的狀態叫做spittles,是不是很有意思?
好了,就到這里,接下來寫完一個簡單的 Controller 就收工…..
(4) 一個簡單的Controller;
在 Spring MVC中,Controller 就是類,只是類中的方法們帶有@RequestMapping注解,以表明它們各自處理什么樣的 Request。
讓我們看一個簡單的Controller示例,它用來處理目標路徑是/的 Request:
@Controller public class HelloController { //請求是/hello,返回的試圖名是index,dispatcherServlet會根據springmvc的配置去找WEB-INF/views/index.jsp頁面 @GetMapping("/hello") public String hello(){ return "index"; } }
第一眼你注意到的應該就是這個@Controller注解,顯然它是用來聲明一個 Controller,然而它和 Spring MVC 卻沒有什么關系,它的功能和 @Component 一致,都是便於component-scanning 能夠查找到它所注解的類。唯一不用 @Component 的原因是,它的表現力沒有這么強,你不會一眼就知道這個它所標注的類是在起到一個 Controller 的作用。
HomeController 唯一的方法是 hello(), 使用了@RequestMapping注解,其中 value 屬性指定了方法所操作的 Request 路徑,即“/”;method 屬性則說明了它所接受的HTTP訪問方式。
每當一個 HTTP GET Request 訪問 / 路徑的時候,home() 方法就會被調用:返回一個“hello”的String字符串。接着 Spring MVC 將“hello”作為view 名,之前說過,DispatcherServlet 會找 view resolver ,把邏輯上的 view 名映射到一個實際的 view。
按照已配置的 InternalResourceViewResolver,“home”將被映射到 /WEB-INF/views/hello.jsp,所以接下來的工作就是 hello.jsp 的編寫:
<html> <body> <h2>Hello World!No web.xml</h2> </body> </html>
最后測試:在瀏覽器中輸入,http://localhost:8080/hello
總結:java配置的更簡潔,取代了web.xml的繁瑣,但是注意這是必要要servlet3.0之后的才能使用。
該案例的github地址:https://github.com/zhouyanger/demo/tree/master/springmvc-noxml-demo