Spring專(zhuān)題2:MVC是如何工作的

Spring MVC 的工作原理则果,分享一篇外文翻譯的技術(shù)貼蜂科。
-英文原文:原文跳轉(zhuǎn)
-譯文跳轉(zhuǎn):譯文跳轉(zhuǎn)

在譯文基礎(chǔ)上,加了一些自己的理解短条,做了些梳理。

Spring MCN工作流程示意圖解析

工作流程圖

1.瀏覽器發(fā)送請(qǐng)求到前端控制器(DispatcherServlet)才菠,DispatcherServlet請(qǐng)求處理器映射器(HandlerMappering)去查找處理器(Handle):通過(guò)xml配置或者注解進(jìn)行查找

2.HandlerMapping匹配到處理該url請(qǐng)求的Controller茸时、Interceptor(根據(jù)xml配置、注解進(jìn)行查找)返回給DispatcherServlet

3.DispatcherServlet調(diào)用Interceptor赋访、Controller進(jìn)行請(qǐng)求處理可都,Controller處理結(jié)果為ModelAndView返回給DispatcherServlet

4.DispatcherServlet調(diào)用ViewResolver渲染ModelAndView為最終的View,最終轉(zhuǎn)為response返回給用戶(hù)

WEB配置項(xiàng)

以Tomcat為例蚓耽,web容器中對(duì)spring進(jìn)行的配置有:

  • web.xml渠牲,具體配置項(xiàng)解讀如下:
<?xml version="1.0" encoding="UTF-8"?>  
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">  

    <!-- 在Spring框架中是如何解決從頁(yè)面?zhèn)鱽?lái)的字符串的編碼問(wèn)題的呢?
    下面我們來(lái)看看Spring框架給我們提供過(guò)濾器CharacterEncodingFilter  
     這個(gè)過(guò)濾器就是針對(duì)于每次瀏覽器請(qǐng)求進(jìn)行過(guò)濾的步悠,然后再其之上添加了父類(lèi)沒(méi)有的功能即處理字符編碼签杈。  
      其中encoding用來(lái)設(shè)置編碼格式,forceEncoding用來(lái)設(shè)置是否理會(huì) request.getCharacterEncoding()方法鼎兽,設(shè)置為true則強(qiáng)制覆蓋之前的編碼格式答姥。-->  
    <filter>  
        <filter-name>characterEncodingFilter</filter-name>  
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
        <init-param>  
            <param-name>encoding</param-name>  
            <param-value>UTF-8</param-value>  
        </init-param>  
        <init-param>  
            <param-name>forceEncoding</param-name>  
            <param-value>true</param-value>  
        </init-param>  
    </filter>  
    <filter-mapping>  
        <filter-name>characterEncodingFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
    <!-- 項(xiàng)目中使用Spring 時(shí),applicationContext.xml配置文件中并沒(méi)有BeanFactory谚咬,要想在業(yè)務(wù)層中的class 文件中直接引用Spring容器管理的bean可通過(guò)以下方式-->  
    <!--1鹦付、在web.xml配置監(jiān)聽(tīng)器ContextLoaderListener-->  
    <!--ContextLoaderListener的作用就是啟動(dòng)Web容器時(shí),自動(dòng)裝配ApplicationContext的配置信息择卦。因?yàn)樗鼘?shí)現(xiàn)了ServletContextListener這個(gè)接口敲长,在web.xml配置這個(gè)監(jiān)聽(tīng)器郎嫁,啟動(dòng)容器時(shí),就會(huì)默認(rèn)執(zhí)行它實(shí)現(xiàn)的方法祈噪。  
    在ContextLoaderListener中關(guān)聯(lián)了ContextLoader這個(gè)類(lèi)泽铛,所以整個(gè)加載配置過(guò)程由ContextLoader來(lái)完成。  
    它的API說(shuō)明  
    第一段說(shuō)明ContextLoader可以由 ContextLoaderListener和ContextLoaderServlet生成钳降。  
    如果查看ContextLoaderServlet的API厚宰,可以看到它也關(guān)聯(lián)了ContextLoader這個(gè)類(lèi)而且它實(shí)現(xiàn)了HttpServlet這個(gè)接口  
    第二段,ContextLoader創(chuàng)建的是 XmlWebApplicationContext這樣一個(gè)類(lèi)遂填,它實(shí)現(xiàn)的接口是WebApplicationContext->ConfigurableWebApplicationContext->ApplicationContext->  
    BeanFactory這樣一來(lái)spring中的所有bean都由這個(gè)類(lèi)來(lái)創(chuàng)建  
     IUploaddatafileManager uploadmanager = (IUploaddatafileManager)    ContextLoaderListener.getCurrentWebApplicationContext().getBean("uploadManager");
     -->  
    <listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>  
    <!--2铲觉、部署applicationContext的xml文件-->  
    <!--如果在web.xml中不寫(xiě)任何參數(shù)配置信息,默認(rèn)的路徑是"/WEB-INF/applicationContext.xml吓坚,  
    在WEB-INF目錄下創(chuàng)建的xml文件的名稱(chēng)必須是applicationContext.xml撵幽。  
    如果是要自定義文件名可以在web.xml里加入contextConfigLocation這個(gè)context參數(shù):  
    在<param-value> </param-value>里指定相應(yīng)的xml文件名,如果有多個(gè)xml文件礁击,可以寫(xiě)在一起并以“,”號(hào)分隔盐杂。  
    也可以這樣applicationContext-*.xml采用通配符,比如這那個(gè)目錄下有applicationContext-ibatis-base.xml哆窿,  
    applicationContext-action.xml链烈,applicationContext-ibatis-dao.xml等文件,都會(huì)一同被載入挚躯。  
    在ContextLoaderListener中關(guān)聯(lián)了ContextLoader這個(gè)類(lèi)强衡,所以整個(gè)加載配置過(guò)程由ContextLoader來(lái)完成。-->  
    <context-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>classpath:spring/applicationContext.xml</param-value>  
    </context-param>  

    <!--如果你的DispatcherServlet攔截"/"码荔,為了實(shí)現(xiàn)REST風(fēng)格漩勤,攔截了所有的請(qǐng)求,那么同時(shí)對(duì)*.js,*.jpg等靜態(tài)文件的訪(fǎng)問(wèn)也就被攔截了缩搅。-->  
    <!--方案一:激活Tomcat的defaultServlet來(lái)處理靜態(tài)文件-->  
    <!--要寫(xiě)在DispatcherServlet的前面越败, 讓 defaultServlet先攔截請(qǐng)求,這樣請(qǐng)求就不會(huì)進(jìn)入Spring了硼瓣,我想性能是最好的吧究飞。-->  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.css</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.swf</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.gif</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.jpg</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.png</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.js</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.html</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.xml</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.json</url-pattern>  
    </servlet-mapping>  
    <servlet-mapping>  
        <servlet-name>default</servlet-name>  
        <url-pattern>*.map</url-pattern>  
    </servlet-mapping>  
    <!--使用Spring MVC,配置DispatcherServlet是第一步。DispatcherServlet是一個(gè)Servlet,,所以可以配置多個(gè)DispatcherServlet-->  
    <!--DispatcherServlet是前置控制器堂鲤,配置在web.xml文件中的噪猾。攔截匹配的請(qǐng)求,Servlet攔截匹配規(guī)則要自已定義筑累,把攔截下來(lái)的請(qǐng)求袱蜡,依據(jù)某某規(guī)則分發(fā)到目標(biāo)Controller(我們寫(xiě)的Action)來(lái)處理。-->  
    <servlet>  
        <servlet-name>DispatcherServlet</servlet-name><!--在DispatcherServlet的初始化過(guò)程中慢宗,框架會(huì)在web應(yīng)用的 WEB-INF文件夾下尋找名為[servlet-name]-servlet.xml 的配置文件坪蚁,生成文件中定義的bean奔穿。-->  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <!--指明了配置文件的文件名,不使用默認(rèn)配置文件名敏晤,而使用dispatcher-servlet.xml配置文件贱田。-->  
        <init-param>  
            <param-name>contextConfigLocation</param-name>  
            <!--其中<param-value>**.xml</param-value> 這里可以使用多種寫(xiě)法-->  
            <!--1、不寫(xiě),使用默認(rèn)值:/WEB-INF/<servlet-name>-servlet.xml-->  
            <!--2嘴脾、<param-value>/WEB-INF/classes/dispatcher-servlet.xml</param-value>-->  
            <!--3男摧、<param-value>classpath*:dispatcher-servlet.xml</param-value>-->  
            <!--4、多個(gè)值用逗號(hào)分隔-->  
            <param-value>classpath:spring/dispatcher-servlet.xml</param-value>  
        </init-param>  
        <load-on-startup>1</load-on-startup><!--是啟動(dòng)順序译打,讓這個(gè)Servlet隨Servletp容器一起啟動(dòng)耗拓。-->  
    </servlet>  
    <servlet-mapping>  
        <!--這個(gè)Servlet的名字是dispatcher,可以有多個(gè)DispatcherServlet奏司,是通過(guò)名字來(lái)區(qū)分的乔询。每一個(gè)DispatcherServlet有自己的WebApplicationContext上下文對(duì)象。同時(shí)保存的ServletContext中和Request對(duì)象中.-->  
        <!--ApplicationContext是Spring的核心韵洋,Context我們通常解釋為上下文環(huán)境竿刁,我想用“容器”來(lái)表述它更容易理解一些,ApplicationContext則是“應(yīng)用的容器”了:P搪缨,Spring把Bean放在這個(gè)容器中食拜,在需要的時(shí)候,用getBean方法取出-->  
        <servlet-name>DispatcherServlet</servlet-name>  
        <!--Servlet攔截匹配規(guī)則可以自已定義副编,當(dāng)映射為@RequestMapping("/user/add")時(shí)监婶,為例,攔截哪種URL合適?-->  
        <!--1齿桃、攔截*.do、*.htm煮盼, 例如:/user/add.do,這是最傳統(tǒng)的方式短纵,最簡(jiǎn)單也最實(shí)用。不會(huì)導(dǎo)致靜態(tài)文件(jpg,js,css)被攔截僵控。-->  
        <!--2香到、攔截/,例如:/user/add,可以實(shí)現(xiàn)現(xiàn)在很流行的REST風(fēng)格报破。很多互聯(lián)網(wǎng)類(lèi)型的應(yīng)用很喜歡這種風(fēng)格的URL悠就。弊端:會(huì)導(dǎo)致靜態(tài)文件(jpg,js,css)被攔截后不能正常顯示。 -->  
        <url-pattern>/</url-pattern> <!--會(huì)攔截URL中帶“/”的請(qǐng)求充易。-->  
    </servlet-mapping>  

    <welcome-file-list><!--指定歡迎頁(yè)面-->  
        <welcome-file>login.html</welcome-file>  
    </welcome-file-list>  
    <error-page> <!--當(dāng)系統(tǒng)出現(xiàn)404錯(cuò)誤梗脾,跳轉(zhuǎn)到頁(yè)面nopage.html-->  
        <error-code>404</error-code>  
        <location>/nopage.html</location>  
    </error-page>  
    <error-page> <!--當(dāng)系統(tǒng)出現(xiàn)java.lang.NullPointerException,跳轉(zhuǎn)到頁(yè)面error.html-->  
        <exception-type>java.lang.NullPointerException</exception-type>  
        <location>/error.html</location>  
    </error-page>  
    <session-config><!--會(huì)話(huà)超時(shí)配置盹靴,單位分鐘-->  
        <session-timeout>360</session-timeout>  
    </session-config>  
</web-app>  
  • applicationContext.xml文件配置
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"  
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
    xmlns:cache="http://www.springframework.org/schema/cache"  
    xsi:schemaLocation="  
    http://www.springframework.org/schema/context  
    http://www.springframework.org/schema/context/spring-context.xsd  
    http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans.xsd  
    http://www.springframework.org/schema/tx  
    http://www.springframework.org/schema/tx/spring-tx.xsd  
    http://www.springframework.org/schema/jdbc  
    http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd  
    http://www.springframework.org/schema/cache  
    http://www.springframework.org/schema/cache/spring-cache-3.1.xsd  
    http://www.springframework.org/schema/aop  
    http://www.springframework.org/schema/aop/spring-aop.xsd  
    http://www.springframework.org/schema/util  
    http://www.springframework.org/schema/util/spring-util.xsd"> 

    <!-- 自動(dòng)掃描web包 ,將帶有注解的類(lèi) 納入spring容器管理 -->  
    <context:component-scan base-package="com.eduoinfo.finances.bank.web"></context:component-scan>  
  
    <!-- 引入jdbc配置文件 -->  
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
        <property name="locations">  
            <list>  
                <value>classpath*:jdbc.properties</value>  
            </list>  
        </property>  
    </bean>  
  
    <!-- dataSource 配置 -->  
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">  
        <!-- 基本屬性 url炸茧、user瑞妇、password -->  
        <property name="url" value="${jdbc.url}" />  
        <property name="username" value="${jdbc.username}" />  
        <property name="password" value="${jdbc.password}" />  
  
        <!-- 配置初始化大小、最小梭冠、最大 -->  
        <property name="initialSize" value="1" />  
        <property name="minIdle" value="1" />  
        <property name="maxActive" value="20" />  
  
        <!-- 配置獲取連接等待超時(shí)的時(shí)間 -->  
        <property name="maxWait" value="60000" />  
  
        <!-- 配置間隔多久才進(jìn)行一次檢測(cè)辕狰,檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒 -->  
        <property name="timeBetweenEvictionRunsMillis" value="60000" />  
  
        <!-- 配置一個(gè)連接在池中最小生存的時(shí)間控漠,單位是毫秒 -->  
        <property name="minEvictableIdleTimeMillis" value="300000" />  
  
        <property name="validationQuery" value="SELECT 'x'" />  
        <property name="testWhileIdle" value="true" />  
        <property name="testOnBorrow" value="false" />  
        <property name="testOnReturn" value="false" />  
  
        <!-- 打開(kāi)PSCache蔓倍,并且指定每個(gè)連接上PSCache的大小 -->  
        <property name="poolPreparedStatements" value="false" />  
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />  
  
        <!-- 配置監(jiān)控統(tǒng)計(jì)攔截的filters -->  
        <property name="filters" value="stat" />  
    </bean>  
  
    <!-- mybatis文件配置,掃描所有mapper文件 -->  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml" p:mapperLocations="classpath:com/eduoinfo/finances/bank/web/dao/*.xml" />  
  
    <!-- spring與mybatis整合配置盐捷,掃描所有dao -->  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.eduoinfo.finances.bank.web.dao" p:sqlSessionFactoryBeanName="sqlSessionFactory" />  
  
    <!-- 對(duì)dataSource 數(shù)據(jù)源進(jìn)行事務(wù)管理 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" />  
  
    <!-- 配置使Spring采用CGLIB代理 -->  
    <aop:aspectj-autoproxy proxy-target-class="true" />  
  
    <!-- 啟用對(duì)事務(wù)注解的支持 -->  
    <tx:annotation-driven transaction-manager="transactionManager" />  
  
    <!-- Cache配置 -->  
    <cache:annotation-driven cache-manager="cacheManager" />  
    <bean id="ehCacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configLocation="classpath:ehcache.xml" />  
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="ehCacheManagerFactory" />  
  
</beans>  

關(guān)于Spring Bean

備注偶翅,加載applicationContext時(shí),使用到了Bean的一些邏輯毙驯,這里簡(jiǎn)單介紹下spring bean的工作機(jī)制倒堕。

在Spring中,所有管理的對(duì)象都是JavaBean對(duì)象爆价,而B(niǎo)eanFactory和ApplicationContext就是spring框架的兩個(gè)IOC容器垦巴,現(xiàn)在一般使用ApplicationnContext,其不但包含了BeanFactory的作用铭段,同時(shí)還進(jìn)行更多的擴(kuò)展骤宣。

Spring和Bean的關(guān)系

bean配置有三種方法:

  • 基于xml配置Bean
  • 使用注解定義Bean
  • 基于java類(lèi)提供Bean定義信息

這里主要介紹第一種,基于xml的Bean配置序愚。

關(guān)鍵字:Bean id憔披、Bean類(lèi)名、property.

一般情況下爸吮,Spring IOC容器中的一個(gè)Bean即對(duì)應(yīng)配置文件中的一個(gè)<bean>,這種鏡像對(duì)應(yīng)關(guān)系應(yīng)該容易理解芬膝。其中id為這個(gè)Bean的名稱(chēng),通過(guò)容器的getBean("foo")即可獲取對(duì)應(yīng)的Bean形娇,在容器中起到定位查找的作用锰霜,是外部程序和Spring IOC容器進(jìn)行交互的橋梁。class屬性指定了Bean對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)桐早。

    <!-- 引入jdbc配置文件 -->  
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
        <property name="locations">  
            <list>  
                <value>classpath*:jdbc.properties</value>  
            </list>  
        </property>  
    </bean> 
使用注解配置信息啟動(dòng)spring容器

Spring提供了一個(gè)context的命名空間癣缅,它提供了通過(guò)掃描類(lèi)包以應(yīng)用注解定義Bean的方式:

<?xml version="1.0" encoding="UTF-8" ?>
<!--①聲明context的命名空間-->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.0.xsd"
         >
    <!--②掃描類(lèi)包以應(yīng)用注解定義的Bean-->
   <context:component-scan base-package="com.baobaotao.anno"/>
   <bean class="com.baobaotao.anno.LogonService"></bean>
   <!-- context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ -->
   <!-- context:component-scan base-package="com.baobaotao">
       <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Dao"/>
       <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Service"/>
       <context:exclude-filter type="aspectj" expression="com.baobaotao..*Controller+"/>
   </context:component-scan -->
</beans>
Bean注入

Bean注入的方式有兩種:

  • 在XML中配置,此時(shí)分別有屬性注入哄酝、構(gòu)造函數(shù)注入和工廠(chǎng)方法注入

  • 使用注解的方式注入 @Autowired,@Resource,@Required友存。

項(xiàng)目安裝

在本文中,我們將使用最新陶衅、最好的Spring Framework 5屡立。我們將重點(diǎn)介紹Spring的經(jīng)典Web堆棧,該堆棧從框架的第一個(gè)版本中就嶄露頭角搀军,并且現(xiàn)在依然是用Spring構(gòu)建Web應(yīng)用程序的主要方式侠驯。

對(duì)于初學(xué)者來(lái)說(shuō)抡秆,為了安裝測(cè)試項(xiàng)目,最好使用Spring Boot和一些初學(xué)者依賴(lài)項(xiàng)吟策;還需要定義parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.M5</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>

請(qǐng)注意儒士,為了使用Spring 5,我們還需要使用Spring Boot 2.x檩坚。截止到撰寫(xiě)本文之時(shí)着撩,這依然是里程碑發(fā)布版,可在Spring Milestone Repository中找到匾委。讓我們把這個(gè)存儲(chǔ)庫(kù)添加到你的Maven項(xiàng)目中:

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

你可以在Maven Central上查看Spring Boot的當(dāng)前版本拖叙。

示例項(xiàng)目

為了理解Spring Web MVC是如何工作的,我們將通過(guò)一個(gè)登錄頁(yè)面實(shí)現(xiàn)一個(gè)簡(jiǎn)單的應(yīng)用程序赂乐。為了顯示登錄頁(yè)面薯鳍,我們需要為上下文根創(chuàng)建帶有GET映射的@Controller注解類(lèi)InternalController。

hello()方法是無(wú)參數(shù)的挨措。它返回一個(gè)由Spring MVC解釋為視圖名稱(chēng)的String(在示例中是login.html模板):

import org.springframework.web.bind.annotation.GetMapping;
@GetMapping("/")
public String hello() {
    return "login";
}

為了處理用戶(hù)登錄挖滤,需要?jiǎng)?chuàng)建另一個(gè)用登錄數(shù)據(jù)處理POST請(qǐng)求的方法。然后根據(jù)結(jié)果將用戶(hù)重定向到成功或失敗的頁(yè)面浅役。

請(qǐng)注意斩松,login()方法接收域?qū)ο笞鳛閰?shù)并返回ModelAndView對(duì)象:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
@PostMapping("/login")
public ModelAndView login(LoginData loginData) {
    if (LOGIN.equals(loginData.getLogin()) 
      && PASSWORD.equals(loginData.getPassword())) {
        return new ModelAndView("success", 
          Collections.singletonMap("login", loginData.getLogin()));
    } else {
        return new ModelAndView("failure", 
          Collections.singletonMap("login", loginData.getLogin()));
    }
}

ModelAndView是兩個(gè)不同對(duì)象的持有者:

  • Model——渲染頁(yè)面數(shù)據(jù)的鍵值映射
  • View——填充模型數(shù)據(jù)的頁(yè)面模板

連接這些是為了方便,這樣控制器方法可以一次返回它們觉既。

要渲染HTML頁(yè)面惧盹,使用Thymeleaf作為視圖模板引擎,該引擎具有可靠和開(kāi)箱即用的與Spring的集成瞪讼。

Servlet作為Java Web應(yīng)用程序的基礎(chǔ)

那么钧椰,當(dāng)在瀏覽器中輸入http:// localhost:8080/時(shí),按Enter鍵符欠,然后請(qǐng)求到達(dá)Web服務(wù)器嫡霞,實(shí)際發(fā)生了什么?你如何從這個(gè)請(qǐng)求中看到瀏覽器中的Web表單背亥?

鑒于該項(xiàng)目是一個(gè)簡(jiǎn)單的Spring Boot應(yīng)用程序,因此可以通過(guò)Spring5Application運(yùn)行它悬赏。

Spring Boot默認(rèn)使用Apache Tomcat狡汉。因此,運(yùn)行應(yīng)用程序時(shí)闽颇,你可能會(huì)在日志中看到以下信息:

2017-10-16 20:36:11.626  INFO 57414 --- [main] 
  o.s.b.w.embedded.tomcat.TomcatWebServer  : 
  Tomcat initialized with port(s): 8080 (http)
2017-10-16 20:36:11.634  INFO 57414 --- [main] 
  o.apache.catalina.core.StandardService   : 
  Starting service [Tomcat]
2017-10-16 20:36:11.635  INFO 57414 --- [main] 
  org.apache.catalina.core.StandardEngine  : 
  Starting Servlet Engine: Apache Tomcat/8.5.23

由于Tomcat是一個(gè)Servlet容器盾戴,因此發(fā)送給Tomcat Web服務(wù)器的每個(gè)HTTP請(qǐng)求自然都由Java servlet處理。所以Spring Web應(yīng)用程序入口點(diǎn)是一個(gè)servlet兵多,這并不奇怪尖啡。

簡(jiǎn)單地說(shuō)橄仆,servlet就是任何Java Web應(yīng)用程序的核心組件;它是低層次的衅斩,不會(huì)像MVC那樣在特定的編程模式中諸多要求盆顾。

一個(gè)HTTP servlet只能接收一個(gè)HTTP請(qǐng)求,以某種方式處理畏梆,然后發(fā)回一個(gè)響應(yīng)您宪。

而且,從Servlet 3.0 API開(kāi)始奠涌,你現(xiàn)在可以超越XML配置宪巨,并開(kāi)始利用Java配置(只有很小的限制條件)。

DispatcherServlet作為Spring MVC的核心

作為一個(gè)Web應(yīng)用程序的開(kāi)發(fā)人員溜畅,我們真正想要做的是抽象出以下繁瑣和模板化的任務(wù)捏卓,并專(zhuān)注于有用的業(yè)務(wù)邏輯:

  • 將HTTP請(qǐng)求映射到某個(gè)處理方法

  • 將HTTP請(qǐng)求數(shù)據(jù)和標(biāo)題解析成數(shù)據(jù)傳輸對(duì)象(DTO)或域?qū)ο?/p>

  • 模型 – 視圖 – 控制器集成

  • 從DTO、域?qū)ο蟮壬身憫?yīng)

Spring DispatcherServlet能夠提供這些慈格。它是Spring Web MVC框架的核心怠晴;此核心組件接收所有請(qǐng)求到應(yīng)用程序。

正如你所看到的峦椰,DispatcherServlet是非沉淠可擴(kuò)展的。例如汤功,它允許你插入不同的現(xiàn)有或新的適配器進(jìn)行大量的任務(wù):

  • 將請(qǐng)求映射到應(yīng)該處理它的類(lèi)或方法(HandlerMapping接口的實(shí)現(xiàn))

  • 使用特定模式處理請(qǐng)求岭洲,如常規(guī)servlet蕊程,更復(fù)雜的MVC工作流,或POJO bean中的方法(HandlerAdapter接口的實(shí)現(xiàn))

  • 按名稱(chēng)解析視圖,允許你使用不同的模板引擎菩彬,XML,XSLT或任何其他視圖技術(shù)(ViewResolver接口的實(shí)現(xiàn))

  • 通過(guò)使用默認(rèn)的Apache Commons文件上傳實(shí)現(xiàn)或編寫(xiě)你自己的MultipartResolver來(lái)解析多部分請(qǐng)求

  • 使用任何LocaleResolver實(shí)現(xiàn)解決語(yǔ)言環(huán)境康栈,包括cookie辆苔,會(huì)話(huà),Accept HTTP頭忿族,或任何其他確定用戶(hù)所期望的語(yǔ)言環(huán)境的方式

處理HTTP請(qǐng)求

首先锣笨,我們將簡(jiǎn)單的HTTP請(qǐng)求的處理追蹤到在控制器層中的一個(gè)方法,然后返回到瀏覽器/客戶(hù)端道批。

DispatcherServlet具有很長(zhǎng)的繼承層次結(jié)構(gòu)错英;自上而下地逐個(gè)理解這些是有價(jià)值的。請(qǐng)求處理方法最讓我們感興趣隆豹。

示意圖

理解HTTP請(qǐng)求椭岩,無(wú)論是在本地還是遠(yuǎn)程的標(biāo)準(zhǔn)開(kāi)發(fā)中,都是理解MVC體系結(jié)構(gòu)的關(guān)鍵部分。

GenericServlet

GenericServlet是Servlet規(guī)范的一部分判哥,不直接關(guān)注HTTP献雅。它定義了接收傳入請(qǐng)求并產(chǎn)生響應(yīng)的service()方法。

注意塌计,ServletRequest和ServletResponse方法參數(shù)如何與HTTP協(xié)議無(wú)關(guān):

public abstract void service(ServletRequest req, ServletResponse res) 
  throws ServletException, IOException;

這是最終被任何請(qǐng)求調(diào)用到服務(wù)器上的方法挺身,包括簡(jiǎn)單的GET請(qǐng)求。

HttpServlet

顧名思義夺荒,HttpServlet類(lèi)就是規(guī)范中定義的基于HTTP的Servlet實(shí)現(xiàn)瞒渠。

更實(shí)際的說(shuō),HttpServlet是一個(gè)抽象類(lèi)技扼,有一個(gè)service()方法實(shí)現(xiàn)伍玖,service()方法實(shí)現(xiàn)通過(guò)HTTP方法類(lèi)型分割請(qǐng)求,大致如下所示:

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        // ...
        doGet(req, resp);
    } else if (method.equals(METHOD_HEAD)) {
        // ...
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        // ...
    }

HttpServletBean

接下來(lái)剿吻,HttpServletBean是層次結(jié)構(gòu)中第一個(gè)Spring-aware類(lèi)窍箍。它使用從web.xml或WebApplicationInitializer接收到的servlet init-param值來(lái)注入bean的屬性。

在請(qǐng)求應(yīng)用程序的情況下丽旅,doGet()椰棘,doPost()等方法應(yīng)特定的HTTP請(qǐng)求而調(diào)用。

FrameworkServlet

FrameworkServlet集成Servlet功能與Web應(yīng)用程序上下文榄笙,實(shí)現(xiàn)了ApplicationContextAware接口邪狞。但它也能夠自行創(chuàng)建Web應(yīng)用程序上下文。

正如你已經(jīng)看到的茅撞,HttpServletBean超類(lèi)注入init-params為bean屬性帆卓。所以,如果在servlet的contextClass init-param中提供了一個(gè)上下文類(lèi)名米丘,那么這個(gè)類(lèi)的一個(gè)實(shí)例將被創(chuàng)建為應(yīng)用程序上下文剑令。否則,將使用默認(rèn)的XmlWebApplicationContext類(lèi)拄查。

由于XML配置現(xiàn)在已經(jīng)過(guò)時(shí)吁津,Spring Boot默認(rèn)使用AnnotationConfigWebApplicationContext配置DispatcherServlet。但是你可以輕松更改堕扶。

例如碍脏,如果你需要使用基于Groovy的應(yīng)用程序上下文來(lái)配置Spring Web MVC應(yīng)用程序,則可以在web.xml文件中使用以下DispatcherServlet配置:

dispatcherServlet
        org.springframework.web.servlet.DispatcherServlet
        contextClass
        org.springframework.web.context.support.GroovyWebApplicationContext

使用WebApplicationInitializer類(lèi)稍算,可以用更現(xiàn)代的基于Java的方式來(lái)完成相同的配置典尾。

DispatcherServlet:統(tǒng)一請(qǐng)求處理

HttpServlet.service()實(shí)現(xiàn),會(huì)根據(jù)HTTP動(dòng)詞的類(lèi)型來(lái)路由請(qǐng)求邪蛔,這在低級(jí)servlet的上下文中是非常有意義的急黎。然而,在Spring MVC的抽象級(jí)別侧到,方法類(lèi)型只是可以用來(lái)映射請(qǐng)求到其處理程序的參數(shù)之一勃教。

因此,F(xiàn)rameworkServlet類(lèi)的另一個(gè)主要功能是將處理邏輯重新加入到單個(gè)processRequest()方法中匠抗,processRequest()方法反過(guò)來(lái)又調(diào)用doService()方法:

@Override
protected final void doGet(HttpServletRequest request, 
  HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, 
  HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}
// …

DispatcherServlet:豐富請(qǐng)求

最后故源,DispatcherServlet實(shí)現(xiàn)doService()方法。在這里汞贸,它增加了一些可能會(huì)派上用場(chǎng)的有用對(duì)象到請(qǐng)求:Web應(yīng)用程序上下文绳军,區(qū)域解析器,主題解析器矢腻,主題源等:

request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, 
  getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

另外门驾,doService()方法準(zhǔn)備輸入和輸出Flash映射。Flash映射基本上是一種模式多柑,該模式將參數(shù)從一個(gè)請(qǐng)求傳遞到另一個(gè)緊跟的請(qǐng)求奶是。這在重定向期間可能非常有用(例如在重定向之后向用戶(hù)顯示一次性信息消息):

FlashMap inputFlashMap = this.flashMapManager
  .retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, 
      Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());

然后,doService()方法調(diào)用負(fù)責(zé)請(qǐng)求調(diào)度的doDispatch()方法竣灌。

DispatcherServlet:調(diào)度請(qǐng)求

dispatch()方法的主要目的是為請(qǐng)求找到合適的處理程序聂沙,并為其提供請(qǐng)求/響應(yīng)參數(shù)。處理程序基本上是任何類(lèi)型的object初嘹,不限于特定的接口及汉。這也意味著Spring需要為此處理程序找到適配器,該處理程序知道如何與處理程序“交談”屯烦。

為了找到匹配請(qǐng)求的處理程序坷随,Spring檢查HandlerMapping接口的注冊(cè)實(shí)現(xiàn)。有很多不同的實(shí)現(xiàn)可以滿(mǎn)足你的需求漫贞。

SimpleUrlHandlerMapping允許通過(guò)URL將請(qǐng)求映射到某個(gè)處理bean甸箱。例如,可以通過(guò)使用java.util.Properties實(shí)例注入其mappings屬性來(lái)配置迅脐,就像這樣:

/welcome.html=ticketController
/show.html=ticketController

可能處理程序映射最廣泛使用的類(lèi)是RequestMappingHandlerMapping芍殖,它將請(qǐng)求映射到@Controller類(lèi)的@ RequestMapping注釋方法。這正是使用控制器的hello()和login()方法連接調(diào)度程序的映射谴蔑。

請(qǐng)注意豌骏,Spring-aware方法使用@GetMapping和@PostMapping進(jìn)行注釋。這些注釋依次用@RequestMapping元注釋標(biāo)記隐锭。

dispatch()方法還負(fù)責(zé)其他一些HTTP特定任務(wù):

  • 在資源未被修改的情況下窃躲,GET請(qǐng)求的短路處理
  • 針對(duì)相應(yīng)的請(qǐng)求應(yīng)用多部分解析器
  • 如果處理程序選擇異步處理該請(qǐng)求,則會(huì)短路處理該請(qǐng)求

處理請(qǐng)求

現(xiàn)在Spring已經(jīng)確定了請(qǐng)求的處理程序和處理程序的適配器钦睡,是時(shí)候來(lái)處理請(qǐng)求了蒂窒。下面是HandlerAdapter.handle()方法的簽名。請(qǐng)注意,處理程序可以選擇如何處理請(qǐng)求:

  • 自主地編寫(xiě)數(shù)據(jù)到響應(yīng)對(duì)象洒琢,并返回null
  • 返回由DispatcherServlet呈現(xiàn)的ModelAndView對(duì)象
@Nullable
ModelAndView handle(HttpServletRequest request, 
                    HttpServletResponse response, 
                    Object handler) throws Exception;

有幾種提供的處理程序類(lèi)型秧秉。以下是SimpleControllerHandlerAdapter如何處理Spring MVC控制器實(shí)例(不要將其與@ Controller注釋POJO混淆)。

注意控制器處理程序如何返回ModelAndView對(duì)象衰抑,并且不自行呈現(xiàn)視圖:

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}

第二個(gè)是SimpleServletHandlerAdapter象迎,它將常規(guī)的Servlet作為請(qǐng)求處理器。

Servlet不知道任何有關(guān)ModelAndView的內(nèi)容呛踊,只是簡(jiǎn)單地自行處理請(qǐng)求砾淌,并將結(jié)果呈現(xiàn)給響應(yīng)對(duì)象。所以這個(gè)適配器只是返回null而不是ModelAndView:

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    ((Servlet) handler).service(request, response);
    return null;
}

我們碰到的情況是谭网,控制器是有若干@RequestMapping注釋的POJO汪厨,所以任何處理程序基本上是包裝在HandlerMethod實(shí)例中的這個(gè)類(lèi)的方法。為了適應(yīng)這個(gè)處理器類(lèi)型愉择,Spring使用RequestMappingHandlerAdapter類(lèi)骄崩。

處理參數(shù)和返回處理程序方法的值

注意,控制器方法通常不會(huì)使用HttpServletRequest和HttpServletResponse薄辅,而是接收和返回許多不同類(lèi)型的數(shù)據(jù)要拂,例如域?qū)ο螅窂絽?shù)等站楚。

此外脱惰,要注意,我們不需要從控制器方法返回ModelAndView實(shí)例窿春±唬可能會(huì)返回視圖名稱(chēng),或ResponseEntity旧乞,或?qū)⒈晦D(zhuǎn)換為JSON響應(yīng)等的POJO蔚润。

RequestMappingHandlerAdapter確保方法的參數(shù)從HttpServletRequest中解析出來(lái)。另外尺栖,它從方法的返回值中創(chuàng)建ModelAndView對(duì)象嫡纠。

在RequestMappingHandlerAdapter中有一段重要的代碼,可確保所有這些轉(zhuǎn)換魔法的發(fā)生:

ServletInvocableHandlerMethod invocableMethod 
  = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(
      this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(
      this.returnValueHandlers);
}
argumentResolvers對(duì)象是不同的HandlerMethodArgumentResolver實(shí)例的組合延赌。

有超過(guò)30個(gè)不同的參數(shù)解析器實(shí)現(xiàn)除盏。它們?cè)试S從請(qǐng)求中提取任何類(lèi)型的信息,并將其作為方法參數(shù)提供挫以。這包括URL路徑變量者蠕,請(qǐng)求主體參數(shù),請(qǐng)求標(biāo)頭掐松,cookies踱侣,會(huì)話(huà)數(shù)據(jù)等粪小。

returnValueHandlers對(duì)象是HandlerMethodReturnValueHandler對(duì)象的組合。還有很多不同的值處理程序可以處理方法的結(jié)果來(lái)創(chuàng)建適配器所期望的ModelAndViewobject抡句。

例如糕再,當(dāng)你從hello()方法返回字符串時(shí),ViewNameMethodReturnValueHandler處理這個(gè)值玉转。但是,當(dāng)你從login()方法返回一個(gè)準(zhǔn)備好的ModelAndView時(shí)殴蹄,Spring會(huì)使用ModelAndViewMethodReturnValueHandler究抓。

渲染視圖

到目前為止,Spring已經(jīng)處理了HTTP請(qǐng)求并接收了ModelAndView對(duì)象袭灯,所以它必須呈現(xiàn)用戶(hù)將在瀏覽器中看到的HTML頁(yè)面刺下。它基于模型和封裝在ModelAndView對(duì)象中的選定視圖來(lái)完成。

另外請(qǐng)注意稽荧,我們可以呈現(xiàn)JSON對(duì)象橘茉,或XML,或任何可通過(guò)HTTP協(xié)議傳輸?shù)钠渌麛?shù)據(jù)格式姨丈。我們將在即將到來(lái)的REST-focused部分接觸更多畅卓。

讓我們回到DispatcherServlet。render()方法首先使用提供的LocaleResolver實(shí)例設(shè)置響應(yīng)語(yǔ)言環(huán)境蟋恬。假設(shè)現(xiàn)代瀏覽器正確設(shè)置了Accept頭翁潘,并且默認(rèn)使用AcceptHeaderLocaleResolver。

在渲染過(guò)程中歼争,ModelAndView對(duì)象可能已經(jīng)包含對(duì)所選視圖的引用拜马,或者只是一個(gè)視圖名稱(chēng),或者如果控制器依賴(lài)于默認(rèn)視圖沐绒,則什么都沒(méi)有俩莽。

由于hello()和login()方法兩者都指定所需的視圖為String名稱(chēng),因此必須用該名稱(chēng)查找乔遮。所以扮超,這是viewResolvers列表開(kāi)始起作用的地方:

for (ViewResolver viewResolver : this.viewResolvers) {
    View view = viewResolver.resolveViewName(viewName, locale);
    if (view != null) {
        return view;
    }
}

這是一個(gè)ViewResolver實(shí)例列表,包括由thymeleaf-spring5集成庫(kù)提供的ThymeleafViewResolver蹋肮。該解析器知道在哪里搜索視圖瞒津,并提供相應(yīng)的視圖實(shí)例。

在調(diào)用視圖的render()方法后括尸,Spring最終通過(guò)發(fā)送HTML頁(yè)面到用戶(hù)的瀏覽器來(lái)完成請(qǐng)求處理巷蚪。

REST支持

除了典型的MVC場(chǎng)景之外,我們還可以使用框架來(lái)創(chuàng)建REST Web服務(wù)濒翻。

簡(jiǎn)而言之屁柏,我們可以接受Resource作為輸入啦膜,指定POJO作為方法參數(shù),并使用@RequestBody對(duì)其進(jìn)行注釋淌喻。也可以使用@ResponseBody注釋方法本身僧家,以指定其結(jié)果必須直接轉(zhuǎn)換為HTTP響應(yīng):

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
@ResponseBody
@PostMapping("/message")
public MyOutputResource sendMessage(
  @RequestBody MyInputResource inputResource) {
    return new MyOutputResource("Received: "
      + inputResource.getRequestMessage());
}

歸功于Spring MVC的可擴(kuò)展性,這也是可行的裸删。

為了將內(nèi)部DTO編組為REST表示八拱,框架使用HttpMessageConverter基礎(chǔ)結(jié)構(gòu)。例如涯塔,其中一個(gè)實(shí)現(xiàn)是MappingJackson2HttpMessageConverter肌稻,它可以使用Jackson庫(kù)將模型對(duì)象轉(zhuǎn)換為JSON或從JSON轉(zhuǎn)換。

為了進(jìn)一步簡(jiǎn)化REST API的創(chuàng)建匕荸,Spring引入了@RestController注解爹谭。默認(rèn)情況下,這很方便地假定了@ResponseBody語(yǔ)義榛搔,并避免在每個(gè)REST控制器上的明確設(shè)置:

import org.springframework.web.bind.annotation.RestController;
@RestController
public class RestfulWebServiceController {
    @GetMapping("/message")
    public MyOutputResource getMessage() {
        return new MyOutputResource("Hello!");
    }
}

結(jié)論

在這篇文章中诺凡,我們?cè)敿?xì)了介紹在Spring MVC框架中請(qǐng)求的處理過(guò)程。了解框架的不同擴(kuò)展是如何協(xié)同工作來(lái)提供所有魔法的践惑,可以讓你能夠事倍功半地處理HTTP協(xié)議難題腹泌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尔觉,隨后出現(xiàn)的幾起案子真屯,更是在濱河造成了極大的恐慌,老刑警劉巖穷娱,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绑蔫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡泵额,警方通過(guò)查閱死者的電腦和手機(jī)配深,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嫁盲,“玉大人篓叶,你說(shuō)我怎么就攤上這事⌒叱樱” “怎么了缸托?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瘾蛋。 經(jīng)常有香客問(wèn)我俐镐,道長(zhǎng),這世上最難降的妖魔是什么哺哼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任佩抹,我火速辦了婚禮叼风,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棍苹。我一直安慰自己无宿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布枢里。 她就那樣靜靜地躺著孽鸡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栏豺。 梳的紋絲不亂的頭發(fā)上彬碱,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音冰悠,去河邊找鬼。 笑死配乱,一個(gè)胖子當(dāng)著我的面吹牛溉卓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搬泥,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼桑寨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了忿檩?” 一聲冷哼從身側(cè)響起尉尾,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎燥透,沒(méi)想到半個(gè)月后沙咏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡班套,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年肢藐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吱韭。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吆豹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出理盆,到底是詐尸還是另有隱情痘煤,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布猿规,位于F島的核電站衷快,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏姨俩。R本人自食惡果不足惜烦磁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一养匈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧都伪,春花似錦呕乎、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至先誉,卻和暖如春湿刽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背褐耳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工诈闺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铃芦。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓雅镊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親刃滓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仁烹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,394評(píng)論 1 92
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理咧虎,服務(wù)發(fā)現(xiàn)卓缰,斷路器,智...
    卡卡羅2017閱讀 134,652評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,806評(píng)論 6 342
  • 【韓喜文2018.07.01星期周日】 好展館讓天下沒(méi)有賣(mài)不出去的產(chǎn)品 好展館讓天下沒(méi)有不能傳承的文化 日精進(jìn):8...
    韓喜文閱讀 48評(píng)論 0 0
  • 以前寫(xiě)過(guò)一篇文章也是有關(guān)維特根斯坦哲學(xué)轉(zhuǎn)折的啟示的砰诵,其目的在于探索如何以一個(gè)正確的方式打開(kāi)聊天征唬。而如今這篇文章則是...
    楓別雨閱讀 717評(píng)論 0 5