08 CRUD

5)、CRUD-员工列表

实验要求:

1)、RestfulCRUD:CRUD满足Rest风格;

URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作

普通CRUD(uri来区分操作)RestfulCRUD

查询getEmpemp---GET

添加addEmp?xxxemp---POST

修改updateEmp?id=xxx&xxx=xxemp/{id}---PUT

删除deleteEmp?id=1emp/{id}---DELETE

2)、实验的请求架构;

实验功能请求URI请求方式

查询所有员工empsGET

查询某个员工(来到修改页面)emp/1GET

来到添加页面empGET

添加员工empPOST

来到修改页面(查出员工进行信息回显)emp/1GET

修改员工empPUT

删除员工emp/1DELETE

3)、员工列表:

thymeleaf公共页面元素抽取

1、抽取公共片段©

2011 The Good Thymes Virtual

Grocery2、引入公共片段~{templatename::selector}:模板名::选择器~{templatename::fragmentname}:模板名::片段名3、默认效果:insert的公共片段在div标签中如果使用th:insert等属性进行引入,可以不用写~{}:行内写法可以加上:[[~{}]];[(~{})];

三种引入公共片段的th属性:

th:insert:将公共片段整个插入到声明引入的元素中

th:replace:将声明引入的元素替换为公共片段

th:include:将被引入的片段的内容包含进这个标签中

© 2011 The Good Thymes Virtual Grocery引入方式效果

© 2011 The Good Thymes Virtual Grocery

© 2011 The Good Thymes Virtual Grocery

© 2011 The Good Thymes Virtual Grocery

引入片段的时候传入参数:

Dashboard(current)

6)、CRUD-员工添加

添加页面

LastNameEmailGender

男女department12345Birth添加

提交的数据格式不对:生日:日期;

2017-12-12;2017/12/12;2017.12.12;

日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型;

2017-12-12---Date; 类型转换,格式化;

默认日期是按照/的方式;

7)、CRUD-员工修改

修改添加二合一表单

<!--发送put请求修改员工数据--><!--

1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)

2、页面创建一个post表单

3、创建一个input项,name="_method";值就是我们指定的请求方式

-->LastNameEmailGender

男女department1Birth添加

8)、CRUD-员工删除

[[${emp.lastName}]]编辑删除

7、错误处理机制

1)、SpringBoot默认的错误处理机制

默认效果:

​ 1)、浏览器,返回一个默认的错误页面

浏览器发送请求的请求头:

​ 2)、如果是其他客户端,默认响应一个json数据

原理:

​ 可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;

给容器中添加了以下组件

​ 1、DefaultErrorAttributes:

帮我们在页面共享信息;@OverridepublicMapgetErrorAttributes(RequestAttributes

requestAttributes,booleanincludeStackTrace){MaperrorAttributes

=newLinkedHashMap();errorAttributes.put("timestamp",newDate());addStatus(errorAttributes,

requestAttributes);addErrorDetails(errorAttributes, requestAttributes,

includeStackTrace);addPath(errorAttributes,

requestAttributes);returnerrorAttributes;}

​ 2、BasicErrorController:处理默认/error请求

@Controller@RequestMapping("${server.error.path:${error.path:/error}}")publicclassBasicErrorControllerextendsAbstractErrorController{@RequestMapping(produces

="text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理publicModelAndViewerrorHtml(HttpServletRequest

request,

HttpServletResponse

response){HttpStatus status = getStatus(request);Mapmodel =

Collections.unmodifiableMap(getErrorAttributes(request,

isIncludeStackTrace(request,

MediaType.TEXT_HTML)));response.setStatus(status.value());//去哪个页面作为错误页面;包含页面地址和页面内容ModelAndView

modelAndView = resolveErrorView(request, response, status,

model);return(modelAndView ==null?newModelAndView("error", model) :

modelAndView);}@RequestMapping@ResponseBody//产生json数据,其他客户端来到这个方法处理;publicResponseEntity>

error(HttpServletRequest request) {Mapbody =

getErrorAttributes(request,isIncludeStackTrace(request,

MediaType.ALL));HttpStatus status =

getStatus(request);returnnewResponseEntity>(body, status);}

​ 3、ErrorPageCustomizer:

@Value("${error.path:/error}")privateString path ="/error";  系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)

​ 4、DefaultErrorViewResolver:

@OverridepublicModelAndViewresolveErrorView(HttpServletRequest request, HttpStatus status,

Map<String, Object> model){ModelAndView modelAndView =

resolve(String.valueOf(status), model);if(modelAndView ==null&&

SERIES_VIEWS.containsKey(status.series())) {modelAndView =

resolve(SERIES_VIEWS.get(status.series()),

model);}returnmodelAndView;}privateModelAndViewresolve(String viewName,

Map<String, Object> model){//默认SpringBoot可以去找到一个页面? 

error/404String errorViewName ="error/"+

viewName;//模板引擎可以解析这个页面地址就用模板引擎解析TemplateAvailabilityProvider provider

=this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext);if(provider

!=null)

{//模板引擎可用的情况下返回到errorViewName指定的视图地址returnnewModelAndView(errorViewName,

model);}//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面  

error/404.htmlreturnresolveResource(errorViewName, model);}

​ 步骤:

​一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;

​1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;

protectedModelAndViewresolveErrorView(HttpServletRequest request,

    HttpServletResponse response, HttpStatus status, Map<String,

Object>

model){//所有的ErrorViewResolver得到ModelAndViewfor(ErrorViewResolver

resolver :this.errorViewResolvers) {      ModelAndView modelAndView =

resolver.resolveErrorView(request, status, model);if(modelAndView

!=null) {returnmodelAndView;      }   }returnnull;}

2)、如果定制错误响应:

1)、如何定制错误的页面;

1)、有模板引擎的情况下;error/状态码;【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;

​ 我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);

​ 页面能获取的信息;

​ timestamp:时间戳

​ status:状态码

​ error:错误提示

​ exception:异常对象

​ message:异常消息

​ errors:JSR303数据校验的错误都在这里

​ 2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;

​ 3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;

2)、如何定制错误的json数据;

​ 1)、自定义异常处理&返回定制json数据;

@ControllerAdvicepublicclassMyExceptionHandler{@ResponseBody@ExceptionHandler(UserNotExistException.class)publicMaphandleException(Exception

e){        Map map =newHashMap<>();       

map.put("code","user.notexist");     

  map.put("message",e.getMessage());returnmap;    }}//没有自适应效果...

​ 2)、转发到/error进行自适应响应效果处理

@ExceptionHandler(UserNotExistException.class)publicStringhandleException(Exception

e, HttpServletRequest request){        Map map

=newHashMap<>();//传入我们自己的错误状态码  4xx 5xx,否则就不会进入定制错误页面的解析流程/**

         * Integer statusCode = (Integer) request

         .getAttribute("javax.servlet.error.status_code");

       */request.setAttribute("javax.servlet.error.status_code",500);   

    map.put("code","user.notexist");       

map.put("message",e.getMessage());//转发到/errorreturn"forward:/error";    }

3)、将我们的定制数据携带出去;

出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);

​ 1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

​ 2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;

​ 容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;

自定义ErrorAttributes

//给容器中加入我们自己定义的ErrorAttributes@ComponentpublicclassMyErrorAttributesextendsDefaultErrorAttributes{@OverridepublicMapgetErrorAttributes(RequestAttributes

requestAttributes,booleanincludeStackTrace){        Map map

=super.getErrorAttributes(requestAttributes, includeStackTrace);       

map.put("company","atguigu");returnmap;    }}

最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,

8、配置嵌入式Servlet容器

SpringBoot默认使用Tomcat作为嵌入式的Servlet容器;

问题?

1)、如何定制和修改Servlet容器的相关配置;

1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】);

server.port=8081

server.context-path=/crud

server.tomcat.uri-encoding=UTF-8

//通用的Servlet容器设置

server.xxx

//Tomcat的设置

server.tomcat.xxx

2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置

@Bean//一定要将这个定制器加入到容器中publicEmbeddedServletContainerCustomizerembeddedServletContainerCustomizer(){returnnewEmbeddedServletContainerCustomizer()

{//定制嵌入式的Servlet容器相关的规则@Overridepublicvoidcustomize(ConfigurableEmbeddedServletContainer

container){            container.setPort(8083);        }    };}

2)、注册Servlet三大组件【Servlet、Filter、Listener】

由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。

注册三大组件用以下方式

ServletRegistrationBean

//注册三大组件@BeanpublicServletRegistrationBeanmyServlet(){ 

  ServletRegistrationBean registrationBean

=newServletRegistrationBean(newMyServlet(),"/myServlet");returnregistrationBean;}

FilterRegistrationBean

@BeanpublicFilterRegistrationBeanmyFilter(){ 

  FilterRegistrationBean registrationBean

=newFilterRegistrationBean();   

registrationBean.setFilter(newMyFilter());   

registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));returnregistrationBean;}

ServletListenerRegistrationBean

@BeanpublicServletListenerRegistrationBeanmyListener(){ 

  ServletListenerRegistrationBean registrationBean

=newServletListenerRegistrationBean<>(newMyListener());returnregistrationBean;}

SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;

DispatcherServletAutoConfiguration中:

@Bean(name

=

DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)@ConditionalOnBean(value

= DispatcherServlet.class, name =

DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)publicServletRegistrationBeandispatcherServletRegistration(

    DispatcherServlet dispatcherServlet){   ServletRegistrationBean

registration =newServletRegistrationBean(        

dispatcherServlet,this.serverProperties.getServletMapping());//默认拦截: / 

所有请求;包静态资源,但是不拦截jsp请求;  

/*会拦截jsp//可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); 

registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());if(this.multipartConfig

!=null) {      registration.setMultipartConfig(this.multipartConfig); 

}returnregistration;}

2)、SpringBoot能不能支持其他的Servlet容器;

3)、替换为其他嵌入式Servlet容器

默认支持:

Tomcat(默认使用)

org.springframework.bootspring-boot-starter-web引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;

Jetty

org.springframework.bootspring-boot-starter-webspring-boot-starter-tomcatorg.springframework.bootspring-boot-starter-jettyorg.springframework.boot

Undertow

org.springframework.bootspring-boot-starter-webspring-boot-starter-tomcatorg.springframework.bootspring-boot-starter-undertoworg.springframework.boot

4)、嵌入式Servlet容器自动配置原理;

EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置?

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Configuration@ConditionalOnWebApplication@Import(BeanPostProcessorsRegistrar.class)//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件//导入了EmbeddedServletContainerCustomizerBeanPostProcessor://后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作publicclassEmbeddedServletContainerAutoConfiguration{@Configuration@ConditionalOnClass({

Servlet.class, Tomcat.class

})//判断当前是否引入了Tomcat依赖;@ConditionalOnMissingBean(value =

EmbeddedServletContainerFactory.class, search =

SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器publicstaticclassEmbeddedTomcat{@BeanpublicTomcatEmbeddedServletContainerFactorytomcatEmbeddedServletContainerFactory(){returnnewTomcatEmbeddedServletContainerFactory();}}/**

* Nested configuration if Jetty is being used.

*/@Configuration@ConditionalOnClass({ Servlet.class, Server.class,

Loader.class,WebAppContext.class })@ConditionalOnMissingBean(value =

EmbeddedServletContainerFactory.class, search =

SearchStrategy.CURRENT)publicstaticclassEmbeddedJetty{@BeanpublicJettyEmbeddedServletContainerFactoryjettyEmbeddedServletContainerFactory(){returnnewJettyEmbeddedServletContainerFactory();}}/**

* Nested configuration if Undertow is being used.

*/@Configuration@ConditionalOnClass({ Servlet.class, Undertow.class,

SslClientAuthMode.class })@ConditionalOnMissingBean(value =

EmbeddedServletContainerFactory.class, search =

SearchStrategy.CURRENT)publicstaticclassEmbeddedUndertow{@BeanpublicUndertowEmbeddedServletContainerFactoryundertowEmbeddedServletContainerFactory(){returnnewUndertowEmbeddedServletContainerFactory();}}

1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)

publicinterfaceEmbeddedServletContainerFactory{//获取嵌入式的Servlet容器EmbeddedServletContainergetEmbeddedServletContainer(

         ServletContextInitializer... initializers);}

2)、EmbeddedServletContainer:(嵌入式的Servlet容器)

3)、以TomcatEmbeddedServletContainerFactory为例

@OverridepublicEmbeddedServletContainergetEmbeddedServletContainer(

    ServletContextInitializer... initializers){//创建一个TomcatTomcat

tomcat =newTomcat();//配置Tomcat的基本环节File baseDir = (this.baseDirectory

!=null?this.baseDirectory         : createTempDir("tomcat"));  

tomcat.setBaseDir(baseDir.getAbsolutePath());   Connector connector

=newConnector(this.protocol);  

tomcat.getService().addConnector(connector);  

customizeConnector(connector);   tomcat.setConnector(connector);  

tomcat.getHost().setAutoDeploy(false);  

configureEngine(tomcat.getEngine());for(Connector additionalConnector

:this.additionalTomcatConnectors) {     

tomcat.getService().addConnector(additionalConnector);   }  

prepareContext(tomcat.getHost(),

initializers);//将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器returngetTomcatEmbeddedServletContainer(tomcat);}

4)、我们对嵌入式容器的配置修改是怎么生效?

ServerProperties、EmbeddedServletContainerCustomizer

EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置?

怎么修改的原理?

5)、容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor

//初始化之前@OverridepublicObjectpostProcessBeforeInitialization(Object

bean, String

beanName)throwsBeansException{//如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件if(beaninstanceofConfigurableEmbeddedServletContainer)

{//postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer)

bean);   }returnbean;}privatevoidpostProcessBeforeInitialization(

ConfigurableEmbeddedServletContainer

bean){//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;for(EmbeddedServletContainerCustomizer

customizer : getCustomizers()) {        customizer.customize(bean);   

}}privateCollectiongetCustomizers(){if(this.customizers ==null) {// Look

up does not include the parent contextthis.customizers

=newArrayList(this.beanFactory//从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer//定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件.getBeansOfType(EmbeddedServletContainerCustomizer.class,false,false) 

          .values());        Collections.sort(this.customizers,

AnnotationAwareOrderComparator.INSTANCE);this.customizers =

Collections.unmodifiableList(this.customizers);   

}returnthis.customizers;}ServerProperties也是定制器

步骤:

1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】

2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;

只要是嵌入式的Servlet容器工厂,后置处理器就工作;

3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法

###5)、嵌入式Servlet容器启动原理;

什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;

获取嵌入式的Servlet容器工厂:

1)、SpringBoot应用启动运行run方法

2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext

3)、refresh(context);刷新刚才创建好的ioc容器;

publicvoidrefresh()throwsBeansException,

IllegalStateException{synchronized(this.startupShutdownMonitor) {//

Prepare this context for refreshing.prepareRefresh();// Tell the

subclass to refresh the internal bean

factory.ConfigurableListableBeanFactory beanFactory =

obtainFreshBeanFactory();// Prepare the bean factory for use in this

context.prepareBeanFactory(beanFactory);try{// Allows post-processing of

the bean factory in context

subclasses.postProcessBeanFactory(beanFactory);// Invoke factory

processors registered as beans in the

context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean

processors that intercept bean

creation.registerBeanPostProcessors(beanFactory);// Initialize message

source for this context.initMessageSource();// Initialize event

multicaster for this context.initApplicationEventMulticaster();//

Initialize other special beans in specific context

subclasses.onRefresh();// Check for listener beans and register

them.registerListeners();// Instantiate all remaining (non-lazy-init)

singletons.finishBeanFactoryInitialization(beanFactory);// Last step:

publish corresponding event.finishRefresh();      }catch(BeansException

ex) {if(logger.isWarnEnabled()) {            logger.warn("Exception

encountered during context initialization - "+"cancelling refresh

attempt: "+ ex);         }// Destroy already created singletons to avoid

dangling resources.destroyBeans();// Reset 'active'

flag.cancelRefresh(ex);// Propagate exception to caller.throwex;     

}finally{// Reset common introspection caches in Spring's core, since

we// might not ever need metadata for singleton beans

anymore...resetCommonCaches();      }   }}

4)、 onRefresh(); web的ioc容器重写了onRefresh方法

5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();

6)、获取嵌入式的Servlet容器工厂:

EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

​从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;

7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());

8)、嵌入式的Servlet容器创建对象并启动Servlet容器;

先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;

==IOC容器启动创建嵌入式的Servlet容器==

9、使用外置的Servlet容器

嵌入式Servlet容器:应用打成可执行的jar

​ 优点:简单、便携;

缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);

外置的Servlet容器:外面安装Tomcat---应用war包的方式打包;

步骤

1)、必须创建一个war项目;(利用idea创建好目录结构)

2)、将嵌入式的Tomcat指定为provided;

org.springframework.bootspring-boot-starter-tomcatprovided

3)、必须编写一个SpringBootServletInitializer的子类,并调用configure方法

publicclassServletInitializerextendsSpringBootServletInitializer{@OverrideprotectedSpringApplicationBuilderconfigure(SpringApplicationBuilder

application){//传入SpringBoot应用的主程序returnapplication.sources(SpringBoot04WebJspApplication.class); 

  }}

4)、启动服务器就可以使用;

原理

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;

war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;

servlet3.0(Spring注解版):

8.2.4 Shared libraries / runtimes pluggability:

规则:

​ 1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:

2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名

​ 3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

流程:

1)、启动Tomcat

2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:

Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer

3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;

4)、每一个WebApplicationInitializer都调用自己的onStartup;

5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法

6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

protectedWebApplicationContextcreateRootApplicationContext(

    ServletContext

servletContext){//1、创建SpringApplicationBuilderSpringApplicationBuilder

builder = createSpringApplicationBuilder();   StandardServletEnvironment

environment =newStandardServletEnvironment();  

environment.initPropertySources(servletContext,null);  

builder.environment(environment);   builder.main(getClass());  

ApplicationContext parent =

getExistingRootWebApplicationContext(servletContext);if(parent !=null)

{this.logger.info("Root context already created (using as parent).");   

  servletContext.setAttribute(           

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,null);     

builder.initializers(newParentContextApplicationContextInitializer(parent)); 

  }  

builder.initializers(newServletContextApplicationContextInitializer(servletContext)); 

builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来builder

= configure(builder);//使用builder创建一个Spring应用SpringApplication

application = builder.build();if(application.getSources().isEmpty()

&& AnnotationUtils         .findAnnotation(getClass(),

Configuration.class) !=null) {     

application.getSources().add(getClass());   }  

Assert.state(!application.getSources().isEmpty(),"No SpringApplication

sources have been defined. Either override the "+"configure method or

add an @Configuration annotation");// Ensure error pages are

registeredif(this.registerErrorPageFilter) {     

application.getSources().add(ErrorPageFilterConfiguration.class);  

}//启动Spring应用returnrun(application);}

7)、Spring的应用就启动并且创建IOC容器

publicConfigurableApplicationContextrun(String...

args){   StopWatch stopWatch =newStopWatch();   stopWatch.start();  

ConfigurableApplicationContext context =null;   FailureAnalyzers

analyzers =null;   configureHeadlessProperty();  

SpringApplicationRunListeners listeners = getRunListeners(args);  

listeners.starting();try{      ApplicationArguments applicationArguments

=newDefaultApplicationArguments(            args);     

ConfigurableEnvironment environment = prepareEnvironment(listeners,     

      applicationArguments);      Banner printedBanner =

printBanner(environment);      context = createApplicationContext();     

analyzers =newFailureAnalyzers(context);      prepareContext(context,

environment, listeners, applicationArguments,           

printedBanner);//刷新IOC容器refreshContext(context);     

afterRefresh(context, applicationArguments);     

listeners.finished(context,null);     

stopWatch.stop();if(this.logStartupInfo)

{newStartupInfoLogger(this.mainApplicationClass)              

.logStarted(getApplicationLog(), stopWatch);      }returncontext;  

}catch(Throwable ex) {      handleRunFailure(context, listeners,

analyzers, ex);thrownewIllegalStateException(ex);   }}

==启动Servlet容器,再启动SpringBoot应用==

五、Docker

1、简介

Docker是一个开源的应用容器引擎;是一个轻量级容器技术;

Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;

运行中的这个镜像称为容器,容器启动是非常快速的。

2、核心概念

docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上);

docker客户端(Client):连接docker主机进行操作;

docker仓库(Registry):用来保存各种打包好的软件镜像;

docker镜像(Images):软件打包好的镜像;放在docker仓库中;

docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用

使用Docker的步骤:

1)、安装Docker

2)、去Docker仓库找到这个软件对应的镜像;

3)、使用Docker运行这个镜像,这个镜像就会生成一个Docker容器;

4)、对容器的启动停止就是对软件的启动停止;

3、安装Docker

1)、安装linux虚拟机

​ 1)、VMWare、VirtualBox(安装);

​ 2)、导入虚拟机文件centos7-atguigu.ova;

​ 3)、双击启动linux虚拟机;使用 root/ 123456登陆

​ 4)、使用客户端连接linux服务器进行命令操作;

​ 5)、设置虚拟机网络;

​ 桥接网络===选好网卡====接入网线;

​ 6)、设置好网络以后使用命令重启虚拟机的网络

service network restart

​ 7)、查看linux的ip地址

ip addr

​ 8)、使用客户端连接linux;

2)、在linux虚拟机上安装docker

步骤:

1、检查内核版本,必须是3.10及以上

uname -r

2、安装docker

yum install docker

3、输入y确认安装

4、启动docker

[root@localhost ~]# systemctl start docker

[root@localhost ~]# docker -v

Docker version 1.12.6, build 3e8e77d/1.12.6

5、开机启动docker

[root@localhost ~]# systemctl enable docker

Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

6、停止docker

systemctl stop docker

4、Docker常用命令&操作

1)、镜像操作

操作命令说明

检索docker search 关键字 eg:docker search redis我们经常去docker hub上检索镜像的详细信息,如镜像的TAG。

拉取docker pull 镜像名:tag:tag是可选的,tag表示标签,多为软件的版本,默认是latest

列表docker images查看所有本地镜像

删除docker rmi image-id删除指定的本地镜像

https://hub.docker.com/

2)、容器操作

软件镜像(QQ安装程序)----运行镜像----产生一个容器(正在运行的软件,运行的QQ);

步骤:

1、搜索镜像

[root@localhost ~]# docker search tomcat

2、拉取镜像

[root@localhost ~]# docker pull tomcat

3、根据镜像启动容器

docker run --name mytomcat -d tomcat:latest

4、docker ps 

查看运行中的容器

5、 停止运行中的容器

docker stop  容器的id

6、查看所有的容器

docker ps -a

7、启动容器

docker start 容器id

8、删除一个容器

docker rm 容器id

9、启动一个做了端口映射的tomcat

[root@localhost ~]# docker run -d -p 8888:8080 tomcat

-d:后台运行

-p: 将主机的端口映射到容器的一个端口    主机端口:容器内部的端口

10、为了演示简单关闭了linux的防火墙

service firewalld status ;查看防火墙状态

service firewalld stop:关闭防火墙

11、查看容器的日志

docker logs container-name/container-id

更多命令参看

https://docs.docker.com/engine/reference/commandline/docker/

可以参考每一个镜像的文档

3)、安装MySQL示例

docker pull mysql

错误的启动

[root@localhost ~]# docker run --name mysql01 -d mysql

42f09819908bb72dd99ae19e792e0a5d03c48638421fa64cce5f8ba0f40f5846

mysql退出了

[root@localhost ~]# docker ps -a

CONTAINER

ID        IMAGE               COMMAND                  CREATED         

   STATUS                           PORTS               NAMES

42f09819908b 

      mysql               "docker-entrypoint.sh"   34 seconds ago     

Exited (1) 33 seconds ago                            mysql01

538bde63e500 

      tomcat              "catalina.sh run"        About an hour ago  

Exited (143) About an hour ago                       compassionate_

goldstine

c4f1ac60b3fc 

      tomcat              "catalina.sh run"        About an hour ago  

Exited (143) About an hour ago                       lonely_fermi

81ec743a5271 

      tomcat              "catalina.sh run"        About an hour ago  

Exited (143) About an hour ago                       sick_ramanujan

//错误日志

[root@localhost ~]# docker logs 42f09819908b

error: database is uninitialized and password option is not specified

  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD;这个三个参数必须指定一个

正确的启动

[root@localhost ~]# docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql

b874c56bec49fb43024b3805ab51e9097da779f2f572c22c695305dedd684c5f

[root@localhost ~]# docker ps

CONTAINER

ID        IMAGE               COMMAND                  CREATED         

   STATUS              PORTS               NAMES

b874c56bec49     

  mysql               "docker-entrypoint.sh"   4 seconds ago       Up 3

seconds        3306/tcp            mysql01

做了端口映射

[root@localhost ~]# docker run -p 3306:3306 --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -d mysql

ad10e4bc5c6a0f61cbad43898de71d366117d120e39db651844c0e73863b9434

[root@localhost ~]# docker ps

CONTAINER

ID        IMAGE               COMMAND                  CREATED         

   STATUS              PORTS                    NAMES

ad10e4bc5c6a 

      mysql               "docker-entrypoint.sh"   4 seconds ago      

Up 2 seconds        0.0.0.0:3306->3306/tcp   mysql02

几个其他的高级操作

docker run --name mysql03 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面

改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql)

docker

run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

指定mysql的一些配置参数

六、SpringBoot与数据访问

1、JDBC

org.springframework.bootspring-boot-starter-jdbcmysqlmysql-connector-javaruntime

spring: 

datasource:    username:root    password:123456   

url:jdbc:mysql://192.168.15.22:3306/jdbc   

driver-class-name:com.mysql.jdbc.Driver

效果:

​ 默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源;

​ 数据源的相关配置都在DataSourceProperties里面;

自动配置原理:

org.springframework.boot.autoconfigure.jdbc:

1、参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;可以使用spring.datasource.type指定自定义的数据源类型;

2、SpringBoot默认可以支持;

org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource、

3、自定义数据源类型

/**

* Generic DataSource configuration.

*/@ConditionalOnMissingBean(DataSource.class)@ConditionalOnProperty(name

="spring.datasource.type")staticclassGeneric{@BeanpublicDataSourcedataSource(DataSourceProperties

properties){//使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性returnproperties.initializeDataSourceBuilder().build(); 

  }}

4、DataSourceInitializer:ApplicationListener

​ 作用:

​ 1)、runSchemaScripts();运行建表语句;

​ 2)、runDataScripts();运行插入数据的sql语句;

默认只需要将文件命名为:

schema-*.sql、data-*.sql

默认规则:schema.sql,schema-all.sql;

可以使用  

schema:

      - classpath:department.sql

      指定位置

5、操作数据库:自动配置了JdbcTemplate操作数据库

2、整合Druid数据源

导入druid数据源@ConfigurationpublicclassDruidConfig{@ConfigurationProperties(prefix

="spring.datasource")@BeanpublicDataSourcedruid(){returnnewDruidDataSource(); 

}//配置Druid的监控//1、配置一个管理后台的Servlet@BeanpublicServletRegistrationBeanstatViewServlet(){ 

      ServletRegistrationBean bean

=newServletRegistrationBean(newStatViewServlet(),"/druid/*");       

MapinitParams =newHashMap<>();       

initParams.put("loginUsername","admin");       

initParams.put("loginPassword","123456");       

initParams.put("allow","");//默认就是允许所有访问initParams.put("deny","192.168.15.21"); 

      bean.setInitParameters(initParams);returnbean;   

}//2、配置一个web监控的filter@BeanpublicFilterRegistrationBeanwebStatFilter(){   

    FilterRegistrationBean bean =newFilterRegistrationBean();       

bean.setFilter(newWebStatFilter());        Map initParams

=newHashMap<>();       

initParams.put("exclusions","*.js,*.css,/druid/*");       

bean.setInitParameters(initParams);       

bean.setUrlPatterns(Arrays.asList("/*"));returnbean;    }}

3、整合MyBatis

org.mybatis.spring.bootmybatis-spring-boot-starter1.3.1

步骤:

​ 1)、配置数据源相关属性(见上一节Druid)

​ 2)、给数据库建表

​ 3)、创建JavaBean

4)、注解版

//指定这是一个操作数据库的mapper@MapperpublicinterfaceDepartmentMapper{@Select("select

* from department where id=#{id}")publicDepartmentgetDeptById(Integer

id);@Delete("delete from department where

id=#{id}")publicintdeleteDeptById(Integer id);@Options(useGeneratedKeys

=true,keyProperty ="id")@Insert("insert into department(departmentName)

values(#{departmentName})")publicintinsertDept(Department

department);@Update("update department set

departmentName=#{departmentName} where

id=#{id}")publicintupdateDept(Department department);}

问题:

自定义MyBatis的配置规则;给容器中添加一个ConfigurationCustomizer;

@org.springframework.context.annotation.ConfigurationpublicclassMyBatisConfig{@BeanpublicConfigurationCustomizerconfigurationCustomizer(){returnnewConfigurationCustomizer(){@Overridepublicvoidcustomize(Configuration

configuration){               

configuration.setMapUnderscoreToCamelCase(true);            }        }; 

  }}

使用MapperScan批量扫描所有的Mapper接口;@MapperScan(value

="com.atguigu.springboot.mapper")@SpringBootApplicationpublicclassSpringBoot06DataMybatisApplication{publicstaticvoidmain(String[]

args){SpringApplication.run(SpringBoot06DataMybatisApplication.class,

args);}}

5)、配置文件版

mybatis: 

config-location:classpath:mybatis/mybatis-config.xml指定全局配置文件的位置 

mapper-locations:classpath:mybatis/mapper/*.xml指定sql映射文件的位置

更多使用参照

http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

4、整合SpringData JPA

1)、SpringData简介

2)、整合SpringData JPA

JPA:ORM(Object Relational Mapping);

1)、编写一个实体类(bean)和数据表进行映射,并且配置好映射关系;

//使用JPA注解配置映射关系@Entity//告诉JPA这是一个实体类(和数据表映射的类)@Table(name

="tbl_user")//@Table来指定和哪个数据表对应;如果省略默认表名就是user;publicclassUser{@Id//这是一个主键@GeneratedValue(strategy

= GenerationType.IDENTITY)//自增主键privateInteger id;@Column(name

="last_name",length =50)//这是和数据表对应的一个列privateString

lastName;@Column//省略默认列名就是属性名privateString email;

2)、编写一个Dao接口来操作实体类对应的数据表(Repository)

//继承JpaRepository来完成对数据库的操作publicinterfaceUserRepositoryextendsJpaRepository{}

3)、基本的配置JpaProperties

spring: jpa:    hibernate:#     更新或者创建数据表结构      ddl-auto:update#    控制台显示SQL    show-sql:true

七、启动配置原理

几个重要的事件回调机制

配置在META-INF/spring.factories

ApplicationContextInitializer

SpringApplicationRunListener

只需要放在ioc容器中

ApplicationRunner

CommandLineRunner

启动流程:

1、创建SpringApplication对象

initialize(sources);privatevoidinitialize(Object[]

sources){//保存主配置类if(sources !=null&& sources.length >0)

{this.sources.addAll(Arrays.asList(sources));   

}//判断当前是否一个web应用this.webEnvironment =

deduceWebEnvironment();//从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来setInitializers((Collection)

getSpringFactoriesInstances(       

ApplicationContextInitializer.class));//从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListenersetListeners((Collection)

getSpringFactoriesInstances(ApplicationListener.class));//从多个配置类中找到有main方法的主配置类this.mainApplicationClass

= deduceMainApplicationClass();}

2、运行run方法

publicConfigurableApplicationContextrun(String...

args){   StopWatch stopWatch =newStopWatch();   stopWatch.start();  

ConfigurableApplicationContext context =null;   FailureAnalyzers

analyzers =null;  

configureHeadlessProperty();//获取SpringApplicationRunListeners;从类路径下META-INF/spring.factoriesSpringApplicationRunListeners

listeners =

getRunListeners(args);//回调所有的获取SpringApplicationRunListener.starting()方法listeners.starting();try{//封装命令行参数ApplicationArguments

applicationArguments =newDefaultApplicationArguments(           

args);//准备环境ConfigurableEnvironment environment =

prepareEnvironment(listeners,           

applicationArguments);//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成Banner

printedBanner =

printBanner(environment);//创建ApplicationContext;决定创建web的ioc还是普通的ioccontext

= createApplicationContext();             analyzers

=newFailureAnalyzers(context);//准备上下文环境;将environment保存到ioc中;而且applyInitializers();//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法//回调所有的SpringApplicationRunListener的contextPrepared();//prepareContext(context,

environment, listeners, applicationArguments,           

printedBanner);//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();//s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版//扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)refreshContext(context);//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调//ApplicationRunner先回调,CommandLineRunner再回调afterRefresh(context,

applicationArguments);//所有的SpringApplicationRunListener回调finished方法listeners.finished(context,null); 

    stopWatch.stop();if(this.logStartupInfo)

{newStartupInfoLogger(this.mainApplicationClass)              

.logStarted(getApplicationLog(), stopWatch);     

}//整个SpringBoot应用启动完成以后返回启动的ioc容器;returncontext;   }catch(Throwable ex)

{      handleRunFailure(context, listeners, analyzers,

ex);thrownewIllegalStateException(ex);   }}

3、事件监听机制

配置在META-INF/spring.factories

ApplicationContextInitializer

publicclassHelloApplicationContextInitializerimplementsApplicationContextInitializer{@Overridepublicvoidinitialize(ConfigurableApplicationContext

applicationContext){       

System.out.println("ApplicationContextInitializer...initialize..."+applicationContext); 

  }}

SpringApplicationRunListener

publicclassHelloSpringApplicationRunListenerimplementsSpringApplicationRunListener{//必须有的构造器publicHelloSpringApplicationRunListener(SpringApplication

application, String[] args){    }@Overridepublicvoidstarting(){       

System.out.println("SpringApplicationRunListener...starting...");   

}@OverridepublicvoidenvironmentPrepared(ConfigurableEnvironment

environment){        Object o =

environment.getSystemProperties().get("os.name");       

System.out.println("SpringApplicationRunListener...environmentPrepared.."+o); 

  }@OverridepublicvoidcontextPrepared(ConfigurableApplicationContext

context){       

System.out.println("SpringApplicationRunListener...contextPrepared..."); 

  }@OverridepublicvoidcontextLoaded(ConfigurableApplicationContext

context){       

System.out.println("SpringApplicationRunListener...contextLoaded...");   

}@Overridepublicvoidfinished(ConfigurableApplicationContext context,

Throwable exception){       

System.out.println("SpringApplicationRunListener...finished...");    }}

配置(META-INF/spring.factories)

org.springframework.context.ApplicationContextInitializer=\

com.atguigu.springboot.listener.HelloApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\

com.atguigu.springboot.listener.HelloSpringApplicationRunListener

只需要放在ioc容器中

ApplicationRunner

@ComponentpublicclassHelloApplicationRunnerimplementsApplicationRunner{@Overridepublicvoidrun(ApplicationArguments

args)throwsException{       

System.out.println("ApplicationRunner...run....");    }}

CommandLineRunner

@ComponentpublicclassHelloCommandLineRunnerimplementsCommandLineRunner{@Overridepublicvoidrun(String...

args)throwsException{       

System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));   

}}

八、自定义starter

starter:

​ 1、这个场景需要使用到的依赖是什么?

​ 2、如何编写自动配置

@Configuration//指定这个类是一个配置类@ConditionalOnXXX//在指定条件成立的情况下自动配置类生效@AutoConfigureAfter//指定自动配置类的顺序@Bean//给容器中添加组件@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置@EnableConfigurationProperties//让xxxProperties生效加入到容器中自动配置类要能加载将需要启动就加载的自动配置类,配置在META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

​ 3、模式:

启动器只用来做依赖导入;

专门来写一个自动配置模块;

启动器依赖自动配置;别人只需要引入启动器(starter)

mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter

步骤:

1)、启动器模块

4.0.0com.atguigu.starteratguigu-spring-boot-starter1.0-SNAPSHOTcom.atguigu.starteratguigu-spring-boot-starter-autoconfigurer0.0.1-SNAPSHOT

2)、自动配置模块

4.0.0com.atguigu.starteratguigu-spring-boot-starter-autoconfigurer0.0.1-SNAPSHOTjaratguigu-spring-boot-starter-autoconfigurerDemo

project for Spring

Bootorg.springframework.bootspring-boot-starter-parent1.5.10.RELEASE<!--

lookup parent from repository -->UTF-8UTF-81.8org.springframework.bootspring-boot-starter

packagecom.atguigu.starter;importorg.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix

="atguigu.hello")publicclassHelloProperties{privateString

prefix;privateString suffix;publicStringgetPrefix(){returnprefix;   

}publicvoidsetPrefix(String prefix){this.prefix = prefix;   

}publicStringgetSuffix(){returnsuffix;    }publicvoidsetSuffix(String

suffix){this.suffix = suffix;    }}

packagecom.atguigu.starter;publicclassHelloService{ 

  HelloProperties

helloProperties;publicHelloPropertiesgetHelloProperties(){returnhelloProperties; 

  }publicvoidsetHelloProperties(HelloProperties

helloProperties){this.helloProperties = helloProperties;   

}publicStringsayHellAtguigu(String

name){returnhelloProperties.getPrefix()+"-"+name +

helloProperties.getSuffix();    }}

packagecom.atguigu.starter;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;importorg.springframework.boot.context.properties.EnableConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@Configuration@ConditionalOnWebApplication//web应用才生效@EnableConfigurationProperties(HelloProperties.class)publicclassHelloServiceAutoConfiguration{@AutowiredHelloProperties

helloProperties;@BeanpublicHelloServicehelloService(){       

HelloService service =newHelloService();       

service.setHelloProperties(helloProperties);returnservice;    }}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容