servlet


WEB项目

1 规律

- 由单机向网络演变
- 由CS向BS演变

2 CS

Server,即客户端服务器程序

3 BS

- Browser Server,即浏览器服务器程序
- 使用浏览器充当客户端,开发量小


Servlet

1 服务器如何给浏览器发送网页

1) 静态网页
- 百度百科、新闻
- 服务器保存HTML,并且直接向浏览器发送此HTML

2) 动态网页
- 淘宝、微博
- 服务器保存组件,用它动态拼一个HTML发送浏览器
- 在Java项目中,该组件就是Servlet
> 组件:满足规范的对象

2 Servlet主要特征

- 保存在服务器上
- 需要满足sun的规范
- 拼动态资源(HTML/IMG等)
> 术语:用来处理HTTP协议

3 什么是Servlet

- 是sun推出的用来在服务器端处理HTTP协议的组件。


服务器

1 名称

- Java服务器
- WEB服务器
- Java Web服务器
- Servlet容器

2 本质

- 是一个软件
- 可以运行Java项目
- 和浏览器对应、平级

3 举例

- Tomcat
- JBoss
- WebLogic
- WebSphere

Tomcat的使用

1 独立使用(上线时)

1) 下载及安装
- 从Apache官网下载
- http://doc.tedu.cn/tomcat/
- 绿色版软件,解压缩可用
> 学生机已经安装完毕,在/tts9/apache-tomcat7

2) 配置JAVA_HOME
- tomcat依赖于java
> 学生机已经配置完毕

3) 启动Tomcat
- Linux:打开/tomcat/bin,右键终端输入./startup.sh
- windows:打开/tomcat/bin,双击startup.bat
> 加权限:chmod +x *sh

4) 访问tomcat
- 打开浏览器,输入http://localhost:8080
- 回车后看到一只猫着代表访问成功

5) 关闭tomcat
- linux:打开/tomcat/bin,输入./shutdown.sh
- windows:打开/tomcat/bin,双击shutdown.bat

2 通过Eclipse调用(开发时)

- 参考doc.tedu.cn/tomcat
- 若配置过程中出错,需要重来
- 需要先remove配置好的tomcat
- 需要将Servers项目删除


Servlet开发步骤

1 创建WEB项目

- WEB项目必须具备标准的WEB目录
- webapp/WEB-INF/web.xml

2 导入jar包

- 通过maven搜索javaee
- 右键项目->properties->Targeted Runtimes->勾选tomcat->Apply

3 开发Servlet

- 继承于HttpServlet
- 间接的实现了Servlet接口(sun的规范)
- 重写service(),动态拼资源

4 配置Servlet

- 在web.xml中进行配置

5 部署项目

- 在Servers下右键tomcat
- 点击Add and Remove
- 弹出框中将要部署的项目从左侧移动到右侧
- 启动tomcat
> 部署就是将代码拷贝到Tomcat下

6 访问Servlet

- 打开浏览器,输入
- http://localhost:8080/Servlet1/ts

详细见Servlet.day01


Servlet执行过程: 详细见Servlet.day01


HTTP协议

1 什么是HTTP协议

- 就是一个规范(w3c)
- 规定了浏览器和服务器如何通信
- 规定了通信时的数据格式

2 如何通信

- 建立连接
- 发送请求
- 接收响应
- 关闭连接

> 一次请求一次连接,尽量降低服务器的压力

3 数据格式

3.1 请求数据

- 请求行:请求的基本信息
- 消息头:请求数据的描述信息
- 实体内容:请求的数据

3.2 响应数据

- 状态行:响应的基本信息
- 消息头:响应数据的描述信息
- 实体内容:响应的数据

4 开发的关注点

4.1 不用管的地方

- 通信的过程已经由浏览器和服务器实现了
- 请求数据由浏览器打包
- 响应数据由服务器打包

4.2 需要处理的地方

- 请求的具体数据由开发提供
- 响应的具体数据由开发提供

> 用request处理请求数据、用response处理响应数据


Tomcat端口被占用问题

1 其他软件占用了此端口

- 修改tomcat端口(8088、8089等)
- 修改/tomcat/conf/server.xml
- 大约60-70行,修改port="8080"

> 问题:访问时发现不是tomcat界面

2 tomcat重复启动

- 手动关闭tomcat(输入shutdown命令)
- 然后再次启动

> 问题:Address already in use, JVM_BIND 8080


Servlet

1 浏览器如何向服务器传参

1.1 浏览器

1) 表单的action属性设置提交目标
2) 通过name属性给数据命名
3) 单选/多选/value,对其值进行设置

1.2 服务器

通过req.gerParameter(),获取浏览器发送过来的值.
String getParameter(String paramName);
String[] getParameterValues(String paramName).

详细见Servlet.day02

2 Servlet运行原理

1) 连接
2) 打包请求数据
3) 发送请求数据
4) 拆包请求数据
5) new request/response对象
6) new Servlet对象
7) 调用Servlet对象
8) 打包响应数据
9) 发送响应数据
10) 拆包响应数据
11) 断开连接

详细见Servlet.day02

3 请求方式

3.1 什么是请求方式

- 就是浏览器向服务器发送数据的方式

3.2 需要掌握2种方式

1) GET

- 使用请求路径传参,将参数附加在路径上发送服务器
- 参数在传递过程中可见,隐私性差
- 请求路径空间有限,只能携带少量参数

> 所有默认的请求都是GET请求

2) POST

- 使用实体内容传参
- 参数在传递过程中不可见,隐私性好
- 实体内容专门用来传参,大小没有限制

> 在form上增加method="post"时

3.3 如何选择请求方式

- 向服务器索取(查询)数据时用GET
- 向服务器提交数据时用POST

> 传递少量数据时用GET,传递大量数据时用POST

4 乱码解决方案

1)get方式

在server.xml的第65行,属性内添加上 URIEncoding="utf-8".
优点: 简单.

2)post方式

- 在获取请求参数前,加 req.setCharacterEncoding("utf-8");
- 响应参数前,加 res.setContentType("text/html;charset=utf-8").
优点: 简单.

3) get/post方式(万能办法)

- Servlet接受乱码String
- 用iso8859-1将乱码String还原成byte
- 再用utf-8将byte转化为String
缺点: 麻烦.

详细见Servlet.day02


重定向
1 定义

在服务器为浏览器提供响应时,回传的数据包中的状态行里面是302状态码,同时在消息头内会增加一个键值对,名称为Location,值是一个新的URL地址。当这个响应到达浏览器的时候,这一次的请求响应过程并未结束,浏览器遇见302状态码之后,会立即按照Location头信息中指定的URL地址发送新的一个请求,这样一个在接到响应后又立即发出请求的过程叫做重定向。

2 重定向的工作原理

在重定向的过程中,影响浏览器做出动作的关键点即响应中的状态码及Location这个消息头。302状态就像一道命令一样,使得浏览器做出新的一次请求,而请求的地址会从头信息中查找。由于这个新的请求动作是由浏览器发出的,所以浏览器的地址栏上的地址会变成Location消息头中的地址。

3 如何重定向

由于发回的响应信息由response对象控制,所以使用如下代码即可实现重定向的过程:
response.sendRedirect(String url);

该方法的参数值url即Location消息头中的重定向地址。注意,该段代码后面如果还有其他代码的话也会被继续执行的。

4 使用场景

1) 解决2个网站之间的跳转问题
2) 解决一个项目内,2个彼此独立的组件之间的跳转问题
3) 数据库增加/修改/删除后,重定向到查询页面

详细见Servlet.day03


路径

1 WEB上的定义:
URI: 资源的名称(可以有多个);
URL: 资源的真名;

URI 包含 URL.

详细见Servlet.day03

2 如何部署Servlet访问路径

Servlet访问路径有3种配置方式,不同的方式其处理请求的能力不同.

2.1 精确匹配(/abc)
- 只有/abc可以访问此Servlet;
- 此Servlet只能处理这个请求.

案例:
<servlet>
<servlet-name>someServlet</servlet-name>
<servlet-class>test.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>someServlet</servlet-name>
<url-pattern>/abc.html</url-pattern>
</servlet-mapping>

2.2 通配符(/*)
- 所有的路径都可以访问此Servlet;
- 此Servlet能处理所有请求.

案例:
<servlet>
<servlet-name>someServlet</servlet-name>
<servlet-class>test.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>someServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

2.3 后缀(*.do)
- 所有以 .do 为后缀的路径都可以访问此Servlet;
- 此Servlet可以处理多个请求.

案例:
<servlet>
<servlet-name>someServlet</servlet-name>
<servlet-class>test.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>someServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

3 一个Servlet实现多请求

3.1 使用后缀匹配模式完成请求资源路径的匹配

修改web.xml文件,将更多的servlet配置节点删除,只保留一个节点即可,
代码如下:

<servlet>
<servlet-name>someServlet</servlet-name>
<servlet-class>web.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>someServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

3.2 分析请求资源后分发

配置完web.xml文件后,不同请求都会发送到Web.SomeServlet来处理,要想起到分发的作用,则需要分析调过来的请求中具体的请求目标是什么。使用如下代码逻辑来完成分发动作。

String uri = request.getRequestURI();
System.out.println("uri:" + uri);
if(uri.equals("/test/list.do")){
System.out.println("进行员工列表的处理...");
}else if(uri.equals("/test/add.do")){
System.out.println("添加员工的处理...");
}


Servlet生命周期

1 什么是Servlet生命周期

Servlet容器如何创建Servlet对象、如何为Servlet对象分配、准备资源、如何调用对应的方法来处理请求、如何销毁Servlet对象的整个过程即Servlet的生命周期。

2 生命周期的四个阶段

阶段一、实例化

Servlet容器调用Servlet的构造器 new 一个 Servlet对象.

配置文件中的load-on-startup节点用于设置该Servlet的创建时机:

当其中的值大于等于0时,表示容器在启动时就会创建实例
小于0时或没有指定时,代表容器在该Servlet被请求时再执行创建
正数的值越小,优先级越高,应用启动时就越先被创建。

阶段二、初始化

Servlet容器调用init()方法(这个方法在javax.servlet.Servlet接口中定义),方法以一个ServletConfig类型的对象作为参数。ServletConfig对象由Servlet引擎负责创建,从中可以读取到事先在web.xml文件中通过<init-param>节点配置的多个name-value名值对。ServletConfig对象还可以让Servlet接受一个ServletContext对象。

一般情况下,init方法不需要编写,因GenericServlet已经提供了init方法的实现,并且提供了getServletConfig方法来获得ServletConfig对象。
注:init方法只被执行一次。

1) 在servlet配置中,增加初始化参数

案例:
<servlet>
<servlet-name>someServlet</servlet-name>
<servlet-class>test/SomeServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-valule>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>someServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

2) 读取Servlet配置中增加的初始化参数

案例:
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,IOException{
System.out.println("SomeServlet's service...");
ServletConfig config = getServletConfig();
String debug = config.getInitParameter("debug");
System.out.println("debug:" + debug);
}

阶段三、就绪

Servlet被初始化以后就处于能够响应请求的就绪状态。
每个对Servlet的请求由一个ServletRequest对象代表,Servlet给客户端的响应由一个ServletResponse对象代表。
当客户端有一个请求时,容器就会将请求与响应对象转给Servlet,以参数的形式传给service方法。
service方法由javax.servlet.Servlet定义,由具体的Servlet实现。

阶段四、销毁

Servlet容器在销毁Servlet对象时会调用destroy方法来释放资源。
通常情况下Servlet容器停止或者重新启动都会引起销毁Servlet对象的动作,
但除此之外,Servlet容器也有自身管理Servlet对象的准则,整个生命周期并不需要人为进行干预。

3 Servlet接口

在ServletAPI中最重要的是Servlet接口,所有Servlet都会直接或间接的与该接口发生联系,或是直接实现该接口,或间接继承自实现了该接口的类。

该接口包括以下三个方法:
init(ServletConfig config)
service(ServletRequest req,ServletResponse res)
destroy( )

4 ServletContext

4.1 什么是Servlet上下文

WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用,是一个全局的环境变量。
该应用中的任何组件,在任何时候都可以访问到该对象,所以Servlet上下文具有唯一性。

4.2 如何获得Servlet上下文

获取该对象的方式有以下四种:
GenericeServlet(HttpServlet)提供的 getServletContext()
ServletConfig提供的 getServletContext()
HttpSession提供的 getServletContext()
FilterConfig提供的 getServletContext()

4.3 Servlet上下文的作用及特点

1) Servlet上下文的作用:

- 在Web应用范围内存取共享数据:如 setAttribute(),getAttribute()
- 访问Web应用的静态资源:如getRealPath(String path)
- 跨多个请求、用户和Servlet

5 config 和 context 不同点

1) 开发Servlet时可能需要读取一些参数;
2) 可以自己写配置文件及工具来设置这些参数;
3) 也可以直接使用web.xml做配置文件,并使用config和context对象做工具来读取参数;
4) config和Servlet是一对一的关系,每个Servlet都会有一个config对象.
5) context和Servlet是一对多的关系,项目中只有一个context对象,所有Servlet共享这个对象.
6) config对象在调用init()方法前创建;context对象在启动时创建.

详细见Servlet.day03

6 实现HttpServlet接口

1) 重写service()方法.
2) 重写doPost()/doGet()方法.
doXXX()方法直接抛出异常,强制开发者重写.

sun这样设置HttpServlet,是为了开发者在写代码时有更多选择的空间,也更方便.

详细见Servlet.day04


Servlet线程安全问题

1 Servlet线程安全问题

1) 什么时候会出现线程安全问题

- 多个人同时修改一份数据时
- 对象、成员变量存储在堆中,多线程共用
- 局部变量存储在栈中,每个线程有自己的栈帧
> 多个人同时修改对象或成员变量时

2 如何解决线程安全问题

加锁(synchronized)


JSP

1 什么是JSP

JSP(Java Server Page)是Sun公司制定的一种服务器端动态页面技术的组件规范,以".jsp"为后缀的文件中既包含HTML静态标记用于表现页面,也包含特殊的代码,用于生成动态内容。

JSP作为简化Servlet开发的一种技术,实质上最终依然要转变为Servlet才可能运行,只不过这个转变过程由Servlet容器来完成。所以遵循JSP的编写规范才能使得JSP转变为需要的Servlet。

2 如何编写JSP

步骤一、创建一个以"jsp"为后缀的文件

步骤二、在文件中添加用于控制页面显示的HTML代码、样式及JavaScript脚本。

步骤三、在需要动态生成的部分添加Java代码的编程逻辑

3 JSP页面中的注释

在JSP页面中可以添加如下两种类型的注释:
<!-- 注释内容 -->
<%-- 注释内容 -->

第一种注释也叫HTML注释,可以出现在JSP页面之中,注释内容中可以包含了一些Java代码,但这些代码会被执行.
第二行注释是JSP注释,不允许注释的内容出现Java代码,写了Java代码也会被忽略,不会执行。

4 JSP页面中的Java代码

JSP页面中可以包含如下三种类型的Java代码:
JSP表达式(方便输出)
JSP小脚本(完成相对较长的逻辑运算)
JSP声明(添加属性或方法)

4.1 JSP表达式

使用表达式可以方便的在JSP页面中输出运算的结果,代码形式如下所示:
<%=3+5%>
<%=add()%>
<%=xx.getName()%>
<%=xx.getName()+"abc"%>
注意:表达式结束不需要写分号。

4.2 JSP小脚本

JSP小脚本可以编写Java代码段,从而实现相对较长的一段运算逻辑。这些Java代码最终都会成为Servlet中Service方法的一部分。由于HTML与Java可以进行混合使用,所以需要注意括号的匹配。

案例:
<table>
<%
List<User> allUser = (List<User>)request.getAttribute("users");
for(User u : allUser){
%>
<tr>
<td> <%=u.getId()%> </td>
<td> <%=u.getName()%> </td>
</tr>
<%
}
%>
</table>

4.3 JSP声明

JSP声明可以为对应的Servlet添加属性和方法。这种形式的代码使用的很少。语法规则如下:
<%!
//属性或方法的声明
%>

如编写下列代码:
<%!
public void fun(){
//… 方法体
}
%>

5 JSP页面中的指令

指令在JSP页面中通常起到转译成Servlet时的格式控制的作用。
基本语法为:
<%@ 指令名 属性=值 %>

常用指令包含以下三种:
page指令
include指令
taglib指令

这些指令都有自己的属性来实现不同的控制功能。taglib指令会在讲解JSP标签时详细介绍。

5.1 page指令

page指令可以实现在JSP页面中导入要用到的Java包,也可以对页面的一些属性进行设置。

1) 导包

<%-- 导包 --%>
<%@ page import="java.util.*"%>
<%@ page import="java.util.*,java.sql.*"%>

使用page指令导包时,需要用到import属性。如果需要导入多个包,可以分成多条page指令来编写,也可以在一条page指令中,使用","逗号作为分隔来实现。注意,page指令要放在页面的最上面编写

2) 设置response.setContentType()方法的参数值

<%-- 设置response.setConentType方法的参数值 --%>
<%@ page contentType="text/html;charset=utf-8"%>

使用page指令可以设置输出内容的编码方式,这样就可以设置浏览器使用正确的解码方式来显示页面。

3) 设置容器读取该文件时的解码方法

<%-- 设置容器读取该文件时的解码方式 --%>
<%@ page pageEncoding="UTF-8"%>

为了保证页面中编写的中文能够保存,以及容器在加载文件时能正确的解码文件中的中文,需要通过page指令的pageEncoding属性来完成。这段代码可以保证页面在加载到内存时正确的解码中文。

5.2 include指令

include指令主要用于将其他页面包含在另一个页面之中。

include指令的语法如下:
<%@ include file="header.html" %>

5.3 taglib指令

taglib指令用于导入JSP的标签库的.

taglib指令的语法如下:
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

6 JSP的运行原理

详细见Servlet.day04

7 JSP隐含(内置)对象

1) request(*)
- HttpServletRequest
- 代表请求对象

2) response
- HttpServletResponse
- 代表响应对象

3) out
- JspWriter
- 和PrintWriter一样
- 输出的数据流

4) config
- ServletConfig
- Servlet配置对象

5) application
- ServletContext
- 全局的Servlet上下文对象

6) exception
- Throwable
- tomcat调用servlet时所抛出的异常

7) session(*)
- HttpSession
- 会话

8) page
- Object
- 指代当前JSP页面对象,相当于this

9) pageContext(*)
- PageContext
- 代表JSP页面上下文
- 页面的管家,通过它可以获取其他8个隐含对象

使用示例:

- <%String user = request.getParameter("user");%>
- <%=request.getParameter("user")%>

8 如何将静态页面转换为动态页面

在将一个静态HTML页面转变为动态的JSP页面时:

1) 首先需要分析页面的整体结构,找到页面中不变的公共部分,如导航、页脚等信息。

2) 将静态页面的脚本拷贝到JSP页面之后,一定要添加page指令pageEncoding属性,保证页面中的中文能够被正确编码。

3) 添加page指令的import属性导入页面中需要的Java包。

4) 将页面中与目标页面不一致的地方进行修改,如修改表头与实际字段一致。

5) 最后将页面中需要动态生成的内容删除,使用小脚本的Java代码来实现运算逻辑。

9 jsp间的传真方式

1) 直接在URL请求后添加
2) jsp:param
< jsp:param name="param name" value="paramvalue" />
3) 绑定到session上
4) 绑定到request上


Servlet的开发模式

1 Model 1

浏览器 --- Servlet/JSP --- DAO

服务器使用一个WEB组件处理请求,其内部将HTML和Java耦合在一起,不利于维护.

2 Model 2(MVC)

浏览器 --- Servlet --- JSP --- DAO

将原来一个WEB组件所做的事情,拆分为2个组件一起做.其中Servlet处理java部分的逻辑,JSP负责展现数据.

MVC 是经典的设计模式,是代码分层思想:
1) M(Model): 业务层,用来处理业务的;
2) V(View): 视图层,用来显示数据;
3) C(Controller): 控制层,用来处理请求,进行调度.

使用它可以降低代码的耦合度,以便团队开发及维护.


转发

1 什么是转发

一个Web组件(Servlet/JSP)将未完成的处理通过容器转交给另外一个Web组件继续完成,这个转交的过程叫做转发。

常见情况是Servlet负责获取数据,然后将数据转交给JSP进行展现。

2 如何实现转发

步骤一、绑定数据到request对象

在转交的过程中一定会有数据的传递,并且涉及到的Web组件都是针对同一个请求,所以利用request来保存共同处理的数据不仅仅能让数据共享,也能够随着请求响应的结束而销毁,不会占用服务器更多的资源。

使用如下代码可以实现数据的绑定:
request.setAttribute(String name,Object obj);

1) setAttribute(): 方法实现数据绑定;
2) getAttribute(): 方法获取绑定的数据;
3) removeAttribute(): 方法移除绑定的数据。

步骤二、获得转发器

使用如下代码可以获取到转发器,用于说明转交的下一个组件的路径:
RequestDispatcher rd = request.getRequestDispatcher(String uri);

步骤三、实现转发

使用转发器完成转发的动作,因下一个Web组件要针对同一个请求和响应继续完成后续的工作,所以在转发时要将本次的请求和响应对象作为参数传给下一个Web组件。实现代码如下所示:
rd.forward(request,response);

其中步骤二和步骤三可以合并为一行代码:
request.getRequestDispatcher(String uri).forward(request,response);

3 转发的特点

1) 浏览器的页面的地址栏地址不变化

转发过程发生在服务器端,客户端只发送了一个请求,虽然请求到达服务器的指定位置后被容器控制着传到了第二个组件继续完成工作,但浏览器并不知道这个过程,所以转发之后地址栏地址不会发生变化。

2) 转发的目的地必须是同一个应用内部的某个地址

毕竟这个转交过程由容器实现,容器只有访问本应用的权限,而不能控制请求到达应用以外的位置。

3) 数据的传递和共享就依赖request对象

转发过程中涉及到的所有Web组件共享同一个request对象和response对象,数据的传递和共享就依赖request对象。

注意:在forward之后的其他语句还是会继续执行完的,只要不报异常。


4 转发 和 重定向 的区别

重定向:
浏览器发送请求到容器访问A,A可以发送一个状态码302和一个Location消息头到浏览器,于是浏览器会立即向Location发新的请求。

转发:
浏览器发送请求到容器访问A,A可以通知容器去调用B。转发所涉及的各个Web组件会共享同一个request和response对象;而重定向不行。

说明:
当请求到达容器,容器会创建request对象和response对象。
当响应发送完毕,容器会立即删除request对象和response对象。即,request对象和response对象的生存时间是一次请求与响应期间。

1) 转发之后,浏览器地址栏的地址不变,重定向会变。

2) 转发的地址必须是同一个应用内部某个地址,而重定向没有这个限制。

3) 转发是一件事情未做完,调用另外一个组件继续做;而重定向是一件事情已经做完,再做另外一件事情。


异常处理

1 编程式异常处理

编程式的异常处理就是在程序中捕获到异常时,使用转发跳转到指定页面进行提示说明。

案例:
try {
//使用dao访问数据库
//交给jsp来完成
request.getRequestDispatcher("list3.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
//使用转发的方式来处理异常。
request.setAttribute("error_msg","系统繁忙,稍后重试");
request.getRequestDispatcher("error.jsp").forward(request,response);
}


2 容器中声明式处理

声明式的处理主要依靠容器自己来完成,即产生异常时抛出给容器,但不能让容器将这些底层信息返回给客户端,所以需要在web.xml文件中添加配置说明,通知容器在捕获到异常时应该将什么样的页面返回给客户端。一旦使用声明式处理方式,则该项目下的任意一个文件错误或异常,都会跳到指定的错误处理页面。

具体步骤:

步骤一、在代码中捕获到异常直接抛出(注意异常类型,必须是ServletException)

案例:
try {
// 使用dao访问数据库
//交给jsp来完成
request.getRequestDispatcher("list3.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
//将异常抛给容器来处理
throw new ServletException(e);
}

步骤二、在web.xml文件中添加配置

案例:
<!-- 配置错误处理页面 -->
<error-page>
<exception-type>javax.servlet.ServletException</exception-type>
<location>/error.jsp</location>
</error-page>


路径

1 什么是路径

在JSP页面或Servlet中需要从一个组件到另一个组件的跳转,通常以链接、表单提交、重定向、转发的形式来完成,其中对目标位置的标识即路径。

链接地址<a href="url">链接文本</a>
表单提交<form action="url">
重定向response.sendRedirect(url);
转发request.getRequestDispatcher(url);

2 什么是相对路径

相对路径指的是相对于当前位置,为了到达目标文件要经过的路径。
书写格式上不以"/"开头,如果为了退至上一级目录可以"../"开头。

3 什么是绝对路径

绝对路径指的是,不管当前文档所在的位置在应用的哪里,都会从一个固定的点出发,然后构建到达目标文件所需要经过的路径。
通常绝对路径在书写格式上以"/"开头。这个固定的点可能是应用名,也可能是应用名之后。


EL表达式

1 什么是EL标签

一套简单的运算规则,用于给JSTL标签的属性赋值,也可以直接用来输出而脱离标签单独使用。

Java 语言脚本request.getParameter("XXX") ,实际应用还必须进行强制类型转换。用户直接使用EL 表达式取得的值,而不用关心它是什么类型。

2 访问Bean属性

Bean:指的是一个公共的类,有无参构造器,按照固定的方式提供属性的get/set访问方式。
针对这种特殊类型的属性访问使用EL表达式实现有两种方式,如下:

2.1 方式一:${对象名.属性名}

${user.name}

执行的过程为:从pageContext、request、session、application中依次查找绑定名为"user"的对象,找到后调用"getName"方法,将返回值输出。

案例:

假定在session中绑定了一个对象,如下:

User obj = new User(1,"胡萝卜");
session.setAttribute("user",obj);

那么${user.name}等价于下面代码:

<%
User u = (User)session.getAttribute("user");
out.print(u.getName());
%>

EL表达式比以上繁琐代码更会处理null。
1) 如果没有为name属性赋过值,页面输出"",不会输出null。
2) 如果取值时绑定名写错,如${obj.name},页面也会输出"",而不是报空指针异常。
3) 如果没有为name属性赋过值,页面输出"",不会输出null。
4) 如果属性名写错会报错,如${user.naaa}.

2.2 方式二:${对象名["属性名"]}

表达式也支持属性名的动态读取,这时需要采用方式二${user["name"]}的形式。

案例:
假定在Servlet中有如下代码:

User obj = new User(1,"胡萝卜");
session.setAttribute("user",obj);
session.setAttribute("pName","id");

在JSP中编写如下代码会输出"1":

${sessionScope.user["id"]}

在JSP中编写如下代码也会输出"1":

${sessionScope.user[sessionScope.pName]}

3 指定对象的查找范围

书写表达式时,如果没有指定搜索范围,那么系统会依次调用pageContext、request、session、application的getAttribute()方法。这样不限定查找范围的代码不利于排错,所以这种取值的操作可以限定对象的查找范围。如:

${sessionScope.user.name}

一旦指定了对象所在的范围,那么只会在范围内查找绑定对象,不会在找不到的时候再去其他区域中查找了。

sessionScope的位置还可以填写pageScope、requestScope、applicationScope。

4 使用EL表达式进行计算

使用EL表达式可以单独进行运算得出结果并直接输出.
如下代码所示,EL进行算术运算,逻辑运算,关系运算,及empty运算。
空运算主要用于判断 字符串/集合 是否为空,是 空/null/找不到值 时都会输出 true 。

<%request.getSession().setAttribute("sampleValue", new Integer(10));%>
${sessionScope.sampleValue} // 显示 10
${sessionScope.sampleValue + 12} <br> // 显示22
${(sessionScope.sampleValue + 12)/3} <br> // 显示7.3
${(sessionScope.sampleValue + 12) /3==4} <br> // 显示 false
${(sessionScope.sampleValue + 12) /3>=5} <br> // 显示 true
<input type="text" name="sample1" value="${sessionScope.sampleValue + 10}"> // 显示值为20的 Text 控件
${empty null} // true

5 使用EL表达式获取请求参数值

以下两种写法分别等价:

${param.username} 与 request.getParameter(“username”);
${paramValues.city} 与request.getParameterValues("city");


JSTL

1 什么是JSTL

Apache组织开发的一套标签库被Sun公司整合后,称为 标准标签库(JSP Standard Tag Library 即JSTL).配合EL表达式,以达到减轻JSP文件的复杂度、方便维护JSP文件的目的.

2 如何使用JSTL

step1: 将标签库对应的jar包(jstl:1.2)拷贝到WEB-INF/lib目录下,以便于系统可以加载所需要的类。

step2: 使用taglib指令导入要使用的JSP标签 <%@taglib uri="" prefix="" %> ,帮助系统定位对应的类。

uri:JSP标签的命名空间;
prefix: 命名空间的前缀.

案例:
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

3 核心标签

3.1 if标签

使用if标签实现属性判断后的输出。

语法:
<c:if test="" var="" scope=""> </c:if>

test: test属性值为true时,执行标签体的内容.test属性可以使用EL表达式赋值;
var: 指定一个绑定名称;
scope: 指定绑定范围(page,request,session,application)

注意: var 和 scope要配合使用.

案例:
<%
User user = new User();
user.setName("胡萝卜");
user.setGender("f");
request.setAttribute("user",user);
%>
姓名:${user.name}<br/>
性别:
<c:if test="${user.gender =='m'}" var="rs" scope="request">男</c:if>
<c:if test="${!rs}">女</c:if>

3.2 choose标签

使用choose标签简化多个if标签的判断。

语法:
<c:choose>
<c:when test=""> </c:when>
<c:when test=""> </c:when>
<c:otherwise> </c:otherwise>
</c:choose>

when表示一个处理分支,当test属性为true时会执行该分支,可以出现1次或者多次.
otherwise表示例外,可以出现0次或1次.

案例:
<%
User user = new User();
user.setName("胡萝卜");
user.setGender("x");
request.setAttribute("user",user);
%>
性别:
<c:choose>
<c:when test="${user.gender == 'm'}">男</c:when>
<c:when test="${user.gender =='f'}">女</c:when>
<c:otherwise>未知</c:otherwise>
</c:choose>

3.3 forEach标签

使用forEach标签完成对集合的遍历输出。

语法:
<c:forEach items="" var="" varStatus=""> </c:forEach>

items: items属性为要遍历的集合,一般使用EL表达式来赋值;
var: var属性为每次取出来的一个对象,绑定到pageContext对象上;
varStatus: varStatus属性指定一个绑定名称,绑定值是一个由容器创建的对象,该对象封装了当前迭代的状态。
index: index返回正在被迭代的对象的下标,下标从0开始.
count: count返回是第几次迭代,从1开始.

案例:
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
</tr>
<c:forEach items="${users}" var="u" varStatus="s">
<tr>
<td>${s.count}</td>
<td>${u.name}</td>
<td>${u.age}</td>
</tr>
</c:forEach>
</table>

4 如何开发自定义标签

步骤一: 编写一个继承自SimpleTagSupport的Java类

案例:
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class HelloTag extends SimpleTagSupport{

}

步骤二: 重写该类的doTag方法,在其中添加处理逻辑

案例:
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class HelloTag extends SimpleTagSupport{
private String info;
private int qty;

public void setInfo(String info) {
this.info = info;
}
public void setQty(int qty) {
this.qty = qty;
}

public void doTag() throws JspException, IOException {
PageContext ctx =(PageContext)getJspContext();
JspWriter out = ctx.getOut();
for(int i=0;i< qty;i++){
out.println(info+"<br/>");
}
}
}

步骤三: 在WEB-INF下面新建一个tld文件,用于配置标签说明文件。

案例:
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0">

<tlib-version>1.1</tlib-version>
<short-name>c1</short-name>
<uri>http://www.tarena.com.cn/mytag</uri>
<tag>
<name>hello</name>
<tag-class>tag.HelloTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>info</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>qty</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>

</taglib>

步骤四: 在页面中引用标签

案例:
<%@taglib uri="http://www.tarena.com.cn/mytag" prefix="c1" %
//… …
<c1:hello info="hello kitty" qty="${1+5}"/>

5 标签的运行原理

1) 容器依据JSP页面中的uri找到tld文件(依据标签中的<c1:hello>hello这个名字找到标签类tag.HelloTag。
2) 接下来实例化该标签,同时属性值赋给参数,调用doTag方法。

注意:
1) JSTL本质上就是Java,每个标签对应一个类.
2) 在tomcat翻译JSP时,JSTL被翻译成了一个Java代码.
3) tomcat会将标签翻译成它对应的标签累的doTag().

详细见Servlet.day05

6 WEB-INF的作用

前提1: 浏览器加载的页面包含对个请求.
前提2: WEB-INF可以保护其内部的资源,避免他们被直接访问.必须通过转发来访问.

1) 直接访问JSP会得不到任何数据,会报错,这是一个BUG.
2) 将JSP放到WEB-INF下,是想保护它,不让别人直接访问它.
3) 若将静态资源放到WEB-INF下,就不能直接访问了.


状态管理

1 为什么需要状态管理

1) Web应用程序使用HTTP协议作为传输数据的标准协议,而HTTP协议是无状态协议,即一次请求对应一次响应,响应结束后连接即断开,
2) 同一个用户的不同请求对于服务器端来讲并不会认为这两个请求有什么关联性,并不会以此区分不同的客户端。
3) 但实际情况中还是需要服务器端能够区分不同的客户端以及记录与客户端相关的一些数据,
所以状态管理能够做到不同客户端的身份识别。

2 什么是状态管理

将客户端与服务器之间多次交互当做一个整体来看待,并且将多次交互中涉及的数据保存下来,提供给后续的交互进行数据的管理, 即状态管理。

状态 指的是当前的数据;
管理 指的是在这个多次交互的过程中对数据的存储、修改、删除。

3 状态管理两种常见模式

如果将数据存储在客户端,每次向服务器端发请求时都将存在客户端的数据随着请求发送到服务器端,修改后再发回到客户端保存的这种模式叫做Cookie。

如果将数据存储在服务器端,并且为这组数据标示一个编号,只将编号发回给客户端。当客户端向服务器发送请求时只需要将这个编号发过来,服务器端按照这个编号找到对应的数据进行管理的这种模式叫做Session——会话。

4 Cookie

4.1 Cookie的原理

只要Cookie的生命周期没有结束,那么不管是存在内存还是硬盘上的信息(Cookie里存的数据)都会在客户端向服务器端发出请求时自动的随着消息头发送过去。

4.2 如何创建Cookie

Servlet API提供了javax.servlet.http.Cookie这种类型来解释Cookie。其中存储的文本以name-value对的形式进行区分,所以创建Cookie时指定name-value对即可。这个name-value最终是以Set-Cookie这种消息头的形式跟随相应数据包到达客户端,所以要想将数据添加到消息头中需要使用response对象提供的方法。

创建Cookie的代码如下:

Cookie c = new Cookie(String name,String value);
response.addCookie( c );

4.3 如何查询Cookie

当客户端向服务器发出请求时,服务器端可以尝试着从请求数据包的消息头中获取是否携带了Cookie信息。实现这一功能的代码如下:

Cookie[] request.getCookies();

request提供的获取Cookie的方法的返回值是Cookie数组,如果想进一步获取某一个Cookie信息可以通过遍历数组,分别获取每一个Cookie的name和value。代码如下:

Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(Cookie c : cookies){
String cookieName = c.getName();
String cookieValue = c.getValue();
}
}

4.4 如何修改Cookie

所谓Cookie的修改,本质是获取到要变更值的Cookie,通过setValue方法将新的数据存入到cookie中,然后由response响应对象发回到客户端,对原有旧值覆盖后即实现了修改。主要实现代码:

Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(Cookie c : cookies){
String cookieName = c.getName();
if(name.equals(“uname”)){
c.setValue(“Mark”);
response.addCookie( c );
}
}

其中response.addCookie(c)是非常重要的语句,如果没有这一行代码,那么就算是使用setValue方法修改了Cookie的值,但是不发回到客户端的话,也不会实现数值的改变。所以只要见到response.addCookie这行代码,即服务器端发回了带有Set-Cookie消息头的信息。

4.5 Cookie的生存时间

默认情况下,Cookie会被浏览器保存在内存中,此时Cookie的生命周期由浏览器决定,只要不关闭浏览器Cookie就会一直存在。

如果希望关闭浏览器后Cookie仍存在,则可以通过设置过期时间使得Cookie存在硬盘上得以保存更长的时间。

设置Cookie的过期时间使用如下代码:

void setMaxAge(int seconds);

该方法是Cookie提供的实例方法。参数seconds的单位为秒,但精度不是很高。
seconds > 0 :代表Cookie保存在硬盘上的时长
seconds = 0 :代表Cookie的生命时长为现在,而这一刻稍纵即逝,所以马上Cookie就等同于过了生存时间,所以会被立即删除。这也是删除Cookie的实现方式。
seconds < 0 :缺省值,浏览器会将Cookie保存在内存中(默认情况)。

4.6 Cookie编码

Cookie作为在网络传输的一段字符串文本,只能保存合法的ASCII字符,如果要保存中文需要将中文变成合法的ASCII字符,即编码。使用如下代码可以实现将中文保存到Cookie中。
Cookie c = new Cookie("city",URLEncoder.encode("北京","utf-8"));

4.7 Cookie解码

服务器读取客户端经过编码之后的信息时,要想能够正确显示需要将信息解码后才能输出。

URLDecoder.decode(value,"utf-8");

实现解码的完整代码如下:

Cookie[] cookies = request.getCookies();
if(cookies != null){
for(int i=0;i<cookies.length;i++){
Cookie c = cookies[i];
String name = c.getName();
String value = c.getValue();
out.println(name + “:” + URLDecoder.decode(value,"utf-8"));
}
}else{
out.println("没有找到cookie");
}

4.8 Cookie的路径问题

1) 什么是Cookie的路径问题

客户端存储Cookie之后,并不是针对同一个应用访问任何资源时都自动发送Cookie到服务器端,而是会进行路径的判断。只有符合路径规范的请求才会发送Cookie到服务器端。

客户端在接受Cookie时会为该Cookie记录一个默认路径,这个路径记录的是添加这个Cookie的Web组件的路径。

如,当客户端向 http://localhost:8080/test/file/addCookie.jsp发送请求时创建了cookie,那么该cookie的路径就是 /test/file.

2) 什么时候发送Cookie

只有当访问的地址是Cookie的路径或者其子路径时,浏览器才发送Cookie到服务器端。

如,Cookie的路径是 /test/file,那么如果访问的是 /test/file/a.jsp 或者 /test/file/b/c.jsp时,都会发送Cookie。
如果访问的是 /test/d.jsp,则浏览器不会发送Cookie。

3) 如何设置Cookie的路径

设置Cookie的路径可以使用Cookie的API方法,setPath(String uri);

如以下代码就实现了设置Cookie的路径为应用的顶级目录,这样所有资源路径要么与此路径相等,要么是子路径,从而实现了客户端发送任何请求时都会发送Cookie。

Cookie c = new Cookie(“uname”,“jack”);
c.setPath(“/test”);
response.addCookie(c);

4) Cookie的限制

Cookie由于存放的位置在客户端,所以可以通过修改设置被用户禁止。

ookie本质就是一小段文本. 一小段: 说的是只能保存少量数据,长度是有限制的,一般为4kb左右;文本: 说的是只能保存字符串,不能保留复杂的对象类型数据。

作为网络中传输的内容,Cookie安全性很低,非常容易通过截取数据包来获取,在没有加密的情况下不要用于存放敏感数据。

就算是能够存放的长度很短,但作为网络中传输的内容也会增加网络的传输量影响带宽。在服务器处理大量请求的时候,Cookie的传递无疑会增加网络的负载量。

5 Session

1) 什么是Session

服务器为不同的客户端在内存中创建了用于保存数据的Session对象,并将用于标识该对象的唯一Id发回给与该对象对应的客户端。当浏览器再次发送请求时,SessionId也会被发送过来,服务器凭借这个唯一Id找到与之对应的Session对象。在服务器端维护的这些用于保存与不同客户端交互时的数据的对象叫做Session。

2) 如何获得Session

获得session有两种情况,要么请求中没有SID,则需要创建;要么请求中包含一个SID,根据SID去找对应的对象,但也存在找到找不到的可能。
在Request类型的API中包含获取到session对象的方法,代码如下所示:

方法一:

HttpSession s = request.getSession(boolean flag);

flag = true:先从请求中找找看是否有SID,没有会创建新Session对象,有SID会查找与编号对应的对象,找到匹配的对象则返回,找不到SID对应的对象时则会创建新Session对象。所以,填写true就一定会得到一个Session对象。
flag= false:不存在SID以及按照SID找不到Session对象时都会返回null,只有根据SID找到对应的对象时会返回具体的Session对象。所以,填写false只会返回已经存在并且与SID匹配上了的Session对象。

方法二:

HttpSession s = request.getSession( );

request.getSession()方法不填写参数时等同于填写true,提供该方法主要是为了书写代码时更方便,大多数情况下还是希望能够返回一个Session对象的。

3) 如何使用Session绑定对象

Session作为服务器端为各客户端保存交互数据的一种方式,采用name-value对的形式来区分每一组数据。向Session添加数据绑定的代码如下:

void session.setAttribute(String name,Object obj);

获取绑定数据或移除绑定数据的代码如下:

void session.getAttribute(String name);
void session.removeAttribute(String name);

Session对象可以保存更复杂的对象类型数据了,不像Cookie只能保存字符串。

4) 如何删除Session对象

如果客户端想删除SID对应的Session对象时,可以使用Session对象的如下方法:

void invalidate()

该方法会使得服务器端与该客户端对应的Session对象不再被Session容器管理,进入到垃圾回收的状态。对于这种立即删除Session对象的操作主要应用于不再需要身份识别的情况下,如登出操作。

6 Session超时

1) 什么是Session超时

Session会以对象的形式占用服务器端的内存,过多的以及长期的消耗内存会降低服务器端的运行效率,所以Session对象存在于内存中时会有默认的时间限制,一旦Session对象存在的时间超过了这个缺省的时间限制则认为是Session超时,Session会失效,不能再继续访问。

Web服务器缺省的超时时间设置一般是30分钟。

2) 如何修改Session的缺省时间限制

有两种方式可以修改Session的缺省时间限制,编程式和声明式。

编程式:

void setMaxInactiveInterval(int seconds)

使用编程式来修改缺省时间只会针对调用该方法的Session对象应用这一原则,不会影响到其他对象,所以更灵活。通常在需要特殊设置时使用这种方式。时间单位是秒,与声明式的时间单位不同。

声明式:

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

使用声明式来修改缺省时间,那么该应用创建的所有Session对象的生命周期都会应用这个规定的时间,单位为分钟。

3) Session验证

Session既然区分不同的客户端,所以可以利用Session来实现对访问资源的保护。如,可以将资源划分为登录后才能访问。Session多用于记录身份信息,在保护资源被访问前可以通过判断Session内的信息来决定是否允许。这是依靠Session实现的验证。

实现Session验证的步骤如下:

步骤一:

为Session对象绑定数据,代码如下:

HttpSession s = request.getSession();
s.setAttribute(“uname”,“Rose”);

步骤二:

读取Session对象中的绑定值,读取成功代表验证成功,读取失败则跳转回登录页面。

HttpSession s = request.getSession();
if(s.getAttribute("uname")==null){
response.sendRedirect("login.jsp");
}else{
//… …
}

4) Session优缺点

Session对象的数据由于保存在服务器端,并不在网络中进行传输,所以安全一些,并且能够保存的数据类型更丰富,同时Session也能够保存更多的数据,Cookie只能保存大约4kb的字符串。

Session的安全性是以牺牲服务器资源为代价的,如果用户量过大,会严重影响服务器的性能。

5) 浏览器禁用Cookie的后果

Session对象的查找依靠的是SID,而这个ID保存在客户端时是以Cookie的形式保存的。一旦浏览器禁用Cookie,那么SID无法保存,Session对象将不再能使用。

为了在禁用Cookie后依然能使用Session,那么将使用其他的存储方法来完成SID的保存。URL地址在网络传输过程中不仅仅能够起到标示地址的作用,还可以在其后携带一些较短的数据,SID就可以通过URL来实现保存,及URL重写。

6) 什么是URL重写

浏览器在访问服务器的某个地址时,会使用一个改写过的地址,即在原有地址后追加SessionID,这种重新定义URL内容的方式叫做URL重写。

如:原有地址的写法为http://localhost:8080/test/some
重写后的地址写法为http://localhost:8080/test/some;jsessionid=4E113CB3

1) 如何实现URL重写

生成链接地址和表单提交时,使用如下代码:

<a href="<%=response.encodeURL(String url)%>">链接地址</a>

如果是重定向,使用如下代码代替response.sendRedirect()

response.encodeRedirectURL(String url);

7 验证码

7.1 验证码的作用

验证码技术可以防止对于应用恶意发送数据,因其不规律且不能由机器代劳,所以一定程度上避免了恶意程序对网站的攻击。

验证码本质上是一张图片,图片内容的准确解析不容易用程序来实现,所以能避免内容被快速读取。并且,图片的内容是使用程序随机生成后绘制得到。

注册、登录这样的功能一般都会配备验证码,一定程度上避免恶意代码的攻击。

7.2 验证码的绘制

绘制验证码图片不仅仅需要随机生成要绘制的内容,同时要配合Java中与绘图有关的一套API来完成。绘制API将画板、画笔、颜料、字体等都解释为对象,绘制的过程就是这些对象互相配合完成的。主要涉及Graphics、Font等类型。

7.2.1 验证码图片的绘制步骤

1) 绘制图片的基本步骤如下:

1) 常见内存画板对象
2) 创建基于该画板的画笔
3) 设定画笔的颜色
4) 设定画板背景的颜色
5) 使用画笔的绘制方法绘制随机内容
6) 更改画笔颜色
7) 绘制随机的两点一线的干扰线
8) 绘制完成后将图片压缩并输出到客户端

以上步骤对应的实现代码如下所示:

package web;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import javax.servlet.*;
import javax.servlet.http.*;
public class CheckcodeServlet extends HttpServlet {

private int width = 80; //图片的宽度
private int height = 30;//图片的高度

public void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
/*
* 绘图
*/
//step1,创建一个内存映像对象(画板)
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//step2,获得画笔
Graphics g = image.getGraphics();
//step3,给笔上色
Random r = new Random();
g.setColor(new Color(r.nextInt(255), r.nextInt(255),r.nextInt(255)));
//step4,给画板设置背景颜色
g.fillRect(0, 0, width, height);
//step5,绘制一个随机的字符串
String number = r.nextInt(99999) + "";
g.setColor(new Color(0,0,0));
//new Font(字体,风格,大小)
g.setFont(new Font(null,Font.ITALIC,24));
g.drawString(number, 5, 25);
//step6,加一些干扰线
for(int i=0;i < 8;i++){
g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255)));
g.drawLine(r.nextInt(width), r.nextInt(height), r.nextInt(width), r.nextInt(height));
}
/*
* 压缩图片并输出到客户端(浏览器)
*/
response.setContentType("image/jpeg");
OutputStream ops =response.getOutputStream();
javax.imageio.ImageIO.write(image, "jpeg", ops);
ops.close();
}
}

2) 配置web.xml文件代码如下:

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

3) HTML页面中添加如下代码加入该图片:

<img src="checkcode"/>

8 密码加密

8.1 摘要加密

摘要加密的特点:不适用密钥,使用摘要加密算法对明文进行加密之后会得到密文,无法反推出明文。

唯一性:不同的明文有不同的密文。

不可逆性:即使知道了摘要加密算法, 也无法反推出明文.

8.2 如何实现摘要加密

public class MD5Util{
private static void test1() throws NoSuchAlgorithmException{
String str = "I love you";
MessageDigest md = MessageDigest.getInstance("md5");
//依据指定的加密算法进行加密
byte[] buf = md.digest(str.getBytes());
//因为字节数组不方便使用,所以,将其转换成一个字符串
//BASE64Encoder的encode方法可以将任意的一个字节数组转换成一个字符串
BASE64Encoder base = new BASE64Encoder();
String str2 = base.encode(buf);
System.out.println(str2);
}
public static String encrypt(String origStr){
MessageDigest md = MessageDigest.getInstance("md5");
byte[] buf = md.digest(str.getBytes());
BASE64Encoder base = new BASE64Encoder();
String str = base.encode(buf);
return str;
}
}


Cookie 和 Session

1 Cookie 和 Session 的异同

1) 浏览器和服务器是多对1的关系
2) 登录时要记录账号,后续查询/增加/修改时要现实此账号.
3) 使用如下对象记录账号:
- request: 多次请求对应不同的request, 不行.
- config: 多个config对应不同的Servlet,多个Servlet不能共享对象中的数据,不行.
- context: 它是单例的,多人同时登录,同时记录账号时,会相互覆盖,产生影响,不行.
4) Cookie和Session专门解决这样存储数据的问题:
- 多个请求之间可以共用此对象中的数据.
- 对个Servlet之间可以共用此对象中的数据.
- 每个浏览器单独存一份数据,数据是分开的,互相不影响.
5) 他们的区别:
- Cookie数据存储在浏览器上;服务器压力小;但容易被篡改;存储量小(约4kb);存储的类型为字符串.
- Session数据存储在服务器上;服务器压力大;无法被篡改;存储量较大;存储的类型多样.
6) 建议:
- 不重要的数据存储在Cookie上
- 重要的数据存储在Session上

详细见Servlet.day08

2 Cookie 和 Session的用途

2.1 通俗的理解

按照如下的规则存数据:

- 多个请求可以共用这样的数据
- 多个Servlet可以共用这样的数据
- 每个浏览器都单独存一份数据

2.2 专业的理解

- HTTP无状态协议:服务器没有记住浏览器
- cookie和session就是管理状态,让服务器记住浏览器的

状态:用来证明浏览器曾经访问过服务器的数据


过滤器

1 什么是过滤器

过滤器是Servlet2.3规范之中一种特殊的Web组件,可以作为Servlet的辅助性插件存在。
例如,对信息的筛选是很多Servlet里面的一个必须的前提,但是相同的功能在每个Servlet中都编写不仅仅不利于以后修改过滤逻辑,也不利于功能的重用,这时可以将这一部分非决定性的功能放在一个过滤器中,通过配置由容器控制所有请求在到达真正的处理逻辑Servlet之前先通过过滤器的检查。如果过滤逻辑需要修改,那么只需要修改这一个组件即可。
所以这种可随时添加、修改,作为Servlet补充功能的Web组件是非常必要的。

作用:

常用来做项目中的一些共性的需求,如:记录日志、过滤敏感词、权限检查;

过滤器会以极低的耦合度来处理这样的需求

共性的需求:几乎每个请求都要做的事情

2 如何编写过滤器

编写过滤器遵循下列步骤:

1) 编写一个实现了Filter接口的类
2) 实现Filter接口的三个方法,过滤逻辑在doFilter方法中实现
3) 在Web程序中注册过滤器
4) 把过滤器和Web应用一起打包部署

步骤一、实现Filter接口

Filter是过滤器API中最核心的接口,定义一个Java类实现该接口以提供过滤逻辑。代码如下:

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;

public class CommentFilter implements Filter{
//… …
}

步骤二、实现doFilter方法

Filter接口中共定义了三个方法,分别是init,doFilter,destroy方法。

init方法在创建Filter时会被调用,且只调用一次,一般在该方法中做一些数据的准备工作,可以通过传入的FilterConfig参数获取在web.xml文件中配置提供给过滤器的初始参数。

destroy方法只有在销毁过滤器对象时被调用一次,用于释放一些资源的操作。

doFilter方法内编写过滤器的具体处理逻辑,会被多次执行。该方法共有三个参数,请求和响应用于获取数据以及追加数据,FilterChain是过滤器链,负责多过滤器的传递。

实现代码如下:

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;

public class CommentFilter implements Filter{

private FilterConfig config;

public void destroy() {
System.out.println("CommentFilter1's destroy...");
}
/*
* 容器会调用doFilter方法来处理请求(
* 相当于servlet的service方法)。
* 容器会将request对象(arg0)和response对象
* (arg1)作为参数传给doFilter方法。
* 如果调用了FilterChain(arg2)的doFilter(request,response)方法,
* 则容器会调用后续的过滤器或者servlet,否则请求处理完毕。
*/
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest)arg0;
HttpServletResponse response =(HttpServletResponse)arg1;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
String content = request.getParameter("content");
String illegalStr = config.getInitParameter("illegalStr");
if(content.indexOf(illegalStr) != -1){
//有敏感字
out.println("<h1>评论内容包含了敏感字</h1>");
}else{
//没有敏感字
// 执行FilterChain的doFilter会调用后续的过滤器或者servlet。
arg2.doFilter(arg0, arg1);
}
System.out.println("Filter1's doFilter end.");
}

/*
* FilterConfig对象可以用来访问过滤器的初始化参数。
* init方法只会执行一次。
*/
public void init(FilterConfig arg0) throws ServletException {
System.out.println("CommentFilter1's init...");
config = arg0;
}
}

步骤三、注册过滤器

在web.xml文件中注册过滤器,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- 过滤器 -->
<filter>
<filter-name>filter1</filter-name>
<filter-class>web.CommentFilter</filter-class>
<!-- 初始化参数,由FilterConfig对象读取-->
<init-param>
<param-name>illegalStr</param-name>
<param-value>胡萝卜</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/comment</url-pattern>
</filter-mapping>
</web-app>

步骤四、部署过滤器

将编译后的过滤器和其他Web组件类合在一起,连同web.xml文件放进应用程序结构中即可。

3 过滤器的执行流程

详细见Servlet.day09

1) 启动服务器时它会创建Filter
2) 关闭服务器时它会销毁Filter
3) 服务器只启动一次,所以Filter是单例的
4) 每个Filter解决一个业务,他们的调用顺序以WEB.xml配置文件中的顺序为准.

4 过滤器的初始化参数

容器启动之后,会创建过滤器实例。通过init方法来完成过滤器的初始化。初始化时可以添加一些配置,提升动态性。而这些参数通过在web.xml文件中的<init-param>以name-value对的形式存在。

读取这些name-value对需要使用FilterConfig对象,从web.xml文件到FilterConfig对象的过程由容器完成,并通过参数传入到init方法之中,只需要设定一些成员对象保存数值就可以在doFilter方法中使用。

4.1 初始化参数的配置

<!-- 过滤器 -->
<filter>
<filter-name>filter1</filter-name>
<filter-class>web.CommentFilter1</filter-class>
<!-- 初始化参数 -->
<init-param>
<param-name>illegalStr</param-name>
<param-value>胡萝卜</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/comment</url-pattern>
</filter-mapping>

4.2 读取初始化参数

读取初始化参数使用如下代码:

public class CommentFilter implements Filter{

private FilterConfig config;
public void init(FilterConfig arg0) throws ServletException {
config = arg0;
}
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
String illegalStr = config.getInitParameter("illegalStr");
// … …
}
public void destroy() {
// … …
}

}

5 过滤器的特点

方便增加或减少某个功能模块,需要添加过滤就多部署一个class修改一下web.xml文件,需要减少某个功能只要删除web.xml中对应的声明即可。

方便修改处理逻辑。当把一些功能独立到某个模块时,如果逻辑变了,只修改这一个文件并更新就可以实现功能的改变,而不用修改每一个使用这个插件的组件。(耦合性低)


监听器

1 什么是监听器

Servlet规范当中定义的一种特殊的组件,用来监听servlet容器产生的事件并进行响应的处理。

2 监听器生命周期相关的事件

容器创建或者销毁request,session,ServletContext(上下文/环境)时产生的事件(统计在线人数)。

3 绑定数据相关的事件

调用了以上三个对象(request,response,ServletContext)的setAttribute,removeAttribute方法时产生的事件。

4 如何编写监听器

step1,写一个java类,实现相应的监听器接口(共有8个接口)。要依据监听的事件类型来选择相应的监听器接口,比如要监听session对象的创建和销毁,要实现HttpSessionListener。
案例:

public class CountListener implements HttpSessionListener{

private int count = 0;

public void sessionCreated(HttpSessionEvent arg0){
System.out.println("sessionCreated…");
count ++;
}

public sessionDestroyed(HttpSessionEvent arg0){
System.out.println("session destroyed…");
count--;
}
}

step2,在监听器接口方法中,实现相应的监听处理逻辑。比如,session对象被删除了,将人数减1。

案例:

public void sessionCreated(HttpSessionEvent arg0){
System.out.print("sessionCreated…");
HttpSession session = args.getSession();
ServletContext ctx = session.getServletContext();
ctx.setAttribute("count",count);
}

step3,注册(在web.xml文件中配置即可)。

案例:

<listener>
<listener-class>web.CountListener</listener-class>
</listener>

5 监听器的应用场景

系统框架级别的代码经常需要检测容器中数据或对象的变化,以一个不受人为控制因素的触发为执行时机,所以对于需要根据数据变化来做出自动反馈的功能都可以使用到监听器.

posted @ 2018-01-14 15:34  Leo_Messi  阅读(236)  评论(0编辑  收藏  举报