分享

Servlet基础知识详解

 quasiceo 2018-07-27
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<code class="language-xml hljs "><!--配置Servlet,tomcat服务器会读取其中的内容-->
<servlet>
<!--Servlet内部名称,可以自定义,但是最好不要乱写,尽量保证有意义-->
    <servlet-name>写创建的类的名字</servlet-name>
<!--Servlet类全名,包名+简单类名-->
    <servlet-class>写创建的类的全名(包名+简单类名)</servlet-class>
</servlet>
<!--Servlet映射配置-->
<servlet-mapping>
<!--Servlet内部名称,可以自定义,但是最好不要乱写,尽量保证有意义,一定要和上面的servlet-name保持一致-->
    <servlet-name>写创建的类的名字</servlet-name>
<!--Servlet映射路径,也称为访问Servlet的名称-->
    <url-pattern>访问该servlet程序时的URL(例如/first)</url-pattern>
</servlet-mapping></code>
配置tomcat服务器,tomcat启动时会加载webapps目录下的每个web应用的web.xml配置文件 将class文件拷贝到tomcat的WEB-INF/classes目录下(拷贝时包括包名),MyEclipse会自动的加载这些内容到指定的目录中,如果不使用工具,则需要手动的加载。 访问方式: http://localhost:8080/Servlet工程名/first 成功访问说明配置正确。
为什么访问URL会执行到自定义的Servlet类中的方法呢?http://localhost 会到C:\Windows\System32\drivers\etc\hosts文件中去寻找localhost的对应ip地址127.0.0.1,接着找到8080端口,进入tomcat服务器,接着进入webapps下的Servlet工程下,关键是怎么着/first呢?
在Servlet工程中的web.xml中的url-pattern中查找是否有/first内容,如果有就成功的加载。 首先匹配servlet-mapping中url-pattern中查看,接着得到servlet-mapping中的servlet-name,从而去Servlet中查找servlet-name,从而得到servlet-class,servlet-class才是整个执行过程中最重要的部分。得到servlet-class文件的全名(实际上是一个字符串)之后,构造HttpServlet子类的对象,然后调用子类里面的方法(这个过程使用反射技术,只能通过反射才能实现)。 如果对反射的知识点不明确,请自行找资料解决。

Servlet映射路径


Servlet的映射路径有servlet-mapping中的url-pattern(注意url-pattern要么要以/开头,要么要以*开头)决定,那么url-pattern究竟怎么写,怎么去匹配呢?有两种匹配方案:

精确匹配:/first(url-pattern) http://localhost:8080/Servlet工程名/first (浏览器中输入)
精确匹配的要点是:url-pattern以/开始,并且必须将url-pattern中的内容精确的写上。在精确匹配时要注意坚决不能漏掉/,否则会报错,一定要带有/

模糊匹配,下面三种方式,但是在一个url-pattern中不能组合下面的模糊匹配方式

/* 或者直接使用/ 在浏览器中输入:http://localhost:8080/Servlet工程/任意路径(不写都行) 但是请注意,在实际写url-pattern时不要使用这两个方式,这两个方式是Servlet的缺省(default)(tomcat服务器内置)路径,该路径可以在conf/web.xml中找到。缺省的Servlet是用于解析web的静态资源文件。 /jpzhu/* 在浏览器中输入:http://localhost:8080/Servlet工程/jpzhu/任意路径(不写都行) *.后缀名(后缀名可以随便取) 在浏览器中输入: http://localhost:8080/任意路径.后缀名 注意模糊匹配不能组合起来用,只能单个使用,例如:/jpzhu/*.do

如果在浏览器中输入的url有多个Servlet同时被匹配,也就是说模糊匹配和精确匹配都能找到对应的Servlet资源,精确匹配的优先级高(长得最像的优先被匹配),但是要尽量避免这种情况的发生。当以后缀名结尾的模糊匹配,优先级最低。

Servlet映射练习

对于如下的一些映射关系:

Servlet1 映射到/abc/* Servlet2 映射到/* Servlet3 映射到/abc Servlet4 映射到*.do

问题:

当请求URL为”/abc/a.html”,此时Servlet1和Servlet2都匹配,该是哪一个被最后匹配呢?Servlet1 当请求URL为”/abc”时,Servlet1、Servlet2、Servlet3都能被匹配,该是哪一个被最后匹配呢?Servlet3 当请求URL为”/abc/a.do”时,Servlet1和Servlet4都能被匹配,该是哪一个被最后匹配呢?Servlet1,因为*.do的优先级是最低的。 当请求URL为”/a.do”时,Servlet2和Servlet4都能被匹配,最后哪一个被正确的匹配呢?Servlet2,因为*.do的优先级最低。 当请求URL为”/xxx/yyy/a.do”时,Servlet2和Servlet4都能被匹配,最后哪一个被正确的匹配呢?Servlet2,因为*.do的优先级最低。

我的理解:这个过程实际上是确实路由的路径,和Rails技术中的路由技术的原则是一样的。

解析URL的顺序:当输入一个URL时,首先会到web.xml文件中查找是否与偶匹配的url-pattern,如果能匹配到url-pattern,则处理,如果不能匹配到,则交给tomcat的default-servlet处理,也就是会去加载静态资源文件。

结论:先找动态资源,后找静态资源。

Servlet生命周期


为什么要学习Servlet生命周期

Servlet程序由tomcat服务器调用,所以有必要去知道究竟怎么去调用,Servlet生命周期就是要去搞明白Servlet对象什么时候去创建?调用什么方法?什么时候Servlet对象被销毁?销毁调用什么方法做了什么事情?Servlet程序的生命周期是由tomcat服务器去控制的。

Servlet重要的生命周期方法

构造方法:创建Servlet对象时调用,所谓的创建Servlet对象是指第一次访问Servlet的时候。Servlet在tomcat服务器中是单实例对象。
init:创建完Servlet对象时调用
service:每次发出请求时调用
destroy:销毁Servlet对象时调用。停止服务器或者重新部署web应用时调用销毁Servlet对象。

模拟通过反射构造Servlet对象
首先说明,该段代码只是对使用反射构造Servlet对象机制的模拟,该段代码并不需要我们自己去写,但是我们需要利用此段代码去明白一下所谓的反射机制。
1
2
3
4
5
6
7
8
9
10
11
12
13
<code class="language-java hljs ">class clazz = class.forName("com.jpzhutech.servlet.firstServlet");  //根据得到的字节码文件得到字节码对象
Object obj = clazz.newInstance();  //调用firstServlet的无参构造函数
Method m = class.getDeclareMethod("init",ServletConfig.class)//创建一个ServletConfig对象
m.invoke(obj,config);  //firstServlet的init方法被调用
Method m = clazz.getDeclareMethod("service",HttpServletRequest.class,HttpServletResponse.class);
m.invoke(obj,request,response); //service方法被调用
Method m = clazz.getDeclareMethod("destroy",null);
m.invoke(obj,null);//调用destroy方法</code>

Servlet单实例多线程


Servlet对象会在请求第一次加载时建立该类的对象,只要没有服务器的重新启动或者资源的重新加载,都不会再去创建新的Servlet对象,所以我们可以知道Servlet对象是单实例的,但是一个常识就是不止一个人会发出同样的URL请求,此时只有一个Servlet对象,怎么去做呢?实际上Servlet中有多线程处理逻辑,这些代码也是不需要我们去写的。

Servlet是单实例多线程的,所以我们应该把共享数据和代码块进行同步(使用synchronized关键字进行同步)。

建议在Servlet中尽量不要使用成员变量,如果确实要使用成员变量,必须要同步,而且要尽量缩小同步代码块的范围(因为同步会导致效率变得十分的低下),原则上哪里使用到了成员变量就同步哪里,以避免因为同步而导致的并发效率低下。

Servlet留给开发者的init方法


在Servlet接口以及其实现HttpServlet类中提供给了两个init方法,其函数原型分别如下:

1
2
<code class="language-java hljs ">init(ServletConfig config); // 系统调用
init();  //留给开发者使用,在其中写有关初始化的逻辑代码</code>

实际上看查看Servlet的源代码,你会知道有参数的init方法会调用无参数的init方法,但是当我们重写(@Override)了有参数的init方法会导致tomcat服务器不调用无参数的init方法,实际上这个原理可以在Servlet编程中很方便的验证,因此我们的原则是在无参数的init方法中写我们的初始化逻辑。

Servlet中核心对象学习


HttpServletRequest对象:请求对象,获取http请求信息 HttpServletResponse对象:响应对象,设置响应对象 ServletConfig对象:Servlet配置对象 ServletContext对象:Servlet上下文对象
HttpServletRequest对象

在Http协议的讲解中已经学完。

HttpServletResponse对象

在Http协议的讲解中已经学完。

ServletConfig对象

总之,ServletConfig对象的作用就是去拿参数,和Properties配置文件的功能差不多。

ServletConfig对象的作用

作用:该对象主要是用于加载Servlet的初始化参数。 Servlet初始化参数配置方法
/conf/web.xml中找到对象的Servlet对象,配置,该参数就是初始化参数。 自己配置的参数会通被封装到ServletConfig对象中,通过有参数的init方法传入。 使用this.getServletConfig()方法得到ServletConfig对象。这也符合不去覆盖有参数的init方法的原则。getServletConfig()方法是Servlet类自己提供的,我们无需去写。
ServletConfig对象的创建和获取

创建时机:在创建完Servlet对象,在调用init方法之前,服务器会自动的创建。
得到对象:直接从有参数的init方法中得到,在代码中使用this.getServletconfig即可实现。一个wen应用中有多个Servletonfig对象,一个Servlet就对应一个ServletConfig对象

ServletContext对象

ServletContext对象被称为Servlet上下文对象,它表示一个当前的web应用环境,怎么理解呢?一个web应用中有且只有一个ServletContext对象。实际上ServletContext相当于该web应用中的web.xml。

ServletContext的创建和得到

创建时机:加载web应用时创建该对象。并且该对象的创建是早于ServletConfig对象的。
得到对象:从ServletConfig的getServletContext方法得到。

ServletContext对象的核心API

getContextPath() 得到当前web应用的路径,web应用路径说白了就是部署到tomcat服务器上运行的web应用的名称,通常用在请求重定向的资源名称中。


getInitParameter(String name) 得到web应用的初始化参数

getInitParameterNames() 得到web应用的初始化参数
关于得到web应用的初始化参数,我们首先应该明白怎么去配置?同样是在web.xml文件中进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
<code class="language-xml hljs "><!--?xml version="1.0" encoding="UTF-8" ?-->
<web-app>
在该子标签下,所有的servlet和servlet-mapping子标签之外,添加配置文件,因为该配置文件是全局的,所以随便写一个Servlet程序都能使用该配置。
<context-param>
    <param-name>AAA</param-name>
    <param-value>AAA's value</param-value>
</context-param>
<!--每个context-param参数其中只有一个param-name和param-value-->
<context-param> 
    <param-name>BBB</param-name>
    <param-value>BBB's value</param-value>
</context-param>
</web-app></code>

上述两个方法的使用和ServletConfig中的该类方法使用是一样的。


setAttribute(String name,Object object) 和域对象有关的方法,用于保存数据
域对象作用是:保存数据,获取数据,可以在不通的动态资源之间共享数据 几乎天天会用该对象方法 getAttribute(String name) 用于获取共享数据 removeAttribute(String name) 删除共享数据
getRequestDispatcher(String path) 转发(类似于重定向) 其效果上相等于跳转,但是转发只能转发到当前应用内的资源,不能转发到该项目之外的资源,但是重定向能重定向到其他项目的资源中
getRealPath(String path) 如何在web应用中得到该应用的资源文件 getResourceAsStream(String path)

代码测试


下面的代码展示在一个Servlet应用中重要的文件,其他的文件请自行生成并使用,主要侧重于功能性测试。

TestServletContext.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<code class="language-java hljs ">package com.jpzhutech.servletcontext;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Servlet implementation class TestServletContext
 */
public class TestServletContext extends HttpServlet {
    private static final long serialVersionUID = 1L;
    /**
     * @see HttpServlet#HttpServlet()
     */
    public TestServletContext() {
        super();
        // TODO Auto-generated constructor stub
    }
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /***
        //ServletContext servletContext = this.getServletConfig().getServletContext();
        ServletContext servletContext = this.getServletContext();//此函数实际上是调用了上面的函数,看其源代码可以知道
        String contextPath = servletContext.getContextPath();
        System.out.println(contextPath);
        案例: getContextPath()函数通常在重定向时用到,当我们请求动态资源失败时,经常需要将其定位到一个静态资源文件中,本示例定位到index.html
        //response.sendRedirect("/servletcontext/index.html");  //当项目名称发生变化时,我们通常不希望去修改我们的源代码,使用getContextPath函数能够实现不修改源代码的效果
        response.sendRedirect(contextPath+"/index.html");
        */
        /**
         * 获取全局ServletContext参数,所有的Servlet程序都能获得
        String initParameter = servletContext.getInitParameter("AAA");
        System.out.println(initParameter);
        //使用迭代器获取初始化参数,这样我们能够获取到全部的Servlet参数
        Enumeration<string> initParameterNames = servletContext.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String nextElement = initParameterNames.nextElement();
            String initParameter2 = servletContext.getInitParameter(nextElement);
            System.out.println(initParameter2);
        }
        */
        /***
         * 域对象相关方法,域对象的作用主要是为了保存数据,获取数据,可以在不同的动态资源之间共享数据
         * 案例:比如现在有两个Servlet对象,在Servlet1中有name=eric,那么我们怎么将数据共享给Servlet2,
         * 在全局配置文件中可以配置参数,这样所有的数据都能被任何的Servlet所共享
         * 方案一:可以通过床底参数的形式传递参数,共享数据。局限:只能传递字符串类型
         * 方案二:可以通过域对象共享数据,并且可以共享任何类型的数据。那么就在Servlet1中将共享的数据保存在域对象中,
         * 然后再Servlet2中在域对象中取出共享的数据,ServletContext是我们接触到的第一个域对象
         * 为什么能这么做?原因是在Servlet编程中,ServletContext在一个应用中有且仅有一个,所以不管存在哪里,只要
         * 是同一个应用,都能被取到,ServletContext的作用域是整个web应用
         * 所有的域对象:HttpServletRequest、ServletContext、HttpSession、PageContext ,这四个都是为了保存数据
         * 共享数据,但是它们的作用域是不同的。
        ServletContext servletContext = this.getServletContext();
        servletContext.setAttribute("name", "eric");  //perfect的方案
        System.out.println("保存成功");
        Student student = new Student("xiaoming",23);
        servletContext.setAttribute("student", student);  //保存对象数据
        */
        /***
         * 转发函数的效果:效果上就是跳转页面,但是与重定向还是有区别的:
         * 1. 地址栏:转发的地址栏不会发生变化,但是重定向的地址栏会变化
         * 2. 转发只能转发到当前项目中的其它资源,但是重定向可以重定向到任意的资源没有任何的限制
         * 3. 转发可以将数据存储到request中,但是如果使用重定向,那么将数据放置在request中是不行的
         * 案例:实现从该Servlet跳转到index.html
        RequestDispatcher requestDispatcher = this.getServletContext().getRequestDispatcher("/index.html");
        //RequestDispatcher requestDispatcher = this.getServletContext().getRequestDispatcher("http://www.google.com"); //不能转发到该项目之外的资源中
        requestDispatcher.forward(request, response);  //但是该重定向操作,不会改变请求的URL,请求的URL仍然是原来的Servlet,这种重定向比较高端
        */
        /***
         * 得到指定资源的真实地址getRealPath()
        String realPath = this.getServletContext().getRealPath("/index.html");
        System.out.println(realPath);
         */
        /***
         * 将指定的资源作为流输出
        InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/index.html"); //该方法的作用是将指定的资源作为流输出
        int length = 0 ;
        byte[] buf = new byte[1024];
        while((length=resourceAsStream.read(buf)) != -1){
            String string = new String(buf,0,length);
            System.out.println(string);
        }
        */
    }
}
class Student{
    private  String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
}
</string></code>
TestServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<code class="language-java hljs ">package com.jpzhutech.servletcontext;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Servlet implementation class TestServlet
 */
public class TestServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    /**
     * @see HttpServlet#HttpServlet()
     */
    public TestServlet() {
        super();
    }
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        String name = (String)servletContext.getAttribute("name");  //只有不删除,就能一直取到
        System.out.println(name);
        Student student = (Student)servletContext.getAttribute("student");
        System.out.println(student);
    }
}
</code>
web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<code class="language-xml hljs "><!--?xml version="1.0" encoding="UTF-8"?-->
  <context-param>
    <param-name>AAA</param-name>
    <param-value>AAA's value</param-value>
  </context-param>
  <context-param>
    <param-name>BBB</param-name>
    <param-value>BBB's value</param-value>
  </context-param>
  <servlet>
    <description></description>
    <display-name>TestServletContext</display-name>
    <servlet-name>TestServletContext</servlet-name>
    <servlet-class>com.jpzhutech.servletcontext.TestServletContext</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>TestServletContext</servlet-name>
    <url-pattern>/TestServletContext</url-pattern>
  </servlet-mapping>
  <servlet>
    <description></description>
    <display-name>TestServlet</display-name>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>com.jpzhutech.servletcontext.TestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/TestServlet</url-pattern>
  </servlet-mapping>
</web-app></code>
index.html
1
2
<code class="language-html hljs ">
</code>
This is my HTML page. 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多