手工实现springmvc助你更加深入理解spring

雨,像银灰色黏湿的蛛丝,织成一片轻柔的网,网住了整个秋的世界。 天也是暗沉沉的,像古老的住宅里缠满着蛛丝网的屋顶。那堆在天上的灰白色的云片,就像屋顶上剥落的白粉。在这古旧的屋顶的笼罩下,一切都是异常的沉闷。园子里绿翳翳的石榴、桑树、葡萄藤,都不过代表着过去盛夏的繁荣,现在已成了古罗马建筑的遗迹一样,在萧萧的雨声中瑟缩不宁,回忆着光荣的过去。草色已经转入忧郁的苍黄,地下找不出一点新鲜的花朵;宿舍墙外一带种的娇嫩的洋水仙,垂了头,含着满眼的泪珠,在那里叹息它们的薄命,才过了两天的晴美的好日子又遇到这样霉气薰薰的雨天。只有墙角的桂花,枝头已经缀着几个黄金一样宝贵的嫩蕊,小心地隐藏在绿油油椭圆形的叶瓣下,透露出一点新生命萌芽的希望。   雨静悄悄地下着,只有一点细细的淅沥沥的声音。桔红色的房屋,像披着鲜艳的袈裟的老僧,垂头合目,受着雨底洗礼。那潮湿的红砖,发出有刺激性的猪血的颜色和墙下绿油油的桂叶成为强烈的对照。灰色的癞蛤蟆,在湿烂发霉的泥地里跳跃着;在秋雨的沉闷的网底,只有它是唯一的充满愉快的生气的东西。它背上灰黄斑驳的花纹,跟沉闷的天空遥遥相应,造成和谐的色调。它噗通噗通地跳着,从草窠里,跳到泥里,溅出深绿的水花。 织梦 内容管理系统   雨,像银灰色黏濡的蛛丝,织成一片轻柔的网,网住了 整个秋的世界。

不知道是偶然还是缘分,点进了张爱玲的文集,看到了秋雨这篇小时候一直看不懂的文章,突然有一种冲击在心底的失落。知道自己一直和文艺搭不上边。却在此时如此深刻的理解了,文中那深深的乡愁之情 每一句都是深深的包含对家的思念。一场秋雨一场寒 哈哈 扯远了。好久没有更新了,一是感觉时间不够用,突然生出一种感悟 时间都去哪了。二是对未来的路有点失落和迷茫了,不知道自己的路在何方。每天虽然都在努力给自己充电,但是充的电好像引不起我的共鸣。我想这很多人可能和我有相同的感觉,时代在变,我们就需要付出更大的努力才能勉强跟上它的脚步,总有一种感觉好像突然就会被错过,以前没有的压力,突然全都压上心头。哈哈 好像又一次扯远了,说一说今天的动机吧!很久没有更新是因为感觉以前写的文章,都有点应付自己。所以想要沉淀沉淀自己。昨天在网上看了一遍博文,感觉很对我的口味。全文我不记的了,最后那句话 如果你在感慨或者迷茫的时候不如埋下头来认真的去做一件事,把其他的事情都抛开,这是你就再也不会把时间浪费在感慨和迷茫上了。好了感慨到此结束进入今天的主题。

spring

spring我相信在java程序员中是每天必须接触的。使用也是必须掌握的。但是我们是不是真的理解或者是掌握了呢!我想大部分人肯定和我一样,处于迷糊的状态。似乎知道怎么用,但是如果需要你掌握其中的精髓原理,可能大部分人都感到有点丈二和尚摸不着头脑。如果大部分的人都是这种情况那我们何不把它精确的掌握呢,那我们是不是比别人高了一个档次了。今天我就是朝这个目标前进。先说一说spring的原理。


spring运行原理流程图.png

spring主要分三个阶段配置,初始化,运行。每一个阶段对应的再上图都可以看出有哪些操作这里就不细说了。

看了Tom哥的springMVC从无到有,虽然有时候坑点很多,但是最终还是感觉受益匪浅。所以今天自己也想手写实现一下。不是因为闲的蛋疼。而是因为太慌了,撸撸代码来平复一下这糟糕的情绪。O(∩_∩)O
好了废话就不说了,开始上代码。


代码包结构.PNG

这是准备的代码结构。文中pom.xml只是引用了servlet.api这一个jar包。

<dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>3.0-alpha-1</version>
    </dependency>
  </dependencies>

一下是web.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <display-name>mySpring</display-name>
  <servlet>
    <servlet-name>lcmvc</servlet-name>
    <servlet-class>com.lc.mvcframework.servlet.LCDispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>application.properties</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>lcmvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

application.properties配置 这里是扫描的路径

scanPackage=com.lc.demo
代码包结构2.png
@Target({ElementType.FIELD}) // 注释在字段上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCAutowrited {
    
    String value() default "";
}


@Target({ElementType.TYPE}) // 注释在类上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCController {
    
    String value() default "";
}

@Target({ElementType.TYPE,ElementType.METHOD}) // 注释在类上也可以在方法上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCRequestMapping {
    
    String value() default "";
}

@Target({ElementType.PARAMETER}) // 注释在参数上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCRequestParam {
    
    String value() default "";
}

@Target({ElementType.TYPE}) // 注释在类上
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//说明该注解将被包含在javadoc中
public @interface LCService {
    
    String value() default "";
}

DemoAction

@LCController
@LCRequestMapping("/demo")
public class DemoAction {
    @LCAutowrited private IDemoService demoService;
    @LCRequestMapping("/edit.do")
    public void edit(HttpServletRequest req,HttpServletResponse rep,
            @LCRequestParam("name") String name) {
        String res = demoService.get(name);
        try {
            rep.getWriter().write(res);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

TestAction

@LCController
@LCRequestMapping("/test")
public class TestAction {
    
    @LCAutowrited private IDemoService demoService;
    @LCRequestMapping("/query.do")
    public void query(HttpServletRequest req,HttpServletResponse rep,
            @LCRequestParam("name") String name) {
        String res = demoService.get(name);
        try {
            rep.getWriter().write(res);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

service接口

public interface IDemoService {
    String get(String name);
}

service实现类

@LCService
public class DemoService implements IDemoService{

    public String get(String name) {
        return "My name is"+name;
    }

}

dispatcherServlet

package com.lc.mvcframework.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.lc.mvcframework.annotation.LCAutowrited;
import com.lc.mvcframework.annotation.LCController;
import com.lc.mvcframework.annotation.LCRequestMapping;
import com.lc.mvcframework.annotation.LCRequestParam;
import com.lc.mvcframework.annotation.LCService;

public class LCDispatcherServlet extends HttpServlet{
    
    private Properties p = new Properties();//根据Properties 加载InputStream流properties配置文件
    
    private List<String> classNames = new ArrayList<String>();
    
    private Map<String,Object> iocs = new HashMap<String,Object>();
    //申明一个handlerMapping
    //private Map<String,Method> handlerMapping = new HashMap<String,Method>();
    
    private List<Handler> handlerMapping = new ArrayList<Handler>();
    public void init(ServletConfig config) throws ServletException {
        // 1 加载配置文件
        // 获取配置文件所在路径 并通过配置文件的路径将文件读取进来
        String path = config.getInitParameter("contextConfigLocation");
        doLoadConfig(path);
        // 2 读取配置文件扫描相关的类
        doScanner(p.getProperty("scanPackage"));//通过获取到的配置文件 取出其中需要扫描的路径
        // 3将已经扫描的类进行初始化并放入ioc容器中
        doInstance();
        // 4依赖注入
        doAutowrited();
        // 5初始化handlerMapping
        initHandlerMapping();
        System.out.println("this is My Spring servlet");
    }
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }
    // 6等待调用
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try{
            doDispatcher(req, resp);
        }catch(Exception e){
            resp.getWriter().write("500 Exception,Details:\r\n"+Arrays.toString(e.getStackTrace()));
        }
    }
    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
        /*
        //判断handlerMapping是否存在
        if(handlerMapping.isEmpty()) return;
        // 获取用户请求的url
        String url = req.getRequestURI();
        //获取绝对路径
        String contextPath = req.getContextPath();
        //将url中的绝对路径去掉获得相对路径
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        // 根据url从handlerMapping中获取对应的值
        if(!handlerMapping.containsKey(url)){
            try {
                resp.getWriter().write("Not Found 404!!!");
                return;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 通过反射调用
        Method method = handlerMapping.get(url);
        // 第一个参数传方法method所对应的对象,第二个参数传对应的实参
        // 这里只能从ioc容器中取值 做到这里发现走不通 因为参数没有 所以只能换一个数据结构
        method.invoke(obj, args);
        System.out.println(handlerMapping.get(url));
        */
        try{
            Handler handler = getHandler(req);
            if(handler == null){
                // 没有匹配上
                resp.getWriter().write("404 Not Found");
                return;
            }
            // 获取参数列表
            Class<?>[] paramTypes = handler.method.getParameterTypes();
            // 保存需要自动赋值的参数值
            Object[] paramValues = new Object[paramTypes.length];
            Map<String,String[]> params = req.getParameterMap();
            for (Entry<String,String[]> param : params.entrySet()) {
                String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
                // 如果找不到匹配的对象,则开始填充参数值
                if(!handler.paramIndexMapping.containsKey(param.getKey())){
                    continue;
                }
                int index = handler.paramIndexMapping.get(param.getKey());
                paramValues[index] = convert(paramTypes[index],value);
            }
            
            // 设置方法中的request和response对象
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex]= req;
            int respIndex =  handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex]= resp;
            handler.method.invoke(handler.controller,paramValues );
        }catch(Exception e){
            System.err.println(e.getStackTrace());
        }
        
    }
    
    private Handler getHandler(HttpServletRequest req) throws Exception{
        if(handlerMapping.isEmpty()) return null;
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        for (Handler handler : handlerMapping) {
            try{
                Matcher matcher = handler.pattern.matcher(url);
                // 如果没有匹配上继续下一个匹配
                if(!matcher.matches()){
                    continue;
                }
                return handler;
            }catch(Exception e){
                throw e;
            }
        }
        return null;
    }
    
    private Object convert(Class<?> type,String value){
        if(Integer.class == type){
            return Integer.valueOf(value);
        }
        return value;
    }
    
    private void  doLoadConfig(String path) {
        // 根据路径获取一个InputStream的流
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(path);
        try {
            p.load(is);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 判断是否为空 如果不为空 则close流
            if(null != is)
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
    private void doScanner(String packageName) {
        // 因为packageName是类似于com.lc.demo这样的格式 需要将所有点替换成/的路径 并将转换成String的路径
        String  url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/")).getFile();
        // 根据路径可以得到一个file
        File files = new File(url);
        // 通过循环遍历找出里面所有的类文件
        for (File f : files.listFiles()) {
            // 判断是不是一个目录 如果是一个目录继续调用这个方法 并将路径拼接上去
            if (f.isDirectory()) {
                doScanner(packageName+"."+f.getName());
            }else { 
                //如果是一个文件就需要将这个类名存入 并将他的后缀去掉
                String className = packageName+"."+f.getName().replace(".class", "");
                // 将className 存入容器之中
                classNames.add(className);
            }   
        }
    }
    private void doInstance() {
        // 初始化容器
        // 判断classNames中是否为空
        if(classNames.isEmpty()) return;
        // 不为空就循环取出
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                // 初始化这里需要注意几个概念
                // 对于controller来说 默认情况 一般是 首字母小写 同时我们也需要注意一点,在并不是所有的controller都会被注入。
                // 注释类型的注释存在于此元素上,则返回true,否则返回false
                if(clazz.isAnnotationPresent(LCController.class)) {
                    // 获取controller的类名
                    String beanName = firstLower(clazz.getSimpleName());//同时需要注意这里我们获取到的类名首字母是大写的 我们需要将其变成小写
                    // 判断是不是已经存在形同的key 如果存在相同的 不处理
                    if(!iocs.containsKey(beanName)) {
                        iocs.put(beanName, clazz.newInstance());
                    }
                }
                // 针对于service来说
                else if(clazz.isAnnotationPresent(LCService.class)) {
                    // 1 IOC容器的结构 Map<String,Object>
                    // String 也可以叫做IOC容器的beanName
                    // beanName有几个规则 默认情况下是类名的首字母小写
                    // 第二种情况 就是当我们自定义了一个名称时应该以我们自定义的优先级高 或者是说以我们自定义的为准
                    // 第三种情况 当我们注入service时我们一般是注入的接口,在java中接口是不能被实例化的。这时我们需要主要接口的实现类
                    // 这时我们又会遇到另外一个问题 就是接口是可以实现多个的,所以这时候我们应该根据接口的类型来注入。
                    // 首先我们取出自定义的
                    LCService service = clazz.getAnnotation(LCService.class);
                    String beanName = service.value();//获取自定义的name
                    // 判断是否为空
                    if("".equals(beanName)) {
                        // 默认首字母小写
                        beanName = firstLower(clazz.getSimpleName());
                    }
                    // 这里是将第一种和第二种情况处理
                    Object instance = clazz.newInstance();
                    iocs.put(beanName, instance);
                    // 处理第三种情况 获取接口类型
                    Class<?> [] classes = clazz.getInterfaces();
                    for (Class<?> c : classes) {
                        // 这里为什么这样可以 因为instance在这里是单例模式 只被初始化一次
                        iocs.put(c.getName(), instance);
                    }
                }else {
                    continue;
                }
                
                
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 依赖注入
    private void doAutowrited() {
        // 判断是否为空 如果为空就直接退出
        if(iocs.isEmpty()) return;
        // 不为空就循环取出
        for (Entry<String, Object> entry : iocs.entrySet()) {
            // 通过反射取出entry中所有的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            // 循环赋值
            for (Field field : fields) {
                // 这里需要注意 只有加了LCAutowirted才进行赋值
                if(!field.isAnnotationPresent(LCAutowrited.class)) continue;
                // 如果有获取它的值
                LCAutowrited autowrited = field.getAnnotation(LCAutowrited.class);  
                String beanName = autowrited.value();
                // 判断beanName是否设置了
                if("".equals(beanName)) {
                    // 从field中取
                    beanName = field.getType().getName();
                }
                // 这里需要注意 因为我们的字段可能加了访问权限 如受保护的 私有的 ...
                // 这里我们需要设置不管什么访问权限全部获取 授权
                field.setAccessible(true);
                // 赋值
                try {
                    field.set(entry.getValue(), iocs.get(beanName));//第一个参数表示给哪个字段赋值,第二个参数要赋的值
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                } 
            }
        }
    }
    // 初始化handlerMapping
    private void initHandlerMapping() {
        if(iocs.isEmpty()) return;
        for (Entry<String, Object> entry : iocs.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            // 这里因为handlerMapping对应的是controller所以这里需要判断是不是controller
            if(!clazz.isAnnotationPresent(LCController.class)) continue;
            // 保存url
            String url = "";
            // 取出LCRequestMapping中的值
            if(clazz.isAnnotationPresent(LCRequestMapping.class)) {
                LCRequestMapping requestMapping = clazz.getAnnotation(LCRequestMapping.class);
                url = requestMapping.value();
            }
            // 扫描所有方法 这里需要注意一点为什么不用clazz.getDeclaredMethods() 因为这个只是扫描所有方法 而这里我们只是需要公有方法即可,减少循环次数
            
            Method[]  methods = clazz.getMethods();
            for (Method method : methods) {
                if(!method.isAnnotationPresent(LCRequestMapping.class)) continue;
//              LCRequestMapping requestMapping = method.getAnnotation(LCRequestMapping.class);
//              String r = requestMapping.value();
//              url = ("/"+url +"/"+ r).replaceAll("/+", "/");//将多余的斜杠去掉
//              handlerMapping.put(url, method);
//              System.out.println("app Mapping   "+url+","+method);
                // 映射URL
                LCRequestMapping requestMapping = method.getAnnotation(LCRequestMapping.class);
                String regex = ("/"+url+ requestMapping.value()).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(pattern, entry.getValue(), method));
                System.out.println("app Mapping   "+regex+","+method);
            }
        }
    }
    
    
    //首字母变成小写
    private String firstLower(String name) {
        char[] chars = name.toCharArray();
        chars[0]+=32;
        return String.valueOf(chars);
    }
    //handler的内部类
    private class Handler{
        protected Object controller;//保存方法对应的实例
        protected Method method;//保存映射的方法
        protected Pattern pattern;
        protected Map<String,Integer> paramIndexMapping;//参数对应顺序
        
        protected Handler(Pattern pattern,Object controller,Method method){
            this.controller = controller;
            this.pattern = pattern;
            this.method = method;
            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);
        }
        private void putParamIndexMapping(Method method){
            // 提取方法中加了注解的参数
            Annotation[][] pa = method.getParameterAnnotations();
            for(int i=0;i<pa.length;i++){
                for (Annotation a : pa[i]) {
                    if(a instanceof LCRequestParam){
                        String paramName = ((LCRequestParam)a).value();
                        if(!"".equals(paramName.trim())){
                            paramIndexMapping.put(paramName, i);
                        }
                    }
                }
            }
            // 提取方法中的request 和response参数
            Class<?>[] paramsTypes = method.getParameterTypes();
            for(int i=0;i<paramsTypes.length;i++){
                Class<?> type = paramsTypes[i];
                if(type == HttpServletRequest.class ||
                        type == HttpServletResponse.class){
                    paramIndexMapping.put(type.getName(), i);
                }
            }
        }
    }
}

/*
        //判断handlerMapping是否存在
        if(handlerMapping.isEmpty()) return;
        // 获取用户请求的url
        String url = req.getRequestURI();
        //获取绝对路径
        String contextPath = req.getContextPath();
        //将url中的绝对路径去掉获得相对路径
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        // 根据url从handlerMapping中获取对应的值
        if(!handlerMapping.containsKey(url)){
            try {
                resp.getWriter().write("Not Found 404!!!");
                return;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 通过反射调用
        Method method = handlerMapping.get(url);
        // 第一个参数传方法method所对应的对象,第二个参数传对应的实参
        // 这里只能从ioc容器中取值 做到这里发现走不通 因为参数没有 所以只能换一个数据结构
        method.invoke(obj, args);
        System.out.println(handlerMapping.get(url));
        */
捕获.PNG

调用成功!!
上文中被注释的这一段本来是按照这个思路写的 但是到后面method.invoke(obj, args);这一步的时候坑来了。obj实例对象和实力参数好像没有,实验了好几种办法都不能实现彻底绝望了,后面又重新找资料 用的后面那种 加了一个handler的数据结构,将每次获取到的实例对象和实例参数保存在一个List<handler>结构中才解决这个问题。没有看spring源码是如何实现的,估计spring实现应该是更好的办法,只能下次再去细看了。个人觉得文中的注释还是写的比较详细的,以及思路也还算详细。这里就不赘述了。谢谢那些没有跳过开头的同学。好久没有更新了所以难免会有点感慨。O(∩_∩)O 如果你觉得有帮助记得点赞嚄!

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 要加“m”说明是MB,否则就是KB了. -Xms:初始值 -Xmx:最大值 -Xmn:最小值 java -Xms8...
    dadong0505阅读 4,825评论 0 53
  • 2月15日,杭州《都市快报》第五版整版刊登了一则题为《关少尘,我要跟你“脱离父子关系”》的声明。 事件过去已有半月...
    何小河vicky阅读 974评论 2 2
  • 这是转型与蜕变30天自由写作第2篇,是我写的第2篇。 我一开始不想来的。 第一次听说自由写作这个事情是去年11月参...
    YUJIA羽佳阅读 438评论 8 5
  • 手机响起来的时候,他正在做梦,一个美梦,一个春梦!他急忙爬起来,穿着短裤走到书桌边,手机就搁在那上面的一个木头做的...
    佘玺阅读 521评论 0 4