1) 选择工作空间 workspace 选择一个文件夹存放程序(代码) 不要用中文和空格
2) 新建一个java 工程(Project)
3) 建包 建类
alt + / : 代码自动补齐,需要配置的
打开preferences(首选项), 搜keys,打开快捷键配置面板
搜 alt+/ ,取消绑定
搜 content assist ,取消原先的绑定,配置成alt+/
ctrl+1: 错误自动修复, 注意,放行的红叉是可修复的,圆形的是不可修复的
导包的三种方式:
1)将类名敲完整,按下alt+/ 进行补齐,会自动导包
2) ctrl+1,修正错误
3) ctrl+shift+o 整理包,导入需要的包,去掉多余的导包语句
ctrl+shift+f : 代码格式化
ctrl+2,L 自动声明变量
shift+enter 光标直接进入下一行
ctrl+alt+方向键(向上向下) 复制行
alt+ 方向键(向上向下) 移动当前行
1) 可以对程序进行调试
在行的开头双击左键打断点,用debug方式来运行程序,程序就会停留在断点位置
F5 跳入(step into) 跳入方法内部 F6 跳过, 让当前行运行 F7跳出 返回上一层程序
resume 直接运行到下一个断点
2) 查看源代码建议使用断点调试的方式,注意当前运行环境需要包含源码
1) 写一个java类, 声明测试方式
修饰符为 public void ,
在方法前面加注解,(@Test)
此方法就可以进行测试了(交给junit运行)
2) 需要在测试方法前做初始化工作
写一个静态方法 public static void init()
在方法前加注解 @BeforeClass
3) 需要在测试后释放资源
写一个静态方法
在方法前加注解 @AfterClass
1) 导入类的静态属性
import static java.lang.System.out;
out.println("haha");
2) 导入类的静态方法
import static java.lang.Math.*; // 导入Math类的所有静态成员
int num = abs(-10);
String [] arr = {"a", "b", "c"}; //数组的静态定义方式,只试用于数组首次定义的时候
// 传统方式
for(int i=0; i<arr.length; i++) {
// i依次表示数组的角标
String s = arr[i];
System.out.println(s);
}
System.out.println("-------------------------------------");
// 在jdk5中我们可以使用增强for循环迭代
// 增强for循环括号里写两个参数,第一个是声明一个变量,变量类型必须是数组元素的类型
// 第二个就是需要迭代的容器
// for循环会循环容器的length次, 每次都将容器的第n-1个元素赋值给声明的变量
for(String s : arr) {
// 循环体, 执行arr.length
// 每次都将arr中的第n-1个元素给s
System.out.println(s); //
}
int --> Integer
byte --> Byte
short --> Short
long --> Long
char --> Character
double --> Double
float --> Float
boolean --> Boolean
1) Integer x = 1; x = x + 1; 经历了什么过程? 装箱à 拆箱 à 装箱
2) 为了优化,虚拟机为包装类提供了缓冲池, Integer池的大小 -128~127 一个字节的大小
3) String池
Java为了优化字符串操作 提供了一个缓冲池
面试题:
String s = “abc” 和 String s = new String(“abc”) 的区别
String s = new String(“abc”) 创建了几个对象
String s = “a” + “b” + “c” + “d” 创建了几个对象
String s1 = “a” String s2 = “b” String s3 = s1 + s2; s3==”ab”?
/*1. String s = "abc", 虚拟机首先会检查String池里有没有"abc"对象(通过equals方法)
// 如果有,直接返回引用,如果没有,会在池里创建一个“abc”对象,并返回引用
String s1 = "abc";
String s2 = "abc";
System.out.println(s1==s2); // result: true
*/
/* 2. String str = new String("abc");
不管缓冲池是否有"abc", 都会在堆内存创建一个"abc"对象,返回引用
// 此时,负责检查并维护缓冲池,其实堆内存的对象是缓冲池中"abc"对象的一个拷贝
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1==s2); // result: false
*/
/* 3. String s = "a" + "b" + "c" + "d"; java编译器有个合并已知量的优化功能
// 在编译阶段就把"a" + "b" + "c" + "d" 合并为 ”abcd“
String s = "a" + "b" + "c" + "d";
// String s = "abcd";
System.out.println(s=="abcd");// result: true
*/
/* 4. String s1 = "a"; String s2 = "b"; String s3 = s1 + s2;
// String是常量,不能相加的,java如何实现的?
StringBuilder sb = new StringBuidler(s1);
sb.append(s2);
s3 = sb.toString();
也就是说实际上s3是方法返回的String对象
凡是方法返回的字符串对象都是在堆内存的
*/
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2; // 堆内存的对象
System.out.println(s3=="ab");// result: false
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 传统方式1
/* 1.获得迭代器
Iterator iter = list.iterator();
// 2.循环判断迭代器是否有下一个
while(iter.hasNext()) {
String str = (String) iter.next(); // 将迭代器的指针移向下一个,并将迭代当前指向的元素返回
System.out.println(str);
}
*/
// 传统方式2
for(Iterator iter=list.iterator(); iter.hasNext(); ) {
String s = (String) iter.next();
System.out.println(s);
}
System.out.println("--------------------------------");
// 增强for循环, 没有使用泛型的集合能不能使用增强for循环迭代?能
for(Object obj : list) {
String s = (String) obj;
System.out.println(s);
}
Map map = new HashMap();
map.put("a", "aaa");
map.put("b", "bbb");
map.put("c", "ccc");
// 传统方式迭代1
// 1. 获得所有的key
Set keys = map.keySet();
// 2.迭代keys获得所有的key
Iterator iter = keys.iterator();
while(iter.hasNext()) {
String key = (String) iter.next(); // a b c
// 3.根据key获得对应的value
String value = (String) map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("---------------------------------");
// 传统方式2,必须掌握这种方式
// 1.获得所有的键值对Entry对象
Set entrys = map.entrySet();
// 2.迭代出所有的entry
iter = entrys.iterator();
while(iter.hasNext()) {
Map.Entry entry = (Entry) iter.next();
// 分别获得key和value
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("-------------------------------------");
System.out.println("增强for循环迭代,");
// 增强for循环迭代,
// 原则上map集合是无法使用增强for循环来迭代的,
// 因为增强for循环只能针对实现了Iterable接口的集合进行迭代
// Iterable是jdk5中新定义的接口,就一个方法iterator方法
// 只有实现了Iterable接口的类,才能保证一定有iterator方法
// java有这样的限定是因为增强for循环内部还是用迭代器实现的
// 而实际上,我们可以通过某种方式来使用增强for循环
for(Object obj : map.entrySet()) {
// obj 依次表示Entry
Map.Entry entry = (Entry) obj;
System.out.println(entry.getKey() + "=" + entry.getValue());
}
// 在使用迭代器迭代集合的过程中,不能对集合进行增删操作
@Test
public void test4() {
List list = new ArrayList();
list.add("wangwu");
list.add("zhangsan");
list.add("lisi");
Iterator iter = list.iterator();
while(iter.hasNext()) {
String name = (String) iter.next();
if("wangwu".equals(name)) {
// 从集合中删掉
//list.remove(name);
// 迭代过程中删除元素需要调用迭代器的方法
iter.remove(); // 删除我迭代的集合被我迭代的最后一个元素
}
}
// 1 2 4
System.out.println(list.size());
}
@Test
public void test5() {
List list = new ArrayList();
list.add("aa");
list.add("bb");
// 使用ListIterator迭代器
ListIterator listIterator = list.listIterator();
while(listIterator.hasNext()) {
listIterator.next();
// 迭代过程中增加元素
listIterator.add("cc");
}
System.out.println(list.size());
}
//在使用增强for循环时,不能对元素进行赋值
int[] arr = {1,2,3};
for(int num : arr) {
num = 0;
}
System.out.println(arr[1]);
1) jdk5中方法的形参可以定义为可变参数,传入实参个数可变
// 设计一个方法求n个数的和
public static int getSum(int... arr) {
// 可变参数在方法中仍被看做一个数组
int sum = 0;
for(int num : arr)
sum += num;
return sum;
}
2)Arrays.asList为例演示传入不同参数的情况
// list长度为3
List list = Arrays.asList("a","b","c");
// list长度为1, 因为考虑1.4语法
String[] arr = {"a","b","c"};
List list = Arrays.asList(arr);
// 同时符合1.4和1.5的语法,此时会优先考虑1.4的语法
// 原因是有了新功能要保证以前的代码不出错,向后兼容
// 现在就需要将arr作为一个元素存入集合
Object obj = arr;
List list2 = Arrays.asList(obj); // 此时只符合1.5的语法,不符合1.4的语法,没有歧义
List list3 = Arrays.asList(new Object[]{arr}); // 优先考虑1.4,所以数组会拆开
//System.out.println(list3.size());
// 基本数据类型数组只符合1.5的语法
int[] nums = {1,2,3};
list = Arrays.asList(nums);
System.out.println(list.size());
问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个
解决办法:
1) 在setGrade方法中做判断,不符合格式要求就抛出异常
2) 直接限定用户的选择,通过自定义类模拟枚举的方式来限定用户的输入
写一个Grade类,私有构造函数,对外提供5个静态的常量表示类的实例
3) jdk5中新定义了枚举类型,专门用于解决此类问题
4) 枚举就是一个特殊的java类,可以定义属性、方法、构造函数、实现接口、继承类
//枚举类就是一个java类,也可以声明属性,方法,构造函数
public enum Grade4 {
A("90-100"),B("80-89"),C("70-79"),D("60-69"),E("0-59");
private String value;
private Grade4(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
//枚举类就是一个java类, 也可以继承抽象和实现接口
public enum Grade5 {
// 抽象类不能创建实例对象
A("90-100"){
// new了一个Grade5的子类实例
public String toLocaleString() {
return "优";
}
}
,B("80-89"){
// new了一个Grade5的子类实例
public String toLocaleString() {
return "良";
}
}
,C("70-79"){
// new了一个Grade5的子类实例
public String toLocaleString() {
return "中";
}
}
,D("60-69"){
// new了一个Grade5的子类实例
public String toLocaleString() {
return "差";
}
}
,E("0-59"){
// new了一个Grade5的子类实例
public String toLocaleString() {
return "不及格";
}
};
private String value;
private Grade5(String value) {
this.value = value;
}
public String getValue() {
return value;
}
// 对外提供一个方法,返回枚举的本地信息
// 一个方法不知道如何实现,可以定义为抽象的
public abstract String toLocaleString();
}
l 练习:请编写一个关于星期几的枚举WeekDay,要求:
Class对象封装了一个java类中定义的成员变量、成员方法、构造方法、类名、包名等
获得class对象的三种方式和区别:
// 1. 根据给定的类名来获得 用于类加载
String classname = "cn.itcast.reflect.Person"; // 来自配置文件
Class clazz = Class.forName(classname); // 此对象代表Person.class
// 2. 如果拿到了对象,不知道是什么类型 用于获得对象的类型
Object obj = new Person();
Class clazz1 = obj.getClass(); // 获得对象具体的类型
// 3. 如果是明确地获得某个类的Class对象 主要用于传参
Class clazz2 = Person.class;
// 在java中所有的类型都会对应一个Class对象 int Integer
Class intClazz = int.class;
Class intarrClazz = int[].class;
Class voidClazz = void.class;
反射就是获得一个java类的各个组成部分
// 反射类的成员方法
Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();
// 反射类的构造函数
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)
// 反射类的属性
Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);
到底框架是什么? 框架就是将开发中大量重复的代码集中起来写个通用的程序
框架就是用反射来实现的, 框架需要现在的类调用将来写的类
框架是将来的程序员调用的,框架不能实现完整的功能,框架只是一些一些通用的代码;
框架要依赖将来写的类来运行.
现在写的类要调用将来写的类,我们先针对接口进行调用,将来的类需要实现接口,那么方法就固定了
但是将来写的类的类名我们无法获知,这时就需要调用者通过配置文件告诉框架具体的类名
1) 泛型是一种可变化的类型, 类型不确定,需要调用者来指定
2) 用途:
一个类的多个成员方法用到的参数类型或返回值类型都是未知的类型,但又需要是同一个类型,就可将方法的
参数类型定义为泛型,此泛型必须在类上先予以声明才能在方法中使用
一个方法的多个参数和返回值需要是同一个类型,也可以用泛型来解决,在方法返回值前面声明泛型
泛型的细节:
1) 泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型
2) 使用带泛型的类创建对象时, 等式两边指定的泛型必须一致
原因: 编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了
3) 等式两边可以在任意一边使用泛型 在另一边不使用 (考虑向后兼容)
3. 泛型的基本概念
以List<E>为例:<>念着typeof 例, List<String> 就是 List typeof String
List<E>中的E称为类型参数变量 方法定义参数形式参数
List<Integer>中的Integer称为实际类型参数
整个List<E>称为泛型类型 GenericType
整个List<Integer>称为参数化的泛型类型
4. 泛型的使用
1)使用带泛型的类时,在创建对象时可以为泛型指定实际类型参数,指定的具体类型相当于给泛型传参
2)子类在继承父类的时候,可以为父类定义的泛型指定实际类型参数
class B<T>
class A extends B<String>
通过子类A获得的父类类型就是一个参数化的类型
3)调用方法时传入参数的具体类型将作为方法中泛型的实际类型
Lesson 2 Servlet(超级重要)
用 java 语言开发动态的web资源,接下来就是介绍如何开发动态的web资源
对于java程序员而言,所谓动态web资源就是à可以运行在服务器上的java程序
开发人员写好一个java类,到底有哪些方法tomcat服务器是不可能知道的
tomcat服务器欲执行我们编写的java类, 就需要知道我们的java类有哪些方法,然后在适当的时间调用这些方法, 所以我们在写的java程序要想运行在服务器上,就必须要实现一个特殊的接口 Servlet.java
interface Servlet { ... }
Servlet接口中就定义了可以被tomcat服务器调用的java方法
通常来讲,我们将实现了Servlet接口的java类称之为 Servlet
编写好的Servlet需要在web.xml文件中做配置, 才能供外界访问
3.1 写一个java类实现Servlet接口
package cn.itcast.servlet;
import java.io.*;
import javax.servlet.*;
public class HelloWorldServlet extends GenericServlet
{
// 实现 service 方法
public void service(ServletRequest request,ServletResponse response)
throws ServletException,java.io.IOException {
// 向浏览器输出一句话
PrintWriter out = response.getWriter();
out.write("hello world!!!");
}
public void init()throws ServletException {
// 初始化 servlet 时被调用
System.out.println("init()");
}
public void destroy() {
// 摧毁 servlet 时被调用
System.out.println("destroy()");
}
}
3.2. 导入 servlet jar包
set classpath=%classpath%;D:\apache-tomcat-6.0.20\lib\servlet-api.jar
3.3. 编译带包的类
javac -d . HelloWorldServlet.java
3.4. 将包拷贝至 day05/WEB-INF/classes 目录下 --> 发布 web 工程
3.5. 在 web.xml 文件中做映射
<!-- 做servlet映射 -->
<!-- servlet元素用于给一个类起别名 -->
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>cn.itcast.servlet.HelloWorldServlet</servlet-class>
</servlet>
<!-- servlet-mapping元素用于将一个Servlet映射到url -->
<!—url必须以/开头,/ 表示当前web应用即上下文路径 -->
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/HelloWorldServlet</url-pattern>
</servlet-mapping>
注意: servlet 对象一旦创建就会驻留在内存中,为所有的请求服务,
servlet 对象什么时候销毁? 直到服务器关闭时或web应用被移除才销毁
3.6. Servlet 执行流程图
4.1. 建一个 web project
4.2. 在src下建包,创建一个java类实现Servlet接口
4.3 在 Webroot\WEB-INF\web.xml 做 servlet 映射
4.4 配置 tomcat 服务器
window--> preferences --> tomcat6.x
4.5 将web工程发布至tomcat 服务器
发布的web应用名称可以配置: web工程右键 选properties-->myeclipse-->web
默认情况使用工程名作为发布后的web应用名
4.6 启动tomcat服务器运行程序
一般来讲我们开发一个Servlet会去继承 HttpServlet
在 eclipse 下开发Servlet 可以直接新建一个Servlet, 覆写 HttpServlet 的 doGet和doPost方法
继承 HttpServlet 的原因是: HttpServlet实现了service方法,将ServletRequst和ServletResponse
强转为子类 HttpServletRequest和HttpServletResponse,让我们用起来更加方便,同时,在service方法中,它判断了请求方式,根据请求方式来调用 doGet 和 doPost
1. * 号统配符
一个Servlet可以映射为多个路径
在映射 Servlet 路径时可以使用‘/*’ 或 ‘*.扩展名’ 的形式
注意: 两者不能同时使用
/* 具有较高的优先级
2. load-on-startup 元素
<servlet>元素下可以配置< load-on-startup>子元素,
配置方式如下:
<load-on-startup>1</load-on-startup>
如果一个Servlet配置了该项,web容器会在web应用被加载时就初始化该Servlet,数字越小则越先初始化
3. tomcat\conf\web.xml
服务器下所有web 应用中的web.xml 都会自动继承该文件中所有的配置
http://localhost:8080/day05/a.html a.html是资源名
上面的url访问的url在web.xml文件中并没有配置
此时会去访问缺省的Servlet,在tomcat\conf\web.xml文件中就配置了一个缺省的DefaultServlet
DefaultServlet帮我们去web应用下读取 a.html 文件,并打给浏览器,如果没有发送 404 页面
也就说,我们通过ie访问服务器访问的都是 Servlet
4. Servlet线程安全问题
解决Servlet线程安全问题: 加上同步的锁(lock)
实现SingleThreadModel接口的Servlet
服务器会做判断,当有请求过来,如果Servlet对象忙着呢,服务器会再创建一个Servlet对象为用户
提供服务,如果Servlet闲置,就直接提供服务
这样的方式实际上是回避了线程安全问题, 单线程访问Servlet, 这样的方式不可取
Lesson 3
一、 WEB 服务器
WEB在英语中即表示网页的意思,它用于表示Internet主机上供外界访问的资源以及超链接所组成的链表
放在internet网上供外界访问的文件或程序被称为web资源
web资源被分为:
静态web资源: html、css、jpg
动态web资源:Servlet、Jsp
思考问题: 从一台计算机的 IE 浏览器如何去访问另一台计算机中的文件
3.1 两台计算机是如何实现通讯的?
IP地址(计算机的唯一标识)
IPV4 4个字节的整数,每个字节以 点号 隔开 192.168.1.100 每个字节的取值 0~255
在计算机中程序会绑定在某一个端口 0~65535 尽量用 1024 以上的
连接一台计算机就需要输入 ip 地址和端口号
作为接收方, 应该绑定ip,监听指定的端口
3.2 在本地写程序添加一个服务,供别人来访问, 假设监听 8888 端口
3.3 编码实现了一个本地服务器程序
作用: 管理本地的资源,只要将html页面放到指定的目录下,外界就可以访问了
3.4 安装服务器的目的: 开发好的web资源可以发布到服务器上,这样外界就可以通过浏览器访问了
源程序: MyServer.java
// ServerSocket 对象可以监听端口
ServerSocket serversocket = new ServerSocket(6666);
while(true) {
Socket socket = serversocket.accept(); // 等待客户端的连接请求,一旦有请求过来,就结束阻塞,返回客户端对象
// 一旦有客户来访问, 就另开一个新线程去提供服务, main线程继续等待下一个客户的连接
new Thread(new MyService(socket)).start();
}
MyService.java
// 提供服务
InputStream in = socket.getInputStream();
Thread.sleep(200);
int len = in.available(); // 估计此流不受阻塞能读取的字节数
byte[] buffer = new byte[len];
in.read(buffer);
String request = new String(buffer);
// 截取第一行
String firstLine = request.substring(0, request.indexOf("\n"));
String uriName = firstLine.split(" ")[1];
OutputStream out = socket.getOutputStream();
// 根据需要访问的资源创建 File 对象
File file = new File("src" + uriName);
if(!file.exists()) {
out.write("对不起!您访问的资源不存在!别瞎搞!!".getBytes());
out.close();
return ;
}
// 从文件读, 往浏览器写
FileInputStream fis = new FileInputStream(file);
buffer = new byte[1024];
while ((len = fis.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
socket.close();
1. 使用 tomcat6.0.20.rar 文件解压即完成安装
2. tomcat 就是一个java程序,一定会用到 jre
所以需要配置环境变量 java_home 配置成jdk的安装目录 c:\jdk1.6
tomcat的启动是通过 startup.bat 文件, 实际上 startup.bat 中是去调用
catalina.bat 文件, 而且是通过 %catalina_home%\bin\catalina.bat 去找
所以为了保证服务器启动正常, 需要配置 catalina_home 环境变量为 tomcat的安装目录
3. tomcat 的目录结构
bin : 存放一些执行文件
conf : 存放服务器的配置文件
lib : 存放tomcat 所依赖的 jar 文件
logs: 存放日志文件
temp: 存放临时文件
webapps: web applications 存放所有的web应用程序(web资源)
work: tomcat 的工作目录, jsp翻译成的Servlet就在这个目录下
4. web应用
多个web资源存放在一个目录下即为一个web应用(web应用程序、web工程)
web 应用的目录结构
静态web资源直接放在目录下
java 类放在classes目录下
web.xml 文件负责管理web应用下所有的web资源
所有jar包放在lib目录下
一个web应用(服务器上一个目录) 需要供外界访问的路径,需要映射虚拟目录
在 tomcat6 中,放在webapps下的web应用,服务器会自动做映射(将文件夹名称作为虚拟路径)
对于 webapps 目录外的web应用需要手动映射虚拟路径
1.1. 在 server.xml 文件可以配置
<host>
<Context path=”/itcast” docBase=”f:\itcast” />
</host>
1.2. 在 %tomcat目录%\conf\catalina\localhost 下写一个 xml文件
文件名就是 虚拟目录
<Context docBase=”f:\itcast” />
多级目录配置 aaa#bbb.xml 对应 /aaa/bbb
如果文件名 是 ROOT.xml 那就是配置了缺省的web应用, 访问时不需要输入 虚拟目录
Web.xml的作用: 管理 web 应用下所有的web资源
通俗地讲,一个web应用下所有的web资源如何被外界访问都需要在此文件下做映射
包括咱们后面学的Servlet jsp 都需要在这个文件中做映射
实验: 配置web应用的 首页
在 web 应用下新建目录 WEB-INF ,在此目录下 新建 web.xml 文件
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<welcome-file-list>
<welcome-file>itcast.html</welcome-file>
</welcome-file-list>
</web-app>
一个完整 url
http:\\www.sina.com:80\itcast\index.jsp
协议名 主机名 端口号 资源名 (uri)
IE 访问服务器的原理,
在IE中输入的地址中包含域名,域名就需要被翻译成服务器的IP,才能访问到服务器
新建一个 web 应用
配置成缺省的web应用
配置首页
配置tomcat监听端口为80
在 windows 中注册主机名
服务器允许在一个IP上配置多个主机,即虚拟主机
http://www.sina.com:80/index.html
中的 www.sina.com 起了两个作用:
找DNS服务器,作为域名被解析为IP
通过Host头告诉服务器访问的主机名
配置方式: 在 server.xml 文件中配置Host元素,需要指定name(主机名)、appBase(默认web应用存放目录)
appBase目录下所有的web应用,tomcat会自动映射虚拟路径
<Host name="www.sohu.com" appBase="F:\sohu\webapps" />
做实验: 给新配置的虚拟主机配置缺省的web应用
<Host name="www.sohu.com" appBase="F:\sohu\webapps" >
<Context path="" docBase="F:\sohu\webapps\abc" />
</Host>
http://www.sohu.com/abc/a.html
访问一个 a.html 静态web资源, IE 做了什么事
1) 将 www.sohu.com 作为域名发送给DNS , 解析成 IP 地址, 访问一台服务器
2) 发送 Host 头(www.sohu.com),告诉服务器我要访问的虚拟主机 ,服务器拿着Host头找匹配的Host元素
3) 将abc作为虚拟目录,告诉服务器我要访问的web应用 ,服务器拿着 abc 找匹配的web应用
4) 将 a.html 作为资源名, 告诉服务器我要访问的 web 资源, 服务器拿着 a.html 去web.xml文件中找映射
虚拟目录对应是一个web应用的目录,所以虚拟目录也被我们称作web应用路径(web应用的上下文contextpath)
1. tcp/ip 协议: 网络通信协议(连接)
ip 协议 : 127.0.0.1 ip地址对应一台计算机 (互联网层)
tcp 高级协议: 三次握手, 发送请求、返回响应、传输数据 (传输层)
2. http 协议是建立在 tcp协议的基础之上 (应用层)
3. Http协议的版本 (w3c)
Http1.0 : 建立连接,发送一次请求就断开
Http1.1 :建立连接,可以无限次发送请求
http请求消息内容: 包括一个请求行、若干消息头、以及实体内容,其中的一些消息头和实体内容都是可选的,消息头和实体内容之间要用空行隔开。
请求行
POST /itcast/ HTTP/1.1
消息头
Accept: image/gif, image/x-xbitmap, */*
Referer: http://localhost:8080/itcast/
Accept-Language: zh-CN,en-GB;q=0.8,ar-YE;q=0.7,ja-JP;q=0.5,de-CH;q=0.3,en-US;q=0.2
Content-Type: application/x-www-form-urlencoded
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; GTB6.5; CIBA)
Host: localhost:8080
Content-Length: 33
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=B0B3FB4FFB0315B3D3C620548DD4E1EB
空一行
消息体
username=zhangsan&password=123456
1. 请求行 GET /itcast/a.html HTTP/1.1
GET 为请求方式 : get方式的请求参数直接跟在url后面,例如:/itcast/a.html?username=aaa&password=111
POST方式: post方式请求参数放在消息体中传输,相对安全,get大小限制1K,post 无数据量限制
2. 请求消息头 : IE浏览器用于向服务器说明情况的(浏览器使用环境)
Accept: text/html,image/* 说明浏览器接受的数据类型
Accept-Charset: ISO-8859-1 说明浏览器使用的字符编码
Accept-Encoding: gzip,compress 说明浏览器支持的压缩格式
Accept-Language: en-us,zh-cn 说明浏览器的语言环境
Host: www.it315.org:80 说明浏览器要访问的主机名
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT 文件的修改事件,用于做缓存
Referer: http://www.it315.org/index.jsp 说明请求来自哪里,防盗链 (做实验)
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) 说明浏览器内核
Cookie 向服务器发送Cookie
Connection: close/Keep-Alive 说明连接状态
Date: Tue, 11 Jul 2000 18:23:51 GMT 客户端计算机时间
3. 实体内容(消息体)
浏览器向服务器发送的数据,例如上传的文件、提交的表单等
http响应消息的内容包括: 一个状态行(类比 http请求信息的”请求行”)、若干消息头、以及实体内容 ,其中的一些消息头和实体内容都是可选的,消息头和实体内容之间要用空行隔开。
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=GB18030
Content-Length: 766
Date: Thu, 07 Jul 2011 15:40:02 GMT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
hello
</body>
</html>
1. 状态行 HTTP/1.1 404 Not Found
协议版本: 目前普遍采用的都是http1.1
响应状态码: 说明服务器的响应状态
常用状态码
状态码 |
含义 |
100~199 |
表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程 |
200~299 |
表示成功接收请求并已完成整个处理过程,常用200 |
300~399 |
为完成请求,客户需进一步细化请求。例如,请求的资源已经移动一个新地址,常用302、307和304 |
400~499 |
客户端的请求有错误,常用404 |
500~599 |
服务器端出现错误,常用 500 |
200: 一切OK
302\307 请求重定向,你访问我,我通知你访问另一个资源
304 通知浏览器去读缓存
404 找不到资源 ,客户端的请求有错误
500 服务器程序出错(服务器端的程序抛异常了)
2. 响应消息头 服务器向浏览器说明情况(处理结果)
Location: http://www.it315.org/index.jsp 通知浏览器去访问另一个资源
Server:apache tomcat 说明服务器
Content-Encoding: gzip 通知浏览器数据的压缩格式
Content-Length: 80 通知浏览器发送数据的长度
Content-Language: zh-cn 通知浏览器语言环境
Content-Type: text/html; charset=GB2312 通知浏览器文件的格式和编码
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT 告诉浏览器文件的修改时间
Refresh: 1;url=http://www.it315.org 通知浏览器自动刷新
Content-Disposition: attachment; filename=aaa.zip 通知浏览器以下载的方式打开资源
Set-Cookie:SS=Q0=5Lb_nQ; path=/search 发cookie
Expires: -1//3种禁止缓存的头字段
Cache-Control: no-cache
Pragma: no-cache
Connection: close/Keep-Alive 连接状态
Date: Tue, 11 Jul 2000 18:23:51 GMT 系统时间
3. 实体内容(响应消息体)
一般为服务器发送给ie浏览器的页面数据
1. https 是一种加密协议 能保证数据的安全
2. 不对称加密 对称加密
3. https
1) 制作数字证书
keytool -genkey -alias tomcat -keyalg RSA
2) 将证书拷贝至 tomcat\conf
3) 修改server.xml 文件 配置https 连接器
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="conf\.keystore" keystorePass="123456" />
4) 重启服务器 打开ie访问
https://localhost:8443
5) ie 中安装证书
继续浏览
查看证书
安装证书
删除证书:
Lesson 5
作用 : 封装 Servlet 初始化参数
1. 可以在 web.xml 文件中 Servlet 元素下 为Servlet配置初始化参数
<init-param>
<param-name>name</param-name>
<param-value>aaaa</param-value>
</init-param>
2. web 容器在初始化Servlet时,会将初始化参数封装到一个 ServletConfig 对象中,传给init方法
3. 我们在Servlet 中覆写 init方法,就可以获得ServletConfig
4. 父类 GenericServlet 中定义了一个成员变量用于记住此对象,并提供了 getServletConfig 方法
我们可以直接调用此方法 获得 config对象
5. 再调用 getInitParameter(name) 方法获得想要配置项
// 指定编码
// 获得ServletConfig 对象
ServletConfig config = getServletConfig();
String encoding = config.getInitParameter("encoding");
System.out.println("encoding=" + encoding);
1. ServletContext对象代表整个web应用
2. ServletContext对象是一个“域”对象(即:可以存储数据的对象)
ServletContext对象的内部维护了一个map集合, key是String类型 value是Object类型
class ServletContext {
private Map<String, Object> map ;
}
3. ServletContext 作为域对象, 多个Servlet 可以共享数据
Servlet6
// 1. 获得ServletContext 对象
ServletContext context = getServletContext();
// 2. 存入域
context.setAttribute(“name”, “zhangsan”);
Servlet7
// 获得 context 域, getAttribute
String name = (String) getServletContext().getAttribute("name");
4.获取web应用的初始化参数
getContext().getInitParameter(“name”);
5. 统计一个web应用的访问量
在 context 域中维护一个count变量
访问Servlet时,取出变量 加1
6. 实现请求转发
实现请求转发需要用到 转发对象 RequestDispatcher,RequestDispatcher有一个 forward 方法能转发请求
7.1. 读取web工程下的资源文件
// 获得绝对路径
String realPath = ServletContext.getRealPath(相对web应用的路径);
注意URL url = ServletContext.getResource(); web的url
// 获得与文件关联的流
InputStream in=
ServletContext.getResourceAsStream(“WEB-INF/classes/config.properties”;
7.2 读取java工程下的文件
// 不能相对虚拟机目录 不能用绝对路径
// 只能类加载的方式读
// 获得 流
ClassLoader classLoader = Demo.class.getClassLoader();
InputStream in = classLoader.getResourceAsStream("a.txt");
// 获得绝对路径
URL url = Demo.class.getClassLoader().getResource("a.txt");
String path = url.getPath();
类加载方式 缺点
1) 不能读取类路径以外文件
2) 由于需要加载到内存,不能读大文件
3) web工程中如果用类加载的方式读
类加载实际上读取的是内存中加载的文件,此时将读不到硬盘上资源文件的修改
解决办法: 通过绝对路径去读硬盘上的文件 避开内存的文件
例如: Demo.Class.getClassLoader().getResource("a.txt").getPath()
HttpServlet 的 Service()方法中的代码
// 调用方法
long lastModified = getLastModified(req);
// 如果为 -1 ,就直接放行,给最新的
if (lastModified == -1) {
doGet(req, resp);
}
// 方法返回不是-1
else {
// 读取IE发送的头If-Modified-Since
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
// 拿着客户端的时间头和方法的返回值比较
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
// 方法的返回值大于ie发送过来的时间头
// 重新向浏览器发送了一个时间头
maybeSetLastModified(resp, lastModified);
// 放行, 发送页面
doGet(req, resp);
} else {
// 方法的返回值没有大于ie发送过来的时间头
// 发送 304 状态码,让用户去读缓存
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
Lesson 6
1) 在计算机中数据以二进制的形式进行存储的, 数据的传输也是通二进制的形式
2)需要存字符,会出现字符与字节之间的转换 (输入字符 读到的字符)
3) 字符和字节之间如何实现转换? 都是通过查码表
4) 字符到字节是编码,字节到字符是解码, 编码和解码用到了不同码表就会出现乱码问题
1) 拿到乱码基本上都是由于解码错误导致的, 解决办法,重新编码再解码
2) 以后将文件交给解析器,出现乱码,首先想到是否通知别人文件的编码格式
Servlet对象 一旦创建就驻留在内存,
Request和response对象,web容器会针对每次用户的请求创建一个request和response
请求结束,响应发给ie。 Request和response对象就会立即被销毁
1. response getWriter方法获得字符流,用于向浏览器输出字符数据
文件名中文乱码问题
由于文件名是跟着 content-disposition 头发送给浏览器的
凡是http头的数据都会经过 url 编码, 编成全世界通用的符号,这样传输过程中才不会乱码
发送 302 状态码和 location 头
ie向浏览器发送了两次请求 第一次直接请求资源,第二次重定向的页面
地址栏是有变化
4. getWriter 和 getOutputStream 不能同时使用,有冲突
response的流我们不用关闭,交给web容器去管理
1. request 帮助我们取获取 ie 浏览器发送给 服务器的请求消息 (请求行 消息头 消息体)
2. request 获得请求参数: getParameter() 和 getParameterValues()
Request 中文参数乱码的解决方法:
1.表单提交post方式下 : request.setCharacterEncoding(“utf-8”); //指定处理消息体的解码方式
2.表单提交get方式下 :
1) 手动解决乱码问题
byte[] bytes = username.getBytes("iso-8859-1");
String str = new String(bytes, "utf-8");
2) 在 %tomcat%\conf\server.xml 中找到当前连接器元素 Connector
配置URIEncoding 属性 它用于指定web容器的url解码方式
3) 配置useBodyEncodingForURI 属性 指定为 true
用于让tomcat按照处理消息体的编码方式处理消息头
超链接提交参数:也是使用 get 方式提交,(和表单的区别是,表单提交,ie会自动进行url编码)
而超链接提交,ie不进行url编码,此时我们需要将超链接中的中文参数手动进行url编码.
同理: 在服务器端的处理方式和 表单 get 方式的处理一样
Servlet负责处理用户的请求,处理完的结果交给jsp来做显示(在Servlet中不输出任何数据,所有数据的显示都交给jsp)
获得RequestDispatcher对象做请求转发,
1.请求转发时, 需要将处理结果存入request域 带给 jsp
2.请求转发时, web容器(如:tomcat)会清空response中的数据
3.转发后, 无法向response输入数据
面试题: 请求转发forward和包含include的区别
Forward的时候,web容器会清空response的数据,并且转发之后,当前Servlet就无法再写入数据了
Include的时候,web容器会让两个Servlet的数据都写入response
1) 用户发送请求(提交表单,超链接)
2) Servlet 做处理, 将数据(响应结果)封装到 javabean对象
3) 将 javabean 对象存入request域
4) 请求转发给 jsp
5) jsp 页面中 从request域取出数据 做显示
Web资源访问的流程
1. 客户端(IE)发送请求给服务器
2. 用一个 Servlet 来响应
客户的需求基本上分为两种:
1) 查看数据
请求转发给jsp做显示
2) 用户登陆\购买商品 完成某一个特定的功能 结果都是成功或失败
请求重定向到一个目标资源
面试题: 请求转发和请求重定向的区别?
1) 请求重定向地址栏有变化 请求转发地址栏无变化
2) 请求重定向客户端向服务器发送两次请求 请求转发发送一次请求
3) 应用场景:
一件事情做完了,应该做第二件事情了,就请求重定向 (用户登陆、购物)
Servlet处理完了,让jsp做显示,用请求转发(mvc设计模式)
读取web中的文件 用 context 读取,严禁用绝对路径,通通以”/”开头
原则: 就看路径是给浏览器用的还是给服务器用的,
给浏览器用的,要加上web应用的名称
给服务器用的,斜线”/”即表示:当前web应用(/表示web应用,不用加web应用的名称)
各种场景:
1) 请求转发: 地址是给服务器用的: /表示web应用,不用加web应用的名称 /a.jsp
web.xml文件中用到了路径: 地址是给服务器用的, 不需要加web应用 /b.jsp /servlet/Servlet1
2) 请求重定向: 路径给浏览器用的,所以需要加上web应用名称 /day06/a.jsp
3) 超链接: 路径给浏览器用的,所以需要加上web应用名称 /day06/a.jsp
4) 表单的action提交: 路径给浏览器用的,/day06/a.jsp
5) img标签的src属性: 路径给浏览器用的,/day06/a.jpg
6) script src: 路径给浏览器用的 /day06/js/a.js
记住:路径 以“/”开头,请求转发不加web应用名,其他的全部都要加
Lesson 7
Cookie cookie = new Cookie(key, value);
Response.addCookie(cookie);
// 获得用户发送的所有Cookie
Cookie[] cookies = request.getCookies(); // 如果没发Cookie过来,为null
// 遍历 找到lastAccessTime
String lastAccessTime = null;
for(int i=0; cookies!=null&&i<cookies.length; i++) {
String name = cookies[i].getName(); // 一个Cookie的name
if("lastAccessTime".equals(name)) {
// 找到了, 几下value
lastAccessTime = cookies[i].getValue();
}
}
cookie默认情况下在当前浏览器进程有效,如果想让cookie长期驻留客户机的硬盘,就需要指定时间
cookie.setMaxAge(time) time以秒为单位
setMaxAge(0) 通知浏览器删除该Cookie
两个页面
显示所有的商品(从数据库找出商品显示),显示最近的记录(读取客户发送的Cookie中存的id,找出书显示)
CookieDemo2
显示商品的详细信息,并向客户端发送Cookie,难点如果产生新的Cookie
将客户端带过来的Cookie分割后存入集合,根据不同的情况将新的id加进去,迭代集合用分隔符串起来
CookieDemo3
request对象有个方法是getSession()
首先看浏览器是否发送了Cookie (JSESSIONID) ,如果发了,拿着id去内存中找对应的session对象返回;
如果没有发Cookie或者没找到对应的Session对象,创建一个新的Session对象
getSession( boolean create)
create 为true的时候,如果内存有session就返回,没有就创建新的
create为false的时候,意味着只查找不创建,有就返回,没有不创建,返回null;
如果想让多个浏览器共享一个session,我们就需要人工发送Cookie,并设置Cookie的有效时间
3.如果浏览器禁用Cookie,那么Session也玩不起来了
要想让session好用,就需要在页面跳转时发送sessionid
有一个技术url重写:
重写后的地址会在原有的url地址的基础上加上JSESSIONID
超链接或表单提交的地址,
重写方式如下:String newUrl = response.encodeURL(url);
请求重定向的地址,
重写方式如下:String newUrl = response.encodeRedirectURL(url);
简单购物车的实现 ListServlet BuyServlet ListCartServlet
用户登录 Login.jsp LoginServlet indes.jsp LogoutServlet
防止表单重复提交(2种方法)
1.可以用js来做,但是只能增加用户的体验,不能完全防止坏人
2.用session做防表单重复提交
FormServlet HandleFormServlet TokenProcessor
html: 静态web资源,DefaultServlet读取html文件,通过response输出给IE浏览器
Servlet:动态web资源,web容器(Servlet引擎)解析web.xml文件,找到url对应的java类
通过反射创建Servlet对象,调用service方法
Class.forName(“cn.itcast.servlet.Servlet1”).newInstance();
jsp: 动态web资源
jsp页面的执行过程: (重要)
1.jsp页面在第一次被访问的时候,web容器(jsp引擎)会将jsp翻译成一个Servlet,然后调用servlet的service方法
2.jsp翻译后的Servlet会被放到 %tomcat安装目录%\work\Catalina\localhost\webcontext
3.当jsp页面被再次访问的时候,web容器会去直接调用Servlet的service()方法,所以通常来讲jsp只是在第一次被访问的时候比较慢
4.如果jsp页面做了修改,此时web容器会重新翻译jsp
实际上jsp就是Servlet,只是提供了一种比较直观的书写方式,因为写jsp就像在写Html
jsp中可以写 java 代码, 有两种jsp中写java代码的方法:
1) jsp 脚本表达式
内容会被放到 out.print()里面,输出给浏览器
格式: <%=new Date() %>
2) jsp脚本片段
内容会原封不动地被翻译到 Servlet 的service方法中
<%
// java 代码
%>
1) Servlet 适合写java代码,因为Servlet就是一个java类
在开发中使用Servlet对用户发送的请求进行处理并做出响应
2) jsp 适合做数据美化,作为 数据显示模板
因为jsp页面直接书写HTML标签
3) 项目中的web层通常使用mvc设计模式 Servlet+jsp+javabean
其中, Servlet做控制器,处理用户请求
jsp作为显示模板
javabean 作为封装数据的实体
4) 如何养成一种良好的编码风格
在Servlet中应避免做任何的数据输出
在 jsp 中应避免去直接书写java代码, 而实际上要做到这点很难, 所以需要用到 el 和 jstl
在 jsp 页面中所有的 html 标签部分被称作模板元素,用于对整个网页进行布局
jsp脚本有三种形式
1) 脚本表达式: 被翻译到 out.print() 方法中
<%=new Date() %>
2) 脚本片段: 被翻译到 service()方法中
<%
for(int i=0; i<10; i++) {
System.out.println(i);
}
%>
3) jsp 声明 : 被翻译到 service()方法外, 用于:写成员变量\成员方法\静态代码块
<%!
private String name;
static {
.......
}
%>
Include指令: 用于包含一个页面
taglib指令: 用于引入标签库文件
page指令: 用于向jsp引擎说明jsp的页面情况
page指令: 一般都放在页面的开头,但是不管放在哪都对整个页面起作用
page指令: 常用的主要有一下几个:
1) import 导包, 导入多个包时需要以“,”隔开 也可以分作多条指令书写
2) session 说明是否使用session
默认值为true,被翻译的Serlvet中会自动获得Session
如果将该值指定为false 被翻译的Servlet的service方法中将不获取session
3) errorPage 指定错误跳转的页面
在 web.xml 文件中同样可以配置错误页面
可根据异常类型或错误编码进行配置error-page
4) pageEncoding
1.通知jsp引擎在翻译jsp的过程中以什么编码方式来解码jsp文件
2.通知 Servlet引擎 response编码方式,相当于 response.setContentType()
说白了-à 只要给jsp指定了 pageEncoding=”utf-8”, jsp引擎在翻译jsp时, 就会自动加上一句
response.setContentType(“text/html;charset=utf-8”)
扩展 : jsp 乱码问题(only in tomcat5)
在 tomcat6 以后jsp就没有乱码问题了, 如果是使用tomcat5 才会出现乱码问题
jsp 乱码解决 à告诉jsp引擎jsp页面是什么码,这样翻译才不会错;告诉response用什么码编码再发给浏览器
对象变量名 ( 可直接在jsp中使用 ) |
对象类型 |
config |
ServletConfig |
application |
ServletContext |
response |
HttpServletResponse |
request |
HttpServletRequest |
session |
HttpSession |
out |
JspWriter |
page |
this |
exception |
Throwable(不是每个jsp都有) |
pageContext |
PageContext |
JspWriter类型, 带缓冲的字符流 (包装流) BufferedWriter
对response.getWriter() 进行了包装,提供了缓冲区,默认大小8KB
写入该流的数据最终会被刷新到 response , 调用response.getWriter().write() 方法
Question: 什么情况下 JspWriter 会将数据刷新?
1) 缓冲区写满(默认大小为8kb, 可以在page指令中通过 buffer属性设置缓冲区大小)
2) jsp 页面结束(此流会被关闭,数据刷新到底层的流)
注意:
1) jsp中输出数据尽量使用 out, 不要使用response直接获得流输出
原因在于: 写入 out 的数据会先进缓冲区,再刷新到response; 如果两个都用,会导致后写的数据显示在前面
2) jsp 页面结束时, 会自动调用 response.getWriter() 将数据刷新
所以在jsp中不要调用 getOutputStream(), 当然也不方便做文件下载
结论: 在jsp中用out输出数据
主要功能: 用于获得其他8大隐式对象
这样做的意义:
欲移除jsp中的java代码,就需要将java代码写到一个java类的成员方法中,然后想办法在jsp
页面中调用该方法,以达到代码复用的目的.
由于jsp中的java代码难免会访问8个隐式对象,因为这些对象都是和web开发相关的对象,所以要移除这部分java代码就需要”将8个对象传递给java类”的方法,
为了方便,我们通常的做法是:只传递一个pageContext对象过去,这样在方法中就可以通过该对象很轻松地获得其他8个对象了
pageContext 也是一个域对象,但只在当前jsp页面有效
重点:
1) 默写9个对象, (具体描述9个对象怎么用)
2) 理解pageContext对象的意义 (获得其他8个对象)
Btw: pageContext 有个特殊的方法 findAttribute()
范围由小到大: page(jsp有效) request(一次请求) session(一次会话) application(当前web应用)
page : PageContext对象
request : request对象
session : session对象
application : ServletContext对象
生命周期: 就是指对象的创建到销毁的期间
page: jsp 页面被执行,生命周期开始,jsp 页面执行完毕 ,生命周期结束
request : 用户发送一个请求,开始,服务器返回响应,请求结束,生命周期结束
session : 用户打开浏览器访问,(getSession方法被调用来)创建session ---(开始)
当session超时或被声明失效,该对象生命周期结束 ---(结束)
application: web应用加载的时候创建(开始), web应用被移除或服务器关闭,对象销毁(结束)
2.1. 什么是域? 为什么把这4个对象叫做域对象呢?
域: 即范围的意思
web中的域对象:可以存储对象,在作用范围内都可以取到,其内部是Map集合的实现 Map<String, Object>
class PageContext {
private Map map = new HashMap();
private HttpSession session;
……
}
四种域对象的作用范围 ( 重要 ):
page: 只在当前jsp页面有效
request: 只在当前请求有效, 每次请求分别对应不同的request域对象
session : 只在一次会话中有效,会话结束就无法取到数据了 (特殊情况,发送Cookie)
// session: 默认情况下,同一个浏览器来访问有效(发送同一个sessionid)
application : 在一个web应用中有效 (只要服务器不关,web应用不移除就可以取数据)
四个域对象的范围由小到大排列依次为: page < request < session < application
原则: 四个域对象在选择的时候,能用范围小的绝不用范围大的
page: 数据只是暂时存放在集合中,如果要在jsp页面的其他地方使用,需要用page(页面中自定义的map)
ps: 什么时候需要用map了,就用page
request:数据只是做显示的,看完了就没用了,就存request域
ps: 请求转发, Servlet 产生的处理结果(数据) 交给jsp显示,
session: 数据给用户看完了,一会还要用,会话结束了就没用了
ps :用户登陆,用户信息发给客户端看,看完了,一会访问别的页面还要看用户信息
ps: 购物车,购物成功了,给用户看购物车,待会随时可以查看购物车
ps: 请求重定向,因为是两次请求,第一次请求的数据,第二次请求还要看
application : 数据给一个用户用完了,别人还要用
ps: 聊天室,聊天记录,需要给所有的用户看
ps: 统计网站在线人数,所有人看到的应该是一个数
总结: 需要定义Map不如用page,
请求转发Servlet带给jsp的数据存request
请求重定向带过去的数据存Session,
全局的数据存application
五、 jsp 细节
只有当jsp页面指定的page指令isErrorPage为true时,才有exception隐式对象
Jsp注释:
jsp 出错:
1) 被翻译的Servlet不能编译,语法错,这时会报告是因为jsp中的哪行导致不能编译
2) 翻译的Servlet 在运行期间出现异常, 报告是jsp的哪行导致的异常
此时会进一步报告导致异常的原因,在Servlet中的哪行出现异常
2. jsp 映射 也是通过servlet 元素
六、内省( introspect )
1)必须有无参构造函数
2)属性必须私有,我们称为字段
3)必须提供标准的get和set方法
例: name 字段 的getter: String getName() settter: void setName(String name)
Jdk中的api : PropertyDescriptor类操作Bean的属性
核心类 BeanUtils
setProperty(bean, name, value)
copyProperties(target, source);
BeanUtils的作用:
1.可以支持String到8种基本数据类型转换
2.其他引用数据类型都需要注册转换器 ConvertUtils.register(Converter, Class)
为了移除jsp页面的java代码,sun公司提供了一些内置的标签à我们称为jsp标签,或jsp动作元素
1. <jsp:include> 相当于 RequestDispatcher 对象的页面引入
dispatcher.include 用于实现 Servlet包含
dispatcher.forward 用于实现servlet转发
forward 在转发时, web容器会清空response中的数据,转发之后就无法向response写入数据
动态引入方式: 在程序运行期间引入,jsp被翻译成两个servlet (check?)
静态引入方式: include指令也能实现页面的引入,将两个jsp翻译成一个Servlet
包含和被包含的jsp页面指令不能发生冲突(其中,import和pageEncoding可以冲突)
2. <jsp:forward> 实现请求转发
结合 <jsp:param> 标签传参,自动进行url编码,编码的方式参照request编码
3. <jsp:useBean id class scope> 内省
反射创建javabean,以id作为key存入指定的域
其实在创建之前,会先去域中找,找到了则不创建
4. <jsp:setProperty> 设置属性值
<jsp:setProperty name=”user” property=”username” value=”zs” />
<jsp:setProperty name=”user” property=”username” param=”username” />
<jsp:setProperty name=”user” property=”*” /> 批量
5. <jsp:getProperty> 获得属性值
八、web开发模式
Sun公司针对web开发提供了两种模式
Model1: jsp+javabean 只适合小型应用
Model2: servlet+jsp+javabean MVC (重要)
Lesson 13: EL表达式 和 JSTL标签
1. el 全名为Expression Language。它是一种数据访问语言(不能写if语句,for循环等逻辑语句),简称: el 表达式
2. EL 能实现如下功能:
1) 使用变量访问web域中存储的对象 ${user}
2) 访问javabean的属性 ${user.address.city}
3) 执行基本的逻辑运算
4) 直接使用隐式对象
5) 调用 el 函数
3. el 表达式用在哪里
1) 在 jsp 页面直接输出数据
2) 在标签中使用el直接为属性赋值
4. el 表达式获取数据
1.在jsp页面使用el表达式可以轻松地获得web域中的对象
2.并对 javabean 、 数组、 list 、 map 进行取值
5. el表达式不能写if,for等逻辑语句,所以需要对web域中的list和map集合进行迭代就需要结合 jstl 迭代标签
JSTL是sun公司开发的一套标签库
1. JSTL标签的作用: 使用JSTL可以在页面中实现一些简单的逻辑,从而替换页面中的脚本代码
2. 如何在页面中使用JSTL标签? 在页面中使用JSTL标签需完成以下2个步骤:
1) 导入jstl.jar和standerd.jar这两个JSTL的jar文件。
2) 在JSP页面中使用 <%@ taglib=”” uri=“” prefix=“” %> 元素导入标签库。
3. 最常用的 jstl 标签为 forEach 和 if 标签
<c:foreach var=”” items=””>
<c:if test=””>
6. el表达式虽然不能进行逻辑判断和写for循环,但是el表达式中可以进行逻辑运算
7. el表达式中的保留关键字
隐含对象名称 |
描 述 |
pageContext |
对应于JSP页面中的pageContext对象 (用于获取JSP的8个隐式对象.最主要用途:${pageContext.request.contextPath} |
pageScope |
代表page域中用于保存属性的Map对象 |
requestScope |
代表request域中用于保存属性的Map对象 |
sessionScope |
代表session域中用于保存属性的Map对象 |
applicationScope |
代表application域中用于保存属性的Map对象 |
param |
表示一个保存了所有请求参数的Map对象 (应用:表单回显) |
paramValues |
表示一个保存了所有请求参数的Map对象,它对于某个请求参数,返回的是一个string[] |
header |
表示一个保存了所有http请求头字段的Map对象 |
headerValues |
同上,返回string[]数组。注意:如果头里面有“-” , 例Accept-Encoding,则要headerValues[“Accept-Encoding”] |
cookie |
表示一个保存了所有cookie的Map对象 |
initParam |
表示一个保存了所有web应用初始化参数的map对象 |
el表达式的11个隐式对象的具体用途 ( 除了pageContext,其余10个都是map类型 )
1. pageContext 作用: 获得servlet上下文路径 (web应用名称)
最常用代码: ${pageContext.request.contextPath }
2. pageScope、requestScope、sessionScope、applicationScope
作用: 分别对应每个域所对应的那个map,可以准确地获得四个域中的对象,用于取值
3. param、paramValues 作用: 获得请求参数,一般用于做表单的回显
4. header、headerValues 作用: 获得请求消息头
5. cookie 作用: 获得浏览器发送的cookie
Cookie也是map集合,key是cookie的name value是对应的cookie对象
6. initParam 作用: 获得web 初始化参数
1. 我们可以开发自定义函数,用于调用java类的方法
案例: 对页面输出的内容进行 html 转义
实现步骤:
1) 编写一个java类, 定义一个静态方法
去tomcat的例子中拷贝一个现成的
%tomcat安装目录%\webapps\examples\WEB-INF\classes
2) 编写标签库描述符(tld)文件,在tld文件中描述自定义函数
找个现成的修改一下
%tomcat安装目录%\webapps\examples\WEB-INF\jsp2
3) 在jsp页面中导入标签库 即可使用函数
<%@ taglib prefix="myfn" uri="/WEB-INF/myfn.tld" %>
<%@ taglib prefix="myfn" uri="http://www.itcast.cn/myfn" %>
帖子的内容 : ${myfn:transfer(requestScope.data) }
2. sun 公司针对常用的字符串处理在jstl技术中提供了el函数中
Lesson 17
一、自定义标签入门
jsp页面作为显示的模板,应尽量使用页面标签来实现,避免写java代码
如果在jsp页面写java代码,首先会令jsp页面难以阅读,不利于页面排版,其次,作为页面美化人员有可能会看不懂java代码,如果将java代码替换成标签,那么只要是懂html标签的人都能看得懂
移除jsp页面中的java代码其实很简单,只需要写一个java类实现Tag接口,在java类中实现jsp页面的代码,在tld标签描述文件中针对标签和java类进行映射。
案例: 实现标签显示来访者的ip
实现步骤:
1) 写一个类 IpTag 实现 Tag 接口
2) 在 WEB-INF 目录下 新建tld文件, 对标签进行描述
3)在 jsp 页面进行调用
二、传统标签 (已过时,但是框架中还是会有很多传统标签)
执行流程:
1)创建对象 初始化
2)调用setPageContext方法传入表示jsp页面的pageContext
3)如果有父标签调用 setParent 方法传入父标签
4)调用 setter 方法为属性赋值
5)调用 doStartTag 方法
6)执行标签体 可选
7)调用 doEndTag 方法
8)调用release方法释放资源
Tag.EVAL_BODY_INCLUDE 执行标签体
Tag.SKIP_BODY 跳过标签体
Tag.EVAL_PAGE 执行剩余页面
Tag.SKIP_PAGE 跳过剩余页面
1) IterationTag 接口
增加doAfterBody()方法,在方法体执行完毕时被调用
增加返回值EVAL_BODY_AGAIN 用于执行再次执行标签体
实现此接口可以实现循环执行标签体
一般通过继承默认实现类BodyTagSupport来实现该标签
2) BodyTag 接口
增加setBodyContent(BodyContent b)方法传入标签体
我们可以通过该方法获得标签体 从而对标签体进行操作
tld 是 taglib descriptor的缩写, tld文件为标签库描述文件
通过 tag元素描述一个标签
<tag>
<description>Outputs Hello, World</description>
<name>helloWorld</name>
<tag-class>cn.itcast.tag.HelloWorldTag</tag-class>
<body-content>empty</body-content>
</tag>
其中 name用于指定标签的名称(name怎么写,在jsp页面怎么写)
tag-class用于指定标签对应的java类
body-content用于指定标签对之间可以输入的内容类型
empty: 表示空标签,即不能有标签体 (常用)
JSP: 标签体不为空,可以写jsp脚本
Scriptless: 不能写jsp脚本,但可以写表达式和el (常用)
Tagdependent: 将原始内容提交 忽略el表达式等运算
需要在页面标签中使用属性需完成以下步骤:
1) 在标签类中定义一个属性<attribute>
2) 为属性添加 setter 方法
3) 在tld文件中对属性进行描述
实现:
<tag>元素的<attribute>子元素用于描述自定义
标签的一个属性,自定义标签所具有的每个属性
都要对应一个<attribute>元素 。
<attribute>
<description>description</description>
<name>aaaa</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>ObjectType</type>
</attribute>
attribute的子元素
元素名 |
是否必须指定 |
描 述 |
description |
否 |
用于指定属性的描述信息。 |
name |
是 |
用于指定属性的名称。属性名称是大小写敏感的,并且不能以jsp、_jsp、java和sun开头。 |
required |
否 |
用于指定在JSP页面中调用自定义标签时是否必须设置这个属性。其取值包括true和false,默认值为false,true表示必须设置,否则可以设置也可以不设置该属性。 |
rtexprvalue |
否 |
rtexprvalue是runtime expression value(运行时表达式)的英文简写,用于指定属性值是一个静态值或动态值。其取值包括true和false,默认值为false,false表示只能为该属性指定静态文本值,例如"123";true表示可以为该属性指定一个JSP动态元素,动态元素的结果作为属性值,例如JSP表达式<%=value %>。 |
type |
否 |
用于指定属性值的Java类型。 |
实现将标签体循环执行指定次数的标签 Loop
由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广, SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能
我们可以通过继承 SimpleTagSupport 类来实现SimpleTag接口
方法详解:
setJspContext方法
用于把JSP页面的pageContext对象传递给标签处理器对象
setParent方法
用于把父标签处理器对象传递给当前标签处理器对象
getParent方法
用于获得当前标签的父标签处理器对象
setJspBody方法
用于把代表标签体的JspFragment对象传递给标签处理器对象
doTag方法
用于完成所有的标签逻辑,包括输出、迭代、修改标签体内容等。
在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位 于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况。
Question: 方法的调用顺序?
开发防盗链标签
开发<c:if>标签
开发<c:if><c:else>标签
choose when otherwise
开发迭代标签
Foreach
开发html转义标签
打包标签库
Lesson 9
jdbc : Java Database Connectivity
sun公司为了统一对数据库的操作,定义了一套api,称之为jdbc
这套api完全由接口组成,我们在编写程序的时候针对接口进行调用
这些接口交给数据库厂家去实现, 不同的数据库厂商会提供不同的实现类,这些实现类被我们称作数据库的驱动
步骤:
建 user 表 user.sql
create database day12 character set utf8 collate utf8_general_ci;
use day12;
create table users(
id int primary key auto_increment,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
)character set utf8 collate utf8_general_ci;
insert into users(name,password,email,birthday)
values('zs','123456','[email protected]','1980-12-04');
insert into users(name,password,email,birthday)
values('lisi','123456','[email protected]','1981-12-04');
insert into users(name,password,email,birthday)
values('wangwu','123456','[email protected]','1979-12-04');
mysql-connector-java-5.0.8-bin.jar
// 1. 注册数据库的驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2. 建立与mysql数据库的连接 用到 jdbc api
String url = "jdbc:mysql://localhost:3306/day11";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 创建用于发送sql语句的 Statement 对象
Statement stmt = conn.createStatement();
// 4. 编写一句 sql
String sql = "select * from users";
// 5. 发送sql, 获得结果集
ResultSet rs = stmt.executeQuery(sql);
// 6. 处理结果集
System.out.println("id | name | password | email | birthday");
while(rs.next()) {
// 有第一行
int id = rs.getInt("id"); // 通过列名取值比较直观
String name = rs.getString("name");
String psw = rs.getString("password");
String email = rs.getString("email");
Date birthday = rs.getDate("birthday");
System.out.println(id + " | " + name + " | " + psw + " | " + email + " | " + birthday);
}
// 7. 关闭连接 释放资源
rs.close();
stmt.close();
conn.close();
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
上面的语句会导致注册两次驱动
原因在于,查看Driver类的源码会发现在静态代码块中完成了注册驱动的工作,
也就是说注册驱动其实很简单,只需要加载驱动类即可
Class.forName(“com.mysql.jdbc.Driver”);
Connection conn = DriverManager.getConnection(url, user, password);
其中:url, 相当于数据库的访问地址,程序员通过url指定需要访问的数据库
jdbc:mysql:[]//localhost:3306/test?参数名:参数值
其中jdbc为主协议,mysql为子协议,localhost为主机名,3306为端口号,test为数据库名
url的后面可以跟参数,常用的参数有:user=root&password=root&characterEncoding=UTF-8
如果url地址后面跟了user和password,创建Connection对象时将不必再次传入值
Connection conn = DriverManager.getConnection(url);
补充: 如果访问的localhost:3306,url 可省写为jdbc:mysql:///test
常用方法有:
createStatement():创建向数据库发送sql的statement对象。
prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
prepareCall(sql):创建执行存储过程的callableStatement对象。
setAutoCommit(boolean autoCommit):设置事务是否自动提交。
commit() :在链接上提交事务。
rollback() :在此链接上回滚事务。
execute(String sql):用于向数据库发送任意sql语句
executeQuery(String sql) :只能向数据发送查询语句。
executeUpdate(String sql):只能向数据库发送insert、update或delete语句
addBatch(String sql) :把多条sql语句放到一个批处理中。
executeBatch():向数据库发送一批sql语句执行。
存储的形式就是一种表格的形式,同样是列+行,说白了就和我们在 dos 命令行窗口查询的结果一样
遍历方式:
一开始游标指向结果集第一行, 也就是表头
通过 next 将游标移向下一行, 如果没有下一行,该方法会返回false
获得当前行的数据需要调用get方法:
get(int index)获得第几列 列数从1开始
get(String columnName) 根据列名获得值 (常用)
数据库的数据类型与java中数据类型的对应关系
ResultSet对象的常用方法
next():移动到下一行
previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。
afterLast() :移动到resultSet的最后面。
由于数据库的资源非常宝贵,所以用完了一定要记得释放资源
特别是Connection对象,因为数据允许的并发访问连接数量往往都比较有限
在java程序中,我们应该将最终必须要执行的代码放到finally当中
释放资源的代码
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
1. 编写程序对User表进行增删改查操作
2.防止 sql 注入
在 service 层进行逻辑判断
使用PreparedStatement对象
3. 编写工具类对 jdbc 程序进行优化
将获得连接和释放资源的代码写到通用的工具类中
实现一个简单的员工信息管理系统,练习对员工表的crud
字段名 |
说明 |
类型 |
id |
编号 |
varchar(40) |
name |
员工姓名 |
varchar(20) |
gender |
性别 |
varchar(4) |
birthday |
出生日期 |
date |
idcard |
身份证号 |
varchar(20) |
degree |
学历 |
varchar(20) |
entrydate |
入职日期 |
date |
position |
部门 |
varchar(40) |
department |
职位 |
varchar(20) |
web开发的两种模式
model1: jsp+javabean 只适合小型应用
model2: servlet+jsp+javabean mvc
jsp 的作用只是显示的模板,所以在mvc中jsp应该禁止外界直接方法,藏在 web-inf下面
web应用的分层框架
案例实现步骤:
1. 搭建开发环境
1) 建工程 建包
2) 导入需要jar包
BeanUtils 相关jar包
jstl 标签
mysql 的驱动
3) 创建数据库和表
create database day12_employee;
use day12_employee;
create table employee
(
id varchar(40) primary key,
name varchar(20),
gender varchar(6),
birthday date,
idcard varchar(20),
degree varchar(20),
entrydate date,
department varchar(20),
position varchar(40)
);
2. 设计bean
Employee.java
3. 实现dao(不是很理解..需要弄清楚)
4. 实现service
简单service 原封不动调用dao的方法
5. 实现web层
首页
在数据量较大的情况下,我们会数据分作多页显示,让用户浏览起来更加的方便,可以根据页码去翻阅每一页的数据
说到分页,一般都会立刻想到先将数据存入集合,再将数据分作多页显示,这样的做法固然可以,可是一旦数据量较大的话就会造成内存的溢出,再者说,大部分用户的浏览习惯都是只看前三页,如果数据总共有100页,那就完全没必要将数据全部从数据库中查出来了,所以一个普遍的实现方式都是根据用户需要浏览的页码,从数据库中查询一页的数据供用户浏览
分页的目的就是为了更加合理地做页面显示,所以首先要解决的就是页面需要显示哪些数据
通常来讲,页面上会显示当前页的信息、当前第几页、总共多少页、页码、上一页、下一页等信息
我们可以使用一个 Page 对象来封装页面需要实现的数据
在service中计算出 Page 对象所需的数据
Service中需要的一些来自于数据库的数据就找 Dao 索取
1)根据需求设计Page对象
2)dao的实现
两个方法:
int getTotalRecord(); // 获得总记录数
List getPageData(int start, int len); // 获得分页数据
3)service 实现
Page getPage(int pageNum); // 计算分页数据
4)Servlet 获得页面提交的 pageNum
找service计算出分页数据 Page
转发给jsp 做分页显示
所谓事务,就是针对数据库的一组操作(多条sql)
位于同一个事务的操作具备同步的特点,也就是要么都成功,要么都失败
在实际中,我们的很多操作都是需要由多条sql来共同完成的,例如,A账户给B账户转账就会对应两条sql
update account set money=money-100 where name=‘a’;
update account set money=money+100 where name=‘b’;
假设第一条sql成功了,而第二条sql失败了,这样就会导致a账户损失了100元,而b账户并未得到100元
如果将两条sql放在一个sql中,当第二条语句失败时,第一条sql语句也同样不会生效,这样a账户就不会有任何的损失----------------à这就是事务
默认情况下,我们向数据库发送的sql语句是会被自动提交的,开启事务就是相当于关闭自动提交功能,改为手动提交,我们只需要将提交事务的操作放在最后一个操作,这样一来,如果在提交事务之前出现异常,由于没有执行提交操作,事务中未提交的操作就会被回滚掉
account.sql
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
aaa 给 bbb 转账 100元
update account set money=money-100 where name='aaa';
// 异常退出
update account set money=money+100 where name='bbb';
// 查询结果
select * from accont;
如果开启事务就可以避免刚才的错误发生
// 开启事务
start transaction;
// 如果操作执行完毕,我们需要将事务提交
commit;
// 还可以手动回顾事务中所有未提交的事务
rollback;
命令行做实验得出结论:
如果开了一个事务, 又敲了一次 start transaction 再次开启事务, 前一个事务会自动提交
在使用 jdbc 操作数据库时,需要使用 Connection 对象对事务进行管理
// 开启事务
Connection.setAutoCommit(false); //设置自动提交为false
// 回滚事务
Connection.rollback();
//提交事务
Connection.commit();
在 jdbc 程序中我们还可以设置回滚点, 让事务回顾到指定的回滚点,而不是自动回滚所有未提交的操作
需要将程序中的异常捕获,在catch语句块中回滚事务,在finally中提交事务
注意 : 将 Commit 操作放在 finally 中是为了保证提交未回滚的事务操作
事务有四大特性,一般来讲,判断一个数据库是否支持事务,就看数据库是否支持这四个特性
l 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
l 一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
l 隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
l 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
由于数据库是多线程并发访问的,所以很容易出现多个线程同时开启事务的情况
多线程开事务容易引起 赃读、不可重复读、幻读 的情况发生
赃读:dirty read
是指一个事务读取了另一个事务未提交的数据,这是相当危险的。
设想一下,A要给B转账100元购买商品, 如果A开启了一个事务做了转账的工作
update account set money=money+100 while name=‘b’;
update account set money=money -100 while name=‘a’;
A先不提交事务,通知B来查询
这时B来查询账户,由于会读到A开启的事务中未提交的数据,就会发现A确实给自己转了100元,
自然就会给A发货,A等B发货后就将事务回滚,不提交,此时,B就会受到损失
不可重复读:non-repeatable read
是指事务中两次查询的结果不一致,原因是在查询的过程中其他事务做了更新的操作 update
例如,银行做报表,第一次查询A账户有100元,第二次查询A账户有200元,原因是期间A存了100元
这样就会导致一行多次统计的报表不一致
不可重复读和脏读的区别是:
脏读是读取前一事务未提交的脏数据,不可重复读是在事务内重复读取了别的线程已提交的数据。
有的时候大家会认为这样的结果是正确的,没问题
我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。
幻读:phantom read 又名虚读
是指在一个事务内两次查询中数据笔数不一致
幻读和不可重复读有些类似,是指两次查询过程中,其他事务做了插入记录的操作,导致记录数有所增加
insert
例如: 银行做报表统计account表中所有用户的总额时,此时总共五个账户,总金额为500元,
这时有一个新的账户产生了,并且存了100元,这时银行再统计会发现帐户总金额为600元了,
造成虚读同样会使银行遇到同样的困惑
实验发现不会出现虚读
来自网络的解释:幻读只是在理论上会发生的一种情况,而现实操作中并不是一定会发生
为了避免多线程开事务引发的问题,我们需要将事务进行隔离
事务有四种隔离级别,不同的隔离级别可以防止不同的错误发生
serializable:可串行化,能避免脏读、不可重复读、幻读情况的发生
repeatable read:可重读,能避免脏读、不可重复读情况的发生
read committed:读取提交的内容,可避免脏读情况发生
read uncommitted:读取未提交的内容最低级别,避免不了任何情况
操作:
//设置事务隔离级别 set transaction isolation level
//查询当前事务隔离级别 select @@tx_isolation
查询看到的都是快照
位于事务中的多次查询,如果隔离级别设置为repeatable read,
那么多次查询读的就是一个快照说白了就是不更新快照
传统的开发模式下,Servlet处理用户的请求,找Dao查询数据,dao会创建与数据库之间的链接,完成数据查询后会关闭数据库的链接。
这样的方式会导致用户每次请求都要向数据库建立链接而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。
解决方案: 就是数据库连接池
连接池就是数据库连接对象的一个缓冲池
我们可以先创建10个数据库连接缓存在连接池中,当用户有请求过来的时候,dao不必创建数据库连接,而是从数据库连接池中获取一个,用完了也不必关闭连接,而是将连接换回池子当中,继续缓存
使用数据库连接池可以极大地提高系统的性能
jdbc针对数据库连接池也定义的接口java.sql.DataSource,所有的数据库连接池实现都要实现该接口
该接口中定义了两个重载的方法
Connection getConnection()
Connection getConnection(String username, String password)
数据库连接池实现思路
1) 定义一个类实现java.sql.DataSource接口
2) 定义一个集合用于保存Connection对象,由于频繁地增删操作,用LinkedList比较好
3) 实现getConnection方法,在方法中取出LinkedList集合中的一个连接对象返回
注意:
返回的Connection对象不是从集合中获得,而是删除
用户用完Connection,会调用close方法释放资源,此时要保证连接换回连接池,而不是关闭连接
重写close方法是难点,解决方案: 装饰设计模式、动态代理
通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
一些开源组织提供了数据源的独立实现,常用的有:
DBCP 数据库连接池
C3P0 数据库连接池
DBCP 是 Apache 软件基金组织下的开源连接池实现
tomcat服务器就是使用DBCP作为数据库连接池
使用DBCP数据源,需要导入两个jar包
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
DBCP核心 API
BasicDataSource
数据源实现
BasicDataSourceFactory
用于创建数据源的工厂类
方法1: 直接创建对象,设置参数
BasicDataSource bds = new BasicDataSource();
// 设置连接数据库需要的配置信息
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://localhost:3306/jdbc3");
bds.setUsername("root");
bds.setPassword("root");
// 设置连接池的参数
bds.setInitialSize(5);
bds.setMaxActive(10);
ds = bds
方法2: 通过工厂类创建对象,读取配置文件
try {
Properties prop = new Properties();
// 读配置文件
InputStream in =
JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
prop.load(in);
ds = BasicDataSourceFactory.createDataSource(prop);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc3
username=root
password=root
#<!-- 初始化连接 -->
initialSize=5
#最大连接数量
maxActive=10
#<!-- 最大空闲连接 -->
maxIdle=10
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
c3p0是一个开源的jdbc连接池,我们熟悉的 Hibernate和 Sprint 框架使用的都是该数据源
方法1:直接创建对象,设置参数
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc3");
cpds.setUser("root");
cpds.setPassword("root");
cpds.setInitialPoolSize(5);
cpds.setMaxPoolSize(15);
方法2:读取配置文件
ComboPooledDataSource cpds = new ComboPooledDataSource("itcast");
配置文件为c3p0-config.xml 该文件需要放在类路径下
<c3p0-config>
<default-config>
<!—- 默认配置 –->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc3</property>
<property name="user">root</property>
<property name="password">root</property>
</default-config>
<named-config name="xwh">
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc3</property>
<property name="user">root</property>
<property name="password">root</property>
</named-config>
</c3p0-config>
元数据,可以理解为描述数据的数据
jdbc中的元数据是指数据库、表、列的定义信息
ResultSetMetaData对象表示结果集 ResultSet对象的元数据
获得该对象:
ResultSetMetaData metaData = rs.getMetaData();
常用方法:
getColumnCount() 返回resultset对象的列数
getColumnName(int column) 获得指定列的名称
getColumnTypeName(int column) 获得指定列的类型
使用jdbc对数据库进行crud操作时,会有很多重复的代码,仔细分析不难发现其实变化的只是其中几行代码
对于 cud(增删改) 操作,代码几乎完全一样, 唯一的区别就是sql语句不同,我们完全可以把相同的代码抽取出来定义在一个工具方法中,然后定义一个参数来接收sql语句
对于 r(查询) 操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet结果集的处理也有所不相同,因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中
// 通用的增删改方法
public static int update(String sql, Object[] params) throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 获得连接
conn = getConnection();
// 预编译sql
pstmt = conn.prepareStatement(sql);
// 将参数设置进去
for(int i=0; params!=null&&i<params.length; i++) {
pstmt.setObject(i+1, params[i]);
}
// 发送sql
int num = pstmt.executeUpdate();
return num;
} finally {
// 释放资源
release(conn, pstmt, rs);
}
}
// 优化查询
public static Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 获得连接
conn = getConnection();
// 预编译sql
pstmt = conn.prepareStatement(sql);
// 将参数设置进去
for(int i=0; params!=null&&i<params.length; i++) {
pstmt.setObject(i+1, params[i]);
}
// 发送sql
rs = pstmt.executeQuery();
// 不知道别人想如何处理结果集
// 干脆想别人所要一个结果集的处理器
// 为了让当前代码继续,定义一个结果集处理器接口
// 策略模式, 规定算法,具体的算法留给将来的调用者实现
Object obj = rsh.handle(rs);
return obj;
} finally {
// 释放资源
release(conn, pstmt, rs);
}
}
public interface ResultSetHandler {
// 处理结果集的方法
public Object handle(ResultSet rs);
}
public class BeanListHandler implements ResultSetHandler {
private Class clazz;
public BeanListHandler(Class clazz) {
this.clazz = clazz;
}
public Object handle(ResultSet rs) {
try {
// 取出结果集所有的记录,封装到bean,存入list返回
List list = new ArrayList();
while (rs.next()) {
Object bean = clazz.newInstance();
// 获得元数据
ResultSetMetaData metaData = rs.getMetaData();
// 获得列的数量
int count = metaData.getColumnCount();
// 遍历列
for(int i=1; i<=count; i++) {
// 取列名
String columnName = metaData.getColumnName(i);
// 取这列的值
Object value = rs.getObject(columnName);
// 反射出属性
Field field = clazz.getDeclaredField(columnName);
// 设置属性
field.setAccessible(true);
field.set(bean, value);
}
// 加入list
list.add(bean);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class BeanHandler implements ResultSetHandler {
private Class clazz;
public BeanHandler(Class clazz) {
this.clazz = clazz;
}
public Object handle(ResultSet rs) {
// 不知道有几列数据,不知道列名,不知道封装到什么样的bean
// 表的列明和javabean的字段名一致
try {
if(rs.next()) {
// 创建bean
Object bean = clazz.newInstance();
// 封装数据
// 获得结果集的元数据
ResultSetMetaData metaData = rs.getMetaData();
int count = metaData.getColumnCount();
// 迭代取每一列的数据
for(int i=1; i<=count; i++) {
// 获得列名 username
String columnName = metaData.getColumnName(i);
// 获得数据 ddd
Object value = rs.getObject(columnName);
// 根据列名反射出映射的属性 username
Field field = clazz.getDeclaredField(columnName);
// 为属性赋值
field.setAccessible(true);
field.set(bean, value);
}
return bean;
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
// 取出第一行的所有记录存入一个Object数组
public class ArrayHandler implements ResultSetHandler {
public Object handle(ResultSet rs) {
try {
if (rs.next()) {
// 指向了第一行的记录
// 获得元数据
ResultSetMetaData metaData = rs.getMetaData();
// 获得列数
int count = metaData.getColumnCount();
// 创建数组
Object[] arr = new Object[count];
// 迭代所有列的值,存入数组
for(int i=1; i<=count; i++) {
Object value = rs.getObject(i); // 获得指定列的值
arr[i-1] = value;
}
return arr;
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
批处理
处理大数据
Clob Character large Object
text
Blob binary large object
Object-Relation Mapping 对象关系映射(对象关系模型)
常用的 O-R Mapping 工具有:
Hibernate
session.save(user)
ibatis
sql 语句要自己写
DBUtils
简单的工具
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装
DBUtils 核心API
org.apache.commons.dbutils.QueryRunner //提供update(cud)和query(r)方法
org.apache.commons.dbutils.ResultSetHandler //结果集处理器,接口类型
org.apache.commons.dbutils.DbUtils //工具类,提供一系列close方法,装载驱动等
API详解
1. QueryRunner
重载的构造函数
public QueryRunner()
调用无参的构造方法,在进行 crud 操作时需要传入 Connection 对象,一般用于事务
public QueryRunner(DataSource ds)
创建对象时传入 数据源 多数情况下采用此构造函数
2. ResultSetHandler
该接口为结果集处理器,所以对结果集进行处理的程序都需要实现该接口
DBUtils框架提供了一系列常用的结果集处理器实现类
l ArrayHandler:把结果集中的第一行数据转成对象数组。
l ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
l BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
l BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
l ColumnListHandler:将结果集中某一列的数据存放到List中。
l KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
l MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
l MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
三、 JDBC 操作多表
多表的关系三种:
1. many2one
典型应用 部门和员工
2. mamy2many
典型应用 老师和学生
3. one2one
典型应用 用户和住址
用户上传头像、上传图片、邮件上传附件等
文件上传表单和普通表单有两个区别
1) 需要文件上传字段 <input type=”file” />
2) form 表单的 enctype 属性需要指定为 multipart/form-data
3) 文件的提交方式必须为POST
在 Servlet 中通过 request.getInputStream 获得表单上传数据,会发现数据是分段发送的
由于自己写程序解析有难度,我们可以使用Apache 开发的开源组件Commons-fileupload
需要导入 jar 包Commons-fileupload 和Commons-io
// 1. 创建工厂类
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2. 创建FileUpload对象
ServletFileUpload upload = new ServletFileUpload(factory);
// 3. 判断是否是上传表单
boolean b = upload.isMultipartContent(request);
if(!b) {
// 不是文件上传
request.setAttribute("message", "对不起,不是文件上传表单!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
// 是文件上传表单
// 4. 解析request,获得FileItem项
List<FileItem> fileitems = upload.parseRequest(request);
// 5. 遍历集合
for(FileItem item : fileitems) {
// 判断是不是普通字段
if(item.isFormField()) {
String name = item.getFieldName();
String value = item.getString();
// 手工的转换了
value = new String(value.getBytes("iso-8859-1"),"utf-8");
System.out.println(name + "=" + value);
}
else {
// 文件上传字段
// 获得文件名
String filename = item.getName();
System.out.println(filename);
filename = filename.substring(filename.lastIndexOf("\\")+1);
System.out.println(filename);
// 创建文件
ServletContext context = getServletContext();
String dir = context.getRealPath("WEN-INF/upload");
File file = new File(dir, filename);
file.createNewFile();
// 获得流,读取数据写入文件
InputStream in = item.getInputStream();
FileOutputStream fos = new FileOutputStream(file);
int len;
byte[] buffer = new byte[1024];
while((len=in.read(buffer))>0)
fos.write(buffer,0,len);
fos.close();
in.close();
item.delete(); // 删除临时文件
}
1) 文件名中文乱码问题,解决办法: 告诉文件上传组件以什么编码方式来解码文件名
ServletUpload.setCharacterEncoding(“utf-8”);
request. setCharacterEncoding(“utf-8”);
2) 普通字段中文乱码问题
fileitem.getString(“utf-8”);
对于大文件不能缓存在内存,需要缓存到硬盘,为了方便管理,我们需要设置临时文件存放目录
// 设置临时文件的存放位置
factory.setRepository(new File("d:/temp"));
文件上传完毕需要删除临时文件,否则会导致服务器存在两份上传文件
// 注意,需要先将流进行关闭,否则会导致临时文件无法删除
out.close();
in.close();
// 删除临时文件
fileitem.delete();
1) 目录需要隐藏,禁止外界直接访问
2) 文件名需要保证不重复
3) 文件应该分目录存放
需要实现对文件上传进度的监听,需要给FileUpload 对象添加 ProgressListener
在upload方法中对与进度相关的数据进行处理
upload.setProgressListener(new ProgressListener() {
long num = 0;
public void update(long bytesRead, long contentLength, int items) {
long progress = bytesRead*100/contentLength;
if(progress==num)
return;
num = progress;
System.out.println("上传进度:" + progress + "%");
// request.getSession().setAttribute("progress", progress);
}
});
实验:
1) 使用 iframe 发送请求,请求一个Servlet, 在Servlet 中返回响应,发送自增的num
此时会发现 iframe 会不停地向Servlet发送请求
2) 点击文件上传按钮后,iframe立刻停止刷新,直至上传完毕页面跳转至新页面
3)为了观察实验结果,将form 的 target 指定为 iframe, UploadServlet回送上传完毕的结果
4) 出现上述问题的原因,浏览器不支持多线程同时访问服务器只能同时发送一个请求,
这样的访问方式为同步访问
5) 要在文件上传的同时在iframe中实现进度访问,就需要ie浏览器与服务器进行异步交互
此时就需要 XMLHttpRequest 对象(ajax)
在javascript中可以直接使用XMLHttpRequest 对象与服务器进行异步通信
Step 1.获得XmlHttpRequest 对象的方式有两种
1. ie7以上版本
var xhr = null;
if(window.XMLHttpRequest)
xhr = new XMLHttpRequest();
2. ie7以下版本
if(window.ActiveXObject)
xhr = new ActiveXObject(“Microsoft.XMLHTTP”);
Step 2.获得对象后需要调用open()方法输入请求地址
注意请求方式, 地址的输入, 并且需要设置为true 指定异步访问该地址
xhr.open(“get”,”/upload/servlet/UploadServlet”, false)
// 调用send 方法发送请求,post方式需要发送消息体,get方式则不用直接传入null值
xhr.send(null);
// 访问 responseText 属性获得 Servlet 回送的数据
document.write(xhr.responseText);
//设置缓冲区大小,字节为单位,默认为10K,一般不用修改
factory.setSizeThreshold(1000);
//设置临时文件存放目录
factory.setRepository(file);
//判断是否为文件上传表单
boolean b = upload.isMultipartContent(request);
//解析request对象
List<FileItem> list = upload.parseRequest(request);
//设置上传文件的最大值
setFileSizeMax(long fileSizeMax)
//设置上传文件总量的最大值
setSizeMax(long sizeMax)
//设置编码格式
setHeaderEncoding(java.lang.String encoding)
//注册进度监听器
setProgressListener(ProgressListener pListener)
//获得表单字段的属性名
item.getFieldName();
//获得普通字段的值
item.getString(charsetName)
//获得文件上传字段的文件名
item.getName()
//获得文件上传的流
item.getInputStream()
Filter 过滤器,又称拦截器
实现 Filter 接口的类我们称之为 Filter (过滤器或拦截器)
Filter能对用户访问的资源进行拦截
在Filter里面可以用 request获得请求消息 用response写入响应消息
chain.doFilter(request, response) 方法放行 目标Servlet使用的是同一个请求和响应
doFilter 方法后面的代码会执行,在目标Servlet 返回响应后执行, 也可以使用同一个请求和响应
1) 写一个类实现 Filter 接口 , 在doFilter 方法中写功能代码
public class Filter1 implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("before");
chain.doFilter(request, response);
System.out.println("after");
}
2) 在web.xml中配置Filter拦截的资源路径
<filter>
<filter-name>filter1</filter-name>
<filter-class>cn.itcast.filter.Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
可以针对某一个url配置多个Filter, 这些Filter就会组成一个Filter链, 用FilterChain对象表示
FilterChain对象的doFilter方法作用就是让Filter链上的当前拦截器放行,请求进入下一个Filter
response 的中文编码问题, 只能在response.getWriter() 第一次被调用之前指定编码才有效
一旦指定了编码,当前Filter链和目标Servlet使用的response都是同一个编码,因为用的本来就是一个
Response,后面再指定编码将被视为无效
Filter 就像一个特殊的Servlet
Filter 在web容器启动是就初始化
Filter 可以实现拦截功能,因为有个 FilterChain 对象,有个 doFilter方法可以实现对访问资源的放行
Filter可以替代Servlet所有的功能,还多一个放行功能
实现Filter的init和destroy方法就可以观察Filter的声明周期
web容器启动时,会读web.xml文件,将所有的Filter都初始化
Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁
Filter链中所有的Filter的拦截顺序 按照 在 web.xml 文件中的配置的先后顺序来进行拦截
在 web.xml 文件中为Filter 配置初始化参数
<init-param>
<param-name>name</param-name>
<param-value>xxxx</param-value>
</init-param>
在 init 方法中读取配置文件
public void init(FilterConfig filterConfig) throws ServletException {
String name = filterConfig.getInitParameter("name");
}
1) 文件缓存
由于html页面的url是没有在 web.xml 文件中配置的 服务器会调用DefaultServlet
在DefaultServlet 中会检查文件的修改时间, 如果没有修改则发送 304头
这样就会导致过滤器也被缓存
可以通过发送 200 状态码,但是 html 页面的数据仍然得不到读取
2) html 页面乱码
在 Filter 和 Html 中指定编码为 utf-8 , 这样会导致 html 页面中文乱码
原因是 html 页面数据会通过 DefaultServlet 发送
查看 web.xml 文件 发现DefaultServlet默认使用 gbk编码
修改配置 加初始化参数
<init-param>
<param-name>fileEncoding</param-name>
<param-value>utf-8</param-value>
</init-param>
Filter的dispatcher元素有4种取值, 分别代表四种拦截方式
REQUEST 拦截直接的请求方式
INCLUDE 拦截页面包含的访问方式
FORWARD 拦截请求转发访问方式
ERROR 拦截出错页面的访问方式
拦截的url地址可以使用 /* *.扩展名
<filter-mapping> 元素中可以配置多个地址 用于拦截多个url或servlet
对于多个条件都符合的url, filter会进行多次拦截
1. 缓存
禁止浏览器缓存所有动态页面
response.setDateHeader("Expires",-1);
response.setHeader("Cache-Control","no-cache");
response.setHeader("Pragma","no-cache");
强制浏览器缓存所有的静态页面 html jpg css
String uri = request.getRequestURI();
String time = null;
if(uri.endsWith(".html"))
time = config.getInitParameter("html");
else if(uri.endsWith(".jpg"))
time = config.getInitParameter("jpg");
long l = Long.parseLong(time);
response.setDateHeader("Expires",System.currentTimeMillis() + l);
2. 实现用户自动登陆
1)在用户登陆成功后,发送一个名称为user的cookie给客户端,cookie的值为用户名和md5加密后的密码
2)编写过滤器检查用户是否带名为user的cookie来,如果有,检查用户名和密码做自动登陆
核心思路:
用户登陆后找LoginServlet , LoginServlet中做登陆,如果登陆成功, 获得用户选择的自动登陆时间
创建一个新的cookie 将用户名和密码用 “_”连接作为value,autoLogin作为name
设置cookie 的有效路径 request.getContextPath() 作用于整个web应用
设置cookie的有效时间为 autologintime
发送 cookie
写一个过滤器,对全站的资源进行拦截, 检查用户发送的cookie有没有一个名为autologin的
如果有 取出用户名和密码 再次做登陆处理 如果登陆成功, 将 user 存入session ,放行
出于安全性考虑, cookie 中的密码应该进行 md5 加密
3. 统一全站字符编码
response和request的post的方式好办
// 解决全站的乱码问题 request response
response.setContentType("text/html;charset=utf-8");
request.setCharacterEncoding("utf-8"); // 只对 post 方式起作用, 对get方式不起作用
对于request的get方式需要手工转换,此时就需要用到 包装设计模式decorator包装 getParameter方法
4. 为全站添加页眉和页脚 -> 添加用户模块
5. 发送压缩后的响应数据
给 IE 浏览器会送的数据 需要进行gzip 压缩 访问速度快 省点浏览
在最后将数据打给浏览器的时候, 将 response 中的数据全部压缩
在过滤器放行的时候传入一个 假的 response 提供缓冲区, 这样后面的资源都会写入我的缓冲区
缓冲区满了 或者 请求快结束的时候 将缓冲区的数据压缩后写入 真的 response
j2se 提供了一个流GZIPOutputStream 用于 gzip压缩
j2se 提供了一个流 ZIPOutputStream 用于 zip 压缩
6. 跟踪用户上次访问时间: Cookie lastAccessTime
7. 统计站内各个页面的访问次数
8. 实现某些资源只能被某些ip访问: ->拦截 -> 检查ip -> 决定是否放行
9. 实现防盗链功能: 针对所有的下载页面
10. 实现html标签转义、过滤敏感词汇
思路:
包装request对象
包装 getParameter 方法
将被包装对象的 getParameter 方法返回的数据进
Lesson 20
一、 事件监听
1. 在程序中经常会用到事件监听机制
2. 关键字:
事件: 用户的一个操作, 可以是点击一个按钮、调用一个方法、创建一个对象
事件源: 发生事件的对象
事件监听器: 负责监听发生在事件源上的事件
事件处理器: 监听器的成员方法,当事件发生的时候会触发对应的处理器(成员方法)
3. 事件处理机制
1) 将监听器绑定到事件源 (注册监听器)
2) 事件发生触发监听器的成员方法,即事件处理器,传递事件对象
3) 事件处理器通过事件获得事件源,进行处理
4. 做事件监听一般都需要做两件事情
1) 写一个类实现监听器接口
2) 将监听器注册到事件源上
二、 servlet事件监听器
1. 在Servlet 技术中主要有三类事件监听器:
1) 监听三个域对象的创建和销毁 application、session、request
ServletContextListener、HttpSessionListener、ServletRequestListener
三个监听器都是接口类型
应用: ServletContextListener(监听application) 用户做初始化工作和资源释放
HttpSessionListener(监听Session) 可以用于统计在线人数
ServletRequestListener(监听servlet) 可用于统计访问次数
public interface ServletContextListener
{
// 事件处理器
Init()
Destroy()
}
web应用启动时, web 容器会将所有的监听器都实例化,并绑定到对应的事件源上
2) 监听三个域对象中属性的变化(增加、删除和替换)
ServletContextAttributeListener,
HttpSessionAttributeListener
HttpServletRequestAttributeListener
处理器
attributeAdded
attributeReplaced
attributeRemoved
3) 感知对象被绑定到session域
HttpSessionBindingListener
该接口由javabean对象来实现
该监听器不需要注册
案例: 统计来访者的ip ServletRequestListener
统计在线人数 HttpSessionListener
统计在线用户
1. HttpSessionAttributeListener
当有属性增加了 getName 判断是不是user属性
如果是 说明有人登陆了 加1
当有属性移除了 getName 判断是不是user属性
如果是 说明有人注销了 减1
2. HttpSessionBindingListener
session定时扫描器 HttpSessionListener
Lesson 22
大部分的web应用都需要集成邮件发送功能
在 internet 网上发送和接收邮件都必须通过一个专门服务器,处理邮件的服务器我们称为邮件服务器
现在很多门户网站都拥有自己的邮件服务器,例如:sina、sohu、163等
电子邮箱是指用户在邮件服务器上申请的账户,
邮件服务器会为每个账户分配一定的空间用于存储发送和接收的邮件
我们发送一封电子邮件就需要将邮件发送给对方电子邮箱所在的服务器,对方可以等待服务器将信件送到邮箱或直接去服务器上收取邮件
在互联网上任何数据的传输都需要遵守协议,比如ie与服务器的数据交互遵循的是http协议
邮件在发送过程中也需要遵守一定的协议
1)用户发送一封电子邮件需要遵循 SMTP 协议
ehlo 主机名 ehlo
auth login //经过base64编码后的用户名和密码
mail from:<[email protected]>
rcpt to:<[email protected]>
Data .号代表邮件内容的结束
quit
2)用户接收一封电子邮件需要遵循 POP3 协议
user<SP>username<CRLF>
pass<SP>password<CRLF>
stat<CRLF> 返回邮箱的统计信息
list<SP>[msg#]<CRLF>返回某一封邮件的统计信息
retr<SP>msg#<CRLF> 最重要的一个命令 接收指定编号的邮件
quit<CRLF>
3)邮件服务器也会根据功能的不同分为接收邮件的服务器和发送邮件的服务器
发送邮件的服务器我们习惯称为SMTP 服务器 默认监听25端口
接收邮件的服务器我们习惯称为 POP3 服务器 默认监听110端口
4) 新浪的一个电子邮箱给搜狐的一个电子邮箱发送邮件的过程如下图所示
资料
新浪服务器 POP3服务器: pop3.sina.com,SMTP服务器: smtp.sina.com
搜狐服务器 pop3.sohu.com smtp.sohu.com
1) 安装一台易邮服务器,新建两个邮件账号
注册的账号是 zhangsan
电子邮箱地址: [email protected]
2) 在dos命令行手动输入命令完成发送邮件(smtp)和接收邮件(pop3)
提示: 用户名和密码需要使用base64编码
String username = "aaa";
String password = "123456";
BASE64Encoder encoder = new BASE64Encoder();
System.out.println(encoder.encode(userName.getBytes()));
System.out.println(encoder.encode(password.getBytes()));
3) 通过RFC822文档实现发送一封简单邮件(不安全.已淘汰)
该文档规定了如何写一封简单邮件
文档中规定邮件分为邮件头和邮件体两部分,两部分需要使用一个空行来分隔,邮件以一个’.’结束
邮件头
from 指定发件人
to 指定收件人
subject 指定主题
cc\bcc 指定抄送和密抄
邮件体
xxxxx
例:
ehlo
auth login
YWFh
MTIzNDU2
mail from:[email protected]
rcpt to:[email protected]
Data
from<[email protected]>
subject<a mail>
xxxxxxxxxxxx
.
RFC822文档有漏洞,可以冒名发送邮件
3) 配置outlook软件 完成邮件的发送和接收
1. Mime 协议
2. javamail
Session 与邮件服务器的会话
Message(抽象类)-à MimeMessage(Message的子类) ---à 用于封装Mime消息
Multipart(抽象类)-à MimeMultipart(Multipart的子类) ---à 用于封装Mime消息体
Bodypart(抽象类)-à MimeBodyPart(Bodypart的子类) ---à 用于封装Mime消息体个部分数据
Transport 用于发送邮件