zoukankan      html  css  js  c++  java
  • Servlet的基础知识

    没有什么固定的结构, 就是稍微总结一下学习到的, 基本上想到哪里写到哪里.

    关于基本的最HttpServlet

    实际上Servlet是J2EE(也就是现在的Java EE)中规范的一个接口, 用于根据客户端请求生成响应内容并将其传给服务器, 也就是Java的动态Web应用的基本. 大概的体系构架, 可以用实例代码稍微探索了一下 :

    1. 这个类是HttpServlet的子类, 对着HttpServlet按Command + b(IDEA中), 可以发现HttpServlet是GenericServlet的子类, 继续往上, 会发现GenericServlet实现了我们上面提到的Servlet接口. 稍微解释一下, 和http有关的方法(例如doGet, doPost之流)都在HttpServlet中给出了实现, 其他方法则一般在GenericServlet中给出了实现, 就是说, 实际上Servlet不单单适用于Http协议的web应用, 当然这里我们只考虑Http协议的web应用, 所以当然是继承了HttpServlet.
    2. 另一方面, 说几个很可能被override的方法, 可以看到override的init 和 destory方法, 这两个方法来源于GenericServlet, 分别在Servlet的实例创建和销毁的时刻被调用, 也只会被调用一次. 另外doGet 和 doPost当然是源于HttpServlet, 但是HttpServlet实际上还有一个方法叫做sevice(), 实际上, 当一个请求被传递给某个Servlet实例时, 最先被调用的反而是这个service(), 在service()方法中会判断http请求的方法(get, post, put...), 然后根据方法选择调用合适的doGet, doPost, doPut...

    容器的概念

    J2EE/Java EE实际上是个规范, 而servlet只是规范的一部分, 但是很容易可以想到, 只有Servlet是不可能可以进行JavaWeb应用的, 除了Servlet, 谁来监听来自客户端的请求? 谁来将请求转化为Java实例? 谁来根据请求来实例化特定的Servlet处理请求呢? 这里就可以引出所谓的Web容器, 我上一篇中使用的Tomcat, 实际就是一个Web容器. 那么现在我来大概说一下整个JavaWeb的过程 :

    • 客户端发出请求, 容器监听收到请求后, 实例化出ServletRequest对象和ServletResponse对象, 然后根据一定的匹配规则(这个后面会说), 找到合适的Servlet, 如果该Servlet已经实例化, 那么直接在一条新的线程中调用该实例的service方法(我们上面已经提到了), 同时传入ServletRequest对象和ServletResponse对象这两个对象, 然后service()选择根据请求方法选择对应的方法, 完成请求后, 再由容器转化为Http响应, 发给客户. 这个过程中有大概这么几点需要注意 :
      • 对于特定的Servlet, 整个过程中是实例化一次(也就是说不存在多个servlet实例), 多次请求只不过是相同实例使用多线程进行处理而已.
      • 所以对于特定Servlet而言, init和destory确实也只可能运行一次.
      • 我探究这一块的时候, 使用了一个很好用的技巧来探索某个函数的调用者(java不像js, 可以直接知道自己的调用者b是谁, 但是我们可以利用Java的异常机制来获取) :
            StackTraceElement stack[] = new Throwable().getStackTrace();
            for(StackTraceElement each : stack){
                System.out.println(each.getClassName() + '.' + each.getMethodName());
            }
    
    • 上面说到一定的匹配规则, 那么这个匹配规则是什么呢, 首先明确这个匹配规则是写在web.xml中的(关于web的结构下面会说到), 然后来看张图吧, 我一个一个解释:

    • 先说这个listener, 这里是在容器中注册一个listener, 如果做过GUI编程的话, 对于监听这个概念应该不会陌生, 这里有很多不同种类的listener接口分别用来监听不同的时间, 我提一个ServletContextListener, 实现这个接口必须实现contextInitialized 和 contextDestoryed方法, 这两个方法分别会在容器启动和关闭的时候(也就是服务器部署和关闭的时候调用), 可以用来做一些整个web project开始之前要做的准备工作以及结束之前要做的收尾.
    • 然后就是我们所谓的Servlet注册, 在servlet里面可以对每一个Servlet类进行名字以及初始化参数的注册(名字可以不等于类名, 这里我这么写只是懒得取名字), 然后在servlet-mapping中实现匹配规则的说明, 例如, 如果我的域名是http://www.baidu.com, 那么这里你想要调用Servlet_1你就应该使用http://www.baidu.com/s1.
    • 最后一个context-param在下面再提, 这里先略过.

    Servlet中参数的初始化以及属性的共享

    ServletContext

    先来说说我们上面提到的context-param里面的设置, 这里可以设置一个键值都是字符串的键值对, 这个键值对对于整个web project都是可见的, 在Servlet中可以用 getServletContext().getInitParameter(String 初始化参数名)来获得初始化参数的值, 那么可以看到这里首先调用了getServletContext(), 这个方法返回一个ServletContext对象, 这个对象是相对于整个项目而言的, 也就是说, 这个对象被每一个Servlet共享, 它的一些方法都是针对整个项目而言的, 例如我们上面提到的getInitParameter, 就能用来获得来web.xml中设置的整个的初始化参数. 所以不难想到, 这个实例实际上是在项目开始部署的时候创建的, 并且早于ServletContextListener的contextInitialized()方法, 在这个方法被调用的时候, 伴随这个这个方法的ServletContextEvent实例中实际上已经带有了ServletContext的引用, 也就是这个listener中完全可以使用context-param中设置的参数. 另外, 除了获取初始化参数之外, 这个对象还能够被用来传递变量, 例如你想在contextInitialized的时候创建一个大家都能使用的变量(例如数据库连接的引用), 那么可以使用该对象的 setAttribute()和getAttribute(), 前者接受一个key(String), 和value(Object), 后者接受key返回value, 用法简单, 实际上就是一个字典, 全局有效, 但是要注意线程安全问题.

    ServletConfig

    然后我们再说说servlet 里面的init-param, 这个和context-param很像, 实际上都能猜出来, 这个是写在某个servlet内部的, 当然就是只属于这个servlet了, 它也是String, String, 但是有一点要注意的是, 它只属于某个特定的Servlet, 所以初始化的时间也是在创建Servlet的阶段, 实际上并不是在构造函数中, 而是在构造函数之后的init中, 之前我们提到过init, 它是源自于GenericServlet, 实际上这个init在GenericServlet中的实现有两个版本 :

        public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
        }
    
        public void init() throws ServletException {
        }
    
    

    在容器构造Servlet的时候, 首先调用的是构造函数, 然后将web.xml中有关于该Servlet的初始化参数保存在ServletConfig当中, 并通过这个有参数的init传递给GenericServlet, 然后这个有参数的init会调用这个无参数的init, 所以当我们override这个无参数的init的时候, 实际上已经可以使用servlet的初始化参数了. 为了证明这一猜想, 我使用了上面说到的技巧, 我们可以看一下具体的调用栈 :

    ervlet_1.init
    javax.servlet.GenericServlet.init
    org.apache.catalina.core.StandardWrapper.initServlet
    org.apache.catalina.core.StandardWrapper.loadServlet
    org.apache.catalina.core.StandardWrapper.allocate
    org.apache.catalina.core.StandardWrapperValve.invoke
    org.apache.catalina.core.StandardContextValve.invoke
    org.apache.catalina.authenticator.AuthenticatorBase.invoke
    org.apache.catalina.core.StandardHostValve.invoke
    org.apache.catalina.valves.ErrorReportValve.invoke
    org.apache.catalina.valves.AbstractAccessLogValve.invoke
    org.apache.catalina.core.StandardEngineValve.invoke
    org.apache.catalina.connector.CoyoteAdapter.service
    org.apache.coyote.http11.Http11Processor.service
    org.apache.coyote.AbstractProcessorLight.process
    org.apache.coyote.AbstractProtocol$ConnectionHandler.process
    org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun
    org.apache.tomcat.util.net.SocketProcessorBase.run
    java.util.concurrent.ThreadPoolExecutor.runWorker
    java.util.concurrent.ThreadPoolExecutor$Worker.run
    org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run
    java.lang.Thread.run
    

    看到这个应该很明确了. 然后由于GenericServlet中保存了ServletConfig的引用, 所以可以用getServletConfig来获得这个引用, 这里实际上是Aggregation, 所以 GenericServlet本身又对一些ServletConfig一些常用的方法进行了一层包装 :

        public String getInitParameter(String name) {
            return this.getServletConfig().getInitParameter(name);
        }
    
        public Enumeration<String> getInitParameterNames() {
            return this.getServletConfig().getInitParameterNames();
        }
    
        public ServletConfig getServletConfig() {
            return this.config;
        }
    
        public ServletContext getServletContext() {
            return this.getServletConfig().getServletContext();
        }
    
    

    HttpSession

    这个和初始化参数就没什么关系了, 主要是属性的共享. ServletContext中设置属性是全项目共享, 而HttpSession是相对于一个Session共享. 那么怎么才算是一个HttpSession呢? 靠的就是session id, 当用户发送请求的时候, 如果服务器端需要用到session, 那么服务器端会在响应请求中附带这个session id, 下次用户请求的时候只需要带上这个session id即可. 附带的方式有多种, 最主要的是利用cookie, 实际上cookie就是响应请求中附带的一组或者多组键值对String: String, 它保存在客户端. 经常用来完成账号, 密码自动填充等功能. 可以利用req.getCookies()来获得Cookie[], 如果要找出里面的某个值需要遍历. 另外可以使用response.addCookie(Cookie cookie)来添加/跟新cookie, 不存在显示的跟新方式, 调用addCookie如果碰到同名的cookie, 会直接替换. 可以用cookie.setMaxAge(int seconds)来设置cookie失效的时间, 如果不设置则默认为-1, 此时浏览器关闭时失效. 但是由于cookie保存在客户端, 所以安全性并不好. 而Session大部分是基于cookie的, 就是因为Session实际内容是保存在服务器端的, 而识别标志session id完全可以保存在cookie当中. 所以安全性能更好, 一般用来做保存登录状态等等, 默认保存session id的cookie的MaxAge显然也是-1, 也就是关闭浏览器就消失, 这也是通常来说我们关闭浏览器再开启就需要重新登录但是登录框的账号总是常常已经填好的原因. Session中可以保存一组或者多组键值对String: Object, 但是同样要考虑到线程安全, 因为用户完全可以用两个tab窗口对同一个网站进行请求, 此时两个tab窗口会处于相同的session, 此时如果两者同时修改session中的属性, 是有可能出现线程安全问题的. 当然, 我们仍然需要考虑定期清除服务器保存的session, 主要来讲有如下几种途径 :

    1. 服务器关闭.
    2. session.invalidate()可以让session立即失效.
    3. session.setMaxInactiveInterval(int seconds)可以设置其生命周期, 如果一旦超时, 则失效. 另外还可以在web.xml中设置, 但是这里是以分钟为单位 :

    现在来考虑另外一个问题, 如果用户浏览器不支持Cookie或者干脆就禁用了Cookie, 那怎么办? 实际上, 解决方案是URL重写. 在tomcat中是这样的, 在实际的url后面加上;jsessionid=123456. 在用户第一次访问网站的时候, 服务器会同时使用cookie和url重写, 如果在用户下次请求到来时发现Cookie, 说明用户可以使用Cookie, 那么以后就不存在url重写, 否则使用url重写. 具体方法就不细说了, 要知道有这么回事, 以后碰到具体问题有思路就行了.

    req.getAttribute

    可以使用这个方法来获得属性, 另一方便还可以添加一部分属性之后用RequestDispatcher转发给另一个Servlet或者jsp进行处理. 这里的属性仍然一组或者多组键值对String: Object. 由于这个属性是相对于request而言的, 所以不需要担心线程安全的问题. (关于转发器在后面再细说)

    未完待续...

  • 相关阅读:
    Java语言----三种循环语句的区别
    选择结构if语句和switch语句的区别
    java中实现多态的机制是什么?
    SpringMVC的运行原理
    Struts1运行原理以及整合步骤
    我回来啦!
    Struts2的运行原理和运行与原理
    初步认识 Web Service
    spring Aop 注解
    mina2.0 spring
  • 原文地址:https://www.cnblogs.com/nzhl/p/6358290.html
Copyright © 2011-2022 走看看