JavaWeb-Servlet

序言

通俗一点,只要是实现了servlet接口的java程序,均称Servlet。Servlet是由sun公司命名的,Servlet = Server + Applet(Applet表示小应用程序),Servlet是在服务器端运行的小程序用来处理服务器请求的

其主要功能在于:交互式地浏览和修改数据,生成动态Web内容。Java Web 应用程序中所有的请求-响应都是由 Servlet来完成的

进一步讲,我们知道,一般的网页程序,是由我们通过浏览器访问来实现的,在这个过程中,我们的浏览器发送访问请求,服务器接收请求,并对浏览器的请求作出相应的处理。这就是我们熟悉的B/S模型(浏览器-服务器模型)。而servlet就是对请求作出处理的组件,运行于支持Java的应用服务器中。
这里写图片描述

当客户机发送请求至服务器时,服务器可以将请求信息发送给Servlet,并让Servlet建立起服务器返回给客户机的响应,当启动Web服务器或客户机第一次请求服务时,可以自动装入Servlet,之后,Servlet继续运行直到其他客户机发出请求。

在servlet刚刚出现的那个年代,servlet的作用十分复杂,既承担着处理数据的作用,又承担着展示页面的作用,美工人员想要参与开发,基本上是不太现实的,毕竟美工不可能再去花时间将页面做好。随着时间的推移,出现了MVC思想,也就是模型-界面-控制器思想,极大的简便了开发,也明确了servlet的作用。
这里写图片描述
  根据上面这张图,我们就能知道,servlet在其中承担的作用是controller,控制器,起到对数据进行操作的作用。顺便补充说明一下,最经典的MVC模型就是JSP+JavaBean+Servlet开发的模式。


Java Web 开发的发展历程

Web 开发技术主要是从静态网页技术动态网页技术的变迁。由于本文主要介绍 Java Web 开发技术,所以对其他语言的 Web 技术不做介绍。

Java Web 开发的大致发展历程如下:静态HTML –> CGI –> Servlet –> JSP。至于Spring、Struts等著名的框架则是在这些技术基础上的最佳编程实践。

(1) 静态网页技术

早期的Web 开发只能提供静态的 HTML 页面。这样的模式显然存在很多弊端:不利于系统扩展,不利于和用户之间进行交互。于是,有了动态页面技术(如大家熟悉的JSP、ASP、PHP等等)。

(2) 动态网页技术
CGI

CGI (Common Gateway Interface,公共网关接口)是最重要的 Web 技术之一。它是最早的动态页面技术。CGI 是外部应用程序与 Web 服务器之间的接口标准。绝大多数的CGI程序被用来解释处理来自表单的输入信息: CGI 允许服务器调用外部程序来处理输入信息,并将相应的输出反馈给浏览器。CGI程序使网页具有交互功能。

注:最流行的CGI 语言是 Perl 和Shell 脚本,但是也可以使用 C、C++ 以及Java 等语言来编写。

CGI 解决了静态页面不利于交互的问题,但其自身也存在缺陷:

A、为每个请求启动一个操作 CGI 程序的系统进程。如果请求频繁,会带来很大的系统开销。如果用Java编写 CGI,除了需要为每个请求启动一个系统进程外,还要在进程中启动一个 JVM ,这将十分低效。

B、重复编写处理网络协议的代码,非常耗时。

(3) Servlet

知道了 Java 编写 CGI 的不足。我们不禁要问,如果有办法可以只运行一个系统进程和一个 JVM ,岂不是能大大减少开销吗?Servlet 正是为此应运而生。

与传统的 CGI 技术相比,Servlet的优势在于:

A、传统的 CGI 中,每个请求都要启动一个新的进程;而在 Servlet 中,每个请求由一个轻量级的 Java 线程处理。

B、传统的 CGI 中,如果有 N 个并发的对同一个 CGI程序的请求,则该CGI程序的代码在内存中重复装载了 N 次;而对于 Servlet,处理请求的是 N 个线程,只需要一份 Servlet 类代码。

Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

(4) JSP

JSP(Java Server Page)是一种实现静态HTML 和动态 HTML 混合编码的技术,它是Servlet API 的一个扩展。


1、编写Servlet

(1) tomcat和servlet的关系

我们之前学过的对象都是自己手动创建,最后由JVM来销毁的,而servlet的整个生命周期,都是由tomacat,也就是服务器控制的

Tomcat 是Web应用服务器,是一个Servlet/JSP容器。Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。而Servlet是一种运行在支持Java语言的服务器上的组件.。Servlet最常见的用途是扩展Java Web服务器功能,提供非常安全的,可移植的,易于使用的CGI替代品。

从http协议中的请求和响应可以得知,浏览器发出的请求是一个请求文本,而浏览器接收到的也应该是一个响应文本。但是在上面这个图中,并不知道是如何转变的,只知道浏览器发送过来的请求也就是request,我们响应回去的就用response。忽略了其中的细节,现在就来探究一下。
这里写图片描述
①:Tomcat将http请求文本接收并解析,然后封装成HttpServletRequest类型的request对象,所有的HTTP头数据读可以通过request对象调用对应的方法查询到。

②:Tomcat同时会要响应的信息封装为HttpServletResponse类型的response对象,通过设置response属性就可以控制要输出到浏览器的内容,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器。

Java Servlet API 是Servlet容器(tomcat)和servlet之间的接口,它定义了serlvet的各种方法,还定义了Servlet容器传送给Servlet的对象类,其中最重要的就是ServletRequest和ServletResponse。所以说我们在编写servlet时,需要实现Servlet接口,按照其规范进行操作。


(2) 手动编写servlet

在前面,我们已经知道了servlet是什么,为什么需要servlet?(为了实现动态网页,而不是显示静态网页,具体情况可以百度查查),也了解了tomcat和servlet的关系等问题。现在来手动编写一个Servlet。

Servlet的开发流程如下:

1、编写一个java类,继承HttpServlet类
2、重写HttpServlet类的doGet方法和doPost方法
3、配置web.xml文件,或者使用注解对servlet进行配置

1、创建一个MyServlet继承HttpServlet,重写doGet和doPost方法,也就是看请求的方式是get还是post,然后用不同的处理方式来处理请求:
这里写图片描述
2、在web.xml中配置MyServlet,为什么需要配置?让浏览器发出的请求知道到达哪个servlet,也就是让tomcat将封装好的request找到对应的servlet让其使用。

配置四个东西:
这里写图片描述
配置之后,浏览器是如何通过我们配置的信息来找到对应的servlet的:
这里写图片描述
按照如下步骤:

1、首先浏览器通过http://localhost:8080/test01/MyServlet来找到web.xml中的url-pattern,这是第一步;
2、匹配到了url-pattern后,就会找到第二步servlet的名字MyServlet;
3、知道了名字,就可以通过servlet-name找到第三步;
4、到了第三步,也就能够知道servlet的位置了,然后到其中找到对应的处理方式进行处理。

3、实验,验证上面配置成功:
这里写图片描述


(3) 详解Servlet配置

配置servlet一共有两种方式,一种是使用web.xml文件配置(上面的例子即是),另外一种就是使用注解配置,下面我们来详解介绍这两种配置方式。

1、使用web.xml文件配置

<webapp>
<!-- 配置一个servlet -->
  <!-- servlet的配置 -->
  <servlet>
    <!-- servlet的内部名称,自定义。尽量有意义 -->
    <servlet-name>MyServlet</servlet-name>
    <!-- servlet的类全名: 包名+简单类名 -->
    <servlet-class>cn.roobtyan.servlet.FirstServlet</servlet-class>
  </servlet>

  <!-- servlet的映射配置 -->
  <servlet-mapping>
    <!-- servlet的内部名称,一定要和上面的内部名称保持一致!! -->
    <servlet-name>MyServlet</servlet-name>
    <!-- servlet的映射路径(访问servlet的名称) -->
    <url-pattern>/first</url-pattern>
  </servlet-mapping>
</webapp>

当你访问/first的时候,服务器自然就会把请求交给MyServlet进行处理了。

2、使用@注解配置

新版本的servlet支持使用注解进行配置,这样极大的简便了开发。注解配置如下:

@WebServlet(name = "LoginServlet",urlPatterns = {"/login"})
public class LoginServlet extends HttpServlet {
        ………………………………
}

然后,你在访问/login的时候,服务器同样就会将处理交由LoginServlet进行处理了。

这样是不是非常爽?(-)实际上,注解的作用和web.xml的作用是相同的,一般都是推荐使用注解的方式进行开发,这样十分简便,可读性也变的更加强大。

3、Servlet映射路径的配置问题

(1)精确匹配
  精确匹配就是我们上面用的那种方式,使用固定的url来访问这个servlet,这种没什么需要说明的。
(2)模糊匹配
  模糊匹配就是比较有意思的了,通过模糊匹配,我们可以让好多路径映射到同一个servlet,模糊匹配一般有如下格式:

1、/*                          任意路径都映射到这个servlet
2、/roobtyan/*                 /roobtyan下的任意路径映射到该servlet
3、*.(*.do  *.action *.html)   是这样的:/任意路径.do/action/html

这里面有两点是需要注意的,一是url要么以/开头,要么以*开头,其他的都是非法的。

(4) Eclipse中创建Servlet

这个就相对简单了,web.xml不用我们手动配置,工具直接帮我们自动配置了。

1、右击项目,在new选项中有直接新建servlet的选项
2、配置MyServlet类中的信息
这里写图片描述
3、配置web.xml中的servlet信息
这里写图片描述
4、查看MyServle01类中的代码和web.xml,其中的配置跟手动的配置是一样的,只是用图形化界面,让我们更方便的创建servlet而产生的。


2、详解Servlet的原理

首先抛出几个问题:

1、servlet的生命周期是什么?

2、为什么创建的servlet是继承自httpServlet,而不是直接实现Servlet接口?

3、servlet的生命周期中,可以看出,执行的是service方法,为什么我们就只需要写doGet和doPost方法呢?

上面这几个问题,我们都应该知道,而不应该就单纯的知道如何配置和使用servlet?上面的问题,下面逐一来解答。

(1) servlet的生命周期

一般来讲,servlet只会初始化一次,也就是整个过程中只存在一个servlet对象,即便是有多次访问,依然只有一个对象,这个对象是可以复用的。我想你一定会好奇这个servlet究竟是在什么时候创建的,所以就来讲一下servlet的生命周期,所谓的生命周期我们在java基础知识中一定也了解过,就是servlet类究竟在什么时候创建,调用了何种方法,最后在什么时候被销毁。我们之前学过的对象都是自己手动创建,最后由JVM来销毁的,而servlet的整个生命周期,都是由tomacat,也就是服务器控制的

Servlet共有三个关键的方法,分别是init()service()destroy()

1、init()方法只会调用一次,只是在创建servlet实例的时候才会创建;
2、service()方法,是进行数据处理的,只要接受了一次请求,就会被调用一次;
3、destroy()方法,销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象,同样也是调用一次。

A、服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf);
  
  B、该servlet对象去处理所有客户端请求,在service(ServletRequest req,ServletResponse res)方法中执行;
  
  C、最后服务器关闭时,才会销毁这个servlet对象,执行destroy()方法。
这里写图片描述
附:Servlet的自动加载

前面我们说了,servlet只有在第一次被访问的时候才会加载,这肯定会造成第一个访问的人访问时间较长,因为他需要等待servlet完成加载.那么,有没有什么方法能够使得servlet自动加载呢,就是在启动服务器的时候就将servlet加载起来呢?答案是有的,同样可以在web.xml中进行配置。

<servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>cn.roobtyan.LoginServlet</servlet-class>
    <!-- 让servlet对象自动加载 -->
    <load-on-startup>1</load-on-startup>  
</servlet>

从上述代码可以看出,就是使用的<login-on-startup> </login-on-startup>配置的。
注意: 其中的整数值越大,创建优先级越低!

传统的 CGI 中,每个请求都要启动一个新的进程还,要在进程中启动一个 JVM ;而在 Servlet中,每个请求由同一个系统进程(因此只启动一个JVM)的一个不同轻量级的 Java 线程处理(即单实例多线程):
这里写图片描述

(2) 深入servlet源码

1、HttpServlet的继承结构

httpServlet继承GenericServlet。懂的人立马就应该知道,GenericServlet(通用Servlet)的作用是什么?大概的就是将实现Servlet接口的方法,简化编写servlet的步骤。具体下面详解:
这里写图片描述
GenericServlet的继承结构,实现了Servlet接口和ServletConfig接口,
这里写图片描述
2、Servlet接口内容
这里写图片描述
从这里可以看到,Servlet生命周期的三个关键方法,init、service、destroy。还有另外两个方法,一个getServletConfig()方法来获取ServletConfig对象,ServletConfig对象可以获取到Servlet的一些信息,ServletName、ServletContext、InitParameter、InitParameterNames、通过查看ServletConfig这个接口就可以知道。

3、ServletConfig接口内容
这里写图片描述
其中ServletContext对象是servlet上下文对象,功能有很多,获得了ServletContext对象,就能获取大部分我们需要的信息,比如获取servlet的路径,等方法。

到此,就知道了Servlet接口中的内容和作用,总结起来就是,三个生命周期运行的方法,获取ServletConfig,而通过ServletConfig又可以获取到ServletContext。而GenericServlet实现了Servlet接口后,也就说明我们可以直接继承GenericServlet,就可以使用上面我们所介绍Servlet接口中的那几个方法了,能拿到ServletConfig,也可以拿到ServletContext,不过那样太麻烦,不能直接获取ServletContext,所以GenericServlet除了实现Servlet接口外,还实现了ServletConfig接口,那样,就可以直接获取ServletContext了

以上就解释了咱们提出的第二个问题:

为什么创建的servlet是继承自httpServlet,而不是直接实现Servlet接口?

4、GenericServlet类的内容详解
这里写图片描述
看上图,用红色框框起来的就是实现Servlet和ServletConfig接口所实现的方法,有9个,这很正常,但是我们可以发现,init方法有两个,一个是带有参数ServletConfig的,一个有无参的方法,为什么这样设计?这里需要知道其中做了什么事情,来看看这两个方法分别做了什么事?

init(ServletConfig config)
这里写图片描述
init()
这里写图片描述
一个成员变量config
这里写图片描述
getServletConfig()
这里写图片描述
通过这几个方法一起来讲解,首先看init(ServletConfig config)方法,因为只有init(ServletConfig config)中带有ServletConfig对象,为了方便能够在其他地方也能直接使用ServletConfig对象,而不仅仅局限在init(ServletConfig config)方法中,所以创建一个私有的成员变量config,在init(ServletConfig config)方法中就将其赋值给config,然后通过getServletConfig()方法就能够获取ServletConfig对象了,这个可以理解,但是在init(ServletConfig config)中,158行,还调用了一个init()方法,并且这个init()方法是空的,什么读没有,这是为什么呢?

这个原因是为了防止一件事情,当我们需要在init方法中做一点别的事情,我们想到的方法就是继承GenericServlet并且重写了init(ServletConfig config)方法,这样依赖,就破坏了原本在GenericServlet类中init(ServletConfig config)写的代码了,也就是在GenericServlet类中的成员变量config会一直是null,无法得到赋值,因为被重写了,就不会在执行GenericServlet中init(ServletConfig config)方法中的代码。要想赋值,就必须在重写的init(ServletConfig config)方法中调用父类的init(ServletConfig config)方法,也就是super.init(ServletConfig config),这样一来,就很不方便,怕有时候会忘了写这句代码,所以在GenericServlet类中增加一个init()方法,以后需要在init方法中需要初始化别的数据,只需要重写init()这个方法,而不需要去覆盖init(ServletConfig config)这个方法,这样设计,就好很多,不用在管init(ServletConfig config)这个其中的内容了。也不用出现其他的问题。

service(ServletRequest req, ServletResponse res)
这里写图片描述
一个抽象方法,说明在GenericServlet类中并没有实现该内容,那么我们想到的是,在它上面肯定还有一层,也就是还有一个子类继承它,实现该方法,要是让我们自己写的Servlet继承GenericServlet,需要自己写service方法,那岂不是累死,并且我们可以看到,service方法中的参数还是ServletRequest,ServletResponse。并没有跟http相关对象挂钩,所以我们接着往下面看。

5、HttpServlet类详解

继承了GenericServlet类,通过我们上面的推测,这个类主要的功能肯定是实现service方法的各种细节和设计。并且通过类名可以知道,该类就跟http挂钩了。
这里写图片描述
关注service(HttpServletRequest req, HttpServletResponse resp)方法和service(ServletRequest req, ServletResponse res)方法。

**service(ServletRequest req, ServletResponse res)**方法:
这里写图片描述
  该方法中就做一件事情,就是将ServletRequest和ServletResponse这两个对象强转为HttpServletRequest和HttpServletResponse对象。

转换为httpServletRequest和HttpServletResponse对象之后,再调用service(HttpServletRequest req, HttpServletResponse resp)方法,这个方法就是判断浏览器过来的请求方式是哪种,每种的处理方式不一样,我们常用的就是get,post,并且,我们处理的方式可能有很多的内容,所以,在该方法内会将get,post等其他5种请求方式提取出来,变成单个的方法,然后我们需要编写servlet时,就可以直接重写doGet或者doPost方法就行了,而不是重写service方法,更加有针对性。所以这里就回到了我们上面编写servlet时的情况,继承httpServlet,而只要重写两个方法,一个doGet,一个doPost,其实就是service方法会调用这两个方法中的一个(看请求方式)

所以也就解答了我们一开始提的问题3:

servlet的生命周期中,可以看出,执行的是service方法,为什么我们就只需要写doGet和doPost方法呢?

(3) Servlet的多线程问题

前面我们讲了,一个servlet在服务器中只会存在一个实例,不论是有多少访问,都掉用的同一个实例,也就是单实例多线程的。这就存在着一定的线程安全问题,比如说,我在servlet中定义了一个局部变量,那么这个变量的值很有可能不是我期待的值,所以,在servlet中要尽量避免使用局部变量。使用Servlet最好保证Servlet是无状态的,也就是没有可以修改的成员变量。


3、Servlet对象

在servlet中共有四个重要的对象:

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

前两个对象,和HTTP协议一块单独一篇博文介绍,我觉得这样看起来更能接受一些:https://blog.csdn.net/weixin_39190897/article/details/82355197。

那么我们现在就介绍后面两个:

(1) ServletConfig对象

1、创建时间:在创建完servlet对象的时候,接着创建servletConfig对象;
2、获取途径:getServletConfig();
3、功能:能得到以下四个内容:
这里写图片描述
详细作用:

getServletName();  //获取servlet的名称,也就是我们在web.xml中配置的servlet-name
getServletContext(); //获取ServletContext对象,该对象的作用看下面讲解
getInitParameter(String); //获取在servlet中初始化参数的值。这里注意与全局初始化参数的区分。这个获取的只是在该servlet下的初始化参数
getInitParameterNames(); //获取在Servlet中所有初始化参数的名字,也就是key值,可以通过key值,来找到各个初始化参数的value值。注意返回的是枚举类型

初始化参数:
这里写图片描述
获取参数:
这里写图片描述
输出结果:
这里写图片描述
  注意:在上面我们所分析的源码过程中,我们就知道,其实可以不用先获得ServletConfig,然后再获取其各种参数,可以直接使用其方法,比如上面我们用的ServletConfig().getServletName();可以直接写成getServletName();而不用在先获取ServletConfig()了,原因就是在GenericServlet中,已经帮我们获取了这些数据,我们只需要直接拿就行。

(2) ServletContext对象

获取途径:getServletContext()getServletConfig().getServletContext()

这两种获取方式的区别就跟上面的解释一样,第一种是直接拿,在GenericServlet中已经帮我们用getServletConfig().getServletContext();拿到了ServletContext。我们只需要直接获取就行了,第二种就相当于我们自己在获取一遍,两种读是一样的。

功能

tomcat为每个web项目都创建一个ServletContext实例,tomcat在启动时创建,服务器关闭时销毁,在一个web项目中共享数据,管理web项目资源,为整个web配置公共信息等,通俗点讲,就是一个web项目,就存在一个ServletContext实例,每个Servlet读可以访问到它。

1、web项目中共享数据

A、setAttribute(String name, Object obj) 在web项目范围内存放内容,以便让在web项目中所有的servlet读能访问到
B、getAttribute(String name) 通过指定名称获得内容
C、removeAttribute(String name) 通过指定名称移除内容   

这里写图片描述
2、整个web项目初始化参数

//这个就是全局初始化参数,每个Servlet中都能获取到该初始化值
A、getInitPatameter(String name)  //通过指定名称获取初始化值
B、getInitParameterNames()  //获得枚举类型

web.xml 文件配置 整个web项目的初始化:
这里写图片描述


4、Servlet简单应用

好了,讲了这么多,你一定是跃跃欲试了,我们就用一个登录控制的例子来简单的看一下servlet开发的步骤。

(1) 应用实例

应用实例1:

编写一个现实登陆的Servlet,并部署访问。

1、我们先写一个简单的HTML,可以在WebRoot新建,也可以复制一个写好的HTML,我们用post提交方式,当然也可以用get提交,大家可以自己尝试一下。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
		<title>登录</title>
	</head>
	<body>
		
		<form action="http://localhost:8080/loginServlet/LoginServlet" method="post">
			用户:<input type="text" name="username" /><br/>
			密码:<input type="password" name="password" /><br/>
			<input type="submit" value="登录" />
			
		</form>
	</body>
</html>

2、完成Servlet,在Web项目的src中右键新建一个类LoginServlet,输入下面代码即可。

//引入所需要的包
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class LoginServlet extends HttpServlet {
	//重写doGet方法
	public void doGet(HttpServletRequest request,
            HttpServletResponse response)
     throws ServletException,
            IOException {
		String username = request.getParameter("username");   
		String password = request.getParameter("password");      
		
		//服务器端打印信息
		//System.out.println("username=" + username);
		//System.out.println("password=" + password);
		//设置编码格式
		response.setContentType("text/html;charset=GB18030");
		
		//返回html页面
		response.getWriter().println("<html>");
		response.getWriter().println("<head>");	
		response.getWriter().println("<title>登录信息</title>");	
		response.getWriter().println("</head>");	
		response.getWriter().println("<body>");	
		response.getWriter().println("欢迎【" + username + "】用户登录成功!!!");	
		response.getWriter().println("</body>");	
		response.getWriter().println("</html>");
		}                 	
	//重写doPost方法
	public void doPost(HttpServletRequest request,
            HttpServletResponse response)throws ServletException,IOException {
		doGet(request, response);               	
	}     
}

3、在WebRoot下的WEB-INF的web.xml,部署我们的Servlet,启动服务器。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="http://java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	
    <servlet>
	   	<servlet-name>MyServlet</servlet-name>
	   	<servlet-class>LoginServlet</servlet-class>
    </servlet>
 
    <servlet-mapping>
	   	<servlet-name>MyServlet</servlet-name>
	        <url-pattern>/LoginServlet</url-pattern>
    </servlet-mapping>
</web-app>

4、一定要启动服务器,之后在浏览器中输入你的URL,此时我们就可以调用Servlet了,我们看一下运行结果。
这里写图片描述
结果:
这里写图片描述


应用实例2:

咱们使用IDE新建一个web项目,首先创建一个前端登录表单login.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>roobtyan登录控制系统</title>
</head>
<body>
    <h1 align="center" style="color: red;">欢迎您登录系统后台</h1><hr/>
    <%--the form start--%>
    <div align="center">
        <form method="post" action="/login">
            Username:<input type="text" name="username"/><br/><br/>
            Password:<input type="password" name="password"/><br/><br/>
            <input type="submit" value="登录"/>
        </form>
    </div>
</body>
</html>

再创建一个登录成功页面,同样使用jsp页面welcome.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>欢迎页面</title>
</head>
<body>
    <h1 align="center" style="color: red">Welcome:</h1>
    <%
        out.println(session.getAttribute("user"));
    %>
    <hr/>
    <span style="align:center; color:yellow">
        Time:<%
            out.println(new Date());
        %>
    </span>
</body>
</html> 

然后创建LoginServlet.java:

public class LoginServlet extends HttpServlet {
    public void service(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        //设置字符编码
        request.setCharacterEncoding("utf8");
        //从request对象中获取username,password
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //判断是否为管理员
        if("administrator".equals(username)&&"123456".equals(password)){
            //登录成功,设置session
            HttpSession session = request.getSession(true);
            session.setAttribute("user", "管理员,欢迎你!");
        }else {
            session.setAttribute("user","登录信息错误,请检查用户名或密码");
        }
        //将页面转发到欢迎页面
        requestDispatcher = request.getRequestDispatcher("/welcome.jsp");
        requestDispatcher.forward(request,response);        
    }
}

最后,配置servlet:

<servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>com.roobtyan.cn.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/login</url-pattern>
</servlet-mapping>

这里对于servlet的配置,我们采取web.xml的方式,主要是因为这种方法相对麻烦,为了让你有着更好的理解,就这样做了。

(2) 转发 VS 重定向

此处只做简要描述,具体理解得结合Http协议内容,详情见另一篇博客:https://blog.csdn.net/weixin_39190897/article/details/82355197#t5。
1、转发

刚才我们用到的:

RequestDispatcher requestDispatcher = request.getRequestDispatcher("/welcome.jsp");
requestDispatcher.forward(request,response);

这个就是转发,按照这样用就可以了。

2、重定向

与转发功能相似的是重定向,重定向的使用是这样的:

response.sendRedirect("/welcome.jsp");

这样也会访问到welcome.jsp这个页面。

3、转发和重定向的区别

虽然二者最终实现的功能是相同的,但是还是有很大不同的。不同之处如下:

1、地址栏变化:转发不会改变地址栏中的URL,而重定向则会改变;
2、跳转范围:转发只能访问到当前web应用中的内容,而重定向则可以访问到任意web应用中的内容;
3、request对象作用范围:转发后,在转发后的页面中仍然可以使用原来的request对象,而重定向,
   原来的request对象则失去作用。

所以,如果想要在多个页面使用相同的request对象,那么只能使用转发,而不能使用重定向。

(3) 图片验证码

其实实现代码的逻辑非常简单,真的超级超级简单。

1、在登录页面上login.jsp将验证码图片使用标签<img src="xxx">将绘制验证码图片的url给它。

2、在服务器端就两个servlet,一个就是用来绘制验证码图片的VerifyCodeServlet,另一个就是登录时验证验证码是否点写正确或是否重复提交的LoginServlet。

3、在VerifyCodeServlet中,将验证码的四个字母存入session中,然后在LoginServlet中,将请求中提交过来的验证码与session中的进行对比,
   如果正确,则验证成功,并且将session中的验证码删除,为什么要删除?保证session中的数据只能被用一次,防止重复提交数据,如果不正确,
   就使用request,将错误信息保存,然后请求转发到登录页面显示错误信息,如果发现session中的数据为null,说明重复提交了数据,也将错误信息
   用同样的方法返回到登录页面。

难点就在于:VerifyCodeServlet的代码实现.对绘图的代码不是很熟悉。

login.jsp

<body>
    <%
        String msg = (String)request.getAttribute("msg");
        if(msg != null){
            out.print(msg);
        }
    %>

    <form action="/test01/LoginServlet" method="post">
        用户名:<input type="text" name="username" /> <br/>
        验证码:<input type="text" name="verifyCode" size="5" /> <img src="/test01/VerifyCodeServlet" /> <br/>
        <input type="submit" value="提交"/>
    </form> 
  </body>

VerifyCodeServlet.java

public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
         //声明验证码
        int width = 60;
        int height = 30;
        //随机字符字典,其中0,o,1,I 等难辨别的字符最好不要
        String data = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789abcdefghijklmnpqrstuvwxyz";   
        Random random = new Random();//随机类
        //1 创建图片数据缓存区域(核心类)
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);//创建一个彩色的图片
        //2 获得画板(图片,ps图层),绘画对象。
        Graphics g = image.getGraphics();
        //3 选择颜色,画矩形3,4步是画一个有内外边框的效果
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, width, height);
        //4白色矩形
        g.setColor(Color.WHITE);
        g.fillRect(1, 1, width-2, height-2);

        /**1 提供缓存区域,为了存放4个随机字符,以便存入session */
        StringBuilder builder = new StringBuilder();

        //5 随机生成4个字符
                    //设置字体颜色
        g.setFont(new Font("宋体", Font.BOLD&Font.ITALIC, 20));
        for(int i = 0 ; i < 4 ;i ++){
            //随机颜色
            g.setColor(new Color(random.nextInt(255),random.nextInt(255), random.nextInt(255)));

            //随机字符
            int index = random.nextInt(data.length());
            String str = data.substring(index, index + 1);

            /**2 缓存*/
            builder.append(str);

            //写入
            g.drawString(str, (width / 6) * (i + 1) , 20);                     
        }
        //给图中绘制噪音点,让图片不那么好辨别
        for(int j=0,n=random.nextInt(100);j<n;j++){
            g.setColor(Color.RED);
            g.fillRect(random.nextInt(width),random.nextInt(height),1,1);//随机噪音点
        }


        /**3 获得随机数据,并保存session*/
        String tempStr = builder.toString();
        request.getSession().setAttribute("sessionCacheData",tempStr);


        //.. 生成图片发送到浏览器 --相当于下载
        ImageIO.write(image, "jpg", response.getOutputStream());
    }

LoginServlet.java

public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //1 获得用户输入的验证码
        String verifyCode = request.getParameter("verifyCode");
        //2 获得服务器session 存放数据 ,如果没有返回null
        String sessionCacheData = (String) request.getSession().getAttribute("sessionCacheData");
        // *将服务器缓存session数据移除
        request.getSession().removeAttribute("sessionCacheData");
        // ** 判断服务器是否存在
        if(sessionCacheData == null){
            request.setAttribute("msg", "请不要重复提交");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
            return;
        }
        //3 比较
        if(! sessionCacheData.equalsIgnoreCase(verifyCode)){
            //用户输入错误
            // * 存放request作用域
            request.setAttribute("msg", "验证码输入错误");
            // * 请求转发
            request.getRequestDispatcher("/login.jsp").forward(request, response);

            return;
        }

        //...... 登录操作

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }
}

效果图:
这里写图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tr0e

分享不易,望多鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值