JavaWeb相关

1、JavaWeb概念

Java Web,是用Java技术来解决相关web互联网领域的技术总和。web包括:web服务器和web客户端两部分。Java在客户端的应用有java applet,不过现在使用的很少。Java在服务器端的应用非常的丰富,比如Servlet,JSP和第三方框架等等。Java技术对Web领域的发展注入了强大的动力。

Java的Web框架虽然各不相同,但基本也都是遵循特定的路数的。使用Servlet或者Filter拦截请求,使用MVC的思想设计架构,使用约定,XML或 Annotation实现配置,运用Java面向对象的特点,面向抽象实现请求和响应的流程,支持Jsp,Freemarker,Velocity等视图。

2、Java EE技术

Java EE是J2EE的一个新的名称,之所以改名,目的还是让大家清楚J2EE只是Java企业应用。在2004年底中国软件技术大会Ioc微容器(也就是Jdon框架的实现原理)演讲中指出:我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。Java EE的核心是EJB3.0,其提供了更兼便捷的企业级的应用框架。J2EE容易让初学者误解是独立于一套Java的技术方案,因此改名为JavaEE。Java EE只是对J2EE的改名而已,实质上它和J2EE是一个东西。

Java EE的主要技术如下:

  • JDBC:Java Database Connectivity,Java处理数据库技术,属于JavaEE标准。
  • RMI:Remote Method Invoke,RMI协议调用远程方法。它使用了序列化方式在客户端和服务器端传递数据。RMI是一种被EJB使用的更底层的协议。
  • XML:Extensible Markup Language,XML是一种可以用来定义其它标记语言的语言,它被用来在不同的商务过程中共享数据。 XML的发展和Java是相互独立的,但是,它和Java具有的相同目标正是平台独立性。通过将Java和XML的组合,可以得到一个完美的具有平台独立性的解决方案。
  • JavaMail:JavaMail是用于存取邮件服务器的API,它提供了一套邮件服务器的抽象类。不仅支持SMTP服务器,也支持IMAP服务器。
  • JNDI:Java Name and Directory Interface,Java命名目录接口。JNDI API被用于执行名字和目录服务。它提供了一致的模型来存取和操作企业级的资源如DNS和LDAP,本地文件系统,或应用服务器中的对象。
  • EJB:Enterprise JavaBean,企业级JavaBean。EJB规范定义了EJB组件在何时如何与它们的容器进行交互作用。
  • JMS:Java Message Service,Java消息服务,基于消息互动的API。
  • JAF:JavaBeans Activation Framework,Java框架平台,专用的数据处理框架。JAF的主要作用在于让java应用程序知道如何对一个数据源进行查看、编辑和打印等操作。
  • JSP:Java Server Pages,Java服务器页面,核心。JSP页面由HTML代码和嵌入其中的Java代码所组成。服务器在页面被客户端所请求以后对这些Java代码进行处理,然后将生成的HTML页面返回给客户端的浏览器。
  • JTA:Java Transaction Architecture,Java事务处理API。JTA定义了一种标准的API,应用系统由此可以访问各种事务监控。
  • Servlet:Java Servlet,核心。用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。
  • JTS:Java Transaction Service,JTS是CORBA OTS事务监控的基本的实现。JTS规定了事务管理器的实现方式。
  • JavaIDL:Java IDL即idltojava编译器就是一个ORB,可用来在Java语言中定义、实现和访问CORBA对象。Java IDL支持的是一个瞬间的CORBA对象,即在对象服务器处理过程中有效。实际上,Java IDL的ORB是一个类库而已,并不是一个完整的平台软件,但它对Java IDL应用系统和其他CORBA应用系统之间提供了很好的底层通信支持,实现了OMG定义的ORB基本功能。

以上就是Java EE核心的13种技术。其中最核心的是JSP和Servlet。

Java EE和Java Web

  • Java EE是一种标准,也是一种技术。Java EE是在Java SE的基础上构建的,它提供 Web 服务、组件模型、管理和通信API,可以用来实现企业级的面向服务体系结构(SOA)和Web 2.0应用程序。
  • Java Web是应用Java EE技术及其他技术所开发的基于浏览器的b/s应用。Java Web开发中会用到Java EE的相关技术,但是不仅仅只用到Java EE,还有许多其他的技术和框架。

3、Web应用程序

3.1、应用程序

应用程序,指为完成某项或多项特定工作的计算机程序,它运行在用户模式,可以和用户进行交互,具有可视的用户界面。应用程序通常又被分为两部分:图形用户接口(GUI)和引擎(Engien)。应用程序有两种模式:C/S模式、B/S模式。

C/S模式:

即Client/Server (客户机/服务器) 结构,是大家熟知的软件系统体系结构,通过将任务合理分配到Client端和Server端,降低了系统的通讯开销,需要安装客户端才可进行管理操作。比如我们常用的QQ、网易云等软件都是采用C/S模式开发的。

客户端和服务器端的程序不同,用户的程序主要在客户端,服务器端主要提供数据管理、数据共享、数据及系统维护和并发控制等,客户端程序主要完成用户的具体的业务。

开发比较容易,操作简便,但应用程序的升级和客户端程序的维护较为困难。

B/S模式:

即Browser/Server (浏览器/服务器) 结构,是随着Internet技术的兴起,对C/S结构的一种变化或者改进的结构。在这种结构下,用户界面完全通过WWW浏览器实现。

客户端基本上没有专门的应用程序,应用程序基本上都在服务器端。由于客户端没有程序,应用程序的升级和维护都可以在服务器端完成,升级维护方便。由于客户端使用浏览器,使得用户界面"丰富多彩",但数据的打印输出等功能受到了限制。为了克服这个缺点,一般把利用浏览器方式实现困难的功能,单独开发成可以发布的控件,在客户端利用程序调用来完成。

3.2、Web应用程序

Web应用程序,计算机网络技术应用,这类应用程序一般借助IE等浏览器来运行。WEB应用程序一般是B/S模式。Web应用程序首先是"应用程序",和用标准的程序语言,如C、C++等编写出来的程序没有什么本质上的不同。然而Web应用程序又有自己独特的地方,就是它是基于Web的,而不是采用传统方法运行的。换句话说,它是典型的浏览器/服务器架构的产物。

一个Web应用程序是由完成特定任务的各种Web组件(web components)构成的,并通过Web将服务展示给外界。在实际应用中,Web应用程序是由多个Servlet、JSP页面、HTML文件以及图像文件等组成。所有这些组件相互协调为用户提供一组完整的服务。

web程序的特点:

  • 建立在互联网上。
  • 通过某种网络协议传输交互数据。
  • 基于Web服务器,应用程序向外公布访问的URL。
  • 用户通过浏览器访问。

优缺点:

  • 基于网络,信息共享更加容易。
  • 只专注于服务器端开发,不必关心客户端。
  • 更新、维护、升级更加方便。
  • 受网络限制。
  • 安全问题。

4、JavaWeb基础

4.1、HTTP协议

HTTP是Hyper Text Transfer Protocol的缩写,即超文本传输协议,是互联网上应用最为广泛的一种安全可靠的无状态网络协议。是TCP/IP网络协议的一种,实现一个客户端和服务器端请求和应答的标准交互协议。其核心描述为一个固定模式的URL(统一资源定位符)字符串形式。

(1)URL结构

4个部分组成。如下:

协议标识:// 主机地址名称或IP:服务器端口号/资源名称

如:http://localhost:8888/unixdu/index.jsp。

说明:协议是http,服务器主机域名或IP地址是localhost,服务器端口号是8888(省略端口号时默认是80),unixdu/index.jsp是请求的资源。

(2)HTTP协议内容

主要是请求和响应,请求由请求对象完成,响应由响应对象完成。

请求如下:

// 请求方法  
GET/POST
//请求资源
资源名称:index.jsp
//协议版本  
Http/1.1
//请求信息
......
//浏览器信息
User-Agent:Mozilla/5.0(WindowsNT 10.0;WOW64)AppleWebKit/537.36(KHTML ,like Gecko) Chrome/44.0.2403.130 Safari/537.36
//内容类型
Accept-Encoding:gzip、deflate、sdch
Accept-Language:zh-CN,zh;q=0.8,zh_TW;q=0.6

响应如下:

//协议版本 
HTTP/1.1
//状态码  
//成功/找不到资源/服务器代码错误/服务器不可用
200/404/500/503 
//响应消息
//服务器软件信息  
Server:JavaWebApplication
//内容类型 
Content-Type:text/html
//内容长度及编码
Content-Length:4553  
Content-Encoding:utf-8
//最后修改日期  
Last-Modified:Fri,04,Sep 2016 13:37:55 GMT

(3)HTTP请求方式

GET请求:

此方式主要用于非隐秘的信息检索(浏览器地址栏显示用户数据),请求的页面可以作为书签和电子邮件发送,请求信息作为查询字符串发送并显示在地址栏中,查询字符个数通常限制在240-255之间。

POST请求:

请求信息通常封装在form表单中,主要用于发送敏感数据,请求的页面不能作为书签和电子邮件发送,请求信息不会出现在地址栏中,查询字符串个数不受限制。

4.2、JSP/Servlet容器

提供JSP和Servlet发布允许的JavaWeb服务器环境,一般用Tomcat。下载、安装、然后启动服务。

访问:http://localhost:8080/
在这里插入图片描述
出现这个界面说明Tomcat是安装并启动成功的。

4.3、JavaWeb目录结构

Java Web有着特定的目录结构。如下:

/ 根目录
/WEB-INF //私有目录
        ---/web.xml//应用程序部署文件,关键
        ---/classes//类路径(存放用户自定义的class文件)
        ---/lib//库文件目录(存放jar文件)
/Other dir//公有目录
         .html//静态网页文件
         .css//样式表文件
         .jsp//动态网页
         .js//JavaScript脚本文件
         .img//图像文件

4.4、测试

桌面建一个空目录:
在这里插入图片描述
空目录下建立两个目录:
在这里插入图片描述
WEB-INF目录中如下:
在这里插入图片描述
classes目录和lib目录是空的,web.xml内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
	http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>Hello</display-name>
	<welcome-file-list>
		<welcome-file>/pages/index.html</welcome-file>
	</welcome-file-list>
</web-app>

pages目录如下:
在这里插入图片描述
只有一个index.html页面,如下:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>测试</title>
</head>
<body bgcolor="pink">
	<h2 align="center">欢迎来到本页面!</h2>
</body>
</html>

将整个Test目录放到Tomcat安装目录下的webapps目录下:
在这里插入图片描述
在这里插入图片描述
启动Tomcat服务,浏览器中访问:http://localhost:8080/Test。
在这里插入图片描述
访问成功,说明这个小应用在Tomcat服务器上发布成功了。如果访问不了,要么是目录结构不正确,要么是web.xml配置有问题。

4.5、Tomcat集成

Eclipse中集成Tomcat运行环境。
在这里插入图片描述
在这里插入图片描述
选择Tomcat的安装目录。
在这里插入图片描述
选择安装的Tomcat版本:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
创建一个动态的web项目,目录结构如下:
在这里插入图片描述
pages是自己建的目录,下面创建一个index.jsp,如下:
在这里插入图片描述
web.xml如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
加到右边来,完成。
在这里插入图片描述
添加成功可以看到server下面有这个应用,双击server查看端口:
在这里插入图片描述
确保这3个配了端口并且没有和其他端口重复,否则会启动失败,启动server,控制台:
在这里插入图片描述
说明启动成功。浏览器访问:http://localhost:8080/test。
在这里插入图片描述
访问成功,说明eclipse中集成Tomcat就成功了。

5、JSP

一个JSP页面的例子:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP小测试</title>
</head>
<body>
   <h2>
      <% 
       out.println("你好鸭,2021!");
      %> 
   </h2>
</body>
</html>

在这里插入图片描述

5.1、静态页面和动态页面

静态页面通过浏览器直接解释页面内容并呈现在客户端浏览器窗口中,通常指HTML页面,一般由HTML标记和普通文本组成。

动态页面是包含除了HTML内容以外的能够处理企业数据的服务器端技术,通常这种技术和服务器端应用程序开发语言有关。三大动态网页技术:ASP、PHP、JSP。而JSP是Java Web的核心内容。

5.2、JSP的概念

JSP是Java Server Pages的缩写,即Java服务器页面,是一种处理Java Web动态页面标准的JEE服务器技术。必需运行在JSP/Servlet容器上。容器利用内置JSP引擎对JSP实现处理。

JSP技术是以Java语言作为脚本语言的,JSP网页为整个服务器端的Java库单元提供了一个接口来服务于HTTP的应用程序。JSP文件后缀名为 *.jsp 。JSP开发的WEB应用可以跨平台使用,既可以运行在Linux上也能运行在Windows上。

JSP是一种动态网页开发技术。是一种Java servlet,主要用于实现Java web应用程序的用户界面部分。网页开发者们通过结合HTML代码、XHTML代码、XML元素以及嵌入JSP操作和命令来编写JSP。JSP通过网页表单获取用户输入数据、访问数据库及其他数据源,然后动态地创建网页。

5.3、使用JSP的理由

JSP程序与CGI程序有着相似的功能,但和CGI程序相比,JSP程序有如下优势:

  • 性能更加优越,因为JSP可以直接在HTML网页中动态嵌入元素而不需要单独引用CGI文件。
  • 服务器调用的是已经编译好的JSP文件,而不像CGI/Perl那样必须先载入解释器和目标脚本。
  • JSP 基于Java Servlet API,因此,JSP拥有各种强大的企业级Java API,包括JDBC,JNDI,EJB,JAXP等等。
  • JSP页面可以与处理业务逻辑的 Servlet 一起使用,这种模式被Java servlet 模板引擎所支持。
  • JSP是Java EE不可或缺的一部分,是一个完整的企业级应用平台。这意味着JSP可以用最简单的方式来实现最复杂的应用。

5.4、JSP的优势

  • 与ASP相比,JSP有两大优势。首先,动态部分用Java编写,而不是VB或其他MS专用语言,所以更加强大与易用。第二点就是JSP易于移植到非MS平台上。
  • 与纯Servlet相比,JSP可以很方便的编写或者修改HTML网页而不用去面对大量的println语句。
  • 与SSI相比,SSI无法使用表单数据、无法进行数据库链接。
  • 与JavaScript相比,虽然JavaScript可以在客户端动态生成HTML,但是很难与服务器交互,因此不能提供复杂的服务,比如访问数据库和图像处理等等。
  • 与静态HTML相比,静态HTML不包含动态信息。

5.5、JSP的开发环境

首先,JSP是以Java语言为脚本的,所以需要JDK的支持。然后JSP需要专门的JSP引擎来进行解析,也就是web服务器,常用的是Tomcat。然后需要开发工具IDE,常用eclipse。最后需要将Tomcat集成到IDE中。

总结:开发JSP的环境为:JDK + IDE工具(Eclipse) + Web服务器(Tomcat)。

5.6、JSP容器和处理

网络服务器需要一个JSP引擎,也就是一个容器来处理JS 页面。容器负责截获对JSP页面的请求。Tomcat容器是一个很好的JSP引擎,也是一个web服务器,能够很好的支持JSP的开发。JSP容器与Web服务器协同合作,为JSP的正常运行提供必要的运行环境和其他服务,并且能够正确识别专属于JSP网页的特殊元素。

JSP容器和JSP文件在Web应用中所处的位置如下:
在这里插入图片描述
web服务器使用JSP创建网页的过程如下:

  • 浏览器发送一个HTTP请求给服务器。
  • Web服务器识别出这是一个对JSP网页的请求,并且将该请求传递给JSP引擎。通过使用URL或者 .jsp 文件来完成。
  • JSP引擎从磁盘中载入JSP文件,然后将它们转化为Servlet。这种转化只是简单地将所有模板文本改用println() 语句,并且将所有的JSP元素转化成Java代码。
  • JSP引擎将Servlet编译成可执行类,并且将原始请求传递给Servlet引擎。
  • Web服务器的某组件将会调用Servlet引擎,然后载入并执行Servlet类。在执行过程中,Servlet产生HTML格式的输出并将其内嵌于HTTP response中上交给Web服务器。
  • Web服务器以静态HTML网页的形式将HTTP response返回到浏览器中。
  • Web浏览器处理HTTP response中动态产生的HTML网页,就好像在处理静态网页一样。

图示如下:
在这里插入图片描述
一般情况下,JSP引擎会检查JSP文件对应的Servlet是否已经存在,并且检查JSP文件的修改日期是否早于Servlet。如果JSP文件的修改日期早于对应的Servlet,那么容器就可以确定JSP文件没有被修改过并且Servlet有效。这使得整个流程与其他脚本语言(比如 PHP)相比要高效快捷一些。JSP网页就是用另一种方式来编写Servlet而不用成为Java 编程高手。除了解释阶段外,JSP网页几乎可以被当成一个普通的Servlet来对待。

5.7、JSP的生命周期

JSP生命周期就是从创建到销毁的整个过程,类似于servlet生命周期,区别在于JSP生命周期还包括将JSP文件编译成servlet。以下是JSP生命周期中的几个阶段:

  • 编译阶段:servlet容器编译servlet源文件,生成servlet类。
  • 初始化阶段:加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法。
  • 执行阶段:调用与JSP对应的servlet实例的服务方法。
  • 销毁阶段:调用与JSP对应的servlet实例的销毁方法,然后销毁servlet实例。

如下图:
在这里插入图片描述
(1)JSP的编译

当浏览器请求JSP页面时,JSP引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个JSP文件。编译的过程有3个步骤,如下:

  • 解析JSP文件。
  • 将JSP文件转为servlet。
  • 编译servlet。

(2)JSP的初始化

容器载入JSP文件后,它会在为请求提供任何服务前调用jspInit()方法。如果需要执行自定义的JSP初始化任务,重写jspInit()方法就行了。如下:

public void jspInit(){
  // 初始化代码
}

一般只会初始化一次,servlet也是如此。通常情况下可以在jspInit()方法中初始化数据库连接、打开文件和创建查询表。

(3)JSP的执行

这一阶段描述了JSP生命周期中一切与请求相关的交互行为,直到被销毁。当JSP网页完成初始化后,JSP引擎将会调用_jspService()方法。_jspService()方法需要一个HttpServletRequest请求对象和一个HttpServletResponse响应对象作为它的参数,如下:

void _jspService(HttpServletRequest request,HttpServletResponse response)
{
   // 服务端处理代码
}

_jspService()方法在每个request中被调用一次并且负责产生与之相对应的response,并且它还负责产生所有7个HTTP方法的回应,比如GET、POST、DELETE等等。

(4)JSP的销毁

销毁阶段描述了当一个JSP网页从容器中被移除时所发生的一切。jspDestroy()方法在JSP中等价于servlet中的销毁方法。当需要执行任何清理工作时可以重写jspDestroy()方法,比如释放数据库连接或者关闭文件夹等等。如下:

public void jspDestroy()
{
   // 清理代码
}

测试:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP小测试</title>
</head>
<body>
   <%! 
     private int initVar = 0;
     private int serviceVar = 0;
     private int destoryVar = 0;
   %>
   
   <%! 
     // 初始化jsp 
     public void jspInit(){
       initVar++;
       System.out.println("调用jspInit方法,JSP文件初始化了" + initVar + "次!");
     }
   
     // 销毁jsp
     public void jspDestory(){
       destoryVar++;
       System.out.println("调用jspDestory方法,JSP文件销毁了" + destoryVar + "次!");
     }
   %>
   
   <%
     serviceVar++;
     System.out.println("调用_jspService方法,JSP响应了" + serviceVar + "次请求!");
     String res = "初始化次数:" + initVar + "次";
     String res2 = "响应次数:" + serviceVar + "次";
     String res3 = "销毁次数:" + destoryVar + "次";
   %>
   
   <h3>
      <%= res %>
   </h3>
   
   <h3>
      <%= res2 %>
   </h3>
   
   <h3>
      <%= res3 %>
   </h3>
</body>
</html>

启动服务器,访问:
在这里插入图片描述
在这里插入图片描述
刷新多次:
在这里插入图片描述
在这里插入图片描述
可以看到,JSP的初始化方法只会调用一次,而核心的_jspService方法可以调用多次,关闭容器,才会执行销毁方法,销毁方法也只会调用一次。

5.8、JSP的页面构成

JSP页面由两大部分构成:HTML普通文本和JSP元素。

HTML普通文本
可以出现在JSP页面中的静态网页文件,属于静态部分。

JSP元素
全部由Java来完成,即真正属于JSP的东西,属于动态部分。JSP元素一般有如下:

  • JSP声明
  • Java脚本
  • JSP表达式
  • JSP注释
  • JSP指令
  • JSP动作

说明:JSP引擎只对JSP元素进行处理,会忽略静态内容,即完成动态数据的处理。

5.8.1、JSP声明

一个声明语句可以声明一个或多个变量、方法,供后面的Java代码使用。在JSP文件中,必须先声明这些变量和方法然后才能使用它们。

格式:

<%!
   声明定义全局变量、常量;
   声明定义全局方法;
%>

示例如下:

   <!-- jsp声明 -->
   <%! 
      // 声明变量
      private int dual = 18;
      private boolean YES = true;
      private boolean NO = false;    
      
      // 声明方法
      public boolean checkAge(int age){
          if(age < 18){
              return false;
          }else {
              return true;
          }
      }
   %>

5.8.2、Java脚本代码

脚本可以包含任意量的Java语句、变量、方法或表达式,只要它们在脚本语言中是有效的。

格式:

<%  
任意合法的Java代码 
 %

示例如下:

    <!-- java脚本 -->
    <% 
      boolean res = checkAge(25);
      // 需要引入java.util.Date包
      Date date = new Date();
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
      String dateStr = sdf.format(date);
    %>

5.8.3、JSP表达式

一个JSP表达式中包含的脚本语言表达式,先被转化成String,然后插入到表达式出现的地方。由于表达式的值会被转化成String,所以可以在一个文本行中使用表达式而不用去管它是否是HTML标签。表达式元素中可以包含任何符合Java语言规范的表达式,但是不能使用分号来结束表达式。

格式:

<%=
 表达式代码 
 %>

JSP表达式用于数据的显示和赋值,表达式一定会有结果,否则会报错。注意:JSP表达式里面一定不能有分号。

示例:

    <!-- JSP表示 -->
    <h2>
              现在成年了吗? <%= res %>
    </h2>
    
    <h2>
             现在时间: <%= dateStr %>
    </h2>

完整的例子:

<%@page import="java.text.SimpleDateFormat"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.Date"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP小测试</title>
</head>
   <!-- jsp声明 -->
   <%! 
      // 声明变量
      private int dual = 18;
      private boolean YES = true;
      private boolean NO = false;    
      
      // 声明方法
      public boolean checkAge(int age){
          if(age < 18){
              return false;
          }else {
              return true;
          }
      }
   %>
<body>
    <!-- java脚本 -->
    <% 
      boolean res = checkAge(25);
      // 需要引入java.util.Date包
      Date date = new Date();
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
      String dateStr = sdf.format(date);
    %>
    
    <!-- JSP表达式 -->
    <h2>
              现在成年了吗? <%= res %>
    </h2>
    
    <h2>
             现在时间: <%= dateStr %>
    </h2>
    
</body>
</html>

5.8.4、JSP注释

JSP注释主要有两个作用:为代码作注释以及将某段代码注释掉。

格式:

<%--
JSP注释内容
--%>

示例:
在这里插入图片描述

5.8.5、JSP指令

JSP指令用来设置与整个JSP页面相关的属性。

JSP指令语法格式:

<%@ directive attribute="value" %>

有3种指令标签,如下:
在这里插入图片描述
(1)page指令

定义JSP页面中的属性。如下:

<%@page import="java.text.SimpleDateFormat"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.Date"%>

page标签指令中的部分属性如下:

  • language:服务器语言支持(JSP只能是Java)。
  • import:引入java包。
  • contentType:定义页面主体内容及编码格式。
  • pageEncoding:编码格式(JSP页面都使用utf-8)。
  • buffer:缓冲区,缓存数据,到了缓存上限,自动向客户端client输出信息,默认值是8KB。
  • autoFlush:自动刷新页面。
  • errorPage:指定错误处理页,定位到错误页。
  • iserrorPage:当前JSP页面是否是一个错误处理页。
  • isELIgnored:表示是否忽略EL语言,默认是不忽略的。

完整的属性如下表:
在这里插入图片描述

(2)include指令

规定包含什么内容到当前JSP页面,一般是包含其他页面。如下:

<%@ include file="top.jsp" %>

top.jsp为被包含的目标资源页面URL。将目标资源页面中内容在编译阶段合并到当前JSP页面中来统一进行编辑(静态包含);目标如果是JSP文件则不生成单独的java文件;当前JSP页面可以使用被包含页面的所有资源(java对象以及JS内容)。

(3)taglib指令

引入自定义的标签库。如下:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%>

prefix是前缀,uri指标签库的位置。使用jstl标签库需要引入jstl依赖。
在这里插入图片描述

5.8.6、JSP隐式对象

目标JSP页面被请求成功后,被创建的Java对象隐式地存在于JSP对象中。这些隐式对象名称固定,只能在JSPJava脚本中及JSP表达式中使用。JSP隐式对象随着JSP页面的加载自动创建并存储在JSP对象中作为它的属性,JSP页面编写者可以直接使用,无需再创建。利用JSP隐式对象我们可以接收到用户请求的数据和访问服务器资源,并对客户请求作出响应。

JSP隐式对象是JSP容器为每个页面提供的Java对象,开发者可以直接使用它们而不用显式声明。JSP隐式对象也被称为预定义变量。

JSP所支持的九大隐式对象:
在这里插入图片描述
(1)request对象

request对象是javax.servlet.http.HttpServletRequest 类的实例。每当客户端请求一个JSP页面时,JSP引擎就会制造一个新的request对象来代表这个请求。request对象提供了一系列方法来获取HTTP头信息,cookies,HTTP方法等等。这个隐式对象很重要。

(2)response对象

response对象是javax.servlet.http.HttpServletResponse类的实例。当服务器创建request对象时会同时创建用于响应这个客户端的response对象。response对象也定义了处理HTTP头模块的接口。通过这个对象,开发者们可以添加新的cookies,时间戳,HTTP状态码等等。这个隐式对象很重要。

(3)out对象

out对象是 javax.servlet.jsp.JspWriter 类的实例,用来在response对象中写入内容。最初的JspWriter类对象根据页面是否有缓存来进行不同的实例化操作。可以在page指令中使用buffered='false’属性来轻松关闭缓存。JspWriter类包含了大部分java.io.PrintWriter类中的方法。不过,JspWriter新增了一些专为处理缓存而设计的方法。还有就是,JspWriter类会抛出IOExceptions异常,而PrintWriter不会。

重要的方法如下:
在这里插入图片描述
(4)session对象

session对象是 javax.servlet.http.HttpSession 类的实例。和Java Servlets中的session对象有一样的行为。session对象用来跟踪在各个客户端请求间的会话。session是web会话作用域对象,表示用户和服务器交互的一个过程,此对象对于每个用户是私有的,只存储当前用户的个人信息,多个用户之间的session对象是不透明的。使用set Attribute设置属性以及get Attribute获取属性的常用方法。这个隐式对象很重要。

(5)application对象

application对象直接包装了servlet的ServletContext类的对象,是javax.servlet.ServletContext 类的实例。这个对象在JSP页面的整个生命周期中都代表着这个JSP页面。这个对象在JSP页面初始化时被创建,随着jspDestroy()方法的调用而被移除。通过向application中添加属性,则所有组成web应用的JSP文件都能访问到这些属性。

application对象是整个web应用中最大的一个作用域对象,它表示当前整个web应用程序。提供set Attribute和get Attribute方法在application中存取对象;此作用域存储的对象被所有与web服务器交互的用户共享。通常统计在线人数会用到。

(6)config对象

config对象是 javax.servlet.ServletConfig 类的实例,直接包装了servlet的ServletConfig类的对象。这个对象允许开发者访问Servlet或者JSP引擎的初始化参数,比如文件路径等。这个隐式对象不常用。

(7)pageContext对象

pageContext对象是javax.servlet.jsp.PageContext类的实例,用来代表整个JSP页面。这个对象主要用来访问页面信息,同时过滤掉大部分实现细节。这个对象存储了request对象和response对象的引用。application对象,config对象,session对象,out对象可以通过访问这个对象的属性来导出。

pageContext对象是当前页面上下文作用域对象,主要提供本页面作用域中对象的设置及获取方法,pageContext作用域存储的对象只对当前JSP页面有效,对其他JSP页面是无效的。

pageContext对象也包含了传给JSP页面的指令信息,包括缓存信息,ErrorPage URL,页面scope等。PageContext类定义了一些字段,包括PAGE_SCOPE,REQUEST_SCOPE,SESSION_SCOPE, APPLICATION_SCOPE。它也提供了40余种方法,有一半继承自javax.servlet.jsp.JspContext 类。

其中一个重要的方法就是 removeAttribute(),它可接受一个或两个参数。
如下:

pageContext.removeAttribute("attrName")

移除四个scope中相关属性。

也可以只移除特定scope中的相关属性:

pageContext.removeAttribute("attrName", PAGE_SCOPE);

(8)page对象

这个对象就是页面实例的引用。它可以被看做是整个JSP页面的代表。page 对象就是this对象的同义词。这个对象不常用。

(9)exception对象

exception对象包装了从先前页面中抛出的异常信息。它通常被用来产生对出错条件的适当响应。

九大隐式对象中的request对象、response对象、session对象、application对象、pageContext对象这5个隐式对象,用到的频率非常高,需要重点掌握。

通过一个例子说明。

先创建一个pojo类:

public class User {

    private String username;

    private String password;

    public User() {

    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

创建login.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录界面</title>
</head>
<body>
	<!--这个表单提交到doLogin.jsp这个页面-->
    <form action="doLogin.jsp" method="post">
    	<label>用户名:</label>
    	<input type="text" name="username" /><br>
    	<label>登录密码:</label>
    	<input type="password" name="password" /><br>
    	<input type="submit" value="登录" />
    </form>
</body>
</html>

创建doLogin.jsp:

<%-- 这个页面用来处理登录界面提交的表单数据 --%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="com.ycz.pojo.User"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>处理表单数据界面 </title>
</head>
	<%
	  // 从request对象中获取用户提交的参数
	  String username = request.getParameter("username");
	  String password = request.getParameter("password");
	  if(username!=null && !username.equals("") && username.equals("yanchengzhi")
	          && password!=null && !password.equals("") && password.equals("ycz123456")){
	      User user = new User(username,password);// 实例化对象
	      // 登录成功,存在session域中
	      session.setAttribute("currentUser", user);
	  } else {
	      // 登录失败,重定向到登录界面
	      response.sendRedirect("login.jsp");
	  }
	%>
<body>
   <a href="index.jsp">访问主页面</a>
</body>
</html>

创建top.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="com.ycz.pojo.User"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP小测试</title>
<style>
	table {
		width: 100%;
		border: 1px solid pink;
		background: rgb(245,245,245);
	}

	img {
		width: 100%;
		height: 120px;
	}

	#td2 {
		text-align: right;
		background: rgb(245,245,245);
		font-family: fantasy;
	}
</style>
</head>

<body>
    <table cellspacing="5" cellpadding="0">
    	<tr>
    		<td>
    			<img src="./images/春天.jpg" alt="spring" />
    		</td>
    	</tr>
    	<tr>
    		<td id="td2">
    		      当前用户:【<%= ((User)(session.getAttribute("currentUser"))).getUsername() %>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 
    		</td>
    	</tr>
    </table>
</body>
</html>

创建index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入taglib标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP小测试</title>
</head>
<body style="background:rgb(245,245,245)">
   <!-- include指令静态包含页面 -->
   <%@ include file = "/views/top.jsp" %>
   <!-- c:if选择标签 -->
   <c:if test="${25>=18}">
      <h1 style="background:red">大哥,你已经老大不小了!</h1>
   </c:if>
   <c:if test="${25<18}">
      <h1 style="background:pink">太小了,还是只幼崽!</h1>
   </c:if>
</body>
</html>

web.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
	http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>jsp-demo</display-name>
	<welcome-file-list>
		<welcome-file>/login.jsp</welcome-file>
	</welcome-file-list>
</web-app>

启动web服务器,访问:http://localhost:8080/jsp-demo/
在这里插入图片描述
输入正确的用户名和密码,点击登录:
在这里插入图片描述
在这里插入图片描述
来到了doLogin.jsp页面,点击链接:
在这里插入图片描述
来到了index.jsp页面,这个页面包含了top.jsp页面,并且session中的对象也可以获取到。

测试out和response对象。创建testLanguage.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试 </title>
</head>
  <%! 
    String language = "java";
    String hy = "";
    public String checkLanguage(String lan){
        if(lan.equalsIgnoreCase("java")){
            return "企业级开发";
        }else if(lan.equalsIgnoreCase("python")){
            return "人工智能";
        } else {
            return "其他领域";
        }
    }
  %>
<body>
  <%
    hy = checkLanguage(language);
    // 根据所选语言重定向页面
    if(hy.equals("企业级开发")){
        response.sendRedirect("1.jsp");
    } else if(hy.equals("人工智能")){
        response.sendRedirect("2.jsp");
    } else {
        response.sendRedirect("3.jsp");
    }
  %>
  
  <h2>适合的领域是:
  <% 
     out.print(checkLanguage(language)); 
     %>
  </h2>
</body>
</html>

1.jsp页面、2.jsp页面、3.jsp页面如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
启动web服务器,访问:http://localhost:8080/jsp-demo/testLanguage.jsp
在这里插入图片描述
自动跳转到了1.jsp页面。

测试错误跳转。创建error.jsp页面:

<%-- isErrorPage属性指定该页面作为错误页面 --%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isErrorPage="true"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>错误页面 </title>
</head>
<body>
     对不起,页面出错了!╮(╯﹏╰)╭\<br/>
     <%=
       // exception为隐式对象,获取错误信息
       exception.getMessage()
     %> 
     
     <%
       // 打印异常轨迹
       exception.printStackTrace();
     %>
</body>
</html>

创建count.jsp页面,这个页面中会发生错误:

<%-- errorPage属性指定跳转的错误页面 --%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" errorPage="error.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试错误跳转 </title>
</head>
<body>
     <%!
       int div = 0;
       int res = 0;
     %>
     
     <% 
       // 除数为0,会发生异常
       res = 25 /div;
     %>
</body>
</html>

启动web服务器,访问:http://localhost:8080/jsp-demo/count.jsp
在这里插入图片描述
页面发生错误,自动跳转到了指定的error.jsp错误页面。

测试pageContext和request对象。创建page0.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试 </title>
</head>
   <%
     // pageContext域中存对象
     pageContext.setAttribute("mess", "com");
     // request域中存对象
     request.setAttribute("mess2", "cn");
   %>
<body>
     从当前pageContext域中取出对象:<%= pageContext.getAttribute("mess") %><br/>
     从当前request域中取出对象:<%= request.getAttribute("mess2") %>  
     
  <%-- 将请求转发到其他页面 --%>
  <%
    // 请求转发后,此页面无法获取
    request.getRequestDispatcher("page2.jsp").forward(request, response);
  %>
</body>
</html>

创建page2.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试 </title>
</head>
<body>
  pegeContext对象只在当前页面有效,转发之后取不到值:<%= pageContext.getAttribute("mess") %><br/>
  转发后可从不同页面中取出request作用域中的对象:<%= request.getAttribute("mess2") %>
</body>
</html>

启动web服务器,访问:http://localhost:8080/jsp-demo/page0.jsp
在这里插入图片描述
直接跳到了page2.jsp页面,因为request对象是page0.jsp页面转发过来的,所以request对象中可以获取到参数,而pageContext对象只在当前页面有效,这个pageContext对象代表的是page2.jsp页面,和page0.jsp页面的pageContext对象是不一样的。

5.9、JSP动作

与JSP指令元素不同的是,JSP动作元素在请求处理阶段起作用。JSP动作元素是用XML语法写成的。利用JSP动作可以动态地插入文件、重用JavaBean组件、把用户重定向到另外的页面、为Java插件生成HTML代码。动作元素只有一种语法,它符合XML标准:

<jsp:action_name attribute="value" />

常用JSP标准动作如下:

<jsp:include></jsp:include>
<jsp:forward></jsp:forward>
<jsp:param></jsp:param>
<jsp:useBean></jsp:useBean>
<jsp:setProperty>
</jsp:setProperty>

动作元素基本上都是预定义的函数,JSP规范定义了一系列的标准动作,它用JSP作为前缀,可用的标准动作元素如下:
在这里插入图片描述
JSP动作元素中的常见属性如下:

  • id属性:id属性是动作元素的唯一标识,可以在JSP页面中引用。动作元素创建的id值可以通过PageContext来调用。
  • scope属性:该属性用于识别动作元素的生命周期。 id属性和scope属性有直接关系,scope属性定义了相关联id对象的寿命。 scope属性有四个可能的值: page、request、session、application。

(1)include动作

include动作用来包含其他jsp或html页面到当前jsp页面中来,以达到合并两个
页面的效果。page属性是此动作必须设置的,引入目标文件的url flush属性是可选属性,引入文件时是否刷新缓存区(false)。
在这里插入图片描述
注意:include动作是动态包含目标文件,本jsp页面和目标被包含的文件都是
独立编译的,本jsp页面不能使用被包含文件中的Java定义及JS定义任何内容。

使用该动作如下:

 <jsp:include page="common/foot.jsp" flush="true"></jsp:include>

include指令和include动作的区别:

  • 包含方式不同(动作是动态包含,指令是静态包含)。
  • 包含页面的功能不同(静态包含能使用被包含页面的所有功能)。
  • 动态包含可以为被包含页面(jsp)传参。
  • 编译生成JSP java文件不同(指令不会生成java文件,动作会生成java文件)。

注意:include指令是在JSP文件被转换成Servlet的时候引入文件,而这里的jsp:include动作不同,插入文件是在页面被请求的时候。

(2)forward动作

forward动作用来执行请求转发到目标资源页面,唯一的动作属性page设置目标
资源页的转发url。forward动作的作用等效于request对象获取RequestDispatcher并调用其forward方法功能。

语法:

<jsp:forward page="相对 URL 地址" />

使用如下:

<jsp:forward page="login.jsp"></jsp:forward>

(3)param动作

param动作用来辅助其他jsp动作实现请求参数的传递设置。

语法:

<jsp:param name="" value=""/>

name属性为参数名称,value属性为参数值。

(4)useBean动作

useBean动作功能实现在jsp页面中创建JavaBean对象并设置其作用域范围,以实现JavaBean的功能重用。如下:

<jsp:useBean id="userObj" class="com.ycz.pojo.Student" scope="request"/>

属性说明:

  • id:必须属性,标识bean对象的唯一引用标识符。
  • class:标识bean对象的实际类型。
  • type:标识bean类的父类型名称。
  • scope:设置bean对象的作用域(request、page、session、application)。

(5)setProperty动作

setProperty动作用来为某个bean对象的属性设置值。如下:

<jsp:useBean id="stu" class="com.ycz.pojo.Student"></jsp:useBean>
<jsp:setProperty property="name" name="stu"
value="Tom"/>

setProperty默认调用相关字段属性中的set方法,获取值调用get方法,注意。其中property属性的值要与set或get方法中的名称一样,而不是跟字段属性名称一样。
在这里插入图片描述
(6)getProperty动作

getProperty动作用来获取某个bean对象的属性值。如下:

<jsp:useBean id="stu" class="com.xzit.pojo.Student"></jsp:useBean>
<jsp:setProperty property="name" name="stu"/>
<jsp:setProperty property="age" name="stu"/>

注意:getProperty动作不能在跳转页面中使用,只能够在本页面中使用。

5.10、EL表达式

EL(Expression Language) ,即表达式语言,EL只限于在JSP页面中使用,被Servlet容器中EL引擎解析,EL是使用Java语言实现其特定功能的,但是EL不能使用任何java语法和java对象。EL的优势在于能够使用最简洁的表达式构建动态JSP页面,从而避免在jsp页面嵌入过多的Java脚本代码。EL表达式计算后得到一个计算结果,EL可以为JSP指令、动作、html标记属性赋值及出现在文档的任何空白处。

(1)EL语法和数据类型

EL的语法很简单,如下:

${EL表达式}

注意:EL表达式若想在JSP中正确被解析必须设置page指令的isELIgnored属性值为false。如果设为true则是禁用EL表达式。EL默认是开启的。

EL中的常量:

整型常量------任何有效的整数,如23-32
浮点常量------任何有效的分数,如0.7-3.342.2E2
字符串常量----使用双引号或单引号包围的字符
布尔常量------truefalse
空常量-------null

EL中的变量:

任何从作用域或请求参数获取的数据引用标识在EL中被称为变量,变量如果是指向某个复合类型(对象)实例的,则可以使用此变量访问对象的属性。但是方法是一定不能调用的。如下:

${message}  //从作用域中获取message变量数据值
${user.name}  //从作用域中获取user对象的name属性值

注意:复合类型实例获取值,找到的是实例类中定义的get方法后面跟的属性名,而不是字段属性名称,一般情况下,字段属性与get后面跟的属性名是一样的,但是也有不一样。

(2)EL中的运算符

算符运算符:+、-、*、div(/的替代)、mod(%的替代)

关系运算符:gt(>的替代)、lt(<的替代)、ge(>=的替代)、le(<=的替代 )、eq(==的替代)、ne(!=的替代)

逻辑运算符:and(&&的替代)、or(||的替代)、not(!的替代)

空运算符:empty(判断是否为空)

(3)EL中的隐式对象

EL表达式隐式对象是预定义好的EL对象,利用此对象可以访问此对象中的变量。有3种常用的隐式对象:

  • 作用域对象:用来访问作用域中的变量。
  • 请求参数对象:访问请求中的参数。
  • cookie对象:访问客户端的cookie。

作用域隐式对象

默认EL表达式都是从作用域中访问目标对象或属性,可以使用作用域对象指定
访问范围。如下:

${pageScope.userName}
${requestScope.current.birth}
${sessionScope.current.birth}
${applicationScope.onLineNum}

参数隐式对象

EL隐式对象param用来获取请求当前JSP页面的请求参数值。如下:

${param.userName}
${param.age}
${param.address}

如果省略了param,则默认在四个作用域中依次搜索。

Cookie隐式对象

EL隐式对象cookie用来获取存储在客户端机器上的cookie信息。如下:

${cookie} //获取所有未过期的cookie名称值对
${empty cookie}  //判断cookie数组是否为空
${cookie['user']}  //获取名称为user的cookie对象
${cookie['user'].name}  //获取名称为user的cookie对象name值
${cookie['user'].value}  //获取名称为user的cookie对象value值

(4)测试

创建实体pojo:

package com.ycz.pojo;

public class Student {

    private String name;

    private Integer age;

    public Student() {

    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

创建新的Servlet组件,如下:

@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取客户端所有未过期Cookie
        Cookie []cookies = request.getCookies();
        if(cookies!=null && cookies.length!=0) {
            int count = 0;
            for(Cookie c:cookies) {
                if(c.getName().equals("username")) {
                    count++;
                    break;
                }
            }
            if(count==0) {
                Cookie cookie = new Cookie("username", "韩立");
                cookie.setMaxAge(60 * 60 * 24);
                response.addCookie(cookie);
            }
        }
    }

}

配置Servlet组件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
	http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>el-demo</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	
	<!-- 配置Servlet -->
	<servlet>
	   <servlet-name>testServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.TestServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>testServlet</servlet-name>
	   <url-pattern>/testServlet.do</url-pattern>
	</servlet-mapping>
</web-app>

创建index.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="com.ycz.pojo.Student"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
   <%
      String name = "韩跑跑";
      // 存在page域中
      pageContext.setAttribute("username", name);
      Student stu = new Student("南宫婉",23);
      // 存在request域中
      request.setAttribute("stu", stu);
   %>
<body>
    <h3>
                  姓名:【${"yanchengzhi"}】<br/>
                  年龄:【${23+2}岁】<br/>
         <!-- EL表达式域中取值 -->
                  喜欢的是:【${pageScope.username}<br/>   
    </h3>
    <a href="el.jsp?pName=韩立&pAge=25">请求前往el.jsp页面</a><br/><br/>
    <form action="el.jsp" method="post">
         <input type="checkbox" name="likes" value="sing"/>唱歌
         <input type="checkbox" name="likes" value="read"/>阅读
         <input type="checkbox" name="likes" value="travel"/>旅游<br>
         <input type="submit" value="提交"/>       
    </form>
    <a href="testServlet.do">请求Servlet</a>
</body>
</html>

创建el.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body bgcolor="pink">
    <h3>
      <!-- 从request域中取值,跨页面没转发的话是取不到的 -->
               从request域中取值:${requestScope.stu.name}<br/>
      <!-- 使用param参数隐式对象获取传过来的值 -->
               获取请求参数:${param.pName}=========>${param.pAge}<br/>
               获取一个参数对应的多个值:<br/>
               第一个值:${paramValues.likes[0]}<br/>
               第二个值:${paramValues.likes[1]}<br/>
               第三个值:${paramValues.likes[2]}<br/>
       Cookie隐式对象获取cookie对象的name:${cookie['username'].name}<br/>
       Cookie隐式对象获取cookie对象的value:${cookie['username'].value}<br/>
    </h3>
</body>
</html>

启动web服务器,访问:http://localhost:8080/el-demo
在这里插入图片描述
先点击最下面一个链接添加Cookie,然后勾选复选框提交:
在这里插入图片描述
在这里插入图片描述
返回,点击链接:
在这里插入图片描述
在这里插入图片描述
URL中携带的参数可以通过EL的隐式对象param获取到。EL表达式比较重要,需要熟练掌握。

5.11、JSTL标签库

JSTL全称JavaServerPages Standard Tag Library,即JSP标准标签库,其使用Java语言实现,为简化JSP页面制作而开发的自定义功能标签。注意的地方:

  • JSTL的使用依赖于jstl.jar文件。
  • JSTL只能在JSP页面中使用。
  • 必须要在JSP中使用taglib指令引入标签库。

大体上分为4类:Core(核心)标签、Format格式化标签、XML标签、SQL标签。重点掌握Core标签。

5.11.1、Core标签

Core标签是JSTL标签库的核心标签,也是最重要的部分。可以分为通用标签、逻辑条件标签、循环迭代标签、其他标签。

在这里插入图片描述
先将jar加到lib中,并且build path。

(1)通用标签

set标签

set标签用来设置一个变量并指定其作用域范围。有以下5个属性:

  • var:设置变量的名称。
  • value:设置变量的值,支持合法的表达式。
  • scope:设置变量的作用域,默认为page。
  • target:设置变量为一个对象。
  • property:设置变量对象的属性与target合用。

如下:

<c:set var="username" value="king" scope="session"/>

out标签

out标签用来输出一个变量的值,通常此标签使用较少,可以使用EL表达式代替
。有以下3个属性:

  • value:以表达式方式查找目标变量。
  • default:设置未找到时使用的默认值。
  • escapeXml:设置是否解析xml及html标记,设为true时不解析,设为false时解析。

如下:

<c:out value="${pageScope.user}" default="未知" escapeXml="false"></c:out>

remove标签

remove标签用来从指定的作用域中移除目标变量。有以下两个属性:

  • val:被移除变量的变量名称(不支持表达式)。
  • scope:设置被移除的变量作用域(默认所有作用域)。

测试。创建实体pojo,如下:

public class Student {

    private String name;

    private String sex;

    private Integer age;

    private String level;

    public Student() {

    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Student(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

}

创建index.jsp。如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="com.ycz.domain.Student"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
   <%
      Student stu = new Student("韩立","男",25);
   %>
<body>
    <!-- 使用set标签设置变量 -->
    <c:set var="user" value="<h3>云过梦无痕</h3>" scope="page"></c:set>
        姓名是:【<%= stu.getName() %>】<br/>
       从page域中取值:【<%= pageContext.getAttribute("user") %>】<br/>
    <!-- set标签重新赋值 -->
    <c:set target="<%= stu %>" property="name" value="韩老魔"></c:set>
        现在姓名是:【<%= stu.getName() %>】<br/>
    <!-- remove标签移除变量 -->
    <c:remove var="user" scope="page"/>
         变量从作用域中移除后取不到:【<%= pageContext.getAttribute("user") %>】
    <!-- 设置一个默认值 -->
    <span style="color:pink">
        <!-- out标签设置默认值 -->
        <c:out value="${pageScope.user}" default="未知" escapeXml="false"></c:out>
    </span>
</body>
</html>

启动web服务器,访问:http://localhost:8080/jstl-demo/
在这里插入图片描述
(2)逻辑条件标签

if标签

if标签用来实现逻辑条件判断,条件成立则执行if标签内的内容。有以下几个属性:

  • test:必须属性,设置if标签boolean表达式。
  • var:设置此if标签计算结果的变量名称。
  • scope:设置变量的作用域。

choose、when标签

choose标签实现多分支语句逻辑判断,类似于Java中的switch语句功能。如下:

<c:choose>
<c:when test="${param.age lt 18 and param.age ge 0}">
未成年
</c:when>
<c:when test="${param.age lt 60 and param.age ge 18}">
成年人尚未步入老年
</c:when>
<c:when test="${param.age ge 60 and param.age le 120}">
老年人
</c:when>
<c:otherwise>
年龄是非法的!
</c:otherwise>
</c:choose>

测试。创建showInfo.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
   <form action="jstlServlet.do" method="post">
        <label>姓名:</label>
        <input type="text" name="name"/><br>
        <label>年龄:</label>
        <input type="text" name="age"/><br>
        <label>性别:</label>
        <input type="radio" name="sex" value="m" checked/><input type="radio" name="sex" value="f"/><br>
        <label>学历:</label>
        <input type="checkbox" name="level" value="zk"/>专科
        <input type="checkbox" name="level" value="bk" checked/>本科
        <input type="checkbox" name="level" value="ss"/>硕士
        <input type="checkbox" name="level" value="bs"/>博士
        <input type="checkbox" name="level" value="others">其他学历<br>
        <input type="submit" value="提交">      
   </form>
</body>
</html>

创建新的Servlet组件,如下:

@WebServlet("/JstlServlet")
public class JstlServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 设置请求体的编码
        request.setCharacterEncoding("utf-8");
        // 获取参数
        String name = request.getParameter("name");
        String sex = request.getParameter("sex");
        String ageStr = request.getParameter("age");
        Integer age = 0;
        if (ageStr != null) {
            age = Integer.parseInt(ageStr);
        }
        String level = request.getParameter("level");
        Student stu = new Student();
        stu.setName(name);
        stu.setSex(sex);
        stu.setAge(age);
        stu.setLevel(level);
        request.setAttribute("stu", stu);
        // 请求转发到其他页面
        RequestDispatcher rd = request.getRequestDispatcher("ifTag.jsp");
        rd.forward(request, response);
    }
}

配置Servlet组件:

	<!-- 配置Servlet -->
	<servlet>
	   <servlet-name>jstlServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.JstlServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>jstlServlet</servlet-name>
	   <url-pattern>/jstlServlet.do</url-pattern>
	</servlet-mapping>

创建ifTag.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
    <!-- if标签 -->
    <c:if test="${requestScope.stu ne null}">
       <c:if test="${requestScope.stu.sex eq 'm'}">
          <img alt="" src="images/m.png">
       </c:if>
       <c:if test="${requestScope.stu.sex eq 'f'}">
          <img alt="" src="images/f.png">
       </c:if>
    </c:if>
    
    <!-- choose when标签 -->
    <c:choose>
       <c:when test="${requestScope.stu.level eq 'zk'}">
                       学生【${requestScope.stu.name}】的学历是:
          <span style="color:red;font-size:24px;font-weight:bold;">专科</span>
       </c:when>
       <c:when test="${requestScope.stu.level eq 'bk'}">
                       学生【${requestScope.stu.name}】的学历是:
          <span style="color:gray;font-size:24px;font-weight:bold;">本科</span>
       </c:when> 
       <c:when test="${requestScope.stu.level eq 'ss'}">
                       学生【${requestScope.stu.name}】的学历是:
          <span style="color:pink;font-size:24px;font-weight:bold;">硕士</span>
       </c:when>
       <c:when test="${requestScope.stu.level eq 'bs'}">
                       学生【${requestScope.stu.name}】的学历是:
          <span style="color:orange;font-size:24px;font-weight:bold;">博士</span>
       </c:when>  
       <c:otherwise>
                       学生【${requestScope.stu.name}】的学历是:
          <span style="color:blue;font-size:24px;font-weight:bold;">其他</span>          
       </c:otherwise>            
    </c:choose>
</body>
</html>

启动web容器,访问:http://localhost:8080/jstl-demo/showInfo.jsp
在这里插入图片描述
填写,提交表单:
在这里插入图片描述
再填写,提交:
在这里插入图片描述
在这里插入图片描述
可以看到,根据性别和学历的不同自动选择相应的头像和样式。

(3)循环迭代标签

forEach标签

forEach标签用来在作用域中查找集合、数组,并对集合、数组中的元素进行迭代访问操作。所有属性如下:

  • items:必须属性,设置被操作的集合查找表达式。
  • var:设置当前被迭代元素的变量引用名称。
  • begin:设置开始迭代元素的开始索引。
  • end:设置结束迭代元素的结束索引。
  • varStatus:设置访问当前元素的状态变量名称。有如下5个属性:current(当前项)、index(当前项的索引)、count(迭代计数器)、first(是否为第一项)、last(是否为最后一项)。
  • step:设置迭代的步长规则(间隔跳距)。

如下:

<c:forEach items="${session.pros}" var="pro" varStatus="sta">
第${sta.index+1}个产品&nbsp;${pro.name}&nbsp;${pro.price}&nbsp;${pro.quantity}&nbsp;<br/>
<c:forEach>

forTokens标签

forTokens标签用来对目标字符串按照给定的分隔符进行分割迭代。属性如下:

  • delims:设置被操作的集合的表达式。

如下:

<c:forTokens item="${'保时捷,奥迪+比亚迪;雷克萨斯'}" delims=",;+" var="car">
${pageScope.car}<br/>
<c:forToken>

测试。创建实体pojo,如下:

public class Car {

    private String proName;

    private Double price;

    private String color;

    public Car() {

    }

    public Car(String proName, Double price, String color) {
        this.proName = proName;
        this.price = price;
        this.color = color;
    }

    public String getProName() {
        return proName;
    }

    public void setProName(String proName) {
        this.proName = proName;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

}
public class DataList {

    private static String names;

    private static List<Car> cList = new ArrayList<>();

    static {
        names = "傲天哥,歪嘴战神哥,装十三哥,D调哥,C调哥";
        cList.add(new Car("雷克萨斯", 670000.54, "银灰"));
        cList.add(new Car("奥迪", 56454545.22, "白色"));
        cList.add(new Car("法拉利", 20343249.43, "狐红"));
        cList.add(new Car("兰博基尼", 208324224.42, "黑色幻影"));
        cList.add(new Car("奔驰", 78234242.34, "蓝影"));
    }

    public static String getNames() {
        return names;
    }

    public static void setNames(String names) {
        DataList.names = names;
    }

    public static List<Car> getcList() {
        return cList;
    }

    public static void setcList(List<Car> cList) {
        DataList.cList = cList;
    }

}

index.jsp中添加一个链接:
在这里插入图片描述
创建新的Servlet组件,如下:

@WebServlet("/ForeachServlet")
public class ForeachServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取数据
        List<Car> list = DataList.getcList();
        // 获取session对象
        HttpSession session = request.getSession();
        session.setAttribute("cars", list);
        response.sendRedirect("foreach.jsp");
    }

}

配置Servlet:

	<!-- 配置Servlet -->
	<servlet>
	   <servlet-name>foreachServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.ForeachServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>foreachServlet</servlet-name>
	   <url-pattern>/foreachServlet.do</url-pattern>
	</servlet-mapping>

创建foreach.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
    <c:if test="${sessionScope.cars ne null}">
        <table style="width: 100%" border="2">
        	<thead>
        		<tr style="background: blue; color: #fff">
        			<th>序号</th>
        			<th>产品名称</th>
        			<th>产品价格</th>
        			<th>产品颜色</th>
        		</tr>
        	</thead>
        	<tbody style="text-align: center">
        		<c:forEach items="${sessionScope.cars}" var="car" varStatus="sta" step="1" begin="0" end="4">
        		    <!-- 奇数行设置样式 -->
        		    <c:if test="${sta.count mod 2 ne 0}">
        		       <tr style="background: orange">
        		           <td>${sta.index+1}</td>
        		           <td>${car.proName}</td>
        		           <td>${car.price}</td>
        		           <td>${car.color}</td>
        		       </tr>
        		    </c:if>
        		    <!-- 偶数行设置样式 -->
        		    <c:if test="${sta.count mod 2 eq 0}">
        		       <tr style="background: pink">
        		           <td>${sta.index+1}</td>
        		           <td>${car.proName}</td>
        		           <td>${car.price}</td>
        		           <td>${car.color}</td>
        		       </tr>
        		    </c:if>        		    
        		</c:forEach>
        	</tbody>
        </table>
    </c:if>
</body>
</html>

启动web服务器,访问:
在这里插入图片描述
点击最下面的链接:
在这里插入图片描述
测试没问题,可以遍历列表。

创建新的Servlet:

@WebServlet("/FortokenServlet")
public class FortokenServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取数据
        String names = DataList.getNames();
        request.setAttribute("names", names);
        // 请求转发
        RequestDispatcher rd = request.getRequestDispatcher("fortoken.jsp");
        rd.forward(request, response);
    }
}

配置Servlet:

	<!-- 配置Servlet-->
	<servlet>
	   <servlet-name>fortokenServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.FortokenServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>fortokenServlet</servlet-name>
	   <url-pattern>/fortokenServlet.do</url-pattern>
	</servlet-mapping>

index.jsp添加新的链接:
在这里插入图片描述
创建fortoken.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
    <c:if test="${requestScope.names ne null}">
       <c:forTokens items="${requestScope.names}" delims="" varStatus="sta">
                             第${sta.index+1}个人的名字是:
           <span style="color: orange; font-style: italic; font-size: 20px">
              ${sta.current}
           </span><br/>
       </c:forTokens>
    </c:if>
</body>
</html>

启动web服务器,访问:
在这里插入图片描述
点击最下面的链接:
在这里插入图片描述
列表遍历没问题。

(4)其他标签

redirect标签

redirect标签用来实现请求重定向功能,此标签可以重定向到本应用内部资源也可以定位到服务器之外的其他资源;通过子标签param可以实现查询参数的传递。属性如下:

  • url:重定向目标资源路径。
  • context:重定向目标url资源所在上下文名称。

如下:

<c:redirect url="/login.jsp"  context="/erpms">
<c:param name="name" value="tom"></c:param>
</c:redirect>

import标签

import标签用来导入其他资源页面在本页面中显示,与include指令及动作功能类似,通过子标签param可以实现查询参数传递。属性如下:

  • url:导入的目标资源路径。
  • context:导入的目标url资源所在的上下文名称。
  • charEncoding:设置导入页面的编码。

如下:

<c:import url="/top.jsp" charEncoding="utf-8" context="">
   <c:param name="name" value="Tom"></c:param>
</c:import>

url标签

url标签用来在应用中设置一个变量,从而可以使用此变量构建资源路径。属性如下:

  • var:设置此url的变量名称。
  • value:设置此url的实际值。
  • scope:设置变量的作用域,默认为page。
  • context:url所在应用的上下文名称。

在index.jsp中添加一个链接:
在这里插入图片描述
创建redirect.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
    <c:if test="${param.location ne null}">
        <!-- 重定向标签 -->
        <c:redirect url="${param.location}">
           <!-- 配合param标签传参 -->
           <c:param name="username" value="凡人修仙传"></c:param>
        </c:redirect>
    </c:if>
</body>
</html>

创建main.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
    <h2>
            获取参数username的值:【${param.username}】
    </h2><br/><br/>
      <table>
          <tr style="height:100px;">
             <td style="background:pink;"></td>
          </tr>
          <tr>
             <td>
                <!-- 测试import标签 -->
                <c:import url="http://www.baidu.com" charEncoding="utf-8"></c:import>
             </td>
          </tr>
      </table><br/>   
</body>
</html>

启动web服务器,访问:
在这里插入图片描述
点击最下面的链接:
在这里插入图片描述
修改redirect.jsp,如下:
在这里插入图片描述
启动web服务器,访问,重新点击链接:
在这里插入图片描述
成功跳到了外链地址。修改redirect.jsp,如下:
在这里插入图片描述
修改main.jsp,如下:
在这里插入图片描述
启动web服务器,访问:
在这里插入图片描述
点击最下面的链接,然后访问:http://localhost:8080/jstl-demo/main.jsp
在这里插入图片描述
点击链接:
在这里插入图片描述
成功跳往百度首页。

5.11.2、自定义标签

(1)自定义标签的组成结构

一个自定义的标签库由以下三部分组成:

  • 标签处理程序:它是标签功能的一个实现类。标签的功能在实现类里体现,是标签的核心部分。
  • 标签库描述符:后缀为.tld的文件,它告诉JSP标签引擎如何解析标签。
  • JSP文件:应用标签,呈现标签功能。

(2)自定义标签库的开发过程

开发一个自定义标签库大概遵循以下4个步骤:

  • 确定标签的功能。
  • 编写实现标签的功能类。
  • 编写标签库描述文件(必须放在WEB_INF或子目录下)。
  • 在页面中测试标签。

(3)测试

现开发一个自定义标签,实现日期的格式转换。首先需要编写标签处理程序。自定义类实现Tag、IterationTag、BodyTag接口或继承TagSupport类、BodyTagSupport类。定义实现标签功能的必要方法,实现标签处理业务处理功能。然后按照规范编写标签库描述符来描述如何实现标签功能(.tld),最后在jsp中测试标签功能。

标签处理程序

定义一个类直接或间接继承TagSupport类BodyTagSupport类即可。如下:

public class DateFormatTag extends TagSupport{

    private static final long serialVersionUID = -898298932394692501L;

}

说明:一般开发自定义标签只要继承TagSupport类或者是BodyTagSupport类就
可以了,不必实现接口,在需要处理带主体内容时继承BodyTagSupport否则继承TagSupport。

javax.servlet.jsp.tagext.TagSupport类部分源码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
javax.servlet.jsp.tagext.IterationTag接口源码如下:
在这里插入图片描述
在这里插入图片描述
这个接口只有一个常量和一个抽象方法。

javax.servlet.jsp.tagext.Tag接口源码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
javax.servlet.jsp.tagext.JspTag接口如下:
在这里插入图片描述
以上类和接口中的常量说明:

  • EVAL_BODY_INCLUDE:包含主体内容,告诉jsp引擎计算标签主体内容。
  • EVAL_PAGE:计算剩余页面部分。
  • SKIP_BODY:跳过标签主体内容。
  • SKIP_PAGE:跳过页面的剩余部分。
  • EVAL_BODY_AGAIN:有条件地循环执行doAfterBody方法从而计算标签主体部分。
  • EVAL_BODY_BUFFERED:返回此值时jsp引擎将生成标签主体内容并封装在BodyContent对象中缓存。

主要方法:

  • doStartTag():jsp容器遇到开始标签时调用此方法,返回值有SKIP_BODY和
    EVAL_BODY_INCLUDE。
  • doAfterBody():如果标签有主体内容,在执行完标签主体后有条件地调用此方
    法,返回值有SKIP_BODY和EVAL_BODY_AGAIN。
  • doEndTag():sp容器在遇到结束标签时调用此方法,返回值EVAL_PAGE或
    SKIP_PAGE。
  • release():标签使用完后标签引擎通过调用此方法释放对象占有的系统资源。
  • setBodyContent():当标签需要处理主体内容时,执行此方法,无返回值。
  • doInitBody():在setBodyContent方法执行后jsp引擎第一次处理主体内容时通过此方法对主体内容进行初始化。

完整的标签处理程序代码如下:

/*
 * 自定义标签类
 * 继承TagSupport
 * 功能实现日期的格式转换
 */
public class DateFormatTag extends TagSupport {

    private static final long serialVersionUID = -898298932394692501L;

    // 给定日期
    private Date date;

    // 转换的格式
    private String format;

    // 定义4个常量
    private static final String DATE_BASE = "date";
    private static final String DATE_FULL = "datefull";
    private static final String TIME = "time";
    private static final String TIME_FULL = "timefull";

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    // 重写doEndTag方法
    @Override
    public int doEndTag() throws JspException {
        PrintWriter printWriter = null;
        try {
            // 由响应对象获取输出流
            printWriter = this.pageContext.getResponse().getWriter();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (format == null || format.equals("")) {
            String dateStr = new SimpleDateFormat("yy/MM/dd").format(date);
            printWriter.write("现在时间是:" + dateStr + "<br/>");
        } else if (format.equals(DATE_BASE)) {
            String dateStr = new SimpleDateFormat("yyyy/MM/dd").format(date);
            printWriter.write("现在时间是:" + dateStr + "<br/>");
        } else if (format.equals(DATE_FULL)) {
            String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
            printWriter.write("现在时间是:" + dateStr + "<br/>");
        } else if (format.equals(TIME)) {
            String dateStr = new SimpleDateFormat("h:m:s").format(date);
            printWriter.write("现在时间是:" + dateStr + "<br/>");
        } else if (format.equals(TIME_FULL)) {
            String dateStr = new SimpleDateFormat("HH:mm:ss").format(date);
            printWriter.write("现在时间是:" + dateStr + "<br/>");
        } else {
            try {
                throw new Exception("format属性无效,不支持该格式转换!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return super.doEndTag();
    }

}

然后定义标签库的描述符,在WEB-INF下创建myTag目录,目录下新建customTag.tld文件,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib
  PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
  "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
  
  <!-- 此文件作为标签库的描述文件,必须放在WEB-INF下 -->
  <taglib>
     <tlib-version>1.0</tlib-version>
     <jsp-version>1.2</jsp-version>
     <short-name>custom_rt</short-name>
     <uri>http://java.sun.com/jstl/core_rt</uri>
     <display-name>CustomTag</display-name>
     <description>自定义标签</description>
     
     <validator>
        <validator-class>
           org.apache.taglibs.standard.tlv.JstlCoreTLV
        </validator-class>
        <description>Provides core validation features for JSTL tags.</description>
     </validator>
     
     <!-- 以下为关键内容 -->
     <tag>
        <!-- 标签名 -->
        <name>date</name>
        <!-- 映射的标签功能程序类 -->
        <tag-class>com.ycz.custom.DateFormatTag</tag-class>
        <body-content>empty</body-content>
        <description>将Date日期格式转换为String类型字符串</description>
        
        <!-- 为标签自定义属性 -->
        <!-- 第一个属性,日期 -->
        <attribute>
           <name>date</name>
           <required>true</required>
           <rtexprvalue>true</rtexprvalue>
           <type>java.util.Date</type>
        </attribute>
        
        <!-- 第二个属性,转换后的String格式 -->
        <attribute>
           <name>format</name>
           <required>true</required>
           <rtexprvalue>true</rtexprvalue>
           <type>java.lang.String</type>
        </attribute>
     </tag>
  </taglib>

web.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
	http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>customTag</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>

index.jsp如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.util.Date"%>
    <!-- 引入自定义标签库 -->
    <%@ taglib prefix="t" uri="WEB-INF/myTag/customTag.tld"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
 <h3>
  <t:date format="date" date="<%= new Date() %>"/><br/>
  <t:date format="datefull" date="<%= new Date() %>"/><br/>
  <t:date format="time" date="<%= new Date() %>"/><br/>
  <t:date format="timefull" date="<%= new Date() %>"/>
 </h3>
</body>
</html>

启动web服务器,访问:http://localhost:8080/customTag/
在这里插入图片描述
日期确实是按指定格式转换的。

6、Servlet

6.1、Servlet概念

Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。

狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

Servlet运行在jsp/Servlet容器上,(比如Tomcat服务器)被用户第一次请求由服务器负责创建的对象,即是Servlet对象。Servlet对象通常作为用户请求的标准处理器,JavaEE的标准组件。一个Servlet对象只会被创建一次。

6.2、Servlet组件

JavaEE标准规定,任何实现了javax.servlet.Servlet接口或继承了javax.servlet.http.HttpServlet抽象类的Java类都是能够作为用户请求处理的Java web组件。通常多数java web应用都是基于http协议的,因此开发一个Servlet一般提倡直接继承HttpServlet。

javax.servlet.Servlet接口的源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
关键的方法是init()、service()、destory()。

javax.servlet.http.HttpServlet部分源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
service方法是核心方法。HttpServlet直接继承自GenericServlet类,GenericServlet类部分源码如下:
在这里插入图片描述
这个类直接实现了Servlet接口,相当于HttpServlet这个子类也实现了Servlet接口。因此,如果想要开发一个可以处理用户请求的Servlet组件,可以直接继承HttpServlet类,重写关键方法就行了,如下:

/**
 * 自定义Servlet组件
 * 继承HttpServlet类
 */
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {
    
	private static final long serialVersionUID = 1L;


	// service方法是核心方法,doGet和doPost等方法都受service方法驱动
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
	}

	// 重写doGet方法
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	// 重写doPost方法
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

6.3、Servlet组件的配置

Servlet组件创建完毕后,还需要进行配置,在web.xml中配置,如下:

	<!-- Servlet组件的配置 -->
	<servlet>
	   <!-- 组件名称 -->
	   <servlet-name>myServlet</servlet-name>
	   <!-- 映射的类 -->
	   <servlet-class>com.ycz.servlet.MyServlet</servlet-class>
	</servlet>
	
	<!-- Servlet的映射配置 -->
	<servlet-mapping>
	   <!-- 映射的名称 -->
	   <servlet-name>myServlet</servlet-name>
	   <!-- 映射的url -->
	   <url-pattern>/myServlet.do</url-pattern>
	</servlet-mapping>

如上,路径为/myServlet.do的url,会映射到myServlet这个名称,而这个名称映射到com.ycz.servlet.MyServlet这个Servlet组件,即/myServlet.do这个请求会到达MyServlet这个类,交由其处理。注意,如果映射路径配置错误,会找不到Servlet组件,可能映射错误。

6.4、例子

通过一个简单的例子测试。

定义一个Servlet组件:

/**
 * 自定义Servlet组件 继承HttpServlet类
 */
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    // service方法是核心方法,doGet和doPost等方法都受service方法驱动
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 调用父类的service方法
        super.service(request, response);
    }

    // 重写doGet方法
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("调用doGet方法!");
    }

    // 重写doPost方法
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("调用doPost方法!");
    }

}

Servlet组件配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
	http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>servlet-demo</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	
	<!-- Servlet组件的配置 -->
	<servlet>
	   <servlet-name>myServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.MyServlet</servlet-class>
	</servlet>
	
	<!-- Servlet的映射配置 -->
	<servlet-mapping>
	   <servlet-name>myServlet</servlet-name>
	   <url-pattern>/myServlet.do</url-pattern>
	</servlet-mapping>
</web-app>

index.jsp页面如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
   <!-- a标签默认是get请求 -->
   <a href="myServlet.do">请求Servlet处理</a>
   <!-- 表单提交是post请求 -->
   <form action="myServlet.do" method="post">
       <input type="submit" value="提交"/>
   </form>
</body>
</html>

启动web服务器,访问:http://localhost:8080/servlet-demo/
在这里插入图片描述
点击链接:
在这里插入图片描述
控制台:
在这里插入图片描述
说明走了MyServlet类里面的doGet方法。点击提交:
在这里插入图片描述
在这里插入图片描述
控制台:
在这里插入图片描述
说明走了MyServlet类里面的doPost方法。

这个例子可以看到,http请求被Servlet处理器处理了,并且get请求会走doGet方法,post请求会走doPost方法。

6.5、Servlet的生命周期

Servlet运行在Servlet容器中,其生命周期由容器来管理。Servlet的生命周期通过javax.servlet.Servlet接口中的init()、service()和destroy()方法来表示。Servlet生命周期可被定义为从创建直到销毁的整个过程,大概可以分为4个阶段:

  • 新建阶段:加载和实例化Servlet对象。
  • 初始化阶段:初始化并调用init()方法。
  • 服务阶段:处理请求并响应,调用service()方法。
  • 销毁阶段:销毁Servlet前,调用destory()方法。

(1)新建阶段

这个阶段主要负责加载和实例化Servlet对象。Servlet容器负责加载和实例化Servlet。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,它必须要知道所需的Servlet类在什么位置,Servlet容器可以从本地文件系统、远程文件系统或者其他的网络服务中通过类加载器加载Servlet类。

成功加载后,容器创建Servlet的实例。容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(不带参数的构造方法),所以我们在编写Servlet类的时候,不应该提供带参数的构造方法。

(2)初始化阶段

在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次。

在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。在初始化期间,如果发生错误,Servlet实例可以抛出ServletException异常。

初始化参数可以在web.xml中进行配置:

	<!-- Servlet组件的配置 -->
	<servlet>
	   <servlet-name>myServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.MyServlet</servlet-class>
	   <!-- 配置Servlet初始化参数 -->
	   <init-param>
	      <param-name>username</param-name>
	      <param-value>yanchengzhi</param-value>
	   </init-param>
	   <init-param>
	      <param-name>password</param-name>
	      <param-value>ycz123456</param-value>
	   </init-param>
	</servlet>

获取初始化参数如下:

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取ServletConfig对象
        ServletConfig servletConfig = this.getServletConfig();
        // 获取Servlet初始化参数
        String username = servletConfig.getInitParameter("username");
        System.out.println("参数username的值是:" + username);
        String password = servletConfig.getInitParameter("password");
        System.out.println("参数password的值是:" + password);
    }

控制台:
在这里插入图片描述
初始化参数和参数值成功获取。

(3)服务阶段

这个阶段主要负责对请求的处理和响应。Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。service()方法为Servlet的核心方法,客户端的业务逻辑应该在该方法内执行。

在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。在service()方法执行期间,如果发生错误,Servlet实例可以抛出ServletException异常。注意,service()方法可以进行多次调用。

(4)销毁阶段

当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。

小结:在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。

6.6、Servlet的运行过程

运行过程与生命周期类似,Servlet的运行过程可以概括为以下5步:

  • Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果Servlet实例对象还未创建,那么继续第二步。如果已经创建了则直接跳到第四步。
  • 装载并创建该Servlet的一个实例对象。
  • 调用Servlet实例对象的init()方法完成初始化。
  • 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去,处理用户请求并响应结果。
  • WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法销毁Servlet对象。

6.7、HttpServletRequest对象

HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。在用户成功请求后由服务器创建并作为参数传递给service方法。HttpServletRequest是处理Http请求的请求对象,它是ServletRequest的子接口。

ServletRequest接口的部分源码:
在这里插入图片描述
在这里插入图片描述
HttpServletRequest接口的部分源码:
在这里插入图片描述
在这里插入图片描述
该接口提供了很多方法,常用方法如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
测试:

@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    // service方法是核心方法,doGet和doPost等方法都受service方法驱动
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 从请求对象中获取参数
        System.out.println("字符编码:" + request.getCharacterEncoding());
        System.out.println("MIME类型:" + request.getContentType());
        System.out.println("上下文URL:" + request.getContextPath());
        System.out.println("请求方法:" + request.getMethod());
        System.out.println("协议名称:" + request.getProtocol());
        System.out.println("主机地址:" + request.getRemoteAddr());
        System.out.println("请求地址:" + request.getRequestURI());
    }

}

在这里插入图片描述

6.8、HttpServletResponse对象

Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象和代表响应的response对象。HttpServletResponse对象代表服务器的响应。这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。

HttpServletResponse接口的部分源码:
在这里插入图片描述
它继承了ServletResponse接口,该接口的部分源码:
在这里插入图片描述
HttpServletResponse接口提供的常用方法如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
测试:

@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 设置响应头
        response.setHeader("content-type", "text/html;charset=UTF-8");
        // 设置编码
        response.setCharacterEncoding("UTF-8");
        // 获取PrintWriter对象
        PrintWriter printWriter = response.getWriter();
        // 向浏览器写消息
        printWriter.write("状态码:" + response.getStatus() + "<br/>");
        printWriter.write("请求已处理!");
        // 强制刷新
        printWriter.flush();
        // 关闭流
        printWriter.close();
    }

}

在这里插入图片描述
点击链接:
在这里插入图片描述

向浏览器写入消息,说明响应成功。

6.9、ServletContext对象

ServletContext是一个全局的储存信息的空间,服务器开始就存在,服务器关闭才释放。可以把ServletContext当成一个公用的空间,可以被所有的用户访问,WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext,它代表当前Web应用,并且它被所有客户端共享。

由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。公共聊天室就会用到它。当web应用关闭、Tomcat关闭或者Web应用reload的时候,ServletContext对象会被销毁。可以通过getServletContext()方法来获取到ServletContext对象,这个对象代表着整个应用。和JSP隐式对象中的application对象类似。

ServletContext接口的部分源码如下:
在这里插入图片描述
测试:

@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取ServletContext上下文对象
        ServletContext servletContext = this.getServletContext();
        System.out.println("上下文对象:" + servletContext);
        String path = servletContext.getContextPath();
        System.out.println("上下文应用的路径:" + path);
    }

}

在这里插入图片描述
ServletContext对象的应用很多,如下:

  • 多个Servlet可以通过ServletContext对象来实现数据间的共享。
  • 实现Servlet的请求转发。
  • 获取Web应用的初始化参数。
  • 利用ServletContext对象读取资源文件(比如properties文件) 。

6.10、例子

创建实体pojo,如下:

package com.ycz.pojo;

public class Person {

    private String name;

    private String sex;
    
    public Person() {
        
    }
    
    public Person(String name,String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "{name:" + this.getName() + ",sex:" + this.getSex() + "}";
    }
      
}

创建index.jsp页面,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
    <form action="testServlet.do" method="post">
    	<label>姓名:</label>
    	<input type="text" name="name"/><br>
    	<label>性别:</label>
    	<input type="radio" name="sex" value="1" checked/><input type="radio" name="sex" value="0" /><br>
    	<input type="submit" value="提交" />
    </form>
</body>
</html>

创建Servlet组件,如下:

@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 设置请求对象请求体的编码(针对表单)
        request.setCharacterEncoding("utf-8");
        // 获取参数
        String name = request.getParameter("name");
        String sex = request.getParameter("sex").equals("0") ? "女" : "男";
        // 构建对象
        Person p = new Person(name, sex);
        // 存进request域中
        request.setAttribute("person", p);
        // 获取发报机
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("show.jsp");
        // 请求转发
        requestDispatcher.forward(request, response);
    }

}

Servlet组件配置,web.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
	http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>servlet-demo</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	
	<!-- Servlet组件配置 -->
	<servlet>
	   <servlet-name>testServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.TestServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>testServlet</servlet-name>
	   <url-pattern>/testServlet.do</url-pattern>
	</servlet-mapping>
</web-app>

创建show.jsp页面,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="com.ycz.pojo.Person"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>信息展示</title>
</head>
   <%!
      Person p = null;
   %>
<body bgcolor="pink">
    <%
      // request域中取出
      p = (Person)request.getAttribute("person");
    %>
    
    <h2>用户信息:<br/>
               姓名: <%= p.getName() %><br/>
               性别:<%= p.getSex() %>
    </h2>
</body>
</html>

启动web服务器,访问:http://localhost:8080/servlet-demo
在这里插入图片描述
填写表单,然后提交:
在这里插入图片描述
在这里插入图片描述
跳转到了show.jsp页面,并且信息展示正确。

index.jsp中添加一个链接:
在这里插入图片描述
然后创建新的Servlet:

@WebServlet("/ResponseServlet")
public class ResponseServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取参数
        String address = request.getParameter("arg");
        // 存到request域中
        request.setAttribute("address", address);
        // 设置响应编码
        response.setCharacterEncoding("UTF-8");
        // 获取发报机
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("response.jsp");
        // 请求转发
        requestDispatcher.forward(request, response);
    }
}

配置Servlet:

	<!-- 配置Servlet -->
	<servlet>
	   <servlet-name>responseServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.ResponseServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>responseServlet</servlet-name>
	   <url-pattern>/responseServlet.do</url-pattern>
	</servlet-mapping>

创建response.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>信息展示</title>
</head>
<body bgcolor="orange">
    <%
      // request域中取出
      String address = (String)request.getAttribute("address");
    %>
    
    <h2>我要去的地方是:【<%= address %>】</h2>
</body>
</html>

启动web服务器,访问:http://localhost:8080/servlet-demo
在这里插入图片描述
点击链接:
在这里插入图片描述
跳转到了response.jsp页面,参数获取成功。

index.jsp中添加一个链接:
在这里插入图片描述
创建一个新的Servlet:

@WebServlet("/ContextServlet")
public class ContextServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取ServletContext对象,它代表的是应用,一个应用中有且只有一个
        ServletContext servletContext = this.getServletContext();
        // context域中存信息
        servletContext.setAttribute("name", "yanchengzhi");
        servletContext.setAttribute("sex", "男");
        servletContext.setAttribute("age", 25);
        servletContext.setAttribute("address", "湖北");
        servletContext.setAttribute("like", "uuuu");
        // 重定向
        response.sendRedirect("app.jsp");
    }

}

配置Servlet组件:

	<!-- 配置Servlet组件 -->
	<servlet>
	   <servlet-name>contextServlet</servlet-name>
	   <servlet-class>com.ycz.servlet.ContextServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>contextServlet</servlet-name>
	   <url-pattern>/contextServlet.do</url-pattern>
	</servlet-mapping>

创建app.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>信息展示</title>
</head>
<body bgcolor="orange">
    <%
      // application域取出,该域对象在整个应用中有效,所有页面中都可使用
      String name = application.getAttribute("name").toString();
      String sex = application.getAttribute("sex").toString();
      String age = application.getAttribute("age").toString();
      String address = application.getAttribute("address").toString();
      String like = application.getAttribute("like").toString();
    %>
    
    <h2>个人信息:<br/>
            姓名:【<%= name %>】<br/>
            性别:【<%= sex %>】<br/>
            年龄:【<%= age %>】<br/>
            住址:【<%= address %>】<br/>
            喜欢的:【<%= like %>】<br/>
    </h2>
</body>
</html>

启动web服务器,访问:http://localhost:8080/servlet-demo
在这里插入图片描述
点击下面的链接:
在这里插入图片描述
重定向到了app.jsp页面,并且从ServletContext域中取出了信息。

6.11、HttpSession对象

javax.servlet.http.HttpSession接口是Java Web会话的规范。HttpSession对象代表一个web交互用户对象。HttpSession对象只能由HttpServletRequest对象通过getSession方法来获取。

该接口提供了一种跨多个页面请求或访问网站时识别用户以及存储有关用户信息的方式。Servlet容器使用这个接口来创建一个HTTP客户端和HTTP服务器之间的session会话。会话持续一个指定的时间段,跨多个连接或页面请求。

HttpSession对象对于每个用户来说都是私有的,对于它们之间数据是不透明的。HttpSession域中只应该存储与当前用户有关联的信息,通常只存储当前用户对象。

HttpSession接口的部分源码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
提供的方法如下:
在这里插入图片描述
在这里插入图片描述

HttpSession对象的生命周期

HttpSession对象有过期时间,在完成最后一次交互后,鼠标在浏览器中不会动了,也不会向服务器发出请求,session过期后便不存在了,服务器会自动清除掉过期的session。

可以在web.xml中配置session的生命周期:

  <session-config>
      <session-timeout>30</session-timeout>
  </session-config>

也可以在程序中以代码设置:

session.setMaxlnactivelnterval(60*3)

HttpSession对象的监听

用户会话在创建成功后或用户退出系统时,可能需要做一些通用的操作,使用监听器可以实现对HttpSession对象进行监听。

HttpSessionListener接口是对HttpSession实现监听的接口规范,实现此接口的类可做为HttpSession的监听器。一般要重写sessionCreated()和sessionDestroyed()方法。

HttpSessionListener接口的源码如下:
在这里插入图片描述
这个接口就两个方法。实现了这个接口的类可以作为session的监听器,如下:

/*
 * session对象的监听器
 */
public class HttpSessionListenerImpl implements HttpSessionListener{

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // TODO Auto-generated method stub
        
    }
}

监听器创建完成后,还需要在web.xml中进行注册,如下:

	<!-- 注册session监听器 -->
	<listener>
	   <listener-class>
	      com.ycz.listener.HttpSessionListenerImpl
	   </listener-class>
	</listener>

6.12、用户登录实现

使用Servlet+JSP+MySQL完成一个用户登录的例子,需要连接数据库。

(1)依赖

导入两个依赖到web项目,如下:
在这里插入图片描述
jstl依赖和mysql连接驱动包。

(2)配置文件

创建conf包,包下创建数据库连接的配置文件db_mysqlconf.properties,如下:

## 数据库连接配置信息
jdbc_url=jdbc:mysql://rm-m5e130nm7h37n6v982o.mysql.rds.aliyuncs.com:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_username=xxxxxx
jdbc_password=xxxxxx

(3)工具类

创建utils包,包下创建Env类,这个类用来加载数据库配置文件信息,如下:

/*
 * 工具类
 * 加载数据库连接配置信息
 */
public class Env extends Properties {

    private static final long serialVersionUID = 4182699870347430944L;

    // 定义4个常量
    public static final String JDBC_URL;
    public static final String JDBC_DRIVER;
    public static final String JDBC_USERNAME;
    public static final String JDBC_PASSWORD;

    // 数据库配置文件所在位置
    private static final String DB_CONF = "com/ycz/conf/db_mysqlconf.properties";

    // 此类实例
    private static Env env;
    
    // 静态代码块集中赋值
    static {
        // 实例化此类对象
        getEnv();
        // 获取属性文件的输入流对象
        InputStream in = Env.class.getClassLoader().getResourceAsStream(DB_CONF);
        try {
            // 加载输入流
            env.load(in);
            // 流关闭
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        JDBC_URL = env.getProperty("jdbc_url");
        JDBC_DRIVER = env.getProperty("jdbc_driver");
        JDBC_USERNAME = env.getProperty("jdbc_username");
        JDBC_PASSWORD = env.getProperty("jdbc_password");
    }

    // 单例模式
    private static void getEnv() {
        if (env == null) {
            env = new Env();
        }
    }

}

测试能否正确获取属性文件配置信息:

public class Test {
    
    public static void main(String[] args) {
        System.out.println(Env.JDBC_URL);
        System.out.println(Env.JDBC_DRIVER);
        System.out.println(Env.JDBC_USERNAME);
        System.out.println(Env.JDBC_PASSWORD);
    }

}

在这里插入图片描述
没问题。

utils包下再创建一个工具类DataSourceManager,用来管理数据源信息:

/*
 * 数据源管理组件
 */
public class DataSourceManager {

    // 获取Connection连接
    public static Connection getConnection() {
        Connection connection = null;
        try {
            // 加载驱动
            Class.forName(Env.JDBC_DRIVER);
            // 获取连接
            connection = DriverManager.getConnection(Env.JDBC_URL, Env.JDBC_USERNAME, Env.JDBC_PASSWORD);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }

    // 关闭Connection连接
    public static void closeConnectoin(Connection connection) {
        try {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 关闭Statement接口
    public static void closeStatement(Statement statement) {
        try {
            if (statement != null && !statement.isClosed()) {
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 关闭ResultSet接口
    public static void closeResultSet(ResultSet resultSet) {
        try {
            if (resultSet != null && !resultSet.isClosed()) {
                resultSet.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

测试能否连接到数据库:

    public static void main(String[] args) {
        Connection connection = DataSourceManager.getConnection();
        if (connection != null) {
            System.out.println("数据库连接成功!");
            // 手动关闭连接
            DataSourceManager.closeConnectoin(connection);
            System.out.println("数据库连接已经手动关闭!");
        } else {
            System.out.println("数据库连接失败!");
        }
    }

在这里插入图片描述
可以连接,没问题。

(4)实体pojo

创建pojo包,在此包下创建User类:

public class User {

    private Integer id;

    private String username;

    private String password;

    public User() {

    }

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

(5)dao层

dao层为数据处理层,与数据库发生交互。创建dao包,包下创建UserDao接口和UserDaoImpl实现类,如下:

public interface UserDao {
    
    User validateLogin(User loginUser);

}
public class UserDaoImpl implements UserDao{

    @Override
    public User validateLogin(User loginUser) {
        // 查询的sql语句
        String sql = "select * from user where username=? and password=?";
        // 获取数据库连接
        Connection connection = DataSourceManager.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        User user = null;
        try {
            // 获取PreparedStatement对象
            ps = connection.prepareStatement(sql);
            // 占位符替换
            ps.setString(1, loginUser.getUsername());
            ps.setString(2, loginUser.getPassword());
            // 执行查询,获取结果集
            rs = ps.executeQuery();
            while(rs.next()) {
                // 从结果集中获取查询记录,封装为对象
                user = new User();
                user.setUsername(rs.getString("username"));
                user.setPassword(rs.getString("password"));
                
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭
            DataSourceManager.closeConnectoin(connection);
            DataSourceManager.closeStatement(ps);
            DataSourceManager.closeResultSet(rs);
        }
        return user;
    }

}

(6)Service层

service层为服务层,调用持久层dao。创建service包,包下创建UserService接口和UserServiceImpl实现类,如下:

public interface UserService {
    
    User validateLogin(User loginUser);

}
public class UserServiceImpl implements UserService {

    @Override
    public User validateLogin(User loginUser) {
        UserDao userDao = new UserDaoImpl();
        return userDao.validateLogin(loginUser);
    }

}

(7)创建登录页面

创建登录页面login.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
   <form action="userServlet.do" method="post">
   	  <label>用户名:</label>
   	  <input type="text" name="username" /><br/>
   	  <label>登录密码:</label>
   	  <input type="password" name="password" /><br/><br/>
   	  <input type="submit" value="登录" />
   </form>
   
   <%
      String mess = request.getParameter("loginfailed");
      if(mess!=null && !mess.equals("") && mess.equals("error")){
   %>
   <label style="color:red">系统提示:用户名不存在或密码错误,登录失败!</label>
   <%
      }
   %>
</body>
</html>

(8)创建Servlet

创建Servlet组件处理用户的登录请求,创建controller包,包下创建UserServlet,如下:

@WebServlet("/UserServlet")
public class UserServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 数据库连接
        Connection connection = DataSourceManager.getConnection();
        try {
            if (connection != null && !connection.isClosed()) {
                System.out.println("已连接MySQL数据库!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("开始执行登录验证!");
        // 请求对象的请求体设置编码
        request.setCharacterEncoding("utf-8");
        // 获取参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        // 服务层组件
        UserService userService = new UserServiceImpl();
        User currentUser = userService.validateLogin(user);
        if(currentUser!=null) {
            System.out.println("用户【" + currentUser.getUsername() +"】登录成功!");
            // 获取session对象
            HttpSession session = request.getSession();
            if(session.isNew()) {
                System.out.println("用户【" + currentUser.getUsername()+ "】第一次访问!");
            } 
            // 用户存到session域中
            session.setAttribute("current", currentUser);
            // 重定向到其他页面
            response.sendRedirect("worker.jsp");
        } else {
            System.out.println("登录失败!");
            response.sendRedirect("login.jsp?loginfailed=error");
        }
    }

}

在web.xml中配置这个Servlet组件:

	<!-- 配置Servlet -->
	<servlet>
	   <servlet-name>userServlet</servlet-name>
	   <servlet-class>com.ycz.controller.UserServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>userServlet</servlet-name>
	   <url-pattern>/userServlet.do</url-pattern>
	</servlet-mapping>

(9)创建公共页面

在资源目录下创建common文件夹,该文件夹下创建几个公共页面。

top.jsp如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="com.ycz.pojo.User"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>头部部分</title>
<!-- 引入样式表 -->
<link type="text/css" rel="stylesheet" href="../css/base.css">
</head>
<body>
    <div style="width:100%;height:120px;margin-left:0px;margin-top:0px;background-color:#eeeeff;" >
       <a href="../worker.jsp">
           <img alt="" src="../images/top3.png" style="width:100%;height:120px;">
       </a>
    </div>
    <div class="f_0">
       	<label class="hand">
		快 速 留 言&nbsp;|&nbsp;
		个 人 中 心&nbsp;|&nbsp;
		留 言  管 理&nbsp;|&nbsp;
		总 部 门 户&nbsp;|&nbsp;
		会 议 信 息&nbsp;|&nbsp;
		资 源 中 心&nbsp;&nbsp;
	    </label>  
	    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
	    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
	    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
	    &nbsp;&nbsp;	         
    </div>  
    <%
       Object obj = session.getAttribute("current");
       User user = null;
       // session域中有这个对象,向下转型
       if(obj!=null){
           user = (User)obj;
       }
    %>
    <label class="hand">
               当前用户:【<%= user.getUsername() %>】
               当前位置:首页
       <%
         // 获取在线人数
         Object num = application.getAttribute("onlineNumber");
         if(num!=null){             
       %>
       
                  在线人数:<%= num %>
                  退出
        <%
         }
        %>
	     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
         &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;                 
    </label>
</body>
</html>

left.jsp如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>左边侧栏部分</title>
</head>
<div style="background-color: skyblue">
	<div style="text-align:left;background-color:#fff;height:100%" class="f_0 f_0_2 hand">
		<img alt="" src="../images/个人中心.png" style="width:100%;height:40px">
	</div>
	<hr color="#fff">
	
	<div style="text-align:center;background-color:#fff;height:100%" class="f_0 f_0_2 hand">
		<a href="../meeting.jsp">
		  <img alt="" src="../images/会议信息.png" style="width:100%;height:40px">
		</a>
	</div>
	<hr color="#fff">
	
	<div style="text-align:center;background-color:#fff;height:100%" class="f_0 f_0_2 hand">
		<img alt="" src="../images/留言管理.png" style="width:100%;height:40px">
	</div>
	<hr color="#fff">
	
	<div style="text-align:center;background-color:#fff;height:100%" class="f_0 f_0_2 hand">
		<img alt="" src="../images/组织部门.png" style="width:100%;height:40px">
	</div>
	<hr color="#fff">
	
	<div style="text-align:center;background-color:#fff;height:100%" class="f_0 f_0_2 hand">
		<img alt="" src="../images/产品管理.png" style="width:100%;height:40px">
	</div>
	<hr color="#fff">
	
	<div style="text-align:center;background-color:#fff;height:100%" class="f_0 f_0_2 hand">
		<img alt="" src="../images/销售管理.png" style="width:100%;height:40px">
	</div>
	<hr color="#fff">
	
	<div style="text-align:center;background-color:#fff;height:100%" class="f_0 f_0_2 hand">
		<img alt="" src="../images/系统管理.png" style="width:100%;height:40px">
	</div>
</div>	
<br>
</html>

foot.jsp如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>页脚部分</title>
</head>
<body>
   <div style="width: 100%;text-align: center;vertical-align: middle;">
       <hr size="3" color="#ffffff">
	   &copy;&nbsp;&nbsp;2019&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
</body>
</html>

welcome.html如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎页面</title>
</head>
    <div style="width:100%;height:430px;margin-top:0px;">
       <img src="../images/合作.jpg" alt="合作" style="wdith:100%;height:100%;">
    </div>
</html>

(10)其他页面

创建meeting.jsp页面,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>会议页面</title>
</head>
   <%
     String location = "会议管理";
     Object obj = session.getAttribute("current");
     if(obj==null){
   %>
      <jsp:forward page="login.jsp"></jsp:forward>
   <%
     }
   %>
<body>
  <table style="width:100%;">
       <tr >
          <td colspan="2">     
          </td>
       </tr>
       <tr>
          <td>
             <!-- include指令静态包含其他页面 -->
             <%@ include file = "/common/left.jsp" %>
          </td>
          <td style="width:85%;background-color:skyblue;height:400px;"></td>
       </tr>
       <tr>
          <td colspan="2">
              <%@ include file = "/common/foot.jsp" %>
          </td>
       </tr>
  </table>
</body>
</html>

创建worker.jsp页面,这个是主页面,用户登录成功后跳到worker.jsp主页面。如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!-- 引入jstl标签库 -->
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页面</title>
</head>
   <!-- session域中没有对象,则重定向到登录页面 -->
   <c:if test="${sessionScope.current eq null}">
      <c:redirect url="login.jsp"></c:redirect>
   </c:if>
<body>
<frameset rows="145px,*" border="0" style="margin-left:100px;margin-top:0px">
	<frame src="/common/top.jsp" scrolling="no" style="margin-left:500px"/>
	<frameset cols="15%,*" border="0" style="margin-left:500px;margin-top:20px">
		<frame src="/common/left.jsp" scrolling="no" style="margin-left:20px"/>
		<frame src="/common/welcome.html" scrolling="no" style="margin-left:20px"/>
	</frameset>
</frameset>
</body>
</html>

(11)创建配置session监听器

创建listener包,包下创建session监听器HttpSessionListenerImpl,如下:

/*
 * session对象的监听器
 *    用于统计在线人数
 */
public class HttpSessionListenerImpl implements HttpSessionListener{

    // 创建
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        // 用户进入时,获取session对象
        HttpSession session = se.getSession();
        // 获取上下文对象
        ServletContext context = session.getServletContext();
        Object obj = context.getAttribute("onlineNumber");
        // 为空则是第一个用户
        if(obj==null){
            context.setAttribute("onlineNumber", new Integer(1));
        } else { // 不为空,往上累加
          context.setAttribute("onlineNumber", new Integer(obj.toString()) + 1);   
        }
    }

    // 销毁
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // 用户退出时
        HttpSession session = se.getSession();
        ServletContext context = session.getServletContext();
        Object obj = context.getAttribute("onlineNumber");
        // 用户退出,人数减1
        context.setAttribute("onlineNumber", new Integer(obj.toString()) - 1);
        
    }

}

在web.xml中配置该监听器:

	<!-- 注册session监听器 -->
	<listener>
	   <listener-class>
	      com.ycz.listener.HttpSessionListenerImpl
	   </listener-class>
	</listener>

(12)测试

启动web服务器,访问:http://localhost:8080/servlet-demo
在这里插入图片描述
输入错误的账号密码:
在这里插入图片描述
在这里插入图片描述
输入正确的账号密码:
在这里插入图片描述
登录:
在这里插入图片描述
在这里插入图片描述
换另一个浏览器,登录:
在这里插入图片描述
在这里插入图片描述
现在的在线人数是2人,退出浏览器,再打开,再登录:
在这里插入图片描述
现在在线人数是3人。可以看到,web服务器不关闭的前提下,只要有用户登录,那么人数会往上加,也有一个问题,那就是用户可以重复登录,后面会用拦截器处理用户的重复登录问题。

6.13、Cookies

Cookie,有时也用其复数形式Cookies,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。定义于RFC2109和2965都已废弃,最新取代的规范是RFC6265。

Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie名称和值可以由服务器端开发自己定义,对于JSP而言也可以直接写入jsessionid,这样服务器可以知道该用户是否合法用户以及是否需要重新登录等。

服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。Cookies最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookies的功用。另一个重要应用场合是“购物车”之类处理。用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookies,以便在最后付款时提取信息。用户可以改变浏览器的设置,以使用或者禁用Cookies。

一般情况下,在公共的计算机上设置Cookie并不安全。因为Cookie很多情况下用于保存用户名及密码信息,避免了二次登录时再次输入,从Cookie中取出信息填充相应区域即可。私人计算机上设置Cookie还是很方便的,可以使用户快速登录某个软件,比如QQ。因为或多或少存在着安全问题,所以对于Cookie还是谨慎使用。

(1)Cookie的创建

javax.servlet.http.Cookie类是能够响应给客户端的文本对象。Cookie必须由响应对象发送给客户端。javax.servlet.http.Cookie类的部分源码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个是Cookie类提供的唯一构造方法,创建一个Cookie对象只能通过该构造器:

Cookie cookie = new Cookie(key,value);

(2)常用方法

先看源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
方法列表如下:
在这里插入图片描述
在这里插入图片描述
(3)设置Cookie的生命周期

即设置Cookie的过期时间,通过setMaxAge()方法来设置。通常有以下3种设置方法:

setMaxAge(0);//不记录Cookie
setMaxAge(-1);//关闭整个浏览器失效(会话级)
setMaxAge(大于0);//以秒为单位的存活周期

注意的是参数为0为不记录Cookie,参数是-1为会话级。浏览器关闭Cookie会失效。

(4)Cookie响应给客户端

Cookie通常是由服务端创建,然后响应给客户端,保存在客户端的浏览器上,然后客户端以后每次访问服务端时都会携带Cookie,以便于服务端识别用户身份信息。服务端是通过response对象添加Cookie到响应头中,然后响应给客户端的。如下:

response.addCookie(cookie);

(5)查找目标Cookie

Cookie是在客户端再次访问服务器时由请求对象带回的数据,因此由请求对象的getCookies()方法获取所有存储在客户端未过期的Cookie数组。如下:

// 获取客户端保存的所有未过期Cookie对象
Cookie []cookies = request.getCookies();

然后通过Cookie对象的getName()方法来查找目标Cookie是否存在。

(6)使用Cookie的限制

Cookie可以完成很多有意义的针对性服务,但是Cookie受到客户端浏览器的设置限制,浏览器可以选择启用或者禁用Cookie功能。

(7)测试

测试Cookie的使用。修改login.jsp页面,添加一个复选框:
在这里插入图片描述
在这里插入图片描述
修改UserServlet,完整的如下:

@WebServlet("/UserServlet")
public class UserServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 数据库连接
        Connection connection = DataSourceManager.getConnection();
        try {
            if (connection != null && !connection.isClosed()) {
                System.out.println("已连接MySQL数据库!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("开始执行登录验证!");
        // 请求对象的请求体设置编码
        request.setCharacterEncoding("utf-8");
        // 获取参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        // 是否选择保存信息
        String save = request.getParameter("save");
        // 服务层组件
        UserService userService = new UserServiceImpl();
        User currentUser = userService.validateLogin(user);
        if (currentUser != null) {
            System.out.println("用户【" + currentUser.getUsername() + "】登录成功!");
            // 获取客户端所有未过期的Cookie
            Cookie[] cookies = request.getCookies();
            // 如果客户端存在Cookie
            if (cookies != null && cookies.length != 0) {
                int count = 0;
                // 遍历Cookie数组
                for (Cookie c : cookies) {
                    if (c.getName().equals("loginInfo")) {
                        if (c != null) {
                            c.setMaxAge(0);// 不记录Cookie
                            response.addCookie(c);// 响应给客户端
                        }
                        count++;
                    }
                }
                if (count == 0) {
                    // 如果用户勾选了保存信息
                    if (save != null && save.equals("on")) {
                        // 新建一个Cookie对象
                        Cookie cookie = new Cookie("loginInfo", username + "---" + password);
                        cookie.setMaxAge(60 * 60 * 24 * 7);// 保存一周
                        response.addCookie(cookie);
                    }
                }
            } else {// 如果客户端不存在Cookie
                // 用户想使用时
                if (save != null && save.equals("on")) {
                    Cookie cookie = new Cookie("loginInfo", username + "---" + password);
                    cookie.setMaxAge(60 * 60 * 24 * 7);
                    response.addCookie(cookie);
                }
            }
            // 获取session对象
            HttpSession session = request.getSession();
            if (session.isNew()) {
                System.out.println("用户【" + currentUser.getUsername() + "】第一次访问!");
            }
            // 用户存到session域中
            session.setAttribute("current", currentUser);
            // 重定向到其他页面
            response.sendRedirect("worker.jsp");
        } else {
            System.out.println("登录失败!");
            response.sendRedirect("login.jsp?loginfailed=error");
        }
    }

}

再修改login.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
    <%! 
      String uName = "";
      String uPass = "";
    %>
<body>
    <%-- 取出Cookie --%>
    <%
      Cookie []cookies = request.getCookies();
      if(cookies!=null && cookies.length!=0){
          for(int i=0;i<cookies.length;i++){
              if(cookies[i].getName().equals("loginInfo")){
                  String loginInfo = cookies[i].getValue();
                  // 提取用户名
                  uName = loginInfo.substring(0,loginInfo.indexOf("---"));
                  // 提取密码
                  uPass = loginInfo.substring(loginInfo.indexOf("---") + 3);
                  break;
              }
          }
      }
    %>
   <form action="userServlet.do" method="post">
   	  <label>用户名:</label>
   	  <!-- Cookie中提取的信息填充 -->
   	  <input type="text" name="username" value="<%= uName %>" /><br/>
   	  <label>登录密码:</label>
   	   <!-- Cookie中提取的信息填充 -->
   	  <input type="password" name="password" value="<%= uPass %>"/><br/><br/>
   	  <input type="checkbox" name="save"/>保存信息&nbsp;&nbsp;&nbsp;
   	  <input type="submit" value="登录" />
   </form>
   
   <%
      String mess = request.getParameter("loginfailed");
      if(mess!=null && !mess.equals("") && mess.equals("error")){
   %>
   <label style="color:red">系统提示:用户名不存在或密码错误,登录失败!</label>
   <%
      }
   %>
</body>
</html>

启动web服务器,测试:
在这里插入图片描述
在这里插入图片描述
登录成功。查看Cookie:
在这里插入图片描述
Cookie成功响应到了客户端的浏览器,并保存在了本地。关闭浏览器,重新访问:
在这里插入图片描述
并没有重新输入用户名和密码,而是从Cookie中提取信息自动填充到了文本框中,直接点击登录便可以进入系统。
在这里插入图片描述
从以上例子可以看出,使用Cookie可以简化用户的登录手续,十分方便。

6.14、URL重写

URL重写就是首先获得一个进入的URL请求然后把它重新写成网站可以处理的另一个URL的过程。举个例子来说,如果通过浏览器进来的URL是"UserProfile.aspx?ID=1"那么它可以被重写成 “UserProfile/1.aspx”,这样的URL,这样的网址可以更好的被网站所阅读。

如果浏览器不支持Cookie或用户阻止了所有Cookie,可以把会话ID附加在HTML页面中所有的URL上,这些页面作为响应发送给客户。这样,当用户单击URL时,会话ID被自动作为请求行的一部分而不是作为头行发送回服务器。这种方法称为URL重写(URL rewriting)。URL重写是将用户请求的原始URL改写成新的URL的一种会话跟踪过程。

一般来说,URL重写是支持会话的非常健壮的方法。在不能确定浏览器是否支持Cookie的情况下应该使用这种方法。然而,使用URL重写应该注意下面几点:

  • 如果使用URL重写,应该在应用程序的所有页面中,对所有的URL编码,包括所有的超链接和表单的action属性值。
  • 应用程序的所有的页面都应该是动态的。因为不同的用户具有不同的会话ID,因此在静态HTML页面中无法在URL上附加会话ID。
  • 所有静态的HTML页面必须通过Servlet运行,在它将页面发送给客户时会重写URL。

当客户机不接受cookie时,server就使用URL重写作为会话跟踪的基本方式。URL重写,添加了附加数据(会话ID)到请求的URL路径上,会话ID必须被编码作为该URL字符串中的路径参数。该参数的名称为jsessionid,例如:index.html;jsessionid=1234

使用URL重写的优势:

  • 浏览器不支持Cookie或禁用Cookie时,实现用户会话跟踪。
  • 有利于被搜索引擎收录。
  • 提高访问的安全性。

相关的方法:

  • encodeURL(String url):只对本应用进行重写并附加会话id在url之后,对空字符串的url则处理成当前应用的上下文根目录然后附加会话id。
  • encodeRedirectURL(String url):可以跨域实现重写,但不附加会话id,如果对本应用有效url进行重写则在url后附加会话id。

URL上附加参数:

Worker.jsp?mess=sucess//url附加唯一查询参数
emp/worker.jsp?mess=sucess&name=Admin&role=1&status=200//url附加多个查询参数

6.15、AOP

AOP是Aspect Oriented Programming的缩写,即面向切面编程,AOP是一种程序设计思想,而不是一种新的技术,要将它理解为技术其实也可以。其目的是将主要核心业务与非核心业务分开处理,降低代码的耦合性,提高其重用性。

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

可以通过预编译方式和运行其动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。

在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的–完成业务逻辑–仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

AOP思想核心:

  • 设计核心业务与非核心业务分离。
  • 有效降低代码的耦合度复杂度,构建松耦合程序模式。
  • 提供可重用性更高更易于维护的系统。。
  • 设计灵活可插拔式的架构。
  • 开发关注点更集中某一方面。

主要功能:

  • 日志记录
  • 性能统计
  • 安全控制
  • 事务处理
  • 异常处理
  • 其他

主要目的:

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

6.16、过滤器Filter

Filter接口是JavaEE API中实现对请求进行拦截的AOP实现接口规范。Filter接口实际上是以特殊形式配置的Servlet,可以实现对用户的Web请求实现拦截处理。任何实现了Filter接口的类对象都可以作为拦截用户web请求的AOP组件,Filter必须在web.xml文件中以<filter><filter-mapping>进行配置,以被jsp容器所加载创建。继承了Filter接口,必须要重写其中的destroy()、doFilter()、init()这三个抽象方法。其中doFilter()方法是核心过滤方法。

小结:任何实现了javax.servlet.Filter接口的类都可以作为过滤器使用。过滤器可以拦截到方法的请求和响应(ServletRequest request, SetvletResponse response),并对请求响应做出响应的过滤操作,比如设置字符编码、鉴权操作。过滤器在请求到达Servlet之前和响应到达客户端之前起作用。

定义一个过滤器如下:

/*
 * 作为过滤器使用
 * 必须实现Filter接口
 */
public class OneFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // TODO Auto-generated method stub

    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

}

只需要实现javax.servlet.Filter接口即可作为过滤器使用。

javax.servlet.Filter接口源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
该接口只有3个方法:init()、doFilter()、destroy()。

init方法有一个FilterConfig接口参数。javax.servlet.FilterConfig接口源码:
在这里插入图片描述
在这里插入图片描述
doFilter方法有3个参数:ServletRequest接口参数、ServletResponse接口参数、FilterChain接口参数。

javax.servlet.FilterChain接口源码:
在这里插入图片描述
这个接口只有1个方法:doFilter()。

FilterChain作为doFilter方法的参数实现过滤器链条驱动功能。唯一的doFilter实现链条传递,将请求转发到下一个过滤器(如果有)。任何阶段过滤器未调用doFilter方法则中断过滤器链条。以下进行测试。

定义一个过滤器:

/*
 * 作为过滤器使用
 * 必须实现Filter接口
 */
public class OneFilter implements Filter {

    private String ip = "";

    // 过滤器初始化
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("初始化1号过滤器!");
        // 获取初始化参数(从web.xml中)
        ip = filterConfig.getInitParameter("ip");
    }

    // 过滤器的核心方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("1号过滤器开始过滤请求......");
        if(ip.equals("127.0.0.1")) {
            System.out.println("已被举报,无法访问本站!");
        } else {
            // 放行请求
            chain.doFilter(request, response);
        }
    }

    // 销毁过滤器
    @Override
    public void destroy() {
        System.out.println("销毁1号过滤器!");
    }

}

定义另一个过滤器:

public class TwoFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("初始化2号过滤器!");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("2号过滤器开始过滤请求......");
        // 过滤完毕,放行
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("销毁2号过滤器!");
    }

}

配置过滤器:

	<!-- 配置自定义过滤器 -->
	<filter>
	    <!-- 过滤器名称 -->
	    <filter-name>one</filter-name>
	    <!-- 映射的过滤器 -->
	    <filter-class>com.ycz.filter.OneFilter</filter-class>
	    <!-- 配置参数 -->
	    <init-param>
	       <param-name>ip</param-name>
	       <param-value>127.0.0.1</param-value>
	    </init-param>
	</filter>
	<filter-mapping>
	   <!-- 映射的过滤器名称 -->
	   <filter-name>one</filter-name>
	   <!-- 拦截的url -->
	   <url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 配置自定义过滤器 -->
	<filter>
	   <filter-name>two</filter-name>
	   <filter-class>com.ycz.filter.TwoFilter</filter-class>
	</filter>
	<filter-mapping>
	   <filter-name>two</filter-name>
	   <url-pattern>/*</url-pattern>
	</filter-mapping>

创建index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>默认界面</title>
</head>
<body bgcolor="pink">
    <h2 style="color:gray;font-style:italic;" align="center">欢迎来到本页面!</h2>
</body>
</html>

启动web服务器,控制台:
在这里插入图片描述
OneFilter和TwoFilter这两个过滤器初始化成功。
访问:http://localhost:8080/filter-demo/
在这里插入图片描述
控制台:
在这里插入图片描述
这个请求被OneFilter的doFilter方法拦截了,到不了TwoFilter拦截器,更到不了web服务器,即请求在到达web服务器之前被过滤器拦截过滤掉了。

修改OneFilter的doFilter方法,如下:
在这里插入图片描述
直接放行请求,按照理论,请求可以通过OneFilter,也能通过TwoFilter,然后到达web服务器,由jsp处理,最后响应结果给浏览器。重启web服务器访问:
在这里插入图片描述
可以成功访问页面。控制台:
在这里插入图片描述
两个过滤器的doFilter方法都起作用了。

编码过滤器的使用

定义编码过滤器,如下:

/*
 * 编码过滤器
 */
public class EncodeFilter implements Filter {

    private String encode;

    // 初始化过滤器
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 从web.xml中获取编码参数
        encode = filterConfig.getInitParameter("encode");
        System.out.println("编码过滤器初始化成功!所有请求和响应的编码为:" + encode);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 将请求和响应对象转换成http的
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        // 设置请求对象和响应对象的编码格式
        httpServletRequest.setCharacterEncoding(encode);
        httpServletResponse.setCharacterEncoding(encode);
        // 放行请求和响应
        chain.doFilter(httpServletRequest, httpServletResponse);
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

}

配置编码过滤器:

	<!-- 配置编码过滤器 -->
	<filter>
	   <filter-name>encodingFilter</filter-name>
	   <filter-class>com.ycz.filter.EncodeFilter</filter-class>
	   <!-- 参数配置 -->
	   <init-param>
	      <param-name>encode</param-name>
	      <param-value>UTF-8</param-value>
	   </init-param>
	</filter>
	<filter-mapping>
	   <filter-name>encodingFilter</filter-name>
	   <url-pattern>/*</url-pattern>
	</filter-mapping>

启动web服务器:
在这里插入图片描述
这个过滤器会将所有请求对象和响应对象的编码格式设置为UTF-8,不会发生中文乱码现象。

重复登录过滤器的使用

定义重复登录过滤器,如下:

/*
 *重复登录过滤器
 */
public class LoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
       System.out.println("初始化登录过滤器完成!");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        // 获取session对象
        HttpSession session = httpServletRequest.getSession();
        if(session == null) {
            // session为空,说明用户未登录,放行,允许登录
            chain.doFilter(httpServletRequest, httpServletResponse);
        }else {
           // session不为空,执行重复验证
            Object current = session.getAttribute("current");
            // 当前用户对象不为空,说明该用户已登录,拦截
            if(current != null) {
                // 下转型
                User u = (User)current;
                System.out.println("用户【" + u.getUsername() + "】已经登录系统!无需重复登录!");
                // 重定向到主页面
                httpServletResponse.sendRedirect("worker.jsp");
            } else {
                // 当前用户对象为空,说明这个用户没有登录,放行,允许登录
                chain.doFilter(httpServletRequest, httpServletResponse);
            }
        }
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

}

过滤器的配置:

	<!-- 配置重复登录过滤器 -->
	<filter>
	   <filter-name>loginFilter</filter-name>
	   <filter-class>com.ycz.filter.LoginFilter</filter-class>
	</filter>
	<filter-mapping>
	   <filter-name>loginFilter</filter-name>
	   <!-- 所有到达userServlet.do的请求都先经过过滤器的过滤 -->
	   <url-pattern>/userServlet.do</url-pattern>
	</filter-mapping>

启动web服务器:
在这里插入图片描述
访问登录:
在这里插入图片描述
开一个新窗口,再访问登录:
在这里插入图片描述
在这里插入图片描述
直接来到了主页面,控制台:
在这里插入图片描述
说明是经过登录过滤器的doFilter方法的,执行了过滤。

过滤器Filter是J2EE的一个规范,也是AOP设计思想的一个体现。在web开发中使用的频率非常高,应该熟练掌握。

6.17、文件的上传、下载

文件上传和下载功能在web应用中使用非常广泛,可以说大部分web应用都会有上传和下载功能。因此,熟练掌握上传下载很有必要。

6.17.1、文件上传

Web应用中文件上传是最基本的通用功能,JavaWeb应用实现用户的文件上传通常使用Apache的fileupload开源组件,commons-fileupload.jar和commons-io.jar是核心依赖。上传下载需要这两个依赖,先加到lib中,build to path,如下:
在这里插入图片描述
几大核心组件如下:

(1)DiskFileItemFactory组件

DiskFileItemFactory是FileItemFactory接口的默认实现,此类将请求消息实体中的每个项目封装成单独的DiskFileItem(FileItem接口的实现)。

org.apache.commons.fileupload.disk.DiskFileItemFactory类源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
org.apache.commons.fileupload.FileItemFactory接口源码:
在这里插入图片描述
属性说明:

  • DEFAULT_SIZE_THRESHOLD:常量,文件大小的临界值,默认为10240字节,即10KB。
  • repository:File类型的变量,用于配置在创建文件项目时,当文件项目大于临界值时使用的临时文件夹,默认采用系统默认的临时文件夹路径,可以通过系统属性java.io.tmpdir获取。
  • sizeThreshold:int型变量,初始值是DEFAULT_SIZE_THRESHOLD,保存上传文件的目录大小的临界值,一般会重新设置,当这个临界值小于上传文件的大小时,会抛出异常,注意即可。

(2)ServletFileUpload组件

ServletFileUpload类是Apache文件上传组件处理文件上传的核心高级类,它的最核心功能是使用parseRequest(HttpServletRequest)方法将通过表单中每一个HTML标签提交的数据封装成一个FileItem对象,然后以List列表的形式返回。

org.apache.commons.fileupload.servlet.ServletFileUpload类源码:
在这里插入图片描述
在这里插入图片描述
以上两个构造方法第二种用的比较多,传一个FileItemFactory类型参数。
在这里插入图片描述
在这里插入图片描述

org.apache.commons.fileupload.FileUpload类源码:
在这里插入图片描述
在这里插入图片描述
org.apache.commons.fileupload.FileUploadBase类部分源码:
在这里插入图片描述
这是个抽象类。
在这里插入图片描述
以上是定义的一些常量。

在这里插入图片描述
定义的一些属性如上。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个parseRequest方法比较重要,这个方法将form表单项转换为FileItem实例并存储到List中返回,传一个HttpServletRequest类型参数,会调用下面的重载方法。

(3)DiskFileItem组件

DiskFileItem类是FileItem接口的实现类,用来封装单个表单字段元素的数据,对于文件上传而言只处理实际是File对象的写入操作。

org.apache.commons.fileupload.disk.DiskFileItem类部分源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

org.apache.commons.fileupload.FileItem接口部分源码:
在这里插入图片描述
org.apache.commons.fileupload.FileItemHeadersSupport接口源码:
在这里插入图片描述
下面进行文件上传的测试。

编写upload.jsp上传页面,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
    <h2>请选择文件上传</h2>
    <!--文件上传要通过表单实现-->
    <form action="uploadServlet.do" method="post" enctype="multipart/form-data">
    	<label>文件一:</label>
    	<input type="file" name="f1"/><br><br>
    	<label>文件二:</label>
    	<input type="file" name="f2"/><br><br>
    	<input type="submit" value="开始上传" />    	
    </form>
</body>
</html>

需要注意:form表单的method属性值必须为post,enctype属性值必须为multipart/form-data。

创建新的Servlet组件,如下:

@WebServlet("/UploadServlet")
public class UploadServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    // 文件的上传目录
    private String uploadDir;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取文件的上传保存目录
        uploadDir = this.getServletContext().getRealPath("/WEB-INF/upload/");
        System.out.println("上传文件的保存目录:" + uploadDir);
        // 创建DiskFileItemFactory对象
        DiskFileItemFactory factory = new DiskFileItemFactory();
        System.out.println("文件的默认临时保存目录:" + System.getProperty("java.io.tmpdir"));
        System.out.println("文件的最大默认临界值:" + DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD + "字节");
        // 创建ServletFileUpload对象
        ServletFileUpload servletFileUpload = new ServletFileUpload();
        // 设置工厂对象
        servletFileUpload.setFileItemFactory(factory);
        // 设置上传的一些必要属性
        servletFileUpload.setFileSizeMax(1024 * 1024 * 10);// 单文件大小临界值为10M
        servletFileUpload.setSizeMax(1024 * 1024 * 100);// 设置总文件临界值100M
        servletFileUpload.setHeaderEncoding("UTF-8");// 设置上传的编码格式
        List<FileItem> items = null;
        try {
            // 获取上传的所有文件项
            items = servletFileUpload.parseRequest(request);
            for (FileItem item : items) {
                // 判断是否为普通表单项
                boolean res = item.isFormField();
                // 不是普通表单项,包含文件
                if (!res) {
                    // 获取文件名称
                    String fileName = item.getName();
                    System.out.println("文件名称:" + fileName);
                    System.out.println("对应的表单字段:" + item.getFieldName());
                    System.out.println("文件大小:" + item.getSize() / 1024 + "KB");
                    System.out.println("文件类型:" + item.getContentType());
                    // 构建文件对象
                    File file = new File(uploadDir + fileName);
                    try {
                        // 文件写入
                        item.write(file);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                // 写入完成后关闭文件项,释放资源
                item.delete();
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
        response.setCharacterEncoding("UTF-8");
        response.setHeader("content-type", "text/html;charset=UTF-8");
        PrintWriter printWriter = response.getWriter();
        printWriter.write("<h2>文件上传成功!</h2>");
        printWriter.flush();
        printWriter.close();
    }

}

配置Servlet组件:

	<!-- 配置Servlet -->
	<servlet>
	   <servlet-name>upload</servlet-name>
	   <servlet-class>com.ycz.servlet.UploadServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>upload</servlet-name>
	   <url-pattern>/uploadServlet.do</url-pattern>
	</servlet-mapping>

启动web服务器,访问:
在这里插入图片描述
选择文件,注意单文件大小不超过10M,点击开始上传按钮:
在这里插入图片描述
控制台:
在这里插入图片描述
按照控制台的输出信息查看目录:
在这里插入图片描述
在这里插入图片描述
存在这两个文件,说明文件上传是成功的。

6.17.2、文件下载

实现文件下载的基本原理:

  • 服务端:设置响应头信息,响应内容为文件流,告知Web用户(浏览器)是文件下载。
  • 客户端:客户端浏览器获取服务器响应内容,自动识别为文件下载,启动浏览器下载程序完成文件保存到客户机磁盘上。

文件下载的大概步骤如下:

  • 获取下载链接请求的文件id参数。
  • 根据id查找目标文件建立文件对象,判断文件是否存在。
  • 基于目标文件建立文件输入流读取文件到内存。
  • 设置响应头内容,告知浏览器是文件流响应方式。
  • 输出文件到客户端浏览器,处理异常关闭文件流。

创建download.jsp下载页面,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
    <h2>资源列表:</h2>
    <!-- 从本项目的服务器上下载资源 -->
    <a href="downloadServlet.do?fileId=山海经.doc">山海经下载</a><br>
    <a href="downloadServlet.do?fileId=张卫健 - 把酒狂歌.mp3">把酒狂歌.mp3下载</a><br>
    <!-- 这个资源服务器上没有 -->
    <a href="downloadServlet.do?fileId=demo.txt">测试未有资源下载</a><br>
</body>
</html>

创建tips.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>404</title>
</head>
<body>
    <h2 style="color:red;font-style:italic;">对不起,资源不存在,无法下载!</h2>
</body>
</html>

创建新的Servlet,如下:

@WebServlet("/DownloadServlet")
public class DownloadServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取下载资源的名称
        String fileName = request.getParameter("fileId");
        // 下载资源所在目录
        String dir = this.getServletContext().getRealPath("/downloadResource/");
        // 构建下载的文件对象
        File file = new File(dir + fileName);
        InputStream in = null;
        ServletOutputStream sos = null;
        // 资源存在,开始下载
        if (file.exists()) {
            in = new FileInputStream(file);
            // 缓存
            byte[] bys = new byte[1024 * 1024 * 10];
            int count = 0;
            // 设置响应头内容,告知浏览器下载文件
            response.setHeader("content-disposition",
                    "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
            // 获取输出流
            sos = response.getOutputStream();
            // 写入文件
            while ((count = in.read(bys, 0, bys.length)) > 0) {
                sos.write(bys, 0, count);
            }
            // 强制写入
            sos.flush();
            // 关闭流
            sos.close();
            in.close();
        } else { // 资源不存在,提示
            response.sendRedirect("tips.jsp");
        }
    }

}

配置Servlet:

	<!-- 配置Servlet -->
	<servlet>
	   <servlet-name>download</servlet-name>
	   <servlet-class>com.ycz.servlet.DownloadServlet</servlet-class>
	</servlet>
	<servlet-mapping>
	   <servlet-name>download</servlet-name>
	   <url-pattern>/downloadServlet.do</url-pattern>
	</servlet-mapping>

准备要下载的资源:
在这里插入图片描述
启动web服务器,访问:
在这里插入图片描述
点击第一个链接:
在这里插入图片描述
第二个链接:
在这里插入图片描述
第三个链接的资源服务器上是没有的,点击:
在这里插入图片描述
下载的测试没问题。

文件的上传下载应用的非常多,应当熟练掌握,几大核心组件API的使用要十分熟悉。

7、Web应用程序的部署、发布

Web应用程序完成后,应该部署在Web服务器上。这里以Tomcat为例。Tomcat安装完成后,目录结构:
在这里插入图片描述
bin目录

在这里插入图片描述
该目录下是一些bat启动文件。

conf目录

在这里插入图片描述
该目录下是一些配置文件。

context.xml内容:
在这里插入图片描述
jaspic-providers.xml内容:
在这里插入图片描述
tomcat-users.xml内容:
在这里插入图片描述
这个文件中可以配置用户的信息,用户名和密码等。

server.xml部分内容:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
lib目录
在这里插入图片描述
该目录下只有服务器运行的必要jar包。

logs目录

在这里插入图片描述
这个目录下只有运行的日志记录。

temp目录是空的。

webapps目录
在这里插入图片描述
这里面是部署的应用,可以是满足web应用特定结构的文件夹,也可以是war包,war包会自动解析成特定结构的文件夹。

work目录
在这里插入图片描述
这个目录下存放的是各个应用下的jsp文件转换成的java文件,和编译后的class文件,class文件是要被ClassLoader加载器加载的。
在这里插入图片描述
以下以几种不同的方式部署web应用到Tomcat服务器上。

7.1、Tomcat控制台部署

这是最常用的部署方式,最大优点是可以通过网络异地在远程服务器上部署Java Web应用程序。

限制条件:

  • 必须具有服务器管理员权限。
  • 受网络影响及服务器必须已经启动。

这种方式可以部署文件夹,也可以部署WAR包。

文件夹部署

在这里插入图片描述
比如说想部署这个项目,先建一个文件夹:
在这里插入图片描述
然后把项目下的classes文件夹复制过来:
在这里插入图片描述
然后把WebContent下的所有内容复制过来:
在这里插入图片描述
在这里插入图片描述
然后将classes目录移到WEB-INF目录下:
在这里插入图片描述
最后将整个文件夹放到Tomcat的webapps目录里:
在这里插入图片描述
启动Tomcat服务,访问:http://localhost:8080/
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,这个项目已经部署成功了。
在这里插入图片描述
可以在控制台这里部署文件或WAR包,但感觉没有手动部署方便。

访问:http://localhost:8080/test1
在这里插入图片描述
输入用户名和密码登录:
在这里插入图片描述
图片资源的路径有问题,但项目是部署成功的,可以访问。

WAR包部署

直接用工具将项目打成war包,然后直接部署到webapps里面就行了。eclipse打war包,选中项目,然后右键,Export:
在这里插入图片描述
在这里插入图片描述
选择一个输出路径,点击Finish。
在这里插入图片描述
打包成功了,现在改一个名:
在这里插入图片描述
然后放到Tomcat的webapps目录里:
在这里插入图片描述
重启Tomcat服务,访问控制台:
在这里插入图片描述
部署成功,访问:http://localhost:8080/test2
在这里插入图片描述
登录:
在这里插入图片描述
访问成功,说明这个部署也成功了。

7.2、自定义xml文件部署

在conf目录下创建catalina/localhost子目录:
在这里插入图片描述
在子目录下创建一个xml文件
在这里插入图片描述

内容如下:

<Context path="/test5" autoDeploy="true" 
docBase="D:\MyWorkspace\servlet-demo\WebContent">
</Context>

path属性是项目的访问路径,reloadable属性是重新加载,docBase属性是要部署的文件夹所在路径,workDir属性是jsp文件转换的java文件和编译文件所在目录。

访问控制台:
在这里插入图片描述
访问:http://localhost:8080/test5/
在这里插入图片描述
登录:
在这里插入图片描述
访问成功。

7.3、配置server.xml部署应用

修改conf目录下的server.xml,如下:
在这里插入图片描述
在Host标签内添加Context标签,类似第二种部署方式,重启Tomcat服务,访问:
在这里插入图片描述
再访问:http://localhost:8080/test4/
在这里插入图片描述
登录:
在这里插入图片描述
访问成功。

丢到webapps里面是最简单的部署方式,但是通过配置文件部署更加灵活。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值