servlet配置

1 Servlet介绍
1.1 简介
Servlet是一种运行于服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面,它担当客户请求与服务器响应的中间层 ,有特殊的技术规范:必须继承某个特定父类;必须配置之后才能执行;有特定的生命周期
Servlet是在Java代码中嵌入页面代码,JSP是在页面代码中嵌入Java代码
JSP不能执行,必须转译成Servlet并编译成class后才能执行,Servlet是学好JSP的基础,能了解JSP的底层运作方式

1.2 Servlet生命周期
1.2.1 生命周期
每个servlet实例的生命周期中只有三种类型的事情,分别对应于由servlet容器所调用的三个方法:

init() 初始化时期:
当servlet第一次被装载的时候由servlet容器调用init(),且只调用一次,默认情况下调用超类的init()方法。
service()运行时期:
接受客户请求并决定调用何种doXXX方法,并将处理结果返回到客户端。
destroy()结束时期:
为可选方法,释放不用的servlet实例所占内存和资源。
Servlet的生命周期可以归纳为以下几步:

Servlet创建一个Servlet实例;
Servlet调用Servlet的init()方法;
客户发送请求到Servlet;
Servlet创建一个请求对象和一个响应对象
Servlet调用service()方法,传递请求和响应对象作为参数;
service()方法获得请求对象的参数,处理请求,访问其他资源。
service()方法通过响应对象将结果传递给Server,最终到达客户端。
对于更多的相同客户端请求, Servlet将创建新的请求和响应对象,仍然激活此Servlet的service方法,传递新的参数对象,不需在初始化一次。
当Servlet不再需要Servlet时(关闭时)调用destory()方法。
1.2.2 servlet配置
<servlet>
    <servlet-name>Servlet的名称</servlet-name>
    <servlet-class>该servlet类的路径</servlet-class>
    <init-param>
          <param-name>参数名</param-name>
          <param-value>参数值</param-value>
     </init-param>
     <jsp-file>/路径/XXX.JSP </jsp-file>
    <load-on-startup>表示web站台被启动时,自动加载该servlet的优先级别,越小越优先加载</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>对应的servlet-name名称</servlet-name>
    <url-pattern>在项目运行时用以访问的URL </url-pattern>
</servlet-mapping>
url-pattern 取值:1、可以与具体的某个Servlet对应。2、也可以用/* 来指定所有的页面,更多关于servlet路径配置可以点此连接查看

1.2.3 servlet中load-on-startup作用
像上面的servlet配置中的标签<load-on-startup>,注意到它里面包含了这段配置:<load-on-startup>1</load-on-startup>,那么这个配置有什么作用呢?

load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。
它的值必须是一个整数,表示servlet应该被载入的顺序
当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
正数的值越小,该servlet的优先级越高,应用启动时就越先加载。
当值相同时,容器就会自己选择顺序来加载。
所以,<load-on-startup>x</load-on-startup>,中x的取值1,2,3,4,5代表的是优先级,而非启动延迟时间

1.3 Servlet接口
1.3.1 HttpServlet
HttpServlet类,httpServlet中各种接受请求处理的方法

方法名    描述
doGet()    处理http的get请求
doPost()    处理http的post请求,主要用于发送HTML文本中FORM的内容
doHead()    用于处理HEADER请求
doPut()    处理http的put请求,模仿 ftp发送
doDelete()    处理http的delete请求
doOptions()    该操作自动决定支持什么HTTP方法
doTrace()    处理HTTP的trace请求
发送请求的三种基本方式
地址栏直接键入地址(默认get方式) 例如:在地址栏输入:http://127.0.0.1/jsp2/index.jsp?a=2
使用表单提交(默认get方式)只有使用表单的method=post才是post提交
点击超链接(默认get方式)

1.3.1.1 service,doGet,doPost区别
HttpServlet 里的三个方法service(HttpServletRequest req, HttpServletResponse resp) ,doGet(HttpServletRequest req, HttpServletResponse resp), doPost(HttpServletRequest req, HttpServletResponse res)的区别和联系

在servlet中默认情况下,无论是get还是post提交过来都会经过service()方法来处理,然后转向到doGet或是doPost方法,可以查看HttpServlet 类的service方法

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();
        if(method.equals("GET"))
        {
            long lastModified = getLastModified(req);
            if(lastModified == -1L)
            {
                doGet(req, resp);
            } else
            {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if(ifModifiedSince < (lastModified / 1000L) * 1000L)
                {
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else
                {
                    resp.setStatus(304);
                }
            }
        } else
        if(method.equals("HEAD"))
        {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else
        if(method.equals("POST"))
            doPost(req, resp);
        else
        if(method.equals("PUT"))
            doPut(req, resp);
        else
        if(method.equals("DELETE"))
            doDelete(req, resp);
        else
        if(method.equals("OPTIONS"))
            doOptions(req, resp);
        else
        if(method.equals("TRACE"))
        {
            doTrace(req, resp);
        } else
        {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object errArgs[] = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }
从上面可以看出 这里的service是用来转向的,但是如果在自己的servlet类中覆盖了service方法,比如说你的service是下面这样的,那么这时service就不是用来转向的,而是用来处理业务的,现在不论客户端是用pos还是get来请求此servlet,都会执行service方法也只能执行servlet方法,不会去执行doPost或是doGet方法

public void service(ServletRequest req, ServletResponse res)    
                      throws ServletException, IOException {    
         res.getOutputStream().print(    
          "image is <img src='images/downcoin.gif'></img><br>");    

protected void doGet(HttpServletRequest request,    
   HttpServletResponse response) throws ServletException, IOException {    
   doPost(request,response);    
    
}
protected void doPost(HttpServletRequest request,    
    HttpServletResponse response) throws ServletException, IOException {    
    ServletOutputStream out=response.getOutputStream();    
    String[] args=(String[])request.getParameterValues("fruit");    
   for(int i=0;i<args.length;i++){    
    out.print(args[i]+"<br>");    
   }          
}   
所以,我们在写servlet的时候,一般都是重写doGet或doPost方法,不会管service方法

1.3.2 表单提交中get和post方式区别
1.3.2.1 表单中区别
表单提交中get和post方式的区别有5点

get是从服务器上获取数据,post是向服务器传送数据。
get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一 一对应,在URL中可以看到。post是通过HTTPpost机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制
get安全性非常低,post安全性较高。
1.3.2.2 http中区别
HTTP请求:get与post方法的区别:
HTTP定义了与服务器交互的不同方法,最基本的方法是 get 和 post。事实上 get 适用于多数请求,而保留 post仅用于更新站点。根据 HTTP规范,get 用于信息获取,而且应该是安全的和幂等的。所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,get 请求一般不应产生副作用。幂等的意味着对同一URL的多个请求应该返回同样的结果。完整的定义并不像看起来那样严格。从根本上讲,其目标是当用户打开一个链接时,可以确信从自身的角度来看没有改变资源。比如,新闻站点的头版不断更新。虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等的,因为它总是返回当前的新闻。反之亦然。但 post请求就不那么轻松了。post表示可能改变服务器上的资源的请求。

在FORM提交的时候,如果不指定Method,则默认为get请求,Form中提交的数据将会附加在url之后,以?分开与url分开。字母数字字符原样发送,但空格转换为+号,其它符号转换为%XX, 其中XX为该符号以16进制表示的ASCII(或ISOLatin-1)值。get请求请提交的数据放置在HTTP请求协议头中,而post提交的数据则 放在实体数据中;
get方式提交的数据最多只能有1024字节,而post则没有此限制
在表单里使用post和get有什么区别
在Form里面,可以使用post也可以使用get。它们都是method的合法取值。但是,post和get方法在使用上至少有两点不同:

get方法通过URL请求来传递用户的输入。post方法通过另外的形式。
get方式的提交需要用Request.QueryString来取得变量的值,而post方式提交时,必须通过Request.Form来访问提交的内容。
2 Servlet原理
2.1 工作原理
2.1.1 问题引入
Servlet是如何工作的?Servlet 如何实例化、共享变量、并进行多线程处理?
假设有一个运行了大量 Servlet 的 web 服务器。通过 Servlet 之间传输信息得到 Servlet 上下文,并设置 session 变量。现在,如果有两名或更多使用者向这个服务发送请求,接下来 session 变量会发生什么变化?究竟是所有用户都是用共同的变量?还是不同的用户使用的变量都不一样?如果是后者,服务器如何区分不同用户?
如果有n名用户访问一个特定的 Servlet,那么该 Servlet 是仅在第一个用户首次访问的时候实例化,还是分别为每个用户实例化?

2.1.2 Servlet容器启动
当 Servlet 容器(比如 Apache Tomcat)启动后,会部署和加载所有 web 应用。当web应用被加载,Servlet 容器会创建一次ServletContext,然后将其保存在服务器的内存中。
web应用的web.xml被解析,找到其中所有 servlet、filter 和 Listener 或 @WebServlet、@WebFilter 和@WebListener 注解的内容,创建一次并保存到服务器的内存中。对于所有过滤器会立即调用init()。当 Servlet 容器停止,将卸载所有 web 应用,调用所有初始化的 Servlet 和过滤器的destroy() 方法,最后回收 ServletContext 和所有 Servlet、Filter 与 Listener实例。
当Servlet 配置的load-on-startup或者 @WebServlet(loadOnStartup) 设置了一个大于 0 的值,则同样会在启动的时候立即调用 init() 方法。
load-on-startup中的值表示那些 Servlet 会以相同顺序初始化。如果配置的值相同,会遵循 web.xml 中指定的顺序或 @WebServlet类加载的顺序。另外,如果不设置 load-on-startup 值,init() 方法只在第一次HTTP请求命中问题中的Servlet时才被调用

2.1.3 HttpServletRequest 与 HttpServletResponse
Servlet 容器附加在一个 web 服务上,这个 web 服务会在某个端口号上监听 HTTP 请求,在开发环境中这个端口通常为 8080,生产环境中通常为 80。当客户端(web 浏览器)发送了一个 HTTP 请求,Servlet 容器会创建新的 HttpServletRequest 和 HttpServletResponse 对象,传递给已创建好并且请求的 URL 匹配 url-pattern 的 Filter 和 Servlet 实例中的方法,所有工作都在同一个线程中处理。
request 对象可以访问所有该 HTTP 请求中的信息,例如 request header 和 request body。response 对象提供需要的控制和发送 HTTP 响应方法,例如设置 header 和 body(通常会带有 JSP 文件中的 HTML 内容)。提交并完成HTTP 响应后,将回收 request 和 response 对象。

2.1.4 HttpSession
当用户第一次访问该 web 应用时,会通过 request.getSession() 第一次获得 HttpSession。之后 Servlet 容器将会创建 HttpSession,生成一个唯一的 ID(可以通过 session.getId() 获取)并储存在服务器内存中。然后 Servlet 容器在该次 HTTP 响应的 Set-Cookie 头部设置一个 Cookie,以JSESSIONID 作为 Cookie 名字,那个唯一的 session ID 作为 Cookie 的值。
按照 HTTP cookie 规则(正常 web 浏览器和 web 服务端必须遵循的标准),当 cookie 有效时,要求客户端(浏览器)在后续请求的 Cookie 头中返回这个 cookie。
使用浏览器内置的HTTP流量监控器,Servlet容器将会确定每个进入的 HTTP 请求的 Cookie 头中是否存在名为JSESSIONID 的 cookie,然后用它的值(session ID)从服务端内存中找到关联的 HttpSession。
可以在 web.xml 中设置 session-timeout ,默认值为 30 分钟。超时到达之前 HttpSession 会一直存活。所以当客户端不再访问该 web 应用超过 30 分钟后,Servlet 容器就会回收这个 session。后续每个请求,即使指定 cookie 名称也不能再访问到相同的 session。Servlet 容器会创建一个新的 Cookie。
另一方面,客户端上的 session cookie 有一个默认存活时间,该事件和该浏览器实例运行时间一样长。所以,当客户端关闭该浏览器实例(所有标签和窗口)后,这个 session 就会被客户端回收。新浏览器实例不再发送与该 session 关联的 cookie。一个新的 request.getSession() 将会返回新的HttpSession 并设置一个拥有新 session ID 的 cookie

2.1.5 ServletContext
ServletContext 与 web 应用存活时间一样长。它被所有 session 中的所有请求共享。
只要客户端一直与相同浏览器实例的web应用交互并且没有超时HttpSession就会存在。
HttpServletRequest和 HttpServletResponse 的存活时间为客户端发送完成到完整的响应(web页面)到达的这段时间。不会被其他地方共享。
所有 Servlet、Filter 和 Listener 对象在web 应用运行时都是活跃的。它们被所有 session 中的请求共享。
设置在 HttpServletRequest、HttpServletResponse 和 HttpSession 中的所有属性在问题中的对象存活时都会一直保持存活。

2.1.6 线程安全
即便如此,最关心的可能是线程安全。现在应该学习到 Servlet 和 filter 被所有请求共享。那是 Java 的一个优点,使得多个不同线程(读取 HTTP 请求)可以使用同一个实例。否则为每个请求重新创建线程的开销实在过于昂贵。
但应该也意识到永远不要将任何 request 或 session 域中的数据赋值给 servlet 或 filter 的实例变量。它将会被所有其他 session 中的所有请求共享。那是非线程安全的
下面的示例对这种情况进行了展示:

public class ExampleServlet extends HttpServlet {
 
    private Object thisIsNOTThreadSafe;
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;
 
        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    }
}
由于当客户端第一次请求某一个JSP文件时,服务端把该JSP编译成一个CLASS文件,并创建一个该类的实例,然后创建一个线程处理CLIENT端的请求。如果有多个客户端同时请求该JSP文件,则服务端会创建多个线程。每个客户端请求对应一个线程。以多线程方式执行可大大降低对系统的资源需求,提高系统的并发量及响应时间.

对JSP中可能用的的变量说明如下:

实例变量: 实例变量是在堆中分配的,并被属于该实例的所有线程共享,所以不是线程安全的. JSP系统提供的8个类变量:JSP中用到的OUT,REQUEST,RESPONSE,CONFIG,PAGE,PAGECONXT是线程安全的(因为每个线程对应的request,respone对象都是不一样的,不存在共享问题), SESSION,APPLICATION在整个系统内被使用,所以不是线程安全的
ServletContext:(线程是不安全的) ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。
HttpSession:(线程是不安全的) HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。 当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。 ServletRequest:(线程是安全的) 对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。
注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用
局部变量: 局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以是线程安全的.
静态类: 静态类不用被实例化,就可直接使用,也不是线程安全的.
外部资源: 在程序中可能会有多个线程或进程同时操作同一个资源(如:多个线程或进程同时对一个文件进行写操作).此时也要注意同步问题. 使它以单线程方式执行,这时,仍然只有一个实例,所有客户端的请求以串行方式执行。这样会降低系统的性能
2.2 Servlet单例多线程
2.2.1 Servlet如何处理多个请求访问
Servlet容器默认是采用单实例多线程的方式处理多个请求的:

当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
容器初始化Servlet主要就是读取配置文件(例如tomcat,可以通过servlet.xml的<Connector>设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。
当请求到达时,Servlet容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;
线程执行Servlet的service方法;
请求结束,放回线程池,等待被调用; (注意:避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,都来操作它,照成数据的不一致,因此产生线程安全问题)
从上面可以看出:

Servlet单实例,减少了产生servlet的开销;
通过线程池来响应多个请求,提高了请求的响应时间;
Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;
每一个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求; Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。
2.2.2 Servlet容器如何采用单实例多线程的方式来处理请求
Java的内存模型JMM(Java Memory Model) JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。根据JMM,我们可以将论文中所讨论的Servlet实例的内存模型抽象为图所示的模型。

工作者线程Work Thread:执行代码的一组线程。
调度线程Dispatcher Thread:每个线程都具有分配给它的线程优先级,线程是根据优先级调度执行的。
Servlet采用多线程来处理多个请求同时访问。servlet依赖于一个线程池来服务请求。线程池实际上是一系列的工作者线程集合。Servlet使用一个调度线程来管理工作者线程。 当容器收到一个Servlet请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servlet的service()方法将在多线程中并发执行。 Servlet容器默认采用单实例多线程的方式来处理请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间,对于Tomcat可以在server.xml中通过<Connector>元素设置线程池中线程的数目。
就实现来说,调度者线程类所担负的责任如其名字,该类的责任是调度线程,只需要利用自己的属性完成自己的责任。所以该类是承担了责任的,并且该类的责任又集中到唯一的单体对象中。而其他对象又依赖于该特定对象所承担的责任,我们就需要得到该特定对象。那该类就是一个单例模式的实现了

2.2.3 Servlet多实例
服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器创建一个Servlet类的多个Servlet实例组成的实例池,对于每个请求分配Servlet实例进行响应处理,之后放回到实例池中等待下此请求。这样就造成并发访问的问题。此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。而对于这样多实例的情况SingleThreadModel接口并不能解决并发访问问题。 SingleThreadModel接口在servlet规范中已经被废弃了。

Servlet并非只是单例的. 当container开始启动,或是客户端发出请求服务时,Container会按照容器的配置负责加载和实例化一个Servlet(也可以配置为多个,不过一般不这么干).不过一般来说一个servlet只会有一个实例。

Struts2的Action是原型,非单实例的;会对每一个请求,产生一个Action的实例来处理。
Struts1的Action,Spring的Ioc容器管理的bean默认是单实例的. Struts1 Action是单实例的,spring mvc的controller也是如此。因此开发时要求必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。 Spring的Ioc容器管理的bean 默认是单实例的。 Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)。 当Spring管理Struts2的Action时,bean默认是单实例的,可以通过配置参数将其设置为原型。(scope=prototype )
2.2.4 容器概念理解
Servlet容器,Web容器,应用服务器
Servlet容器的主要任务就是管理Servlet的生命周期;
Web容器也称之为web服务器,主要任务就是管理和部署web应用的; 应用服务器的功能非常强大,不仅可以管理和部署web应用,也可以部署EJB应用,实现容器管理的事务等等。。。
Web服务器就是跟基于HTTP的请求打交道,而EJB容器更多是跟数据库,事务管理等服务接口交互,所以应用服务器的功能是很多的。 常见的web服务器就是Tomcat,但Tomcat同样也是Servlet服务器; 常见的应用服务器有WebLogic,WebSphere,但都是收费的; 没有Servlet容器,可以用Web容器直接访问静态Html页面,比如安装了apache等;如果需要显示Jsp/Servlet,就需要安装一个Servlet容器;但是光有servlet容器也是不够的,它需要被解析为html显示,所以仍需要一个web容器;所以,我们常把web容器和Servlet容器视为一体,因为他们两个容器都有对方的功能实现了,都没有独立的存在了,比如tomcat

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值