JAVA——J2EE之Servlet 基础篇(一)

一、开发Servlet

Servlet 本身不能独立运行,需要在一个web应用中运行的
而一个web应用是部署在tomcat中的

所以开发一个servlet需要如下几个步骤
创建web应用项目
编写servlet代码
部署到tomcat中

开发的IDE也有各种选择,有的使用MyEclipse,有的使用eclipse 的EE版本,有的使用IDEA开发。

本例使用Eclipse EE版 结合独立的 tomcat进行一次java普通项目的创建

这样做的好处是,通过最原始的方式创建一个web应用,可以掌握最基本的知识。

必读: 基于Tomcat的程序要成功运行,对于配置文件的正确性有着苛刻的要求,任何一个地方出错了,都会导致运行失败。 如果你是第一次学习本教程,务必严格按照教程的指导,完全模仿操作,直到成功看到运行效果。 第一次成功之后,信心,思路都会有较好的铺垫,然后再根据自己的疑惑,在“成功”的代码上做原本想做的改动和调整,这样可以大大节约学习的时间,提高效率,切勿一来就擅自改动,给自己的学习制造障碍

指定项目输出到classes目录

首先在WEB-INF下创建classes目录

把项目的class文件输出由原来的 j2ee/bin 设置到 j2ee/web/WEB-INF/classes下

步骤:
项目右键->properties->Java Build Path->Source->右下角的 Brower-> 指定位置是 j2ee/web/WEB-INF/classes。

为什么要有这一步? 在ecilpse中默认输出的class是在bin目录下,但是tomcat启动之后,在默认情况下,不会去bin目录找这些class文件,而是到WEB-INF/classes这个目录下去寻找。 所以通过这一步的配置,使得eclipse的class文件输出到WEB-INF/classes目录下,那么这样就和tomcat兼容了。

SERVLET 基础 调用流程

这里写图片描述
首先访问html文件,不加html是不行滴,那就变成直接访问

有.html后缀请求的就是硬盘上的静态文件, 不带后缀请求的就是Servlet, 这时才会创建相应的servlet对象(如果是第一次请求此servlet的话)

http://127.0.0.1/login.html

打开一个静态的html页面,在这个页面中可以通过form,以post的形式提交数据

2./login路径

在上一步的login.html中,用form,把账号和密码,提交到/login这个路径,并且附带method=”post”

3.找到对应的Servlet

tomcat接受到一个新的请求:

http://127.0.0.1/login

其路径是/login,接着就到配置文件web.xml进行匹配发现/login对应的Servlet类是 LoginServlet
接下来的工作,就会基于这个LoginServlet进行。

4.实例化Servlet对象

Tomcat 定位到了LoginServlet后,发现并没有LoginServlet的实例存在,于是就调用LoginServlet的public无参的构造方法LoginServlet()实例化一个LoginServlet对象以备后续使用

5.调用doGet或者doPost

Tomcat从上一步拿到了LoginServlet的实例之后,根据页面login.html提交信息的时候带的method=”post”,去调用对应的doPost方法

6.request获取参数

接着流程进入了doPost方法中,

protected void doPost(HttpServletRequest request, HttpServletResponse response){
...
}

在这个方法中,通过参数request,把页面上传递来的账号和密码信息取出来

String name = request.getParameter("name");
String password = request.getParameter("password");

7.response设置响应

接着,根据账号和密码是否正确(判断是否是admin和123), 创建不同的html字符串

然后把html字符串通过如下方式,设置在了response对象上。

意味着response对象上有个输出流?

PrintWriter pw = response.getWriter();
pw.println(html);

到这里,Servlet的工作就做完了。

8.tomcat把html传递给浏览器

在Servlet完成工作之后,tomcat拿到被Servlet修改过的response,根据这个response 生成html 字符串,然后再通过HTTP协议,这个html字符串,回发给浏览器,浏览器再根据HTTP协议获取这个html字符串,并渲染在界面上。

这样在效果上,浏览器就可以看到Servlet中生成的字符串了。

二、DOGET方法 DOPOST方法 SERVICE方法

doGet()

当浏览器使用get方式提交数据的时候,servlet需要提供doGet()方法
哪些是get方式呢?

form**默认**的提交方式
如果通过一个超链访问某个地址
如果在地址栏直接输入某个地址
ajax指定使用get方式的时候

doPost()

当浏览器使用post方式提交数据的时候,servlet需要提供doPost()方法
哪些是post方式呢?

form上显示设置 method=”post”的时候
ajax指定post方式的时候

service()

LoginServlet继承了HttpServlet,同时也继承了一个方法

service(HttpServletRequest , HttpServletResponse )

实际上,在执行doGet()或者doPost()之前,都会先执行service()

由service()方法进行判断,到底该调用doGet()还是doPost()

可以发现,service(), doGet(), doPost() 三种方式的参数列表都是一样的。

所以,有时候也会直接重写service()方法,在其中提供相应的服务,就不用区分到底是get还是post了。

比如把前面的登录的LoginServlet,改为提供service方法,也可以达到相同的效果

三、中文问题

获取中文的参数

为了成功获取中文参数,需要做如下操作
1. login.html中加上

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

这句话的目的是告诉浏览器,等下发消息给服务器的时候,使用UTF-8编码

  1. login.html
    form的method修改为post

  2. 在servlet进行解码和编码

byte[] bytes= name.getBytes(“ISO-8859-1”);
name = new String(bytes,”UTF-8”);

先根据ISO-8859-1解码,然后用UTF-8编码
这样就可以得到正确的中文参数了

这样需要对每一个提交的数据都进行编码和解码处理,如果觉得麻烦,也可以使用一句话代替:

request.setCharacterEncoding("UTF-8"); 

并且把这句话放在request.getParameter()之前

以上是使用UTF-8的方式获取中文呢。 也可以使用GBK。把所有的UTF-8替换为GBK即可。 GB2312同理。

返回中文的响应

在Servlet中,加上

 response.setContentType("text/html; charset=UTF-8");
 PrintWriter pw = response.getWriter();
 pw.println(html);

四、生命周期

一个Servlet的生命周期由 实例化,初始化,提供服务,销毁,被回收 个步骤组成

1.实例化

当用户通过浏览器输入一个路径,这个路径对应的servlet被调用的时候,该Servlet就会被实例化

为LoginServlet**显式**提供一个构造方法 LoginServlet()

然后通过浏览器访问,就可以观察到
“LoginServlet 构造方法 被调用”

无论访问了多少次LoginServlet
LoginSerlvet构造方法 只会执行一次,所以Serlvet是单实例

2.初始化

LoginServlet 继承了HttpServlet,同时也继承了init(ServletConfig) 方法

init方式是一个实例方法,所以会在构造方法执行后执行。

无论访问了多少次LoginSerlvet
init初始化 只会执行一次

public class LoginServlet extends HttpServlet {

    public LoginServlet() {
        System.out.println("LoginServlet 构造方法 被调用");
    }

     //这个ServletConfig哪来的?
    public void init(ServletConfig config) {
        System.out.println("init(ServletConfig)");
    }

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 略

    }

}

3.提供服务

接下来就是执行service()方法,然后通过浏览器传递过来的信息进行判断,是调用doGet()还是doPost()方法,重写service()方法可替代doGet()还是doPost()方法

在service()中就会编写我们的业务代码,在本例中就是判断用户输入的账号和密码是否正确

4.销毁

接着是销毁destroy()
在如下几种情况下,会调用destroy()
1. 该Servlet所在的web应用重新启动
在serverl.xml中配置该web应用的时候用到了

<Context path="/" docBase="e:\\project\\j2ee\\web" debug="0" reloadable="false" />

如果把 reloadable=”false” 改为reloadable=”true” 就表示有任何类发生的更新,web应用会自动重启
当web应用自动重启的时候,destroy()方法就会被调用

  1. 关闭tomcat的时候 destroy()方法会被调用,但是这个一般都发生的很快,不易被发现。

5.被回收

当该Servlet被销毁后,就满足垃圾回收的条件了。 当下一次垃圾回收GC来临的时候,就有可能被回收。

五、跳转——服务端跳转和客户端跳转

页面跳转是开发一个web应用经常会发生的事情。

比如登录成功或是失败后,分别会跳转到不同的页面。

跳转的方式有两种,服务端跳转客户端跳转

1.准备两个页面 success.html fail.html
首先在web目录下准备两个页面 success.html,fail.html
分别用于显示登录成功 或者登录失败

如果登录成功了,就服务端跳转到success.html

如果登录失败了,就客户端跳转到fail.html

<div style="color:green">login success</div>
<div style="color:red">login fail</div>

1.服务端跳转request.getRequestDispatcher(“success.html”).forward(request, response)

在Servlet中进行服务端跳转的方式:

if ("admin".equals(name) && "123".equals(password)) {
request.getRequestDispatcher("success.html").forward(request, response);
        }

Dispatcher是调度收发 的意思
服务端跳转可以看到浏览器的地址依然是/login 路径,并不会变成success.html
这里写图片描述

2.客户端跳转——sendRedirect

在Servlet中进行客户端跳转的方式:

//成功是服务端跳转
if ("admin".equals(name) && "123".equals(password)) {
            request.getRequestDispatcher("success.html").forward(request, response);
        }
        else{//失败是客户端跳转
            response.sendRedirect("fail.html");
        }

可以观察到,浏览器地址发生了变化

六、自启动

有的时候会有这样的业务需求:
tomcat一启动,就需要执行一些初始化的代码,比如校验数据库的完整性等。

但是Servlet的生命周期是在用户访问浏览器对应的路径开始的。如果没有用户的第一次访问,就无法执行相关代码。

这个时候,就需要Servlet实现自启动 即,伴随着tomcat的启动,自动启动初始化,在初始化方法init()中,就可以进行一些业务代码的工作了。

1.load-on-startup

在web.xml中,配置Hello Servlet的地方,增加一句

<load-on-startup>10</load-on-startup>

取值范围是1-99

即表明该Servlet会随着Tomcat的启动而初始化。

同时,为HelloServlet提供一个init(ServletConfig) 方法,验证自启动

如图所示,在tomcat完全启动之前,就打印了init of HelloServlet
10 中的10表示启动顺序
如果有多个Servlet都配置了自动启动,小的数字先启动

七、REQUEST常见方法

request对象的类是HttpServletRequest,提供了很多有实用价值的方法

request的常见方法

request.getRequestURL(): 浏览器发出请求时的完整URL,包括协议 主机名 端口(如果有)” +
request.getRequestURI(): 浏览器发出请求的资源名部分,去掉了协议和主机名” +
request.getQueryString(): 请求行中的参数部分,只能显示以get方式发出的参数,post方式的看不到
request.getRemoteAddr(): 浏览器所处于的客户机的IP地址
request.getRemoteHost(): 浏览器所处于的客户机的主机名
request.getRemotePort(): 浏览器所处于的客户机使用的网络端口
request.getLocalAddr(): 服务器的IP地址
request.getLocalName(): 服务器的主机名
request.getMethod(): 得到客户机请求方式一般是GET或者POST

获取参数

request.getParameter(): 是常见的方法,用于获取单值的参数
request.getParameterValues(): 用于获取具有多值得参数,比如注册的时候提交的爱好,可以使多选的。
request.getParameterMap(): 用于遍历所有的参数,并返回Map类型

本例准备了一个注册的例子
包括一个注册页面 register.html
服务端的 RegisterServlet

分别演示了获取单值参数,多值参数以及遍历所有的参数

获取头信息

request.getHeader() 获取浏览器传递过来的头信息
比如getHeader(“user-agent”) 可以获取浏览器的基本资料,这样就能判断是firefox、IE、chrome、或者是safari浏览器
request.getHeaderNames() 获取浏览器所有的头信息名称,根据头信息名称就能遍历出所有的头信息

在本例,修改HelloServlet,使其获取头信息
访问HelloServlet获取如下头信息:
host: 主机地址
user-agent: 浏览器基本资料
accept: 表示浏览器接受的数据类型
accept-language: 表示浏览器接受的语言
accept-encoding: 表示浏览器接受的压缩方式,是压缩方式,并非编码
connection: 是否保持连接
cache-control: 缓存时限

public class HelloServlet extends HttpServlet{

    public void init(ServletConfig config){
        System.out.println("init of Hello Servlet");
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response){

        Enumeration<String> headerNames= request.getHeaderNames();
        while(headerNames.hasMoreElements()){
            String header = headerNames.nextElement();
            String value = request.getHeader(header);
            System.out.printf("%s\t%s%n",header,value);
        }

        try {
            response.getWriter().println("<h1>Hello Servlet!</h1>");
            response.getWriter().println(new Date().toLocaleString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

服务端传参

setAttribute和getAttribute可以用来在进行服务端跳转的时候,在不同的Servlet之间进行数据共享

在MVC章节会提供更好实例帮助大家理解这两个方法的用途

八、RESPONSE常见用法

通过response设置响应已经用得比较多了,在前面的Servlet学习中都是用到

public void doGet(HttpServletRequest request, HttpServletResponse response){

        try {
            PrintWriter pw= response.getWriter();
            pw.println("<h1>Hello Servlet</h1>");

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

通过response.getWriter(); 获取一个PrintWriter 对象
可以使用println(),append(),write(),format()等等方法设置返回给浏览器的html内容。

1.设置响应格式

response.setContentType("text/html");

“text/html” 是即格式 ,在request获取头信息 中对应的request.getHeader(“accept”).
“text/html” 是存在的,表示浏览器可以识别这种格式,如果换一个其他的格式, 比如 “text/lol” ,浏览器不能识别,那么打开此servlet就会弹出一个下载的对话框

这样的手段也就常常用于实现下载功能

2.设置响应编码

设置响应编码有两种方式
utf-8

1. response.setContentType("text/html; charset=UTF-8");
2. response.setCharacterEncoding("UTF-8");

这两种方式都需要在response.getWriter调用之前执行才能生效。

他们的区别在于

1. response.setContentType("text/html; charset=UTF-8");

不仅发送到浏览器的内容会使用UTF-8编码,而且还通知浏览器使用UTF-8编码方式进行显示。所以总能正常显示中文

2. response.setCharacterEncoding("UTF-8"); 

仅仅是发送的浏览器的内容是UTF-8编码的,至于浏览器是用哪种编码方式显示不管。 所以当浏览器的显示编码方式不是UTF-8的时候,就会看到乱码,需要手动再进行一次设置。

3. 301或者302客户端跳转

客户端有两种跳转
302 表示临时跳转
301 表示永久性跳转

302就是前面在客户端跳转章节用到过的

response.sendRedirect("fail.html");

301要使用另外的手段:

response.setStatus(301);
response.setHeader("Location", "fail.html");

用户感受不出这两种跳转的区别,但是可以借助火狐的调试工具看到响应的头信息是:
301 Moved Permanently。
301和302的区别主要在搜索引擎对页面排名的时候有影响,这是属于SEO范畴的概念,在此就不展开了。

4.设置不使用缓存

使用缓存可以加快页面的加载降低服务端的负担。但是也可能看到过时的信息,可以通过如下手段通知浏览器不要使用缓存

public void doGet(HttpServletRequest request, HttpServletResponse response) {

        try {
            response.setDateHeader("Expires", 0);
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("pragma", "no-cache");
            response.getWriter().println("<h1>Hello Servlet!</h1>");
            response.getWriter().println(new Date().toLocaleString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

九、上传文件

1.首先准备上传页面 upload.html

上传页面有两点需要注意
1.。 form 的method必须是post的,get不能上传文件。 还需要加上enctype=”multipart/form-data” 表示提交的数据是二进制文件

<form action="uploadPhoto" method="post" enctype="multipart/form-data">

2.。 需要提供type=”file” 的字段进行上传

<!DOCTYPE html>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<form action="uploadPhoto" method="post" enctype="multipart/form-data">
  英雄名称:<input type="text" name="heroName" /> <br>
  上传头像 : <input type="file" name="filepath" /> <br>
  <input type="submit" value="上传">
</form>

2.接着准备 UploadPhotoServlet

在UploadPhotoServlet进行上传的功能开发。
1. 需要用到两个第三方的jar包,commons-io-1.4.jar和commons-fileupload-1.2.2.jar。 需要下载可以评论联系,放在WEB-INF/lib下

  1. 前部分代码是固定写法,用来做一些准备工作。 直到遍历出Item,一个Item就是对应一个浏览器提交的数据,通过item.getInputStream可以打开浏览器上传的文件的输入流

  2. 客户提交的文件名有可能是一样的,所以在服务端保存文件的时候,不能使用客户提交的文件名。这里使用的是一种粗糙的解决文件名重复的办法,即使用时间戳

  3. 读取输入流中的数据,保存在服务端的目录下 e:/project/j2ee/web/uploaded,这个目录是通过getRealPath获取到的。 如果项目部署在其他地方,那么会自动做相应的变化。
    注: 为什么要放这里? 因为后续网页上显示的时候是通过http://127.0.0.1/uploaded/xxx.jpg 路径来获取图片。

  4. 根据临时生成的文件名,创建一个html img元素,然后通过response返回浏览器

public class UploadPhotoServlet extends HttpServlet {

    public void doPost(HttpServletRequest request, HttpServletResponse response) {

        String filename = null;
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            // 设置上传文件的大小限制为1M
            factory.setSizeThreshold(1024 * 1024);

            List items = null;
            try {
                items = upload.parseRequest(request);
            } catch (FileUploadException e) {
                e.printStackTrace();
            }

            Iterator iter = items.iterator();
            while (iter.hasNext()) {
                FileItem item = (FileItem) iter.next();
                if (!item.isFormField()) {

                    // 根据时间戳创建头像文件
                    filename = System.currentTimeMillis() + ".jpg";

                    //通过getRealPath获取上传文件夹,如果项目在e:/project/j2ee/web,那么就会自动获取到 e:/project/j2ee/web/uploaded
                    String photoFolder =request.getServletContext().getRealPath("uploaded");

                    File f = new File(photoFolder, filename);
                    f.getParentFile().mkdirs();

                    // 通过item.getInputStream()获取浏览器上传的文件的输入流
                    InputStream is = item.getInputStream();

                    // 复制文件
                    FileOutputStream fos = new FileOutputStream(f);
                    byte b[] = new byte[1024 * 1024];
                    int length = 0;
                    while (-1 != (length = is.read(b))) {
                        fos.write(b, 0, length);
                    }
                    fos.close();

                } else {
                    System.out.println(item.getFieldName());
                    String value = item.getString();
                    value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
                    System.out.println(value);
                }
            }

            String html = "<img width='200' height='150' src='uploaded/%s' />";
            response.setContentType("text/html");
            PrintWriter pw= response.getWriter();

            pw.format(html, filename);

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

配置web.xml

<servlet>
    <servlet-name>UploadPhotoServlet</servlet-name>
    <servlet-class>UploadPhotoServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UploadPhotoServlet</servlet-name>
    <url-pattern>/uploadPhoto</url-pattern>
</servlet-mapping> 

再强调一遍:把右侧的两个jar包commons-io-1.4.jar,commons-fileupload-1.2.2.jar 复制到WEB-INF/lib 目录下

把jar包导入到项目中,导包办法:右键 project->properties->java build path->libaries->add external jars

重启tomcat, 打开页面进行上传

http://127.0.0.1/upload.html

选中某个图片点击上传,就可以看到上传的图片了

如何处理其他非File字段

因为浏览器指定了以二进制的形式提交数据,那么就不能通过常规的手段获取非File字段:

request.getParameter("heroName")

在遍历Item时(Item即对应浏览器提交的字段),可以通过

item.isFormField

来判断是否是常规字段还是提交的文件。 当item.isFormField返回true的时候,就表示是常规字段。

然后通过item.getFieldName()和item.getString()就知道分别是哪个字段,以及字段的值了。

猜你喜欢

转载自blog.csdn.net/jae_peng/article/details/79992085