Spring參考手冊(cè) 6 網(wǎng)絡(luò)

翻譯自Spring官方文檔 4.1.2版本

相關(guān)文章:

一癌佩、Web MVC framework

1.1 Spring Web MVC framework簡(jiǎn)介

Spring Web model-view-controller (MVC) framework的核心是一個(gè)DispatcherServlet跨嘉,這個(gè)調(diào)度器負(fù)責(zé)分配請(qǐng)求到處理器貌夕,可實(shí)現(xiàn)配置處理器映射涩盾、視圖解析缕粹、本地化雅倒、時(shí)區(qū)和主題解析還有上傳文件的支持别惦。默認(rèn)的處理器基于@Controller@RequestMapping注解,提供了一個(gè)很靈活的處理方法嘁字。在Spring 3.0的簡(jiǎn)介可了解到恩急,@Controller機(jī)制還允許你通過@PathVariable注解和其他特性創(chuàng)建RESTful網(wǎng)址和應(yīng)用程序。

1.2 DispatcherServlet

Spring web MVC framework與其他許多MCV框架類似纪蜒,請(qǐng)求驅(qū)動(dòng)衷恭,圍繞一個(gè)中心Servlet,由它分配請(qǐng)求到控制器并提供其他功能來促進(jìn)web應(yīng)用程序開發(fā)纯续。Spring的DispatcherServlet然而做的要更多攘滩,它已經(jīng)完全與Spring IoC容器整合,這樣就允許你使用Spring的其他特性侨把。

Spring Web MVC DispatcherServlet的請(qǐng)求處理工作流通過下面的圖示來說明盐固。懂設(shè)計(jì)模式的讀者會(huì)認(rèn)出DispatcherServlet使用的是一種前端控制器設(shè)計(jì)模式。

DispatcherServlet是一個(gè)實(shí)實(shí)在在的Servlet倦炒,這樣它可以被聲明在web.xml里显沈。你需要映射你希望DispatcherServlet處理的請(qǐng)求。這是一個(gè)標(biāo)準(zhǔn)的Java EE Servlet配置:

<web-app>
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>/example/*</url-pattern>
    </servlet-mapping>

</web-app>

所有以/example開始的請(qǐng)求都將會(huì)被名為example的DispatcherServlet實(shí)例處理逢唤。在Servlet 3.0+環(huán)境拉讯,你還可以通過編程方式來配置Servlet容器,下面的代碼基本上與上面的web.xml是等價(jià)的:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/example/*");
    }

}

在初始化一個(gè)DispatcherServlet時(shí)鳖藕,Spring MVC會(huì)查找在WEB-INF文件夾下的一個(gè)名為[servlet-name]-servlet.xml的文件并在創(chuàng)建bean魔慷,重寫全局范圍內(nèi)相同名字的bean。

假設(shè)有如下DispatcherServlet配置:

<web-app>
    <servlet>
        <servlet-name>golfing</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>golfing</servlet-name>
        <url-pattern>/golfing/*</url-pattern>
    </servlet-mapping>
</web-app>

你需要一個(gè)文件/WEB-INF/golfing-servlet.xml著恩,它將會(huì)包含所有Spring Web MVC特定的組件(beans)院尔。也可以通過一個(gè)Servlet初始化參數(shù)來改變這個(gè)位置纹烹。

針對(duì)單一DispatcherServlet場(chǎng)景也可以只有一個(gè)根上下文:

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></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>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

1.2.1 DispatcherServlet處理順序

在你設(shè)置一個(gè)DispatcherServlet后,當(dāng)一個(gè)請(qǐng)求進(jìn)入DispatcherServlet會(huì)開始處理請(qǐng)求:

  • WebApplicationContext被搜索并且綁定請(qǐng)求作為一個(gè)屬性這樣controller和其他元素在處理過程中可以使用它召边。默認(rèn)它被綁定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTEkey下铺呵。

  • 本地化解析器被綁定到這個(gè)請(qǐng)求。如果你不需要本地化解析那你就不需要它隧熙。

  • 主題解析器被綁定到這個(gè)請(qǐng)求片挂。如果你不使用主題你可以忽略它。

  • 如果你指定了一個(gè)multipart文件解析器贞盯,這個(gè)請(qǐng)求將會(huì)被檢查音念;如果找到了multipart這個(gè)請(qǐng)求會(huì)被包裝成一個(gè)MultipartHttpServletRequest來進(jìn)行進(jìn)一步處理。(比如上傳文件時(shí))

  • 一個(gè)適當(dāng)?shù)奶幚砥鞅凰阉黪锔摇H绻业搅司玩溄拥教幚砥鱽頊?zhǔn)備一個(gè)model或者渲染闷愤。

  • 一個(gè)model被返回,視圖被渲染

1.3 實(shí)現(xiàn)控制器

在Spring 2.5里已經(jīng)介紹了一種基于注解的編程模型件余,使用像@RequestMapping, @RequestParam, @ModelAttribute這樣的注解讥脐。通過這種形式實(shí)現(xiàn)控制器不需要繼承指定的父類或者實(shí)現(xiàn)指定的接口。此外它們通常也不直接依賴Servlet APIs啼器。

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

如你所見旬渠,@Controller@RequestMapping注解允許靈活的方法名。@Controller@RequestMapping和許多其他注解構(gòu)成了Spring MVC實(shí)現(xiàn)的基礎(chǔ)端壳。

1.3.1 使用@Controller定義一個(gè)控制器

@Controller注解標(biāo)明一個(gè)指定的類扮演控制器的角色告丢。Spring不要求你繼承任何父控制器類或者引用Servlet API。但是如果需要你仍然可以引用Servlet的特性损谦。

你可以在DispatcherServlet上下文中使用標(biāo)準(zhǔn)的Spring bean定義規(guī)則明確的定義一個(gè)控制器bean岖免。但是@Controller也允許自動(dòng)檢測(cè),自動(dòng)注冊(cè)為bean照捡。

為了自動(dòng)檢測(cè)被注解的控制器颅湘,你需要在配置文件添加組件掃描:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 控制器所在的包路徑 -->
    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    <!-- ... -->

</beans>

1.3.2 使用@RequestMapping映射請(qǐng)求

你可以使用@RequestMapping注解來映射URL就像這個(gè)/appointments,既可以在類上也可以在指定方法上麻敌。

下面這個(gè)例子展示了Spring MVC中使用注解的控制器:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(value="/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(value="/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

這個(gè)例子里@RequestMapping被用到了很多地方栅炒。第一個(gè)用例實(shí)在類級(jí)別掂摔,表示所有處理方法都在/appointments路徑下术羔。get()方法將請(qǐng)求映射更加細(xì)化:它只接收GET請(qǐng)求,HTTP GET /appointments會(huì)調(diào)用這個(gè)方法乙漓。getNewForm()聯(lián)合了HTTP方法定義和路徑级历,那么GET請(qǐng)求appointments/new將會(huì)被這個(gè)方法處理。

getForDay()方法展示了@RequestMapping的另一種用法:URI 模版叭披。(詳見下一章)

@RequestMapping定義在類級(jí)別并不是必須的寥殖。沒有了它所有路徑都是絕對(duì)的不是相對(duì)的玩讳。下面的例子展示了一個(gè)多處理活動(dòng)的控制器:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

}

上面的例子并沒有指定GET,PUT嚼贡,POST等等熏纯,因?yàn)?code>@RequestMapping默認(rèn)會(huì)映射所有HTTP方法。使用@RequestMapping(method = RequestMethod.POST)縮小了映射粤策。

URI Template模式

URI templates 可以被用于在@RequestMapping方法中方便的訪問URL中被選中的一部分樟澜。

一個(gè)URI Template是一個(gè)類似URI的字符串,包含了一個(gè)或多個(gè)變量的名字叮盘。當(dāng)你替換了這些變量模版就會(huì)變成一個(gè)URI秩贰。例如http://www.example.com/users/{userId}包含變量userId。指定fred為變量的值就變成http://www.example.com/users/fred

在Spring MVC你可以用@PathVariable注解來綁定一個(gè)URI template 的變量:

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

當(dāng)一個(gè)請(qǐng)求/owners/fred到來后柔吼,String ownerId的值就是fred毒费。

你還可以指定URI template變量的名字:

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // implementation omitted
}

當(dāng)URI template變量的名字與方法參數(shù)的名字相同時(shí)可以省略@PathVariable注解中的值。

一個(gè)方法可以有任意數(shù)量的@PathVariable注解:

@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

當(dāng)一個(gè)@PathVariable注解被用在一個(gè)Map<String, String>參數(shù)愈魏,這個(gè)map會(huì)被所有URI template變量填充觅玻。

路徑模式

除了URI template外,@RequestMapping注解還支持Ant-style路徑培漏,例如/myPath/*.do串塑。也支持URI template變量和Ant-style聯(lián)合使用,例如:/owners/*/pets/{petId}北苟。

消費(fèi)媒體類型

你可以通過指定一系列的消費(fèi)媒體類型縮窄主映射范圍桩匪。只有請(qǐng)求頭的Content-Type與指定的媒體類型匹配時(shí)請(qǐng)求才會(huì)被接受,例如:

@Controller
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

只有當(dāng)請(qǐng)求頭的Content-Type包含application/json時(shí)這個(gè)請(qǐng)求才會(huì)被接受并處理友鼻。消費(fèi)類型表達(dá)式可以是否定的傻昙,例如!text/plain,這將會(huì)匹配所有Content-Type不包含text/plain的請(qǐng)求彩扔。

生產(chǎn)媒體類型

你可以通過指定一系列的生成媒體類型縮窄主映射范圍妆档。只有請(qǐng)求頭的Accept與其中一個(gè)媒體類型匹配時(shí)請(qǐng)求才會(huì)被接受,例如:

@Controller
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // implementation omitted
}

與消費(fèi)媒體類型類似虫碉,生產(chǎn)類型表達(dá)式可以是否定的贾惦,例如!text/plain,這將會(huì)匹配所有Accept不包含text/plain的請(qǐng)求敦捧。

請(qǐng)求參數(shù)和請(qǐng)求頭

你可以通過請(qǐng)求參數(shù)條件縮窄請(qǐng)求映射范圍须板,例如"myParam", "!myParam"或者"myParam=myValue"。前兩個(gè)是測(cè)試參數(shù)是否存在的兢卵,第三個(gè)是指定一個(gè)參數(shù)的值习瑰。下面是一個(gè)例子:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

這個(gè)例子里,只有請(qǐng)求的參數(shù)里有myParam并且它的值為myParam時(shí)才會(huì)被這個(gè)方法處理秽荤。同樣的方法可以用來測(cè)試請(qǐng)求頭:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

1.3.3 定義@RequestMapping處理方法

一個(gè)@RequestMapping處理方法可以擁有一個(gè)非常靈活的簽名甜奄。

支持的參數(shù)類型

下面是支持的方法參數(shù):

  • Request 或者 response對(duì)象 (Servlet API)
  • Session 對(duì)象 (Servlet API)柠横,這個(gè)參數(shù)永遠(yuǎn)不會(huì)是null
  • org.springframework.web.context.request.WebRequest或者org.springframework.web.context.request.NativeWebRequest。允許通用的請(qǐng)求參數(shù)訪問课兄,就像request/session屬性訪問一樣牍氛,但是沒有被束縛于Servlet/Portlet API
  • java.util.Locale當(dāng)前請(qǐng)求的本地化
  • 用來訪問請(qǐng)求內(nèi)容的java.io.InputStream / java.io.Reader
  • 用來生產(chǎn)響應(yīng)內(nèi)容的java.io.OutputStream / java.io.Writer
  • org.springframework.http.HttpMethodHTTP請(qǐng)求方法
  • java.security.Principal當(dāng)前已驗(yàn)證的用戶
  • @PathVariable注解的參數(shù)
  • @MatrixVariable注解的參數(shù)
  • @RequestParam注解的參數(shù)
  • @RequestHeader注解的參數(shù),用來訪問HTTP請(qǐng)求頭烟阐。
  • @RequestBody注解的參數(shù)糜俗,用來訪問HTTP請(qǐng)求體
  • @RequestPart注解的參數(shù),用來訪問一個(gè)請(qǐng)求的multipart/form-data部分
  • HttpEntity<?>參數(shù)用來訪問HTTP請(qǐng)求頭和內(nèi)容曲饱。
  • java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap傳遞值給web視圖
  • org.springframework.web.servlet.mvc.support.RedirectAttributes在請(qǐng)求重定向時(shí)傳遞屬性悠抹,如果一個(gè)方發(fā)返回一個(gè)"redirect:"前綴的視圖或者RedirectView
  • @ModelAttribute注解,用來綁定請(qǐng)求的參數(shù)到一個(gè)實(shí)體類扩淀,例如一個(gè)User類
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult上面實(shí)體類的校驗(yàn)結(jié)果
  • org.springframework.web.bind.support.SessionStatus標(biāo)記表單處理狀態(tài)為完成
  • org.springframework.web.util.UriComponentsBuilder構(gòu)建當(dāng)前請(qǐng)求的host, port, scheme, context path

需要注意的是Errors 或者 BindingResult需要緊跟著實(shí)體類楔敌。這個(gè)例子將不會(huì)正常工作:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

下面這個(gè)例子才是正確的:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

支持的返回類型

下面是支持的返回類型:

  • 一個(gè)ModelAndView對(duì)象
  • 一個(gè)Model對(duì)象
  • 一個(gè)Map對(duì)象
  • 一個(gè)View對(duì)象
  • 一個(gè)String對(duì)象,它可以被理解為一個(gè)本地視圖名稱
  • void驻谆,如果方法自己處理響應(yīng)
  • 如果一個(gè)方法上有@ResponseBody注解卵凑,返回類型將被寫到響應(yīng)體內(nèi),如果配置了JSON轉(zhuǎn)換器胜臊,返回的內(nèi)容還會(huì)被轉(zhuǎn)換成JSON字符串勺卢。
  • 一個(gè)HttpEntity<?> 或者 ResponseEntity<?>對(duì)象,可以用來訪問HTTP響應(yīng)頭和內(nèi)容
  • 一個(gè)HttpHeaders對(duì)象象对,返回一個(gè)沒有響應(yīng)體的response
  • 一個(gè)Callable<?>可以被返回當(dāng)應(yīng)用程序想在一個(gè)Spring MVC管理的線程里生產(chǎn)異步的返回值
  • 一個(gè)DeferredResult<?>或者ListenableFuture<?>可以被返回當(dāng)應(yīng)用程序想在一個(gè)它自己選擇的線程里生產(chǎn)異步的返回值
  • 任何其他的返回值都會(huì)被當(dāng)做一個(gè)model的屬性暴露給視圖

使用@RequestParam綁定請(qǐng)求參數(shù)

在controller里使用@RequestParam注解可以綁定一個(gè)請(qǐng)求參數(shù)到一個(gè)方法的參數(shù)黑忱。

下面的展示了使用方法:

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

這個(gè)注解的參數(shù)默認(rèn)是必須存在的,但是你可以指定它為可選的勒魔,例如:@RequestParam(value="petId", required=false)

如果方法參數(shù)的類型不是String甫煞,那么將會(huì)自動(dòng)進(jìn)行類型轉(zhuǎn)換。

如果@RequestParam注解使用在一個(gè)Map<String, String> 或者 MultiValueMap<String, String>參數(shù)上冠绢,請(qǐng)求里所有的參數(shù)都會(huì)被填充進(jìn)來抚吠。

使用@RequestBody注解映射請(qǐng)求體

@RequestBody注解聲明一個(gè)方法的參數(shù)被綁定了HTTP請(qǐng)求體的值。

@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

HttpMessageConverter負(fù)責(zé)轉(zhuǎn)換HTTP請(qǐng)求信息到一個(gè)對(duì)象也可以把一個(gè)對(duì)象轉(zhuǎn)換為HTTP響應(yīng)體弟胀。

下面是幾個(gè)默認(rèn)的HttpMessageConverter

  • ByteArrayHttpMessageConverter 轉(zhuǎn)換 byte 數(shù)組
  • StringHttpMessageConverter 轉(zhuǎn)換 字符串
  • FormHttpMessageConverter 轉(zhuǎn)換表單數(shù)據(jù) 到/從 一個(gè)MultiValueMap<String, String>
  • SourceHttpMessageConverter 轉(zhuǎn)換 到/從 javax.xml.transform.Source

JSON轉(zhuǎn)換器(譯者增加):

  • FastJsonHttpMessageConverter使用FastJson轉(zhuǎn)換

相關(guān)配置:

<bean id="fastJsonHttpMessageConverter"
          class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
        <property name="supportedMediaTypes">
            <list>
                <value>text/html;charset=UTF-8</value>
                <value>application/json</value>
            </list>
        </property>
        <property name="features">
            <list>
                <value>WriteMapNullValue</value>
                <value>QuoteFieldNames</value>
            </list>
        </property>
    </bean>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list >
                <ref bean="fastJsonHttpMessageConverter" />
            </list>
        </property>
    </bean>

使用@ResponseBody注解映射響應(yīng)體

@RequestBody相似楷力。這個(gè)注解表明返回類型要被直接寫入響應(yīng)體中:

@RequestMapping(value = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
    return "Hello World";
}

當(dāng)然Spring會(huì)使用HttpMessageConverter轉(zhuǎn)換對(duì)象,然后才寫入相應(yīng)體孵户,如果配置的是FastJsonHttpMessageConverter那么對(duì)象將會(huì)被轉(zhuǎn)換成JSON字符串寫入響應(yīng)體萧朝。

使用@RestController注解創(chuàng)建REST Controllers

簡(jiǎn)單的講就是將一個(gè)Controller所有的@RequestMapping方法自動(dòng)加上@ResponseBody

使用@RequestHeader注解映射請(qǐng)求頭

@RequestHeader注解允許一個(gè)方法參數(shù)綁定一個(gè)請(qǐng)求頭延届。

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

當(dāng)@RequestHeader注解在一個(gè)Map<String, String>, MultiValueMap<String, String>, 或者 HttpHeaders時(shí)會(huì)填充所有請(qǐng)求頭剪勿。

使用@ControllerAdvice注解為controller增加全局處理

@ControllerAdvice注解是一個(gè)組件注解,允許實(shí)現(xiàn)的類被自動(dòng)發(fā)現(xiàn)方庭。當(dāng)使用MVC命名空間時(shí)它會(huì)自動(dòng)啟用厕吉。它注解的
類里可以包含@ExceptionHandler, @InitBinder@ModelAttribute注解的方法,這些方法將會(huì)應(yīng)用到相應(yīng)的@RequestMapping方法上械念。

@ControllerAdvice注解還允許匹配controller的一部分子集:

// 匹配所有@RestController注解的Controller
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// 匹配指定包下的Controller
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// 匹配指定Controller類
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

1.4 解析視圖

所有的MVC框架針對(duì)于web應(yīng)用程序都提供了一種方法來處理視圖头朱。Spring提供了視圖解析器,允許你在瀏覽器中渲染模型而不用你嘗試使用一個(gè)特別的視圖技術(shù)龄减。Spring允許你使用JSP, Velocity templates and XSLT等視圖项钮。

有兩個(gè)接口對(duì)Spring處理視圖很重要,它們是ViewResolverView希停。ViewResolver在視圖名稱和真實(shí)視圖之間提供了一個(gè)映射烁巫。View接口引導(dǎo)傳遞請(qǐng)求到一個(gè)視圖。

1.4.1 視圖解析和ViewResolver接口

Spring提供了不少視圖解析器宠能,這個(gè)表格列出了它們的大部分亚隙;一些例子緊隨其后。

ViewResolver 描述
AbstractCachingViewResolver 抽象視圖解析緩存視圖违崇。通常視圖需要準(zhǔn)備后才能使用阿弃;拓展這個(gè)視圖解析器可以提供緩存
XmlViewResolver 允許將配置寫在XML文件里,默認(rèn)的配置文件是/WEB-INF/views.xml
ResourceBundleViewResolver 使用一個(gè)properties文件配置視圖羞延。默認(rèn)的文件名views.properties
UrlBasedViewResolver 根據(jù)邏輯名稱匹配視圖資源
InternalResourceViewResolver UrlBasedViewResolver的子類渣淳,支持InternalResourceView
VelocityViewResolver / FreeMarkerViewResolver 支持返回模板引擎的視圖
ContentNegotiatingViewResolver 根據(jù)請(qǐng)求文件名或者Accept頭解析視圖

例如,如果使用JSP作為視圖伴箩,你可以使用UrlBasedViewResolver入愧。這個(gè)視圖解析器將一個(gè)視圖名轉(zhuǎn)化為一個(gè)URL,傳遞請(qǐng)求到RequestDispatcher來渲染視圖嗤谚。

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

當(dāng)返回一個(gè)字符串test作為邏輯視圖名稱時(shí)砂客,這個(gè)視圖解析器會(huì)傳遞請(qǐng)求到RequestDispatcher,它會(huì)將請(qǐng)求送到/WEB-INF/jsp/test.jsp

當(dāng)你在web應(yīng)用程序中聯(lián)合不同的視圖技術(shù)時(shí),可以使用ResourceBundleViewResolver

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver根據(jù)basename屬性指定的值檢查ResourceBundle呵恢。它會(huì)使用[viewname].(class)屬性作為視圖類鞠值,使用[viewname].url屬性作為視圖url。views.properties默認(rèn)路徑在WEB-INF/classes

welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp

productList.(class)=org.springframework.web.servlet.view.velocity.VelocityView
productList.url=/WEB-INF/template/test2.vm 

還有一個(gè)種方式來使用多視圖技術(shù)渗钉,Spring支持多個(gè)視圖解析器彤恶,它會(huì)按照配置的順序依次匹配合適的視圖。通過order屬性可以指定順序鳄橘,order值越大順序越靠后声离,下面是個(gè)例子:

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/views.xml"/>
</bean>

如果到最后Spring一個(gè)視圖解析都沒有匹配上就會(huì)拋出一個(gè)ServletException

1.4.2 重定向視圖

特殊的redirect:前綴允許你完成重定向操作瘫怜。如果你返回的一個(gè)視圖名字擁有redirect:前綴术徊,UrlBasedViewResolver(及其子類)會(huì)識(shí)別出這個(gè)前綴,剩下的視圖名字會(huì)被當(dāng)做重定向的URL鲸湃。一個(gè)邏輯視圖名像這樣的:redirect:/myapp/some/resource會(huì)被重定向到當(dāng)前的Servlet上下文中赠涮,而redirect:http://myhost.com/some/arbitrary/path這個(gè)會(huì)重定向到指定的URL子寓。

1.4.3 轉(zhuǎn)發(fā)視圖

幾乎與重定向一樣,只不過使用forward:前綴笋除。

1.5 使用flash attributes

Flash attributes提供了一種方式斜友,讓一個(gè)請(qǐng)求存儲(chǔ)屬性來讓另一個(gè)請(qǐng)求使用。通常用于重定向時(shí)垃它,例如Post/Redirect/Get模式鲜屏。Flash attributes在重定向前被臨時(shí)存儲(chǔ),當(dāng)重定向后被置為可用并被立即移除国拇。

Spring MVC有兩個(gè)主要的抽象支持flash attributes洛史。FlashMap被用來承載flash attributes,FlashMapManager被用來存儲(chǔ)酱吝,取回和管理FlashMap實(shí)例也殖。

使用注解的controllers通常不需要直接使用FlashMap。一個(gè)@RequestMapping方法可以接收一個(gè)RedirectAttributes類型的參數(shù)掉瞳,在重定向場(chǎng)景使用它添加flash attributes毕源。

1.6 構(gòu)建URIs

Spring MVC提供了一種機(jī)制用來構(gòu)建并且編碼一個(gè)URI使用UriComponentsBuilderUriComponents

例如你可以拓展和編碼一個(gè)URI模版字符串:

UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}").build();

URI uri = uriComponents.expand("42", "21").encode().toUri();

注意UriComponents是不可變的陕习,expand()encode()操作返回一個(gè)新的實(shí)例霎褐。

你還可以使用一個(gè)單獨(dú)的URI components:

UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

在一個(gè)Servlet環(huán)境ServletUriComponentsBuilder子類提供了一個(gè)靜態(tài)工廠方法從一個(gè)Servlet requests中復(fù)制可用的URL信息:

HttpServletRequest request = ...

// 復(fù)用 host, scheme, port, path and query string
// 替換 the "accountId" 查詢參數(shù)

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();

1.7 Spring 文件上傳支持

Spring內(nèi)置的multipart支持在web應(yīng)用程序處理文件上傳。Spring針對(duì)使用Apache Commons FileUpload的用戶提供了一個(gè)MultipartResolver實(shí)現(xiàn)该镣,另一個(gè)實(shí)現(xiàn)是針對(duì)使用Servlet 3.0 multipart request parsing的用戶冻璃。

默認(rèn)的,Spring不處理文件上傳损合,因?yàn)橐恍╅_發(fā)者想自己處理文件上傳省艳。你可以通過添加一個(gè)multipart解析器到web應(yīng)用程序的上下文中來啟用Spring文件上傳處理。每一個(gè)請(qǐng)求都會(huì)被檢查是否包含文件上傳的部分嫁审。如果沒有找到文件上傳的部分跋炕,請(qǐng)求將會(huì)繼續(xù)。如果在一個(gè)請(qǐng)求中找到了文件上傳部分律适,MultipartResolver將會(huì)被使用辐烂。經(jīng)過處理后請(qǐng)求里的文件上傳的屬性像其他別的屬性被對(duì)待。

1.7.1 MultipartResolver與Commons FileUpload

下面的例子展示了如何配置CommonsMultipartResolver

<bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- 最大的文件尺寸(bytes) -->
    <property name="maxUploadSize" value="100000"/>

</bean>

你還需要添加Commons FileUpload的依賴:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

當(dāng)Spring DispatcherServlet發(fā)現(xiàn)一個(gè)上傳文件的請(qǐng)求捂贿,它會(huì)激活配置的MultipartResolver并把請(qǐng)求傳遞過去纠修。解析器會(huì)包裝當(dāng)前的HttpServletRequest成為一個(gè)MultipartHttpServletRequest來支持文件上傳。使用MultipartHttpServletRequest你可以獲得文件上傳數(shù)據(jù)的相關(guān)信息也可以訪問到文件上傳的數(shù)據(jù)厂僧。

1.7.2 MultipartResolver與Servlet 3.0

為了使用基于Servlet 3.0的上傳文件處理你需要在web.xml中使用"multipart-config"標(biāo)記DispatcherServlet扣草。還有編程式、使用javax.servlet.annotation.MultipartConfig注解自定義Servlet。

一旦配置好后你需要添加StandardServletMultipartResolver到你的Spring配置:

<bean id="multipartResolver"
        class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

(只要不是關(guān)于Spring的配置一般都沒有例子辰妙,感興趣可以自行搜索)

1.7.3 處理從表單上傳的文件

首先創(chuàng)建一個(gè)表單來允許用戶上傳文件:

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="/form" enctype="multipart/form-data">
            <input type="text" name="name"/>
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

下一步創(chuàng)建一個(gè)controller處理文件上傳鹰祸。

@Controller
public class FileUploadController {

    @RequestMapping(value = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

當(dāng)使用Servlet 3.0文件上傳解析時(shí)你還可以使用javax.servlet.http.Part作為方法參數(shù):

@Controller
public class FileUploadController {

    @RequestMapping(value = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") Part file) {

        InputStream inputStream = file.getInputStream();
        // store bytes from uploaded file somewhere

        return "redirect:uploadSuccess";
    }

}

1.8 處理異常

@ExceptionHandler注解可以在一個(gè)controller里或者一個(gè)有@ControllerAdvice注解的類里處理異常。定義在一個(gè)controller里只對(duì)那個(gè)controller里@RequestMapping方法產(chǎn)生的異常有效上岗;定義在@ControllerAdvice類時(shí)對(duì)所有的controller生效福荸。下面這個(gè)例子只對(duì)該controller本地生效:

@Controller
public class SimpleController {

    // @RequestMapping 方法省略 ...

    @ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleIOException(IOException ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

下面是在@ControllerAdvice注解的類中使用:

@ControllerAdvice
public class SimpleExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleIOException(Exception ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

所有Controller產(chǎn)生的異常都會(huì)被捕獲蕴坪。

自定義默認(rèn)Servlet容器錯(cuò)誤頁面

你可以在web.xml聲明一個(gè)<error-page>元素:

<error-page>
    <location>/error</location>
</error-page>

或者指定一個(gè)HTTP狀態(tài)碼:

<error-page>
    <error-code>404</error-code>
    <location>/error</location>
</error-page>

注意:實(shí)際的地址可以是一個(gè)JSP頁面或者容器內(nèi)其他URL包括一個(gè)@Controller方法肴掷。

@Controller
public class ErrorController {

    @RequestMapping(value="/error", produces="application/json")
    @ResponseBody
    public Map<String, Object> handle(HttpServletRequest request) {

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));

        return map;
    }

}

1.9 配置Spring MVC

1.9.1 啟用MVC

Java config方式

@Configuration
@EnableWebMvc
public class WebConfig {

}

MVC XML Namespace方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven />

</beans>

1.9.2 自定義默認(rèn)配置

為了使用Java自定義默認(rèn)配置你僅僅需要實(shí)現(xiàn)WebMvcConfigurer接口或者繼承WebMvcConfigurerAdapter類并重寫你需要的方法。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    protected void addFormatters(FormatterRegistry registry) {
        // Add formatters and/or converters
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // Configure the list of HttpMessageConverters to use
    }

}

自定義<mvc:annotation-driven />默認(rèn)配置:

<mvc:annotation-driven conversion-service="conversionService">
    <mvc:message-converters>
        <bean class="org.example.MyHttpMessageConverter"/>
        <bean class="org.example.MyOtherHttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <list>
            <bean class="org.example.MyFormatter"/>
            <bean class="org.example.MyOtherFormatter"/>
        </list>
    </property>
</bean>

1.9.3 攔截器

你可以配置HandlerInterceptors或者WebRequestInterceptors應(yīng)用在所有即將到來的請(qǐng)求背传,或者只匹配特殊的URL路徑呆瞻。

一個(gè)用Java注冊(cè)攔截器的例子:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleInterceptor());
        registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/").excludePathPatterns("/admin/");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }

}

或者在XML文件中使用<mvc:interceptors>元素:

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
    <mvc:interceptor>
        <mvc:mapping path="/"/>
        <mvc:exclude-mapping path="/admin/"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor" />
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

1.9.4 視圖控制器

這個(gè)一個(gè)快捷方式定義一個(gè)ParameterizableViewController當(dāng)執(zhí)行時(shí)立即跳轉(zhuǎn)到一個(gè)視圖。使用它在靜態(tài)視圖的場(chǎng)景径玖,當(dāng)在視圖生成相應(yīng)之前沒有Java controller邏輯執(zhí)行痴脾。

一個(gè)Java配置例子,使/請(qǐng)求跳轉(zhuǎn)到一個(gè)叫做home的視圖:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
    }

}

同樣的配置梳星,在XML中使用<mvc:view-controller>元素:

<mvc:view-controller path="/" view-name="home"/>

1.9.5 視圖解析器

配置一個(gè)velocity模版引擎視圖解析器:

<bean id="velocityViewResolver"
    class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
    <property name="suffix" value=".vm" />
    <property name="contentType" value="text/html;charset=utf-8" />
    <property name="exposeSessionAttributes" value="true" />
    <property name="allowSessionOverride" value="true" />
    <property name="toolboxConfigLocation" value="/WEB-INF/Toolbox.xml" />
</bean>

配置一個(gè)JSP視圖解析器:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/view/"
          p:suffix=".jsp"/>

1.9.6 靜態(tài)Resources服務(wù)

這個(gè)選項(xiàng)允許在一個(gè)特殊的URL路徑下請(qǐng)求靜態(tài)資源赞赖,由ResourceHttpRequestHandler服務(wù)于任何列表中的Resource位置。這提供了一種方便的方法從位置服務(wù)靜態(tài)資源而不是從web應(yīng)用程序根目錄冤灾,包括classpath的位置前域。處理器還會(huì)正確的計(jì)算Last-Modified頭,這樣對(duì)于那些已經(jīng)緩存在客戶端的資源一個(gè)304狀態(tài)碼將會(huì)被返回韵吨,避免不必要的間接開銷匿垄。

例如使用/resources/**URL模式來服務(wù)靜態(tài)資源,靜態(tài)資源文件目錄是web應(yīng)用程序根目錄下的public-resources文件夾归粉,使用Java配置:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
    }

}

用XML配置:

<mvc:resources mapping="/resources/**" location="/public-resources/"/>

設(shè)置一個(gè)1年的有效時(shí)間:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/").setCachePeriod(31556926);
    }

}

XML配置:

<mvc:resources mapping="/resources/**" location="/public-resources/" cache-period="31556926"/>

mapping屬性必須是一個(gè)可以被SimpleUrlHandlerMapping使用的Ant模式椿疗,location必須指定一個(gè)或多個(gè)有效的資源文件夾地址。多個(gè)資源位置可以使用逗號(hào)分割的一列值糠悼。例如使用web應(yīng)用程序的根目錄和/META-INF/public-web-resources/

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/", "classpath:/META-INF/public-web-resources/");
    }

}

使用XML配置:

<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/public-web-resources/"/>

當(dāng)一個(gè)新版本應(yīng)用程序被部署時(shí)靜態(tài)資源可能改變届榄,推薦你合并一個(gè)版本字符串到映射模式,強(qiáng)制客戶端請(qǐng)求新版本的資源倔喂。版本化的URL已經(jīng)被內(nèi)置在框架里铝条,可以通過在資源處理器里配置一個(gè)資源鏈來啟用。

內(nèi)置的VersionResourceResolver可以被配置為不同的策略滴劲。例如攻晒,一個(gè)FixedVersionStrategy可以使用一個(gè)屬性、一個(gè)日期或者其他的東西來作為版本班挖。一個(gè)ContentVersionStrategy使用一個(gè)由資源的內(nèi)容生產(chǎn)的MD5字符串作為版本鲁捏。

ContentVersionStrategy是一個(gè)不錯(cuò)的默認(rèn)的選擇,除了有些請(qǐng)求不能使用(例如,與JavaScript module loaders)给梅。你可以為不同的URL模式配置不同的版本策略假丧。請(qǐng)牢記根據(jù)內(nèi)容來計(jì)算版本的做法是消耗昂貴的,因此資源鏈緩存在產(chǎn)品環(huán)境一定要開啟动羽。

Java配置示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/")
                .addResourceLocations("/public-resources/")
                .resourceChain(true).addResolver(
                    new VersionResourceResolver().addContentVersionStrategy("/"));
    }

}

XML示例:

<mvc:resources mapping="/resources/" location="/public-resources/">
    <mvc:resource-chain>
        <mvc:resource-cache />
        <mvc:resolvers>
            <mvc:version-resolver>
                <mvc:content-version-strategy patterns="/"/>
            </mvc:version-resolver>
        </mvc:resolvers>
    </mvc:resource-chain>
</mvc:resources>

為了使上面配置生效包帚,應(yīng)用程序必須使用版本渲染URL。最簡(jiǎn)單的方式是配置ResourceUrlEncodingFilter包裝response重寫encodeURL方法运吓。這個(gè)對(duì)JSP, FreeMarker, Velocity, 和其他任何調(diào)用response encodeURL 方法的視圖技術(shù)都有效渴邦。

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拘哨,隨后出現(xiàn)的幾起案子谋梭,更是在濱河造成了極大的恐慌,老刑警劉巖倦青,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓮床,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡产镐,警方通過查閱死者的電腦和手機(jī)隘庄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來癣亚,“玉大人丑掺,你說我怎么就攤上這事√釉悖” “怎么了吼鱼?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绰咽。 經(jīng)常有香客問我菇肃,道長(zhǎng),這世上最難降的妖魔是什么取募? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任琐谤,我火速辦了婚禮,結(jié)果婚禮上玩敏,老公的妹妹穿的比我還像新娘斗忌。我一直安慰自己,他們只是感情好旺聚,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布织阳。 她就那樣靜靜地躺著,像睡著了一般砰粹。 火紅的嫁衣襯著肌膚如雪唧躲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音弄痹,去河邊找鬼饭入。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肛真,可吹牛的內(nèi)容都是我干的谐丢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蚓让,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼乾忱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凭疮,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤饭耳,失蹤者是張志新(化名)和其女友劉穎串述,沒想到半個(gè)月后执解,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纲酗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年衰腌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觅赊。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡右蕊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吮螺,到底是詐尸還是另有隱情饶囚,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布鸠补,位于F島的核電站萝风,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏紫岩。R本人自食惡果不足惜规惰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泉蝌。 院中可真熱鬧歇万,春花似錦、人聲如沸勋陪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诅愚。三九已至寒锚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背壕曼。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工苏研, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腮郊。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓摹蘑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親轧飞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衅鹿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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