關(guān)于SpringBoot的自動(dòng)配置和啟動(dòng)過程

一埃唯、簡(jiǎn)介

Spring Boot簡(jiǎn)化了Spring應(yīng)用的開發(fā)懂诗,采用約定大于配置的思想,去繁從簡(jiǎn)抛虏,很方便就能構(gòu)建一個(gè)獨(dú)立的博其、產(chǎn)品級(jí)別的應(yīng)用。

1.傳統(tǒng)J2EE開發(fā)的缺點(diǎn)

開發(fā)笨重迂猴、配置繁多復(fù)雜慕淡、開發(fā)效率低下、部署流程復(fù)雜沸毁、第三方技術(shù)集成難度大峰髓。

2.SpringBoot的優(yōu)點(diǎn)

  • 快速重建獨(dú)立運(yùn)行的Spring項(xiàng)目以及與主流框架集成。
  • 使用嵌入式的Servlet容器息尺,應(yīng)用無需打成WAR包
  • starters自動(dòng)依賴與版本控制
  • 大量的自動(dòng)配置携兵、簡(jiǎn)化開發(fā),也可以修改其默認(rèn)值
  • 無需配置XML搂誉,無代碼生成
  • 準(zhǔn)生產(chǎn)環(huán)境的運(yùn)行時(shí)應(yīng)用監(jiān)控
  • 與云計(jì)算的天然繼承

3.SpringBoot helloworld說明

1.starters

  • SpringBoot為我們提供了簡(jiǎn)化企業(yè)級(jí)開發(fā)絕大多數(shù)場(chǎng)景的starters pom(啟動(dòng)器)徐紧,只要引入了相應(yīng)場(chǎng)景的starters pom,相關(guān)技術(shù)的絕大部分配置將會(huì)消除(字段配置)炭懊,從而簡(jiǎn)化我們的開發(fā)并级。業(yè)務(wù)中我們就會(huì)使用到SpringBoot為我們字段配置的Bean。
  • 這些starters幾乎涵蓋了javaee所有常用場(chǎng)景侮腹,SpringBoot對(duì)這些場(chǎng)景依賴的jar也做了嚴(yán)格的測(cè)試和版本控制嘲碧。
  • spring-boot-dependencies里面定義了jar包的版本。

2.入口類和@SpringBootApplication

  • 程序從main方法開始運(yùn)行父阻。
  • 使用SpringApplication.run()加載主程序類
  • 主程序類需標(biāo)注@SpringBootApplication
  • @EnableAutoConfiguration是核心注解
  • @Import導(dǎo)入所有的自動(dòng)配置場(chǎng)景
  • @AutoConfigurationPackage定義默認(rèn)的包掃描規(guī)則愈涩。
  • 程序啟動(dòng)掃描主程序類所在的包以及下面所有子包的組件

3.自動(dòng)配置

自動(dòng)配置xxxAutoConfiguration

  • SpringBoot中存現(xiàn)大量的這些類,這些類的作用就是幫我們進(jìn)行自動(dòng)裝配
  • 它會(huì)將這個(gè)場(chǎng)景需要的所有組件都注冊(cè)到容器中加矛,并配置好
  • 他們?cè)陬惵窂较碌腗ETA-INF/spring.factories文件中
  • spring-boot-autoconfigure.jar中包含了所有場(chǎng)景的字段配置類代碼
  • 這些自動(dòng)配置類是SpringBoot進(jìn)行自動(dòng)裝配的關(guān)鍵钠署。

二、SpringBoot配置

1.配置文件

  • SpringBoot使用一個(gè)全局的配置文件荒椭。配置文件名是固定的谐鼎。
    -application.properties或者application.yml

  • 配置文件放在src/main/resources目錄或者類路徑/config下。

  • 全局配置文件的作用是對(duì)一些默認(rèn)配置進(jìn)行修改

2.配置文件值注入

  • @Value和@ConfigurationProperties為屬性注入值進(jìn)行對(duì)比
對(duì)比點(diǎn) @ConfigurationProperties @Value
功能 批量注入配置文件中的屬性 一個(gè)個(gè)指定
松散綁定(松散語(yǔ)法) 支持 不支持
SpEL 不支持 支持
JSR303數(shù)據(jù)校驗(yàn) 支持 不支持
復(fù)雜類型封裝 支持 不支持
  • 屬性名匹配規(guī)則
    -person.firstName 使用標(biāo)準(zhǔn)方式
    -person.first-name 使用-
    -person.first_name 使用_
    -PERSON_FIRST_NAME 推薦系統(tǒng)屬性使用這種寫法

  • @PropertySource
    加載指定的配置文件

  • ConfigurationProperties
    -與@Bean結(jié)合為屬性賦值
    -與@PropertySource(只能用于properties文件)結(jié)合讀取指定文件。

  • ConfigurationProperties Validation
    -支持JSR303進(jìn)行配置文件值校驗(yàn)狸棍。


@Component
@PropertySource(value={"classpath:person.properties"})
@ConfigurationProperties(prefix="person")
@Validated
public class Person{
    @Email
    @Value("${person.email}")
    private String email身害;
}
  • ImportResource讀取外部配置文件

3.配置文件占位符

  • RandomValuePropertySource
    配置文件中可以使用隨機(jī)數(shù)
    -${random.value}
    -${random.int}
    -${random.long}
    -${random.int(10)}
    -${random.int[1024,65536]}

  • 屬性配置占用符
    -可以在配置文件中引用前面配置過的屬性(Y優(yōu)先級(jí)前面配置過的這里都可以使用)。
    -${app.name:默認(rèn)值}來指定找不到屬性時(shí)的默認(rèn)值草戈。

app.name=MyApp    
app.description=${app.name} is a SpringBoot Application

4.profile

profile是Spring對(duì)不同環(huán)境提供不同配置功能的支持塌鸯,可以通過激活指定參數(shù)的方式快速切換環(huán)境。

1.多profile文件形式

  • 格式:application-{profile}.properties/yml
    application-dev.properties唐片、application-prod.properties

2.多profile文檔塊模式

spring.profiles.active=prod #激活指定配置
spring.profiles=prod
server.port=80
# default表示未指定時(shí)的默認(rèn)配置
spring.profiles=default
server.port=8080

3.激活方式

  • 命令行:--spring.profiles.active=dev
  • 配置文件:spring.profiles.active=dev
  • jvm參數(shù):-Dspring.profiles.active=dev

5.配置文件加載位置

SpringBoot啟動(dòng)會(huì)掃描一下位置的application.properties或者application.yml文件作為SpringBoot的默認(rèn)配置文件丙猬。
- file:./config/
- file:./
- classpath:/config/
-classpath:/
-以上是按照優(yōu)先級(jí)從高到低的順序,所有位置的文件都會(huì)被加載费韭,高優(yōu)先級(jí)配置內(nèi)容會(huì)覆蓋低優(yōu)先級(jí)配置內(nèi)容茧球。
-可以通過配置spring.config.location來改變默認(rèn)配置。

6.外部配置加載順序

  1. 命令行參數(shù)
  2. 來自java:comp/env的JNDI屬性
  3. Java系統(tǒng)屬性(System.getProperties())
  4. 操作系統(tǒng)環(huán)境變量
  5. RandomValuePropertySource配置的random.*屬性值
  6. jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
  7. jar包內(nèi)部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
  8. jar包外部的application.properties或application.yml(不帶spring.profile)配置文件
  9. jar包內(nèi)部的application.properties或application.yml(不帶spring.profile)配置文件
  10. @Configuration注解類上的@PropertySource星持。
  11. 通過SpringApplication.setDefaultproperties指定的默認(rèn)屬性抢埋。

7.自動(dòng)配置原理

1.SpringBoot啟動(dòng)的時(shí)候加載主配置類,開啟了自動(dòng)配置功能@EnableAutoConfiguration

2.@EnableAutoConfiguration作用

  • 利用EnableAutoConfigurationImportSelector給容器中導(dǎo)入一些組件督暂。
  • 將類路徑小META-INF/spring.factories里面配置的所有EnableAutoConfiguration的值加入到了容器中揪垄。

3.@Conditional派生注解

@Conditional擴(kuò)展注解 作用(判斷是否滿足當(dāng)期指定條件)
@ConditionalOnJava 系統(tǒng)的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 滿足SpEL表達(dá)式指定
@ConditionalOnClass 系統(tǒng)中有指定的類
@ConditionalOnMissingClass 容器中沒有指定類
@ConditionalOnSingleCandidate 容器中只有一個(gè)指定的Bean,或者這個(gè)Bean是首選Bean
@ConditionalOnProperty 系統(tǒng)中指定的屬性是否有指定的值
@ConditionalOnResource 類路徑下是否存在指定資源文件
@ConditionalOnWebApplication 當(dāng)前是web環(huán)境
@ConditionalOnNotWebApplication 當(dāng)前不是web環(huán)境
@ConditionalOnJndi JNDI存在指定項(xiàng)
  • 作用:必須是@Conditional指定的條件成立逻翁,才給容器中添加組件饥努,配置配里面的所有內(nèi)容才生效。

三八回、SpringBoot與日志

1.日志框架

市場(chǎng)上存在非常多的日志框架酷愧,JUL(java.util.logging)、JCL(Apache Commons Logging)辽社、Log4J、Log4J2翘鸭、Logback滴铅、SLF4j、jboss-logging等就乓。

  • SpringBoot早框架內(nèi)部使用JCL汉匙。spring-boot-starter-logging采用了slf4j+logback的形式,SpringBoot也能自動(dòng)配置(jul生蚁、log4j2噩翠、logback)并簡(jiǎn)化配置。
日志門面 日志實(shí)現(xiàn)
JCL邦投、SLF4J伤锚、jboss-logging log4j、JUL志衣、Log4j2屯援、Logback
日志系統(tǒng) 配置文件
Logback logback-spring.xml猛们、logback-spring.groovy、logback.xml或logback.groovy
Log4j2 log4j2-spring.xml狞洋、log4j2.xml
JUL logging.properties
image.png
  • 總結(jié):
    1.SpringBoot底層也是使用slf4j+logback的方式進(jìn)行日志記錄弯淘。
    2.SpringBoot也把其他的日志都替換成了slf4j。
    3.如果要引入其他日志框架吉懊,要排除Spring框架的commons-logging依賴庐橙。

四、Web開發(fā)

1.SpringBoot對(duì)靜態(tài)資源的映射規(guī)則

  • 所有/webjars/**,都去classpath:/META-INF/resources/webjars/ 下面找資源借嗽。
  • "/**" 訪問當(dāng)前項(xiàng)目的任何資源态鳖,都去(靜態(tài)資源的文件夾)找映射。
  • 歡迎頁(yè)淹魄;靜態(tài)資源文件夾下的所有index.html頁(yè)面郁惜,被"/**" 映射。
  • 所有的 **/favicon.ico都是在靜態(tài)資源文件下找甲锡。

2.SpringMVC自動(dòng)配置

1.SpringMVC auto-configuration

SpringBoot對(duì)SpringMVC的默認(rèn)配置(WebMvcAutoConfiguration)如下:

  • 包含了ContentNegotiatingViewResolver和BeanNameViewResolver兆蕉。

    • 自動(dòng)配置了ViewResolver
    • ContentNegotiatingViewResolver:組合所有的視圖解析器
  • 支持靜態(tài)資源,包括支持Wenjars

  • 靜態(tài)首頁(yè)訪問

  • 支持favicon.ico

  • 自動(dòng)注冊(cè)了Converter缤沦、GenericConverter虎韵、Formatter。

    • Converter:轉(zhuǎn)換器缸废。
    • Formatter:格式化器
  • 支持HttpMessageConverters

    • HttpMessageConverter:SpringMVC用來轉(zhuǎn)換Http請(qǐng)求和響應(yīng)包蓝。
    • HttpMessageConverters:從容器中確定,獲取所有的HttpMessageConverter企量;
  • 自動(dòng)注入MessageCodesResolver测萎,定義錯(cuò)誤碼生成規(guī)則。

  • 自動(dòng)使用ConfigurableWebBindingInitializer届巩。

2.擴(kuò)展SpringMVC

編寫一個(gè)配置類(@Configuration)硅瞧,是WebMvcConfigurerAdapter類型,不能標(biāo)注@EnableWebMvc注解

@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

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

原理

  1. WebMvcAutoConfiguration是SpringMVC的自動(dòng)配置類恕汇。
  2. 在做其他自動(dòng)配置時(shí)會(huì)導(dǎo)入腕唧。
  3. 容器中所有的WebMvcConfigurer都會(huì)一起被注冊(cè)。
  4. 我們自定義的配置類也會(huì)被調(diào)用瘾英。

3.全面接管SpringMVC

如果想要使SpringMVC的自動(dòng)配置失效枣接,只需要在我們自定義的配置類中添加@EnableWebMvc注解即可。

@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

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

原理

  1. @EnableWebMvc的注解
@Import(DelegatingWebMvcConfiguation.class)
public @interface EnableWebMvc{}
  1. DelegatingWebMvcConfiguation
@Configuration
public class DelegatingWebMvcConfiguation extend WebMvcConfigurationSupport{}
  1. WebMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class,DispatcherServlet.class,
                      WebMvcConfigurerAdapter.class})
//容器中沒有這個(gè)組件缺谴,這個(gè)自動(dòng)配置類才會(huì)生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class,
                ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration{}
  1. @EnableWebMvc會(huì)將WebMvcConfigurationSupport組件導(dǎo)入進(jìn)來但惶。
  2. 導(dǎo)入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。

4.修改默認(rèn)配置

  1. SpringBoot在自動(dòng)配置很多組件的時(shí)候,顯卡容器中有沒有用戶自己配置的(@Bean 榆骚、@Component),如果有就用用戶配置的片拍,如果沒有,才會(huì)進(jìn)行自動(dòng)配置妓肢;如果某些組件可以有多個(gè)捌省,將用戶配置的和自己默認(rèn)的組合起來。
  2. 在SpringBoot中有許多的xxxConfigurer幫助我們進(jìn)行擴(kuò)展配置
  3. 在SpringBoot中有許多的xxxCustomizer幫助我們進(jìn)行定制配置

5.默認(rèn)訪問首頁(yè)

使用自定義WebMvcConfigurationAdapter進(jìn)行配置

//使用WebMvcConfigurationAdapter可以擴(kuò)展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //瀏覽器發(fā)送/desperado 請(qǐng)求來到success
        registry.addViewController("/desperado").setViewName("success");
    }

    //所有的webMvcConfigurerAdapter組件都會(huì)一起起作用
    @Bean   //將組件注冊(cè)到容器
    public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
        WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                //配置默認(rèn)路徑的頁(yè)面
                registry.addViewController("/").setViewName("login");
                registry.addViewController("/index.html").setViewName("login");
            }
        };
        return adapter;
    }
}

6.國(guó)際化

1.編寫國(guó)際化配置文件
編寫不同語(yǔ)言的配置文件碉钠,比如login.properties纲缓、login_en_US.properties、login_zh_CN.properties等喊废。

  1. SpringBoot自動(dòng)配置好了管理國(guó)際化資源文件的組件祝高。
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(
        prefix = "spring.messages"
    )
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
            //設(shè)置國(guó)際化資源文件的基礎(chǔ)名(去掉語(yǔ)言國(guó)家代碼)
      
                messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }

        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }

        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }

        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }

原理
根據(jù)請(qǐng)求頭帶來的區(qū)域信息獲取Locale進(jìn)行國(guó)際化。

五污筷、錯(cuò)誤處理機(jī)制

1.默認(rèn)的錯(cuò)誤處理機(jī)制

  1. 瀏覽器工闺,默認(rèn)返回一個(gè)默認(rèn)的錯(cuò)誤頁(yè)面。
  2. 其他客戶端瓣蛀,默認(rèn)響應(yīng)一個(gè)json數(shù)據(jù)陆蟆。

原理

  1. 在DefaultErrorAttributes中獲取錯(cuò)誤頁(yè)面的信息
public class DefaultErrorAttributes implements ErrorAttributes {
 
    //獲取錯(cuò)誤頁(yè)面的信息
    public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        Throwable error = this.getError(request);
        HttpStatus errorStatus = this.determineHttpStatus(error);
        errorAttributes.put("status", errorStatus.value());
        errorAttributes.put("error", errorStatus.getReasonPhrase());
        errorAttributes.put("message", this.determineMessage(error));
        this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
        return errorAttributes;
    }
}
  1. 在BasicErrorController中處理/error請(qǐng)求
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
   
    //產(chǎn)生html類型的數(shù)據(jù),瀏覽器發(fā)送的請(qǐng)求來打這個(gè)方法處理
    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
       //去哪個(gè)頁(yè)面作為錯(cuò)誤頁(yè)面惋增。包含頁(yè)面地址和頁(yè)面內(nèi)容
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

   //產(chǎn)生json數(shù)據(jù)叠殷,其他客戶端來到這個(gè)方法處理
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
}
  1. ErrorPageCustomizer進(jìn)行錯(cuò)誤配置
public class ErrorProperties {
    @Value("${error.path:/error}")
    private String path = "/error";
    private boolean includeException;
    private ErrorProperties.IncludeStacktrace includeStacktrace;
    private final ErrorProperties.Whitelabel whitelabel;
}
  1. ErrorMvcAutoConfiguration生成錯(cuò)誤頁(yè)面
public class ErrorMvcAutoConfiguration {
    
    private static class StaticView implements View {
        private static final Log logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);

        private StaticView() {
        }

        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            if (response.isCommitted()) {
                String message = this.getMessage(model);
                logger.error(message);
            } else {
                StringBuilder builder = new StringBuilder();
                Date timestamp = (Date)model.get("timestamp");
                Object message = model.get("message");
                Object trace = model.get("trace");
                if (response.getContentType() == null) {
                    response.setContentType(this.getContentType());
                }

                builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>There was an unexpected error (type=").append(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
                if (message != null) {
                    builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
                }

                if (trace != null) {
                    builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
                }

                builder.append("</body></html>");
                response.getWriter().append(builder.toString());
            }
        }

        private String htmlEscape(Object input) {
            return input != null ? HtmlUtils.htmlEscape(input.toString()) : null;
        }

        private String getMessage(Map<String, ?> model) {
            Object path = model.get("path");
            String message = "Cannot render error page for request [" + path + "]";
            if (model.get("message") != null) {
                message = message + " and exception [" + model.get("message") + "]";
            }

            message = message + " as the response has already been committed.";
            message = message + " As a result, the response may have the wrong status code.";
            return message;
        }

        public String getContentType() {
            return "text/html";
        }
    }

5.DefaultErrorViewResolver解析頁(yè)面

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
  
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }

        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //默認(rèn)去找一個(gè)頁(yè), error/404
        String errorViewName = "error/" + viewName; 
        //模板引擎可以解析這個(gè)頁(yè)面地址就要模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        //模板引擎可用的情況下返回到errorViewName指定的視圖地址
        //模板引擎不可以诈皿,就在靜態(tài)資源文件夾下找對(duì)應(yīng)的頁(yè)面
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

2.錯(cuò)誤頁(yè)面的優(yōu)先級(jí)(自定義錯(cuò)誤頁(yè)面)

  1. 在模板引擎的情況下林束,error/狀態(tài)碼(將錯(cuò)誤頁(yè)面命名為 錯(cuò)誤狀態(tài)碼.html 放在模板引擎文件夾里面的error文件夾下),發(fā)生此狀態(tài)碼的錯(cuò)誤就會(huì)來到對(duì)應(yīng)的頁(yè)面稽亏;
  2. 沒有模板引擎(模板引擎找不到錯(cuò)誤頁(yè)面),就在靜態(tài)資源文件夾下找壶冒。
  3. 以上都沒有錯(cuò)誤頁(yè)面,就是默認(rèn)來到SpringBoot默認(rèn)的錯(cuò)誤提示頁(yè)面截歉。

3.如何定制錯(cuò)誤的json數(shù)據(jù)

  1. 自定義異常處理&返回定制json數(shù)據(jù)(沒有自適應(yīng)效果)
@ControllerAdvice
public class MyExceptionHandler  {
    
    @ResponseBody
    @ExceptionHandler(CustomException.class)
    public Map<String,Object> handleException(Exception e){
        HashMap<String, Object> map = new HashMap<>();
        map.put("code","錯(cuò)誤信息");
        map.put("message",e.getMessage());
        return map;
    }
}

2.轉(zhuǎn)發(fā)到/error進(jìn)行自適應(yīng)響應(yīng)效果處理胖腾。

@ControllerAdvice
public class MyExceptionHandler  {

    @ResponseBody
    @ExceptionHandler(CustomException.class)
    public String handleException(Exception e, HttpServletRequest request){
        HashMap<String, Object> map = new HashMap<>();
        //傳入我們自己的錯(cuò)誤狀態(tài)碼,否則就不會(huì)進(jìn)入定制錯(cuò)誤頁(yè)面的解析流程
        request.setAttribute("java.servlet.error.status_code","500");
        map.put("code","錯(cuò)誤信息");
        map.put("message",e.getMessage());
        //轉(zhuǎn)發(fā)到/error
        return "forward:/error";
    }
}

4.將定制的數(shù)據(jù)發(fā)送出去

出現(xiàn)錯(cuò)誤之后怎披,回來到/error請(qǐng)求胸嘁,會(huì)被BasicErrorController處理瓶摆,響應(yīng)出去可以獲取的數(shù)據(jù)是由getErrorAttributes得到的凉逛。

  1. 編寫一個(gè)ErrorController的實(shí)現(xiàn)類(或者是編寫AbstractErrorController的子類),放到容器中群井。
  2. 頁(yè)面上能用的數(shù)據(jù)状飞,或者是json返回能用的數(shù)據(jù)都是通過errorAttributes.getErrorAttributes得到;容器中DefaultErrorAttributes.getErrorAttributes()默認(rèn)進(jìn)行數(shù)據(jù)處理.
//給容器中加入自定義的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //獲取ErrorAttributes的map
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        //加入自己屬性字段
        map.put("name","desperado");
        return map;
    }
}

六、配置嵌入式Servlet容器

SpringBoot默認(rèn)使用Tomcat作為內(nèi)嵌的Servlet容器

image.png

1.修改Servlet容器的配置

在配置文件application文件中修改和server有關(guān)的配置诬辈。

server.port=8081
server.context_path=/crud
server.tomcat.uri-encoding=utf-8

2. 定制Servlet容器的相關(guān)配置

編寫一個(gè)EmbeddedServletContainerCustomizer(2.x中使用WebServerFactoryCustomizer)酵使,來修改Servlet容器的配置。

@Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                 factory.setPort(8081);
            }
        };
    }

3.注冊(cè)Servlet三大組件

由于SpringBoot是默認(rèn)以jar包的方式啟動(dòng)內(nèi)嵌的Servlet容器來啟動(dòng)SpringBoot的web應(yīng)用焙糟,沒有web.xml文件口渔。所以注冊(cè)Servlet、Filter穿撮、Listener的方式也不同

1. 注入Servlet

    @Bean
    public ServletRegistrationBean<MyServlet> myServlet(){
        ServletRegistrationBean<MyServlet> registrationBean = 
                new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
        return registrationBean;
    }

2. 注入Filter

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter(){
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new MyFilter());
        registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
        return registrationBean;
    }

3. 注入Listener

    @Bean
    public ServletListenerRegistrationBean<MyListener> myListener(){
        ServletListenerRegistrationBean<MyListener> registrationBean =
                new ServletListenerRegistrationBean<>(new MyListener());
        return registrationBean;
    }

4.替換為其他嵌入式Servlet容器

替換為其他的Servlet非常簡(jiǎn)單缺脉,只需要在pom中引入其依賴,然后排除tomcat的依賴即可.

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

5. 嵌入式Servlet容器自動(dòng)配置原理

  1. EmbeddedServletContainerAutoConfiguration(2.x對(duì)應(yīng)ServletWebServerFactoryConfiguration):嵌入式容器的自動(dòng)配置
@Configuration
class ServletWebServerFactoryConfiguration {
    ServletWebServerFactoryConfiguration() {
    }

    @Configuration
    @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedUndertow {
        public EmbeddedUndertow() {
        }

        @Bean
        public UndertowServletWebServerFactory undertowServletWebServerFactory() {
            return new UndertowServletWebServerFactory();
        }
    }

    @Configuration
    @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedJetty {
        public EmbeddedJetty() {
        }

        @Bean
        public JettyServletWebServerFactory JettyServletWebServerFactory() {
            return new JettyServletWebServerFactory();
        }
    }

    @Configuration
   //判斷當(dāng)前是否引入了tomcat依賴
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class}) 
    ///判斷當(dāng)前容器沒有用戶自己定義ServletWebServerFactory:
    //嵌入式的Servlet容器工廠悦穿;作用:創(chuàng)建嵌入式的Servlet容器

    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedTomcat {
        public EmbeddedTomcat() {
        }

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }
    }
}
  1. 嵌入式Servlet容器工廠
image.png
  1. 嵌入式的Servlet容器
image.png

4.以tomcat為例

public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        return thisb.getTomcatWebServer(tomcat);
    }

  1. 容器中導(dǎo)入 WebServerFactoryCustomizerBeanPostProcessor

public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
  
     //初始化之前   
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //如果當(dāng)前初始化的是一個(gè)WebServerFactory類型的組件
        if (bean instanceof WebServerFactory) {
            this.postProcessBeforeInitialization((WebServerFactory)bean);
        }

        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
       //  獲取所有的定制器攻礼,調(diào)用每一個(gè)定制器的customize方法來給servlet容器進(jìn)行屬性賦值
        ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
            customizer.customize(webServerFactory);
        });
    }

    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
        if (this.customizers == null) {
            // 定制Servlet容器,給容器中可以添加一個(gè)WebServerFactoryCustomizer類型的組件
            this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }

        return this.customizers;
    }

    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
      //從容器中獲取所有這個(gè)類型的組件:WebServerFactoryCustomizer
        return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
    }
}

總結(jié)

  1. SpringBoot根據(jù)導(dǎo)入的依賴情況栗柒,給容器中添加相應(yīng)的WebServerFactory【TomcatServletWebServerFactory】礁扮。
  2. 容器中某個(gè)組件要?jiǎng)?chuàng)建對(duì)象就會(huì)使用WebServerFactoryCustomizerBeanPostProcessor后置處理器,只要是嵌入式的Servlet工廠瞬沦,后置處理器就會(huì)進(jìn)行處理太伊。
  3. 后置處理器從容器中獲取所有的WebServerFactoryCustomizer,調(diào)用定制器的定制方法

6.嵌入式Servlet容器啟動(dòng)過程

  1. SpringBoot啟動(dòng)運(yùn)行run方法蛙埂。
  2. 調(diào)用refreshContext(context);刷新IOC容器【創(chuàng)建IOC容器倦畅,并初始化容器,創(chuàng)建容器中的每一個(gè)組件】绣的;如果是web應(yīng)用創(chuàng)建AnnotationConfigServletWebServerApplicationContext叠赐,如果是reactive應(yīng)用創(chuàng)建AnnotationConfigReactiveWebServerApplicationContext,否則創(chuàng)建AnnotationConfigApplicationContext屡江。
 protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }
  1. 調(diào)用refresh(context);刷新上面創(chuàng)建好的IOC容器
 public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            //準(zhǔn)備刷新的context
            this.prepareRefresh();
            //調(diào)用子類去刷新內(nèi)部的實(shí)例工廠
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            //準(zhǔn)備在這個(gè)context中要使用的實(shí)例工廠
            this.prepareBeanFactory(beanFactory);

            try {
                
                // 允許在上下文子類中對(duì)bean工廠進(jìn)行后置處理芭概。
                this.postProcessBeanFactory(beanFactory);
                //在context中調(diào)用注冊(cè)為bean的工廠處理器。
                this.invokeBeanFactoryPostProcessors(beanFactory);
                //注冊(cè)攔截bean創(chuàng)建的bean處理器惩嘉。      
                this.registerBeanPostProcessors(beanFactory);
                //初始化此context的消息源
                this.initMessageSource();
                //初始化此上下文的事件多播器罢洲。
                this.initApplicationEventMulticaster();
                //在特定的context子類中初始化其他特殊bean。
                this.onRefresh();
                // 檢查監(jiān)聽器bean并注冊(cè)它們文黎。
                this.registerListeners();
                // 實(shí)例化所有剩余(非延遲初始化)單例惹苗。
                this.finishBeanFactoryInitialization(beanFactory);
                //最后一步:發(fā)布相應(yīng)的事件。
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                //摧毀已經(jīng)創(chuàng)建的單例以避免占用資源耸峭。
                this.destroyBeans();
                //重置 ‘a(chǎn)ctive’ 標(biāo)志
                this.cancelRefresh(var9); 
                //Propagate exception to caller.
                throw var9;
            } finally {
              //從我們開始桩蓉,重置Spring核心中的常見內(nèi)省緩存
              //可能不再需要單例bean的元數(shù)據(jù)了...
                this.resetCommonCaches();
            }

        }
    }
  1. 調(diào)用onRefresh();web的IOC容器重寫了onRefresh方法。
  2. Web IOC容器創(chuàng)建嵌入式的Servlet容器劳闹。
private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = this.getWebServerFactory();
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }

        this.initPropertySources();
    }
  1. 獲取嵌入式的Servlet容器工廠: ServletWebServerFactory factory = this.getWebServerFactory();從IOC容器中獲取ServletWebServerFactory組件院究;

  2. 使用容器工廠獲取嵌入式的Servlet容器:his.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()})洽瞬;

public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat);
    }
  1. 嵌入式的Servlet容器創(chuàng)建并啟動(dòng)Servlet容器。

9.先啟動(dòng)嵌入式的Servlet容器业汰,再將IOC容器中剩余的沒有創(chuàng)建出來的對(duì)象獲取出來伙窃、IOC容器啟動(dòng)就會(huì)創(chuàng)建嵌入式的Servlet容器。

7.使用外置的Servlet容器

1. 嵌入式Servlet容器優(yōu)缺點(diǎn)

優(yōu)點(diǎn):簡(jiǎn)單样漆、便捷为障。
缺點(diǎn):默認(rèn)不支持JSP,優(yōu)化定制比較復(fù)雜放祟。

2.使用外部Servlet容器步驟

  1. 必須創(chuàng)建一個(gè)war項(xiàng)目产场。
  2. 將嵌入式的Tomcat指定為provided。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐tomcat</artifactId>
    <scope>provided</scope>
</dependency>

  1. 必須編寫一個(gè)SpringBootServletInitializer的子類舞竿,并調(diào)用configure方法京景。
public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //傳入SpringBoot應(yīng)用的主程序
        return application.sources(SpringBoot04WebJspApplication.class);
  }
}

  1. 啟動(dòng)服務(wù)器就可以了。

3.原理與規(guī)則

原理
啟動(dòng)服務(wù)器骗奖,服務(wù)器啟動(dòng)SpringBoot應(yīng)用[SpringBootServletInitializer],啟動(dòng)IOC容器确徙。

規(guī)則

  1. 服務(wù)器啟動(dòng)會(huì)創(chuàng)建當(dāng)前web應(yīng)用里面每一個(gè)jar包里面的ServletContainerInitializer實(shí)例。
  2. ServletContainerInitializer的實(shí)現(xiàn)放在jar包的META-INF/services文件下执桌,有一個(gè)名為javax.servlet.ServletContainerInitializer的文件鄙皇,內(nèi)容就是ServletContainerInitializer實(shí)現(xiàn)類的全類名。
  3. 還可以使用@HandlerType仰挣,在啟動(dòng)應(yīng)用時(shí)加載指定的類伴逸。

4. 啟動(dòng)流程

  1. 啟動(dòng)tomcat。
  2. 加載spring-web包下META-INF/services下面的javax.servlet.ServletContainerInitializer文件膘壶。
image.png
  1. SpringServletContainerInitializer將@HandlerType標(biāo)注的所有這個(gè)類型的類都傳入搭配onStartup方法的Set中错蝴,為這些WebApplicationInitializer類型的類創(chuàng)建實(shí)例。
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}
  1. 每個(gè)WebApplicationInitializer到調(diào)用自己的onStartup()方法颓芭。
image.png
  1. 相當(dāng)于SpringBootServletInitializer的類會(huì)被創(chuàng)建對(duì)象顷锰,并執(zhí)行onStartup()方法。

  2. SpringBootServletInitializer實(shí)例執(zhí)行onStartup的時(shí)候會(huì)crateRootApplicationContext創(chuàng)建容器亡问。

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        // 1.創(chuàng)建SpringApplicationBuilder
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        // 2.調(diào)用configure方法官紫,子類重新了這個(gè)方法,將SpringBoot的主程序類傳入
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
        // 3.使用builder創(chuàng)建一個(gè)Spring應(yīng)用
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        //確保錯(cuò)誤頁(yè)被注冊(cè)
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
        // 4. q啟動(dòng)Spring應(yīng)用
        return this.run(application);
    }
  1. Spring的應(yīng)用啟動(dòng)并且創(chuàng)建IOC容器州藕。

  2. 先啟動(dòng)Servlet容器束世,再啟動(dòng)SpringBoot應(yīng)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末床玻,一起剝皮案震驚了整個(gè)濱河市毁涉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笨枯,老刑警劉巖薪丁,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異馅精,居然都是意外死亡严嗜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門洲敢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漫玄,“玉大人,你說我怎么就攤上這事压彭∧烙牛” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵壮不,是天一觀的道長(zhǎng)汗盘。 經(jīng)常有香客問我,道長(zhǎng)询一,這世上最難降的妖魔是什么隐孽? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮健蕊,結(jié)果婚禮上菱阵,老公的妹妹穿的比我還像新娘。我一直安慰自己缩功,他們只是感情好晴及,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫡锌,像睡著了一般虑稼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上势木,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天动雹,我揣著相機(jī)與錄音,去河邊找鬼跟压。 笑死胰蝠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的震蒋。 我是一名探鬼主播茸塞,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼查剖!你這毒婦竟也來了钾虐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤笋庄,失蹤者是張志新(化名)和其女友劉穎效扫,沒想到半個(gè)月后倔监,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菌仁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年浩习,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片济丘。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谱秽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摹迷,到底是詐尸還是另有隱情疟赊,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響趟妥,放射性物質(zhì)發(fā)生泄漏跌造。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦鼠证、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至颂碧,卻和暖如春荠列,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背载城。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工肌似, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诉瓦。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓川队,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親睬澡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子固额,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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