Servlet基本结构的源码解析

来源:互联网 发布:北京外企和fdi数据 编辑:程序博客网 时间:2024/06/05 05:48

如何写一个Servlet类?围绕这个问题,可以看一下Servlet的类结构。首先Servlet类也是一个Java类,只不过这个类比较特殊,它不能单独运行,必须要依托Servlet容器才能运行,Servlet类是一个组件,供Servlet引擎调用。既然是这样,那么Servlet类和Servlet引擎必然要遵循一套规范,以约束彼此的行为,遵循规范编写的Servlet类可以运行在任何符合规范的Servlet引擎上。那我们就看看,如何编写一个符合规范的Servlet。首先看一下要编写一个Servlet基本类图的结构(图是从网上找的):


其中Servlet接口是所有Servlet的父接口,Servlet规范为了方便实现Servlet类,提供了一个抽象类GenericServlet,它提供了Servlet的默认实现,可以通过继承自GenericServlet类来简化编程。Servlet主要是处理Http请求的,所以有提供了HttpServlet,它继承自GenericServlet,一般来说,我们编写的大部分Servlet都继承自HttpServlet。在上边的类图结构中,ServletConfig接口封装了代表Servlet容器的对象(ServletContext),和Servlet初始化信息,为了方便编程GenericServlet实现了此接口。在下面的源码分析中有详细的解释。

Servlet的生命周期

Servlet相当与Servlet容器的组件,Servlet接口定义了三个方法用于实现Servlet的声明周期,分别是:

(1)初始化init(ServletConfig config):每个Servlet只能初始化一次,初始化的时候,Servlet引擎将一个ServletConfig对象传递进来,里面包含了这个Servlet的初始化参数,具体的Servlet类应该使用一个私有引用保存这个config。Servlet可以有构造方法,init在构造方法之后执行;

(2)处理请求service(ServletRequest,ServletResponse):只有在init方法成功执行后才能处理请求,每次请求都会调用service方法;

(3)销毁destroy():当要从容器中移除该Servlet时,会调用destroy方法,用来关闭资源,做一些清理工作,只调用一次。

同时Servlet接口还定义了getServletConfig()方法,就是返回init方法中传递进来的config,下面是Servlet的源码:

[java] view plain copy
  1. import java.io.IOException;  
  2.   
  3. /** 
  4.  * 所有servlet都要实现的接口 
  5.  * Servlet是供Servlet引擎调用的Java类,它相当于Servlet引擎的组件 
  6.  * 在Servlet接口中定义了三个方法,也被称为Servlet的声明周期: 
  7.  * void init(ServletConfig config):Servlet被初始化时被Servlet引擎调用,只执行一次,且后于构造方法 
  8.  * void service(ServletRequet,ServletResponse):响应请求,每当有一个请求,Servlet引擎就调用该方法 
  9.  * void destroy():Servlet被销毁时被Servlet引擎调用,用于关闭资源等服务 
  10.  * 以上三个方法都是被Servlet引擎(容器)调用 
  11.  * 除了以上方法之外,还提供了个方法: 
  12.  * ServletConfig getServletConfig:一个ServletConfig对象,在init方法被传递进来,包含了Servlet的初始化信息和 
  13.  * 代表Servlet容器的对象(ServletContext) 
  14.  * String getServletInfo():返回该Servlet的基本信息 
  15.  */  
  16. public interface Servlet {  
  17.   
  18.     /** 
  19.      * 被servlet引擎调用用于初始化该Servlet,只有在该方法完成之后,才能响应请求 
  20.      * 如果init方法抛出了异常,则一定不能响应请求。init方法在构造方法之后执行。 
  21.      * Servlet引擎会把ServletConfig对象传进来 
  22.      */  
  23.     public void init(ServletConfig config) throws ServletException;  
  24.   
  25.     /** 
  26.      * 返回ServletConfig对象,其中包含着初始化信息和初始化参数 
  27.      * @see #init 
  28.      */  
  29.     public ServletConfig getServletConfig();  
  30.   
  31.     /** 
  32.      * 响应请求,必须在init(Servletconfig config)成功后才能执行 
  33.      */  
  34.     public void service(ServletRequest req, ServletResponse res)  
  35.             throws ServletException, IOException;  
  36.   
  37.     /**返回基本信息*/  
  38.     public String getServletInfo();  
  39.   
  40.     /** 
  41.      * 被Servlet引擎调用,用来关闭资源,或者做一些清楚工作 
  42.      */  
  43.     public void destroy();  
  44. }  
Servlet的默认实现

GenericServlet实现了Servlet接口,同时也实现了ServletConfig接口,之所以实现ServletConfig接口是为了方便编程。下面代码中的注释已经很清楚了,不过还是要说一下init方法,在GenericServlet中提供了两个init方法:

(1)重写Servlet接口的init(ServletConfig config)方法,GenericSerlvet内部用私有属性保存此config,初始化工作调用无参的init()方法;

(2)重载的无参init()方法,子类应该覆盖这个方法,它会被有参数的init方法调用。之所以这么设计是为了方便编程,前面提到,如果重写父类的init(ServletConfig)方法,需要调用super.init(config),或者自己保存config,没必要增加这麻烦,直接继承自无参的init。Servlet引擎调用的init(ServletConfig)方法,而在init(ServletConfig)在最后调用了无参的init()。

[java] view plain copy
  1. /** 
  2.  * 对Servlet接口的方法提供了默认是实现,通过继承GenericServlet方法,可以方便别写Servlet 
  3.  * 1.首先需要注意的一点是它实现了ServletConfig接口,在Servlet的init(ServletConfig config)方法 
  4.  * 中,Servlet容器会把一个ServletConfig传递进来,但是这个config只能在init()方法中使用,如果想在其他 
  5.  * 方法中是使用,则需要使用一个似有引用把config保存起来,供getServletConfig()调用。 
  6.  * GenericServlet也是这样做的,它有一个ServletConfig属性,在init(ServletConfig config)中 
  7.  * 保存传递进来的config。 
  8.  * 但同时它还实现了ServletConfig接口,这样做的目的是方便编程,在需要使用ServletConfig的方法时, 
  9.  * 无需先获得ServletConfig对象,而可以直接使用这些方法。其实GenericServlet也仅仅是简单的调用 
  10.  * config的相关方法,相当于一个代理。 
  11.  *  
  12.  * 2.GenericServlet实现了两个init方法 
  13.  * 一个是重写Serlvet接口的init(ServletConfig)方法,使用该方法获得ServletConfig对象 
  14.  * 它会在最后调用无参的init()方法; 
  15.  * 一个是重载的无参init()方法 
  16.  * 子类应该选择重写无参的init()方法,为什么呢? 
  17.  * 如果重写init(ServletConfig)方法,那么首先需要在第一行调用super(config) 
  18.  * 要不然,就得自己去保存传递进来的ServletConfig对象,增加不必要的麻烦 
  19.  * 其次,重写无参的init()方法,会被有参数的init(ServletConfig)调用,达到同样的目的 
  20.  * 结论:无参的init()方法就是为了方便子类覆盖 
  21.  */  
  22. public abstract class GenericServlet implements Servlet, ServletConfig,  
  23.         java.io.Serializable {  
  24.   
  25.     private static final long serialVersionUID = 1L;  
  26.   
  27.     private transient ServletConfig config;  
  28.   
  29.     public GenericServlet() {  
  30.         // NOOP  
  31.     }  
  32.   
  33.     @Override  
  34.     public void destroy() {  
  35.         // NOOP by default  
  36.     }  
  37.   
  38.     /**实现ServletConfig接口的方法*/  
  39.     public String getInitParameter(String name) {  
  40.         return getServletConfig().getInitParameter(name);  
  41.     }  
  42.     public Enumeration<String> getInitParameterNames() {  
  43.         return getServletConfig().getInitParameterNames();  
  44.     }  
  45.     @Override  
  46.     public ServletContext getServletContext() {  
  47.         return getServletConfig().getServletContext();  
  48.     }  
  49.     @Override  
  50.     public String getServletName() {  
  51.         return config.getServletName();  
  52.     }  
  53.     @Override  
  54.     public ServletConfig getServletConfig() {  
  55.         return config;  
  56.     }  
  57.    
  58.     @Override  
  59.     public String getServletInfo() {  
  60.         return "";  
  61.     }  
  62.   
  63.     /** 
  64.      * 注意,这个方法会在最后调用无参的init()方法 
  65.      * 子类应该选择覆盖init()方法 
  66.      */  
  67.     @Override  
  68.     public void init(ServletConfig config) throws ServletException {  
  69.         this.config = config;  
  70.         this.init();  
  71.     }  
  72.   
  73.     /**默认空实现,子类如果要覆盖init方法的话,应该选择覆盖此方法 
  74.      * init(ServletConfig)会在最后调用这个方法 
  75.      * */  
  76.     public void init() throws ServletException {  
  77.         // NOOP by default  
  78.     }  
  79.   
  80.     public void log(String msg) {  
  81.         getServletContext().log(getServletName() + ": " + msg);  
  82.     }  
  83.   
  84.     public void log(String message, Throwable t) {  
  85.         getServletContext().log(getServletName() + ": " + message, t);  
  86.     }  
  87.   
  88.     @Override  
  89.     public abstract void service(ServletRequest req, ServletResponse res)  
  90.             throws ServletException, IOException;  
  91. }  
处理HTTP请求

为了方便处理HTTP请求,Servlet规范定义了HttpServlet抽象类,通过实现HttpServlet类可以把重点放到业务逻辑的实现上,而不是去处理和Servlet引擎的交互。HttpServlet针对每种HTTP的请求方式都提供了具体的doXXX方法,其中最常用的还是doGet和doPost方法。在理解HttpServlet类时,最重要的是理解Servlet的生命周期,Servlet引擎在处理请求是调用的是哪个方法?是Servlet接口规定的service(ServletRequest,ServletResponse)方法,在HttpServlet类中,处理请求的方法依然是首先调用service方法,service方法根据不同的请求方式,把请求转发到具体的doXXX方法中

在这里有两个HTTP字段需要注意,一个是响应实体首部Last-Modified(服务器端),表示Servlet的修改时间;一个是请求头部,If-Modified-Since(客户端),如果资源在指定的时间之后没有修改过,那么表示缓存有效,可以直接使用缓存。HttpServlet.getLastModified()方法用于返回当前内容修改的时间。Service在处理请求时,需要根据其返回值的不同需要做相应的处理:(服务端修改时间比客户端修改时间旧,本地缓存有效,否则无效,服务器响应请求)

(1)如果其返回值是-1,则二话不说,直接响应本次请求;

(2)如果是一个正数,且请求头中没有If-Modified-Since字段,或者If-Modified-Since字段表示的时间比getLastModified()返回的之间旧,表示缓存无效,需要处理本次请求。比如,如果当前修改时间是2014年9月1日,请求头中的If-Modified-Since表示的时间是2014年8月1日,也就是说请求中这样说到:如果在2014年8月1日之后没有修改过资源,那么缓存有效。但是很明显,这种情形下,缓存已经失效了。同时把Last-Modified字段写入响应头中

(3)返回值是整数,但是请求头中If-Modified-Since表示的时间新于getLastModified()表示的时间,那么表示缓存有效,服务器直接返回一个304(Not Modified),告诉浏览器直接使用缓存。

[java] view plain copy
  1. import java.io.IOException;  
  2. import java.io.OutputStreamWriter;  
  3. import java.io.PrintWriter;  
  4. import java.io.UnsupportedEncodingException;  
  5. import java.lang.reflect.Method;  
  6. import java.text.MessageFormat;  
  7. import java.util.Enumeration;  
  8. import java.util.ResourceBundle;  
  9.   
  10. import javax.servlet.GenericServlet;  
  11. import javax.servlet.ServletException;  
  12. import javax.servlet.ServletOutputStream;  
  13. import javax.servlet.ServletRequest;  
  14. import javax.servlet.ServletResponse;  
  15.   
  16.   
  17. /** 
  18.  * 会方便处理HTTP请求的Servlet而提供的抽象类,继承了GenericServlet 
  19.  * 我们写的绝大多数Servlet都应该继承自HttpServlet 
  20.  * HttpServlet根据不同的http请求提供了不同的方法: 
  21.  * doGet:如果servlet支持http GET方法 
  22.  * doPost:如果servlet支持http POST方法 
  23.  * 以上两个方法最常用,其他的还有doPut,doDelete,doHead,doOptions,doTrace 
  24.  * 
  25.  * @author  Various 
  26.  */  
  27. public abstract class HttpServlet extends GenericServlet {  
  28.   
  29.     private static final long serialVersionUID = 1L;  
  30.       
  31.     //HTTP的各种方法  
  32.     private static final String METHOD_DELETE = "DELETE";  
  33.     private static final String METHOD_HEAD = "HEAD";  
  34.     private static final String METHOD_GET = "GET";  
  35.     private static final String METHOD_OPTIONS = "OPTIONS";  
  36.     private static final String METHOD_POST = "POST";  
  37.     private static final String METHOD_PUT = "PUT";  
  38.     private static final String METHOD_TRACE = "TRACE";  
  39.   
  40.     private static final String HEADER_IFMODSINCE = "If-Modified-Since";  
  41.     private static final String HEADER_LASTMOD = "Last-Modified";  
  42.   
  43.     private static final String LSTRING_FILE =  
  44.         "javax.servlet.http.LocalStrings";  
  45.     private static ResourceBundle lStrings =  
  46.         ResourceBundle.getBundle(LSTRING_FILE);  
  47.   
  48.   
  49.   
  50.     /** 
  51.      * 被service()方法调用去处理GET请求 
  52.      * 重写此方法以支持GET请求同样也自动的支持HEAD请求 
  53.      * 一个HEAD请求就是一个GET请求,只不过HEAD请求只返回首部,不返回响应实体部分 
  54.      * 子类如果要支持GET方法,就必须要重写次方法,因为HttpServlet的默认实现是 
  55.      * 发送错误 
  56.      */  
  57.     protected void doGet(HttpServletRequest req, HttpServletResponse resp)  
  58.         throws ServletException, IOException  
  59.     {  
  60.         //返回使用的协议,protocol/majorVersion,如HTTP/1.1  
  61.         String protocol = req.getProtocol();  
  62.         String msg = lStrings.getString("http.method_get_not_supported");  
  63.         //由于是默认实现,根据不同的协议,直接报错  
  64.         if (protocol.endsWith("1.1")) {  
  65.             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);  
  66.         } else {  
  67.             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);  
  68.         }  
  69.     }  
  70.   
  71.   
  72.     /** 
  73.      * 返回的值代表当前输出响应内容的修改时间,总是返回-1,子类可重写 
  74.      * getLastModified方法是一个回调方法,由HttpServlet的service方法调用, 
  75.      * service方法可以根据返回值在响应消息中自动生成Last_Modified头字段(最后被修改的时间) 
  76.      * Servlet受到一个GET方式的请求时,HttpServlet重载的service方法在调用doGet之前,会先 
  77.      * 调用本方法,并根据返回值来决定是否调用doGet方法和在响应消息是是否生成Last_Modified字段 
  78.      * 具体规则如下: 
  79.      * 1.如果是一个负数(本方法默认实现),直接调用doGet方法 
  80.      * 2.如果是一个正数,且请求消息中没有包含If-Modified-Since请求头,或者请求头中的时间值 
  81.      *   比返回值旧时,这说明要么是第一次请求,要么是缓存过期了,service将根据返回值生成一个 
  82.      *   Last-Modified字段,并调用doGet方法 
  83.      * 3.本方法返回值是一个正数,且请求消息中包含的If-Modified-Since的时间值比返回值新或者相同, 
  84.      * 说明缓存有效,service方法将不调用doGet方法,而是返回304(Not Modified)告诉浏览器缓存仍然有效 
  85.      */  
  86.     protected long getLastModified(HttpServletRequest req) {  
  87.         return -1;  
  88.     }  
  89.   
  90.   
  91.    /** 
  92.     * 没有相应实体,其他与GET方法相同,也正是通过调用doGet来完成请求 
  93.     */  
  94.     protected void doHead(HttpServletRequest req, HttpServletResponse resp)  
  95.         throws ServletException, IOException {  
  96.         //本类的内部类  
  97.         NoBodyResponse response = new NoBodyResponse(resp);  
  98.   
  99.         doGet(req, response);  
  100.         response.setContentLength();  
  101.     }  
  102.   
  103.   
  104.     /** 
  105.      * 也是被service方法调用,默认实现是报错,参考doGet方法 
  106.      */  
  107.     protected void doPost(HttpServletRequest req, HttpServletResponse resp)  
  108.         throws ServletException, IOException {  
  109.   
  110.         String protocol = req.getProtocol();  
  111.         String msg = lStrings.getString("http.method_post_not_supported");  
  112.         if (protocol.endsWith("1.1")) {  
  113.             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);  
  114.         } else {  
  115.             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);  
  116.         }  
  117.     }  
  118.   
  119.     /** 
  120.      * 被Servlet容器调用,完成请求 
  121.      * 把请求转发到具体的方法上,通过调用重载的service(HttpServletRequest,HttpServletResponse) 
  122.      * 完成这个方法做的事情就是把request和response转换成HttpServerRequest,HttpServletResponse, 
  123.      * 具体的转发工作由重载的service方法完成 
  124.      */  
  125.     @Override  
  126.     public void service(ServletRequest req, ServletResponse res)  
  127.         throws ServletException, IOException {  
  128.   
  129.         HttpServletRequest  request;  
  130.         HttpServletResponse response;  
  131.   
  132.         try {  
  133.             request = (HttpServletRequest) req;  
  134.             response = (HttpServletResponse) res;  
  135.         } catch (ClassCastException e) {  
  136.             throw new ServletException("non-HTTP request or response");  
  137.         }  
  138.         service(request, response);  
  139.     }  
  140.       
  141.   
  142.     /** 
  143.      * 处理标准的HTTP请求,此方法把具体的请求转发到相应的doXXX方法上 
  144.      * 被service(ServletRequest,ServletResponse)调用 
  145.      * 没有理由重写此方法 
  146.      */  
  147.     protected void service(HttpServletRequest req, HttpServletResponse resp)  
  148.         throws ServletException, IOException {  
  149.         //返回HTTP的请求方式,如GET,POST,HEAD  
  150.         String method = req.getMethod();  
  151.           
  152.         /** 
  153.          * 如果是GET方式 
  154.          * 1.首先通过getLastModified(req)获得修改时间 
  155.          *  i.如果修改时间==-1,不管请求是怎样的,直接调用doGet 
  156.          *  ii.如果不是-1,则需要获取请求中的If-Modified_Since的值,保存到ifModifiedSince变量中 
  157.          *     如果请求头中没有If-Modified-Since字段,则ifModifiedSince=-1,通过比较lastModified 
  158.          *     和ifModifiedSince的值来判断缓存是否过期: 
  159.          *     如果lastModified代表的日期比ifModifiedSince代表的日期新时,则说明缓存失效了, 
  160.          *     比如lastModified代表9月1号修改的,ifModifiedSince为8月1号,  
  161.          *     意思是如果自8月1号没有修改过的话,则可以使用缓存,很明显已经修改过了, 
  162.          *     所以不能使用缓存,这个时候要调用doGet方法响应,同时在相应头设置Last-Modified的值。 
  163.          *     如果lastModified代表的时间比ifModifiedSince旧时,也就是没有修改过, 
  164.          *     则返回304(Not Modified)告诉浏览器,直接使用缓存。 
  165.          * 2.判断是何种方式,调用具体的doXXX方法 
  166.          *      
  167.          */  
  168.         if (method.equals(METHOD_GET)) {  
  169.             long lastModified = getLastModified(req);  
  170.             if (lastModified == -1) {  
  171.                 // servlet doesn't support if-modified-since, no reason  
  172.                 // to go through further expensive logic  
  173.                 doGet(req, resp);  
  174.             } else {  
  175.                 long ifModifiedSince;  
  176.                 try {  
  177.                     ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);  
  178.                 } catch (IllegalArgumentException iae) {  
  179.                     // Invalid date header - proceed as if none was set  
  180.                     ifModifiedSince = -1;  
  181.                 }  
  182.                 if (ifModifiedSince < (lastModified / 1000 * 1000)) {  
  183.                     // If the servlet mod time is later, call doGet()  
  184.                     // Round down to the nearest second for a proper compare  
  185.                     // A ifModifiedSince of -1 will always be less  
  186.                     maybeSetLastModified(resp, lastModified);  
  187.                     doGet(req, resp);  
  188.                 } else {  
  189.                     resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);  
  190.                 }  
  191.             }  
  192.   
  193.         } else if (method.equals(METHOD_HEAD)) {  
  194.             long lastModified = getLastModified(req);  
  195.             maybeSetLastModified(resp, lastModified);  
  196.             doHead(req, resp);  
  197.   
  198.         } else if (method.equals(METHOD_POST)) {  
  199.             doPost(req, resp);  
  200.   
  201.         } else if (method.equals(METHOD_PUT)) {  
  202.             doPut(req, resp);  
  203.   
  204.         } else if (method.equals(METHOD_DELETE)) {  
  205.             doDelete(req, resp);  
  206.   
  207.         } else if (method.equals(METHOD_OPTIONS)) {  
  208.             doOptions(req,resp);  
  209.   
  210.         } else if (method.equals(METHOD_TRACE)) {  
  211.             doTrace(req,resp);  
  212.   
  213.         } else {  
  214.             //没有适合的响应方法,只能报错了  
  215.             String errMsg = lStrings.getString("http.method_not_implemented");  
  216.             Object[] errArgs = new Object[1];  
  217.             errArgs[0] = method;  
  218.             errMsg = MessageFormat.format(errMsg, errArgs);  
  219.   
  220.             resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);  
  221.         }  
  222.     }  
  223.   
  224.   
  225.     /* 
  226.      * 设置响应的实体首部字段Lat-Modified 
  227.      */  
  228.     private void maybeSetLastModified(HttpServletResponse resp,  
  229.                                       long lastModified) {  
  230.         if (resp.containsHeader(HEADER_LASTMOD))  
  231.             return;  
  232.         if (lastModified >= 0)  
  233.             resp.setDateHeader(HEADER_LASTMOD, lastModified);  
  234.     }  
  235. }  
ServletConfig

ServletConfig封装了Serlvet的初始化参数,同时其内部包含代表Servlet容器的对象,具体的解释都在源码中了:

[java] view plain copy
  1. /** 
  2.  * 在一个Servlet被初始化是被Serlvet容器传递给init方法 
  3.  * 包含了Servletd的配置信息。它提供了两个方法用于Servlet的初始化参数: 
  4.  * public String getInitParameter(String name):获取指定参数的值 
  5.  * public Enumeration<String> getInitParameterNames():获得所有的参数名称,遍历该Enumeration,可获得所有参数 
  6.  * 不得不吐槽一下,Enumeration都已经被Iterator取代了,竟然还在用这个,真土 
  7.  * 初始参数是在Servlet的配置中指定的,如下面的示例就指定了名字和年龄,可以通过上面两个方法获得这些值: 
  8.  * <servlet> 
  9.  *   <servlet-name>LifeCircle</servlet-name> 
  10.  *  <servlet-class>com.servlet.base.LifeCircle</servlet-class> 
  11.  *   <init-param> 
  12.  *    <param-name>name</param-name> 
  13.  *     <param-value>caoxiaoyong</param-value> 
  14.  *  </init-param> 
  15.  *  <init-param> 
  16.  *    <param-name>age</param-name> 
  17.  *    <param-value>25</param-value> 
  18.  *  </init-param> 
  19.  *</servlet> 
  20.  *---------------------------------------------- 
  21.  *还有一个方法用于获取Serlvet容器的对象: 
  22.  *public ServletContext getServletContext() 
  23.  */  
  24. public interface ServletConfig {  
  25.   
  26.     /**返回Servlet的名称 */  
  27.     public String getServletName();  
  28.     public ServletContext getServletContext();  
  29.     public String getInitParameter(String name);  
  30.     public Enumeration<String> getInitParameterNames();  
  31. }  
通过上面的分析,我们应该能很清晰的回答如何编写一个Servlet类了:如果是处理Http请求,则继承自HttpServlet类,并根据请求方法去覆盖相应的doXXX方法;如果不是Http请求,则继承GenericServlet类。如果认真的分析了上面四个接口或类的源码,我想收获的不止这一点。

转载请注明:喻红叶《Servlet基本结构的源码分析》

地址:http://blog.csdn.net/yuhongye111/article/details/38946909

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 黑坑青鱼滑口怎么办 不交物业费物业怎么办 车牌刮了一点漆怎么办 电脑光驱线坏了怎么办 做系统不读光驱怎么办 光盘放进电脑没反应怎么办 不服省高院裁定维持原判怎么办 咖啡和酒一起喝怎么办 跟法官联系不上怎么办 四维没有预约到怎么办 钥匙锁在车里怎么办 如果孩子很叛逆骂人打人怎么办 错过了今年规培怎么办 枣木怎么办才能搞直了 高中生和家里闹意见离家出走怎么办 校长信箱实名举报了怎么办 枣子吃多了胀气怎么办 红枣吃多了会怎么办 宁波南苑e家会员卡怎么办 宁波社保卡丢了怎么办 奶茶汉堡店经营不好改怎么办 军人保障卡丢了怎么办 军人保障卡丢失了怎么办 军人保障卡掉了怎么办 椎基底动脉供血不足怎么办 颈椎压迫神经脑供血不足怎么办 脑部基底动脉轻度狭窄怎么办 胸壁疼痛我该怎么办 厂房面积小于泄压面积怎么办 江苏海门农村自建房房产证怎么办 颈总动脉斑块形成怎么办 颈椎引起腔梗头晕怎么办 魅族手机变成英文怎么办 员工失去了工作乐趣怎么办 古墓丽影9出bug怎么办 气炉子打不着火怎么办 下面人员不参加公司拓展怎么办 重点班的差生怎么办 江苏考生选修考d怎么办 眼睛里有虫子该怎么办 屋里毛絮特别多怎么办