码迷,mamicode.com
首页 > 编程语言 > 详细

java web -- gacl 汇总

时间:2016-12-01 09:24:56      阅读:456      评论:0      收藏:0      [点我收藏+]

标签:mvc设计模式   sources   针对   源代码   phi   super   动画   最大数   coder   

(五)——Servlet开发(一)
一、Servlet简介
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
1、编写一个Java类,实现servlet接口。
2、把开发好的Java类部署到web服务器中。
按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet
二、Servlet的运行过程
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。




三、Servlet调用图
 Servlet调用图

四、在Eclipse中开发Servlet
在eclipse中新建一个web project工程,eclipse会自动创建下图所示目录结构:
 
4.1、Servlet接口实现类
Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。
HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
4.2、通过Eclipse创建和编写Servlet
选中gacl.servlet.study包,右键→New→Servlet,如下图所示:




这样,我们就通过Eclipse帮我们创建好一个名字为ServletDemo1的Servlet,创建好的ServletDemo01里面会有如下代码:
复制代码
package gacl.servlet.study;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletDemo1 extends HttpServlet {
/**
* The doGet method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to get.
*
* @param request the request send by the client to the server
* @param response the response send by the server to the client
* @throws ServletException if an error occurred
* @throws IOException if an error occurred
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.print(" This is ");
out.print(this.getClass());
out.println(", using the GET method");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
/**
* The doPost method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to post.
*
* @param request the request send by the client to the server
* @param response the response send by the server to the client
* @throws ServletException if an error occurred
* @throws IOException if an error occurred
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.print(" This is ");
out.print(this.getClass());
out.println(", using the POST method");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
}
复制代码
这些代码都是Eclipse自动生成的,而web.xml文件中也多了<servlet></servlet>和<servlet-mapping></servlet-mapping>两对标签,这两对标签是配置ServletDemo1的,如下图所示:

然后我们就可以通过浏览器访问ServletDemo1这个Servlet,如下图所示:

五、Servlet开发注意细节
5.1、Servlet访问URL映射配置
由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。例如:
复制代码
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/servlet/ServletDemo1</url-pattern>
</servlet-mapping>
复制代码
同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的注册名。 例如:
复制代码
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/servlet/ServletDemo1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/1.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/2.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/3.php</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/4.ASPX</url-pattern>
</servlet-mapping>
复制代码
通过上面的配置,当我们想访问名称是ServletDemo1的Servlet,可以使用如下的几个地址去访问:
http://localhost:8080/JavaWeb_Servlet_Study_20140531/servlet/ServletDemo1
http://localhost:8080/JavaWeb_Servlet_Study_20140531/1.htm
http://localhost:8080/JavaWeb_Servlet_Study_20140531/2.jsp
http://localhost:8080/JavaWeb_Servlet_Study_20140531/3.php
http://localhost:8080/JavaWeb_Servlet_Study_20140531/4.ASPX
ServletDemo1被映射到了多个URL上。
5.2、Servlet访问URL使用*通配符映射
在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是"*.扩展名",另一种格式是以正斜杠(/)开头并以"/*"结尾。例如:

复制代码
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletDemo1</servlet-name>
<url-pattern>/*</url-pattern>
复制代码
*可以匹配任意的字符,所以此时可以用任意的URL去访问ServletDemo1这个Servlet,如下图所示:

对于如下的一些映射关系:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
问题:
当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪个servlet响应
Servlet引擎将调用Servlet3。
当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
匹配的原则就是"谁长得更像就找谁"
5.3、Servlet与普通Java类的区别
Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
如果在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
举例:
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>
org.apache.catalina.servlets.InvokerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
5.4、缺省Servlet
如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。 例如:
复制代码
<servlet>
<servlet-name>ServletDemo2</servlet-name>
<servlet-class>gacl.servlet.study.ServletDemo2</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 将ServletDemo2配置成缺省Servlet -->
<servlet-mapping>
<servlet-name>ServletDemo2</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
复制代码
当访问不存在的Servlet时,就使用配置的默认Servlet进行处理,如下图所示:

在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。
复制代码
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
复制代码
当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。
5.5、Servlet的线程安全问题
当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。例如下面的代码:
不存在线程安全问题的代码:
复制代码
package gacl.servlet.study;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletDemo3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 当多线程并发访问这个方法里面的代码时,会存在线程安全问题吗
* i变量被多个线程并发访问,但是没有线程安全问题,因为i是doGet方法里面的局部变量,
* 当有多个线程并发访问doGet方法时,每一个线程里面都有自己的i变量,
* 各个线程操作的都是自己的i变量,所以不存在线程安全问题
* 多线程并发访问某一个方法的时候,如果在方法内部定义了一些资源(变量,集合等)
* 那么每一个线程都有这些东西,所以就不存在线程安全问题了
*/
int i=1;
i++;
response.getWriter().write(i);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
存在线程安全问题的代码:
复制代码
package gacl.servlet.study;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletDemo3 extends HttpServlet {
int i=1;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
i++;
try {
Thread.sleep(1000*4);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getWriter().write(i+"");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
把i定义成全局变量,当多个线程并发访问变量i时,就会存在线程安全问题了,如下图所示:同时开启两个浏览器模拟并发访问同一个Servlet,本来正常来说,第一个浏览器应该看到2,而第二个浏览器应该看到3的,结果两个浏览器都看到了3,这就不正常。

线程安全问题只存在多个线程并发操作同一个资源的情况下,所以在编写Servlet的时候,如果并发访问某一个资源(变量,集合等),就会存在线程安全问题,那么该如何解决这个问题呢?
先看看下面的代码:
复制代码
package gacl.servlet.study;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletDemo3 extends HttpServlet {
int i=1;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 加了synchronized后,并发访问i时就不存在线程安全问题了,
* 为什么加了synchronized后就没有线程安全问题了呢?
* 假如现在有一个线程访问Servlet对象,那么它就先拿到了Servlet对象的那把锁
* 等到它执行完之后才会把锁还给Servlet对象,由于是它先拿到了Servlet对象的那把锁,
* 所以当有别的线程来访问这个Servlet对象时,由于锁已经被之前的线程拿走了,后面的线程只能排队等候了
*
*/
synchronized (this) {//在java中,每一个对象都有一把锁,这里的this指的就是Servlet对象
i++;
try {
Thread.sleep(1000*4);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getWriter().write(i+"");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
现在这种做法是给Servlet对象加了一把锁,保证任何时候都只有一个线程在访问该Servlet对象里面的资源,这样就不存在线程安全问题了,如下图所示:

这种做法虽然解决了线程安全问题,但是编写Servlet却万万不能用这种方式处理线程安全问题,假如有9999个人同时访问这个Servlet,那么这9999个人必须按先后顺序排队轮流访问。
针对Servlet的线程安全问题,Sun公司是提供有解决方案的:让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
查看Sevlet的API可以看到,SingleThreadModel接口中没有定义任何方法和常量,在Java中,把没有定义任何方法和常量的接口称之为标记接口,经常看到的一个最典型的标记接口就是"Serializable",这个接口也是没有定义任何方法和常量的,标记接口在Java中有什么用呢?主要作用就是给某个对象打上一个标志,告诉JVM,这个对象可以做什么,比如实现了"Serializable"接口的类的对象就可以被序列化,还有一个"Cloneable"接口,这个也是一个标记接口,在默认情况下,Java中的对象是不允许被克隆的,就像现实生活中的人一样,不允许克隆,但是只要实现了"Cloneable"接口,那么对象就可以被克隆了。
让Servlet实现了SingleThreadModel接口,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。

(六)——Servlet开发
ServletConfig讲解
1.1、配置Servlet初始化参数
  在Servlet的配置文件web.xml中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
例如:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>Chapter6</display-name>

<servlet>
<servlet-name>Demo1</servlet-name>
<servlet-class>com.demos.Demo1</servlet-class>
<init-param>
<param-name>name</param-name>
<param-value>c0</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123</param-value>
</init-param>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>/servlet/Demo1</url-pattern> <!-- 不能直接设置 /Demo1 , 否知导致 Tomcat 无法启动 -->
</servlet-mapping>

</web-app>

Demo1.java
package com.demos;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/Demo1")
public class Demo1 extends HttpServlet {
private static final long serialVersionUID = 1L;

private ServletConfig config;

public Demo1() {
super();
}

/**
* 当servlet配置了初始化参数后,web容器在创建servlet实例对象时,
* 会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,
* 将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以
* 得到当前servlet的初始化参数信息。
*/
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.config = config;
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = this.config.getInitParameter("name");
//获取在web.xml中配置单个初始化参数
response.getWriter().print(name); //获取指定的初始化参数
response.getWriter().print("<hr/>");

//获取所有的初始化参数
Enumeration<String> e = config.getInitParameterNames();
while(e.hasMoreElements()){
name = e.nextElement();
String value = config.getInitParameter(name);
response.getWriter().print(name + ":" + value + "<br/>");
}

}

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

ServletContext对象
一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。

1 多个Servlet通过ServletContext对象实现数据共享
package com.demos;

import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/ServletContextDemo1")
public class ServletContextDemo1 extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String data = "data from Demo1";

/**
* ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,
* 可以通过ServletConfig.getServletContext方法获得ServletContext对象。
*/
ServletContext context = this.getServletConfig().getServletContext();
context.setAttribute("data", data);
}

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

}

/***********************************************************************************************/
package com.demos;

import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/ServletContextDemo2")
public class ServletContextDemo2 extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String data = (String)context.getAttribute("data");
response.getWriter().print(data);
}

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

}
先运行ServletContextDemo1,将数据data存储到ServletContext对象中,然后运行ServletContextDemo2就可以从ServletContext对象中取出数据了,这样就实现了数据共享,如下图所示:

2 获取WEB应用的初始化参数
Web.xml
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/test</param-value>
</context-param>

package com.demos;

import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/ServletContextDemo3")
public class ServletContextDemo3 extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String contextInitParam = context.getInitParameter("url");
response.getWriter().print(contextInitParam);
}

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

}

3 用servletContext实现请求转发
package com.demos;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/ServletContextDemo4")
public class ServletContextDemo4 extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String data = "<h1><font color=‘red‘>abcdefghjkl</font></h1>";
response.getWriter().write(data);
// response.getOutputStream().write(data.getBytes());
ServletContext context = this.getServletContext();
RequestDispatcher dispatcher = context.getRequestDispatcher("/ServletContextDemo5"); //获取请求转发对象(RequestDispatcher)
dispatcher.forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}

package com.demos;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/ServletContextDemo5")
public class ServletContextDemo5 extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// response.getOutputStream().write("this is ServletContextDemo5".getBytes());
response.getWriter().println("This is demo5");
}

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

}

4 使用servletContext读取资源文件
package gacl.servlet.study;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 使用servletContext读取资源文件
*
* @author gacl
*
*/
public class ServletContextDemo6 extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* response.setContentType("text/html;charset=UTF-8");目的是控制浏览器用UTF-8进行解码;
* 这样就不会出现中文乱码了
*/
response.setHeader("content-type","text/html;charset=UTF-8");
readSrcDirPropCfgFile(response);//读取src目录下的properties配置文件
response.getWriter().println("<hr/>");
readWebRootDirPropCfgFile(response);//读取WebRoot目录下的properties配置文件
response.getWriter().println("<hr/>");
readPropCfgFile(response);//读取src目录下的db.config包中的db3.properties配置文件
response.getWriter().println("<hr/>");
readPropCfgFile2(response);//读取src目录下的gacl.servlet.study包中的db4.properties配置文件

}

/**
* 读取src目录下的gacl.servlet.study包(同包)中的db4.properties配置文件
* @param response
* @throws IOException
*/
private void readPropCfgFile2(HttpServletResponse response)
throws IOException {
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/gacl/servlet/study/db4.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("读取src目录下的gacl.servlet.study包中的db4.properties配置文件:");
response.getWriter().println(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}

/**
* 读取src目录下的db.config包中的db3.properties配置文件
* @param response
* @throws FileNotFoundException
* @throws IOException
*/
private void readPropCfgFile(HttpServletResponse response)
throws FileNotFoundException, IOException {
//通过ServletContext获取web资源的绝对路径
String path = this.getServletContext().getRealPath("/WEB-INF/classes/db/config/db3.properties");
InputStream in = new FileInputStream(path);
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("读取src目录下的db.config包中的db3.properties配置文件:");
response.getWriter().println(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}

/**
*读取WebRoot目录下的properties配置文件
* @param response
* @throws IOException
*/
private void readWebRootDirPropCfgFile(HttpServletResponse response)
throws IOException {
/**
* 通过ServletContext对象读取WebRoot目录下的properties配置文件
* “/”代表的是项目根目录
*/
InputStream in = this.getServletContext().getResourceAsStream("/db2.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("读取WebRoot目录下的db2.properties配置文件:");
response.getWriter().print(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}

/**
*读取src目录下的properties配置文件
* @param response
* @throws IOException
*/
private void readSrcDirPropCfgFile(HttpServletResponse response) throws IOException {
/**
* 通过ServletContext对象读取src目录下的db1.properties配置文件
*/
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db1.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("读取src目录下的db1.properties配置文件:");
response.getWriter().println(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}

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

5 使用类装载器读取资源文件
package gacl.servlet.study;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 用类装载器读取资源文件
* 通过类装载器读取资源文件的注意事项:不适合装载大文件,否则会导致jvm内存溢出
* @author gacl
*
*/
public class ServletContextDemo7 extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* response.setContentType("text/html;charset=UTF-8");目的是控制浏览器用UTF-8进行解码;
* 这样就不会出现中文乱码了
*/
response.setHeader("content-type","text/html;charset=UTF-8");
test1(response);
response.getWriter().println("<hr/>");
test2(response);
response.getWriter().println("<hr/>");
//test3();
test4();

}

/**
* 读取类路径下的资源文件
* @param response
* @throws IOException
*/
private void test1(HttpServletResponse response) throws IOException {
//获取到装载当前类的类装载器
ClassLoader loader = ServletContextDemo7.class.getClassLoader();
//用类装载器读取src目录下的db1.properties配置文件
InputStream in = loader.getResourceAsStream("db1.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("用类装载器读取src目录下的db1.properties配置文件:");
response.getWriter().println(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}

/**
* 读取类路径下面、包下面的资源文件
* @param response
* @throws IOException
*/
private void test2(HttpServletResponse response) throws IOException {
//获取到装载当前类的类装载器
ClassLoader loader = ServletContextDemo7.class.getClassLoader();
//用类装载器读取src目录下的gacl.servlet.study包中的db4.properties配置文件
InputStream in = loader.getResourceAsStream("gacl/servlet/study/db4.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("用类装载器读取src目录下的gacl.servlet.study包中的db4.properties配置文件:");
response.getWriter().println(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}

/**
* 通过类装载器读取资源文件的注意事项:不适合装载大文件,否则会导致jvm内存溢出
*/
public void test3() {
/**
* 01.avi是一个150多M的文件,使用类加载器去读取这个大文件时会导致内存溢出:
* java.lang.OutOfMemoryError: Java heap space
*/
InputStream in = ServletContextDemo7.class.getClassLoader().getResourceAsStream("01.avi");
System.out.println(in);
}

/**
* 读取01.avi,并拷贝到e:\根目录下
* 01.avi文件太大,只能用servletContext去读取
* @throws IOException
*/
public void test4() throws IOException {
// path=G:\Java学习视频\JavaWeb学习视频\JavaWeb\day05视频\01.avi
// path=01.avi
String path = this.getServletContext().getRealPath("/WEB-INF/classes/01.avi");
/**
* path.lastIndexOf("\\") + 1是一个非常绝妙的写法
*/
String filename = path.substring(path.lastIndexOf("\\") + 1);//获取文件名
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/01.avi");
byte buffer[] = new byte[1024];
int len = 0;
OutputStream out = new FileOutputStream("e:\\" + filename);
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.close();
in.close();
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

this.doGet(request, response);
}

}

客户端缓存Servlet的输出
  对于不经常变化的数据,在servlet中可以为其设置合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能。例如:

package gacl.servlet.study;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ServletDemo5 extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String data = "abcddfwerwesfasfsadf";
/**
* 设置数据合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能
* 这里是将数据的缓存时间设置为1天
*/
response.setDateHeader("expires",System.currentTimeMillis() + 24 * 3600 * 1000);
response.getOutputStream().write(data.getBytes());
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

this.doGet(request, response);
}

}

(八、七)——HttpServletResponse对象
Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。
request和response对象即然代表请求和响应,那我们要获取客户机提交过来的数据,只需要找request对象就行了。要向客户机输出数据,只需要找response对象就行了。

1 使用OutputStream流向客户端浏览器输出中文数据
package gacl.response.study;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ResponseDemo01 extends HttpServlet {
private static final long serialVersionUID = 4312868947607181532L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
outputChineseByOutputStream(response);//使用OutputStream流输出中文
}

/**
* 使用OutputStream流输出中文
* @param request
* @param response
* @throws IOException
*/
public void outputChineseByOutputStream(HttpServletResponse response) throws IOException{
/**使用OutputStream输出中文注意问题:
* 在服务器端,数据是以哪个码表输出的,那么就要控制客户端浏览器以相应的码表打开,
* 比如:outputStream.write("中国".getBytes("UTF-8"));//使用OutputStream流向客户端浏览器输出中文,以UTF-8的编码进行输出
* 此时就要控制客户端浏览器以UTF-8的编码打开,否则显示的时候就会出现中文乱码,那么在服务器端如何控制客户端浏览器以以UTF-8的编码显示数据呢?
* 可以通过设置响应头控制浏览器的行为,例如:
* response.setHeader("content-type", "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示数据
*/
String data = "中国";
OutputStream outputStream = response.getOutputStream();//获取OutputStream输出流
response.setHeader("content-type", "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
/**
* data.getBytes()是一个将字符转换成字节数组的过程,这个过程中一定会去查码表,
* 如果是中文的操作系统环境,默认就是查找查GB2312的码表,
* 将字符转换成字节数组的过程就是将中文字符转换成GB2312的码表上对应的数字
* 比如: "中"在GB2312的码表上对应的数字是98
* "国"在GB2312的码表上对应的数字是99
*/
/**
* getBytes()方法如果不带参数,那么就会根据操作系统的语言环境来选择转换码表,如果是中文操作系统,那么就使用GB2312的码表
*/
byte[] dataByteArr = data.getBytes("UTF-8");//将字符转换成字节数组,指定以UTF-8编码进行转换
outputStream.write(dataByteArr);//使用OutputStream流向客户端输出字节数组
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

客户端浏览器接收到数据后,就按照响应头上设置的字符编码来解析数据,如下所示:

2 使用PrintWriter流向客户端浏览器输出中文数据
使用PrintWriter流输出中文注意问题:
  在获取PrintWriter输出流之前首先使用"response.setCharacterEncoding(charset)"设置字符以什么样的编码输出到浏览器,如:response.setCharacterEncoding("UTF-8");设置将字符以"UTF-8"编码输出到客户端浏览器,然后再使用response.getWriter();获取PrintWriter输出流,这两个步骤不能颠倒,如下:

response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器
/**
* PrintWriter out = response.getWriter();这句代码必须放在response.setCharacterEncoding("UTF-8");之后
* 否则response.setCharacterEncoding("UTF-8")这行代码的设置将无效,浏览器显示的时候还是乱码
*/
PrintWriter out = response.getWriter();//获取PrintWriter输出流

  然后再使用response.setHeader("content-type", "text/html;charset=字符编码");设置响应头,控制浏览器以指定的字符编码编码进行显示,例如:
//通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
response.setHeader("content-type", "text/html;charset=UTF-8");

  除了可以使用response.setHeader("content-type", "text/html;charset=字符编码");设置响应头来控制浏览器以指定的字符编码编码进行显示这种方式之外,还可以用如下的方式来模拟响应头的作用
/**
* 多学一招:使用HTML语言里面的<meta>标签来控制浏览器行为,模拟通过设置响应头控制浏览器行为
*response.getWriter().write("<meta http-equiv=‘content-type‘ content=‘text/html;charset=UTF-8‘/>");
* 等同于response.setHeader("content-type", "text/html;charset=UTF-8");
*/
response.getWriter().write("<meta http-equiv=‘content-type‘ content=‘text/html;charset=UTF-8‘/>");


范例:使用PrintWriter流向客户端浏览器输出"中国"这两个汉字
package gacl.response.study;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ResponseDemo01 extends HttpServlet {
private static final long serialVersionUID = 4312868947607181532L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
outputChineseByPrintWriter(response);//使用PrintWriter流输出中文
}
/**
* 使用PrintWriter流输出中文
* @param request
* @param response
* @throws IOException
*/
public void outputChineseByPrintWriter(HttpServletResponse response) throws IOException{
String data = "中国";

//通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
//response.setHeader("content-type", "text/html;charset=UTF-8");

response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器
/**
* PrintWriter out = response.getWriter();这句代码必须放在response.setCharacterEncoding("UTF-8");之后
* 否则response.setCharacterEncoding("UTF-8")这行代码的设置将无效,浏览器显示的时候还是乱码
*/
PrintWriter out = response.getWriter();//获取PrintWriter输出流
/**
* 多学一招:使用HTML语言里面的<meta>标签来控制浏览器行为,模拟通过设置响应头控制浏览器行为
* out.write("<meta http-equiv=‘content-type‘ content=‘text/html;charset=UTF-8‘/>");
* 等同于response.setHeader("content-type", "text/html;charset=UTF-8");
*/
out.write("<meta http-equiv=‘content-type‘ content=‘text/html;charset=UTF-8‘/>");
out.write(data);//使用PrintWriter流向客户端输出字符
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
3 使用OutputStream或者PrintWriter向客户端浏览器输出数字
package gacl.response.study;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ResponseDemo01 extends HttpServlet {
private static final long serialVersionUID = 4312868947607181532L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

outputOneByOutputStream(response);//使用OutputStream输出1到客户端浏览器

}
/**
* 使用OutputStream流输出数字1
* @param request
* @param response
* @throws IOException
*/
public void outputOneByOutputStream(HttpServletResponse response) throws IOException{
response.setHeader("content-type", "text/html;charset=UTF-8");
OutputStream outputStream = response.getOutputStream();
outputStream.write("使用OutputStream流输出数字1:".getBytes("UTF-8"));
//outputStream.write(1); // 此处无法显示数字1 , 如果想在服务端显示数字, 必须先转化为字符串格式
outputStream.write((1+"").getBytes());
}

}
如果希望服务器输出什么浏览器就能看到什么,那么在服务器端都要以字符串的形式进行输出。

如果使用PrintWriter流输出数字,那么也要先将数字转换成字符串后再输出,如下:

/**
* 使用PrintWriter流输出数字1
* @param request
* @param response
* @throws IOException
*/
public void outputOneByPrintWriter(HttpServletResponse response) throws IOException{
response.setHeader("content-type", "text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();//获取PrintWriter输出流
out.write("使用PrintWriter流输出数字1:");
out.write(1+"");
}

文件下载
  文件下载功能是web开发中经常使用到的功能,使用HttpServletResponse对象就可以实现文件的下载
文件下载功能的实现思路:
  1.获取要下载的文件的绝对路径
  2.获取要下载的文件名
  3.设置content-disposition响应头控制浏览器以下载的形式打开文件
  4.获取要下载的文件输入流
  5.创建数据缓冲区
  6.通过response对象获取OutputStream流
  7.将FileInputStream流写入到buffer缓冲区
8.使用OutputStream将缓冲区的数据输出到客户端浏览器

package gacl.response.study;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 文件下载
*/
public class ResponseDemo02 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
downloadFileByOutputStream(response);//下载文件,通过OutputStream流
}
/**
* 下载文件,通过OutputStream流
* @param response
* @throws FileNotFoundException
* @throws IOException
*/
private void downloadFileByOutputStream(HttpServletResponse response)
throws FileNotFoundException, IOException {
//1.获取要下载的文件的绝对路径
String realPath = this.getServletContext().getRealPath("/download/1.JPG");
//2.获取要下载的文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);
//3.设置content-disposition响应头控制浏览器以下载的形式打开文件
response.setHeader("content-disposition", "attachment;filename="+fileName);
//4.获取要下载的文件输入流
InputStream in = new FileInputStream(realPath);
int len = 0;
//5.创建数据缓冲区
byte[] buffer = new byte[1024];
//6.通过response对象获取OutputStream流
OutputStream out = response.getOutputStream();
//7.将FileInputStream流写入到buffer缓冲区
while ((len = in.read(buffer)) > 0) {
//8.使用OutputStream将缓冲区的数据输出到客户端浏览器
out.write(buffer,0,len);
}
in.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

使用Response实现中文文件下载
package gacl.response.study;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 文件下载
*/
public class ResponseDemo02 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
downloadChineseFileByOutputStream(response);//下载中文文件
}
/**
* 下载中文文件,中文文件下载时,文件名要经过URL编码,否则会出现文件名乱码
* @param response
* @throws FileNotFoundException
* @throws IOException
*/
private void downloadChineseFileByOutputStream(HttpServletResponse response)
throws FileNotFoundException, IOException {
String realPath = this.getServletContext().getRealPath("/download/张家界国家森林公园.JPG");//获取要下载的文件的绝对路径
String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);//获取要下载的文件名

//设置content-disposition响应头控制浏览器以下载的形式打开文件,中文文件名要使用URLEncoder.encode方法进行编码,否则会出现文件名乱码
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
InputStream in = new FileInputStream(realPath);//获取文件输入流
int len = 0;
byte[] buffer = new byte[1024];
OutputStream out = response.getOutputStream();
while ((len = in.read(buffer)) > 0) {
out.write(buffer,0,len);//将缓冲区的数据输出到客户端浏览器
}
in.close();
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
文件下载注意事项:编写文件下载功能时推荐使用OutputStream流,避免使用PrintWriter流,因为OutputStream流是字节流,可以处理任意类型的数据,而PrintWriter流是字符流,只能处理字符数据,如果用字符流处理字节数据,会导致数据丢失。

范例:使用PrintWriter流下载文件
package gacl.response.study;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 文件下载
*/
public class ResponseDemo02 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
downloadFileByPrintWriter(response);//下载文件,通过PrintWriter流
}
/**
* 下载文件,通过PrintWriter流,虽然也能够实现下载,但是会导致数据丢失,因此不推荐使用PrintWriter流下载文件
* @param response
* @throws FileNotFoundException
* @throws IOException
*/
private void downloadFileByPrintWriter(HttpServletResponse response)
throws FileNotFoundException, IOException {
String realPath = this.getServletContext().getRealPath("/download/张家界国家森林公园.JPG");//获取要下载的文件的绝对路径
String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);//获取要下载的文件名
//设置content-disposition响应头控制浏览器以下载的形式打开文件,中文文件名要使用URLEncoder.encode方法进行编码
response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8"));
FileReader in = new FileReader(realPath);
int len = 0;
char[] buffer = new char[1024];
PrintWriter out = response.getWriter();
while ((len = in.read(buffer)) > 0) {
out.write(buffer,0,len);//将缓冲区的数据输出到客户端浏览器, 此处读取图片数据,最终会导致图片数据丢失
}
in.close();
}

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

生成随机图片用作验证码
  生成图片主要用到了一个BufferedImage类
package gacl.response.study;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ResponseDemo03 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

response.setHeader("refresh", "5");//设置refresh响应头控制浏览器每隔5秒钟刷新一次
//1.在内存中创建一张图片
BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
//2.得到图片
//Graphics g = image.getGraphics();
Graphics2D g = (Graphics2D)image.getGraphics();
g.setColor(Color.WHITE);//设置图片的背景色
g.fillRect(0, 0, 80, 20);//填充背景色
//3.向图片上写数据
g.setColor(Color.BLUE);//设置图片上字体的颜色
g.setFont(new Font(null, Font.BOLD, 20));
g.drawString(makeNum(), 0, 20);
//4.设置响应头控制浏览器浏览器以图片的方式打开
response.setContentType("image/jpeg");//等同于response.setHeader("Content-Type", "image/jpeg");
//5.设置响应头控制浏览器不缓存图片数据
response.setDateHeader("expries", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
//6.将图片写给浏览器
ImageIO.write(image, "jpg", response.getOutputStream());
}
/**
* 生成随机数字
* @return
*/
private String makeNum(){
Random random = new Random();
String num = random.nextInt(9999999)+"";
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 7-num.length(); i++) {
sb.append("0");
}
num = sb.toString()+num;
return num;
}

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

结果每隔5秒自动刷新一次

response实现请求重定向
  请求重定向指:一个web资源收到客户端请求后,通知客户端去访问另外一个web资源,这称之为请求重定向。
// 应用场景:用户登陆,用户首先访问登录页面,登录成功后,就会跳转到某个页面,这个过程就是一个请求重定向的过程
// 实现方式:response.sendRedirect(String location),即调用response对象的sendRedirect方法实现请求重定向
// sendRedirect内部的实现原理:使用response设置302状态码和设置location响应头实现重定向
package gacl.response.study;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ResponseDemo04 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 1.调用sendRedirect方法实现请求重定向,
* sendRedirect方法内部调用了
* response.setHeader("Location", "/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");
* response.setStatus(HttpServletResponse.SC_FOUND);//设置302状态码,等同于response.setStatus(302);
*/
response.sendRedirect("/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");

//2.使用response设置302状态码和设置location响应头实现重定向实现请求重定向
//response.setHeader("Location", "/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");
//response.setStatus(HttpServletResponse.SC_FOUND);//设置302状态码,等同于response.setStatus(302);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
(九)——通过Servlet生成验证码图片
1 BufferedImage -- DrawImage
package gacl.response.study;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 生成随机图片,用来作为验证码
*/
public class DrawImage extends HttpServlet {
private static final long serialVersionUID = 3038623696184546092L;

public static final int WIDTH = 120;//生成的图片的宽度
public static final int HEIGHT = 30;//生成的图片的高度
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String createTypeFlag = request.getParameter("createTypeFlag");//接收客户端传递的createTypeFlag标识
//1.在内存中创建一张图片
BufferedImage bi = new BufferedImage(WIDTH, HEIGHT,BufferedImage.TYPE_INT_RGB);
//2.得到图片
Graphics g = bi.getGraphics();
//3.设置图片的背影色
setBackGround(g);
//4.设置图片的边框
setBorder(g);
//5.在图片上画干扰线
drawRandomLine(g);
//6.写在图片上随机数
//String random = drawRandomNum((Graphics2D) g,"ch");//生成中文验证码图片
//String random = drawRandomNum((Graphics2D) g,"nl");//生成数字和字母组合的验证码图片
//String random = drawRandomNum((Graphics2D) g,"n");//生成纯数字的验证码图片
//String random = drawRandomNum((Graphics2D) g,"l");//生成纯字母的验证码图片
String random = drawRandomNum((Graphics2D) g,createTypeFlag);//根据客户端传递的createTypeFlag标识生成验证码图片
//7.将随机数存在session中
request.getSession().setAttribute("checkcode", random);
//8.设置响应头通知浏览器以图片的形式打开
response.setContentType("image/jpeg");//等同于response.setHeader("Content-Type", "image/jpeg");
//9.设置响应头控制浏览器不要缓存
response.setDateHeader("expries", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
//10.将图片写给浏览器
ImageIO.write(bi, "jpg", response.getOutputStream());
}
/**
* 设置图片的背景色
* @param g
*/
private void setBackGround(Graphics g) {
// 设置颜色
g.setColor(Color.WHITE);
// 填充区域
g.fillRect(0, 0, WIDTH, HEIGHT);
}
/**
* 设置图片的边框
* @param g
*/
private void setBorder(Graphics g) {
// 设置边框颜色
g.setColor(Color.BLUE);
// 边框区域
g.drawRect(1, 1, WIDTH - 2, HEIGHT - 2);
}
/**
* 在图片上画随机线条
* @param g
*/
private void drawRandomLine(Graphics g) {
// 设置颜色
g.setColor(Color.GREEN);
// 设置线条个数并画线
for (int i = 0; i < 5; i++) {
int x1 = new Random().nextInt(WIDTH);
int y1 = new Random().nextInt(HEIGHT);
int x2 = new Random().nextInt(WIDTH);
int y2 = new Random().nextInt(HEIGHT);
g.drawLine(x1, y1, x2, y2);
}
}
/**
* 画随机字符
* @param g
* @param createTypeFlag
* @return
* String... createTypeFlag是可变参数,
* Java1.5增加了新特性:可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。注意:可变参数必须位于最后一项
*/
private String drawRandomNum(Graphics2D g,String... createTypeFlag) {
// 设置颜色
g.setColor(Color.RED);
// 设置字体
g.setFont(new Font("宋体", Font.BOLD, 20));
//常用的中国汉字
String baseChineseChar = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6";
//数字和字母的组合
String baseNumLetter = "0123456789ABCDEFGHJKLMNOPQRSTUVWXYZ";
//纯数字
String baseNum = "0123456789";
//纯字母
String baseLetter = "ABCDEFGHJKLMNOPQRSTUVWXYZ";
//createTypeFlag[0]==null表示没有传递参数
if (createTypeFlag.length > 0 && null != createTypeFlag[0]) {
if (createTypeFlag[0].equals("ch")) {
// 截取汉字
return createRandomChar(g, baseChineseChar);
}else if (createTypeFlag[0].equals("nl")) {
// 截取数字和字母的组合
return createRandomChar(g, baseNumLetter);
}else if (createTypeFlag[0].equals("n")) {
// 截取数字
return createRandomChar(g, baseNum);
}else if (createTypeFlag[0].equals("l")) {
// 截取字母
return createRandomChar(g, baseLetter);
}
}else {
// 默认截取数字和字母的组合
return createRandomChar(g, baseNumLetter);
}

return "";
}
/**
* 创建随机字符
* @param g
* @param baseChar
* @return 随机字符
*/
private String createRandomChar(Graphics2D g,String baseChar) {
StringBuffer sb = new StringBuffer();
int x = 5;
String ch ="";
// 控制字数
for (int i = 0; i < 4; i++) {
// 设置字体旋转角度
int degree = new Random().nextInt() % 30;
ch = baseChar.charAt(new Random().nextInt(baseChar.length())) + "";
sb.append(ch);
// 正向角度 先正向旋转, 添加字符, 再反向旋转回来, 这样相当于图片没转, 字符旋转了
g.rotate(degree * Math.PI / 180, x, 20);
g.drawString(ch, x, 20);
// 反向角度
g.rotate(-degree * Math.PI / 180, x, 20);
x += 30;
}
return sb.toString();
}
}
2 在Form表单中使用验证码图片
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>在Form表单中使用验证码</title>
<script type="text/javascript">
//刷新验证码
function changeImg(){
// 在末尾加Math.random()的作用:
// 如果两次请求地址一样,服务器只会处理第一次请求,第二次请求返回内容和第一次一样。或者说如果地址相同,第一次请求时,将自动缓存,导致第二次不会重复请求了。Math.random()是调用javascript语法中的数学函数,能够产生随机数。
// 末尾加Math.random()使每次请求地址不相同,服务器每次都去做不同的响应。也可以使用new date()时间戳的形式作为参数传递。
document.getElementById("validateCodeImg").src="${pageContext.request.contextPath}/servlet/DrawImage?"+Math.random();
}
</script>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/CheckServlet" method="post">
验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/servlet/DrawImage" id="validateCodeImg" onclick="changeImg()">
<a href="javascript:void(0)" onclick="changeImg()">看不清,换一张</a>
<br/>
<input type="submit" value="提交">
</form>
</body>
</html>

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>在Form表单中使用验证码</title>
<script type="text/javascript">
//刷新验证码
function changeImg(obj,createTypeFlag){
document.getElementById(obj.id).src="${pageContext.request.contextPath}/servlet/DrawImage?createTypeFlag="+createTypeFlag+"&"+Math.random();
}
</script>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/CheckServlet" method="post">
数字字母混合验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/servlet/DrawImage" id="validateCodeImg1" onclick="changeImg(this,‘nl‘)">
<br/>
中文验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/servlet/DrawImage?createTypeFlag=ch" id="validateCodeImg2" onclick="changeImg(this,‘ch‘)">
<br/>
英文验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/servlet/DrawImage?createTypeFlag=l" id="validateCodeImg3" onclick="changeImg(this,‘l‘)">
<br/>
数字验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/servlet/DrawImage?createTypeFlag=n" id="validateCodeImg4" onclick="changeImg(this,‘n‘)">
<br/>
<input type="submit" value="提交">
</form>
</body>
</html>

3 服务器端对form表单提交上来的验证码处理
package gacl.response.study;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 服务器端接收到验证码后的处理
*/
public class CheckServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String clientCheckcode = request.getParameter("validateCode");//接收客户端浏览器提交上来的验证码
String serverCheckcode = (String) request.getSession().getAttribute("checkcode");//从服务器端的session中取出验证码
if (clientCheckcode.equals(serverCheckcode)) {//将客户端验证码和服务器端验证比较,如果相等,则表示验证通过
System.out.println("验证码验证通过!");
}else {
System.out.println("验证码验证失败!");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

(十)——HttpServletRequest对象
HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。
1 获得客户机信息
  getRequestURL 方法返回客户端发出请求时的完整URL。
  getRequestURI 方法返回请求行中的资源名部分。
  getQueryString 方法返回请求行中的参数部分。
  getPathInfo 方法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头。
  getRemoteAddr 方法返回发出请求的客户机的IP地址。
  getRemoteHost 方法返回发出请求的客户机的完整主机名。
  getRemotePort 方法返回客户机所使用的网络端口号。
  getLocalAddr 方法返回WEB服务器的IP地址。
getLocalName 方法返回WEB服务器的主机名。

范例:通过 request 对象获取客户端请求信息
package gacl.request.study;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 通过request对象获取客户端请求信息
*/
public class RequestDemo01 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 1.获得客户机信息
*/
String requestUrl = request.getRequestURL().toString();//得到请求的URL地址
String requestUri = request.getRequestURI();//得到请求的资源
String queryString = request.getQueryString();//得到请求的URL地址中附带的参数
String remoteAddr = request.getRemoteAddr();//得到来访者的IP地址
String remoteHost = request.getRemoteHost();
int remotePort = request.getRemotePort();
String remoteUser = request.getRemoteUser();
String method = request.getMethod();//得到请求URL地址时使用的方法
String pathInfo = request.getPathInfo();
String localAddr = request.getLocalAddr();//获取WEB服务器的IP地址
String localName = request.getLocalName();//获取WEB服务器的主机名
response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器
//通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
response.setHeader("content-type", "text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("获取到的客户机信息如下:");
out.write("<hr/>");
out.write("请求的URL地址:"+requestUrl);
out.write("<br/>");
out.write("请求的资源:"+requestUri);
out.write("<br/>");
out.write("请求的URL地址中附带的参数:"+queryString);
out.write("<br/>");
out.write("来访者的IP地址:"+remoteAddr);
out.write("<br/>");
out.write("来访者的主机名:"+remoteHost);
out.write("<br/>");
out.write("使用的端口号:"+remotePort);
out.write("<br/>");
out.write("remoteUser:"+remoteUser);
out.write("<br/>");
out.write("请求使用的方法:"+method);
out.write("<br/>");
out.write("pathInfo:"+pathInfo);
out.write("<br/>");
out.write("localAddr:"+localAddr);
out.write("<br/>");
out.write("localName:"+localName);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

2 获得客户机请求头
  getHeader(string name)方法:String
  getHeaders(String name)方法:Enumeration
  getHeaderNames()方法
范例:通过request对象获取客户端请求头信息
package gacl.request.study;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 获取客户端请求头信息
* 客户端请求头:
*
*/
public class RequestDemo02 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器
//通过设置响应头控制浏览器以UTF-8的编码显示数据
response.setHeader("content-type", "text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
Enumeration<String> reqHeadInfos = request.getHeaderNames();//获取所有的请求头
out.write("获取到的客户端所有的请求头信息如下:");
out.write("<hr/>");
while (reqHeadInfos.hasMoreElements()) {
String headName = (String) reqHeadInfos.nextElement();
String headValue = request.getHeader(headName);//根据请求头的名字获取对应的请求头的值
out.write(headName+":"+headValue);
out.write("<br/>");
}
out.write("<br/>");
out.write("获取到的客户端Accept-Encoding请求头的值:");
out.write("<hr/>");
String value = request.getHeader("Accept-Encoding");//获取Accept-Encoding请求头对应的值
out.write(value);

Enumeration<String> e = request.getHeaders("Accept-Encoding");
while (e.hasMoreElements()) {
String string = (String) e.nextElement();
System.out.println(string);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

3 获得客户机请求参数(客户端提交的数据)
getParameter(String)方法(常用)
getParameterValues(String name)方法(常用)
getParameterNames()方法(不常用)
getParameterMap()方法(编写框架时常用)
getMethod();

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Html的Form表单元素</title>
</head>
<fieldset style="width:500px;">
<legend>Html的Form表单元素</legend>
<!--form表单的action属性规定当提交表单时,向何处发送表单数据,method属性指明表单的提交方式,分为get和post,默认为get-->
<form action="${pageContext.request.contextPath}/servlet/RequestDemo03" method="post">
<!--输入文本框,SIZE表示显示长度,maxlength表示最多输入长度-->
编&nbsp;&nbsp;号(文本框):
<input type="text" name="userid" value="NO." size="2" maxlength="2"><br>
<!--输入文本框,通过value指定其显示的默认值-->
用户名(文本框):<input type="text" name="username" value="请输入用户名"><br>
<!--密码框,其中所有输入的内容都以密文的形式显示-->
密&nbsp;&nbsp;码(密码框):
<!--&nbsp;表示的是一个空格-->
<input type="password" name="userpass" value="请输入密码"><br>
<!--单选按钮,通过checked指定默认选中,名称必须一样,其中value为真正需要的内容-->
性&nbsp;&nbsp;别(单选框):
<input type="radio" name="sex" value="男" checked>男
<input type="radio" name="sex" value="女">女<br>
<!--下拉列表框,通过<option>元素指定下拉的选项-->
部&nbsp;&nbsp;门(下拉框):
<select name="dept">
<option value="技术部">技术部</option>
<option value="销售部" SELECTED>销售部</option>
<option value="财务部">财务部</option>
</select><br>
<!--复选框,可以同时选择多个选项,名称必须一样,其中value为真正需要的内容-->
兴&nbsp;&nbsp;趣(复选框):
<input type="checkbox" name="inst" value="唱歌">唱歌
<input type="checkbox" name="inst" value="游泳">游泳
<input type="checkbox" name="inst" value="跳舞">跳舞
<input type="checkbox" name="inst" value="编程" checked>编程
<input type="checkbox" name="inst" value="上网">上网
<br>
<!--大文本输入框,宽度为34列,高度为5行-->
说&nbsp;&nbsp;明(文本域):
<textarea name="note" cols="34" rows="5">
</textarea>
<br>
<!--隐藏域,在页面上无法看到,专门用来传递参数或者保存参数-->
<input type="hidden" name="hiddenField" value="hiddenvalue"/>
<!--提交表单按钮,当点击提交后,所有填写的表单内容都会被传输到服务器端-->
<input type="submit" value="提交(提交按钮)">
<!--重置表单按钮,当点击重置后,所有表单恢复原始显示内容-->
<input type="reset" value="重置(重置按钮)">
</form>
<!--表单结束-->
</fieldset>
</body>
<!--完结标记-->
</html>
<!--完结标记-->

在服务器端使用getParameter方法和getParameterValues方法接收表单参数,代码如下:
package gacl.request.study;
import java.io.IOException;
import java.text.MessageFormat;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 获取客户端通过Form表单提交上来的参数
*/
public class RequestDemo03 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//客户端是以UTF-8编码提交表单数据的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码
request.setCharacterEncoding("UTF-8");
/**
* 编&nbsp;&nbsp;号(文本框):
<input type="text" name="userid" value="NO." size="2" maxlength="2">
*/
String userid = request.getParameter("userid");//获取填写的编号,userid是文本框的名字,<input type="text" name="userid">
/**
* 用户名(文本框):<input type="text" name="username" value="请输入用户名">
*/
String username = request.getParameter("username");//获取填写的用户名
/**
* 密&nbsp;&nbsp;码(密码框):<input type="password" name="userpass" value="请输入密码">
*/
String userpass = request.getParameter("userpass");//获取填写的密码
String sex = request.getParameter("sex");//获取选中的性别
String dept = request.getParameter("dept");//获取选中的部门
//获取选中的兴趣,因为可以选中多个值,所以获取到的值是一个字符串数组,因此需要使用getParameterValues方法来获取
String[] insts = request.getParameterValues("inst");
String note = request.getParameter("note");//获取填写的说明信息
String hiddenField = request.getParameter("hiddenField");//获取隐藏域的内容

String instStr="";
/**
* 获取数组数据的技巧,可以避免insts数组为null时引发的空指针异常错误!
*/
for (int i = 0; insts!=null && i < insts.length; i++) {
if (i == insts.length-1) {
instStr+=insts[i];
}else {
instStr+=insts[i]+",";
}
}

String htmlStr = "<table>" +
"<tr><td>填写的编号:</td><td>{0}</td></tr>" +
"<tr><td>填写的用户名:</td><td>{1}</td></tr>" +
"<tr><td>填写的密码:</td><td>{2}</td></tr>" +
"<tr><td>选中的性别:</td><td>{3}</td></tr>" +
"<tr><td>选中的部门:</td><td>{4}</td></tr>" +
"<tr><td>选中的兴趣:</td><td>{5}</td></tr>" +
"<tr><td>填写的说明:</td><td>{6}</td></tr>" +
"<tr><td>隐藏域的内容:</td><td>{7}</td></tr>" +
"</table>";
htmlStr = MessageFormat.format(htmlStr, userid,username,userpass,sex,dept,instStr,note,hiddenField);

response.setCharacterEncoding("UTF-8");//设置服务器端以UTF-8编码输出数据到客户端
response.setContentType("text/html;charset=UTF-8");//设置客户端浏览器以UTF-8编码解析数据
response.getWriter().write(htmlStr);//输出htmlStr里面的内容到客户端浏览器显示
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

在服务器端使用getParameterNames方法接收表单参数,代码如下:
Enumeration<String> paramNames = request.getParameterNames();//获取所有的参数名
while (paramNames.hasMoreElements()) {
String name = paramNames.nextElement();//得到参数名
String value = request.getParameter(name);//通过参数名获取对应的值
System.out.println(MessageFormat.format("{0}={1}", name,value));
}
运行结果如下:

在服务器端使用 getParameterMap 方法接收表单参数,代码如下:
//request对象封装的参数是以Map的形式存储的
Map<String, String[]> paramMap = request.getParameterMap();
for(Map.Entry<String, String[]> entry :paramMap.entrySet()){
String paramName = entry.getKey();
String paramValue = "";
String[] paramValueArr = entry.getValue();
for (int i = 0; paramValueArr!=null && i < paramValueArr.length; i++) {
if (i == paramValueArr.length-1) {
paramValue+=paramValueArr[i];
}else {
paramValue+=paramValueArr[i]+",";
}
}
System.out.println(MessageFormat.format("{0}={1}", paramName,paramValue));
}
运行结果如下:

4 Request对象实现请求转发
请求转发:指一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理。
请求转发的应用场景:MVC设计模式
在Servlet中实现请求转发的两种方式:
1 通过ServletContext的getRequestDispatcher(String path)方法,该方法返回一个RequestDispatcher对象,调用这个对象的forward方法可以实现请求转发。
例如:将请求转发的test.jsp页面
RequestDispatcher reqDispatcher =this.getServletContext().getRequestDispatcher("/test.jsp");
reqDispatcher.forward(request, response);
2 通过request对象提供的getRequestDispatche(String path)方法,该方法返回一个RequestDispatcher对象,调用这个对象的forward方法可以实现请求转发。
例如:将请求转发的test.jsp页面
request.getRequestDispatcher("/test.jsp").forward(request, response);
request对象同时也是一个域对象(Map容器),开发人员通过request对象在实现转发时,把数据通过request对象带给其它web资源处理。
例如:请求RequestDemo06 Servlet,RequestDemo06将请求转发到test.jsp页面
package gacl.request.study;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RequestDemo06 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String data="大家好,我是孤傲苍狼,我正在总结JavaWeb";
/**
* 将数据存放到request对象中,此时把request对象当作一个Map容器来使用
*/
request.setAttribute("data", data);
//客户端访问RequestDemo06这个Servlet后,RequestDemo06通知服务器将请求转发(forward)到test.jsp页面进行处理
request.getRequestDispatcher("/test.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
test.jsp页面代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Request对象实现请求转发</title>
</head>

<body>
使用普通方式取出存储在request对象中的数据:
<h3 style="color:red;"><%=(String)request.getAttribute("data")%></h3>
使用EL表达式取出存储在request对象中的数据:
<h3 style="color:red;">${data}</h3>
</body>
</html>
运行结果如下:

request对象作为一个域对象(Map容器)使用时,主要是通过以下的四个方法来操作:
setAttribute(String name,Object o)方法,将数据作为request对象的一个属性存放到request对象中,例如:request.setAttribute("data", data);
getAttribute(String name)方法,获取request对象的name属性的属性值,例如:request.getAttribute("data")
removeAttribute(String name)方法,移除request对象的name属性,例如:request.removeAttribute("data")
getAttributeNames方法,获取request对象的所有属性名,返回的是一个,例如:Enumeration<String> attrNames = request.getAttributeNames();

请求重定向和请求转发的区别:
一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理,称之为请求转发/307。
一个web资源收到客户端请求后,通知浏览器去访问另外一个web资源进行处理,称之为请求重定向/302。


(十一)——使用Cookie进行会话管理
一、会话的概念
  会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。
  有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学曾经来过,这称之为有状态会话。
二、会话过程中要解决的一些问题?
  每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。
三、保存会话数据的两种技术
3.1、Cookie
  Cookie是客户端技术,程序把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。
3.2、Session
  Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务。
四、Java提供的操作Cookie的API
  Java中的javax.servlet.http.Cookie类用于创建一个Cookie
Cookie类的主要方法
No. 方法 类型 描述
1 Cookie(String name, String value)
构造方法 实例化Cookie对象,传入cooke名称和cookie的值
2 public String getName()
普通方法 取得Cookie的名字
3 public String getValue()
普通方法 取得Cookie的值
4 public void setValue(String newValue)
普通方法 设置Cookie的值
5 public void setMaxAge(int expiry) 普通方法 设置Cookie的最大保存时间,即cookie的有效期,当服务器给浏览器回送一个cookie时,如果在服务器端没有调用setMaxAge方法设置cookie的有效期,那么cookie的有效期只在一次会话过程中有效,用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一次会话,当用户关闭浏览器,会话就结束了,此时cookie就会失效,如果在服务器端使用setMaxAge方法设置了cookie的有效期,比如设置了30分钟,那么当服务器把cookie发送给浏览器时,此时cookie就会在客户端的硬盘上存储30分钟,在30分钟内,即使浏览器关了,cookie依然存在,在30分钟内,打开浏览器访问服务器时,浏览器都会把cookie一起带上,这样就可以在服务器端获取到客户端浏览器传递过来的cookie里面的信息了,这就是cookie设置maxAge和不设置maxAge的区别,不设置maxAge,那么cookie就只在一次会话中有效,一旦用户关闭了浏览器,那么cookie就没有了,那么浏览器是怎么做到这一点的呢,我们启动一个浏览器,就相当于启动一个应用程序,而服务器回送的cookie首先是存在浏览器的缓存中的,当浏览器关闭时,浏览器的缓存自然就没有了,所以存储在缓存中的cookie自然就被清掉了,而如果设置了cookie的有效期,那么浏览器在关闭时,就会把缓存中的cookie写到硬盘上存储起来,这样cookie就能够一直存在了。
6 public int getMaxAge() 普通方法 获取Cookies的有效期
7 public void setPath(String uri)
普通方法 设置cookie的有效路径,比如把cookie的有效路径设置为"/xdp",那么浏览器访问"xdp"目录下的web资源时,都会带上cookie,再比如把cookie的有效路径设置为"/xdp/gacl",那么浏览器只有在访问"xdp"目录下的"gacl"这个目录里面的web资源时才会带上cookie一起访问,而当访问"xdp"目录下的web资源时,浏览器是不带cookie的
8 public String getPath()
普通方法 获取cookie的有效路径
9 public void setDomain(String pattern)
普通方法 设置cookie的有效域
10 public String getDomain() 普通方法 获取cookie的有效域
  response接口也中定义了一个addCookie方法,它用于在其响应头中增加一个相应的Set-Cookie头字段。 同样,request接口中也定义了一个getCookies方法,它用于获取客户端提交的Cookie。

五、Cookie使用范例
5.1、使用cookie记录用户上一次访问的时间
package gac.xdp.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* cookie实例:获取用户上一次访问的时间
*/
public class CookieDemo01 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置服务器端以UTF-8编码进行输出
response.setCharacterEncoding("UTF-8");
//设置浏览器以UTF-8编码进行接收,解决中文乱码问题
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//获取浏览器访问访问服务器时传递过来的cookie数组
Cookie[] cookies = request.getCookies();
//如果用户是第一次访问,那么得到的cookies将是null
if (cookies!=null) {
out.write("您上次访问的时间是:");
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("lastAccessTime")) {
Long lastAccessTime =Long.parseLong(cookie.getValue());
Date date = new Date(lastAccessTime);
out.write(date.toLocaleString());
}
}
} else {
out.write("这是您第一次访问本站!");
}

//用户访问过之后重新设置用户的访问时间,存储到cookie中,然后发送到客户端浏览器
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");//创建一个cookie,cookie的名字是lastAccessTime
//将cookie对象添加到response对象中,这样服务器在输出response对象中的内容时就会把cookie也输出到客户端浏览器
response.addCookie(cookie);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
第一次访问时这个Servlet时,效果如下所示:

点击浏览器的刷新按钮,进行第二次访问,此时就服务器就可以通过cookie获取浏览器上一次访问的时间了,效果如下:

  在上面的例子中,在程序代码中并没有使用setMaxAge方法设置cookie的有效期,所以当关闭浏览器之后,cookie就失效了,要想在关闭了浏览器之后,cookie依然有效,那么在创建cookie时,就要为cookie设置一个有效期。如下所示:
//用户访问过之后重新设置用户的访问时间,存储到cookie中,然后发送到客户端浏览器
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");//创建一个cookie,cookie的名字是lastAccessTime
//设置Cookie的有效期为1天
cookie.setMaxAge(24*60*60);
//将cookie对象添加到response对象中,这样服务器在输出response对象中的内容时就会把cookie也输出到客户端浏览器
response.addCookie(cookie);
  

用户第一次访问时,服务器发送给浏览器的cookie就存储到了硬盘上,如下所示:

  这样即使关闭了浏览器,下次再访问时,也依然可以通过cookie获取用户上一次访问的时间。

六、Cookie注意细节
一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。
6.1、删除Cookie
注意:删除cookie时,path必须一致,否则不会删除
package gac.xdp.cookie;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 删除cookie
*/
public class CookieDemo02 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//创建一个名字为lastAccessTime的cookie
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");
//将cookie的有效期设置为0,命令浏览器删除该cookie
cookie.setMaxAge(0);
response.addCookie(cookie);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
6.2、cookie中存取中文
  要想在cookie中存储中文,那么必须使用URLEncoder类里面的encode(String s, String enc)方法进行中文转码,例如:
Cookie cookie = new Cookie("userName", URLEncoder.encode("孤傲苍狼", "UTF-8"));
response.addCookie(cookie);
  在获取cookie中的中文数据时,再使用URLDecoder类里面的decode(String s, String enc)进行解码,例如:
URLDecoder.decode(cookies[i].getValue(), "UTF-8")


(十二)——Session
一、Session简单介绍
  在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
二、Session和Cookie的主要区别
Cookie是把用户的数据写给用户的浏览器。
Session技术把用户的数据写到用户独占的session中。
Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。
三、session实现原理
3.1、服务器是如何实现一个session为一个用户浏览器服务的?
  服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务器时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。可以用如下的代码证明:
package xdp.gacl.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setCharacterEncoding("UTF=8");
response.setContentType("text/html;charset=UTF-8");
//使用request对象的getSession()获取session,如果session不存在则创建一个
HttpSession session = request.getSession();
//将数据存储到session中
session.setAttribute("data", "孤傲苍狼");
//获取session的Id
String sessionId = session.getId();
//判断session是不是新创建的
if (session.isNew()) {
response.getWriter().print("session创建成功,session的id是:"+sessionId);
}else {
response.getWriter().print("服务器已经存在该session了,session的id是:"+sessionId);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
  第一次访问时,服务器会创建一个新的sesion,并且把session的Id以cookie的形式发送给客户端浏览器,如下图所示:

点击刷新按钮,再次请求服务器,此时就可以看到浏览器再请求服务器时,会把存储到cookie中的session的Id一起传递到服务器端了,如下图所示:

我猜想request.getSession()方法内部新创建了Session之后一定是做了如下的处理
//获取session的Id
String sessionId = session.getId();
//将session的Id存储到名字为JSESSIONID的cookie中
Cookie cookie = new Cookie("JSESSIONID", sessionId);
//设置cookie的有效路径
cookie.setPath(request.getContextPath());
response.addCookie(cookie);

四、浏览器禁用Cookie后的session处理
4.1、IE8禁用cookie
工具->internet选项->隐私->设置->将滑轴拉到最顶上(阻止所有cookies)


4.2、解决方案:URL重写
response.encodeRedirectURL(java.lang.String url) 用于对sendRedirect方法后的url地址进行重写。
response.encodeURL(java.lang.String url)用于对表单action和超链接的url地址进行重写
4.3、范例:禁用Cookie后servlet共享Session中的数据, 没懂

/******************************** IndexServlet ********************************/
package xdp.gacl.session;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//首页:列出所有书
public class IndexServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//创建Session
request.getSession();
out.write("本网站有如下书:<br/>");
Set<Map.Entry<String,Book>> set = DB.getAll().entrySet();
for(Map.Entry<String,Book> me : set){
Book book = me.getValue();
String url =request.getContextPath()+ "/servlet/BuyServlet?id=" + book.getId();
//response. encodeURL(java.lang.String url)用于对表单action和超链接的url地址进行重写
url = response.encodeURL(url);//将超链接的url地址进行重写
out.println(book.getName() + " <a href=‘"+url+"‘>购买</a><br/>");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
/**
* @author gacl
* 模拟数据库
*/
class DB{
private static Map<String,Book> map = new LinkedHashMap<String,Book>();
static{
map.put("1", new Book("1","javaweb开发"));
map.put("2", new Book("2","spring开发"));
map.put("3", new Book("3","hibernate开发"));
map.put("4", new Book("4","struts开发"));
map.put("5", new Book("5","ajax开发"));
}

public static Map<String,Book> getAll(){
return map;
}
}
class Book{

private String id;
private String name;
public Book() {
super();
}
public Book(String id, String name) {
super();
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

/******************************** BuyServlet ********************************/
package xdp.gacl.session;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class BuyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("id");
Book book = DB.getAll().get(id); //得到用户想买的书
HttpSession session = request.getSession();
List<Book> list = (List) session.getAttribute("list"); //得到用户用于保存所有书的容器
if(list==null){
list = new ArrayList<Book>();
session.setAttribute("list", list);
}
list.add(book);
//response. encodeRedirectURL(java.lang.String url)用于对sendRedirect方法后的url地址进行重写
String url = response.encodeRedirectURL(request.getContextPath()+"/servlet/ListCartServlet");
System.out.println(url);
response.sendRedirect(url);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

/******************************** ListCartServlet ********************************/
package xdp.gacl.session;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class ListCartServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
HttpSession session = request.getSession();
List<Book> list = (List) session.getAttribute("list");
if(list==null || list.size()==0){
out.write("对不起,您还没有购买任何商品!!");
return;
}

//显示用户买过的商品
out.write("您买过如下商品:<br>");
for(Book book : list){
out.write(book.getName() + "<br/>");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
在禁用了cookie的IE8下的运行效果如下:(http://www.cnblogs.com/xdp-gacl/p/3855702.html)

通过查看IndexServlet生成的html代码可以看到,每一个超链接后面都带上了session的Id,如下所示
本网站有如下书:<br/>javaweb开发 <a href=‘/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=1‘>购买</a><br/>
spring开发 <a href=‘/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=2‘>购买</a><br/>
hibernate开发 <a href=‘/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=3‘>购买</a><br/>
struts开发 <a href=‘/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=4‘>购买</a><br/>
ajax开发 <a href=‘/JavaWeb_Session_Study_20140720/servlet/BuyServlet;jsessionid=96BDFB9D87A08D5AB1EAA2537CDE2DB2?id=5‘>购买</a><br/>
所以,当浏览器禁用了cookie后,就可以用URL重写这种解决方案解决Session数据共享问题。而且response. encodeRedirectURL(java.lang.String url) 和response. encodeURL(java.lang.String url)是两个非常智能的方法,当检测到浏览器没有禁用cookie时,那么就不进行URL重写了。我们在没有禁用cookie的火狐浏览器下访问,效果如下: (http://www.cnblogs.com/xdp-gacl/p/3855702.html)

从演示动画中可以看到,浏览器第一次访问时,服务器创建Session,然后将Session的Id以Cookie的形式发送回给浏览器,response. encodeURL(java.lang.String url)方法也将URL进行了重写,当点击刷新按钮第二次访问,由于火狐浏览器没有禁用cookie,所以第二次访问时带上了cookie,此时服务器就可以知道当前的客户端浏览器并没有禁用cookie,那么就通知response. encodeURL(java.lang.String url)方法不用将URL进行重写了。

五、session对象的创建和销毁时机
5.1、session对象的创建时机
在程序中第一次调用request.getSession()方法时就会创建一个新的Session,可以用isNew()方法来判断Session是不是新创建的
范例:创建session
//使用request对象的getSession()获取session,如果session不存在则创建一个
HttpSession session = request.getSession();
//获取session的Id
String sessionId = session.getId();
//判断session是不是新创建的
if (session.isNew()) {
response.getWriter().print("session创建成功,session的id是:"+sessionId);
}else {
response.getWriter().print("服务器已经存在session,session的id是:"+sessionId);
}
5.2、session对象的销毁时机
session对象默认30分钟没有使用,则服务器会自动销毁session,在web.xml文件中可以手工配置session的失效时间,例如:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
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">
<display-name></display-name>

<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 设置Session的有效时间:以分钟为单位-->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
</web-app>
当需要在程序中手动设置Session失效时,可以手工调用session.invalidate方法,摧毁session。
HttpSession session = request.getSession();
//手工调用session.invalidate方法,摧毁session
session.invalidate();


(十三)——使用Session防止表单重复提交

在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交。
一、 表单重复提交的常见应用场景
有如下的form.jsp页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Form表单</title>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交" id="submit">
</form>
</body>
</html>

package xdp.gacl.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DoFormServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//客户端是以UTF-8编码传输数据到服务器端的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码
request.setCharacterEncoding("UTF-8");
String userName = request.getParameter("username");
try {
//让当前的线程睡眠3秒钟,模拟网络延迟而导致表单重复提交的现象
Thread.sleep(3*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("向数据库中插入数据:"+userName);
}

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

}

如果没有进行form表单重复提交处理,那么在网络延迟的情况下下面的操作将会导致form表单重复提交多次
场景一:在网络延迟的情况下让用户有时间点击多次submit按钮导致表单重复提交
场景二:表单提交后用户点击【刷新】按钮导致表单重复提交
点击浏览器的刷新按钮,就是把浏览器上次做的事情再做一次,因为这样也会导致表单重复提交。
场景三:用户提交表单后,点击浏览器的【后退】按钮回退到表单页面后进行再次提交

二、利用JavaScript防止表单重复提交
采用JavaScript来防止表单重复提交,具体做法如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Form表单</title>
<script type="text/javascript">
var isCommitted = false;//表单是否已经提交标识,默认为false
function dosubmit(){
if(isCommitted==false){
isCommitted = true;//提交表单后,将表单是否已经提交标识设置为true
return true;//返回true让表单正常提交
}else{
return false;//返回false那么表单将不提交
}
}
</script>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/DoFormServlet" onsubmit="return dosubmit()" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交" id="submit">
</form>
</body>
</html>
除了用这种方式之外,经常见的另一种方式就是表单提交之后,将提交按钮设置为不可用,让用户没有机会点击第二次提交按钮,代码如下:
function dosubmit(){
//获取表单提交按钮
var btnSubmit = document.getElementById("submit");
//将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮
btnSubmit.disabled= "disabled";
//返回true让表单可以正常提交
return true;
}
另外还有一种做法就是提交表单后,将提交按钮隐藏起来,这种做法和将提交按钮设置为不可用是差不多的,可能会让用户误以为是bug

三、利用Session防止表单重复提交

对于[ 场景二 ]和[ 场景三 ]导致表单重复提交的问题,既然客户端无法解决,那么就在服务器端解决,在服务器端解决就需要用到session了。

具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
当前用户的Session中不存在Token(令牌)。
用户提交的表单数据中没有Token(令牌)。
看具体的范例:
1.创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面

package xdp.gacl.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FormServlet extends HttpServlet {
private static final long serialVersionUID = -884689940866074733L;

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

String token = TokenProccessor.getInstance().makeToken();//创建令牌
System.out.println("在FormServlet中生成的token:"+token);
request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)
request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面
}

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

}
2.在form.jsp中使用隐藏域来存储Token(令牌)

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>form表单</title>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
<%--使用隐藏域存储生成的token--%>
<%--
<input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
--%>
<%--使用EL表达式取出存储在session中的token--%>
<input type="hidden" name="token" value="${token}"/>
用户名:<input type="text" name="username">
<input type="submit" value="提交">
</form>
</body>
</html>
3.DoFormServlet处理表单提交

package xdp.gacl.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DoFormServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
if(b==true){
System.out.println("请不要重复提交");
return;
}
request.getSession().removeAttribute("token");//移除session中的token
System.out.println("处理用户提交请求!!");
}

/**
* 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
* @param request
* @return
* true 用户重复提交了表单
* false 用户没有重复提交表单
*/
private boolean isRepeatSubmit(HttpServletRequest request) {
String client_token = request.getParameter("token");
//1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
if(client_token==null){
return true;
}
//取出存储在Session中的token
String server_token = (String) request.getSession().getAttribute("token");
//2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
if(server_token==null){
return true;
}
//3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
if(!client_token.equals(server_token)){
return true;
}

return false;
}

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

}
生成Token的工具类 TokenProccessor

package xdp.gacl.session;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import sun.misc.BASE64Encoder;

public class TokenProccessor {

/*
*单例设计模式(保证类的对象在内存中只有一个)
*1、把类的构造函数私有
*2、自己创建一个类的对象
*3、对外提供一个公共的方法,返回类的对象
*/
private TokenProccessor(){}

private static final TokenProccessor instance = new TokenProccessor();

/**
* 返回类的对象
* @return
*/
public static TokenProccessor getInstance(){
return instance;
}

/**
* 生成Token
* Token:Nv6RRuGEVvmGjB+jimI/gw==
* @return
*/
public String makeToken(){ //checkException
// 7346734837483 834u938493493849384 43434384
String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
//数据指纹 128位长 16个字节 md5
try {
MessageDigest md = MessageDigest.getInstance("md5");
byte md5[] = md.digest(token.getBytes());
//base64编码--任意二进制编码明文字符 adfsdfsdfsf
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
首先访问FormServlet,在FormServlet中生成Token之后再重定向到form.jsp页面,这次是在服务器端处理表单重复提交的,运行效果如下:( http://www.cnblogs.com/xdp-gacl/p/3859416.html )
(十四)——JSP原理
一、什么是JSP?
JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术。
JSP这门技术的最大的特点在于,写jsp就像在写html,但它相比html而言,html只能为用户提供静态数据,而Jsp技术允许在页面中嵌套java代码,为用户提供动态数据。
二、JSP原理
2.1、Web服务器是如何调用并执行一个jsp页面的?
浏览器向服务器发请求,不管访问的是什么资源,其实都是在访问Servlet,所以当访问一个jsp页面时,其实也是在访问一个Servlet,服务器在执行jsp的时候,首先把jsp翻译成一个Servlet,所以我们访问jsp时,其实不是在访问jsp,而是在访问jsp翻译过后的那个Servlet,例如下面的代码:
index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">

<title>First Jsp</title>

</head>

<body>
<%
out.print("Hello Jsp");
%>
</body>
</html>

当我们通过浏览器访问index.jsp时,服务器首先将index.jsp翻译成一个index_jsp.class,在Tomcat服务器的work\Catalina\localhost\项目名\org\apache\jsp目录下可以看到index_jsp.class的源代码文件index_jsp.java,index_jsp.java的代码如下:

package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import java.util.*;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
private static java.util.List _jspx_dependants;
private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.AnnotationProcessor _jsp_annotationprocessor;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspInit() {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
_jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());
}
public void _jspDestroy() {
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write(‘\r‘);
out.write(‘\n‘);
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
out.write("\r\n");
out.write("\r\n");
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <base href=\"");
out.print(basePath);
out.write("\">\r\n");
out.write(" \r\n");
out.write(" <title>First Jsp</title>\r\n");
out.write("\t\r\n");
out.write(" </head>\r\n");
out.write(" \r\n");
out.write(" <body>\r\n");
out.write(" ");
out.print("Hello Jsp");

out.write("\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)) {
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
out.clearBuffer();
} catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
}
finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}

我们可以看到,index_jsp这个类是继承 org.apache.jasper.runtime.HttpJspBase这个类的,通过查看Tomcat服务器的源代码,可以知道在apache-tomcat-6.0.20-src\java\org\apache\jasper\runtime目录下存HttpJspBase这个类的源代码文件,如下图所示:

我们可以看看HttpJsBase这个类的源代码,如下所示:

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jasper.runtime;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.HttpJspPage;
import javax.servlet.jsp.JspFactory;
import org.apache.jasper.compiler.Localizer;
/**
* This is the super class of all JSP-generated servlets.
*
* @author Anil K. Vijendran
*/
public abstract class HttpJspBase
extends HttpServlet
implements HttpJspPage


{

protected HttpJspBase() {
}
public final void init(ServletConfig config)
throws ServletException {
super.init(config);
jspInit();
_jspInit();
}

public String getServletInfo() {
return Localizer.getMessage("jsp.engine.info");
}
public final void destroy() {
jspDestroy();
_jspDestroy();
}
/**
* Entry point into service.
*/
public final void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
_jspService(request, response);
}

public void jspInit() {
}
public void _jspInit() {
}
public void jspDestroy() {
}
protected void _jspDestroy() {
}
public abstract void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException;
}

HttpJspBase类是继承HttpServlet的,所以HttpJspBase类是一个Servlet,而index_jsp又是继承HttpJspBase类的,所以index_jsp类也是一个Servlet,所以当浏览器访问服务器上的index.jsp页面时,其实就是在访问index_jsp这个Servlet,index_jsp这个Servlet使用_jspService这个方法处理请求。
2.2、Jsp页面中的html排版标签是如何被发送到客户端的?
浏览器接收到的这些数据

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="http://localhost:8080/JavaWeb_Jsp_Study_20140603/">

<title>First Jsp</title>

</head>

<body>
Hello Jsp
</body>
</html>

都是在_jspService方法中使用如下的代码输出给浏览器的:

out.write(‘\r‘);
out.write(‘\n‘);
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
out.write("\r\n");
out.write("\r\n");
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <base href=\"");
out.print(basePath);
out.write("\">\r\n");
out.write(" \r\n");
out.write(" <title>First Jsp</title>\r\n");
out.write("\t\r\n");
out.write(" </head>\r\n");
out.write(" \r\n");
out.write(" <body>\r\n");
out.write(" ");
out.print("Hello Jsp");

out.write("\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");

在jsp中编写的java代码和html代码都会被翻译到_jspService方法中去,在jsp中编写的java代码会原封不动地翻译成java代码,如<%out.print("Hello Jsp");%>直接翻译成out.print("Hello Jsp");
,而HTML代码则会翻译成使用out.write("<html标签>\r\n");
的形式输出到浏览器。在jsp页面中编写的html排版标签都是以out.write("<html标签>\r\n");
的形式输出到浏览器,浏览器拿到html代码后才能够解析执行html代码。
2.3、Jsp页面中的java代码服务器是如何执行的?
在jsp中编写的java代码会被翻译到_jspService方法中去,当执行_jspService方法处理请求时,就会执行在jsp编写的java代码了,所以Jsp页面中的java代码服务器是通过调用_jspService方法处理请求时执行的。
2.4、Web服务器在调用jsp时,会给jsp提供一些什么java对象?
查看_jspService方法可以看到,Web服务器在调用jsp时,会给Jsp提供如下的8个java对象

PageContext pageContext;
HttpSession session;
ServletContext application;
ServletConfig config;
JspWriter out;
Object page = this;
HttpServletRequest request,
HttpServletResponse response
其中page对象,request和response已经完成了实例化,而其它5个没有实例化的对象通过下面的方式实例化
pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
这8个java对象在Jsp页面中是可以直接使用的,如下所示:

<%
session.setAttribute("name", "session对象");//使用session对象,设置session对象的属性
out.print(session.getAttribute("name")+"<br/>");//获取session对象的属性
pageContext.setAttribute("name", "pageContext对象");//使用pageContext对象,设置pageContext对象的属性
out.print(pageContext.getAttribute("name")+"<br/>");//获取pageContext对象的属性
application.setAttribute("name", "application对象");//使用application对象,设置application对象的属性
out.print(application.getAttribute("name")+"<br/>");//获取application对象的属性
out.print("Hello Jsp"+"<br/>");//使用out对象
out.print("服务器调用index.jsp页面时翻译成的类的名字是:"+page.getClass()+"<br/>");//使用page对象
out.print("处理请求的Servlet的名字是:"+config.getServletName()+"<br/>");//使用config对象
out.print(response.getContentType()+"<br/>");//使用response对象
out.print(request.getContextPath()+"<br/>");//使用request对象
%>

运行结果如下:

2.5、Jsp最佳实践
Jsp最佳实践就是jsp技术在开发中该怎么去用。
不管是JSP还是Servlet,虽然都可以用于开发动态web资源。但由于这2门技术各自的特点,在长期的软件实践中,人们逐渐把servlet作为web应用中的控制器组件来使用,而把JSP技术作为数据显示模板来使用。其原因为,程序的数据通常要美化后再输出:让jsp既用java代码产生动态数据,又做美化会导致页面难以维护。让servlet既产生数据,又在里面嵌套html代码美化数据,同样也会导致程序可读性差,难以维护。因此最好的办法就是根据这两门技术的特点,让它们各自负责各的,servlet只负责响应请求产生数据,并把数据通过转发技术带给jsp,数据的显示jsp来做。
2.6、Tomcat服务器的执行流程

第一次执行:
客户端通过电脑连接服务器,因为是请求是动态的,所以所有的请求交给WEB容器来处理
在容器中找到需要执行的*.jsp文件
之后*.jsp文件通过转换变为*.java文件
*.java文件经过编译后,形成*.class文件
最终服务器要执行形成的*.class文件
第二次执行:
因为已经存在了*.class文件,所以不在需要转换和编译的过程
修改后执行:
1.源文件已经被修改过了,所以需要重新转换,重新编译。

(十五)——JSP基础语法
任何语言都有自己的语法,JAVA中有,JSP虽然是在JAVA上的一种应用,但是依然有其自己扩充的语法,而且在JSP中,所有的JAVA语句都可以使用。
一、JSP模版元素
JSP页面中的HTML内容称之为JSP模版元素。
JSP模版元素定义了网页的基本骨架,即定义了页面的结构和外观。
二、JSP表达式
JSP脚本表达式(expression)用于将程序数据输出到客户端
语法:<%= 变量或表达式 %>
举例:输出当前系统时间:
<%= new java.util.Date() %>
JSP引擎在翻译脚本表达式时,会将程序数据转成字符串,然后在相应位置用out.print(…) 将数据输给客户端。
JSP脚本表达式中的变量或表达式后面不能有分号(;)。
三、JSP脚本片断
JSP脚本片断(scriptlet)用于在JSP页面中编写多行Java代码。语法:
<%
多行java代码
%>
在<% %>中可以定义变量、编写语句,不能定义方法。
范例:在Scriptlet中定义变量、编写语句

<%
int sum=0;//声明变量
/*编写语句*/
for (int i=1; i<=100; i++) {
sum+=i;
}
out.println("<h1>Sum="+sum+"</h1>");
%>

注意事项:
JSP脚本片断中只能出现java代码,不能出现其它模板元素, JSP引擎在翻译JSP页面中,会将JSP脚本片断中的Java代码将被原封不动地放到Servlet的_jspService方法中。
JSP脚本片断中的Java代码必须严格遵循Java语法,例如,每执行语句后面必须用分号(;)结束。
在一个JSP页面中可以有多个脚本片断,在两个或多个脚本片断之间可以嵌入文本、HTML标记和其他JSP元素。
举例:

<%
int x = 10;
out.println(x);
%>
<p>这是JSP页面文本</p>
<%
int y = 20;
out.println(y);
%>

多个脚本片断中的代码可以相互访问,犹如将所有的代码放在一对<%%>之中的情况。如:out.println(x);
单个脚本片断中的Java语句可以是不完整的,但是,多个脚本片断组合后的结果必须是完整的Java语句,例如:

<%
for (int i=1; i<5; i++) {
%>
<H1>http://localhost:8080/JavaWeb_Jsp_Study_20140603/</H1>
<%
}
%>

四、JSP声明
JSP页面中编写的所有代码,默认会翻译到servlet的service方法中, 而Jsp声明中的java代码被翻译到_jspService方法的外面。语法:
<%!
java代码
%>
所以,JSP声明可用于定义JSP页面转换成的Servlet程序的静态代码块、成员变量和方法 。
多个静态代码块、变量和函数可以定义在一个JSP声明中,也可以分别单独定义在多个JSP声明中。
JSP隐式对象的作用范围仅限于Servlet的_jspService方法,所以在JSP声明中不能使用这些隐式对象。
JSP声明案例:

<%!
static {
System.out.println("loading Servlet!");
}
private int globalVar = 0;
public void jspInit() {
System.out.println("initializing jsp!");
}
%>
<%!
public void jspDestroy() {
System.out.println("destroying jsp!");
}
%>

五、JSP注释
在JSP中,注释有两大类:
显式注释:直接使用HTML风格的注释:<!- - 注释内容- ->
隐式注释:直接使用JAVA的注释://、/*……*/
 JSP自己的注释:<%-- 注释内容--%>
这三种注释的区别

<!--这个注释可以看见-->
<%
//JAVA中的单行注释
/*
JAVA中的多行注释
*/
%>
<%--JSP自己的注释--%>

HTML的注释在浏览器中查看源文件的时候是可以看得到的,而JAVA注释和JSP注释在浏览器中查看源文件时是看不到注释的内容的,这就是这三种注释的区别。

(十六)——JSP指令
一、JSP指令简介
JSP指令(directive)是为JSP引擎而设计的,它们并不直接产生任何可见输出,而只是告诉引擎如何处理JSP页面中的其余部分。
在JSP 2.0规范中共定义了三个指令:
page指令
Include指令
taglib指令
JSP指令的基本语法格式:<%@ 指令 属性名="值" %>
例如:
<%@ page contentType="text/html;charset=gb2312"%>
如果一个指令有多个属性,这多个属性可以写在一个指令中,也可以分开写。
例如:
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.util.Date"%>
也可以写作:
<%@ page contentType="text/html;charset=gb2312" import="java.util.Date"%>
二、Page指令
page指令用于定义JSP页面的各种属性,无论page指令出现在JSP页面中的什么地方,它作用的都是整个JSP页面,为了保持程序的可读性和遵循良好的编程习惯,page指令最好是放在整个JSP页面的起始位置。例如:

JSP 2.0规范中定义的page指令的完整语法:

<%@ page
[ language="java" ]
[ extends="package.class" ]
[ import="{package.class | package.*}, ..." ]
[ session="true | false" ]
[ buffer="none | 8kb | sizekb" ]
[ autoFlush="true | false" ]
[ isThreadSafe="true | false" ]
[ info="text" ]
[ errorPage="relative_url" ]
[ isErrorPage="true | false" ]
[ contentType="mimeType [ ;charset=characterSet ]" | "text/html ; charset=ISO-8859-1" ]
[ pageEncoding="characterSet | ISO-8859-1" ]
[ isELIgnored="true | false" ]
%>

2.1、page指令的import属性
在Jsp页面中,Jsp引擎会自动导入下面的包
java.lang.*
javax.servlet.*
javax.servlet.jsp.*
javax.servlet.http.*
可以在一条page指令的import属性中引入多个类或包,其中的每个包或类之间使用逗号(,)分隔
例如:
<%@ page import="java.util.*,java.io.*,java.sql.*"%>
上面的语句也可以改写为使用多条page指令的import属性来分别引入各个包或类
例如:
<%@ page import="java.util.Date"%>
<%@ page import="java.io.*" %>
<%@ page import="java.sql.*" %>
2.2、page指令的errorPage属性
errorPage属性的设置值必须使用相对路径,如果以“/”开头,表示相对于当前Web应用程序的根目录(注意不是站点根目录),否则,表示相对于当前页面
可以在web.xml文件中使用<error-page>元素为整个Web应用程序设置错误处理页面。
<error-page>元素有3个子元素,<error-code>、<exception-type>、<location>
<error-code>子元素指定错误的状态码,例如:<error-code>404</error-code>
<exception-type>子元素指定异常类的完全限定名,例如:<exception-type>java.lang.ArithmeticException</exception-type>
<location>子元素指定以“/”开头的错误处理页面的路径,例如:<location>/ErrorPage/404Error.jsp</location>
如果设置了某个JSP页面的errorPage属性,那么在web.xml文件中设置的错误处理将不对该页面起作用。
2.3、使用errorPage属性指明出错后跳转的错误页面
比如Test.jsp页面有如下的代码:

<%@ page language="java" import="java.util.*" errorPage="/ErrorPage/error.jsp" pageEncoding="UTF-8"%>
<html>
<head>
<title>测试page指令的errorPage属性</title>
</head>
<body>
<%
//这行代码肯定会出错,因为除数是0,一运行就会抛出异常
int x = 1/0;
%>
</body>
</html>

在Test.jsp中,page指令的errorPage属性指明了出错后跳转到"/ErrorPage/error.jsp",error.jsp页面代码如下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>错误信息友好提示页面</title>
</head>
<body>
对不起,出错了,请联系管理员解决!
</body>
</html>

运行结果如下:

2.4、在web.xml中使用<error-page>标签为整个web应用设置错误处理页面
例如:使用<error-page>标签配置针对404错误的处理页面
web.xml的代码下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
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_3_0.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

<!-- 针对404错误的处理页面 -->
<error-page>
<error-code>404</error-code>
<location>/ErrorPage/404Error.jsp</location>
</error-page>

</web-app>

404Error.jsp代码如下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>404错误友好提示页面</title>
<!-- 3秒钟后自动跳转回首页 -->
<meta http-equiv="refresh" content="3;url=${pageContext.request.contextPath}/index.jsp">
</head>
<body>
<img alt="对不起,你要访问的页面没有找到,请联系管理员处理!"
src="${pageContext.request.contextPath}/img/404Error.png"/><br/>
3秒钟后自动跳转回首页,如果没有跳转,请点击<a href="${pageContext.request.contextPath}/index.jsp">这里</a>
</body>
</html>

当访问一个不存在的web资源时,就会跳转到在web.xml中配置的404错误处理页面404Error.jsp,如下图所示:

2.5、关于在web.xml中使用<error-page>标签为整个web应用设置错误处理页面在IE下无法跳转的解决办法
这里需要注意的是,如果错误页面比较小,那么当访问服务器上不存在的web资源或者访问服务器出错时在IE浏览器下是无法跳转到错误页面的,显示的是ie自己的错误页面,而在火狐和google浏览器下(其他浏览器没有测试过)是不存在注意的问题的。
我们可以通过下面的实验来证明
在web.xml中配置500错误时的错误友好提示页面
<!-- 针对500错误的处理页面 -->
<error-page>
<error-code>500</error-code>
<location>/ErrorPage/500Error.jsp</location>
</error-page>
500Error.jsp页面的代码如下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>500(服务器错误)错误友好提示页面</title>
<!-- 3秒钟后自动跳转回首页 -->
<meta http-equiv="refresh" content="3;url=${pageContext.request.contextPath}/index.jsp">
</head>
<body>
<img alt="对不起,服务器出错!"
src="${pageContext.request.contextPath}/img/500Error.png"/><br/>
3秒钟后自动跳转回首页,如果没有跳转,请点击<a href="${pageContext.request.contextPath}/index.jsp">这里</a>
</body>
</html>

500Error.jsp页面的字节大小

在IE8浏览器下的运行结果:

在IE下访问Test.jsp出现500错误后,显示的是ie自己的错误页面,而不是我们定制的那个500错误页面,而在google和火狐下却是可以正常跳转到我们自己定制的那个500错误页面的,如下图所示:


很多人遇到这个问题,而解决这个问题的办法有两种:
1、修改IE浏览器的设置(不推荐)
操作步骤:在IE【工具】->【Internet选项】->【高级】中勾掉【显示友好http错误提示】

经过这样的设置之后,访问服务器出错后就可以直接跳转到我们定制的500错误页面了,如下图所示:

这种做法需要修改客户端浏览器的配置,不推荐这样的方式。
2.不修改IE浏览器的设置下确保定制的错误页面的大小>1024字节
修改500Error.jsp,多添加一些内容,让页面的字节数大一些,修改后的500Error.jsp的代码如下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>500(服务器错误)错误友好提示页面</title>
<!-- 3秒钟后自动跳转回首页 -->
<meta http-equiv="refresh" content="3;url=${pageContext.request.contextPath}/index.jsp">
</head>
<body>
<img alt="对不起,服务器出错了,请联系管理员解决!"
src="${pageContext.request.contextPath}/img/500Error.png"/><br/>
3秒钟后自动跳转回首页,如果没有跳转,请点击<a href="${pageContext.request.contextPath}/index.jsp">这里</a>
</body>
</html>

也就多加了几个中文,让500Error.jsp多了几个字节,500Error.jsp现在的字节数如下:

在IE下访问,当服务器出错时,就可以正常跳转到500Error.jsp这个定制的错误页面了,如下图所示:

经过测试,当定制的错误页面的size=617bytes时,在IE8下已经可以跳转到定制的错误页面了,其他版本的IE浏览器没有经过测试,不过为了保险起见,定制的错误页面的size最好超过1024bytes。
2.6、使用page指令的isErrorPage属性显式声明页面为错误页面
如果某一个jsp页面是作为系统的错误处理页面,那么建议将page指令的isErrorPage属性(默认为false)设置为"true"来显式声明这个Jsp页面是一个错误处理页面。
例如:将error.jsp页面显式声明为错误处理页面

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isErrorPage="true"%>
<html>
<head>
<title>错误信息友好提示页面</title>
</head>

<body>
对不起,出错了,请联系管理员解决!
</body>
</html>

将error.jsp页面显式声明为错误处理页面后,有什么好处呢,好处就是Jsp引擎在将jsp页面翻译成Servlet的时候,在Servlet的 _jspService方法中会声明一个exception对象,然后将运行jsp出错的异常信息存储到exception对象中,如下所示:

由于Servlet的_jspService方法中声明了exception对象,那么就可以在error.jsp页面中使用exception对象,这样就可以在Jsp页面中拿到出错的异常信息了,如下:

如果没有设置isErrorPage="true",那么在jsp页面中是无法使用exception对象的,因为在Servlet的_jspService方法中不会声明一个exception对象,如下所示:


Jsp有9大内置对象,而一般情况下exception对象在Jsp页面中是获取不到的,只有设置page指令的isErrorPage属性为"true"来显式声明Jsp页面是一个错误处理页面之后才能够在Jsp页面中使用exception对象。
三、include指令
在JSP中对于包含有两种语句形式:
@include指令
<jsp:include>指令
3.1、@include指令
@include可以包含任意的文件,当然,只是把文件的内容包含进来。
include指令用于引入其它JSP页面,如果使用include指令引入了其它JSP页面,那么JSP引擎将把这两个JSP翻译成一个servlet。所以include指令引入通常也称之为静态引入。
语法:<%@ include file="relativeURL"%>,其中的file属性用于指定被引入文件的路径。路径以“/”开头,表示代表当前web应用。
include指令细节注意问题:
被引入的文件必须遵循JSP语法。
被引入的文件可以使用任意的扩展名,即使其扩展名是html,JSP引擎也会按照处理jsp页面的方式处理它里面的内容,为了见明知意,JSP规范建议使用.jspf(JSP fragments(片段))作为静态引入文件的扩展名。
由于使用include指令将会涉及到2个JSP页面,并会把2个JSP翻译成一个servlet,所以这2个JSP页面的指令不能冲突(除了pageEncoding和导包除外)。
include指令使用范例:
新建head.jspf页面和foot.jspf页面,分别作为jsp页面的头部和尾部,存放于WebRoot下的jspfragments文件夹中,代码如下:
head.jspf代码:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1 style="color:red;">网页头部</h1>
foot.jspf代码:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1 style="color:blue;">网页尾部</h1>
在WebRoot文件夹下创建一个IncludeTagTest.jsp页面,在IncludeTagTest.jsp页面中使用@include指令引入head.jspf页面和foot.jspf页面,代码如下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>jsp的Include指令测试</title>
</head>

<body>
<%--使用include标签引入引入其它JSP页面--%>
<%@include file="/jspfragments/head.jspf" %>
<h1>网页主体内容</h1>
<%@include file="/jspfragments/foot.jspf" %>
</body>
</html>

运行结果如下:

我们查看一下jsp引擎将IncludeTagTest.jsp翻译成IncludeTagTest_jsp类之后的源代码,找到Tomcat服务器的work\Catalina\localhost\JavaWeb_Jsp_Study_20140603\org\apache\jsp目录下找到IncludeTagTest_jsp.java,如下图所示:

打开IncludeTagTest_jsp.java,里面的代码如下所示:

package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import java.util.*;
import java.util.*;
import java.util.*;
public final class IncludeTagTest_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
private static java.util.List _jspx_dependants;
static {
_jspx_dependants = new java.util.ArrayList(2);
_jspx_dependants.add("/jspfragments/head.jspf");
_jspx_dependants.add("/jspfragments/foot.jspf");
}
private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.AnnotationProcessor _jsp_annotationprocessor;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspInit() {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
_jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());
}
public void _jspDestroy() {
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" \r\n");
out.write(" <title>jsp的Include指令测试</title>\r\n");
out.write(" \r\n");
out.write(" </head>\r\n");
out.write(" \r\n");
out.write(" <body>\r\n");
out.write(" ");
out.write("\r\n");
out.write("<h1 style=\"color:red;\">网页头部</h1>\r\n");
out.write("\r\n");
out.write(" <h1>网页主体内容</h1>\r\n");
out.write(" ");
out.write("\r\n");
out.write("<h1 style=\"color:blue;\">网页尾部</h1>\r\n");
out.write("\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try { out.clearBuffer(); } catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}

可以看到,head.jspf和foot.jspf页面的内容都使用out.write输出到浏览器显示了。
3.2、总结@include指令
使用@include可以包含任意的内容,文件的后缀是什么都无所谓。这种把别的文件内容包含到自身页面的@include语句就叫作静态包含,作用只是把别的页面内容包含进来,属于静态包含。
3.3、jsp:include指令
jsp:include指令为动态包含,如果被包含的页面是JSP,则先处理之后再将结果包含,而如果包含的是非*.jsp文件,则只是把文件内容静态包含进来,功能与@include类似。后面再具体介绍

(十七)——JSP中的九个内置对象
一、JSP运行原理
每个JSP 页面在第一次被访问时,WEB容器都会把请求交给JSP引擎(即一个Java程序)去处理。JSP引擎先将JSP翻译成一个_jspServlet(实质上也是一个servlet) ,然后按照servlet的调用方式进行调用。
由于JSP第一次访问时会翻译成servlet,所以第一次访问通常会比较慢,但第二次访问,JSP引擎如果发现JSP没有变化,就不再翻译,而是直接调用,所以程序的执行效率不会受到影响。
JSP引擎在调用JSP对应的_jspServlet时,会传递或创建9个与web开发相关的对象供_jspServlet使用。JSP技术的设计者为便于开发人员在编写JSP页面时获得这些web对象的引用,特意定义了9个相应的变量,开发人员在JSP页面中通过这些变量就可以快速获得这9大对象的引用。
二、认识九个内置对象

NO. 内置对象 类型
1 pageContext javax.servlet.jsp.PageContext
2 request javax.servlet.http.HttpServletRequest
3 response javax.servlet.http.HttpServletResponse
4 session javax.servlet.http.HttpSession
5 application javax.servlet.ServletContext
6 config javax.servlet.ServletConfig
7 out javax.servlet.jsp.JspWriter
8 page java.lang.Object
9 exception java.lang.Throwable




request,response,session,application,config这些对象在前面都已经作了详细的介绍,这里重点介绍一下剩下的pageContext对象,out对象,page对象。
三、内置对象使用说明
3.1、page对象
page对象表示当前一个JSP页面,可以理解为一个对象本身,即:把一个JSP当作一个对象来看待。page对象在开发中几乎不用,了解一下即可
3.2、out对象
out对象用于向客户端发送文本数据。
out对象是通过调用pageContext对象的getOut方法返回的,其作用和用法与ServletResponse.getWriter方法返回的PrintWriter对象非常相似。
JSP页面中的out对象的类型为JspWriter,JspWriter相当于一种带缓存功能的PrintWriter,设置JSP页面的page指令的buffer属性可以调整它的缓存大小,甚至关闭它的缓存。
只有向out对象中写入了内容,且满足如下任何一个条件时,out对象才去调用ServletResponse.getWriter方法,并通过该方法返回的PrintWriter对象将out对象的缓冲区中的内容真正写入到Servlet引擎提供的缓冲区中:
设置page指令的buffer属性关闭了out对象的缓存功能
out对象的缓冲区已满
整个JSP页面结束
out对象的工作原理图

3.3、pageContext对象
pageContext对象是JSP技术中最重要的一个对象,它代表JSP页面的运行环境,这个对象不仅封装了对其它8大隐式对象的引用,它自身还是一个域对象(容器),可以用来保存数据。并且,这个对象还封装了web开发中经常涉及到的一些常用操作,例如引入和跳转其它资源、检索其它域对象中的属性等。
3.4、通过pageContext获得其他对象
getException方法返回exception隐式对象
getPage方法返回page隐式对象
getRequest方法返回request隐式对象
getResponse方法返回response隐式对象
getServletConfig方法返回config隐式对象
getServletContext方法返回application隐式对象
getSession方法返回session隐式对象
getOut方法返回out隐式对象
3.5、pageContext封装其它8大内置对象的意义
如果在编程过程中,把pageContext对象传递给一个普通java对象,那么这个java对象将可以获取8大隐式对象,此时这个java对象就可以和浏览器交互了,此时这个java对象就成为了一个动态web资源了,这就是pageContext封装其它8大内置对象的意义,把pageContext传递给谁,谁就能成为一个动态web资源,那么什么情况下需要把pageContext传递给另外一个java类呢,什么情况下需要使用这种技术呢,在比较正规的开发中,jsp页面是不允许出现java代码的,如果jsp页面出现了java代码,那么就应该想办法把java代码移除掉,我们可以开发一个自定义标签来移除jsp页面上的java代码,首先围绕自定义标签写一个java类,jsp引擎在执行自定义标签的时候就会调用围绕自定义标签写的那个java类,在调用java类的时候就会把pageContext对象传递给这个java类,由于pageContext对象封装了对其它8大隐式对象的引用,因此在这个java类中就可以使用jsp页面中的8大隐式对象(request,response,config,application,exception,Session,page,out)了,pageContext对象在jsp自定义标签开发中特别重要。
3.6、pageContext作为域对象
pageContext对象可以作为容器来使用,因此可以将一些数据存储在pageContext对象中。
pageContext对象的常用方法
public void setAttribute(java.lang.String name,java.lang.Object value)
public java.lang.Object getAttribute(java.lang.String name)
public void removeAttribute(java.lang.String name)
public java.lang.Object findAttribute(java.lang.String name)
重点介绍一下findAttribute方法,这个方法是用来查找各个域中的属性的,查看这个方法的API可以看到关于这个方法的描述:
Searches for the named attribute in page, request, session (if valid), and application scope(s) in order and returns the value associated or null.
当要查找某个属性时,findAttribute方法按照查找顺序"page→request→session→application"在这四个对象中去查找,只要找到了就返回属性值,如果四个对象都没有找到要查找的属性,则返回一个null。
范例:使用pageContext的findAttribute方法查找属性值

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<head>
<title>pageContext的findAttribute方法查找属性值</title>
</head>
<%
pageContext.setAttribute("name1", "孤傲苍狼");
request.setAttribute("name2", "白虎神皇");
session.setAttribute("name3", "玄天邪帝");
application.setAttribute("name4", "灭世魔尊");
%>
<%
//使用pageContext的findAttribute方法查找属性,由于取得的值为Object类型,因此必须使用String强制向下转型,转换成String类型
//查找name1属性,按照顺序"page→request→session→application"在这四个对象中去查找
String refName1 = (String)pageContext.findAttribute("name1");
String refName2 = (String)pageContext.findAttribute("name2");
String refName3 = (String)pageContext.findAttribute("name3");
String refName4 = (String)pageContext.findAttribute("name4");
String refName5 = (String)pageContext.findAttribute("name5");//查找一个不存在的属性
%>
<h1>pageContext.findAttribute方法查找到的属性值:</h1>
<h3>pageContext对象的name1属性:<%=refName1%></h3>
<h3>request对象的name2属性:<%=refName2%></h3>
<h3>session对象的name3属性:<%=refName3%></h3>
<h3>application对象的name4属性:<%=refName4%></h3>
<h3>查找不存在的name5属性:<%=refName5%></h3>
<hr/>
<h1>使用EL表达式进行输出:</h1>
<h3>pageContext对象的name1属性:${name1}</h3>
<h3>request对象的name2属性:${name2}</h3>
<h3>session对象的name3属性:${name3}</h3>
<h3>application对象的name4属性:${name4}</h3>
<h3>不存在的name5属性:${name5}</h3>

运行结果:

EL表达式语句在执行时,会调用pageContext.findAttribute方法,用标识符为关键字,分别从page、request、 session、application四个域中查找相应的对象,找到则返回相应对象,找不到则返回”” (注意,不是null,而是空字符串)。
pageContext对象中封装了访问其它域的方法
public java.lang.Object getAttribute(java.lang.String name,int scope)
public void setAttribute(java.lang.String name, java.lang.Object value,int scope)
public void removeAttribute(java.lang.String name,int scope)
代表各个域的常量
PageContext.APPLICATION_SCOPE
PageContext.SESSION_SCOPE
PageContext.REQUEST_SCOPE
PageContext.PAGE_SCOPE
范例:pageContext访问其它域

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<head>
<title>pageContext访问其它域</title>
</head>
<%
//此时相当于往session对象中存放了一个name属性,等价于 session.setAttribute("name","孤傲苍狼");
pageContext.setAttribute("name","孤傲苍狼",PageContext.SESSION_SCOPE);
%>
<%
//取得session对象的属性,使用pageContext对象获取
String refName1 = (String)pageContext.getAttribute("name",PageContext.SESSION_SCOPE);
//由于取得的值为Object类型,因此必须使用String强制向下转型,转换成String类型
String refName2 = (String)session.getAttribute("name");
%>
<h1>取出存放在session对象中的属性值:</h1>
<p>第一种做法:使用pageContext.getAttribute("attributeName",PageContext.SESSION_SCOPE);去取出session对象中值</p>
<h3>姓名:<%=refName1%></h3>
<p>第二种做法:使用session.getAttribute("attributeName");去取出session对象中值</p>
<h3>姓名:<%=refName2%></h3>


3.7、PageContext引入和跳转到其他资源
PageContext类中定义了一个forward方法(用来跳转页面)和两个include方法(用来引入页面)来分别简化和替代RequestDispatcher.forward方法和include方法。
方法接收的资源如果以“/”开头, “/”代表当前web应用。
范例:使用pageContext的forward方法跳转到其他页面

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<head>
<title>使用pageContext的forward方法跳转页面</title>
</head>
<%
//使用pageContext的forward方法跳转到pageContextDemo05.jsp页面,/代表了当前的web应用
pageContext.forward("/pageContextDemo05.jsp");
//使用pageContext.forward(relativeUrlPath)替代RequestDispatcher.forward(relativeUrlPath)
//使用RequestDispatcher的forward方法实现的跳转方式
//pageContext.getRequest().getRequestDispatcher("/pageContextDemo05.jsp").forward(request, response);
%>

运行结果如下:

pageContext.forward("/pageContextDemo05.jsp");
这种写法是用来简化和替代pageContext.getRequest().getRequestDispatcher("/pageContextDemo05.jsp").forward(request, response);这种写法的。在实际开发中,使用pageContext.forward(relativeUrlPath)方法跳转页面用得不多,主要是因为要在Jsp页面中嵌套java代码,所以这种做法简单了解一下即可,在开发中,要想从一个Jsp页面采用服务器端跳转的方式跳转到另一个Jsp页面,那么一般会使用<jsp:forward>标签,<jsp:forward>标签用于把请求转发给另外一个资源。
范例:使用pageContext的include方法引入资源

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<head>
<title>使用pageContext的include方法引入资源</title>
</head>
<%
pageContext.include("/jspfragments/head.jsp");
%>
使用pageContext的include方法引入资源
<%
pageContext.include("/jspfragments/foot.jsp");
%>
<hr/>
<%--
<jsp:include page="/jspfragments/head.jsp"/>
使用jsp:include标签引入资源
<jsp:include page="/jspfragments/foot.jsp"/>
--%>

运行结果:

在实际开发中,使用pageContext的include方法引入页面这种做法也很少用,一般都使用jsp:include标签引入资源,因此这种做法了解一下即可。

(十八)——JSP属性范围
所谓的属性范围就是一个属性设置之后,可以经过多少个其他页面后仍然可以访问的保存范围。
一、JSP属性范围
JSP中提供了四种属性范围,四种属性范围分别指以下四种:
当前页:一个属性只能在一个页面中取得,跳转到其他页面无法取得
一次服务器请求:一个页面中设置的属性,只要经过了服务器跳转,则跳转之后的页面可以继续取得。
一次会话:一个用户设置的内容,只要是与此用户相关的页面都可以访问(一个会话表示一个人,这个人设置的东西只要这个人不走,就依然有效)
上下文中:在整个服务器上设置的属性,所有人都可以访问
二、属性的操作方法
既然JSP中提供了四种属性范围,则四种属性范围中都将包含以下的属性操作方法。
No.
方法
描述
public void setAttribute(String name,Object value)
设置属性
public object getAttribute(String name)
取得属性
public void removeAttribute(String name)
删除属性
属性的操作无外乎就是增加、取得和删除这个几个操作。
单词Attribute的意思是“属性”,setAttribute(String name,Object value)从单词的组合来看就可以知道是这个方法的是设置属性,设置属性的名字和属性的值,名字(name)为String类型,值(value)为Object类型,由于值为Object类型,这表示可以设置任意类型的数据作为值,因为所有的类都是从Object类型继承而来。因此设置属性值的时候可以是任意类型的数据。getAttribute(String name)方法是根据属性的名字取得属性,removeAttribute(String name)方法是根据属性的名字删除属性。
三、JSP四种属性范围的具体介绍
3.1、page属性范围(pageContext)
page属性范围相对好理解一些:在一个页面设置的属性,跳转到其他页面就无法访问了。但是在使用page属性范围的时候必须注意的是,虽然习惯上将页面范围的属性称为page范围,但是实际上操作的时候是使用pageContext内置对象完成的。
pageContext属性范围操作流程图

pageContext从字面上的定义,可以发现,是表示一个页面(page)的上下文(Context),可以表示一个页面中的所有内容。
从操作流程图来看,在第一个页面设置的属性经过服务器端跳转到第二个页面以后,在第二个页面是无法取得在第一个页面中设置的属性的,就好比现在坐着的桌子上有一支笔,但一旦离开了这张桌子,坐到别的桌子上时,笔就没有了。
下面通过代码来观察此范围的属性
范例:pageContextDemo01.jsp
在页面中设置两个属性

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
//此时设置的属性只能够在本页中取得
pageContext.setAttribute("name","孤傲苍狼"); //设置属性
pageContext.setAttribute("date",new Date()); //设置属性
//注意:这里设置的两个属性的名字分别为name和date,这两个是字符串类型的数据,但对应的属性值MLDN和new Date这个两个值却不是字符串类型,而是两个Object类型的数据。
%>
<%
//取得设置的属性
String refName = (String)pageContext.getAttribute("name");
//由于取得的值为Object类型,因此必须使用String强制向下转型,转换成String类型
Date refDate = (Date)pageContext.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>

程序运行结果如下:

这说明了在本页设置的pageContext范围属性在本页确实可以取得,下面使用跳转语句,观察跳转之后是否还可以取得属性。
范例:pageScopeDemo02.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
pageContext.setAttribute("name","孤傲苍狼");
pageContext.setAttribute("date",new Date());
%>
<%--使用jsp:forward标签进行服务器端跳转--%>
<jsp:forward page="/pageScopeDemo03.jsp" />

范例:pageScopeDemo03.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
String refName = (String)pageContext.getAttribute("name");
Date refDate = (Date)pageContext.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>

在以上程序中的pageScopeDemo02.jsp只是设置了两个属性,跳转到pageScopeDemo03.jsp之后再在pageScopeDemo03.jsp中取在pageScopeDemo02.jsp设置的page属性。此时,运行结果如下:

使用了服务器端跳转,但是发现内容并不能取得,证明page范围的属性只能在本页中取得,跳转到其他页面之中不能取得。如果现在希望跳转到其他页面之中,依然可以取得,则可以扩大属性范围,使用request属性范围即可。
3.2、request属性范围
request属性范围表示在一次服务器跳转中有效,只要是服务器跳转,则设置的request属性可以一直传递下去。

范例:requestScopeDemo01.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
request.setAttribute("name","孤傲苍狼");
request.setAttribute("date",new Date());
%>
<%--使用jsp:forward标签进行服务器端跳转--%>
<jsp:forward page="/requestScopeDemo02.jsp" />

范例:requestScopeDemo02.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
//取得requestScopdemo01.jsp设置的属性
String refName = (String)request.getAttribute("name");
Date refDate = (Date)request.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>

运行结果如下:

从运行结果来看,程序跳转了,但是与page范围相比,内容可以向下继续传递,即在第一个页面设置的属性跳转到第二个页面后在第二个页面中依然可以取得第一个页面设置的属性。
如果现在有第三个页面了,则也可以继续向后传递
范例:修改requestScopeDemo02.jsp
<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%--使用jsp:forward标签进行服务器端跳转--%>
<jsp:forward page="/requestScopeDemo03.jsp" />
范例:requestScopeDemo03.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
//取得requestScopdemo01.jsp设置的属性
String refName = (String)request.getAttribute("name");
Date refDate = (Date)request.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>

以上的结果依然可以访问,但是如果,此时使用了超链接的方式传递的话,则属性是无法向下继续传递的。
范例:修改requestScopeDemo03.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
//取得requestScopdemo01.jsp设置的属性
String refName = (String)request.getAttribute("name");
Date refDate = (Date)request.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>
<h1>
<%--使用超链接的形式跳转,这是客户端跳转,URL地址会改变--%>
<a href="${pageContext.request.contextPath}/requestScopeDemo04.jsp">跳转到requestScopeDemo04.jsp</a>
</h1>

此时使用了超链接跳转,一旦跳转之后,地址栏改变,所以此种跳转也可以称为客户端跳转。
requestScopeDemo04.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
//取得requestScopdemo01.jsp设置的属性
String refName = (String)request.getAttribute("name");
Date refDate = (Date)request.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>

运行结果:


requestScopeDemo04.jsp页面显示的结果是null。这说明了在requestScopeDemo01.jsp这个页面设置的属性经过超链接这种客户端跳转到别的页面时别的页面是无法取得requestScopeDemo01.jsp中设置的属性的。
如果还想进一步扩大属性范围,则可以使用session范围属性
3.3、session属性范围
session设置的属性不管如何跳转,都可以取得的。当然,session只针对一个用户

在第一个页面上设置的属性,跳转(服务器跳转/客户端跳转)到其他页面之后,其他的页面依然可以取得第一个页面上设置的属性。
下面通过代码来观察session属性范围
范例:sessionScopeDemo01.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
//此时设置的属性只能够在与本页相关的任何页面中取得
session.setAttribute("name","孤傲苍狼"); //设置属性
session.setAttribute("date",new Date());
%>
<%--使用服务器端跳转--%>
<jsp:forward page="/sessionScopeDemo02.jsp"/>

这里使用的是服务器端跳转
sessionScopeDemo02.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
String refName = (String)session.getAttribute("name");
Date refDate = (Date)session.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>
<%--使用超链接这种客户端跳转--%>
<h1><a href="${pageContext.request.contextPath}/sessionScopeDemo03.jsp">sessionScopeDemo03</a></h1>

这里使用的是超链接这种客户端跳转
运行程序sessionScopeDemo01.jsp结果如下所示:

sessionScopeDemo03.jsp

<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.*"%>
<%
String refName = (String)session.getAttribute("name");
Date refDate = (Date)session.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>

点击超链接sessionScopeDemo03,跳转到了sessionScopeDemo03.jsp这个页面,此时程序的运行结果如下:

这说明了即使是采用客户端跳转,在别的页面依然可以取得第一个页面中设置的session属性。但是,如果,此时新开了一个浏览器,则sessionScopeDemo03.jsp肯定无法取得sessionScopeDemo01.jsp中设置的session对象的属性,因为session只是保留了一个人的信息。
如果一个属性想让所有的用户都可以访问,则可以使用最后一种属性范围:application范围。
3.4、application属性范围

因为application属性范围是在服务器上设置的一个属性,所以一旦设置之后任何用户都可以浏览到此属性。
下面通过代码来观察application属性范围
范例:applicationScopeDemo01.jsp

<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="java.util.*"%>
<%
//此时设置的属性任何用户都可以取得
application.setAttribute("name","孤傲苍狼"); //设置属性
application.setAttribute("date",new Date());
%>
<h1><a href="${pageContext.request.contextPath}/applicationScopeDemo02.jsp">applicationScopeDemo02</a></h1>

范例:applicationScopeDemo02.jsp

<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="java.util.*"%>
<%
String refName = (String)application.getAttribute("name");
Date refDate = (Date)application.getAttribute("date");
%>
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>

观察页面的运行效果:

开启多个浏览器窗口,运行applicationScopeDemo02.jsp时,都可以显示出上图所示的结果,因为属性范围设置在了服务器中,所以只要是连接到此服务器的任意用户都可以取得此属性,当然,如果服务器关闭的话,则此属性肯定消失。
如把Tomcat服务器先关闭后再重新启动,打开浏览器窗口运行applicationScopeDemo02.jsp时,得到的结果如下图所示:

注意:如果在服务器上设置了过多的application属性,则会影响到服务器的性能。
3.5、关于pageContext属性范围的进一步补充
之前所讲解的四种属性范围,实际上都是通过pageContext属性范围设置上的。打开pageContext所在的说明文档。

PageContext类继承了JspContext类,在JspContext类中定义了setAttribute方法,如下:
public abstract void setAttribute(String name,Object value,int scope)
此方法中存在一个scope的整型变量,此变量就表示一个属性的保存范围。


PageContext类继承了JspContext类,所以在PageContext类中实现了抽象的setAttribute方法:
public abstract void setAttribute(String name,Object value,int scope)
这个setAttribute()方法如果不写后面的int类型的scope参数,则此参数默认为PAGE_SCOPE,则此时setAttribute()方法设置的就是page属性范围,如果传递过来的int类型参数scope为REQUEST_SCOPE,则此时setAttribute()方法设置的就是request属性范围,同理,传递的scope参数为SESSION_SCOPE和APPLICATION_SCOPE时,则表示setAttribute()方法设置的就是session属性范围和application属性范围。
下面通过代码来观察此四种属性范围常量的作用,以:request为例
范例:pageScopeDemo04.jsp

<%@page contentType="text/html;charset=GBK"%>
<%@page import="java.util.*"%>
<%
pageContext.setAttribute("name","孤傲苍狼",PageContext.REQUEST_SCOPE); //设置属性,并指明属性范围
pageContext.setAttribute("date",new Date(),PageContext.REQUEST_SCOPE); //设置属性,并指明属性范围
%>
<jsp:forward page="/pageScopeDemo05.jsp"/>

pageScopeDemo05.jsp

<%@page contentType="text/html;charset=GBK"%>
<%@page import="java.util.*"%>
<%
//使用request对象获取属性
String refName = (String)request.getAttribute("name");
Date refDate = (Date)request.getAttribute("date");
//也可以使用pageContext对象获取属性,只要在获取时指明对象的属性范围即可
String refName2 = (String)pageContext.getAttribute("name", PageContext.REQUEST_SCOPE);
Date refDate2 = (Date)pageContext.getAttribute("date", PageContext.REQUEST_SCOPE);
%>
使用request对象获取属性:
<h1>姓名:<%=refName%></h1>
<h1>日期:<%=refDate%></h1>
使用pageContext对象获取属性:
<h1>姓名:<%=refName2%></h1>
<h1>日期:<%=refDate2%></h1>

运行结果:

从运行结果可以看到:在pageScopeDemo04.jsp使用的是pageContext对象调用setAttribute()方法设置的属性范围是request的属性范围,因此在调用此方法时,把一个int类型的scope范围常量REQUEST_SCOPE传递了进来,这个REQUEST_SCOPE属性范围常量的作用就是告诉pageContext对象现在要设置的属性范围是request的属性范围,所以pageScopeDemo05.jsp这个页面中可以直接使用request.getAttribute();方法获取在pageScopeDemo04.jsp设置的属性。
四、jsp四种属性范围的使用场合
1、request:如果客户向服务器发请求,产生的数据,用户看完就没用了,像这样的数据就存在request域,像新闻数据,属于用户看完就没用的。
2、session:如果客户向服务器发请求,产生的数据,用户用完了等一会儿还有用,像这样的数据就存在session域中,像购物数据,用户需要看到自己购物信息,并且等一会儿,还要用这个购物数据结帐。
3、application(servletContext):如果客户向服务器发请求,产生的数据,用户用完了,还要给其它用户用,像这样的数据就存在application(servletContext)域中,像聊天数据。

(十九)——JSP标签
一、JSP标签介绍
JSP标签也称之为Jsp Action(JSP动作)元素,它用于在Jsp页面中提供业务逻辑功能,避免在JSP页面中直接编写java代码,造成jsp页面难以维护。
二、JSP常用标签
jsp的常用标签有以下三个
<jsp:include>标签
<jsp:forward>标签
<jsp:param>标签
2.1、<jsp:include>标签
<jsp:include>标签用于把另外一个资源的输出内容插入进当前JSP页面的输出内容之中,这种在JSP页面执行时的引入方式称之为动态引入。
语法:
<jsp:include page="relativeURL | <%=expression%>" flush="true|false" />
page属性用于指定被引入资源的相对路径,它也可以通过执行一个表达式来获得。
flush属性指定在插入其他资源的输出内容时,是否先将当前JSP页面的已输出的内容刷新到客户端。
范例:使用jsp:include标签引入资源

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>jsp的jsp:include标签测试</title>
</head>
<body>
<%--使用jsp:include标签引入其它JSP页面--%>
<jsp:include page="/jspfragments/head.jsp"/>
<h1>网页主体内容</h1>
<jsp:include page="/jspfragments/foot.jsp"/>
</body>
</html>

运行结果:

2.2、<jsp:include>标签与include指令的区别
<jsp:include>标签是动态引入, <jsp:include>标签涉及到的2个JSP页面会被翻译成2个servlet,这2个servlet的内容在执行时进行合并。
而include指令是静态引入,涉及到的2个JSP页面会被翻译成一个servlet,其内容是在源文件级别进行合并。
通过下面的例子来说明<jsp:include>标签与include指令的区别
demo.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%!
int i=1000;
%>
<h1>demo.jsp中i的值为:<%=i%></h1>
分别使用include指令和<jsp:include>标签两种包含语句,包含以上的demo.jsp
范例:使用@include包含内容
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%!
int i=10;
%>
<h1>JspIncludeTagDemo01.jsp中i的值为:<%=i%></h1>
<h1><%@include file="/jspfragments/demo.jsp"%></h1>
此时在编译jsp时就已经提示出错了,如下所示:

这个错误说的是变量i已经重复定义了
运行JspIncludeTagDemo01.jsp,结果如下:

运行后发现出现了重复定义变量i的错误提示信息,因为静态包含是将全部内容包含进来之后,再进行处理,属于先包含后处理。由于被包含进来的页面demo.jsp中定义了一个变量i,而包含页面JspIncludeTagDemo01.jsp本身又定义了一个变量i,所以服务器在处理JspIncludeTagDemo01.jsp这个页面时就会发现里面有两个重复定义的变量i,因此就会报错。
而如果现在使用的是<jsp:include>动态包含的话,观察以下程序:
范例:使用动态包含

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1>JspIncludeTagDemo02.jsp</h1>
<%!
int i=10;
%>
<h1>JspIncludeTagDemo02.jsp中i的值为:<%=i%></h1>
<h1><jsp:include page="/jspfragments/demo.jsp" /></h1>

运行结果:

发现结果已经可以正确地显示,而且不会互相影响,这是因为使用jsp:include属于动态包含,动态包含就是指先将各个页面分别处理,处理完之后再将处理后的结果包含进来。
不管是<jsp:include>标签,还是include指令,它们都会把两个JSP页面内容合并输出,所以这两个页面不要出现重复的HTML全局架构标签,否则输出给客户端的内容将会是一个格式混乱的HTML文档。
2.3、*.jspf扩展名文件在jsp:include、@include和c:import中的区别
JSP规范建议使用.jspf(JSP fragments)作为静态引入文件的扩展名。今天无意中发现,把一个JSP文件命名为jspf扩展名,然后include到另一个jsp文件中的,发现只有用"@include"指令的时候,jspf文件的内容才会被解析并执行其中的jsp指令和tag,而使用"jsp:include"和JSTL的"c:import"都没有用,jspf文件被当作纯文本文件处理了。
比如现在有一个head.jspf页面和foot.jspf页面
head.jspf
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1 style="color:red;">网页头部</h1>
foot.jspf
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1 style="color:blue;">网页尾部</h1>
首先使用"@include"指令将"head.jspf和foot.jspf" include到IncludeTagTest.jsp页面,如下所示:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>jsp的Include指令测试</title>
</head>
<body>
 <%--使用include标签引入引入jspf页面--%>
<%@include file="/jspfragments/head.jspf" %>
<h1>网页主体内容</h1>
<%@include file="/jspfragments/foot.jspf" %>
</body>
</html>

运行IncludeTagTest.jsp页面,运行结果如下:

jspf文件的内容会被解析并执行其中的jsp指令和tag,查看浏览器解析JspIncludeTagTest.jsp页面生成的源代码,如下所示:

然后再使用<jsp:include>"标签将"head.jspf和foot.jspf" include到JspIncludeTagTest.jsp页面中,如下所示:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>jsp的jsp:include标签测试</title>
</head>
<body>
<%--使用jsp:include标签引入其它JSPf页面--%>
<jsp:include page="/jspfragments/head.jspf"/>
<h1>网页主体内容</h1>
<jsp:include page="/jspfragments/foot.jspf"/>
</body>
</html>

运行JspIncludeTagTest.jsp页面,运行结果如下:

查看浏览器解析JspIncludeTagTest.jsp页面生成的源代码,如下所示:

可以看到,head.jspf和foot.jspf中的<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>没有解析执行,而是原封不动地作为文本内容输出到页面上了,在IE下看不到<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>的输出,在google和火狐浏览器下运行可以看到页面上输出<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>,如下所示:

这说明jspf文件Tomcat服务器被当作纯文本文件处理了,没有当作jsp页面来解析执行,那么该如何解决这个问题呢?如何让tomcat服务器能够解析执行*.jspf文件中的java代码和标签呢,有如下的几种解决办法:
解决办法一:修改web.xml文件,添加对扩展名为*.jspf文件的映射
如下所示:

<!-- 让jspf扩展名同样成为JSP Servlet处理的文件。 -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jspf</url-pattern>
</servlet-mapping>
<!-- 让jsp扩展名同样成为JSP Servlet处理的文件。 -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>

上面的配置方式也可以简写成这样:
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<!-- 让jspf扩展名同样成为JSP Servlet处理的文件。-->
<url-pattern>*.jspf</url-pattern>
</servlet-mapping>
两种写法的效果都是一样的。
添加这样的配置信息后,此时tomcat服务器就可以正常解析执行*.jspf文件了,如下所示:

解决办法二:修改Tomcat服务器的web.xml文件,添加对扩展名为*.jspf文件的映射
找到tomcat服务器的web.xml文件,如下所示:

找到名字为jsp的那个Servlet,如下所示:

<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>

然后根据Servlet名找到对应的servlet-mapping配置,如下所示:
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
在这里可以看到,名字为jsp的那个Servlet只支持*.jsp和*.jspx两种扩展名,因此可以在这个地方添加多一个<url-pattern>*.jspf</url-pattern>,如下所示:

<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
<url-pattern>*.jspf</url-pattern>
</servlet-mapping>

经过这样的配置之后,Tomcat服务器就可以正常解析和执行*.jspf文件了。
2.3、<jsp:forward>标签
<jsp:forward>标签用于把请求转发给另外一个资源。
语法:
<jsp:forward page="relativeURL | <%=expression%>" />
page属性用于指定请求转发到的资源的相对路径,它也可以通过执行一个表达式来获得。
范例:使用<jsp:forward>标签跳转页面
forwarddemo01.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--使用<jsp:forward>标签跳转到forwarddemo02.jsp--%>
<jsp:forward page="/forwarddemo02.jsp"/>
forwarddemo02.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1>跳转之后的页面!!</h1>
运行结果如下:

从页面的显示效果来看,页面已经完成了跳转,但地址栏没有变化,地址栏中显示的地址还是forwarddemo01.jsp,但页面显示的内容却是forwardemo02.jsp中的内容。因为此跳转属于服务器端跳转。只要是服务器端跳转,则地址栏永远没有变化。
2.4、<jsp:param>标签
当使用<jsp:include>和<jsp:forward>标签引入或将请求转发给其它资源时,可以使用<jsp:param>标签向这个资源传递参数。
语法1:
<jsp:include page="relativeURL | <%=expression%>">
<jsp:param name="parameterName" value="parameterValue|<%= expression %>" />
</jsp:include>
语法2:
<jsp:forward page="relativeURL | <%=expression%>">
<jsp:param name="parameterName" value="parameterValue|<%= expression %>" />
</jsp:include>
<jsp:param>标签的name属性用于指定参数名,value属性用于指定参数值。在<jsp:include>和<jsp:forward>标签中可以使用多个<jsp:param>标签来传递多个参数。
范例:使用<jsp:param>标签向被包含的页面传递参数
JspIncludeTagDemo03.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1>JspIncludeTagDemo03.jsp</h1>
<hr/>
<jsp:include page="/jspfragments/Inc.jsp">
<jsp:param name="parm1" value="hello" />
<jsp:param name="parm2" value="gacl" />
</jsp:include>

Inc.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1>接收从JspIncludeTagDemo03.jsp页面中传递过来的参数:</h1>
<h2><%=request.getParameter("parm1")%></h2>
<h2><%=request.getParameter("parm2")%></h2>
在JspIncludeTagDemo03.jsp页面中使用<jsp:include>标签将Inc.jsp页面包含进来,使用<jsp:param/>标签向Inc.jsp页面传递了两个参数parm1和parm2
JspIncludeTagDemo03.jsp页面运行结果如下:

范例:使用<jsp:param>标签向要跳转的页面传递参数
forwarddemo03.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--使用<jsp:forward>标签跳转到forwarddemo04.jsp--%>
<%--使用<jsp:param>标签向forwarddemo04.jsp传递参数--%>
<jsp:forward page="/forwarddemo04.jsp">
<jsp:param name="ref1" value="hello" />
<jsp:param name="ref2" value="gacl" />
</jsp:forward>

forwarddemo04.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<h1>跳转之后的页面!!</h1>
<h1>接收从forwarddemo03.jsp传递过来的参数:</h1>
<h1>ref1:<%=request.getParameter("ref1")%></h1>
<h1>ref2:<%=request.getParameter("ref2")%></h1>
运行结果如下:

(二十)——JavaBean总结
一、什么是JavaBean
JavaBean是一个遵循特定写法的Java类,它通常具有如下特点:
这个Java类
必须具有一个无参的构造函数
属性必须私有化
私有化的属性必须通过public类型的方法暴露给其它程序,并且方法的命名也必须遵守一定的命名规范。
javaBean范例:
package gacl.javabean.study;
/**
* @author gacl
* Person类就是一个最简单的JavaBean
*/
public class Person {
//------------------Person类封装的私有属性---------------------------------------
// 姓名 String类型
private String name;
// 性别 String类型
private String sex;
// 年龄 int类型
private int age;
//是否已婚 boolean类型
private boolean married;
//---------------------------------------------------------
//------------------Person类的无参数构造方法---------------------------------------
/**
* 无参数构造方法
*/
public Person() {
}
//---------------------------------------------------------
//------------------Person类对外提供的用于访问私有属性的public方法---------------------------------------
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
//---------------------------------------------------------
}

JavaBean在J2EE开发中,通常用于封装数据,对于遵循以上写法的JavaBean组件,其它程序可以通过反射技术实例化JavaBean对象,并且通过反射那些遵守命名规范的方法,从而获知JavaBean的属性,进而调用其属性保存数据。
二、JavaBean的属性
JavaBean的属性可以是任意类型,并且一个JavaBean可以有多个属性。每个属性通常都需要具有相应的setter、 getter方法,setter方法称为属性修改器,getter方法称为属性访问器。
属性修改器必须以小写的set前缀开始,后跟属性名,且属性名的第一个字母要改为大写,例如,name属性的修改器名称为setName,password属性的修改器名称为setPassword。
属性访问器通常以小写的get前缀开始,后跟属性名,且属性名的第一个字母也要改为大写,例如,name属性的访问器名称为getName,password属性的访问器名称为getPassword。
一个JavaBean的某个属性也可以只有set方法或get方法,这样的属性通常也称之为只写、只读属性。
三、在JSP中使用JavaBean
JSP技术提供了三个关于JavaBean组件的动作元素,即JSP标签,它们分别为:
<jsp:useBean>标签:用于在JSP页面中查找或实例化一个JavaBean组件。
<jsp:setProperty>标签:用于在JSP页面中设置一个JavaBean组件的属性。
<jsp:getProperty>标签:用于在JSP页面中获取一个JavaBean组件的属性。
3.1、<jsp:useBean>标签
<jsp:useBean>标签用于在指定的域范围内查找指定名称的JavaBean对象,如果存在则直接返回该JavaBean对象的引用,如果不存在则实例化一个新的JavaBean对象并将它以指定的名称存储到指定的域范围中。
常用语法:
<jsp:useBean id="beanName" class="package.class" scope="page|request|session|application"/>
"id"属性用于指定JavaBean实例对象的引用名称和其存储在域范围中的名称。
"class"属性用于指定JavaBean的完整类名(即必须带有包名)。
"scope"属性用于指定JavaBean实例对象所存储的域范围,其取值只能是page、request、session和application等四个值中的一个,其默认值是page。

<jsp:useBean>标签使用范例:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--
在jsp中使用jsp:useBean标签来实例化一个Java类的对象
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
┝<jsp:useBean>:表示在JSP中要使用JavaBean。
┝id:表示生成的实例化对象,凡是在标签中看见了id,则肯定表示一个实例对象。
┝class:此对象对应的包.类名称
┝scope:此javaBean的保存范围,四种范围:page、request、session、application
--%>
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
<%
//person对象在上面已经使用jsp:useBean标签实例化了,因此在这里可以直接使用person对象
//使用setXxx方法为对象的属性赋值
//为person对象的name属性赋值
person.setName("孤傲苍狼");
//为person对象的Sex属性赋值
person.setSex("男");
//为person对象的Age属性赋值
person.setAge(24);
//为person对象的married属性赋值
person.setMarried(false);
%>
<!DOCTYPE HTML>
<html>
<head>
<title>jsp:useBean标签使用范例</title>
</head>

<body>
<%--使用getXxx()方法获取对象的属性值 --%>
<h2>姓名:<%=person.getName()%></h2>
<h2>性别:<%=person.getSex()%></h2>
<h2>年龄:<%=person.getAge()%></h2>
<h2>已婚:<%=person.isMarried()%></h2>
</body>
</html>

运行结果如下:

3.2、<jsp:useBean>执行原理
上面我们在index.jsp中使用<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>实例化了一个gacl.javabean.study.Person类的对象,那么这个peson对象是怎么实例化出来的呢?index.jsp在执行的过程中首先会翻译成一个servlet,因此我们可以通过查看index.jsp页面生成的servlet的java代码来查看peson对象的实例化过程
找到tomcat服务器下的"work\Catalina\localhost\项目名称\org\apache\jsp"这个目录,就可以看到将index.jsp页面翻译成servlet的java源码了,如下所示:

使用文本编辑器打开index_jsp.java文件,在_jspService方法中可以看到person对象的创建过程,如下所示:

gacl.javabean.study.Person person = null;
synchronized (_jspx_page_context) {
person = (gacl.javabean.study.Person) _jspx_page_context.getAttribute("person", PageContext.PAGE_SCOPE);
if (person == null){
person = new gacl.javabean.study.Person();
_jspx_page_context.setAttribute("person", person, PageContext.PAGE_SCOPE);
}
}

下面我们来分析一下上述生成的代码:
首先是定义一个person对象,值是null
gacl.javabean.study.Person person = null;//定义一个空的person对象
然后是使用pageContext对象的getAttribute方法获取存储在PageContext.PAGE_SCOPE域中的Person对象
person = (gacl.javabean.study.Person) _jspx_page_context.getAttribute("person", PageContext.PAGE_SCOPE);
如果在PageContext.PAGE_SCOPE域中的Person对象没有找到person对象,那么就创建一个新的person对象,然后使用pageContext对象的setAttribute方法将新创建的person存储在PageContext.PAGE_SCOPE域中
if (person == null){
person = new gacl.javabean.study.Person();
_jspx_page_context.setAttribute("person", person, PageContext.PAGE_SCOPE);
}
也就是说,在index.jsp中使用<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>来实例化person对象的过程实际上是执行了上述的java代码来实例化Person对象。这就是<jsp:useBean>标签的执行原理:"首先在指定的域范围内查找指定名称的JavaBean对象,如果存在则直接返回该JavaBean对象的引用,如果不存在则实例化一个新的JavaBean对象并将它以指定的名称存储到指定的域范围中。
3.3、带标签体的<jsp:useBean>标签
语法:
<jsp:useBean ...>
Body
</jsp:useBean>
功能:
Body部分的内容只在<jsp:useBean>标签创建JavaBean的实例对象时才执行。这种做法用得不多,了解一下即可
3.4、<jsp:setProperty>标签
<jsp:setProperty>标签用于设置和访问JavaBean对象的属性。
语法格式一:
<jsp:setProperty name="beanName" property="propertyName" value="string字符串"/>
语法格式二:
<jsp:setProperty name="beanName" property="propertyName" value="<%= expression %>" />
语法格式三:
<jsp:setProperty name="beanName" property="propertyName" param="parameterName"/>
语法格式四:
<jsp:setProperty name="beanName" property= "*" />
name属性用于指定JavaBean对象的名称。
property属性用于指定JavaBean实例对象的属性名。
value属性用于指定JavaBean对象的某个属性的值,value的值可以是字符串,也可以是表达式。为字符串时,该值会自动转化为JavaBean属性相应的类型,如果value的值是一个表达式,那么该表达式的计算结果必须与所要设置的JavaBean属性的类型一致。
param属性用于将JavaBean实例对象的某个属性值设置为一个请求参数值,该属性值同样会自动转换成要设置的JavaBean属性的类型。

<jsp:setProperty>标签使用范例1:使用jsp:setProperty标签设置person对象的属性值

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--
在jsp中使用jsp:useBean标签来实例化一个Java类的对象
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
┝<jsp:useBean>:表示在JSP中要使用JavaBean。
┝id:表示生成的实例化对象,凡是在标签中看见了id,则肯定表示一个实例对象。
┝class:此对象对应的包.类名称
┝scope:此javaBean的保存范围,四种范围:page、request、session、application
--%>
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
<%--
使用jsp:setProperty标签设置person对象的属性值
jsp:setProperty在设置对象的属性值时会自动把字符串转换成8种基本数据类型
但是jsp:setProperty对于复合数据类型无法自动转换
--%>
<jsp:setProperty property="name" name="person" value="白虎神皇"/>
<jsp:setProperty property="sex" name="person" value="男"/>
<jsp:setProperty property="age" name="person" value="24"/>
<jsp:setProperty property="married" name="person" value="false"/>
<%--
birthday属性是一个Date类型,这个属于复合数据类型,因此无法将字符串自动转换成Date ,用下面这种写法是会报错的
<jsp:setProperty property="birthday" name="person" value="1988-05-07"/>
--%>
<jsp:setProperty property="birthday" name="person" value="<%=new Date()%>"/>
<!DOCTYPE HTML>
<html>
<head>
<title>jsp:setProperty标签使用范例</title>
</head>

<body>
<%--使用getXxx()方法获取对象的属性值 --%>
<h2>姓名:<%=person.getName()%></h2>
<h2>性别:<%=person.getSex()%></h2>
<h2>年龄:<%=person.getAge()%></h2>
<h2>已婚:<%=person.isMarried()%></h2>
<h2>出生日期:<%=person.getBirthday()%></h2>
</body>
</html>

运行效果如下:

<jsp:setProperty>标签使用范例2:使用请求参数为bean的属性赋值

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--
在jsp中使用jsp:useBean标签来实例化一个Java类的对象
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
┝<jsp:useBean>:表示在JSP中要使用JavaBean。
┝id:表示生成的实例化对象,凡是在标签中看见了id,则肯定表示一个实例对象。
┝class:此对象对应的包.类名称
┝scope:此javaBean的保存范围,四种范围:page、request、session、application
--%>
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
<%--
jsp:setProperty标签可以使用请求参数为bean的属性赋值
param="param_name"用于接收参数名为param_name的参数值,然后将接收到的值赋给name属性
--%>
<jsp:setProperty property="name" name="person" param="param_name"/>
<!DOCTYPE HTML>
<html>
<head>
<title>jsp:setProperty标签使用范例</title>
</head>

<body>
<%--使用getXxx()方法获取对象的属性值 --%>
<h2>姓名:<%=person.getName()%></h2>
</body>
</html>

运行结果如下:

<jsp:setProperty>标签使用范例3:用所有的请求参数为bean的属性赋值

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--
在jsp中使用jsp:useBean标签来实例化一个Java类的对象
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
┝<jsp:useBean>:表示在JSP中要使用JavaBean。
┝id:表示生成的实例化对象,凡是在标签中看见了id,则肯定表示一个实例对象。
┝class:此对象对应的包.类名称
┝scope:此javaBean的保存范围,四种范围:page、request、session、application
--%>
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
<%--
jsp:setProperty标签用所有的请求参数为bean的属性赋值
property="*"代表bean的所有属性
--%>
<jsp:setProperty property="*" name="person"/>
<!DOCTYPE HTML>
<html>
<head>
<title>jsp:setProperty标签使用范例</title>
</head>

<body>
<%--使用getXxx()方法获取对象的属性值 --%>
<h2>姓名:<%=person.getName()%></h2>
<h2>性别:<%=person.getSex()%></h2>
<h2>年龄:<%=person.getAge()%></h2>
</body>
</html>

运行结果如下所示:

3.5、<jsp:getProperty>标签
<jsp:getProperty>标签用于读取JavaBean对象的属性,也就是调用JavaBean对象的getter方法,然后将读取的属性值转换成字符串后插入进输出的响应正文中。
语法:
<jsp:getProperty name="beanInstanceName" property="PropertyName" />
name属性用于指定JavaBean实例对象的名称,其值应与<jsp:useBean>标签的id属性值相同。
property属性用于指定JavaBean实例对象的属性名。
如果一个JavaBean实例对象的某个属性的值为null,那么,使用<jsp:getProperty>标签输出该属性的结果将是一个内容为“null”的字符串。
范例:使用jsp:getProperty获取bean对象的属性值

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--
在jsp中使用jsp:useBean标签来实例化一个Java类的对象
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
┝<jsp:useBean>:表示在JSP中要使用JavaBean。
┝id:表示生成的实例化对象,凡是在标签中看见了id,则肯定表示一个实例对象。
┝class:此对象对应的包.类名称
┝scope:此javaBean的保存范围,四种范围:page、request、session、application
--%>
<jsp:useBean id="person" class="gacl.javabean.study.Person" scope="page"/>
<%--
使用jsp:setProperty标签设置person对象的属性值
jsp:setProperty在设置对象的属性值时会自动把字符串转换成8种基本数据类型
但是jsp:setProperty对于复合数据类型无法自动转换
--%>
<jsp:setProperty property="name" name="person" value="白虎神皇"/>
<jsp:setProperty property="sex" name="person" value="男"/>
<jsp:setProperty property="age" name="person" value="24"/>
<jsp:setProperty property="married" name="person" value="false"/>
<%--
birthday属性是一个Date类型,这个属于复合数据类型,因此无法将字符串自动转换成Date ,用下面这种写法是会报错的
<jsp:setProperty property="birthday" name="person" value="1988-05-07"/>
--%>
<jsp:setProperty property="birthday" name="person" value="<%=new Date()%>"/>
<!DOCTYPE HTML>
<html>
<head>
<title>jsp:getProperty标签使用范例</title>
</head>

<body>
<%--使用jsp:getProperty标签获取对象的属性值 --%>
<h2>姓名:<jsp:getProperty property="name" name="person"/></h2>
<h2>性别:<jsp:getProperty property="sex" name="person"/></h2>
<h2>年龄:<jsp:getProperty property="age" name="person"/></h2>
<h2>已婚:<jsp:getProperty property="married" name="person"/></h2>
<h2>出生日期:<jsp:getProperty property="birthday" name="person"/></h2>
</body>
</html>

运行结果如下:

关于JavaBean方面的内容基本上就这么多了,只需要掌握JavaBean的写法,以及掌握<jsp:useBean>标签,<jsp:setProperty>标签,<jsp:getProperty>标签的使用!

(二十一)——JavaWeb的两种开发模式
SUN公司推出JSP技术后,同时也推荐了两种web应用程序的开发模式,一种是JSP+JavaBean模式,一种是Servlet+JSP+JavaBean模式。
一、JSP+JavaBean开发模式
1.1、jsp+javabean开发模式架构
jsp+javabean开发模式的架构图如下图(图1-1)所示
图1-1

在jsp+javabean架构中,JSP负责控制逻辑、表现逻辑、业务对象(javabean)的调用。
JSP+JavaBean模式适合开发业务逻辑不太复杂的web应用程序,这种模式下,JavaBean用于封装业务数据,JSP即负责处理用户请求,又显示数据。
1.2、JSP+JavaBean开发模式编写计算器
首先分析一下jsp和javabean各自的职责,jsp负责显示计算器(calculator)页面,供用户输入计算数据,并显示计算后的结果,javaBean负责接收用户输入的计算数据并且进行计算,JavaBean具有firstNum、secondNum、result、 operator属性,并提供一个calculate方法。
1、编写CalculatorBean,负责接收用户输入的计算数据并且进行计算
CalculatorBean代码如下:
复制代码
package me.gacl.domain;
import java.math.BigDecimal;
/**
* @author gacl
* CalculatorBean用于接收输入参数和计算
*/
public class CalculatorBean {
//用户输入的第一个数
private double firstNum;
//用户输入的第二个数
private double secondNum;
//用户选择的操作运算符
private char operator = ‘+‘;
//运算结果
private double result;
public double getFirstNum() {
return firstNum;
}
public void setFirstNum(double firstNum) {
this.firstNum = firstNum;
}
public double getSecondNum() {
return secondNum;
}
public void setSecondNum(double secondNum) {
this.secondNum = secondNum;
}
public char getOperator() {
return operator;
}
public void setOperator(char operator) {
this.operator = operator;
}
public double getResult() {
return result;
}
public void setResult(double result) {
this.result = result;
}
/**
* 用于计算
*/
public void calculate() {
switch (this.operator) {
case ‘+‘: {
this.result = this.firstNum + this.secondNum;
break;
}
case ‘-‘: {
this.result = this.firstNum - this.secondNum;
break;
}
case ‘*‘: {
this.result = this.firstNum * this.secondNum;
break;
}
case ‘/‘: {
if (this.secondNum == 0) {
throw new RuntimeException("被除数不能为0!!!");
}
this.result = this.firstNum / this.secondNum;
// 四舍五入
this.result = new BigDecimal(this.result).setScale(2,
BigDecimal.ROUND_HALF_UP).doubleValue();
break;
}
default:
throw new RuntimeException("对不起,传入的运算符非法!!");
}
}
}
复制代码
2、编写calculator.jsp,负责显示计算器(calculator)页面,供用户输入计算数据,并显示计算后的结果
calculator.jsp页面代码如下:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--使用me.gacl.domain.CalculatorBean --%>
<jsp:useBean id="calcBean" class="me.gacl.domain.CalculatorBean"/>
<%--接收用户输入的参数 --%>
<jsp:setProperty name="calcBean" property="*"/>
<%
//使用CalculatorBean进行计算
calcBean.calculate();
%>
<!DOCTYPE HTML>
<html>
<head>
<title>使用【jsp+javabean开发模式】开发的简单计算器</title>
</head>

<body>
<br/>
计算结果是:
<jsp:getProperty name="calcBean" property="firstNum"/>
<jsp:getProperty name="calcBean" property="operator"/>
<jsp:getProperty name="calcBean" property="secondNum"/>
=
<jsp:getProperty name="calcBean" property="result"/>

<br/><hr> <br/>
<form action="${pageContext.request.contextPath}/calculator.jsp" method="post">
<table border="1px">
<tr>
<td colspan="2">简单的计算器</td>
</tr>
<tr>
<td>第一个参数</td>
<td><input type="text" name="firstNum"></td>
</tr>
<tr>
<td>运算符</td>
<td><select name="operator">
<option value="+">+</option>
<option value="-">-</option>
<option value="*">*</option>
<option value="/">/</option>
</select></td>
</tr>
<tr>
<td>第二个参数</td>
<td><input type="text" name="secondNum"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="计算"></td>
</tr>
</table>
</form>
</body>
</html>
复制代码
运行结果如下:

二、Servlet+JSP+JavaBean开发模式
在平时的JavaWeb项目开发中,在不使用第三方mvc开发框架的情况下,通常会选择Servlet+JSP+JavaBean开发模式来开发JavaWeb项目,Servlet+JSP+JavaBean组合开发就是一种MVC开发模式了,控制器(Controller)采用Servlet、模型(Model)采用JavaBean、视图(View)采用JSP。在讲解Servlet+JSP+JavaBean开发模式之前,先简单了解一下MVC开发模式。
2.1、Web开发中的请求-响应模型
图2-1

在Web世界里,具体步骤如下:
1、Web浏览器(如IE)发起请求,如访问http://www.iteye.com/
2、Web服务器(如Tomcat)接收请求,处理请求(比如用户新增,则将把用户保存一下),最后产生响应(一般为html)。
3、web服务器处理完成后,返回内容给web客户端(一般就是我们的浏览器),客户端对接收的内容进行处理(如web浏览器将会对接收到的html内容进行渲染以展示给客户)。
因此,在Web世界里:都是Web客户端发起请求,Web服务器接收、处理并产生响应。
一般Web服务器是不能主动通知Web客户端更新内容。虽然现在有些技术如服务器推(如Comet)、还有现在的HTML5 websocket可以实现Web服务器主动通知Web客户端。
到此我们了解了在web开发时的请求/响应模型,接下来我们看一下标准的MVC模型是什么。
2.2、标准MVC模型概述
MVC模型:是一种架构型的模式,本身不引入新功能,只是帮助我们将开发的结构组织的更加合理,使展示与模型分离、流程控制逻辑、业务逻辑调用与展示逻辑分离。如下图(图2-2)所示:
图2-2

2.3、MVC(Model-View-Controller)的概念
首先让我们了解下MVC(Model-View-Controller)的概念:
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型(domain)或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据) 和 服务层(行为)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。
从图2-1我们还看到,在标准的MVC中模型能主动推数据给视图进行更新(观察者设计模式,在模型上注册视图,当模型更新时自动更新视图),但在Web开发中模型是无法主动推给视图(无法主动更新用户界面),因为在Web开发是请求-响应模型。
那接下来我们看一下在Web里MVC是什么样子,我们称其为 Web MVC 来区别标准的MVC。
2.4.、Web MVC概述
Web MVC中的M(模型)-V(视图)-C(控制器)概念和标准MVC概念一样,我们再看一下Web MVC标准架构,如下图(图2-3)所示:
图2-3

在Web MVC模式下,模型无法主动推数据给视图,如果用户想要视图更新,需要再发送一次请求(即请求-响应模型)。
2.5、Servlet+JSP+JavaBean开发模式介绍
Servlet+JSP+JavaBean架构其实可以认为就是我们所说的Web MVC模型,只是控制器采用Servlet、模型采用JavaBean、视图采用JSP,如图2-4
图2-4

具体示例代码:
1、模型(model)

2、视图(View)

3、控制器(controller)

从Servlet+JSP+JavaBean(Web MVC)架构可以看出,视图和模型分离了,控制逻辑和展示逻辑分离了。
三、Servlet+JSP+JavaBean开发模式的缺点
Servlet+JSP+JavaBean(Web MVC)架构虽然实现了视图和模型分离以及控制逻辑和展示逻辑分离,但也有一些比较严重的缺点
3.1、Servlet作为控制器的缺点
此处的控制器使用Servlet,使用Servlet作为控制器有以下几个缺点:
1、控制逻辑可能比较复杂,其实我们可以按照规约,如请求参数submitFlag=toLogin,我们其实可以直接调用toLogin方法,来简化控制逻辑;而且每个模块基本需要一个控制器,造成控制逻辑可能很复杂。现在流行的Web MVC框架(如Struts2)都支持"请求参数submitFlag=toAdd,就可以直接调用toAdd方法"这样的处理机制,在Struts2中类似这样的处理机制就称为"动态方法调用"
2、请求参数到模型的封装比较麻烦,如果能交给框架来做这件事情,我们可以从中得到解放。
请求参数到模型的封装代码:
复制代码
// 1收集参数
String username = req.getParameter("username");
String password = req.getParameter("password");
// 2封装参数
UserBean user = new UserBean();
user.setUsername(username);
user.setPassword(password);
复制代码
当有几十个甚至上百个参数需要封装到模型中时,这样写恐怕就痛苦万分了,要写几十次甚至上百次这样的代码,估计写到吐了,所以现在流行的Web MVC框架(如Struts2)都提供了非常方便的获取参数,封装参数到模型的机制,减少这些繁琐的工作。
3、选择下一个视图,严重依赖Servlet API,这样很难或基本不可能更换视图。
例如:使用Servlet API提供的request对象的getRequestDispatcher方法选择要展示给用户看的视图
private void toLogin(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//使用Servlet API提供的request对象的getRequestDispatcher方法选择视图
// 此处和JSP视图技术紧密耦合,更换其他视图技术几乎不可能
request.getRequestDispatcher("/mvc/login.jsp").forward(request, response);
}
4、给视图传输要展示的模型数据,也需要使用Servlet API,更换视图技术也要一起更换,很麻烦。
例如:使用Servlet API提供的request对象给视图传输要展示的模型数据
//使用Servlet API提供的request对象给视图login.jsp传输要展示的模型数据(user)
request.setAttribute("user", user);
request.getRequestDispatcher("/mvc/login.jsp").forward(request, response)
3.2、JavaBean作为模型的缺点
此处模型使用JavaBean,JavaBean组件类既负责收集封装数据,又要进行业务逻辑处理,这样可能造成JavaBean组件类很庞大,所以一般现在项目都是采用三层架构,而不直接采用JavaBean。

3.3、视图的缺点
现在被绑定在JSP,很难更换视图,比如Velocity、FreeMarker;比如我要支持Excel、PDF视图等等。
关于JavaWeb的两种开发模式的讲解就介绍到这里,下一篇将使用servlet+jsp+javabean来开发一个用户登录注册功能,以此来加深servlet+jsp+javabean开发模式的理解。

(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册
一、Servlet+JSP+JavaBean开发模式(MVC)介绍
Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp负责数据显示,javabean负责封装数据。 Servlet+JSP+JavaBean模式程序各个模块之间层次清晰,web开发推荐采用此种模式。
这里以一个最常用的用户登录注册程序来讲解Servlet+JSP+JavaBean开发模式,通过这个用户登录注册程序综合案例,把之前的学过的XML、Xpath、Servlet、jsp的知识点都串联起来。
二、创建MVC架构的Web项目
在MyEclipse中新创建一个webmvcframework项目,导入项目所需要的开发包(jar包),创建项目所需要的包,在java开发中,架构的层次是以包的形式体现出来的
项目所需要的开发包(jar包)
序号 开发包名称 描述
1 dom4j-1.6.1.jar dom4j用于操作XML文件
2 jaxen-1.1-beta-6.jar 用于解析XPath表达式
3 commons-beanutils-1.8.0.jar 工具类,用于处理bean对象
4 commons-logging.jar commons-beanutils-1.8.0.jar的依赖jar包
5 jstl.jar jstl标签库和EL表达式依赖包
6 standard.jar jstl标签库和EL表达式依赖包






 

 



项目所需要的包
序号 包名 描述 所属层次
1 me.gacl.domain 存放系统的JavaBean类(只包含简单的属性以及属性对应的get和set方法,不包含具体的业务处理方法),提供给【数据访问层】、【业务处理层】、【Web层】来使用 domain(域模型)层
2 me.gacl.dao 存放访问数据库的操作接口类 数据访问层
3 me.gacl.dao.impl 存放访问数据库的操作接口的实现类
4 me.gacl.service 存放处理系统业务接口类 业务处理层
5 me.gacl.service.impl 存放处理系统业务接口的实现类
6 me.gacl.web.controller 存放作为系统控制器的Servlet Web层(表现层)
7 me.gacl.web.UI 存放为用户提供用户界面的servlet(UI指的是user interface)
8 me.gacl.web.filter 存放系统的用到的过滤器(Filter)
9 me.gacl.web.listener 存放系统的用到的监听器(Listener)
10 me.gacl.util 存放系统的通用工具类,提供给【数据访问层】、【业务处理层】、【Web层】来使用
11 junit.test 存放系统的测试类

一个良好的JavaWeb项目架构应该具有以上的11个包,这样显得层次分明,各个层之间的职责也很清晰明了,搭建JavaWeb项目架构时,就按照上面的1~11的序号顺序创建包:domain→dao→dao.impl→service→service.impl→web.controller→web.UI→web.filter→web.listener→util→junit.test,包的层次创建好了,项目的架构也就定下来了,当然,在实际的项目开发中,也不一定是完完全全按照上面说的来创建包的层次结构,而是根据项目的实际情况,可能还需要创建其他的包,这个得根据项目的需要来定了
在src目录(类目录)下面,创建用于保存用户数据的xml文件(DB.xml)
在WEB-INF目录下创建一个pages目录,pages目录存放系统的一些受保护(不允许用户直接通过URL地址访问)的jsp页面,用户要想访问这些受保护的jsp页面,那么只能通过me.gacl.web.UI这个包里面的Servlet
创建好的项目如下图(图-1)所示:

图-1
三、分层架构的代码编写
分层架构的代码也是按照【域模型层(domain)】→【数据访问层(dao、dao.impl)】→【业务处理层(service、service.impl)】→【表现层(web.controller、web.UI、web.filter、web.listener)】→【工具类(util)】→【测试类(junit.test)】的顺序进行编写的。
3.1、开发domain层
在me.gacl.domain包下创建一个User类

User类具体代码如下:
复制代码
package me.gacl.domain;
import java.io.Serializable;
import java.util.Date;
/**
* @author gacl
* 用户实体类
*/
public class User implements Serializable {
private static final long serialVersionUID = -4313782718477229465L;

// 用户ID
private String id;
// 用户名
private String userName;
// 用户密码
private String userPwd;
// 用户邮箱
private String email;
// 用户生日
private Date birthday;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
复制代码
3.2、开发数据访问层(dao、dao.impl)
在me.gacl.dao包下创建一个IUserDao接口类,对于开发接口类,我习惯以字母I作类的前缀,这样一眼就看出当前这个类是一个接口,这也算是一种良好的开发习惯吧,通过看类名就可以方便区分出是接口还是具体的实现类。

IUserDao接口的具体代码如下:
复制代码
package me.gacl.dao;
import me.gacl.domain.User;
public interface IUserDao {
/**
* 根据用户名和密码来查找用户
* @param userName
* @param userPwd
* @return 查到到的用户
*/
User find(String userName, String userPwd);
/**
* 添加用户
* @param user
*/
void add(User user);
/**根据用户名来查找用户
* @param userName
* @return 查到到的用户
*/
User find(String userName);
}
复制代码
对于接口中的方法定义,这个只能是根据具体的业务来分析需要定义哪些方法了,但是无论是多么复杂的业务,都离不开基本的CRUD(增删改查)操作,Dao层是直接和数据库交互的,所以Dao层的接口一般都会有增删改查这四种操作的相关方法。
在me.gacl.dao.impl包下创建一个UserDaoImpl类

UserDaoImpl类是IUserDao接口的具体实现类,对于接口的实现类命名方式,我习惯以"接口名(去除前缀I)+impl"形式或者"接口名+impl"形式来命名:IUserDao(接口)→UserDaoImpl(实现类)或者IUserDao(接口)→IUserDaoImpl(实现类),这也算是一些个人的编程习惯吧,平时看到的代码大多数都是以这两种形式中的一种来来命名接口的具体实现类的,反正就是要能够一眼看出接口对应的实现类是哪一个就可以了。
UserDaoImpl类的具体代码如下:
复制代码
package me.gacl.dao.impl;
import java.text.SimpleDateFormat;
import org.dom4j.Document;
import org.dom4j.Element;
import me.gacl.dao.IUserDao;
import me.gacl.domain.User;
import me.gacl.util.XmlUtils;
/**
* IUserDao接口的实现类
* @author gacl
*/
public class UserDaoImpl implements IUserDao {
@Override
public User find(String userName, String userPwd) {
try{
Document document = XmlUtils.getDocument();
//使用XPath表达式来操作XML节点
Element e = (Element) document.selectSingleNode("//user[@userName=‘"+userName+"‘ and @userPwd=‘"+userPwd+"‘]");
if(e==null){
return null;
}
User user = new User();
user.setId(e.attributeValue("id"));
user.setEmail(e.attributeValue("email"));
user.setUserPwd(e.attributeValue("userPwd"));
user.setUserName(e.attributeValue("userName"));
String birth = e.attributeValue("birthday");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
user.setBirthday(sdf.parse(birth));

return user;

}catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("deprecation")
@Override
public void add(User user) {
try{
Document document = XmlUtils.getDocument();
Element root = document.getRootElement();
Element user_node = root.addElement("user"); //创建user结点,并挂到root
user_node.setAttributeValue("id", user.getId());
user_node.setAttributeValue("userName", user.getUserName());
user_node.setAttributeValue("userPwd", user.getUserPwd());
user_node.setAttributeValue("email", user.getEmail());

SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
user_node.setAttributeValue("birthday", sdf.format(user.getBirthday()));

XmlUtils.write2Xml(document);

}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public User find(String userName) {
try{
Document document = XmlUtils.getDocument();
Element e = (Element) document.selectSingleNode("//user[@userName=‘"+userName+"‘]");
if(e==null){
return null;
}
User user = new User();
user.setId(e.attributeValue("id"));
user.setEmail(e.attributeValue("email"));
user.setUserPwd(e.attributeValue("userPwd"));
user.setUserName(e.attributeValue("userName"));
String birth = e.attributeValue("birthday");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
user.setBirthday(sdf.parse(birth));

return user;

}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
复制代码
3.3、开发service层(service层对web层提供所有的业务服务)
在me.gacl.service包中创建IUserService接口类

IUserService接口的具体代码如下:
复制代码
package me.gacl.service;
import me.gacl.domain.User;
import me.gacl.exception.UserExistException;
public interface IUserService {
/**
* 提供注册服务
* @param user
* @throws UserExistException
*/
void registerUser(User user) throws UserExistException;
/**
* 提供登录服务
* @param userName
* @param userPwd
* @return
*/
User loginUser(String userName, String userPwd);
}
复制代码
在me.gacl.service.impl包中创建UserServiceImpl类

UserServiceImpl类为IUserService接口的具体实现类,具体代码如下:
复制代码
package me.gacl.service.impl;
import me.gacl.dao.IUserDao;
import me.gacl.dao.impl.UserDaoImpl;
import me.gacl.domain.User;
import me.gacl.exception.UserExistException;
import me.gacl.service.IUserService;
public class UserServiceImpl implements IUserService {
private IUserDao userDao = new UserDaoImpl();

@Override
public void registerUser(User user) throws UserExistException {
if (userDao.find(user.getUserName())!=null) {
//checked exception
//unchecked exception
//这里抛编译时异常的原因:是我想上一层程序处理这个异常,以给用户一个友好提示
throw new UserExistException("注册的用户名已存在!!!");
}
userDao.add(user);
}
@Override
public User loginUser(String userName, String userPwd) {
return userDao.find(userName, userPwd);
}
}
复制代码
3.4、开发web层
3.4.1、 开发注册功能
  1、在me.gacl.web.UI包下写一个RegisterUIServlet为用户提供注册界面

RegisterUIServlet收到用户请求后,就跳到register.jsp
RegisterUIServlet的代码如下:
复制代码
package me.gacl.web.UI;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* 为用户提供注册的用户界面的Servlet
* RegisterUIServlet负责为用户输出注册界面
* 当用户访问RegisterUIServlet时,就跳转到WEB-INF/pages目录下的register.jsp页面
*/
public class RegisterUIServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/pages/register.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
2、在/WEB-INF/pages/目录下编写用户注册的jsp页面register.jsp

凡是位于WEB-INF目录下的jsp页面是无法直接通过URL地址直接访问的,

在开发中如果项目中有一些敏感web资源不想被外界直接访问,那么可以考虑将这些敏感的web资源放到WEB-INF目录下,这样就可以禁止外界直接通过URL来访问了。
register.jsp页面的代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>用户注册</title>
</head>
<body style="text-align: center;">
<form action="${pageContext.request.contextPath}/servlet/RegisterServlet" method="post">
<table width="60%" border="1">
<tr>
<td>用户名</td>
<td>

<input type="text" name="userName">
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" name="userPwd">
</td>
</tr>
<tr>
<td>确认密码</td>
<td>
<input type="password" name="confirmPwd">
</td>
</tr>
<tr>
<td>邮箱</td>
<td>
<input type="text" name="email">
</td>
</tr>
<tr>
<td>生日</td>
<td>
<input type="text" name="birthday">
</td>
</tr>
<tr>
<td>
<input type="reset" value="清空">
</td>
<td>
<input type="submit" value="注册">
</td>
</tr>
</table>
</form>
</body>
</html>
复制代码
register.jsp中的<form action="${pageContext.request.contextPath}/servlet/RegisterServlet" method="post">指明表单提交后,交给RegisterServlet进行处理
3、在me.gacl.web.controller包下编写用于处理用户注册的RegisterServlet

RegisterServlet担任着以下几个职责:
1、接收客户端提交到服务端的表单数据。
2、校验表单数据的合法性,如果校验失败跳回到register.jsp,并回显错误信息。
3、如果校验通过,调用service层向数据库中注册用户。
为了方便RegisterServlet接收表单数据和校验表单数据,在此我设计一个用于校验注册表单数据RegisterFormbean,再写WebUtils工具类,封装客户端提交的表单数据到formbean中。
在me.gacl.web.formbean包下创建一个用于校验注册表单数据RegisterFormbean

RegisterFormbean代码如下:
复制代码
1 package me.gacl.web.formbean;
2
3 import java.util.HashMap;
4 import java.util.Map;
5
6 import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
7
8 /**
9 * 封装的用户注册表单bean,用来接收register.jsp中的表单输入项的值
* RegisterFormBean中的属性与register.jsp中的表单输入项的name一一对应
* RegisterFormBean的职责除了负责接收register.jsp中的表单输入项的值之外还担任着校验表单输入项的值的合法性
* @author gacl
*
*/
public class RegisterFormBean {
//RegisterFormBean中的属性与register.jsp中的表单输入项的name一一对应
//<input type="text" name="userName"/>
private String userName;
//<input type="password" name="userPwd"/>
private String userPwd;
//<input type="password" name="confirmPwd"/>
private String confirmPwd;
//<input type="text" name="email"/>
private String email;
//<input type="text" name="birthday"/>
private String birthday;

/**
* 存储校验不通过时给用户的错误提示信息
*/
private Map<String, String> errors = new HashMap<String, String>();
public Map<String, String> getErrors() {
return errors;
}
public void setErrors(Map<String, String> errors) {
this.errors = errors;
}
/*
* validate方法负责校验表单输入项
* 表单输入项校验规则:
* private String userName; 用户名不能为空,并且要是3-8的字母 abcdABcd
* private String userPwd; 密码不能为空,并且要是3-8的数字
* private String confirmPwd; 两次密码要一致
* private String email; 可以为空,不为空要是一个合法的邮箱
* private String birthday; 可以为空,不为空时,要是一个合法的日期
*/
public boolean validate() {
boolean isOk = true;
if (this.userName == null || this.userName.trim().equals("")) {
isOk = false;
errors.put("userName", "用户名不能为空!!");
} else {
if (!this.userName.matches("[a-zA-Z]{3,8}")) {
isOk = false;
errors.put("userName", "用户名必须是3-8位的字母!!");
}
}
if (this.userPwd == null || this.userPwd.trim().equals("")) {
isOk = false;
errors.put("userPwd", "密码不能为空!!");
} else {
if (!this.userPwd.matches("\\d{3,8}")) {
isOk = false;
errors.put("userPwd", "密码必须是3-8位的数字!!");
}
}
// private String password2; 两次密码要一致
if (this.confirmPwd != null) {
if (!this.confirmPwd.equals(this.userPwd)) {
isOk = false;
errors.put("confirmPwd", "两次密码不一致!!");
}
}
// private String email; 可以为空,不为空要是一个合法的邮箱
if (this.email != null && !this.email.trim().equals("")) {
if (!this.email.matches("\\w+@\\w+(\\.\\w+)+")) {
isOk = false;
errors.put("email", "邮箱不是一个合法邮箱!!");
}
}
// private String birthday; 可以为空,不为空时,要是一个合法的日期
if (this.birthday != null && !this.birthday.trim().equals("")) {
try {
DateLocaleConverter conver = new DateLocaleConverter();
conver.convert(this.birthday);
} catch (Exception e) {
isOk = false;
errors.put("birthday", "生日必须要是一个日期!!");
}
}
return isOk;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
public String getConfirmPwd() {
return confirmPwd;
}
public void setConfirmPwd(String confirmPwd) {
this.confirmPwd = confirmPwd;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
复制代码
WebUtils工具类
在me.gacl.util包下创建一个WebUtils工具类,该工具类的功能就是封装客户端提交的表单数据到formbean中

复制代码
package me.gacl.util;
import java.util.Enumeration;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.beanutils.BeanUtils;
/**
* @author gacl
* 把request对象中的请求参数封装到bean中
*/
public class WebUtils {
/**
* 将request对象转换成T对象
* @param request
* @param clazz
* @return
*/
public static <T> T request2Bean(HttpServletRequest request,Class<T> clazz){
try{
T bean = clazz.newInstance();
Enumeration<String> e = request.getParameterNames();
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = request.getParameter(name);
BeanUtils.setProperty(bean, name, value);
}
return bean;
}catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 生成UUID
* @return
*/
public static String makeId(){
return UUID.randomUUID().toString();
}

}
复制代码
XmlUtils.java
package me.gacl.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

public class XmlUtils {

private static String filename = "DB.xml";

public static Document getDocument() throws DocumentException{

URL url = XmlUtils.class.getClassLoader().getResource(filename);
String realpath = url.getPath();
System.out.println(realpath);

SAXReader reader = new SAXReader();
return reader.read(new File(realpath));

}

public static void write2Xml(Document document) throws IOException{

URL url = XmlUtils.class.getClassLoader().getResource(filename);
String realpath = url.getPath();
System.out.println(realpath);

OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter(new FileOutputStream(realpath), format );
writer.write( document );
writer.close();

}

}

负责处理用户注册的RegisterServlet完整代码:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import me.gacl.domain.User;
import me.gacl.exception.UserExistException;
import me.gacl.service.IUserService;
import me.gacl.service.impl.UserServiceImpl;
import me.gacl.util.WebUtils;
import me.gacl.web.formbean.RegisterFormBean;
/**
* 处理用户注册的Servlet
* @author gacl
*
*/
public class RegisterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//将客户端提交的表单数据封装到RegisterFormBean对象中
RegisterFormBean formbean = WebUtils.request2Bean(request,RegisterFormBean.class);
//校验用户注册填写的表单数据
if (formbean.validate() == false) {//如果校验失败
//将封装了用户填写的表单数据的formbean对象发送回register.jsp页面的form表单中进行显示
request.setAttribute("formbean", formbean);
//校验失败就说明是用户填写的表单数据有问题,那么就跳转回register.jsp
request.getRequestDispatcher("/WEB-INF/pages/register.jsp").forward(request, response);
return;
}
User user = new User();
try {
// 注册字符串到日期的转换器
ConvertUtils.register(new DateLocaleConverter(), Date.class);
BeanUtils.copyProperties(user, formbean);//把表单的数据填充到javabean中
user.setId(WebUtils.makeId());//设置用户的Id属性
IUserService service = new UserServiceImpl();
//调用service层提供的注册用户服务实现用户注册
service.registerUser(user);
String message = String.format(
"注册成功!!3秒后为您自动跳到登录页面!!<meta http-equiv=‘refresh‘ content=‘3;url=%s‘/>",
request.getContextPath()+"/servlet/LoginUIServlet");
request.setAttribute("message",message);
request.getRequestDispatcher("/message.jsp").forward(request,response);
} catch (UserExistException e) {
formbean.getErrors().put("userName", "注册用户已存在!!");
request.setAttribute("formbean", formbean);
request.getRequestDispatcher("/WEB-INF/pages/register.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace(); // 在后台记录异常
request.setAttribute("message", "对不起,注册失败!!");
request.getRequestDispatcher("/message.jsp").forward(request,response);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
用户注册时如果填写的表单数据校验不通过,那么服务器端就将一个存储了错误提示消息和表单数据的formbean对象存储到request对象中,然后发送回register.jsp页面,因此我们需要在register.jsp页面中取出request对象中formbean对象,然后将用户填写的表单数据重新回显到对应的表单项上面,将出错时的提示消息也显示到form表单上面,让用户知道是哪些数据填写不合法!
修改register.jsp页面,代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>用户注册</title>
</head>
<body style="text-align: center;">
<form action="${pageContext.request.contextPath}/servlet/RegisterServlet" method="post">
<table width="60%" border="1">
<tr>
<td>用户名</td>
<td>
<%--使用EL表达式${}提取存储在request对象中的formbean对象中封装的表单数据(formbean.userName)以及错误提示消息(formbean.errors.userName)--%>
<input type="text" name="userName" value="${formbean.userName}">${formbean.errors.userName}
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" name="userPwd" value="${formbean.userPwd}">${formbean.errors.userPwd}
</td>
</tr>
<tr>
<td>确认密码</td>
<td>
<input type="password" name="confirmPwd" value="${formbean.confirmPwd}">${formbean.errors.confirmPwd}
</td>
</tr>
<tr>
<td>邮箱</td>
<td>
<input type="text" name="email" value="${formbean.email}">${formbean.errors.email}
</td>
</tr>
<tr>
<td>生日</td>
<td>
<input type="text" name="birthday" value="${formbean.birthday}">${formbean.errors.birthday}
</td>
</tr>
<tr>
<td>
<input type="reset" value="清空">
</td>
<td>
<input type="submit" value="注册">
</td>
</tr>
</table>
</form>
</body>
</html>
复制代码
到此,用户注册功能就算是开发完成了!
下面测试一下开发好的用户注册功能:
输入URL地址:http://localhost:8080/webmvcframework/servlet/RegisterUIServlet访问register.jsp页面,运行效果如下:

如果输入的表单项不符合校验规则,那么是无法进行注册的,运行效果如下:

3.4.2、 开发登录功能
1、在me.gacl.web.UI包下写一个LoginUIServlet为用户提供登录界面

LoginUIServlet收到用户请求后,就跳到login.jsp
LoginUIServlet的代码如下:
复制代码
package me.gacl.web.UI;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author gacl
* LoginUIServlet负责为用户输出登陆界面
* 当用户访问LoginUIServlet时,就跳转到WEB-INF/pages目录下的login.jsp页面
*/
public class LoginUIServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/pages/login.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
2、在/WEB-INF/pages/目录下编写用户登录的jsp页面login.jsp

login.jsp页面的代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>用户登陆</title>
</head>

<body>
<form action="${pageContext.request.contextPath }/servlet/LoginServlet" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="登陆">
</form>
</body>
</html>
复制代码
login.jsp中的<form action="${pageContext.request.contextPath}/servlet/LoginServlet" method="post">指明表单提交后,交给LoginServlet进行处理。
3、在me.gacl.web.controller包下编写用于处理用户登录的LoginServlet

LoginServlet的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.domain.User;
import me.gacl.service.IUserService;
import me.gacl.service.impl.UserServiceImpl;
/**
* 处理用户登录的servlet
* @author gacl
*
*/
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取用户填写的登录用户名
String username = request.getParameter("username");
//获取用户填写的登录密码
String password = request.getParameter("password");

IUserService service = new UserServiceImpl();
//用户登录
User user = service.loginUser(username, password);
if(user==null){
String message = String.format(
"对不起,用户名或密码有误!!请重新登录!2秒后为您自动跳到登录页面!!<meta http-equiv=‘refresh‘ content=‘2;url=%s‘",
request.getContextPath()+"/servlet/LoginUIServlet");
request.setAttribute("message",message);
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
//登录成功后,就将用户存储到session中
request.getSession().setAttribute("user", user);
String message = String.format(
"恭喜:%s,登陆成功!本页将在3秒后跳到首页!!<meta http-equiv=‘refresh‘ content=‘3;url=%s‘",
user.getUserName(),
request.getContextPath()+"/index.jsp");
request.setAttribute("message",message);
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
到此,用户登录的功能就算是开发完成了。
下面测试一下开发好的用户登录功能,输入URL地址:http://localhost:8080/webmvcframework/servlet/LoginUIServlet访问login.jsp页面,输入正确的用户名和密码进行登录,运行效果如下:

如果输入的用户名和密码错误,那么就无法登录成功,运行效果如下:

3.4.3、 开发注销功能
在me.gacl.web.controller包下编写用于处理用户注销的LogoutServlet
LogoutServlet的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import java.text.MessageFormat;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LogoutServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//移除存储在session中的user对象,实现注销功能
request.getSession().removeAttribute("user");
//由于字符串中包含有单引号,在这种情况下使用MessageFormat.format方法拼接字符串时就会有问题
//MessageFormat.format方法只是把字符串中的单引号去掉,不会将内容填充到指定的占位符中
String tempStr1 = MessageFormat.format(
"注销成功!!3秒后为您自动跳到登录页面!!<meta http-equiv=‘refresh‘ content=‘3;url={0}‘/>",
request.getContextPath()+"/servlet/LoginUIServlet");
System.out.println(tempStr1);//输出结果:注销成功!!3秒后为您自动跳到登录页面!!<meta http-equiv=refresh content=3;url={0}/>
System.out.println("---------------------------------------------------------");
/**
* 要想解决"如果要拼接的字符串包含有单引号,那么MessageFormat.format方法就只是把字符串中的单引号去掉,不会将内容填充到指定的占位符中"这个问题,
* 那么可以需要使用单引号引起来的字符串中使用2个单引号引起来,例如:"<meta http-equiv=‘‘refresh‘‘ content=‘‘3;url={0}‘‘/>"
* 这样MessageFormat.format("<meta http-equiv=‘‘refresh‘‘ content=‘‘3;url={0}‘‘/>","index.jsp")就可以正常返回
* <meta http-equiv=‘‘refresh‘‘ content=‘‘3;url=index.jsp‘/>
*/
String tempStr2 = MessageFormat.format(
"注销成功!!3秒后为您自动跳到登录页面!!<meta http-equiv=‘‘refresh‘‘ content=‘‘3;url={0}‘‘/>",
request.getContextPath()+"/servlet/LoginUIServlet");
/**
* 输出结果:
* 注销成功!!3秒后为您自动跳到登录页面!!
* <meta http-equiv=‘refresh‘ content=‘3;url=/webmvcframework/servlet/LoginUIServlet‘/>
*/
System.out.println(tempStr2);

String message = String.format(
"注销成功!!3秒后为您自动跳到登录页面!!<meta http-equiv=‘refresh‘ content=‘3;url=%s‘/>",
request.getContextPath()+"/servlet/LoginUIServlet");
request.setAttribute("message",message);
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
用户登录成功后,会将登录的用户信息存储在session中,所以我们要将存储在session中的user删除掉,这样就可以实现用户注销了。
用户登录成功后就会跳转到index.jsp页面,在index.jsp页面中放一个【退出登陆】按钮,当点击【退出登陆】按钮时,就访问LogoutServlet,将用户注销。
index.jsp的代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<%--为了避免在jsp页面中出现java代码,这里引入jstl标签库,利用jstl标签库提供的标签来做一些逻辑判断处理 --%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML>
<html>
<head>
<title>首页</title>
<script type="text/javascript">
function doLogout(){
//访问LogoutServlet注销当前登录的用户
window.location.href="${pageContext.request.contextPath}/servlet/LogoutServlet";
}
</script>
</head>

<body>
<h1>孤傲苍狼的网站</h1>
<hr/>
<c:if test="${user==null}">
<a href="${pageContext.request.contextPath}/servlet/RegisterUIServlet" target="_blank">注册</a>
<a href="${pageContext.request.contextPath}/servlet/LoginUIServlet">登陆</a>
</c:if>
<c:if test="${user!=null}">
欢迎您:${user.userName}
<input type="button" value="退出登陆" onclick="doLogout()">
</c:if>
<hr/>
</body>
</html>
复制代码
测试开发好的注销功能,效果如下:

到此,所有的功能都开发完成了,测试也通过了。
四、开发总结
通过这个小例子,可以了解到mvc分层架构的项目搭建,在平时的项目开发中,也都是按照如下的顺序来进行开发的:
1、搭建开发环境
1.1 创建web项目
1.2 导入项目所需的开发包
1.3 创建程序的包名,在java中是以包来体现项目的分层架构的
2、开发domain
把一张要操作的表当成一个VO类(VO类只定义属性以及属性对应的get和set方法,没有涉及到具体业务的操作方法),VO表示的是值对象,通俗地说,就是把表中的每一条记录当成一个对象,表中的每一个字段就作为这个对象的属性。每往表中插入一条记录,就相当于是把一个VO类的实例对象插入到数据表中,对数据表进行操作时,都是直接把一个VO类的对象写入到表中,一个VO类对象就是一条记录。每一个VO对象可以表示一张表中的一行记录,VO类的名称要和表的名称一致或者对应。
3、开发dao
3.1 DAO操作接口:每一个DAO操作接口规定了,一张表在一个项目中的具体操作方法,此接口的名称最好按照如下格式编写:“I表名称Dao”。
├DAO接口里面的所有方法按照以下的命名编写:
├更新数据库:doXxx()
├查询数据库:findXxx()或getXxx()
3.2 DAO操作接口的实现类:实现类中完成具体的增删改查操作
├此实现类完成的只是数据库中最核心的操作,并没有专门处理数据库的打开和关闭,因为这些操作与具体的业务操作无关。
4、开发service(service 对web层提供所有的业务服务)
5、开发web层

(二十三)——jsp自定义标签开发入门
一、自定义标签的作用
自定义标签主要用于移除Jsp页面中的java代码。
二、自定义标签开发和使用
2.1、自定义标签开发步骤
1、编写一个实现Tag接口的Java类(标签处理器类)
复制代码
package me.gacl.web.tag;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
public class ViewIPTag implements Tag {
//接收传递进来的PageContext对象
private PageContext pageContext;

@Override
public int doEndTag() throws JspException {
System.out.println("调用doEndTag()方法");
return 0;
}
@Override
public int doStartTag() throws JspException {
System.out.println("调用doStartTag()方法");
HttpServletRequest request =(HttpServletRequest) pageContext.getRequest();
JspWriter out = pageContext.getOut();
String ip = request.getRemoteAddr();
try {
//这里输出的时候会抛出IOException异常
out.write(ip);
} catch (IOException e) {
//捕获IOException异常后继续抛出
throw new RuntimeException(e);
}
return 0;
}
@Override
public Tag getParent() {
return null;
}
@Override
public void release() {
System.out.println("调用release()方法");
}
@Override
public void setPageContext(PageContext pageContext) {
System.out.println("setPageContext(PageContext pageContext)");
this.pageContext = pageContext;
}
@Override
public void setParent(Tag arg0) {
}
}
复制代码
2、在WEB-INF/目录下新建tld文件,在tld文件中对标签处理器类进行描述

gacl.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">
<!-- description用来添加对taglib(标签库)的描述 -->
<description>孤傲苍狼开发的自定义标签库</description>
<!--taglib(标签库)的版本号 -->
<tlib-version>1.0</tlib-version>
<short-name>GaclTagLibrary</short-name>
<!--
为自定义标签库设置一个uri,uri以/开头,/后面的内容随便写,如这里的/gacl ,
在Jsp页面中引用标签库时,需要通过uri找到标签库
在Jsp页面中就要这样引入标签库:<%@taglib uri="/gacl" prefix="gacl"%>
-->
<uri>/gacl</uri>

<!--一个taglib(标签库)中包含多个自定义标签,每一个自定义标签使用一个tag标记来描述 -->
<!-- 一个tag标记对应一个自定义标签 -->
<tag>
<description>这个标签的作用是用来输出客户端的IP地址</description>
<!--
为标签处理器类配一个标签名,在Jsp页面中使用标签时是通过标签名来找到要调用的标签处理器类的
通过viewIP就能找到对应的me.gacl.web.tag.ViewIPTag类
-->
<name>viewIP</name>
<!-- 标签对应的处理器类-->
<tag-class>me.gacl.web.tag.ViewIPTag</tag-class>
<body-content>empty</body-content>
</tag>

</taglib>
复制代码
2.2、在Jsp页面中使用自定义标签
1、使用"<%@taglib uri="标签库的uri" prefix="标签的使用前缀"%>"指令引入要使用的标签库。
例如:在jspTag_Test1.jsp中引用gacl标签库
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!-- 使用taglib指令引用gacl标签库,标签库的前缀(prefix)可以随便设置,如这里设置成 prefix="xdp" -->
<%@taglib uri="/gacl" prefix="xdp"%>
<!DOCTYPE HTML>
<html>
<head>
<title>输出客户端的IP</title>
</head>

<body>
你的IP地址是(使用java代码获取输出):
<%
//在jsp页面中使用java代码获取客户端IP地址
String ip = request.getRemoteAddr();
out.write(ip);
%>
<hr/>
你的IP地址是(使用自定义标签获取输出):
<%--使用自定义标签viewIP --%>
<xdp:viewIP/>
</body>
</html>
复制代码
标签的运行效果如下:

从运行效果种可以看到,使用自定义标签就可以将jsp页面上的java代码移除掉,如需要在jsp页面上输出客户端的IP地址时,使用 <xdp:viewIP/>标签就可以代替jsp页面上的这些代码:
<%
//在jsp页面中使用java代码获取客户端IP地址
String ip = request.getRemoteAddr();
out.write(ip);
%>
这就是开发和使用自定义标签的好处,可以让我们的Jsp页面上不嵌套java代码。
三、自定义标签的执行流程
JSP引擎遇到自定义标签时,首先创建标签处理器类的实例对象,然后按照JSP规范定义的通信规则依次调用它的方法。
1、public void setPageContext(PageContext pc), JSP引擎实例化标签处理器后,将调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器,标签处理器以后可以通过这个pageContext对象与JSP页面进行通信。
2、public void setParent(Tag t),setPageContext方法执行完后,WEB容器接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null。
3、public int doStartTag(),调用了setPageContext方法和setParent方法之后,WEB容器执行到自定义标签的开始标记时,就会调用标签处理器的doStartTag方法。
4、public int doEndTag(),WEB容器执行完自定义标签的标签体后,就会接着去执行自定义标签的结束标记,此时,WEB容器会去调用标签处理器的doEndTag方法。
5、public void release(),通常WEB容器执行完自定义标签后,标签处理器会驻留在内存中,为其它请求服务器,直至停止web应用时,web容器才会调用release方法。
我们在tomcat服务器的"work\Catalina\localhost\JavaWeb_JspTag_study_20140816\org\apache\jsp"目录下可以找到将jspTag_Test1.jsp翻译成Servlet后的java源代码,如下图所示:

打开jspTag_005fTest1_jsp.java文件,可以看到setPageContext(PageContext pc)、setParent(Tag t)、doStartTag()、doEndTag()、release()这5个方法的调用顺序和过程。
jspTag_005fTest1_jsp.java的代码如下:
复制代码
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class jspTag_005fTest1_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {

private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
private static java.util.List _jspx_dependants;
static {
_jspx_dependants = new java.util.ArrayList(1);
_jspx_dependants.add("/WEB-INF/gacl.tld");
}
private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody;
private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.AnnotationProcessor _jsp_annotationprocessor;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspInit() {
_005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(getServletConfig());
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
_jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());
}
public void _jspDestroy() {
_005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.release();
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("<!-- 引用gacl标签库,标签库的前缀(prefix)可以随便设置,如这里设置成 prefix=\"gacl\" -->\r\n");
out.write("\r\n");
out.write("<!DOCTYPE HTML>\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <title>输出客户端的IP</title>\r\n");
out.write(" </head>\r\n");
out.write(" \r\n");
out.write(" <body>\r\n");
out.write(" 你的IP地址是(使用java代码获取输出):\r\n");
out.write(" ");
//在jsp页面中使用java代码获取客户端IP地址
String ip = request.getRemoteAddr();
out.write(ip);

out.write("\r\n");
out.write(" <hr/>\r\n");
out.write(" 你的IP地址是(使用自定义标签获取输出):");
if (_jspx_meth_xdp_005fviewIP_005f0(_jspx_page_context))
return;
out.write("\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try { out.clearBuffer(); } catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
private boolean _jspx_meth_xdp_005fviewIP_005f0(PageContext _jspx_page_context)
throws Throwable {
PageContext pageContext = _jspx_page_context;
JspWriter out = _jspx_page_context.getOut();
// xdp:viewIP
me.gacl.web.tag.ViewIPTag _jspx_th_xdp_005fviewIP_005f0 = (me.gacl.web.tag.ViewIPTag) _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.get(me.gacl.web.tag.ViewIPTag.class);
_jspx_th_xdp_005fviewIP_005f0.setPageContext(_jspx_page_context);
_jspx_th_xdp_005fviewIP_005f0.setParent(null);
int _jspx_eval_xdp_005fviewIP_005f0 = _jspx_th_xdp_005fviewIP_005f0.doStartTag();
if (_jspx_th_xdp_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
_005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.reuse(_jspx_th_xdp_005fviewIP_005f0);
return true;
}
_005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.reuse(_jspx_th_xdp_005fviewIP_005f0);
return false;
}
}
复制代码
下面重点分析一下上述代码中标红色的那个 private boolean _jspx_meth_xdp_005fviewIP_005f0(PageContext _jspx_page_context)方法中的代码
①、这里是实例化一个viewIP标签处理器类me.gacl.web.tag.ViewIPTag的对象
// xdp:viewIP
me.gacl.web.tag.ViewIPTag _jspx_th_xdp_005fviewIP_005f0 = (me.gacl.web.tag.ViewIPTag) _005fjspx_005ftagPool_005fxdp_005fviewIP_005fnobody.get(me.gacl.web.tag.ViewIPTag.class);
②、实例化标签处理器后,调用setPageContext方法将JSP页面的pageContext对象传递给标签处理器
_jspx_th_xdp_005fviewIP_005f0.setPageContext(_jspx_page_context);
③、setPageContext方法执行完后,接着调用的setParent方法将当前标签的父标签传递给当前标签处理器,如果当前标签没有父标签,则传递给setParent方法的参数值为null
_jspx_th_xdp_005fviewIP_005f0.setParent(null);
④、调用了setPageContext方法和setParent方法之后,WEB容器执行到自定义标签的开始标记时,就会调用标签处理器的doStartTag方法
int _jspx_eval_xdp_005fviewIP_005f0 = _jspx_th_xdp_005fviewIP_005f0.doStartTag();
⑤、WEB容器执行完自定义标签的标签体后,就会接着去执行自定义标签的结束标记,此时,WEB容器会去调用标签处理器的doEndTag方法
if (_jspx_th_xdp_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)
这就是自定义标签的执行流程。
这里以一个入门级的案例来讲解javaweb的自定义标签开发,在后面的博文中会进行更加详尽的介绍。

(二十四)——jsp传统标签开发
一、标签技术的API
1.1、标签技术的API类继承关系

二、标签API简单介绍
2.1、JspTag接口
JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属性和方法。JspTag接口有Tag和SimpleTag两个直接子接口,JSP2.0以前的版本中只有Tag接口,所以把实现Tag接口的自定义标签也叫做传统标签,把实现SimpleTag接口的自定义标签叫做简单标签。
2.2、Tag接口
Tag接口是所有传统标签的父接口,其中定义了两个重要方法(doStartTag、doEndTag)方法和四个常量(EVAL_BODY_INCLUDE、SKIP_BODY、EVAL_PAGE、SKIP_PAGE),这两个方法和四个常量的作用如下:
(1)WEB容器在解释执行JSP页面的过程中,遇到自定义标签的开始标记就会去调用标签处理器的doStartTag方法,doStartTag方法执行完后可以向WEB容器返回常量EVAL_BODY_INCLUDE或SKIP_BODY。如果doStartTag方法返回EVAL_BODY_INCLUDE,WEB容器就会接着执行自定义标签的标签体;如果doStartTag方法返回SKIP_BODY,WEB容器就会忽略自定义标签的标签体,直接解释执行自定义标签的结束标记。
(2)WEB容器解释执行到自定义标签的结束标记时,就会调用标签处理器的doEndTag方法,doEndTag方法执行完后可以向WEB容器返回常量EVAL_PAGE或SKIP_PAGE。如果doEndTag方法返回常量EVAL_PAGE,WEB容器就会接着执行JSP页面中位于结束标记后面的JSP代码;如果doEndTag方法返回SKIP_PAGE,WEB容器就会忽略JSP页面中位于结束标记后面的所有内容。
从doStartTag和doEndTag方法的作用和返回值的作用可以看出,开发自定义标签时可以在doStartTag方法和doEndTag方法体内编写合适的Java程序代码来实现具体的功能,通过控制doStartTag方法和doEndTag方法的返回值,还可以告诉WEB容器是否执行自定义标签中的标签体内容和JSP页面中位于自定义标签的结束标记后面的内容。
2.3、IterationTag接口
IterationTag接口继承了Tag接口,并在Tag接口的基础上增加了一个doAfterBody方法和一个EVAL_BODY_AGAIN常量。实现IterationTag接口的标签除了可以完成Tag接口所能完成的功能外,还能够通知WEB容器是否重复执行标签体内容。对于实现了IterationTag接口的自定义标签,WEB容器在执行完自定义标签的标签体后,将调用标签处理器的doAfterBody方法,doAfterBody方法可以向WEB容器返回常量EVAL_BODY_AGAIN或SKIP_BODY。如果doAfterBody方法返回EVAL_BODY_AGAIN,WEB容器就会把标签体内容再重复执行一次,执行完后接着再调用doAfterBody方法,如此往复,直到doAfterBody方法返回常量SKIP_BODY,WEB容器才会开始处理标签的结束标记和调用doEndTag方法。
可见,开发自定义标签时,可以通过控制doAfterBody方法的返回值来告诉WEB容器是否重复执行标签体内容,从而达到循环处理标签体内容的效果。例如,可以通过一个实现IterationTag接口的标签来迭代输出一个集合中的所有元素,在标签体部分指定元素的输出格式。
在JSP API中也提供了IterationTag接口的默认实现类TagSupport,我们在编写自定义标签的标签处理器类时,可以继承和扩展TagSupport类,这相比实现IterationTag接口将简化开发工作。
2.4、BodyTag接口
BodyTag接口继承了IterationTag接口,并在IterationTag接口的基础上增加了两个方法(setBodyContent、doInitBody)和一个EVAL_BODY_BUFFERED常量。实现BodyTag接口的标签除了可以完成IterationTag接口所能完成的功能,还可以对标签体内容进行修改。对于实现了BodyTag接口的自定义标签,标签处理器的doStartTag方法不仅可以返回前面讲解的常量EVAL_BODY_INCLUDE或SKIP_BODY,还可以返回常量EVAL_BODY_BUFFERED。如果doStartTag方法返回EVAL_BODY_BUFFERED,WEB容器就会创建一个专用于捕获标签体运行结果的BodyContent对象,然后调用标签处理器的setBodyContent方法将BodyContent对象的引用传递给标签处理器,WEB容器接着将标签体的执行结果写入到BodyContent对象中。在标签处理器的后续事件方法中,可以通过先前保存的BodyContent对象的引用来获取标签体的执行结果,然后调用BodyContent对象特有的方法对BodyContent对象中的内容(即标签体的执行结果)进行修改和控制其输出。
在JSP API中也提供了BodyTag接口的实现类BodyTagSupport,我们在编写能够修改标签体内容的自定义标签的标签处理器类时,可以继承和扩展BodyTagSupport类,这相比实现BodyTag接口将简化开发工作。
2.5、SimpleTag接口
SimpleTag接口是JSP2.0中新增的一个标签接口。由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广,因此,SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口。SimpleTag接口与传统标签接口最大的区别在于,SimpleTag接口只定义了一个用于处理标签逻辑的doTag方法,该方法在WEB容器执行自定义标签时调用,并且只被调用一次。那些使用传统标签接口所完成的功能,例如是否执行标签体、迭代标签体、对标签体内容进行修改等功能都可以在doTag方法中完成。
在JSP API中也提供了SimpleTag接口的实现类SimpleTagSupport,我们在编写简单标签时,可以继承和扩展SimpleTagSupport类,这相比实现SimpleTag接口将简化开发工作。
2.6、传统标签接口中的各个方法可以返回的返回值说明
下图列举了Tag接口、IterationTag接口和BodyTag接口中的主要方法及它们分别可以返回的返回值的说明。

三、开发传统标签实现页面逻辑
开发人员在编写Jsp页面时,经常还需要在页面中引入一些逻辑,例如:
控制jsp页面某一部分内容是否执行。
控制整个jsp页面是否执行。
控制jsp页面内容重复执行。
修改jsp页面内容输出。
自定义标签除了可以移除jsp页面java代码外,它也可以实现以上功能。
3.1、控制jsp页面某一部分内容是否执行
编写一个类实现tag接口,控制doStartTag()方法的返回值,如果这个方法返回EVAL_BODY_INCLUDE,则执行标签体,如果返回SKIP_BODY,则不执行标签体。
SUN公司针对tag接口提供了一个默认的实现类TagSupport,TagSupport类中实现了tag接口的所有方法,因此我们可以编写一个类继承TagSupport类,然后再重写doStartTag方法。
示例代码如下:
TagDemo1.java
package me.gacl.web.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
/**
* @author gacl
* TagSupport类实现了Tag接口,TagDemo1继承TagSupport类
*
*/
public class TagDemo1 extends TagSupport {
/* 重写doStartTag方法,控制标签体是否执行
* @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
//如果这个方法返回EVAL_BODY_INCLUDE,则执行标签体,如果返回SKIP_BODY,则不执行标签体
//return Tag.EVAL_BODY_INCLUDE;
return Tag.SKIP_BODY;
}
}
在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:
<tag>
<name>demo1</name>
<tag-class>me.gacl.web.tag.TagDemo1</tag-class>
<!--demo1标签有标签体,所以这里的body-content设置为JSP-->
<body-content>JSP</body-content>
</tag>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gacl" prefix="gacl" %>
<!DOCTYPE HTML>
<html>
<head>
<title>控制标签体是否执行</title>
</head>
<body>
<%--在jsp页面中使用自定义标签 demo1标签是带有标签体的,标签体的内容是"孤傲苍狼"这几个字符串--%>
<gacl:demo1>
孤傲苍狼
</gacl:demo1>
</body>
</html>

运行效果如下:
 
3.2、控制整个jsp页面是否执行
编写一个类实现tag接口,控制doEndTag()方法的返回值,如果这个方法返回EVAL_PAGE,则执行标签余下的jsp页面,如果返回SKIP_PAGE,则不执行余下的jsp。
示例代码如下:
TagDemo2.java
package me.gacl.web.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
/**
* @author gacl
* TagSupport类实现了Tag接口,TagDemo2继承TagSupport类
*/
public class TagDemo2 extends TagSupport{
/* 重写doEndTag方法,控制jsp页面是否执行
* @see javax.servlet.jsp.tagext.TagSupport#doEndTag()
*/
@Override
public int doEndTag() throws JspException {
//如果这个方法返回EVAL_PAGE,则执行标签余下的jsp页面,如果返回SKIP_PAGE,则不执行余下的jsp
return Tag.SKIP_PAGE;
//return Tag.EVAL_PAGE;
}
}
在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:
<tag>
<name>demo2</name>
<tag-class>me.gacl.web.tag.TagDemo2</tag-class>
<!--demo2标签没有标签体,所以这里的body-content设置为empty-->
<body-content>empty</body-content>
</tag>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gacl" prefix="gacl" %>
<!DOCTYPE HTML>
<html>
<head>
<title>控制jsp页面是否执行</title>
</head>
<body>
<h1>jsp页面的内容1</h1>
<%--在jsp页面中使用自定义标签 demo2标签是不带标签体的--%>
<gacl:demo2/>
<h1>jsp页面的内容2</h1>
</body>
</html>

运行效果如下:
 
3.3、控制jsp页面内容重复执行
编写一个类实现Iterationtag接口,控制doAfterBody()方法的返回值,如果这个方法返回EVAL_BODY_AGAIN, 则web服务器又执行一次标签体,依次类推,一直执行到doAfterBody方法返回SKIP_BODY,则标签体才不会重复执行。
示例代码如下:
TagDemo3.java
package me.gacl.web.tag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.IterationTag;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
public class TagDemo3 extends TagSupport {
int x = 5;
@Override
public int doStartTag() throws JspException {
return Tag.EVAL_BODY_INCLUDE;
}
/* 控制doAfterBody()方法的返回值,
* 如果这个方法返回EVAL_BODY_AGAIN, 则web服务器又执行一次标签体,
* 依次类推,一直执行到doAfterBody方法返回SKIP_BODY,则标签体才不会重复执行。
* @see javax.servlet.jsp.tagext.TagSupport#doAfterBody()
*/
@Override
public int doAfterBody() throws JspException {
x--;
if(x>0){
return IterationTag.EVAL_BODY_AGAIN;
}else{
return IterationTag.SKIP_BODY;
}
}
}
在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:
<tag>
<name>demo3</name>
<tag-class>me.gacl.web.tag.TagDemo3</tag-class>
<!--demo3标签有标签体,所以这里的body-content设置为JSP-->
<body-content>JSP</body-content>
</tag>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gacl" prefix="gacl" %>
<!DOCTYPE HTML>
<html>
<head>
<title>控制页面内容重复执行5次</title>
</head>
<body>
<%--在jsp页面中使用自定义标签 demo3标签--%>
<gacl:demo3>
<h3>jsp页面的内容</h3>
</gacl:demo3>
</body>
</html>
运行效果如下:

3.4、修改jsp页面内容输出
编写一个类实现BodyTag接口,控制doStartTag()方法返回EVAL_BODY_BUFFERED,则web服务器会创建BodyContent对象捕获标签体,然后在doEndTag()方法体内,得到代表标签体的bodyContent对象,从而就可以对标签体进行修改操作。
SUN公司针对BodyTag接口提供了一个默认的实现类BodyTagSupport,BodyTagSupport类中实现了BodyTag接口的所有方法,因此我们可以编写一个类继承BodyTagSupport类,然后再根据需要重写doStartTag方法和doEndTag()方法。
示例代码如下:
TagDemo4.java
package me.gacl.web.tag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.Tag;
/**
* @author gacl
* BodyTagSupport类实现了BodyTag接口接口,TagDemo4继承 BodyTagSupport类
*/
public class TagDemo4 extends BodyTagSupport {
/* 控制doStartTag()方法返回EVAL_BODY_BUFFERED
* @see javax.servlet.jsp.tagext.BodyTagSupport#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
return BodyTag.EVAL_BODY_BUFFERED;
}
@Override
public int doEndTag() throws JspException {
//this.getBodyContent()得到代表标签体的bodyContent对象
BodyContent bodyContent = this.getBodyContent();
//拿到标签体
String content = bodyContent.getString();
//修改标签体里面的内容,将标签体的内容转换成大写
String result = content.toUpperCase();
try {
//输出修改后的内容
this.pageContext.getOut().write(result);
} catch (IOException e) {
throw new RuntimeException(e);
}
return Tag.EVAL_PAGE;
}
}
在WEB-INF目录下的tld文件中添加对该标签处理类的描述,如下:
<tag>
<name>demo4</name>
<tag-class>me.gacl.web.tag.TagDemo4</tag-class>
<!--demo4标签有标签体,所以这里的body-content设置为JSP-->
<body-content>JSP</body-content>
</tag>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gacl" prefix="gacl" %>
<!DOCTYPE HTML>
<html>
<head>
<title>修改jsp页面内容输出</title>
</head>
<body>
<%--在jsp页面中使用自定义标签 demo4标签--%>
<gacl:demo4>
<h3>xdp_gacl</h3>
</gacl:demo4>
</body>
</html>
运行效果如下:

四、jsp传统标签开发总结
在现在的jsp标签开发中,很少直接使用传统标签来开发了,目前用得较多的都是简单标签,所以Jsp的传统标签开发了解一下即可,下一篇重点介绍jsp简单标签的开发

(二十五)——jsp简单标签开发(一)
javaweb学习总结(二十五)——jsp简单标签开发(一)
一、简单标签(SimpleTag)
由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广, SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单、便于编写和调用的SimpleTag接口来实现标签的功能。

实现SimpleTag接口的标签通常称为简单标签。简单标签共定义了5个方法:
setJspContext方法
setParent和getParent方法
setJspBody方法
doTag方法(非常重要),简单标签使用这个方法就可以完成所有的业务逻辑
二、SimpleTag方法介绍
2.1、setJspContext方法
用于把JSP页面的pageContext对象传递给标签处理器对象
2.2、setParent方法
用于把父标签处理器对象传递给当前标签处理器对象
2.3、getParent方法
用于获得当前标签的父标签处理器对象
2.4、setJspBody方法
用于把代表标签体的JspFragment对象传递给标签处理器对象
2.5、doTag方法
用于完成所有的标签逻辑,包括输出、迭代、修改标签体内容等。在doTag方法中可以抛出javax.servlet.jsp.SkipPageException异常,用于通知WEB容器不再执行JSP页面中位于结束标记后面的内容,这等效于在传统标签的doEndTag方法中返回Tag.SKIP_PAGE常量的情况。
三、SimpleTag接口方法的执行顺序
当web容器开始执行标签时,会调用如下方法完成标签的初始化:
1. WEB容器调用标签处理器对象的setJspContext方法,将代表JSP页面的pageContext对象传递给标签处理器对象。
2. WEB容器调用标签处理器对象的setParent方法,将父标签处理器对象传递给这个标签处理器对象。注意,只有在标签存在父标签的情况下,WEB容器才会调用这个方法。
3. 如果调用标签时设置了属性,容器将调用每个属性对应的setter方法把属性值传递给标签处理器对象。如果标签的属性值是EL表达式或脚本表达式,则WEB容器首先计算表达式的值,然后把值传递给标签处理器对象。
4. 如果简单标签有标签体,WEB容器将调用setJspBody方法把代表标签体的JspFragment对象传递进来。
5. 执行标签时WEB容器调用标签处理器的doTag()方法,开发人员在方法体内通过操作JspFragment对象,就可以实现是否执行、迭代、修改标签体的目的。
四、开发简单标签实现页面逻辑
SUN公司针对SimpleTag接口提供了一个默认的实现类SimpleTagSupport,SimpleTagSupport类中实现了SimpleTag接口的所有方法,因此我们可以编写一个类继承SimpleTagSupport类,然后根据业务需要再重写doTag方法。
4.1、控制jsp页面某一部分内容是否执行
 编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法里面不调用jspFrament.invoke方法即可。
示例代码如下:
SimpleTagDemo1.java
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* SimpleTagSupport类实现了SimpleTag接口,
* SampleTagDemo1类继承SimpleTagSupport
*/
public class SimpleTagDemo1 extends SimpleTagSupport {
/* 简单标签使用这个方法就可以完成所有的业务逻辑
* @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
* 重写doTag方法,控制标签体是否执行
*/
@Override
public void doTag() throws JspException, IOException {
//得到代表jsp标签体的JspFragment
JspFragment jspFragment = this.getJspBody();
//得到jsp页面的的PageContext对象
//PageContext pageContext = (PageContext) jspFragment.getJspContext();
//调用JspWriter将标签体的内容输出到浏览器
//jspFragment.invoke(pageContext.getOut());
//将标签体的内容输出到浏览器,注释下面一行就不显示
jspFragment.invoke(null);
}
}
在WEB-INF目录下新建一个simpletag.tld文件,然后在simpletag.tld文件中添加对该标签处理类的描述,如下:

simpletag.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">
<!-- description用来添加对taglib(标签库)的描述 -->
<description>孤傲苍狼开发的SimpleTag自定义标签库</description>
<!--taglib(标签库)的版本号 -->
<tlib-version>1.0</tlib-version>
<short-name>GaclSimpleTagLibrary</short-name>
<!--
为自定义标签库设置一个uri,uri以/开头,/后面的内容随便写,如这里的/simpletag ,
在Jsp页面中引用标签库时,需要通过uri找到标签库
在Jsp页面中就要这样引入标签库:<%@taglib uri="/simpletag" prefix="gacl"%>
-->
<uri>/simpletag</uri>
<!--一个taglib(标签库)中包含多个自定义标签,每一个自定义标签使用一个tag标记来描述 -->
<!-- 一个tag标记对应一个自定义标签 -->
<tag>
<description>SimpleTag(简单标签)Demo1</description>
<!--
为标签处理器类配一个标签名,在Jsp页面中使用标签时是通过标签名来找到要调用的标签处理器类的
通过demo1就能找到对应的me.gacl.web.simpletag.SimpleTagDemo1类
-->
<name>demo1</name>
<!-- 标签对应的处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo1</tag-class>
<!--
tld文件中有四种标签体类型 :empty JSP scriptless tagdepentend
在简单标签(SampleTag)中标签体body-content的值只允许是empty和scriptless,不允许设置成JSP,如果设置成JSP就会出现异常
在传统标签中标签体body-content的值只允许是empty和JSP
如果标签体body-content的值设置成tagdepentend,那么就表示标签体里面的内容是给标签处理器类使用的,
例如:开发一个查询用户的sql标签,此时标签体重的SQL语句就是给SQL标签的标签处理器来使用的
<gacl:sql>SELECT * FROM USER</gacl:sql>
在这种情况下,sql标签的<body-content>就要设置成tagdepentend,tagdepentend用得比较少,了解一下即可
-->
<body-content>scriptless</body-content>
</tag>
</taglib>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/simpletag" prefix="gacl" %>
<!DOCTYPE HTML>
<html>
<head>
<title>用简单标签控制标签体是否执行</title>
</head>
<body>
<%--在jsp页面中使用自定义标签 demo1标签是带有标签体的,标签体的内容是"孤傲苍狼"这几个字符串--%>
<gacl:demo1>
孤傲苍狼
</gacl:demo1>
</body>
</html>

运行效果如下:

4.2、控制jsp页面内容重复执行
编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法里面重复调用jspFrament.invoke方法即可。
示例代码如下:
SimpleTagDemo2.java
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* SimpleTagSupport类实现了SimpleTag接口,
* SampleTagDemo2类继承SimpleTagSupport
*/
public class SimpleTagDemo2 extends SimpleTagSupport {
/* 简单标签使用这个方法就可以完成所有的业务逻辑
* @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
* 重写doTag方法,控制标签执行5次
*/
@Override
public void doTag() throws JspException, IOException {
// 得到代表jsp标签体的JspFragment
JspFragment jspFragment = this.getJspBody();
for (int i = 0; i < 5; i++) {
// 将标签体的内容输出到浏览器
jspFragment.invoke(null);
}
}
}
在WEB-INF目录下的simpletag.tld文件中添加对该标签处理类的描述,如下:
<tag>
<!-- 标签名 -->
<name>demo2</name>
<!-- 标签处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo2</tag-class>
<!-- 标签体允许的内容 ,scriptless表示标签体的内容不允许是java脚本代码-->
<body-content>scriptless</body-content>
</tag>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/simpletag" prefix="gacl" %>
<!DOCTYPE HTML>
<html>
<head>
<title>用简单标签控制标签体执行5次</title>
</head>
<body>
<%--在jsp页面中使用自定义标签 --%>
<gacl:demo2>
孤傲苍狼<p/>
</gacl:demo2>
</body>
</html>

运行效果如下:

4.3、修改jsp页面内容输出
编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法调用jspFrament.invoke方法时,让执行结果写一个自定义的缓冲中即可,然后开发人员可以取出缓冲的数据修改输出。
示例代码如下:
SimpleTagDemo3.java
package me.gacl.web.simpletag;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* SimpleTagSupport类实现了SimpleTag接口,
* SampleTagDemo3类继承SimpleTagSupport
*/
public class SimpleTagDemo3 extends SimpleTagSupport {
/* 简单标签使用这个方法就可以完成所有的业务逻辑
* @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
* 重写doTag方法,修改标签体里面的内容,将标签体的内容转换成大写
*/
@Override
public void doTag() throws JspException, IOException {
// 得到代表jsp标签体的JspFragment
JspFragment jspFragment = this.getJspBody();
StringWriter sw = new StringWriter();
//将标签体的内容写入到sw流中
jspFragment.invoke(sw);
//获取sw流缓冲区的内容
String content = sw.getBuffer().toString();
content = content.toUpperCase();
PageContext pageContext = (PageContext) this.getJspContext();
//将修改后的content输出到浏览器中
pageContext.getOut().write(content);
}
}
在WEB-INF目录下的simpletag.tld文件中添加对该标签处理类的描述,如下:
<tag>
<!-- 标签名 -->
<name>demo3</name>
<!-- 标签处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo3</tag-class>
<!-- 标签体允许的内容 ,scriptless表示标签体的内容不允许是java脚本代码-->
<body-content>scriptless</body-content>
</tag>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%--<%@taglib uri="/simpletag" prefix="gacl" %>--%>
<%--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录 --%>
<%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>
<!DOCTYPE HTML>
<html>
<head>
<title>用简单标签修改jsp页面内容输出</title>
</head>
<body>
<%--在jsp页面中使用自定义标签 --%>
<gacl:demo3>
gacl_xdp
</gacl:demo3>
</body>
</html>
运行效果如下:

4.4、控制整个jsp页面是否执行
编写一个类继承SimpleTagSupport,然后再重写doTag方法,在doTag方法抛出SkipPageException异常即可,jsp收到这个异常,将忽略标签余下jsp页面的执行。
示例代码如下:
SimpleTagDemo4.java
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* SimpleTagSupport类实现了SimpleTag接口,
* SampleTagDemo4类继承SimpleTagSupport
*/
public class SimpleTagDemo4 extends SimpleTagSupport {
/* 简单标签使用这个方法就可以完成所有的业务逻辑
* @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
* 重写doTag方法,控制标签余下的Jsp不执行
*/
@Override
public void doTag() throws JspException, IOException {
//抛出一个SkipPageException异常就可以控制标签余下的Jsp不执行
throw new SkipPageException();
}
}
在WEB-INF目录下的simpletag.tld文件中添加对该标签处理类的描述,如下:
<tag>
<!-- 标签名 -->
<name>demo4</name>
<!-- 标签处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo4</tag-class>
<!-- 标签体允许的内容 ,empty表示该标签没有标签体-->
<body-content>empty</body-content>
</tag>
在jsp页面中导入并使用自定义标签,如下:
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%--<%@taglib uri="/simpletag" prefix="gacl" %>--%>
<%--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录 --%>
<%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>
<!DOCTYPE HTML>
<html>
<head>
<title>用简单标签控制标签余下的Jsp不执行</title>
</head>
<body>
<h1>孤傲苍狼</h1>
<%--在jsp页面中使用自定义标签 --%>
<gacl:demo4/>
<!-- 这里的内容位于 <gacl:demo4/>标签后面,因此不会输出到页面上 -->
<h1>白虎神皇</h1>
</body>
</html>
运行效果如下:

五、简单标签开发的一些注意细节
5.1、标签类编写细节
开发标签类时,不要直接去实现SimpleTag接口,而是应该继承SimpleTagSupport类,SimpleTagSupport类是SimpleTag接口的一个默认实现类,通过继承SimpleTagSupport类,就可以直接使用SimpleTagSupport类已经实现的那些方法,如果SimpleTagSupport类的方法实现不满足业务要求,那么就可以根据具体的业务情况将相应的方法进行重写。
5.2、tld文件中标签体类型设置细节
我们开发好一个简单标签后,需要在tld文件中添加对该标签的描述,例如:
<tag>
<!-- 标签名 -->
<name>demo2</name>
<!-- 标签处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo2</tag-class>
<!-- 标签体允许的内容 ,scriptless表示标签体的内容不允许是java脚本代码-->
<body-content>scriptless</body-content>
</tag>
开发好一个标签后,在tld文件中使用<tag>来描述一个标签,描述的内容包括标签名(name),标签处理器类(tag-class),标签体的内容(body-content)。
tld文件中有四种标签体(body-content)类型 :empty、JSP、scriptless、tagdependent
简单标签标签体的细节注意问题:
在简单标签(SampleTag)中标签体body-content的值只允许是empty、scriptless、tagdependent,不允许设置成JSP,如果设置成JSP就会出现异常:
The TLD for the class me.gacl.web.simpletag.SimpleTagDemo1 specifies an invalid body-content (JSP) for a SimpleTag
body-content的值如果设置成empty,那么就表示该标签没有标签体,如果是设置成scriptless,那么表示该标签是有标签体的,但是标签体中的内容不可以是<%java代码%>,例如:
<gacl:xdpdemo1>
<%
//嵌套在标签体中的java代码
int i= 0;
%>
孤傲苍狼
</gacl:xdpdemo1>
否则运行标签时就会出现如下错误:
Scripting elements ( &lt;%!, &lt;jsp:declaration, &lt;%=, &lt;jsp:expression, &lt;%, &lt;jsp:scriptlet ) are disallowed here
jsp标签技术出现的目的就是为了移除在jsp页面上编写的java代码的,如果在jsp标签中允许出现java代码,那么就违背了jsp标签技术设计时的初衷了。所以在简单标签的标签体中是不允许出现java代码的。
传统标签标签体的细节注意问题:
在传统标签中标签体body-content的值允许是empty、JSP、scriptless、tagdependent,body-content的值如果是设置成JSP,那么表示该标签是有标签体的,并且标签体的内容可以是任意的,包括java代码,如果是设置成scriptless,那么表示该标签是有标签体的,但是标签体的内容不能是java代码

如果传统标签和简单标签的标签体body-content的值设置成tagdependent,那么就表示标签体里面的内容是给标签处理器类使用的,tagdependent用得比较少,了解一下即可
5.3、tld文件中标签库的uri设置细节
如果在一个项目中使用或者开发了多个标签库,例如:

那么标签库的uri不能设置成相同的,否则在Jsp页面中通过uri引用标签库时就不知道引用哪一个标签库了,如果真的有那么巧,两个标签库的uri是刚好一样的,如下图所示:

那么在jsp页面中引用标签库时如果"<%@taglib uri="/gacl" prefix="gacl" %>"这样引用,那么就无法判断当前引用的标签库到底是gacl.tld标签库中的标签还是simpletag.tld标签库中的标签,因为两个标签库的uri刚好都是"/gacl",在两个标签库的引用uri一样的情况下,为了能够在jsp中区别到底引用的是哪个标签库,可以换一种引用方式:<%@taglib uri="要引用的标签库的tld文件目录" prefix="gacl"%>,使用taglib指令引入标签库时,taglib指令的uri属性指定为标签库的tld文件目录,这样就可以区别开了,例如:
引用gacl.tld标签库:<%@taglib uri="/WEB-INF/gacl.tld" prefix="gacl"%>、
引用simpletag.tld标签库:<%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>
所以当在项目中引用了多个标签库,如果标签库的uri刚好是一样的,就可以用这种方式解决。
六、简单标签开发步骤总结
1、编写一个类继承SimpleTagSupport类,然后根据业务需要重写SimpleTagSupport类中已经实现了的方法,一般情况下只需要重写doTag()方法即可。
2、在WEB-INF目录下创建一个tld文件,在tld文件中添加对该标签的描述。tld文件不一定放在WEB-INF目录下,也可以放在别的目录,习惯是放在WEB-INF目录下。

(二十六)——jsp简单标签标签库开发(二)
一、JspFragment类介绍
javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象代表JSP页面中的一段符合JSP语法规范的JSP片段,这段JSP片段中不能包含JSP脚本元素。
WEB容器在处理简单标签的标签体时,会把标签体内容用一个JspFragment对象表示,并调用标签处理器对象的setJspBody方法把JspFragment对象传递给标签处理器对象。JspFragment类中只定义了两个方法,如下所示:
getJspContext方法
用于返回代表调用页面的JspContext对象.
public abstract void invoke(java.io.Writer out)
用于执行JspFragment对象所代表的JSP代码片段,参数out用于指定将JspFragment对象的执行结果写入到哪个输出流对象中,如果 传递给参数out的值为null,则将执行结果写入到JspContext.getOut()方法返回的输出流对象中。(简而言之,可以理解为写给浏览器)
1.1、invoke方法详解
JspFragment.invoke方法是JspFragment最重要的方法,利用这个方法可以控制是否执行和输出标签体的内容、是否迭代执行标签体的内容或对标签体的执行结果进行修改后再输出。例如:
在标签处理器中如果没有调用JspFragment.invoke方法,其结果就相当于忽略标签体内容;
在标签处理器中重复调用JspFragment.invoke方法,则标签体内容将会被重复执行;
若想在标签处理器中修改标签体内容,只需在调用invoke方法时指定一个可取出结果数据的输出流对象(例如StringWriter),让标签体的执行结果输出到该输出流对象中,然后从该输出流对象中取出数据进行修改后再输出到目标设备,即可达到修改标签体的目的。
二、开发带属性的标签
自定义标签可以定义一个或多个属性,这样,在JSP页面中应用自定义标签时就可以设置这些属性的值,通过这些属性为标签处理器传递参数信息,从而提高标签的灵活性和复用性。
要想让一个自定义标签具有属性,通常需要完成两个任务:
在标签处理器中编写每个属性对应的setter方法
在TLD文件中描术标签的属性
为自定义标签定义属性时,每个属性都必须按照JavaBean的属性命名方式,在标签处理器中定义属性名对应的setter方法,用来接收 JSP页面调用自定义标签时传递进来的属性值。 例如属性url,在标签处理器类中就要定义相应的setUrl(String url)方法。
在标签处理器中定义相应的set方法后,JSP引擎在解析执行开始标签前,也就是调用doStartTag方法前,会调用set属性方法,为标签设置属性。
2.1、开发带属性的标签范例
范例1:通过标签的属性控制标签体的执行次数
示例代码如下:
SimpleTagDemo5.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* SimpleTagSupport类实现了SimpleTag接口,
* SampleTagDemo5类继承SimpleTagSupport
* 通过标签的属性控制标签体的执行次数
*/
public class SimpleTagDemo5 extends SimpleTagSupport {
/**
* 定义标签的属性
*/
private int count;
/**count属性对应的set方法
* @param count
*/
public void setCount(int count) {
this.count = count;
}
/* 简单标签使用这个方法就可以完成所有的业务逻辑
* @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
* 重写doTag方法,通过标签的属性控制标签体的执行次数
*/
@Override
public void doTag() throws JspException, IOException {
for (int i = 0; i < count; i++) {
this.getJspBody().invoke(null);
}
}
}
复制代码
在WEB-INF目录下的tld文件中添加对该标签的描述,如下所示:
复制代码
<tag>
<!-- 标签名 -->
<name>demo5</name>
<!-- 标签处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo5</tag-class>
<!-- 标签体允许的内容-->
<body-content>scriptless</body-content>
<!-- 标签的属性描述 -->
<attribute>
<description>描述标签的count属性</description>
<!-- 标签的count属性 -->
<name>count</name>
<required>true</required>
<!-- rtexprvalue用来指示标签的属性值是否可以是一个表达式,
一般设置为true,true就表示允许标签的属性值可以是一个表达式-->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
复制代码
在jsp页面引入标签库并使用自定义标签
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%--< %@taglib uri="/simpletag" prefix="gacl" %>--%>
<%--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录 --%>
<%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>
<!DOCTYPE HTML>
<html>
<head>
<title>通过标签的属性控制标签体的执行次数</title>
</head>
<body>
<%--在jsp页面中使用自定义标签,标签有一个count属性 --%>
<gacl:demo5 count="2">
<h1>孤傲苍狼</h1>
</gacl:demo5>
</body>
</html>
复制代码
运行效果如下:

如果标签的属性值是8种基本数据类型,那么在JSP页面在传递字符串时,JSP引擎会自动转换成相应的类型,但如果标签的属性值是复合数据类型,那么JSP引擎是无法自动转换的
范例2:标签接收的属性值是一个复合数据类型,该如何给标签的属性赋值
示例代码如下:
SimpleTagDemo6.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import java.util.Date;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* SimpleTagSupport类实现了SimpleTag接口,
* SampleTagDemo6类继承SimpleTagSupport
* 标签的属性说明
*/
public class SimpleTagDemo6 extends SimpleTagSupport {
/**
* 定义标签的属性
*/
private Date date;
/**date属性对应的set方法
* @param date
*/
public void setDate(Date date) {
this.date = date;
}
/* 简单标签使用这个方法就可以完成所有的业务逻辑
* @see javax.servlet.jsp.tagext.SimpleTagSupport#doTag()
* 重写doTag方法,输出date属性值
*/
@Override
public void doTag() throws JspException, IOException {
this.getJspContext().getOut().write(date.toLocaleString());
}
}
复制代码
在WEB-INF目录下的tld文件中添加对该标签的描述,如下所示:
复制代码
<tag>
<!-- 标签名 -->
<name>demo6</name>
<!-- 标签处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo6</tag-class>
<!-- 标签体允许的内容-->
<body-content>empty</body-content>
<!-- 标签的属性描述 -->
<attribute>
<description>描述标签的date属性</description>
<!-- 标签的date属性,复合数据类型 -->
<name>date</name>
<required>true</required>
<!-- rtexprvalue用来指示标签的属性值是否可以是一个表达式,
一般设置为true,true就表示允许标签的属性值可以是一个表达式-->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
复制代码
在jsp页面引入标签库并使用自定义标签
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%--< %@taglib uri="/simpletag" prefix="gacl" %>--%>
<%--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录 --%>
<%@taglib uri="/WEB-INF/simpletag.tld" prefix="gacl"%>
<!DOCTYPE HTML>
<html>
<head>
<title>如果标签接收的属性值是一个复合数据类型,该如何给标签的属性赋值</title>
</head>
<body>
<%--
在jsp页面中使用自定义标签,标签有一个date属性 ,是一个复合数据类型
如果标签的属性值是8种基本数据类型,那么在JSP页面在传递字符串时,JSP引擎会自动转换成相应的类型
但如果标签的属性值是复合数据类型,那么JSP引擎是无法自动转换的,
这里将一个字符串赋值给demo6标签的date属性,在运行标签时就会出现如下错误:
Unable to convert string "1988-05-07" to class "java.util.Date" for attribute "date":
Property Editor not registered with the PropertyEditorManager
<gacl:demo6 date="1988-05-07">
</gacl:demo6>
--%>
<%--如果一定要给标签的复合属性赋值,那么可以采用表达式的方式给复合属性赋值,如下所示: --%>
<%
Date d = new Date();
request.setAttribute("date", d);
%>
<gacl:demo6 date="${date}"/>
<hr/>
<gacl:demo6 date="<%=new Date()%>"/>
</body>
</html>
复制代码
运行效果如下:

2.1、tld文件中用于描述标签属性的<attribute>元素说明
<tag>元素的<attribute>子元素用于描述自定义标签的一个属性,自定义标签所具有的每个属性都要对应一个<attribute>元素
例如:
复制代码
<tag>
<!-- 标签名 -->
<name>demo5</name>
<!-- 标签处理器类-->
<tag-class>me.gacl.web.simpletag.SimpleTagDemo5</tag-class>
<!-- 标签体允许的内容-->
<body-content>scriptless</body-content>
<!-- 标签的属性描述 -->
<attribute>
<description>描述标签的count属性</description>
<!-- 标签的count属性 -->
<name>count</name>
<required>true</required>
<!-- rtexprvalue用来指示标签的属性值是否可以是一个表达式,
一般设置为true,true就表示允许标签的属性值可以是一个表达式-->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
复制代码
<attribute>元素的子元素说明:

到此,简单标签的开发技术就算是全部讲完了,在下一篇博文中会编写一些自定义标签的案例来加深自定标签技术的学习和理解。


(二十七)——jsp简单标签开发案例和打包
一、开发标签库
1.1、开发防盗链标签
1、编写标签处理器类:RefererTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* 防盗链标签RefererTag
*/
public class RefererTag extends SimpleTagSupport {
/**
* 网站域名
*/
private String site;
/**
* 要跳转的页面
*/
private String page;
@Override
public void doTag() throws JspException, IOException {
//获取jsp页面的PageContext对象
PageContext pageContext = (PageContext) this.getJspContext();
//通过PageContext对象来获取HttpServletRequest对象
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
//获取请求的来路(Referer)
String referer = request.getHeader("referer");
//如果来路是null或者来路不是来自我们自己的site,那么就将请求重定向到page页面
if (referer == null || !referer.startsWith(site)) {
//获取HttpServletResponse对象
HttpServletResponse response = (HttpServletResponse)pageContext.getResponse();
String webRoot = request.getContextPath();
if (page.startsWith(webRoot)) {
//重定向到page页面
response.sendRedirect(page);
} else {
//重定向到page页面
response.sendRedirect(webRoot+page);
}
//重定向后,控制保护的页面不要执行
throw new SkipPageException();
}
}
public void setSite(String site) {
this.site = site;
}
public void setPage(String page) {
this.page = page;
}
}
复制代码
2、在WEB-INF目录下tld文件中添加对该标签的描述,如下:
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" 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 web-jsptaglibrary_2_0.xsd">
<description>孤傲苍狼开发的简单标签库</description>
<tlib-version>1.0</tlib-version>
<short-name>TagLib</short-name>
<uri>/gaclTagLib</uri>
<tag>
<!-- 标签名 -->
<name>referer</name>
<!-- 标签处理器类 -->
<tag-class>me.gacl.web.simpletag.RefererTag</tag-class>
<!-- 标签体允许的内容 -->
<body-content>empty</body-content>
<!-- 标签的属性描述 -->
<attribute>
<description>描述标签的site属性</description>
<!-- 标签的site属性 -->
<name>site</name>
<required>true</required>
<!-- rtexprvalue用来指示标签的属性值是否可以是一个表达式, 一般设置为true,true就表示允许标签的属性值可以是一个表达式 -->
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>描述标签的page属性</description>
<!-- 标签的page属性 -->
<name>page</name>
<required>true</required>
<!-- rtexprvalue用来指示标签的属性值是否可以是一个表达式, 一般设置为true,true就表示允许标签的属性值可以是一个表达式 -->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
复制代码
3、测试:在jsp页面中导入标签库并使用防盗链标签
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
< %--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gaclTagLib" prefix="gacl" %>
< %--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录
<%@taglib uri="/WEB-INF/TagLib.tld" prefix="gacl"%>
--%>
< %--在Jsp页面中使用防盗链标签
当用户尝试直接通过URL地址(http://localhost:8080/JavaWeb_JspTag_study_20140816/simpletag/refererTagTest.jsp)访问这个页面时,
防盗链标签的标签处理器内部就会进行处理,将请求重定向到/index.jsp
--%>
<gacl:referer site="http://localhost:8080" page="/index.jsp"/>
<!DOCTYPE HTML>
<html>
<head>
<title>防盗链标签测试</title>
</head>
<body>
网站内部资料
</body>
</html>
复制代码
运行效果如下:

1.2、开发<c:if>标签
1、编写标签处理器类:IFTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* 开发if标签
*/
public class IFTag extends SimpleTagSupport {
/**
* if标签的test属性
*/
private boolean test;
@Override
public void doTag() throws JspException, IOException {
if (test) {
this.getJspBody().invoke(null);
}
}
public void setTest(boolean test) {
this.test = test;
}
}
复制代码
2、在WEB-INF目录下tld文件中添加对该标签的描述,如下:
复制代码
<tag>
<description>if标签</description>
<name>if</name>
<tag-class>me.gacl.web.simpletag.IFTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<description>if标签的test属性</description>
<name>test</name>
<rtexprvalue>true</rtexprvalue>
<required>true</required>
</attribute>
</tag>
复制代码
3、测试:在jsp页面中导入标签库并使用if标签
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
< %--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gaclTagLib" prefix="c" %>
< %--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录
<%@taglib uri="/WEB-INF/TagLib.tld" prefix="c"%>
--%>
<!DOCTYPE HTML>
<html>
<head>
<title>if链标签测试</title>
</head>
<body>
< %--if标签的test属性值为true ,标签体的内容会输出--%>
<c:if test="true">
<h3>网站内部资料</h3>
</c:if>
< %--if标签的test属性值为false ,标签体的内容不会输出--%>
<c:if test="false">
这里的内部不输出
</c:if>
</body>
</html>
复制代码
运行效果如下:

1.3、开发<c:when><c:otherwise>标签
这个标签的开发稍微有一点难度,因为这里面涉及到两个标签处理器类共享同一个变量的问题,如下:
复制代码
<c:when test="${user != null}">
用户不为空
</c:when>
<c:otherwise>
用户为空
</c:otherwise>
复制代码
<c:when>标签和<c:otherwise>标签对应着两个不同的标签处理器类,我们希望做到的效果是,如果<c:when>标签执行了,那么就<c:otherwise>标签就不要再执行,那么这里面就涉及到一个问题:<c:when>标签执行的时候该如何通知<c:otherwise>标签不要执行了呢?这个问题就涉及到了两个标签处理器类如何做到相互通讯的问题,如果<c:when>标签执行了,就要通过某种方式告诉<c:otherwise>标签不要执行,那么该如何做到这样的效果呢?让<c:when>标签处理器类和<c:otherwise>标签处理器类共享同一个变量就可以做到了,那么又该怎么做才能够让两个标签处理器类共享同一个变量呢,标准的做法是这样的:让两个标签拥有同一个父标签。
 1、开发父标签:ChooseTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* when标签和otherwise标签的父标签
*/
public class ChooseTag extends SimpleTagSupport {
/**
* 定义一个boolean类型的属性,该属性用于标识该标签下的某一个子标签是否已经执行过了,
* 如果该标签下的某一个子标签已经执行过了,就将该属性设置为true
*/
private boolean isExecute;
@Override
public void doTag() throws JspException, IOException {
//输出标签体中的内容
this.getJspBody().invoke(null);
}
public boolean isExecute() {
return isExecute;
}
public void setExecute(boolean isExecute) {
this.isExecute = isExecute;
}
}
复制代码
2、开发when标签和otherwise标签
WhenTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* when标签
*/
public class WhenTag extends SimpleTagSupport {
/**
* test属性,该属性值为true时,输出标签体中的内容
*/
private boolean test;
@Override
public void doTag() throws JspException, IOException {
//获取标签的父标签
ChooseTag parentTag = (ChooseTag) this.getParent();
if (test == true && parentTag.isExecute() == false) {
//输出标签体中的内容
this.getJspBody().invoke(null);
//将父标签的isExecute属性设置为true,告诉父标签,我(when标签)已经执行过了
parentTag.setExecute(true);
}
}
public void setTest(boolean test) {
this.test = test;
}
}
复制代码
OtherWiseTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* otherwise标签
*/
public class OtherWiseTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
//获取标签的父标签
ChooseTag parentTag = (ChooseTag) this.getParent();
//如果父标签下的when标签没有执行过
if (parentTag.isExecute() == false) {
//输出标签体中的内容
this.getJspBody().invoke(null);
//设置父标签的isExecute属性为true,告诉父标签,我(otherwise标签)已经执行过了
parentTag.setExecute(true);
}
}
}
复制代码
3、在WEB-INF目录下tld文件中添加对ChooseTag、WhenTag、OtherWiseTag这三对标签的描述,如下:
复制代码
<tag>
<description>choose标签</description>
<name>choose</name>
<tag-class>me.gacl.web.simpletag.ChooseTag</tag-class>
<body-content>scriptless</body-content>
</tag>
<tag>
<description>when标签</description>
<name>when</name>
<tag-class>me.gacl.web.simpletag.WhenTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<description>when标签的test属性</description>
<name>test</name>
<rtexprvalue>true</rtexprvalue>
<required>true</required>
</attribute>
</tag>
<tag>
<description>otherwise标签</description>
<name>otherwise</name>
<tag-class>me.gacl.web.simpletag.OtherWiseTag</tag-class>
<body-content>scriptless</body-content>
</tag>
复制代码
4、测试:在jsp页面中导入标签库并测试when和otherwise标签
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
< %--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gaclTagLib" prefix="c" %>
< %--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录
<%@taglib uri="/WEB-INF/TagLib.tld" prefix="c"%>
--%>
<!DOCTYPE HTML>
<html>
<head>
<title>when和otherwise标签测试</title>
</head>
<body>
<c:choose>
<c:when test="${user==null}">
when标签标签体输出的内容:
<h3>用户为空</h3>
</c:when>
<c:otherwise>
用户不为空
</c:otherwise>
</c:choose>
<hr/>
<c:choose>
<c:when test="${user!=null}">
用户不为空
</c:when>
<c:otherwise>
otherwise标签标签体输出的内容:
<h3>用户为空</h3>
</c:otherwise>
</c:choose>
</body>
</html>
复制代码
运行效果如下:

1.4、开发foreach迭代标签
1、编写标签处理器类:ForEachTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* ForEach迭代标签
*/
public class ForEachTag extends SimpleTagSupport {
/**
* 存储集合
*/
private List items;
/**
* 迭代集合时使用的变量
*/
private String var;
public void setItems(List items) {
this.items = items;
}
public void setVar(String var) {
this.var = var;
}
@Override
public void doTag() throws JspException, IOException {
PageContext pageContext = (PageContext) this.getJspContext();
Iterator it = items.iterator();
while (it.hasNext()) {
//得到一个迭代出来的对象
Object object = (Object) it.next();
//将迭代出来的对象存放到pageContext对象中
pageContext.setAttribute(var, object);
//输出标签体中的内容
this.getJspBody().invoke(null);
}
}
}
复制代码
2、在WEB-INF目录下tld文件中添加对该标签的描述,如下:
复制代码
<tag>
<description>foreach标签</description>
<name>foreach</name>
<tag-class>me.gacl.web.simpletag.ForEachTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<description>foreach标签的items属性</description>
<name>items</name>
<rtexprvalue>true</rtexprvalue>
<required>true</required>
</attribute>
<attribute>
<description>foreach标签的var属性</description>
<name>var</name>
<rtexprvalue>false</rtexprvalue>
<required>true</required>
</attribute>
</tag>
复制代码
3、测试:在jsp页面中导入标签库并使用foreach标签
复制代码
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page language="java" pageEncoding="UTF-8"%>
< %--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gaclTagLib" prefix="c" %>
< %--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录
<%@taglib uri="/WEB-INF/TagLib.tld" prefix="c"%>
--%>
<!DOCTYPE HTML>
<html>
<head>
<title>foreach标签测试</title>
</head>
<%
List<String> data = new ArrayList<String>();
data.add("孤傲苍狼");
data.add("xdp");
//将集合存储到pageContext对象中
pageContext.setAttribute("data", data);
%>
<body>
< %--迭代存储在pageContext对象中的data集合 --%>
<c:foreach items="${data}" var="str">
${str}<br/>
</c:foreach>
</body>
</html>
复制代码
运行效果如下:

目前这个foreach标签的功能较弱,只能遍历list集合,下面我们改造一下,使我们的foreach标签可以遍历所有集合类型,修改后foreach标签的代码如下:
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* ForEach迭代标签
*/
public class ForEachTag extends SimpleTagSupport {
/**
* 存储数据
*/
private Object items;
/**
* 迭代集合时使用的变量
*/
private String var;
/**
* 集合,用于存储items中的数据
*/
private Collection collection;
@Override
public void doTag() throws JspException, IOException {
PageContext pageContext = (PageContext) this.getJspContext();
//迭代collection集合
Iterator it = collection.iterator();
while (it.hasNext()) {
//得到一个迭代出来的对象
Object object = (Object) it.next();
//将迭代出来的对象存放到pageContext对象中
pageContext.setAttribute(var, object);
//输出标签体中的内容
this.getJspBody().invoke(null);
}
}
public void setVar(String var) {
this.var = var;
}
public void setItems(Object items) {
if (items instanceof Collection) {
collection = (Collection) items;//list
}else if (items instanceof Map) {
Map map = (Map) items;
collection = map.entrySet();//map
}else if (items.getClass().isArray()) {
collection = new ArrayList();
//获取数组的长度
int len = Array.getLength(items);
for (int i = 0; i < len; i++) {
//获取数组元素
Object object = Array.get(items, i);
collection.add(object);
}
}
this.items = items;
}
}
复制代码
测试功能增强后的foreach标签,如下:
复制代码
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.HashSet"%>
<%@page import="java.util.Set"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page language="java" pageEncoding="UTF-8"%>
<%--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gaclTagLib" prefix="c" %>
<%--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录
<%@taglib uri="/WEB-INF/TagLib.tld" prefix="c"%>
--%>
<!DOCTYPE HTML>
<html>
<head>
<title>foreach标签测试</title>
</head>
<%
//list集合
List<String> listData = new ArrayList<String>();
listData.add("孤傲苍狼");
listData.add("xdp");
//对象数组
Integer intObjArr[] = new Integer[]{1,2,3};
//基本数据类型数组
int intArr[] = new int[]{4,5,6};
//map集合
Map<String,String> mapData = new HashMap<String,String>();
mapData.put("a", "aaaaaa");
mapData.put("b", "bbbbbb");
//将集合存储到pageContext对象中
pageContext.setAttribute("listData", listData);
pageContext.setAttribute("intObjArr", intObjArr);
pageContext.setAttribute("intArr", intArr);
pageContext.setAttribute("mapData", mapData);
%>
<body>
< %--迭代存储在pageContext对象中的list集合 --%>
<c:foreach items="${listData}" var="str">
${str}<br/>
</c:foreach>
<hr/>
< %--迭代存储在pageContext对象中的数组 --%>
<c:foreach items="${intObjArr}" var="num">
${num}<br/>
</c:foreach>
<hr/>
< %--迭代存储在pageContext对象中的数组 --%>
<c:foreach items="${intArr}" var="num">
${num}<br/>
</c:foreach>
<hr/>
< %--迭代存储在pageContext对象中的map集合 --%>
<c:foreach items="${mapData}" var="me">
${me}<br/>
</c:foreach>
</body>
</html>
复制代码
测试结果:

1.5、开发html转义标签
输出html 文本需要转义
1、编写标签处理器类:HtmlEscapeTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* html转义标签
*/
public class HtmlEscapeTag extends SimpleTagSupport {
/**
* @param message
* @return 转义html标签
*/
private String filter(String message) {
if (message == null){
return (null);
}
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case ‘<‘:
result.append("&lt;");
break;
case ‘>‘:
result.append("&gt;");
break;
case ‘&‘:
result.append("&amp;");
break;
case ‘"‘:
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
@Override
public void doTag() throws JspException, IOException {
StringWriter sw = new StringWriter();
//将标签体中的内容先输出到StringWriter流
this.getJspBody().invoke(sw);
//得到标签体中的内容
String content = sw.getBuffer().toString();
//转义标签体中的html代码
content = filter(content);
//输出转义后的content
this.getJspContext().getOut().write(content);
}
}
复制代码
2、在WEB-INF目录下tld文件中添加对该标签的描述,如下:
复制代码
<tag>
<description>HtmlEscape标签</description>
<name>htmlEscape</name>
<tag-class>me.gacl.web.simpletag.HtmlEscapeTag</tag-class>
<body-content>scriptless</body-content>
</tag>
复制代码
3、测试:在jsp页面中导入标签库并使用htmlEscape标签
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
< %--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gaclTagLib" prefix="c" %>
< %--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录
<%@taglib uri="/WEB-INF/TagLib.tld" prefix="c"%>
--%>
<!DOCTYPE HTML>
<html>
<head>
<title>html转义标签测试</title>
</head>
<body>
<c:htmlEscape>
<a href="http://www.cnblogs.com">访问博客园</a>
</c:htmlEscape>
</body>
</html>
复制代码
运行结果:

1.6、开发out输出标签
1、编写标签处理器类:OutTag.java
复制代码
package me.gacl.web.simpletag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* @author gacl
* 开发输出标签
*/
public class OutTag extends SimpleTagSupport {
/**
* 要输出的内容
*/
private String content;
/**
* 是否将内容中的html进行转义后输出
*/
private boolean escapeHtml;
public void setContent(String content) {
this.content = content;
}
public void setEscapeHtml(boolean escapeHtml) {
this.escapeHtml = escapeHtml;
}
@Override
public void doTag() throws JspException, IOException {
if (escapeHtml == true) {
//转义内容中的html代码
content = filter(content);
//输出转义后的content
this.getJspContext().getOut().write(content);
}else {
this.getJspContext().getOut().write(content);
}
}
/**
* @param message
* @return 转义html标签
*/
private String filter(String message) {
if (message == null){
return (null);
}
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case ‘<‘:
result.append("&lt;");
break;
case ‘>‘:
result.append("&gt;");
break;
case ‘&‘:
result.append("&amp;");
break;
case ‘"‘:
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}
复制代码
2、在WEB-INF目录下tld文件中添加对该标签的描述,如下:
复制代码
<tag>
<description>out标签</description>
<name>out</name>
<tag-class>me.gacl.web.simpletag.OutTag</tag-class>
<body-content>empty</body-content>
<attribute>
<description>out标签的content属性,表示要输出的内容</description>
<name>content</name>
<rtexprvalue>true</rtexprvalue>
<required>true</required>
</attribute>
<attribute>
<description>out标签的escapeHtml属性,表示是否将内容中的html进行转义后输出</description>
<name>escapeHtml</name>
<rtexprvalue>true</rtexprvalue>
<required>false</required>
</attribute>
</tag>
复制代码
3、测试:在jsp页面中导入标签库并使用out标签
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
< %--在jsp页面中导入自定义标签库 --%>
<%@taglib uri="/gaclTagLib" prefix="c" %>
< %--在jsp页面中也可以使用这种方式导入标签库,直接把uri设置成标签库的tld文件所在目录
<%@taglib uri="/WEB-INF/TagLib.tld" prefix="c"%>
--%>
<!DOCTYPE HTML>
<html>
<head>
<title>out标签测试</title>
</head>
<body>
< %--使用out标签输出content属性的内容 --%>
<c:out content="<a href=‘http://www.cnblogs.com‘>访问博客园</a>"/>
<hr/>
< %--使用out标签输出 content属性的内容,内容中的html代码会进行转义处理--%>
<c:out content="<a href=‘http://www.cnblogs.com‘>访问博客园</a>" escapeHtml="true"/>
</body>
</html>
复制代码
运行效果如下:

二、打包开发好的标签库
我们的标签库开发好之后,为了方便别人使用,可以将开发好的标签库打包成一个jar包,具体的打包步骤如下:
1、新建一个普通的java工程,例如:taglib

2、将在JavaWeb_JspTag_study_20140816这个web工程中开发好标签库的java代码拷贝到普通java工程taglib项目中,如下:

此时,我们可以看到,拷贝到taglib项目的标签代码都有错误,这是因为taglib项目中缺少了javaEE的jar包,而标签类是是基于javaEE API进行开发的,所以还需要将javaEE的jar包添加到taglib项目中。
在taglib项目中创建一个【lib】文件夹,用于存放标签类依赖的javaEE的jar包。找到tomcat服务器目录下的lib文件夹,如下图所示:

将【jsp-api.jar】和【servlet-api.jar】这两个jar包拷贝到tagib项目中的lib文件夹中,然后添加【jsp-api.jar】和【servlet-api.jar】这两个jar包的引用,如下所示:


在taglib项目中引用了【jsp-api.jar】和【servlet-api.jar】这两个jar包后,标签类中的代码就不再报错了
3、在taglib项目中添加一个【META-INF】文件夹,如下所示:

将位于WEB-INF目录下的标签库对应的tld文件拷贝到taglib项目的【META-INF】文件夹中

4、将taglib项目打包成jar包




此时就可以看到我们打包好的jar包了,如下所示:

将标签库打包成jar包之后,以后哪个web项目要使用标签库,那么就将打包好的标签库jar包添加到web项目中就可以使用标签库中的标签了。

(二十八)——JSTL标签库之核心标签
一、JSTL标签库介绍
JSTL标签库的使用是为弥补html标签的不足,规范自定义标签的使用而诞生的。使用JSLT标签的目的就是不希望在jsp页面中出现java逻辑代码
二、JSTL标签库的分类
核心标签(用得最多)
国际化标签(I18N格式化标签)
数据库标签(SQL标签,很少使用)
XML标签(几乎不用)
JSTL函数(EL函数)
三、核心标签库使用说明
JSTL的核心标签库标签共13个,使用这些标签能够完成JSP页面的基本功能,减少编码工作。
从功能上可以分为4类:表达式控制标签、流程控制标签、循环标签、URL操作标签。
(1)表达式控制标签:out标签、set标签、remove标签、catch标签。
(2)流程控制标签:if标签、choose标签、when标签、otherwise标签。
(3)循环标签:forEach标签、forTokens标签。
(4)URL操作标签:import标签、url标签、redirect标签、param标签。
在JSP页面引入核心标签库的代码为:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3.1、表达式控制标签——out标签使用总结
3.1.1、<c:out>标签的功能
<c:out>标签主要是用来输出数据对象(字符串、表达式)的内容或结果。
在使用Java脚本输出时常使用的方式为: <% out.println(“字符串”)%> 或者 <%=表达式%> ,在web开发中,为了避免暴露逻辑代码会尽量减少页面中的Java脚本,使用<c:out>标签就可以实现以上功能。
<c:out value=”字符串”>
<c:out value=”EL表达式”>
JSTL的使用是和EL表达式分不开的,EL表达式虽然可以直接将结果返回给页面,但有时得到的结果为空,<c:out>有特定的结果处理功能,EL的单独使用会降低程序的易读性,建议把EL的结果输入放入<c:out>标签中。
3.1.2、<c:out>标签的语法
<c:out>标签的使用有两种语法格式:
【语法1】:<c:out value=”要显示的数据对象” [escapeXml=”true|false”] [default=”默认值”]/>
【语法2】:<c:out value=”要显示的数据对象” [escapeXml=”true|false”]>默认值</c:out>
这两种方式没有本质的区别,只是格式上的差别。[escapeXml=”true|false”] [default=”默认值”]这些使用[]属性表示是不是必须的。
3.1.3、<c:out>标签的属性

3.1.4、<c:out>标签的使用范例
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: --表达式控制标签“out”标签的使用</title>
</head>
<body>
<h3><c:out value="下面的代码演示了c:out的使用,以及在不同属性值状态下的结果。"/></h3>
<hr/>
<ul>
< %--(1)直接输出了一个字符串。 --%>
<li>(1)<c:out value="JSTL的out标签的使用" /></li>
<li>(2)<c:out value="<a href=‘http://www.cnblogs.com/‘>点击链接到博客园</a>" /></li>
< %--escapeXml="false"表示value值中的html标签不进行转义,而是直接输出 --%>
<li>(3)<c:out value="<a href=‘http://www.cnblogs.com/‘>点击链接到博客园</a>" escapeXml="false"/></li>
< %--(4)字符串中有转义字符,但在默认情况下没有转换。 --%>
<li>(4)<c:out value="&lt未使用字符转义&gt" /></li>
< %--(5)使用了转义字符&lt和&gt分别转换成<和>符号。 --%>
<li>(5)<c:out value="&lt使用字符转义&gt" escapeXml="false"></c:out></li>
< %--(6)设定了默认值,从EL表达式${null}得到空值,所以直接输出设定的默认值。 --%>
<li>(6)<c:out value="${null}">使用了默认值</c:out></li>
< %--(7)未设定默认值,输出结果为空。 --%>
<li>(7)<c:out value="${null}"></c:out></li>
< %--(8)设定了默认值,从EL表达式${null}得到空值,所以直接输出设定的默认值。 --%>
<li>(8)<c:out value="${null}" default="默认值"/></li>
< %--(9)未设定默认值,输出结果为空。 --%>
<li>(9)<c:out value="${null}"/></li>
</ul>
</body>
</html>
复制代码
运行结果如下:

3.2、表达式控制标签——set标签使用总结
3.2.1、<c:set>标签的功能
<c:set>标签用于把某一个对象存在指定的域范围内,或者将某一个对象存储到Map或者JavaBean对象中。
3.2.2、<c:set>标签的语法
<c:set>标签的编写共有4种语法格式。
语法1:存值,把一个值放在指定的域范围内。
<c:set value=”值1” var=”name1” [scope=”page|request|session|application”]/>
含义:把一个变量名为name1值为“值1”的变量存储在指定的scope范围内。
语法2:
<c:set var=”name2” [scope=”page|request|session|application”]>
值2
</c:set>
含义:把一个变量名为name2,值为值2的变量存储在指定的scope范围内。
语法3:
<c:set value=”值3” target=”JavaBean对象” property=”属性名”/>
含义:把一个值为“值3”赋值给指定的JavaBean的属性名。相当与setter()方法。
语法4:
<c:set target=”JavaBean对象” property=”属性名”>
值4
</c:set>
含义:把一个值4赋值给指定的JavaBean的属性名。
从功能上分语法1和语法2、语法3和语法4的效果是一样的,只是把value值放置的位置不同,至于使用那个根据个人的喜爱,语法1和语法2是向scope范围内存储一个值,语法3和语法4是给指定的JavaBean赋值。
3.2.3、<c:set>标签的属性

3.2.4、<c:set>标签的使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
< %--使用JSP的指令元素指定要使用的JavaBean --%>
<jsp:useBean id="person" class="javabean.Person"/>
< %--负责实例化Bean,id指定实例化后的对象名,可以通过${person}得到person在内存中的值
(或者使用person.toString()方法)。 --%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: --表达式控制标签“set”标签的使用</title>
</head>
<body>
<h3>代码给出了给指定scope范围赋值的示例。</h3>
<ul>
< %--通过<c:set>标签将data1的值放入page范围中。--%>
<li>把一个值放入page域中:<c:set var="data1" value="xdp" scope="page"/></li>
< %--使用EL表达式从pageScope得到data1的值。--%>
<li>从page域中得到值:${pageScope.data1}</li>
< %--通过<c:set>标签将data2的值放入request范围中。--%>
<li>把一个值放入request域中:<c:set var="data2" value="gacl" scope="request"/></li>
< %--使用EL表达式从requestScope得到data2的值。--%>
<li>从request域中得到值:${requestScope.data2}</li>
< %--通过<c:set>标签将值name1的值放入session范围中。--%>
<li>把一个值放入session域中。<c:set value="孤傲苍狼" var="name1" scope="session"></c:set></li>
< %--使用EL表达式从sessionScope得到name1的值。--%>
<li>从session域中得到值:${sessionScope.name1} </li>
< %--把name2放入application范围中。 --%>
<li>把一个值放入application域中。<c:set var="name2" scope="application">白虎神皇</c:set></li>
< %--使用EL表达式从application范围中取值,用<c:out>标签输出使得页面规范化。 --%>
<li>使用out标签和EL表达式嵌套从application域中得到值:
<c:out value="${applicationScope.name2}">未得到name的值</c:out>
</li>
< %--不指定范围使用EL自动查找得到值 --%>
<li>未指定scope的范围,会从不同的范围内查找得到相应的值:${data1}、${data2}、${name1}、${name2}</li>
</ul>
<hr/>
<h3>使用Java脚本实现以上功能</h3>
<ul>
<li>把一个值放入page域中。<%pageContext.setAttribute("data1","xdp");%></li>
<li>从page域中得到值:<%out.println(pageContext.getAttribute("data1"));%></li>
<li>把一个值放入request域中。<%request.setAttribute("data2","gacl");%></li>
<li>从request域中得到值:<%out.println(request.getAttribute("data2"));%></li>
<li>把一个值放入session域中。<%session.setAttribute("name1","孤傲苍狼");%></li>
<li>从session中域得到值:<%out.println(session.getAttribute("name1"));%></li>
< %--out.println()方法与<%=%>表达式输出功能一样
但使用表达式输出(<%=%>)明显要比使用out.println()输出更好。
--%>
<li><%=session.getAttribute("name1") %></li>
<li>把另一个值放入application域中。<%application.setAttribute("name2","白虎神皇");%></li>
<li> 从application域中得到值:<%out.println(application.getAttribute("name2"));%></li>
<li><%=application.getAttribute("name2")%></li>
<li>未指定scope的范围,会从不同的范围内查找得到相应的值:
<%=pageContext.findAttribute("data1")%>、
<%=pageContext.findAttribute("data2")%>、
<%=pageContext.findAttribute("name1")%>、
<%=pageContext.findAttribute("name2")%>
</li>
</ul>
<hr/>
<h3>操作JavaBean,设置JavaBean的属性值</h3>
< %--设置JavaBean的属性值,等同与setter方法,Target指向实例化后的对象,property指向要插入值的参数名。
注意:使用target时一定要指向实例化后的JavaBean对象,也就是要跟<jsp:useBean>配套使用,
也可以java脚本实例化,但这就失去了是用标签的本质意义。
使用Java脚本实例化:
< %@page import="javabean.Person"%
<% Person person=new Person(); %>
--%>
<c:set target="${person}" property="name">孤傲苍狼</c:set>
<c:set target="${person}" property="age">25</c:set>
<c:set target="${person}" property="sex">男</c:set>
<c:set target="${person}" property="home">中国</c:set>
<ul>
<li>使用的目标对象为:${person}</li>
<li>从Bean中获得的name值为:<c:out value="${person.name}"></c:out></li>
<li>从Bean中获得的age值为:<c:out value="${person.age}"></c:out></li>
<li>从Bean中获得的sex值为:<c:out value="${person.sex}"></c:out></li>
<li>从Bean中获得的home值为:<c:out value="${person.home}"></c:out></li>
</ul>
<hr/>
<h3>操作Map</h3>
<%
Map map = new HashMap();
request.setAttribute("map",map);
%>
< %--将data对象的值存储到map集合中 --%>
<c:set property="data" value="gacl" target="${map}"/>
${map.data}
</body>
</html>
复制代码
jsp页面中使用到的javabean.Person类的代码如下:
复制代码
package javabean;
/**
* 项目名称:JSTLStudy
* 类名称:Person
* 类描述:一个只有getter和setter方法的JavaBean或者说一个pojo(简单的Java对象(Plain Old Java Objects))类,
* 作为一个vo(数据传输对象)。定义了四个变量age、name、sex和home。
*/
public class Person {
private String age;
private String home;
private String name;
private String sex;
public String getAge() {
return age;
}
public String getHome() {
return home;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public void setAge(String age) {
this.age = age;
}
public void setHome(String home) {
this.home = home;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
}
复制代码
运行结果如下:

3.3、表达式控制标签——remove标签使用总结
3.3.1、<c:remove>标签的功能
<c:remove>标签主要用来从指定的JSP范围内移除指定的变量。
3.3.2、<c:remove>标签的语法
<c:remove var=”变量名” [scope=”page|request|session|application”]/>
其中var属性是必须的,scope可以以省略。
3.3.3、<c:remove>标签的使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: --表达式控制标签“remove”标签的使用</title>
</head>
<body>
<ul>
<c:set var="name" scope="session">孤傲苍狼</c:set>
<c:set var="age" scope="session">25</c:set>
<li><c:out value="${sessionScope.name}"></c:out></li>
<li><c:out value="${sessionScope.age}"></c:out></li>
< %--使用remove标签移除age变量 --%>
<c:remove var="age" />
<li><c:out value="${sessionScope.name}"></c:out></li>
<li><c:out value="${sessionScope.age}"></c:out></li>
</ul>
</body>
</html>
复制代码
运行结果如下:

3.4、表达式控制标签——catch标签使用总结
3.4.1、<c:catch>标签的功能
<c:catch>标签用于捕获嵌套在标签体中的内容抛出的异常。
3.4.2、<c:catch>标签的语法
其语法格式如下:<c:catch [var="varName"]>容易产生异常的代码</c:catch>
var属性用于标识<c:catch>标签捕获的异常对象,它将保存在page这个Web域中。
3.4.3、<c:catch>标签的使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: --表达式控制标签“catch”标签实例</title>
</head>
<body>
<h4>catch标签实例</h4>
<hr>
< %--把容易产生异常的代码放在<c:catch></c:catch>中,
自定义一个变量errorInfo用于存储异常信息 --%>
<c:catch var="errorInfo">
< %--实现了一段异常代码,向一个不存在的JavaBean中插入一个值--%>
<c:set target="person" property="hao"></c:set>
</c:catch>
< %--用EL表达式得到errorInfo的值,并使用<c:out>标签输出 --%>
异常:<c:out value="${errorInfo}" /><br />
异常 errorInfo.getMessage:<c:out value="${errorInfo.message}" /><br />
异常 errorInfo.getCause:<c:out value="${errorInfo.cause}" /><br />
异常 errorInfo.getStackTrace:<c:out value="${errorInfo.stackTrace}" />
</body>
</html>
复制代码
运行结果如下:

3.5、流程控制标签——if标签使用总结
3.5.1、<c:if>标签的功能
<c:if>标签和程序中的if语句作用相同,用来实现条件控制。
3.5.2、<c:if>标签的语法
【语法1】:没有标签体内容(body)
<c:if test="testCondition" var="varName" [scope="{page|request|session|application}"]/>
【语法2】:有标签体内容
<c:if test="testCondition" [var="varName"] [scope="{page|request|session|application}"]>
标签体内容
</c:if>
【参数说明】:
(1)test属性用于存放判断的条件,一般使用EL表达式来编写。
(2)var属性用来存放判断的结果,类型为true或false。
(3)scopes属性用来指定var属性存放的范围。
3.5.3、<c:if>标签的属性

3.5.4、<c:if>标签的使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: --流程控制标签 if标签示例</title>
</head>
<body>
<h4>if标签示例</h4>
<hr>
<form action="JSTL_if_tag.jsp" method="post">
<input type="text" name="uname" value="${param.uname}">
<input type="submit" value="登录">
</form>
< %--使用if标签进行判断并把检验后的结果赋给adminchock,存储在默认的page范围中。 --%>
<c:if test="${param.uname==‘admin‘}" var="adminchock">
< %--可以把adminchock的属性范围设置为session,这样就可以在其他的页面中得到adminchock的值,
使用<c:if text=”${adminchock}”><c:if>判断,实现不同的权限。 --%>
<c:out value="管理员欢迎您!"/>
</c:if>
< %--使用EL表达式得到adminchock的值,如果输入的用户名为admin将显示true。 --%>
${adminchock}
</body>
</html>
复制代码
运行结果如下:

3.6、流程控制标签——choose标签、when标签、otherwise标签配合使用讲解
3.6.1、<c:choose>、<c:when>和<c:otherwise>标签的功能
<c:choose>、<c:when>和<c:otherwise>这3个标签通常情况下是一起使用的,<c:choose>标签作为<c:when>和<c:otherwise>标签的父标签来使用。
使用<c:choose>,<c:when>和<c:otherwise>三个标签,可以构造类似 “if-else if-else” 的复杂条件判断结构。
3.6.2、语法
 <c:choose>
<c:when test="条件1">
//业务逻辑1
<c:when>
 <c:when test="条件2">
//业务逻辑2
<c:when>
 <c:when test="条件n">
//业务逻辑n
<c:when>
<c:otherwise>
//业务逻辑
 </c:otherwise>
 </c:choose>
3.6.3、使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: -- choose及其嵌套标签标签示例</title>
</head>
<body>
<h4>choose及其嵌套标签示例</h4>
<hr/>
< %--通过set标签设定score的值为85 --%>
<c:set var="score" value="85"/>
<c:choose>
< %--使用<c:when>进行条件判断。
如果大于等于90,输出“您的成绩为优秀”;
如果大于等于70小于90,输出“您的成绩为良好”;
大于等于60小于70,输出“您的成绩为及格”;
其他(otherwise)输出“对不起,您没能通过考试”。
--%>
<c:when test="${score>=90}">
你的成绩为优秀!
</c:when>
<c:when test="${score>70 && score<90}">
您的成绩为良好!
</c:when>
<c:when test="${score>60 && score<70}">
您的成绩为及格
</c:when>
<c:otherwise>
对不起,您没有通过考试!
</c:otherwise>
</c:choose>
</body>
</html>
复制代码
运行结果如下:

3.7、循环标签——forEach标签使用总结
3.7.1、<c:forEach>标签的功能
该标签根据循环条件遍历集合(Collection)中的元素。
3.7.2、<c:forEach>标签的语法
 <c:forEach
var=”name”
items=”Collection”
varStatus=”StatusName”
begin=”begin”
end=”end”
step=”step”>
本体内容
</c:forEach>
【参数解析】:
(1)var设定变量名用于存储从集合中取出元素。
(2)items指定要遍历的集合。
(3)varStatus设定变量名,该变量用于存放集合中元素的信息。
(4)begin、end用于指定遍历的起始位置和终止位置(可选)。
(5)step指定循环的步长。
3.7.3、<c:forEach>标签属性
循环标签属性说明
属性名称 是否支持EL表达式 属性类型 是否必须 默认值
var NO String 是 无
items YES Arrays
Collection
Iterator
Enumeration
Map
String []args 是 无
begin YES int 否 0
end YES int 否 集合中最后一个元素
step YES int 否 1
varStatus NO String 否 无
  










  

其中varStatus有4个状态属性,如下表所示:
varStatus的4个状态
属性名 类型 说明
index int 当前循环的索引值
count int 循环的次数
frist boolean 是否为第一个位置
last boolean 是否为最后一个位置





3.7.4、<c:forEach>使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page import="java.util.ArrayList"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: -- forEach标签实例</title>
</head>
<body>
<h4><c:out value="forEach实例"/></h4>
<%
List<String>list = new ArrayList<String>();
list.add(0, "贝贝");
list.add(1, "晶晶");
list.add(2, "欢欢");
list.add(3, "莹莹");
list.add(4, "妮妮");
request.setAttribute("list", list);
%>
<B><c:out value="不指定begin和end的迭代:" /></B><br>
< %--不使用begin和end的迭代,从集合的第一个元素开始,遍历到最后一个元素。 --%>
<c:forEach var="fuwa" items="${list}">
&nbsp;<c:out value="${fuwa}"/><br/>
</c:forEach>
<B><c:out value="指定begin和end的迭代:" /></B><br>
< %--指定begin的值为1、end的值为3、step的值为2,
从第二个开始首先得到晶晶,每两个遍历一次,
则下一个显示的结果为莹莹,end为3则遍历结束。 --%>
<c:forEach var="fuwa" items="${list}" begin="1" end="3" step="2">
&nbsp;<c:out value="${fuwa}"/><br/>
</c:forEach>
<B><c:out value="输出整个迭代的信息:" /></B><br>
< %--指定varStatus的属性名为s,并取出存储的状态信息 --%>
<c:forEach var="fuwa"
items="${list}"
begin="3"
end="4"
varStatus="s"
step="1">
&nbsp;<c:out value="${fuwa}" />的四种属性:<br>
&nbsp;&nbsp;&nbsp;&nbsp;所在位置,即索引:<c:out value="${s.index}" /><br>
&nbsp;&nbsp;&nbsp;&nbsp;总共已迭代的次数:<c:out value="${s.count}" /><br>
&nbsp;&nbsp;&nbsp;&nbsp;是否为第一个位置:<c:out value="${s.first}" /><br>
&nbsp;&nbsp;&nbsp;&nbsp;是否为最后一个位置:<c:out value="${s.last}" /><br>
</c:forEach>
</body>
</html>
复制代码
运行结果如下:

3.8、循环标签——forTokens标签使用总结
3.8.1、<c:forTokens>标签的功能
该标签用于浏览字符串,并根据指定的字符将字符串截取。
3.8.2、<c:forTokens>标签的语法
语法:
<c:forTokens items=”strigOfTokens”
delims=”delimiters”
[var=”name”
begin=”begin”
end=”end”
step=”len”
varStatus=”statusName”] >
本体内容
</c:forTokens>
【参数说明】
(1)items指定被迭代的字符串。
(2)delims指定使用的分隔符。
(3)var指定用来存放遍历到的成员。
(4)begin指定遍历的开始位置(int型从取值0开始)。
(5)end指定遍历结束的位置(int型,默认集合中最后一个元素)。
(6)step遍历的步长(大于0的整型)。
(7)varStatus存放遍历到的成员的状态信息。
3.8.3、<c:forTokens>使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: -- forTokens标签实例</title>
</head>
<body>
<h4><c:out value="forToken实例"/></h4>
<hr/>
< %--提示:分隔符的作用是根据标识,截取字符串。
如果未设定分隔符或在字符串中没有找到分隔付,将把整个元素作为一个元素截取。
在实际应用中用于在除去某些符号在页面中显示。 --%>
<c:forTokens var="str" items="北、京、欢、迎、您" delims="、">
<c:out value="${str}"></c:out><br/>
</c:forTokens>
<br/>
<c:forTokens items="123-4567-8854" delims="-" var="t">
<c:out value="${t}"></c:out><br/>
</c:forTokens>
<br/>
<c:forTokens items="1*2*3*4*5*6*7"
delims="*"
begin="1"
end="3"
var="n"
varStatus="s">
&nbsp;<c:out value="${n}" />的四种属性:<br>
&nbsp;&nbsp;&nbsp;&nbsp;所在位置,即索引:<c:out value="${s.index}" /><br>
&nbsp;&nbsp;&nbsp;&nbsp;总共已迭代的次数:<c:out value="${s.count}" /><br>
&nbsp;&nbsp;&nbsp;&nbsp;是否为第一个位置:<c:out value="${s.first}" /><br>
&nbsp;&nbsp;&nbsp;&nbsp;是否为最后一个位置:<c:out value="${s.last}" /><br>
</c:forTokens>
</body>
</html>
复制代码
运行结果如下:

3.9、URL操作标签——import标签使用讲解
3.9.1、<c:import>标签的功能
该标签可以把其他静态或动态文件包含到本JSP页面,与<jsp:include>的区别为:<jsp:include>只能包含同一个web应用中的文件。而<c:import>可以包含其他web应用中的文件,甚至是网络上的资源。
3.9.2、<c:import>标签的语法
【语法1】:
<c:import
url=”url”
[context=”context”]
[value=”value”]
[scope=”page|request|session|application”]
[charEncoding=”encoding”]/>
【语法2】:
<c:import
url=”url”
varReader=”name”
[context=”context”]
[charEncoding=”encoding”]/>
【参数说明】:
(1)URL为资源的路径,当引用的资源不存在时系统会抛出异常,因此该语句应该放在<c:catch></c:catch>语句块中捕获。
(2)引用资源有两种方式:绝对路径和相对路径。
使用绝对路径的示例如下:<c:import url=”http://www.baidu.com”>
使用相对路径的示例如下:<c:import url=”aa.txt”>,aa.txt放在同一文件目录。
(3)如果以“/”开头表示应用的根目录下。例如:tomcat应用程序的根目录文件夹为webapps。导入webapps下的文件bb.txt的编写方式为:<c:import url=”/bb.txt”>
如果访问webapps管理文件夹中其他web应用就要用context属性。
(4)context属性用于在访问其他web应用的文件时,指定根目录。例如,访问root下的index.jsp的实现代码为:<c:import url=”/index.jsp” context=”/root”>
等同于webapps/root/index.jsp
(5)var、scope、charEncoding、varReader是可选属性。
3.9.3、<c:import>标签的使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: -- import标签实例</title>
</head>
<body>
<h4><c:out value="import实例"/></h4>
<hr/>
<h4><c:out value="绝对路径引用的实例" /></h4>
< %--使用绝对路径导入百度首页,
导入时使用<c:catch></c:catch>捕获异常。
--%>
<c:catch var="error1">
<c:import url="http://wwww.baidu.com" charEncoding="utf-8"/>
</c:catch>
${error1}
<hr/>
<h4>
<c:out value="相对路径引用本应用中的文件" />
</h4>
< %--使用相对路径导入同一文件夹下的“JSTL的import标签使用说明”文件,
接收的字符编码格式使用charEncoding设置为utf-8。 --%>
<c:catch var="error2">
<c:import url="JSTL的import标签使用说明" charEncoding="utf-8"/>
</c:catch>
${error2}
<hr/>
<h4><c:out value="使用字符串输出相对路径引用的实例,并保存在session范围内" /></h4>
< %--导入“JSTL的import标签使用说明.txt”,
使用var定义的变量接收要导入的文件,并存储在session中,
如果在其他页面同样也要导入该文件,只须使用<c:out>输出“JSTL的import标签使用说明.txt”的值即可。
--%>
<c:catch var="error3">
<c:import
var="myurl"
url="JSTL的import标签使用说明"
scope="session"
charEncoding="utf-8"/>
<c:out value="${myurl}"></c:out>
<hr/>
<c:out value="${myurl}" />
</c:catch>
${error3}
</body>
</html>
复制代码
3.10、URL操作标签——url标签使用总结
3.10.1、<c:url>标签的功能
<c:url>标签用于在JSP页面中构造一个URL地址,其主要目的是实现URL重写。
3.10.2、<c:url>标签的语法
【语法1】:指定一个url不做修改,可以选择把该url存储在JSP不同的范围中。
<c:url
value=”value”
[var=”name”]
[scope=”page|request|session|application”]
[context=”context”]/>
【语法2】:配合 <c:param>标签给url加上指定参数及参数值,可以选择以name存储该url。
<c:url
value=”value”
[var=”name”]
[scope=”page|request|session|application”]
[context=”context”]>
<c:param name=”参数名” value=”值”>
</c:url>
3.10.3、<c:url>标签的主要属性

3.10.4、<c:url>标签使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: -- url标签实例</title>
</head>
<body>
<c:out value="url标签使用"></c:out>
<h4>使用url标签生成一个动态的url,并把值存入session中.</h4>
<hr/>
<c:url value="http://www.baidu.com" var="url" scope="session">
</c:url>
<a href="${url}">百度首页(不带参数)</a>
<hr/>
<h4>
配合 &lt;c:param&gt;标签给url加上指定参数及参数值,生成一个动态的url然后存储到paramUrl变量中
</h4>
<c:url value="http://www.baidu.com" var="paramUrl">
<c:param name="userName" value="孤傲苍狼"/>
<c:param name="pwd">123456</c:param>
</c:url>
<a href="${paramUrl}">百度首页(带参数)</a>
</body>
</html>
复制代码
3.11、URL操作标签——redirect标签使用总结
3.11.1、<c:redirect>标签的功能
该标签用来实现请求的重定向。同时可以配合使用<c:param>标签在url中加入指定的参数。
3.11.2、<c:redirect>标签的语法
【语法1】:
<c:redirect url=”url” [context=”context”]/>
【语法2】:
<c:redirect url=”url”[context=”context”]>
<c:param name=”name1” value=”value1”>
</c:redirect>
【参数说明】:
(1)url指定重定向页面的地址,可以是一个string类型的绝对地址或相对地址。
(2)context用于导入其他web应用中的页面。
3.11.3、<c:redirect>标签的属性

3.11.4、<c:redirect>标签使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
<title>JSTL: -- redirect标签实例</title>
</head>
<body>
<c:redirect url="http://www.baidu.com">
< %--在重定向时使用<c:param>标签为URL添加了两个参数:uname=GACL和password=123 --%>
<c:param name="uname">GACL</c:param>
<c:param name="password">123</c:param>
</c:redirect>
</body>
</html>
复制代码
3.12、<c:param>标签使用总结
在JSP页面进行URL的相关操作时,经常要在URL地址后面附加一些参数。<c:param>标签可以嵌套在<c:import>、<c:url>或<c:redirect>标签内,为这些标签所使用的URL地址附加参数。
<c:param>标签在为一个URL地址附加参数时,将自动对参数值进行URL编码,例如,如果传递的参数值为“中国”, 则将其转换为“%d6%d0%b9%fa”后再附加到URL地址后面,这也就是使用<c:param>标签的最大好处。
示例1:与<c:url>标签嵌套使用
<c:url value="http://www.baidu.com" var="paramUrl">
<c:param name="userName" value="孤傲苍狼"/>
<c:param name="pwd">123456</c:param>
</c:url>
<a href="${paramUrl}">百度首页(带参数)</a>
示例2:与<c:redirect>标签嵌套使用
<c:redirect url="http://www.baidu.com">
< %--在重定向时使用<c:param>标签为URL添加了两个参数:uname=GACL和password=123 --%>
<c:param name="uname">GACL</c:param>
<c:param name="password">123</c:param>
</c:redirect>
关于JSTL核心标签库中的标签掌握以上的那些标签基本上就可以应付开发了。

(二十九)——EL表达式
一、EL表达式简介
EL 全名为Expression Language。EL主要作用:
1、获取数据
EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的web域 中检索java对象、获取数据。(某个web域 中的对象,访问javabean的属性、访问list集合、访问map集合、访问数组)
2、执行运算
利用EL表达式可以在JSP页面中执行一些基本的关系运算、逻辑运算和算术运算,以在JSP页面中完成一些简单的逻辑运算。${user==null}
3、获取web开发常用对象
EL 表达式定义了一些隐式对象,利用这些隐式对象,web开发人员可以很轻松获得对web常用对象的引用,从而获得这些对象中的数据。
4、调用Java方法
EL表达式允许用户开发自定义EL函数,以在JSP页面中通过EL表达式调用Java类的方法。
1.1、获取数据
使用EL表达式获取数据语法:"${标识符}"
EL表达式语句在执行时,会调用pageContext.findAttribute方法,用标识符为关键字,分别从page、request、session、application四个域中查找相应的对象,找到则返回相应对象,找不到则返回”” (注意,不是null,而是空字符串)。
EL表达式可以很轻松获取JavaBean的属性,或获取数组、Collection、Map类型集合的数据
el表达式获取数据范例:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@page import="me.gacl.domain.Person"%>
<%@page import="me.gacl.domain.Address"%>
<!DOCTYPE HTML>
<html>
<head>
<title>el表达式获取数据</title>
</head>
<body>
<%
request.setAttribute("name","孤傲苍狼");
%>
< %--${name}等同于pageContext.findAttribute("name") --%>
使用EL表达式获取数据:${name}
<hr>
<!-- 在jsp页面中,使用el表达式可以获取bean的属性 -->
<%
Person p = new Person();
p.setAge(12);
request.setAttribute("person",p);
%>
使用el表达式可以获取bean的属性:${person.age}
<hr>
<!-- 在jsp页面中,使用el表达式可以获取bean中的。。。。。。。。。的属性 -->
<%
Person person = new Person();
Address address = new Address();
person.setAddress(address);
request.setAttribute("person",person);
%>
${person.address.name}
<hr>
<!-- 在jsp页面中,使用el表达式获取list集合中指定位置的数据 -->
<%
Person p1 = new Person();
p1.setName("孤傲苍狼");
Person p2 = new Person();
p2.setName("白虎神皇");
List<Person> list = new ArrayList<Person>();
list.add(p1);
list.add(p2);
request.setAttribute("list",list);
%>
<!-- 取list指定位置的数据 -->
${list[1].name}
<!-- 迭代List集合 -->
<c:forEach var="person" items="${list}">
${person.name}
</c:forEach>
<hr>
<!-- 在jsp页面中,使用el表达式获取map集合的数据 -->
<%
Map<String,String> map = new LinkedHashMap<String,String>();
map.put("a","aaaaxxx");
map.put("b","bbbb");
map.put("c","cccc");
map.put("1","aaaa1111");
request.setAttribute("map",map);
%>
<!-- 根据关键字取map集合的数据 -->
${map.c}
${map["1"]}
<hr>
<!-- 迭代Map集合 -->
<c:forEach var="me" items="${map}">
${me.key}=${me.value}<br/>
</c:forEach>
<hr>
</body>
</html>
复制代码
运行效果如下:
s
1.2、执行运算
语法:${运算表达式},EL表达式支持如下运算符:
1、关系运算符

2、逻辑运算符:

3、empty运算符:检查对象是否为null(空)
4、二元表达式:${user!=null?user.name :""}
5、[ ] 和 . 号运算符
使用EL表达式执行运算范例:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@page import="me.gacl.domain.User"%>
<!DOCTYPE HTML>
<html>
<head>
<title>el表达式运算符</title>
</head>
<body>
<h3>el表达式进行四则运算:</h3>
加法运算:${365+24}<br/>
减法运算:${365-24}<br/>
乘法运算:${365*24}<br/>
除法运算:${365/24}<br/>
<h3>el表达式进行关系运算:</h3>
< %--${user == null}和 ${user eq null}两种写法等价--%>
${user == null}<br/>
${user eq null}<br/>
<h3>el表达式使用empty运算符检查对象是否为null(空)</h3>
<%
List<String> list = new ArrayList<String>();
list.add("gacl");
list.add("xdp");
request.setAttribute("list",list);
%>
< %--使用empty运算符检查对象是否为null(空) --%>
<c:if test="${!empty(list)}">
<c:forEach var="str" items="${list}">
${str}<br/>
</c:forEach>
</c:if>
<br/>
<%
List<String> emptyList = null;
%>
< %--使用empty运算符检查对象是否为null(空) --%>
<c:if test="${empty(emptyList)}">
对不起,没有您想看的数据
</c:if>
<br/>
<h3>EL表达式中使用二元表达式</h3>
<%
session.setAttribute("user",new User("孤傲苍狼"));
%>
${user==null? "对不起,您没有登陆 " : user.username}
<br/>
<h3>EL表达式数据回显</h3>
<%
User user = new User();
user.setGender("male");
//数据回显
request.setAttribute("user",user);
%>
<input type="radio" name="gender" value="male" ${user.gender==‘male‘?‘checked‘:‘‘}>男
<input type="radio" name="gender" value="female" ${user.gender==‘female‘?‘checked‘:‘‘}> 女
<br/>65 </body>
</html>
复制代码
运行结果如下:

1.3、获得web开发常用对象
EL表达式语言中定义了11个隐含对象,使用这些隐含对象可以很方便地获取web开发中的一些常见对象,并读取这些对象的数据。
语法:${隐式对象名称}:获得对象的引用
隐含对象名称:
1.pageContex : 对应于JSP页面中的pageContext对象(注意:取的是pageContext对象。)
2.pageScope : 代表page域中用于保存属性的Map对象
3.requestScope : 代表request域中用于保存属性的Map对象
4.sessionScope : 代表session域中用于保存属性的Map对象
5.applicationScope : 代表application域中用于保存属性的Map对象
6.param : 表示一个保存了所有请求参数的Map对象
7.paramValues : 表示一个保存了所有请求参数的Map对象,它对于某个请求参数,返回的是一个string[]
8.header : 表示一个保存了所有http请求头字段的Map对象,注意:如果头里面有“-” ,例Accept-Encoding,则要header[“Accept-Encoding”]
9.headerValues : 表示一个保存了所有http请求头字段的Map对象,它对于某个请求参数,返回的是一个string[]数组。注意:如果头里面有“-” ,例Accept-Encoding,则要headerValues[“Accept-Encoding”]
10.cookie : 表示一个保存了所有cookie的Map对象
11.initParam : 表示一个保存了所有web应用初始化参数的map对象
测试EL表达式中的11个隐式对象:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>el隐式对象</title>
</head>
<body>
<br/>---------------1、pageContext对象:获取JSP页面中的pageContext对象------------------------<br/>
${pageContext}
<br/>---------------2、pageScope对象:从page域(pageScope)中查找数据------------------------<br/>
<%
pageContext.setAttribute("name","孤傲苍狼"); //map
%>
${pageScope.name}
<br/>---------------3、requestScope对象:从request域(requestScope)中获取数据------------------------<br/>
<%
request.setAttribute("name","白虎神皇"); //map
%>
${requestScope.name}
<br/>---------------4、sessionScope对象:从session域(sessionScope)中获取数据------------------------<br/>
<%
session.setAttribute("user","xdp"); //map
%>
${sessionScope.user}
<br/>---------------5、applicationScope对象:从application域(applicationScope)中获取数据------------------------<br/>
<%
application.setAttribute("user","gacl"); //map
%>
${applicationScope.user}
<br/>--------------6、param对象:获得用于保存请求参数map,并从map中获取数据------------------------<br/>
<!-- http://localhost:8080/JavaWeb_EL_Study_20140826/ELDemo03.jsp?name=aaa -->
${param.name}
<!-- 此表达式会经常用在数据回显上 -->
<form action="${pageContext.request.contextPath}/servlet/RegisterServlet" method="post">
<input type="text" name="username" value="${param.username}">
<input type="submit" value="注册">
</form>
<br/>--------------7、paramValues对象:paramValues获得请求参数 //map{"",String[]}------------------------<br/>
<!-- http://localhost:8080/JavaWeb_EL_Study_20140826/ELDemo03.jsp?like=aaa&like=bbb -->
${paramValues.like[0]}
${paramValues.like[1]}
<br/>--------------8、header对象:header获得请求头------------------------<br/>
${header.Accept}<br/>
< %--${header.Accept-Encoding} 这样写会报错
测试headerValues时,如果头里面有“-” ,例Accept-Encoding,则要headerValues[“Accept-Encoding”]
--%>
${header["Accept-Encoding"]}
<br/>--------------9、headerValues对象:headerValues获得请求头的值------------------------<br/>
< %--headerValues表示一个保存了所有http请求头字段的Map对象,它对于某个请求参数,返回的是一个string[]数组
例如:headerValues.Accept返回的是一个string[]数组 ,headerValues.Accept[0]取出数组中的第一个值
--%>
${headerValues.Accept[0]}<br/>
< %--${headerValues.Accept-Encoding} 这样写会报错
测试headerValues时,如果头里面有“-” ,例Accept-Encoding,则要headerValues[“Accept-Encoding”]
headerValues["Accept-Encoding"]返回的是一个string[]数组,headerValues["Accept-Encoding"][0]取出数组中的第一个值
--%>
${headerValues["Accept-Encoding"][0]}
<br/>--------------10、cookie对象:cookie对象获取客户机提交的cookie------------------------<br/>
<!-- 从cookie隐式对象中根据名称获取到的是cookie对象,要想获取值,还需要.value -->
${cookie.JSESSIONID.value} //保存所有cookie的map
<br/>--------------11、initParam对象:initParam对象获取在web.xml文件中配置的初始化参数------------------------<br/>
< %--
<!-- web.xml文件中配置初始化参数 -->
<context-param>
<param-name>xxx</param-name>
<param-value>yyyy</param-value>
</context-param>
<context-param>
<param-name>root</param-name>
<param-value>/JavaWeb_EL_Study_20140826</param-value>
</context-param>
--%>
< %--获取servletContext中用于保存初始化参数的map --%>
${initParam.xxx}<br/>
${initParam.root}
</body>
</html>
复制代码
RegisterServlet的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RegisterServlet extends HttpServlet {
/*
* 处理用户注册的方法
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1、接收参数
String userName = request.getParameter("username");
/**
* 2、直接跳转回/ELDemo03.jsp页面,没有使用request.setAttribute("userName", userName)将userName存储到request对象中
* 但是在ELDemo03.jsp页面中可以使用${param.username}获取到request对象中的username参数的值
*/
request.getRequestDispatcher("/ELDemo03.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
测试结果如下:

注意:
测试header和headerValues时,如果头里面有“-” ,例Accept-Encoding,则要header["Accept-Encoding"]、headerValues["Accept-Encoding"]
测试cookie时,例${cookie.key}取的是cookie对象,如访问cookie的名称和值,须${cookie.key.name}或${cookie.key.value}
1.4、使用EL调用Java方法
EL表达式语法允许开发人员开发自定义函数,以调用Java类的方法。语法:${prefix:method(params)}
在EL表达式中调用的只能是Java类的静态方法,这个Java类的静态方法需要在TLD文件中描述,才可以被EL表达式调用。
EL自定义函数用于扩展EL表达式的功能,可以让EL表达式完成普通Java程序代码所能完成的功能。
1.5、EL Function开发步骤
一般来说, EL自定义函数开发与应用包括以下三个步骤:
1、编写一个Java类的静态方法
2、编写标签库描述符(tld)文件,在tld文件中描述自定义函数。
3、在JSP页面中导入和使用自定义函数
示例:开发对html标签进行转义的el function
1、编写html转义处理工具类,工具类中添加对html标签进行转义的静态处理方法,如下:
复制代码
package me.gacl.util;
/**
* @ClassName: HtmlFilter
* @Description: html转义处理工具类
* @author: 孤傲苍狼
* @date: 2014-8-27 上午12:09:15
*
*/
public class HtmlFilter {
/**
* @Method: filter
* @Description: 静态方法,html标签转义处理
* @Anthor:孤傲苍狼
*
* @param message 要转义的内容
* @return 转义后的内容
*/
public static String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case ‘<‘:
result.append("&lt;");
break;
case ‘>‘:
result.append("&gt;");
break;
case ‘&‘:
result.append("&amp;");
break;
case ‘"‘:
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}
复制代码
2、在WEB-INF目录下编写标签库描述符(tld)文件,在tld文件中描述自定义函数

elFunction.tld的代码如下:
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" 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 web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<short-name>EL Function</short-name>
<!--
自定义EL函数库的引用URI,
在JSP页面中可以这样引用:<%@taglib uri="/ELFunction" prefix="fn" %>
-->
<uri>/ELFunction</uri>
<!--<function>元素用于描述一个EL自定义函数 -->
<function>
<description>html标签转义处理方法</description>
<!--<name>子元素用于指定EL自定义函数的名称-->
<name>filter</name>
<!--<function-class>子元素用于指定完整的Java类名-->
<function-class>me.gacl.util.HtmlFilter</function-class>
<!--<function-signature>子元素用于指定Java类中的静态方法的签名,
方法签名必须指明方法的返回值类型及各个参数的类型,各个参数之间用逗号分隔。-->
<function-signature>java.lang.String filter(java.lang.String)</function-signature>
</function>
</taglib>
复制代码
3、在JSP页面中导入和使用自定义函数
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--引入EL自定义函数库 --%>
<%@taglib uri="/ELFunction" prefix="fn" %>
<!DOCTYPE HTML>
<html>
<head>
<title>使用EL调用Java方法</title>
</head>
<body>
< %--使用EL调用filter方法--%>
${fn:filter("<a href=‘‘>点点</a>")}
</body>
</html>
复制代码
运行结果如下:

1.6、开发EL Function注意事项
编写完标签库描述文件后,需要将它放置到<web应用>\WEB-INF目录中或WEB-INF目录下的除了classes和lib目录之外的任意子目录中。
TLD文件中的<uri> 元素用指定该TLD文件的URI,在JSP文件中需要通过这个URI来引入该标签库描述文件。
<function>元素用于描述一个EL自定义函数,其中:
<name>子元素用于指定EL自定义函数的名称。
<function-class>子元素用于指定完整的Java类名,
<function-signature>子元素用于指定Java类中的静态方法的签名,方法签名必须指明方法的返回值类型及各个参数的类型,各个参数之间用逗号分隔。
1.7、EL注意事项
EL表达式是JSP 2.0规范中的一门技术 。因此,若想正确解析EL表达式,需使用支持Servlet2.4/JSP2.0技术的WEB服务器。
注意:有些Tomcat服务器如不能使用EL表达式
(1)升级成tomcat6
(2)在JSP中加入<%@ page isELIgnored="false" %>
1.8、EL表达式保留关键字

所谓保留字的意思是指变量在命名时,应该避开上述的名字,以免程序编译时发生错误,关于EL表达式的内容的总结就这么多。

(三十)——EL函数库
一、EL函数库介绍
由于在JSP页面中显示数据时,经常需要对显示的字符串进行处理,SUN公司针对于一些常见处理定义了一套EL函数库供开发者使用。
这些EL函数在JSTL开发包中进行描述,因此在JSP页面中使用SUN公司的EL函数库,需要导入JSTL开发包,并在页面中导入EL函数库,如下所示:
MyEclipse自带的JSTL开发包:

fn.tld就是EL函数库的对应的tld描述文件,如下图所示:

在页面中使用JSTL定义的EL函数:<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
EL函数集:
static boolean contains(String input, String substring)
static boolean containsIgnoreCase(String input, String substring)
static boolean endsWith(String input, String suffix)
static String escapeXml(String input)
static int indexOf(String input, String substring)
static String join(String[] array, String separator)
static int length(Object obj)
static String replace(String input, String before, String after)
static String[] split(String input, String delimiters)
static boolean startsWith(String input, String prefix)
static String substring(String input, int beginIndex, int endIndex)
static String substringAfter(String input, String substring)
static String substringBefore(String input, String substring)
static String toLowerCase(String input)
static String toUpperCase(String input)
static String trim(String input)

二、EL函数使用范例
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@page import="me.gacl.domain.User"%>
< %--引入EL函数库 --%>
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<!DOCTYPE HTML>
<html>
<head>
<title>EL函数库中的方法使用范例</title>
</head>
<body>
<h3>fn:toLowerCase函数使用范例:</h3>
< %--fn:toLowerCase函数将一个字符串中包含的所有字符转换为小写形式,并返回转换后的字符串,
它接收一个字符串类型的参数。fn:toLowerCase("")的返回值为空字符串--%>
< %--fn:toLowerCase("Www.CNBLOGS.COM") 的返回值为字符串“www.cnblogs.com” --%>
fn:toLowerCase("Www.CNBLOGS.COM")的结果是:${fn:toLowerCase("Www.CNBLOGS.COM")}
<hr/>
<h3>fn:toUpperCase函数使用范例:</h3>
< %--fn:toUpperCase函数将一个字符串中包含的所有字符转换为大写形式,并返回转换后的字符串,
它接收一个字符串类型的参数。fn:toUpperCase("")的返回值为空字符串--%>
fn:toUpperCase("cnblogs.com")的结果是:${fn:toUpperCase("cnblogs.com")}
<hr/>
<h3>fn:trim函数使用范例:</h3>
< %--fn:trim函数删除一个字符串的首尾的空格,并返回删除空格后的结果字符串,
它接收一个字符串类型的参数。需要注意的是,fn:trim函数不能删除字符串中间位置的空格。--%>
fn:trim(" cnblogs.com ")的结果是:${fn:trim(" cnblogs.com ")}
<hr/>
<h3>fn:length函数使用范例:</h3>
< %--fn:length函数返回一个集合或数组大小,或返回一个字符串中包含的字符的个数,返回值为int类型。
fn:length函数接收一个参数,这个参数可以是<c:forEach>标签的items属性支持的任何类型,
包括任意类型的数组、java.util.Collection、java.util.Iterator、java.util.Enumeration、
java.util.Map等类的实例对象和字符串。
如果fn:length函数的参数为null或者是元素个数为0的集合或数组对象,则函数返回0;如果参数是空字符串,则函数返回0
--%>
<%
List<String> list = Arrays.asList("1","2","3");
request.setAttribute("list",list);
%>
fn:length(list)计算集合list的size的值是:${fn:length(list)}
<br/>
fn:length("cnblogs.com")计算字符串的长度是:${fn:length("cnblogs.com")}
<hr/>
<h3>fn:split函数使用范例:</h3>
< %--
fn:split函数以指定字符串作为分隔符,将一个字符串分割成字符串数组并返回这个字符串数组。
fn:split函数接收两个字符串类型的参数,第一个参数表示要分割的字符串,第二个参数表示作为分隔符的字符串
--%>
fn:split("cnblogs.com",".")[0]的结果是:${fn:split("cnblogs.com",".")[0]}
<hr/>
<h3>fn:join函数使用范例:</h3>
< %--
fn:join函数以一个字符串作为分隔符,将一个字符串数组中的所有元素合并为一个字符串并返回合并后的结果字符串。
fn:join函数接收两个参数,第一个参数是要操作的字符串数组,第二个参数是作为分隔符的字符串。
如果fn:join函数的第二个参数是空字符串,则fn:join函数的返回值直接将元素连接起来。
--%>
<%
String[] StringArray = {"www","iteye","com"};
pageContext.setAttribute("StringArray", StringArray);
%>
< %--fn:join(StringArray,".")返回字符串“www.iteye.com”--%>
fn:join(StringArray,".")的结果是:${fn:join(StringArray,".")}
<br/>
< %--fn:join(fn:split("www,iteye,com",","),".")的返回值为字符串“www.iteye.com”--%>
fn:join(fn:split("www,iteye,com",","),".")的结果是:${fn:join(fn:split("www,iteye,com",","),".")}
<hr/>
<h3>fn:indexOf函数使用范例:</h3>
< %--
fn:indexOf函数返回指定字符串在一个字符串中第一次出现的索引值,返回值为int类型。
fn:indexOf函数接收两个字符串类型的参数,如果第一个参数字符串中包含第二个参数字符串,
那么,不管第二个参数字符串在第一个参数字符串中出现几次,fn:indexOf函数总是返回第一次出现的索引值;
如果第一个参数中不包含第二个参数,则fn:indexOf函数返回-1。如果第二个参数为空字符串,则fn:indexOf函数总是返回0。
--%>
fn:indexOf("www.iteye.com","eye")的返回值为:${fn:indexOf("www.iteye.com","eye")}
<hr/>
<h3>fn:contains函数使用范例:</h3>
< %--
fn:contains函数检测一个字符串中是否包含指定的字符串,返回值为布尔类型。
fn:contains函数在比较两个字符串是否相等时是大小写敏感的。
fn:contains函数接收两个字符串类型的参数,如果第一个参数字符串中包含第二个参数字符串,则fn:contains函数返回true,否则返回false。
如果第二个参数的值为空字符串,则fn:contains函数总是返回true。
实际上,fn:contains(string, substring)等价于fn:indexOf(string, substring) != -1
忽略大小的EL函数:fn:containsIgnoreCase
--%>
<%
User user = new User();
String likes[] = {"sing","dance"};
user.setLikes(likes);
//数据回显
request.setAttribute("user",user);
%>
< %--使用el函数回显数据 --%>
<input type="checkbox" name="like"
vlaue="sing" ${fn:contains(fn:join(user.likes,","),"sing")?‘checked‘:‘‘}/>唱歌
<input type="checkbox" name="like"
value="dance" ${fn:contains(fn:join(user.likes,","),"dance")?‘checked‘:‘‘}/>跳舞
<input type="checkbox" name="like"
value="basketball" ${fn:contains(fn:join(user.likes,","),"basketball")?‘checked‘:‘‘}/>蓝球
<input type="checkbox" name="like"
value="football" ${fn:contains(fn:join(user.likes,","),"football")?‘checked‘:‘‘}/>足球
<hr/>
<h3>fn:startsWith函数和fn:endsWith函数使用范例:</h3>
< %--
fn:startsWith函数用于检测一个字符串是否是以指定字符串开始的,返回值为布尔类型。
fn:startsWith函数接收两个字符串类型的参数,如果第一个参数字符串以第二个参数字符串开始,则函数返回true,否则函数返回false。
如果第二个参数为空字符串,则fn:startsWith函数总是返回true。
与fn:startsWith函数对应的另一个EL函数为:fn:endsWith,用于检测一个字符串是否是以指定字符串结束的,返回值为布尔类型。
--%>
fn:startsWith("www.iteye.com","iteye")的返回值为:${fn:startsWith("www.iteye.com","iteye")}
<br/>
fn:endsWith("www.iteye.com","com")的返回值为:${fn:endsWith("www.iteye.com","com")}
<hr/>
<h3>fn:replace使用范例:</h3>
< %--
fn:replace函数将一个字符串中包含的指定子字符串替换为其它的指定字符串,并返回替换后的结果字符串。
fn:replace方法接收三个字符串类型的参数,第一个参数表示要操作的源字符串,第二个参数表示源字符串中要被替换的子字符串,
第三个参数表示要被替换成的字符串。
--%>
fn:replace("www iteye com ", " ", ".")的返回值为字符串:${fn:replace("www iteye com", " ", ".")}
<hr/>
<h3>fn:substring使用范例:</h3>
< %--
fn:substring函数用于截取一个字符串的子字符串并返回截取到的子字符串。
fn:substring函数接收三个参数,第一个参数是用于指定要操作的源字符串,第二个参数是用于指定截取子字符串开始的索引值,
第三个参数是用于指定截取子字符串结束的索引值,第二个参数和第三个参数都是int类型,其值都从0开始。
--%>
fn:substring("www.it315.org", 4, 9) 的返回值为字符串:${fn:substring("www.it315.org", 4, 9)}
<h3>fn:substringAfter函数和fn:substringBefore函数使用范例:</h3>
< %--
fn:substringAfter函数用于截取并返回一个字符串中的指定子字符串第一次出现之后的子字符串。
fn:substringAfter函数接收两个字符串类型的参数,第一个参数表示要操作的源字符串,第二个参数表示指定的子字符串
与之对应的EL函数为:fn:substringBefore
--%>
fn:substringAfter("www.it315.org",".")的返回值为字符串:${fn:substringAfter("www.it315.org",".")}
<br/>
fn:substringBefore("www.it315.org",".")的返回值为字符串:${fn:substringBefore("www.it315.org",".")}
<hr/>
</body>
</html>
复制代码
jsp页面中使用到的me.gacl.domain.User类的代码如下:
复制代码
package me.gacl.domain;
public class User {
/**
* 兴趣爱好
*/
private String likes[];
public String[] getLikes() {
return likes;
}
public void setLikes(String[] likes) {
this.likes = likes;
}
}
复制代码
运行结果如下:

(三十一)——国际化(i18n)
javaweb学习总结(三十一)——国际化(i18n)
一、国际化开发概述
软件的国际化:软件开发时,要使它能同时应对世界不同地区和国家的访问,并针对不同地区和国家的访问,提供相应的、符合来访者阅读习惯的页面或数据。
国际化(internationalization)又称为 i18n(读法为i 18 n,据说是因为internationalization(国际化)这个单词从i到n之间有18个英文字母,i18n的名字由此而来)
二、合格的国际化软件
软件实现国际化,需具备以下两个特征:
1、对于程序中固定使用的文本元素,例如菜单栏、导航条等中使用的文本元素、或错误提示信息,状态信息等,需要根据来访者的地区和国家,选择不同语言的文本为之服务。
2、对于程序动态产生的数据,例如(日期,货币等),软件应能根据当前所在的国家或地区的文化习惯进行显示。
三、固定文本元素的国际化
对于软件中的菜单栏、导航条、错误提示信息,状态信息等这些固定不变的文本信息,可以把它们写在一个properties文件中,并根据不同的国家编写不同的properties文件。这一组properties文件称之为一个资源包。
3.1、创建资源包和资源文件
一个资源包中的每个资源文件都必须拥有共同的基名。除了基名,每个资源文件的名称中还必须有标识其本地信息的附加部分。例如:一个资源包的基名是“myproperties”,则与中文、英文环境相对应的资源文件名则为: "myproperties_zh.properties" "myproperties_en.properties"

每个资源包都应有一个默认资源文件,这个文件不带有标识本地信息的附加部分。若ResourceBundle对象在资源包中找不到与用户匹配的资源文件,它将选择该资源包中与用户最相近的资源文件,如果再找不到,则使用默认资源文件。例如:myproperties.properties
3.2、资源文件的书写格式
资源文件的内容通常采用"关键字=值"的形式,软件根据关键字检索值显示在页面上。一个资源包中的所有资源文件的关键字必须相同,值则为相应国家的文字。
并且资源文件中采用的是properties格式文件,所以文件中的所有字符都必须是ASCII字码,属性(properties)文件是不能保存中文的,对于像中文这样的非ACSII字符,须先进行编码。
例如:
国际化的中文环境的properties文件

国际化的英文环境的properties文件

java提供了一个native2ascII工具用于将中文字符进行编码处理,native2ascII的用法如下所示:

3.3、编程实现固定文本的国际化
在JavaAPI中提供了一个ResourceBundle 类用于描述一个资源包,并且 ResourceBundle类提供了相应的方法getBundle,这个方法可以根据来访者的国家地区自动获取与之对应的资源文件予以显示。
ResourceBundle类提供了一个静态方法getBundle,该方法用于装载资源文件,并创建ResourceBundle实例:
Locale currentLocale = Locale.getDefault();
ResourceBundle myResources = ResourceBundle.getBundle(basename, currentLocale);
basename为资源包基名(且必须为完整路径)。
如果与该locale对象匹配的资源包子类找不到。一般情况下,则选用默认资源文件予以显示。
加载资源文件后, 程序就可以调用ResourceBundle 实例对象的 getString 方法获取指定的资源信息名称所对应的值。
String value = myResources.getString(“key");
范例:根据国家地区自动获取与之对应的资源文件
复制代码
package me.gacl.i18n;
import java.util.Locale;
import java.util.ResourceBundle;
/**
* @ClassName: I18NTest
* @Description: 编程实现固定文本的国际化
* @author: 孤傲苍狼
* @date: 2014-8-29 下午9:34:05
*
*/
public class I18NTest {
public static void main(String[] args) {
//资源包基名(包名+myproperties)
String basename = "me.gacl.i18n.resource.myproperties";
//设置语言环境
Locale cn = Locale.CHINA;//中文
Locale us = Locale.US;//英文
//根据基名和语言环境加载对应的语言资源文件
ResourceBundle myResourcesCN = ResourceBundle.getBundle(basename,cn);//加载myproperties_zh.properties
ResourceBundle myResourcesUS = ResourceBundle.getBundle(basename,us);//加载myproperties_en.properties
//加载资源文件后, 程序就可以调用ResourceBundle实例对象的 getString方法获取指定的资源信息名称所对应的值。
//String value = myResources.getString(“key");
String usernameCN = myResourcesCN.getString("username");
String passwordCN = myResourcesCN.getString("password");
String usernameUS = myResourcesUS.getString("username");
String passwordUS = myResourcesUS.getString("password");
System.out.println(usernameCN+"--"+passwordCN);
System.out.println(usernameUS+"--"+passwordUS);
}
}
复制代码
运行结果:

3.4、在WEB应用中实现固定文本的国际化
如下所示:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>国际化(i18n)测试</title>
</head>
<%
//加载i18n资源文件,request.getLocale()获取访问用户所在的国家地区
ResourceBundle myResourcesBundle = ResourceBundle.getBundle("me.gacl.i18n.resource.myproperties",request.getLocale());
%>
<body>
<form action="" method="post">
<%=myResourcesBundle.getString("username")%>:<input type="text" name="username"/><br/>
<%=myResourcesBundle.getString("password")%>:<input type="password" name="password"/><br/>
<input type="submit" value="<%=myResourcesBundle.getString("submit")%>">
</form>
</body>
</html>
复制代码
运行结果:
浏览器语言是中文环境下的显示效果:

浏览器语言是英文环境下的显示效果:

同样一个页面,在不同语言环境的浏览器下显示出了不同的语言文字效果,这样就实现了固定文本的国际化。
IE浏览器切换使用语言:工具→Internet选项



四、动态数据的国际化
数值,货币,时间,日期等数据由于可能在程序运行时动态产生,所以无法像文字一样简单地将它们从应用程序中分离出来,而是需要特殊处理。Java 中提供了解决这些问题的 API 类(位于 java.util 包和 java.text 包中)
4.1、Locale 类
Locale 实例对象代表一个特定的地理,政治、文化区域。
一个 Locale 对象本身不会验证它代表的语言和国家地区信息是否正确,只是向本地敏感的类提供国家地区信息,与国际化相关的格式化和解析任务由本地敏感的类去完成。(若JDK中的某个类在运行时需要根据 Locale 对象来调整其功能,这个类就称为本地敏感类)
4.2、DateFormat类(日期格式化)
DateFormat 类可以将一个日期/时间对象格式化为表示某个国家地区的日期/时间字符串。
DateFormat 类除了可按国家地区格式化输出日期外,它还定义了一些用于描述日期/时间的显示模式的 int 型的常量,包括FULL, LONG, MEDIUM, DEFAULT, SHORT,实例化DateFormat对象时,可以使用这些常量,控制日期/时间的显示长度。
4.2.1、实例化DateFormat类
实例化DateFormat类有九种方式,以下三种为带参形式,下面列出的三种方式也可以分别不带参,或只带显示样式的参数。
getDateInstance(int style, Locale aLocale):以指定的日期显示模式和本地信息来获得DateFormat实例对象,该实例对象不处理时间值部分。
getTimeInstance(int style, Locale aLocale):以指定的时间显示模式和本地信息来获得DateFormat实例对象,该实例对象不处理日期值部分。
getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale):以单独指定的日期显示模式、时间显示模式和本地信息来获得DateFormat实例对象。
4.2.2、DateFormat 对象的方法
format:将date对象格式化为符合某个本地环境习惯的字符串。
parse:将字符串解析为日期/时间对象
注意:parse和format完全相反,一个是把date时间转化为相应地区和国家的显示样式,一个是把相应地区的时间日期转化成date对象,该方法在使用时,解析的时间或日期要符合指定的国家、地区格式,否则会抛异常。
DateFormat 对象通常不是线程安全的,每个线程都应该创建自己的 DateFormat 实例对象
4.2.3、DateFormat使用范例
复制代码
package me.gacl.i18n;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
/**
* @ClassName: DateFormatTest
* @Description: DateFormat类测试
* DateFormat类可以将一个日期/时间对象格式化为表示某个国家地区的日期/时间字符串
* @author: 孤傲苍狼
* @date: 2014-8-29 下午10:03:26
*
*/
public class DateFormatTest {
public static void main(String[] args) throws ParseException {
Date date = new Date(); // 当前这一刻的时间(日期、时间)
// 输出日期部分
DateFormat df = DateFormat.getDateInstance(DateFormat.FULL,Locale.GERMAN);
String result = df.format(date);
System.out.println(result);
// 输出时间部分
df = DateFormat.getTimeInstance(DateFormat.FULL, Locale.CHINA);
result = df.format(date);
System.out.println(result);
// 输出日期和时间
df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG,Locale.CHINA);
result = df.format(date);
System.out.println(result);
// 把字符串反向解析成一个date对象
String s = "10-9-26 下午02时49分53秒";
df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG,Locale.CHINA);
Date d = df.parse(s);
System.out.println(d);
}
}
复制代码
4.3、NumberFormat类(数字格式化)
NumberFormat类可以将一个数值格式化为符合某个国家地区习惯的数值字符串,也可以将符合某个国家地区习惯的数值字符串解析为对应的数值
NumberFormat类的方法:
format 方法:将一个数值格式化为符合某个国家地区习惯的数值字符串
parse 方法:将符合某个国家地区习惯的数值字符串解析为对应的数值。
实例化NumberFormat类时,可以使用locale对象作为参数,也可以不使用,下面列出的是使用参数的。
getNumberInstance(Locale locale):以参数locale对象所标识的本地信息来获得具有多种用途的NumberFormat实例对象
getIntegerInstance(Locale locale):以参数locale对象所标识的本地信息来获得处理整数的NumberFormat实例对象
getCurrencyInstance(Locale locale):以参数locale对象所标识的本地信息来获得处理货币的NumberFormat实例对象
getPercentInstance(Locale locale):以参数locale对象所标识的本地信息来获得处理百分比数值的NumberFormat实例对象
范例:
复制代码
package me.gacl.i18n;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
/**
* @ClassName: NumberFormatTest
* @Description: NumberFormat类测试
* @author: 孤傲苍狼
* @date: 2014-8-29 下午10:25:29
*
*/
public class NumberFormatTest {
public static void main(String[] args) throws ParseException {
int price = 89;
NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.CHINA);
String result = nf.format(price);
System.out.println(result);
String s = "¥89.00";
nf = NumberFormat.getCurrencyInstance(Locale.CHINA);
Number n = nf.parse(s);
System.out.println(n.doubleValue() + 1);
double num = 0.5;
nf = NumberFormat.getPercentInstance();
System.out.println(nf.format(num));
}
}
复制代码
运行结果:

4.4、MessageFormat(文本格式化)
如果一个字符串中包含了多个与国际化相关的数据,可以使用MessageFormat类对这些数据进行批量处理。
例如:At 12:30 pm on jul 3,1998, a hurricance destroyed 99 houses and caused $1000000 of damage
以上字符串中包含了时间、数字、货币等多个与国际化相关的数据,对于这种字符串,可以使用MessageFormat类对其国际化相关的数据进行批量处理。
MessageFormat 类如何进行批量处理呢?
1.MessageFormat类允许开发人员用占位符替换掉字符串中的敏感数据(即国际化相关的数据)。
2.MessageFormat类在格式化输出包含占位符的文本时,messageFormat类可以接收一个参数数组,以替换文本中的每一个占位符。
4.4.1、模式字符串与占位符
模式字符串:
At {0} on {1},a destroyed {2} houses and caused {3} of damage
字符串中的{0}、{1}、{2}、{3}就是占位符
4.4.2、格式化模式字符串
1、实例化MessageFormat对象,并装载相应的模式字符串。
2、使用format(object obj[])格式化输出模式字符串,参数数组中指定占位符相应的替换对象。
范例:
复制代码
package me.gacl.i18n;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;
/**
* @ClassName: MessageFormatTest
* @Description: MessageFormat类测试
* @author: 孤傲苍狼
* @date: 2014-8-29 下午10:29:19
*
*/
public class MessageFormatTest {
public static void main(String[] args) {
//模式字符串
String pattern = "On {0}, a hurricance destroyed {1} houses and caused {2} of damage.";
//实例化MessageFormat对象,并装载相应的模式字符串
MessageFormat format = new MessageFormat(pattern, Locale.CHINA);
Object arr[] = {new Date(), 99, 100000000};
//格式化模式字符串,参数数组中指定占位符相应的替换对象
String result = format.format(arr);
System.out.println(result);
}
}
复制代码
运行结果:

4.4.3、占位符的三种书写方式
{argumentIndex}: 0-9 之间的数字,表示要格式化对象数据在参数数组中的索引号
{argumentIndex,formatType}: 参数的格式化类型
{argumentIndex,formatType,FormatStyle}: 格式化的样式,它的值必须是与格式化类型相匹配的合法模式、或表示合法模式的字符串。
范例:
复制代码
package me.gacl.i18n;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;
/**
* @ClassName: MessageFormatTest
* @Description: MessageFormat类测试
* @author: 孤傲苍狼
* @date: 2014-8-29 下午10:29:19
*
*/
public class MessageFormatTest {
public static void main(String[] args) {
//模式字符串
String pattern = "At {0, time, short} on {0, date}, a destroyed {1} houses and caused {2, number, currency} of damage.";
//实例化MessageFormat对象,并装载相应的模式字符串
MessageFormat format = new MessageFormat(pattern, Locale.US);
Object arr[] = {new Date(), 99, 100000000};
//格式化模式字符串,参数数组中指定占位符相应的替换对象
String result = format.format(arr);
System.out.println(result);
}
}
复制代码
运行结果:

五、在WEB应用中使用国际化标签库实现固定文本的国际化
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
< %--导入国际化标签库 --%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE HTML>
<html>
<head>
<title>国际化(i18n)测试</title>
</head>
< %--
//加载i18n资源文件,request.getLocale()获取访问用户所在的国家地区
ResourceBundle myResourcesBundle = ResourceBundle.getBundle("me.gacl.i18n.resource.myproperties",request.getLocale());
--%>
<body>
< %--
<form action="" method="post">
<%=myResourcesBundle.getString("username")%>:<input type="text" name="username"/><br/>
<%=myResourcesBundle.getString("password")%>:<input type="password" name="password"/><br/>
<input type="submit" value="<%=myResourcesBundle.getString("submit")%>">
</form>
--%>
<fmt:setBundle var="bundle" basename="me.gacl.i18n.resource.myproperties" scope="page"/>
<form action="">
<fmt:message key="username" bundle="${bundle}"/><input type="text" name="username"><br/>
<fmt:message key="password" bundle="${bundle}"/><input type="password" name="password"><br/>
<input type="submit" value="<fmt:message key="submit" bundle="${bundle}"/>">
</form>
</body>
</html>
复制代码
以上就是JavaWeb开发中国际化的总结内容。

(三十二)——JDBC学习入门
一、JDBC相关概念介绍
1.1、数据库驱动
这里的驱动的概念和平时听到的那种驱动的概念是一样的,比如平时购买的声卡,网卡直接插到计算机上面是不能用的,必须要安装相应的驱动程序之后才能够使用声卡和网卡,同样道理,我们安装好数据库之后,我们的应用程序也是不能直接使用数据库的,必须要通过相应的数据库驱动程序,通过驱动程序去和数据库打交道,如下所示:

1.2、JDBC介绍
SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范(接口),称之为JDBC。这套接口由数据库厂商去实现,这样,开发人员只需要学习jdbc接口,并通过jdbc加载具体的驱动,就可以操作数据库。
如下图所示:

JDBC全称为:Java Data Base Connectivity(java数据库连接),它主要由接口组成。
组成JDBC的2个包:
 java.sql
 javax.sql
开发JDBC应用需要以上2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。
二、编写JDBC程序
2.1、搭建实验环境
1、在mysql中创建一个库,并创建user表和插入表的数据。
SQL脚本如下:
复制代码
create database jdbcStudy character set utf8 collate utf8_general_ci;
use jdbcStudy;
create table users(
id int primary key,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
);
insert into users(id,name,password,email,birthday) values(1,‘zhansan‘,‘123456‘,‘zs@sina.com‘,‘1980-12-04‘);
insert into users(id,name,password,email,birthday) values(2,‘lisi‘,‘123456‘,‘lisi@sina.com‘,‘1981-12-04‘);
insert into users(id,name,password,email,birthday) values(3,‘wangwu‘,‘123456‘,‘wangwu@sina.com‘,‘1979-12-04‘);
复制代码
2、新建一个Java工程,并导入数据驱动。
s
3、编写程序从user表中读取数据,并打印在命令行窗口中。
具体代码如下:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcFirstDemo {
public static void main(String[] args) throws Exception {
//要连接的数据库URL
String url = "jdbc:mysql://localhost:3306/jdbcStudy";
//连接的数据库时使用的用户名
String username = "root";
//连接的数据库时使用的密码
String password = "XDP";
//1.加载驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());不推荐使用这种方式来加载驱动
Class.forName("com.mysql.jdbc.Driver");//推荐使用这种方式来加载驱动
//2.获取与数据库的链接
Connection conn = DriverManager.getConnection(url, username, password);
//3.获取用于向数据库发送sql语句的statement
Statement st = conn.createStatement();
String sql = "select id,name,password,email,birthday from users";
//4.向数据库发sql,并获取代表结果集的resultset
ResultSet rs = st.executeQuery(sql);
//5.取出结果集的数据
while(rs.next()){
System.out.println("id=" + rs.getObject("id"));
System.out.println("name=" + rs.getObject("name"));
System.out.println("password=" + rs.getObject("password"));
System.out.println("email=" + rs.getObject("email"));
System.out.println("birthday=" + rs.getObject("birthday"));
}
//6.关闭链接,释放资源
rs.close();
st.close();
conn.close();
}
}
复制代码
运行结果如下:

2.2、DriverManager类讲解
Jdbc程序中的DriverManager用于加载驱动,并创建与数据库的链接,这个API的常用方法:
DriverManager.registerDriver(new Driver())
DriverManager.getConnection(url, user, password),
注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
2、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
推荐方式:Class.forName("com.mysql.jdbc.Driver");
采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
2.3、数据库URL讲解
URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:

常用数据库URL地址的写法:
Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
SqlServer写法:jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
MySql写法:jdbc:mysql://localhost:3306/sid
如果连接的是本地的Mysql数据库,并且连接使用的端口是3306,那么的url地址可以简写为: jdbc:mysql:///数据库
2.4、Connection类讲解
Jdbc程序中的Connection,它用于代表数据库的链接,Collection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
createStatement():创建向数据库发送sql的statement对象。
prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
prepareCall(sql):创建执行存储过程的callableStatement对象。
setAutoCommit(boolean autoCommit):设置事务是否自动提交。
commit() :在链接上提交事务。
rollback() :在此链接上回滚事务。
2.5、Statement类讲解
Jdbc程序中的Statement对象用于向数据库发送SQL语句, Statement对象常用方法:
executeQuery(String sql) :用于向数据发送查询语句。
executeUpdate(String sql):用于向数据库发送insert、update或delete语句
execute(String sql):用于向数据库发送任意sql语句
addBatch(String sql) :把多条sql语句放到一个批处理中。
executeBatch():向数据库发送一批sql语句执行。
2.6、ResultSet类讲解
Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
获取任意类型的数据
getObject(int index)
getObject(string columnName)
获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
ResultSet还提供了对结果集进行滚动的方法:
next():移动到下一行
Previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。
afterLast() :移动到resultSet的最后面。
2.7、释放资源
Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象,特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

(三十三)——使用JDBC对数据库进行CRUD
一、statement对象介绍
Jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。
Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sql语句,executeUpdate执行完后,将会返回一个整数(即增删改语句导致了数据库几行数据发生了变化)。
Statement.executeQuery方法用于向数据库发送查询语句,executeQuery方法返回代表查询结果的ResultSet对象。
1.1、CRUD操作-create
使用executeUpdate(String sql)方法完成数据添加操作,示例操作:
复制代码
Statement st = conn.createStatement();
String sql = "insert into user(….) values(…..) ";
int num = st.executeUpdate(sql);
if(num>0){
System.out.println("插入成功!!!");
}
复制代码
1.2、CRUD操作-update
使用executeUpdate(String sql)方法完成数据修改操作,示例操作:
复制代码
Statement st = conn.createStatement();
String sql = “update user set name=‘’ where name=‘’";
int num = st.executeUpdate(sql);
if(num>0){
System.out.println(“修改成功!!!");
}
复制代码
1.3、CRUD操作-delete
使用executeUpdate(String sql)方法完成数据删除操作,示例操作:
复制代码
Statement st = conn.createStatement();
String sql = “delete from user where id=1;
int num = st.executeUpdate(sql);
if(num>0){
System.out.println(“删除成功!!!");
}
复制代码
1.4、CRUD操作-read
使用executeQuery(String sql)方法完成数据查询操作,示例操作:
复制代码
Statement st = conn.createStatement();
String sql = “select * from user where id=1;
ResultSet rs = st.executeUpdate(sql);
while(rs.next()){
//根据获取列的数据类型,分别调用rs的相应方法映射到java对象中
}
复制代码
二、使用jdbc对数据库增删改查
2.1、搭建实验环境
1、在mysql中创建一个库,并创建user表和插入表的数据。
SQL脚本如下:
复制代码
create database jdbcStudy;
use jdbcStudy;
create table users(
id int primary key,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
);
复制代码
2、新建一个JavaWeb工程,并导入MySQL数据库驱动。

3、在src目录下创建一个db.properties文件,如下图所示:

在db.properties中编写MySQL数据库的连接信息,代码如下所示:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy
username=root
password=XDP
4、编写一个JdbcUtils工具类,用于连接数据库,获取数据库连接和释放数据库连接,代码如下:
复制代码
package me.gacl.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static{
try{
//读取db.properties文件中的数据库连接信息
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(in);
//获取数据库连接驱动
driver = prop.getProperty("driver");
//获取数据库连接URL地址
url = prop.getProperty("url");
//获取数据库连接用户名
username = prop.getProperty("username");
//获取数据库连接密码
password = prop.getProperty("password");
//加载数据库驱动
Class.forName(driver);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getConnection
* @Description: 获取数据库连接对象
* @Anthor:孤傲苍狼
*
* @return Connection数据库连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url, username,password);
}
/**
* @Method: release
* @Description: 释放资源,
* 要释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//关闭Connection数据库连接对象
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
2.2、使用statement对象完成对数据库的CRUD操作
测试代码如下:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: JdbcCRUDByStatement
* @Description: 通过Statement对象完成对数据库的CRUD操作
* @author: 孤傲苍狼
* @date: 2014-9-15 下午11:22:12
*
*/
public class JdbcCRUDByStatement {
@Test
public void insert(){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
//获取一个数据库连接
conn = JdbcUtils.getConnection();
//通过conn对象获取负责执行SQL命令的Statement对象
st = conn.createStatement();
//要执行的SQL命令
String sql = "insert into users(id,name,password,email,birthday) values(3,‘白虎神皇‘,‘123‘,‘bhsh@sina.com‘,‘1980-09-09‘)";
//执行插入操作,executeUpdate方法返回成功的条数
int num = st.executeUpdate(sql);
if(num>0){
System.out.println("插入成功!!");
}
}catch (Exception e) {
e.printStackTrace();
}finally{
//SQL执行完成之后释放相关资源
JdbcUtils.release(conn, st, rs);
}
}
@Test
public void delete(){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "delete from users where id=3";
st = conn.createStatement();
int num = st.executeUpdate(sql);
if(num>0){
System.out.println("删除成功!!");
}
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
@Test
public void update(){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "update users set name=‘孤傲苍狼‘,email=‘gacl@sina.com‘ where id=3";
st = conn.createStatement();
int num = st.executeUpdate(sql);
if(num>0){
System.out.println("更新成功!!");
}
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
@Test
public void find(){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "select * from users where id=3";
st = conn.createStatement();
rs = st.executeQuery(sql);
if(rs.next()){
System.out.println(rs.getString("name"));
}
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码
三、PreparedStatement对象介绍
PreperedStatement是Statement的子类,它的实例对象可以通过调用Connection.preparedStatement()方法获得,相对于Statement对象而言:PreperedStatement可以避免SQL注入的问题。
Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可对SQL进行预编译,从而提高数据库的执行效率。并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。
3.1、使用PreparedStatement对象完成对数据库的CRUD操作
测试代码如下:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: JdbcCRUDByPreparedStatement
* @Description: 通过PreparedStatement对象完成对数据库的CRUD操作
* @author: 孤傲苍狼
* @date: 2014-9-15 下午11:21:42
*
*/
public class JdbcCRUDByPreparedStatement {
@Test
public void insert(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
//获取一个数据库连接
conn = JdbcUtils.getConnection();
//要执行的SQL命令,SQL中的参数使用?作为占位符
String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)";
//通过conn对象获取负责执行SQL命令的prepareStatement对象
st = conn.prepareStatement(sql);
//为SQL语句中的参数赋值,注意,索引是从1开始的
/**
* SQL语句中各个字段的类型如下:
* +----------+-------------+
| Field | Type |
+----------+-------------+
| id | int(11) |
| name | varchar(40) |
| password | varchar(40) |
| email | varchar(60) |
| birthday | date |
+----------+-------------+
*/
st.setInt(1, 1);//id是int类型的
st.setString(2, "白虎神皇");//name是varchar(字符串类型)
st.setString(3, "123");//password是varchar(字符串类型)
st.setString(4, "bhsh@sina.com");//email是varchar(字符串类型)
st.setDate(5, new java.sql.Date(new Date().getTime()));//birthday是date类型
//执行插入操作,executeUpdate方法返回成功的条数
int num = st.executeUpdate();
if(num>0){
System.out.println("插入成功!!");
}
}catch (Exception e) {
e.printStackTrace();
}finally{
//SQL执行完成之后释放相关资源
JdbcUtils.release(conn, st, rs);
}
}
@Test
public void delete(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "delete from users where id=?";
st = conn.prepareStatement(sql);
st.setInt(1, 1);
int num = st.executeUpdate();
if(num>0){
System.out.println("删除成功!!");
}
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
@Test
public void update(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "update users set name=?,email=? where id=?";
st = conn.prepareStatement(sql);
st.setString(1, "gacl");
st.setString(2, "gacl@sina.com");
st.setInt(3, 2);
int num = st.executeUpdate();
if(num>0){
System.out.println("更新成功!!");
}
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
@Test
public void find(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "select * from users where id=?";
st = conn.prepareStatement(sql);
st.setInt(1, 1);
rs = st.executeQuery();
if(rs.next()){
System.out.println(rs.getString("name"));
}
}catch (Exception e) {
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码
以上就是使用JDBC对数据库进行CRUD的简单总结。

(三十四)——使用JDBC处理MySQL大数据
一、基本概念
大数据也称之为LOB(Large Objects),LOB又分为:clob和blob,clob用于存储大文本,blob用于存储二进制数据,例如图像、声音、二进制文等。
在实际开发中,有时是需要用程序把大文本或二进制数据直接保存到数据库中进行储存的。
对MySQL而言只有blob,而没有clob,mysql存储大文本采用的是Text,Text和blob分别又分为:
TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT
TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB
二、搭建测试环境
2.1、搭建的测试项目架构
如下:

2.2、编写db.properties配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy
username=root
password=XDP
2.3、编写JdbcUtils工具类
复制代码
package me.gacl.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static{
try{
//读取db.properties文件中的数据库连接信息
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(in);
//获取数据库连接驱动
driver = prop.getProperty("driver");
//获取数据库连接URL地址
url = prop.getProperty("url");
//获取数据库连接用户名
username = prop.getProperty("username");
//获取数据库连接密码
password = prop.getProperty("password");
//加载数据库驱动
Class.forName(driver);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getConnection
* @Description: 获取数据库连接对象
* @Anthor:孤傲苍狼
*
* @return Connection数据库连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url, username,password);
}
/**
* @Method: release
* @Description: 释放资源,
* 要释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//关闭Connection数据库连接对象
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
三、使用JDBC处理MySQL的大文本
对于MySQL中的Text类型,可调用如下方法设置
PreparedStatement.setCharacterStream(index, reader, length);//注意length长度须设置,并且设置为int型
对MySQL中的Text类型,可调用如下方法获取
reader = resultSet.getCharacterStream(String columnLabel);
2 string s = resultSet.getString(String columnLabel);
3.1、 测试范例
1、编写SQL测试脚本
复制代码
create database jdbcstudy;
use jdbcstudy;
create table testclob
(
id int primary key auto_increment,
resume text
);
复制代码
2、编写测试代码如下:
复制代码
package me.gacl.demo;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: JdbcOperaClob
* @Description: 使用JDBC操作MySQL的大文本
* @author: 孤傲苍狼
* @date: 2014-9-19 下午10:10:04
*
*/
public class JdbcOperaClob {
/**
* @Method: add
* @Description:向数据库中插入大文本数据
* @Anthor:孤傲苍狼
*
*/
@Test
public void add(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
Reader reader = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into testclob(resume) values(?)";
st = conn.prepareStatement(sql);
//这种方式获取的路径,其中的空格会被使用“%20”代替
String path = JdbcOperaClob.class.getClassLoader().getResource("data.txt").getPath();
//将“%20”替换回空格
path = path.replaceAll("%20", " ");
File file = new File(path);
reader = new FileReader(file);
st.setCharacterStream(1, reader,(int) file.length());
int num = st.executeUpdate();
if(num>0){
System.out.println("插入成功!!");
}
//关闭流
reader.close();
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
/**
* @Method: read
* @Description: 读取数据库中的大文本数据
* @Anthor:孤傲苍狼
*
*/
@Test
public void read(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "select resume from testclob where id=2";
st = conn.prepareStatement(sql);
rs = st.executeQuery();
String contentStr ="";
String content = "";
if(rs.next()){
//使用resultSet.getString("字段名")获取大文本数据的内容
content = rs.getString("resume");
//使用resultSet.getCharacterStream("字段名")获取大文本数据的内容
Reader reader = rs.getCharacterStream("resume");
char buffer[] = new char[1024];
int len = 0;
FileWriter out = new FileWriter("D:\\1.txt");
while((len=reader.read(buffer))>0){
contentStr += new String(buffer);
out.write(buffer, 0, len);
}
out.close();
reader.close();
}
System.out.println(content);
System.out.println("-----------------------------------------------");
System.out.println(contentStr);
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码
四、使用JDBC处理MySQL的二进制数据
对于MySQL中的BLOB类型,可调用如下方法设置:
PreparedStatement. setBinaryStream(i, inputStream, length);
对MySQL中的BLOB类型,可调用如下方法获取:
InputStream in = resultSet.getBinaryStream(String columnLabel);
InputStream in = resultSet.getBlob(String columnLabel).getBinaryStream();
4.1、 测试范例
1、编写SQL测试脚本
create table testblob
(
id int primary key auto_increment,
image longblob
);
2、编写测试代码如下:
复制代码
package me.gacl.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: JdbcOperaClob
* @Description: 使用JDBC操作MySQL的二进制数据(例如图像、声音、二进制文)
* @author: 孤傲苍狼
* @date: 2014-9-19 下午10:10:04
*
*/
public class JdbcOperaBlob {
/**
* @Method: add
* @Description:向数据库中插入二进制数据
* @Anthor:孤傲苍狼
*
*/
@Test
public void add(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into testblob(image) values(?)";
st = conn.prepareStatement(sql);
//这种方式获取的路径,其中的空格会被使用“%20”代替
String path = JdbcOperaBlob.class.getClassLoader().getResource("01.jpg").getPath();
//将“%20”替换会空格
path = path.replaceAll("%20", " ");
File file = new File(path);
FileInputStream fis = new FileInputStream(file);//生成的流
st.setBinaryStream(1, fis,(int) file.length());
int num = st.executeUpdate();
if(num>0){
System.out.println("插入成功!!");
}
fis.close();
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
/**
* @Method: read
* @Description: 读取数据库中的二进制数据
* @Anthor:孤傲苍狼
*
*/
@Test
public void read() {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select image from testblob where id=?";
st = conn.prepareStatement(sql);
st.setInt(1, 1);
rs = st.executeQuery();
if (rs.next()) {
//InputStream in = rs.getBlob("image").getBinaryStream();//这种方法也可以
InputStream in = rs.getBinaryStream("image");
int len = 0;
byte buffer[] = new byte[1024];
FileOutputStream out = new FileOutputStream("D:\\1.jpg");
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码
关于使用JDBC处理MySQL大数据的内容就总结这么多!

(三十五)——使用JDBC处理Oracle大数据
一、Oracle中大数据处理
在Oracle中,LOB(Large Object,大型对象)类型的字段现在用得越来越多了。因为这种类型的字段,容量大(最多能容纳4GB的数据),且一个表中可以有多个这种类型的字段,很灵活,适用于数据 量非常大的业务领域(如图象、档案等)。
LOB类型分为BLOB和CLOB两种:BLOB即二进制大型对象(Binary Large Object),适用于存贮非文本的字节流数据(如程序、图象、影音等)。而CLOB,即字符型大型对象(Character Large Object),则与字符集相关,适于存贮文本型的数据(如历史档案、大部头著作等)。
二、搭建测试环境
2.1、建立两个测试用的数据库表
建表SQL语句为:
CREATE TABLE TEST_CLOB ( ID NUMBER(3), CLOBCOL CLOB)
CREATE TABLE TEST_BLOB ( ID NUMBER(3), BLOBCOL BLOB)
2.2、搭建测试项目架构

2.3、编写db.properties配置文件
oracleDb_Driver=oracle.jdbc.driver.OracleDriver
oracleDb_Url=jdbc:oracle:thin:@localhost:1521:GACL
oracleDb_UserName=GACL_XDP
oracleDb_Password=P
2.4、编写JdbcUtils工具类
复制代码
package me.gacl.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JdbcUtils {
private static String oracleDb_Driver = null;
private static String oracleDb_Url = null;
private static String oracleDb_UserName = null;
private static String oracleDb_Password = null;
static{
try{
//读取db.properties文件中的数据库连接信息
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(in);
//获取数据库连接驱动
oracleDb_Driver = prop.getProperty("oracleDb_Driver");
//获取数据库连接URL地址
oracleDb_Url = prop.getProperty("oracleDb_Url");
//获取数据库连接用户名
oracleDb_UserName = prop.getProperty("oracleDb_UserName");
//获取数据库连接密码
oracleDb_Password = prop.getProperty("oracleDb_Password");
//加载数据库驱动
Class.forName(oracleDb_Driver);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getOracleConnection
* @Description: 获取Oracle数据库连接对象
* @Anthor:孤傲苍狼
*
* @return Connection数据库连接对象
* @throws SQLException
*/
public static Connection getOracleConnection() throws SQLException{
return DriverManager.getConnection(oracleDb_Url, oracleDb_UserName,oracleDb_Password);
}
/**
* @Method: release
* @Description: 释放资源,
* 要释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//关闭Connection数据库连接对象
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
三、JDBC处理Oracle大数据
3.1、JDBC处理CLOB数据
复制代码
package me.gacl.demo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.junit.Test;
import me.gacl.utils.JdbcUtils;
/**
* @ClassName: JdbcOperaOracleClob
* @Description:Oracle中字符型大型对象(Character Large Object)数据处理
* @author: 孤傲苍狼
* @date: 2014-10-7 下午3:53:19
*
*/
public class JdbcOperaOracleClob {
/**
CREATE TABLE TEST_CLOB ( ID NUMBER(3), CLOBCOL CLOB)
*/
/**
* @Method: clobInsert
* @Description:往数据库中插入一个新的CLOB对象
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void clobInsert() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
conn = JdbcUtils.getOracleConnection();
boolean defaultCommit = conn.getAutoCommit();
/*开启事务,设定不自动提交 */
conn.setAutoCommit(false);
try {
/* 插入一个空的CLOB对象 */
String sql = "INSERT INTO TEST_CLOB VALUES (?, EMPTY_CLOB())";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
stmt.executeUpdate();
/* 查询此CLOB对象并锁定 */
sql = "SELECT CLOBCOL FROM TEST_CLOB WHERE ID=? FOR UPDATE";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
/* 取出此CLOB对象 */
oracle.sql.CLOB clob = (oracle.sql.CLOB) rs.getClob("CLOBCOL");
/* 向CLOB对象中写入数据 */
BufferedWriter out = new BufferedWriter(clob.getCharacterOutputStream());
//这种方式获取的路径,其中的空格会被使用“%20”代替
String path = JdbcOperaClob.class.getClassLoader().getResource("data.txt").getPath();
//将“%20”替换回空格
path = path.replaceAll("%20", " ");
BufferedReader in = new BufferedReader(new FileReader(path));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
System.out.println("插入成功");
} catch (Exception ex) {
/* 出错回滚 */
conn.rollback();
throw ex;
}finally{
/* 恢复原提交状态 */
conn.setAutoCommit(defaultCommit);
JdbcUtils.release(conn,stmt,rs);
}
}
/**
* @Method: clobRead
* @Description: CLOB对象读取
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void clobRead() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
conn = JdbcUtils.getOracleConnection();
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 查询CLOB对象 */
String sql = "SELECT * FROM TEST_CLOB WHERE ID=?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
/* 获取CLOB对象 */
oracle.sql.CLOB clob = (oracle.sql.CLOB) rs.getClob("CLOBCOL");
/* 以字符形式输出 */
BufferedReader in = new BufferedReader(clob.getCharacterStream());
BufferedWriter out = new BufferedWriter(new FileWriter("D:\\2.txt"));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
out.close();
in.close();
}
} catch (Exception ex) {
conn.rollback();
throw ex;
}finally{
/* 恢复原提交状态 */
conn.setAutoCommit(defaultCommit);
JdbcUtils.release(conn,stmt,rs);
}
}
/**
* @Method: clobModify
* @Description:修改CLOB对象(是在原CLOB对象基础上进行覆盖式的修改)
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void clobModify() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
conn = JdbcUtils.getOracleConnection();
boolean defaultCommit = conn.getAutoCommit();
// 开启事务
conn.setAutoCommit(false);
try {
/* 查询CLOB对象并锁定 */
String sql = "SELECT CLOBCOL FROM TEST_CLOB WHERE ID=? FOR UPDATE";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
/* 获取此CLOB对象 */
oracle.sql.CLOB clob = (oracle.sql.CLOB) rs.getClob("CLOBCOL");
/* 进行覆盖式修改 */
BufferedWriter out = new BufferedWriter(clob.getCharacterOutputStream());
// 这种方式获取的路径,其中的空格会被使用“%20”代替
String path = JdbcOperaClob.class.getClassLoader().getResource("data2.txt").getPath();
// 将“%20”替换回空格
path = path.replaceAll("%20", " ");
BufferedReader in = new BufferedReader(new FileReader(path));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.close();
}
/*提交事务 */
conn.commit();
} catch (Exception ex) {
/*出错回滚事务 */
conn.rollback();
throw ex;
}finally{
/*恢复原提交状态 */
conn.setAutoCommit(defaultCommit);
JdbcUtils.release(conn,stmt,rs);
}
}
/**
* @Method: clobReplace
* @Description:替换CLOB对象(将原CLOB对象清除,换成一个全新的CLOB对象)
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void clobReplace() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
conn = JdbcUtils.getOracleConnection();
boolean defaultCommit = conn.getAutoCommit();
// 开启事务
conn.setAutoCommit(false);
try {
/* 清空原CLOB对象 */
String sql = "UPDATE TEST_CLOB SET CLOBCOL=EMPTY_CLOB() WHERE ID=?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
stmt.executeUpdate();
/* 查询CLOB对象并锁定 */
sql = "SELECT CLOBCOL FROM TEST_CLOB WHERE ID=? FOR UPDATE";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
/* 获取此CLOB对象 */
oracle.sql.CLOB clob = (oracle.sql.CLOB) rs.getClob("CLOBCOL");
/* 更新数据 */
BufferedWriter out = new BufferedWriter(clob.getCharacterOutputStream());
// 这种方式获取的路径,其中的空格会被使用“%20”代替
String path = JdbcOperaClob.class.getClassLoader().getResource("db.properties").getPath();
// 将“%20”替换回空格
path = path.replaceAll("%20", " ");
BufferedReader in = new BufferedReader(new FileReader(path));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出错回滚 */
conn.rollback();
throw ex;
} finally {
/* 恢复原提交状态 */
conn.setAutoCommit(defaultCommit);
JdbcUtils.release(conn, stmt, rs);
}
}
}
复制代码
3.2、JDBC处理BLOB数据
Oracle定义了一个BLOB字段用于保存二进制数据,但这个字段并不能存放真正的二进制数据,只能向这个字段存一个指针,然后把数据放到指针所指向的Oracle的LOB段中, LOB段是在数据库内部表的一部分。因而在操作Oracle的Blob之前,必须获得指针(定位器)才能进行Blob数据的读取和写入。
如何获得表中的Blob指针呢? 可以先使用insert语句向表中插入一个空的blob(调用oracle的函数empty_blob()),这将创建一个blob的指针,然后再把这个empty的blob的指针查询出来,这样就可得到BLOB对象,从而读写blob数据了。
1、插入空blob:insert into testblob(id,image) values(?,empty_blob())
2、获得blob的cursor:
select image from testblob where id=? for update 注意: 必 须加for update锁定该行,直至该行被修改完毕,保证不产生并发冲突。
Blob b = rs.getBlob("image");

3、利用 io和获取到的cursor往数据库读写数据
注意:以上操作需开启事务。
BLOB对象的存取范例
复制代码
package me.gacl.demo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.junit.Test;
import me.gacl.utils.JdbcUtils;
/**
* @ClassName: JdbcOperaOracleBlob
* @Description:Oracle中大数据处理
* @author: 孤傲苍狼
* @date: 2014-10-7 下午3:53:19
*
*/
public class JdbcOperaOracleBlob {
/**
* @Method: blobInsert
* @Description: 向数据库中插入一个新的BLOB对象
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void blobInsert() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
boolean defaultCommit = true;
try {
conn = JdbcUtils.getOracleConnection();
//得到数据库事务处理的默认提交方式
defaultCommit = conn.getAutoCommit();
//1、开启事务
conn.setAutoCommit(false);
//2、插入一个空的BLOB对象
String sql = "INSERT INTO TEST_BLOB VALUES (?, EMPTY_BLOB())";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
stmt.executeUpdate();
//3、查询此BLOB对象并锁定。注意: 必 须加for update锁定该行,直至该行被修改完毕,保证不产生并发冲突
sql = "SELECT BLOBCOL FROM TEST_BLOB WHERE ID=? FOR UPDATE";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
//4、取出此BLOB对象 ,并强制转换成Oracle的BLOB对象
oracle.sql.BLOB blob = (oracle.sql.BLOB) rs.getBlob("BLOBCOL");
//5、使用IO向BLOB对象中写入数据
BufferedOutputStream out = new BufferedOutputStream(blob.getBinaryOutputStream());
BufferedInputStream in = new BufferedInputStream(JdbcOperaOracleBlob.class.getClassLoader().getResourceAsStream("01.jpg"));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.close();
}
//6、提交事务
conn.commit();
} catch (Exception ex) {
//7、出错回滚事务
conn.rollback();
throw ex;
} finally {
//8、恢复数据库事务处理的默认提交方式
conn.setAutoCommit(defaultCommit);
//释放资源
JdbcUtils.release(conn, stmt, rs);
}
}
/**
* @Method: blobModify
* @Description:修改BLOB对象(是在原BLOB对象基础上进行覆盖式的修改)
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void blobModify() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
boolean defaultCommit = true;
try {
conn = JdbcUtils.getOracleConnection();
//得到数据库事务处理的默认提交方式
defaultCommit = conn.getAutoCommit();
//1、开启事务
conn.setAutoCommit(false);
//2、查询此BLOB对象并锁定。注意: 必 须加for update锁定该行,直至该行被修改完毕,保证不产生并发冲突
String sql = "SELECT BLOBCOL FROM TEST_BLOB WHERE ID=? FOR UPDATE";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
//3、取出此BLOB对象 ,并强制转换成Oracle的BLOB对象
oracle.sql.BLOB blob = (oracle.sql.BLOB) rs.getBlob("BLOBCOL");
//4、使用IO向BLOB对象中写入数据
BufferedOutputStream out = new BufferedOutputStream(blob.getBinaryOutputStream());
BufferedInputStream in = new BufferedInputStream(JdbcOperaOracleBlob.class.getClassLoader().getResourceAsStream("02.jpg"));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.close();
}
//5、提交事务
conn.commit();
} catch (Exception ex) {
//6、出错回滚事务
conn.rollback();
throw ex;
} finally {
//8、恢复数据库事务处理的默认提交方式
conn.setAutoCommit(defaultCommit);
//释放资源
JdbcUtils.release(conn, stmt, rs);
}
}
/**
* @Method: blobReplace
* @Description:替换BLOB对象(将原BLOB对象清除,换成一个全新的BLOB对象)
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void blobReplace() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
boolean defaultCommit = true;
try {
conn = JdbcUtils.getOracleConnection();
//得到数据库事务处理的默认提交方式
defaultCommit = conn.getAutoCommit();
//1、开启事务
conn.setAutoCommit(false);
//2、清空原BLOB对象
String sql = "UPDATE TEST_BLOB SET BLOBCOL=EMPTY_BLOB() WHERE ID=?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
stmt.executeUpdate();
//3、查询此BLOB对象并锁定。注意: 必 须加for update锁定该行,直至该行被修改完毕,保证不产生并发冲突
sql = "SELECT BLOBCOL FROM TEST_BLOB WHERE ID=? FOR UPDATE";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
//4、取出此BLOB对象 ,并强制转换成Oracle的BLOB对象
oracle.sql.BLOB blob = (oracle.sql.BLOB) rs.getBlob("BLOBCOL");
//5、使用IO向BLOB对象中写入数据
BufferedOutputStream out = new BufferedOutputStream(blob.getBinaryOutputStream());
BufferedInputStream in = new BufferedInputStream(JdbcOperaOracleBlob.class.getClassLoader().getResourceAsStream("01.jpg"));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.close();
}
//6、提交事务
conn.commit();
} catch (Exception ex) {
//7、出错回滚事务
conn.rollback();
throw ex;
} finally {
//8、恢复数据库事务处理的默认提交方式
conn.setAutoCommit(defaultCommit);
//释放资源
JdbcUtils.release(conn, stmt, rs);
}
}
/**
* @Method: blobRead
* @Description:BLOB对象读取
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void blobRead() throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
boolean defaultCommit = true;
try {
conn = JdbcUtils.getOracleConnection();
//得到数据库事务处理的默认提交方式
defaultCommit = conn.getAutoCommit();
//1、开启事务
conn.setAutoCommit(false);
//2、查询BLOB对象
String sql = "SELECT BLOBCOL FROM TEST_BLOB WHERE ID=?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
rs = stmt.executeQuery();
if (rs.next()) {
//3、取出此BLOB对象 ,并强制转换成Oracle的BLOB对象
oracle.sql.BLOB blob = (oracle.sql.BLOB) rs.getBlob("BLOBCOL");
//4、以二进制流的形式输出
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("D:/1.jpg"));
BufferedInputStream in = new BufferedInputStream(blob.getBinaryStream());
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.close();
}
//5、提交事务
conn.commit();
} catch (Exception ex) {
//6、出错回滚事务
conn.rollback();
throw ex;
} finally {
//8、恢复数据库事务处理的默认提交方式
conn.setAutoCommit(defaultCommit);
//释放资源
JdbcUtils.release(conn, stmt, rs);
}
}
}
复制代码
四、使用JDBC处理Oracle大数据总结
通过JDBC操纵Oracle数据库的LOB字段,不外乎插入、修改、替换、读取四种方式,掌握起来并不难。观察上述程序对LOB类型字段的存取,我们可以看出,较之其它类型字段,有下面几个显著不同的特点:
1、必须取消自动提交。
存取操作开始前,必须用setAutoCommit(false)取消自动提交。其它类型字段则无此特殊要求。这是因为存取LOB类型字段时,通常要进行多次操作可以完成。不这样的话,Oracle将抛出“读取违反顺序”的错误。
2、插入方式不同。
LOB数据不能象其它类型数据一样直接插入(INSERT)。插入前必须先插入一个空的LOB对象,CLOB类型 的空对象为EMPTY_CLOB(),BLOB类型的空对象为EMPTY_BLOB()。之后通过SELECT命令查询得到先前插入的记录并锁定,继而将 空对象修改为所要插入的LOB对象。
3、修改方式不同。
其它类型的字段修改时,用UPDATE … SET…命令即可。而LOB类型字段,则只能用SELECT … FOR UPDATE命令将记录查询出来并锁定,然后才能修改。且修改也有两种改法:一是在原数据基础上的修改(即覆盖式修改),执行SELECT … FOR UPDATE后再改数据;二是替换(先将原数据清掉,再修改),先执行UPDATE命令将LOB字段之值设为空的LOB对象,然后进行第一种改法。建议使 用替换的方法,以实现与其它字段UPDATE操作后一样的效果。
4、存取时应使用由数据库JDBC驱动程序提供的LOB操作类。
对于Oracle数据库,应使用oracle.sql.CLOB和oracle.sql.BLOB。不使用由数据库JDBC驱动程序提供的LOB类时,程序运行时易于出现“抽象方法调用”的错误,这是因为JDBC所定义的java.sql.Clob与 java.sql.Blob接口,其中的一些方法并未在数据库厂家提供的驱动程序中真正实现。
5、存取手段与文件操作相仿。
对于BLOB类型,应用InputStream/OutputStream类,此类不进行编码转换,逐个字节存取。oracle.sql.BLOB类相应提供了getBinaryStream()和getBinaryOutputStream()两个方法,前一个 方法用于读取Oracle的BLOB字段,后一个方法用于将数据写入Oracle的BLOB字段。
对于CLOB类型,应用Reader/Writer类,此类进行编码转换。oracle.sql.CLOB类相应 提供了getCharacterStream()和getCharacterOutputStream()两个方法,前一个方法用于读取Oracle的 CLOB字段,后一个方法用于将数据写入Oracle的CLOB字段。
需要说明的是,为了大幅提高程序执行效率,对BLOB/CLOB字段的读写操作,应该使用缓冲操作类(带 Buffered前缀),即:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。 例程中全部使用了缓冲操作类。

(三十六)——使用JDBC进行批处理
在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
JDBC实现批处理有两种方式:statement和preparedstatement
一、使用Statement完成批处理
1、使用Statement对象添加要批量执行SQL语句,如下:
Statement.addBatch(sql1);
Statement.addBatch(sql2);
Statement.addBatch(sql3);
2、执行批处理SQL语句:Statement.executeBatch();
3、清除批处理命令:Statement.clearBatch();
1.1、使用Statement完成批处理范例
1、编写测试的SQL脚本创建表
复制代码
create table testbatch
(
id int primary key,
name varchar(20)
);
复制代码
2、编写测试代码,如下所示:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: JdbcBatchHandleByStatement
* @Description: 使用Statement实现JDBC批处理操作
* @author: 孤傲苍狼
* @date: 2014-9-20 下午10:05:45
*
*/
public class JdbcBatchHandleByStatement {
@Test
public void testJdbcBatchHandleByStatement(){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql1 = "insert into testbatch(id,name) values(1,‘aaa‘)";
String sql2 = "insert into testbatch(id,name) values(2,‘bbb‘)";
String sql3 = "insert into testbatch(id,name) values(3,‘CCC‘)";
String sql4 = "insert into testbatch(id,name) values(4,‘DDD‘)";
String sql5 = "update testbatch set name=‘gacl‘ where id=1";
String sql6 = "insert into testbatch(id,name) values(5,‘FFF‘)";
String sql7 = "delete from testbatch where id=2";
st = conn.createStatement();
//添加要批量执行的SQL
st.addBatch(sql1);
st.addBatch(sql2);
st.addBatch(sql3);
st.addBatch(sql4);
st.addBatch(sql5);
st.addBatch(sql6);
st.addBatch(sql7);
//执行批处理SQL语句
st.executeBatch();
//清除批处理命令
st.clearBatch();
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码
1.2、采用Statement.addBatch(sql)方式实现批处理的优缺点
采用Statement.addBatch(sql)方式实现批处理:
优点:可以向数据库发送多条不同的SQL语句。
缺点:SQL语句没有预编译。
当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句。例如:
Insert into user(name,password) values(‘aa‘,‘111‘);
Insert into user(name,password) values(‘bb‘,‘222‘);
Insert into user(name,password) values(‘cc‘,‘333‘);
Insert into user(name,password) values(‘dd‘,‘444‘);
二、使用PreparedStatement完成批处理
2.1、使用PreparedStatement完成批处理范例
测试代码如下:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: JdbcBatchHandleByStatement
* @Description: 使用prepareStatement实现JDBC批处理操作
* @author: 孤傲苍狼
* @date: 2014-9-20 下午10:05:45
*
*/
public class JdbcBatchHandleByPrepareStatement {
@Test
public void testJdbcBatchHandleByPrepareStatement(){
long starttime = System.currentTimeMillis();
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into testbatch(id,name) values(?,?)";
st = conn.prepareStatement(sql);
for(int i=1;i<1000008;i++){ //i=1000 2000
st.setInt(1, i);
st.setString(2, "aa" + i);
st.addBatch();
if(i%1000==0){
st.executeBatch();
st.clearBatch();
}
}
st.executeBatch();
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
long endtime = System.currentTimeMillis();
System.out.println("程序花费时间:" + (endtime-starttime)/1000 + "秒!!");
}
}
复制代码
2.2、采用PreparedStatement.addBatch()方式实现批处理的优缺点
采用PreparedStatement.addBatch()实现批处理
优点:发送的是预编译后的SQL语句,执行效率高。
缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
关于JDBC批处理的内容就总结这么多。

(三十七)——获得MySQL数据库自动生成的主键
测试脚本如下:
复制代码
create table test1
(
id int primary key auto_increment,
name varchar(20)
);
复制代码
测试代码:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import me.gacl.utils.JdbcUtils;
public class Test {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into test1(name) values(?)";
st = conn.prepareStatement(sql);
st.setString(1, "aaa");
st.executeUpdate();
//获取数据库自动生成的主键
rs = st.getGeneratedKeys();
if(rs.next()){
System.out.println(rs.getInt(1));
}
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码

(三十八)——事务
一、事务的概念
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
例如:A——B转帐,对应于如下两条sql语句
update from account set money=money+100 where name=‘B‘;
update from account set money=money-100 where name=‘A‘;
二、MySQL数据库中操作事务命令
1、编写测试SQL脚本,如下:
复制代码
/*创建账户表*/
create table account(
id int primary key auto_increment,
name varchar(40),
money float
);
/*插入测试数据*/
insert into account(name,money) values(‘A‘,1000);
insert into account(name,money) values(‘B‘,1000);
insert into account(name,money) values(‘C‘,1000);
复制代码
下面我们在MySQL数据库中模拟A——B转帐这个业务场景
2.1、开启事务(start transaction)
使用"start transaction"开启MySQL数据库的事务,如下所示:

我们首先在数据库中模拟转账失败的场景,首先执行update语句让A用户的money减少100块钱,如下图所示:

然后我们关闭当前操作的dos命令行窗口,这样就导致了刚才执行的update语句的数据库的事务没有被提交,那么我们对A用户的修改就不算是是真正的修改了,下次在查询A用户的money时,依然还是之前的1000,如下图所示:

2.2、提交事务(commit)
下面我们在数据库模拟A——B转账成功的场景

我们手动提交(commit)数据库事务之后,A——B转账100块钱的这个业务操作算是真正成功了,A账户中少了100,B账户中多了100。
2.3、回滚事务(rollback)

通过手动回滚事务,让所有的操作都失效,这样数据就会回到最初的初始状态!
三、JDBC中使用事务
当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列的JDBC控制事务语句
Connection.setAutoCommit(false);//开启事务(start transaction)
Connection.rollback();//回滚事务(rollback)
Connection.commit();//提交事务(commit)
3.1、JDBC使用事务范例
在JDBC代码中演示银行转帐案例,使如下转帐操作在同一事务中执行
"update account set money=money-100 where name=‘A‘"
update account set money=money+100 where name=‘B‘
代码如下:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: TransactionDemo1
* @Description:
* JDBC中使用事务来模似转帐
create table account(
id int primary key auto_increment,
name varchar(40),
money float
);
insert into account(name,money) values(‘A‘,1000);
insert into account(name,money) values(‘B‘,1000);
insert into account(name,money) values(‘C‘,1000);
* @author: 孤傲苍狼
* @date: 2014-9-22 下午11:16:17
*
*/
public class TransactionDemo1 {
/**
* @Method: testTransaction1
* @Description: 模拟转账成功时的业务场景
* @Anthor:孤傲苍狼
*
*/
@Test
public void testTransaction1(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name=‘A‘";
st = conn.prepareStatement(sql1);
st.executeUpdate();
String sql2 = "update account set money=money+100 where name=‘B‘";
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
System.out.println("成功!!!"); //log4j
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
/**
* @Method: testTransaction1
* @Description: 模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库自动回滚事务
* @Anthor:孤傲苍狼
*
*/
@Test
public void testTransaction2(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name=‘A‘";
st = conn.prepareStatement(sql1);
st.executeUpdate();
//用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作
int x = 1/0;
String sql2 = "update account set money=money+100 where name=‘B‘";
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
System.out.println("成功!!!");
}catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
/**
* @Method: testTransaction1
* @Description: 模拟转账过程中出现异常导致有一部分SQL执行失败时手动通知数据库回滚事务
* @Anthor:孤傲苍狼
*
*/
@Test
public void testTransaction3(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name=‘A‘";
st = conn.prepareStatement(sql1);
st.executeUpdate();
//用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交
int x = 1/0;
String sql2 = "update account set money=money+100 where name=‘B‘";
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
System.out.println("成功!!!");
}catch (Exception e) {
try {
//捕获到异常之后手动通知数据库执行回滚事务的操作
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码
3.2、设置事务回滚点
在开发中,有时候可能需要手动设置事务的回滚点,在JDBC中使用如下的语句设置事务回滚点
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
Conn.commit();//回滚后必须通知数据库提交事务
设置事务回滚点范例:
复制代码
package me.gacl.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;
import me.gacl.utils.JdbcUtils;
import org.junit.Test;
/**
* @ClassName: TransactionDemo1
* @Description:
* JDBC中使用事务来模似转帐
create table account(
id int primary key auto_increment,
name varchar(40),
money float
);
insert into account(name,money) values(‘A‘,1000);
insert into account(name,money) values(‘B‘,1000);
insert into account(name,money) values(‘C‘,1000);
* @author: 孤傲苍狼
* @date: 2014-9-22 下午11:16:17
*
*/
public class TransactionDemo2 {
/**
* @Method: testTransaction1
* @Description: 模拟转账成功时的业务场景
* @Anthor:孤傲苍狼
*
*/
@Test
public void testTransaction1(){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
Savepoint sp = null;
try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name=‘A‘";
st = conn.prepareStatement(sql1);
st.executeUpdate();
//设置事务回滚点
sp = conn.setSavepoint();
String sql2 = "update account set money=money+100 where name=‘B‘";
st = conn.prepareStatement(sql2);
st.executeUpdate();
//程序执行到这里出现异常,后面的sql3语句执行将会中断
int x = 1/0;
String sql3 = "update account set money=money+100 where name=‘C‘";
st = conn.prepareStatement(sql3);
st.executeUpdate();
conn.commit();
}catch (Exception e) {
try {
/**
* 我们在上面向数据库发送了3条update语句,
* sql3语句由于程序出现异常导致无法正常执行,数据库事务而已无法正常提交,
* 由于设置的事务回滚点是在sql1语句正常执行完成之后,sql2语句正常执行之前,
* 那么通知数据库回滚事务时,不会回滚sql1执行的update操作
* 只会回滚到sql2执行的update操作,也就是说,上面的三条update语句中,sql1这条语句的修改操作起作用了
* sql2的修改操作由于事务回滚没有起作用,sql3由于程序异常没有机会执行
*/
conn.rollback(sp);//回滚到设置的事务回滚点
//回滚了要记得通知数据库提交事务
conn.commit();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}
复制代码
四、事务的四大特性(ACID)
4.1、原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败
4.2、一致性(Consistency)
官网上事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。
4.3、隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
4.4、持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
事务的四大特性中最麻烦的是隔离性,下面重点介绍一下事务的隔离级别
五、事务的隔离级别
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。
5.1、事务不考虑隔离性可能会引发的问题
如果事务不考虑隔离性,可能会引发如下问题:
1、脏读
 脏读指一个事务读取了另外一个事务未提交的数据。
 这是非常危险的,假设A向B转帐100元,对应sql语句如下所示
1.update account set money=money+100 where name=‘B‘;
2.update account set money=money-100 where name=‘A‘;
  当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的帐户,就会发现自己多了100元钱。如果A等B走后再回滚,B就会损失100元。
2、不可重复读
不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。
例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户内存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。
3、虚读(幻读)
虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。
5.2、事务隔离性的设置语句
MySQL数据库共定义了四种隔离级别:
Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
Read committed(读已提交):可避免脏读情况发生。
Read uncommitted(读未提交):最低级别,以上情况均无法保证。
mysql数据库查询当前事务隔离级别:select @@tx_isolation
例如:

mysql数据库默认的事务隔离级别是:Repeatable read(可重复读)
mysql数据库设置事务隔离级别:set transaction isolation level 隔离级别名
例如:

5.3、使用MySQL数据库演示不同隔离级别下的并发问题
同时打开两个窗口模拟2个用户并发访问数据库
1、当把事务的隔离级别设置为read uncommitted时,会引发脏读、不可重复读和虚读
A窗口
set transaction isolation level read uncommitted;--设置A用户的数据库隔离级别为Read uncommitted(读未提交)
start transaction;--开启事务
select * from account;--查询A账户中现有的钱,转到B窗口进行操作
select * from account--发现a多了100元,这时候A读到了B未提交的数据(脏读)
B窗口
start transaction;--开启事务
update account set money=money+100 where name=‘A‘;--不要提交,转到A窗口查询
2、当把事务的隔离级别设置为read committed时,会引发不可重复读和虚读,但避免了脏读
A窗口
set transaction isolation level read committed;
start transaction;
select * from account;--发现a帐户是1000元,转到b窗口
select * from account;--发现a帐户多了100,这时候,a读到了别的事务提交的数据,两次读取a帐户读到的是不同的结果(不可重复读)
B窗口
start transaction;
update account set money=money+100 where name=‘aaa‘;
commit;--转到a窗口
3、当把事务的隔离级别设置为repeatable read(mysql默认级别)时,会引发虚读,但避免了脏读、不可重复读
A窗口
set transaction isolation level repeatable read;
start transaction;
select * from account;--发现表有4个记录,转到b窗口
select * from account;--可能发现表有5条记录,这时候发生了a读取到另外一个事务插入的数据(虚读)
B窗口
start transaction;
insert into account(name,money) values(‘ggg‘,1000);
commit;--转到a窗口
4、当把事务的隔离级别设置为Serializable时,会避免所有问题
A窗口
set transaction isolation level Serializable;
start transaction;
select * from account;--转到b窗口
B窗口
start transaction;
insert into account(name,money) values(‘ggg‘,1000);--发现不能插入,只能等待a结束事务才能插入

(三十九)——数据库连接池
一、应用程序直接获取数据库连接的缺点
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。如下图所示:

二、使用数据库连接池优化程序性能
2.1、数据库连接池的基本概念
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出.对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标.数据库连接池正式针对这个问题提出来的.数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。如下图所示:

数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的.无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量.连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中.
数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放.
2.2、编写数据库连接池
编写连接池需实现java.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:
Connection getConnection()
Connection getConnection(String username, String password)
实现DataSource接口,并实现连接池功能的步骤:
在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中。
实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。
当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把conn还给数据库。Collection保证将自己返回到LinkedList中是此处编程的难点。
数据库连接池核心代码
使用动态代理技术构建连接池中的connection
复制代码
proxyConn = (Connection) Proxy.newProxyInstance(this.getClass()
.getClassLoader(), conn.getClass().getInterfaces(),
new InvocationHandler() {
//此处为内部类,当close方法被调用时将conn还回池中,其它方法直接执行
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if (method.getName().equals("close")) {
pool.addLast(conn);
return null;
}
return method.invoke(conn, args);
}
});
复制代码
数据库连接池编写范例:
复制代码
package me.gacl.demo;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Properties;
import javax.sql.DataSource;
/**
* @ClassName: JdbcPool
* @Description:编写数据库连接池
* @author: 孤傲苍狼
* @date: 2014-9-30 下午11:07:23
*
*/
public class JdbcPool implements DataSource{
/**
* @Field: listConnections
* 使用LinkedList集合来存放数据库链接,
* 由于要频繁读写List集合,所以这里使用LinkedList存储数据库连接比较合适
*/
private static LinkedList<Connection> listConnections = new LinkedList<Connection>();
static{
//在静态代码块中加载db.properties数据库配置文件
InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
try {
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
//数据库连接池的初始化连接数大小
int jdbcPoolInitSize =Integer.parseInt(prop.getProperty("jdbcPoolInitSize"));
//加载数据库驱动
Class.forName(driver);
for (int i = 0; i < jdbcPoolInitSize; i++) {
Connection conn = DriverManager.getConnection(url, username, password);
System.out.println("获取到了链接" + conn);
//将获取到的数据库连接加入到listConnections集合中,listConnections集合此时就是一个存放了数据库连接的连接池
listConnections.add(conn);
}
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public PrintWriter getLogWriter() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
/* 获取数据库连接
* @see javax.sql.DataSource#getConnection()
*/
@Override
public Connection getConnection() throws SQLException {
//如果数据库连接池中的连接对象的个数大于0
if (listConnections.size()>0) {
//从listConnections集合中获取一个数据库连接
final Connection conn = listConnections.removeFirst();
System.out.println("listConnections数据库连接池大小是" + listConnections.size());
//返回Connection对象的代理对象
return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(!method.getName().equals("close")){
return method.invoke(conn, args);
}else{
//如果调用的是Connection对象的close方法,就把conn还给数据库连接池
listConnections.add(conn);
System.out.println(conn + "被还给listConnections数据库连接池了!!");
System.out.println("listConnections数据库连接池大小为" + listConnections.size());
return null;
}
}
});
}else {
throw new RuntimeException("对不起,数据库忙");
}
}
@Override
public Connection getConnection(String username, String password)
throws SQLException {
return null;
}
}
复制代码
db.properties配置文件如下:
复制代码
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy
username=root
password=XDP
jdbcPoolInitSize=10
复制代码
写一个JdbcUtil测试数据库连接池
复制代码
package me.gacl.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import me.gacl.demo.JdbcPool;
public class JdbcUtil {
/**
* @Field: pool
* 数据库连接池
*/
private static JdbcPool pool = new JdbcPool();
/**
* @Method: getConnection
* @Description: 从数据库连接池中获取数据库连接对象
* @Anthor:孤傲苍狼
* @return Connection数据库连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
return pool.getConnection();
}
/**
* @Method: release
* @Description: 释放资源,
* 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//关闭Connection数据库连接对象
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
三、开源数据库连接池
现在很多WEB服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
也有一些开源组织提供了数据源的独立实现:
DBCP 数据库连接池
C3P0 数据库连接池
在使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。
3.1、DBCP数据源
DBCP 是 Apache 软件基金组织下的开源连接池实现,要使用DBCP数据源,需要应用程序应在系统中增加如下两个 jar 文件:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
3.2、在应用程序中加入dbcp连接池
1.导入相关jar包
commons-dbcp-1.2.2.jar、commons-pool.jar
2、在类目录下加入dbcp的配置文件:dbcpconfig.properties
dbcpconfig.properties的配置信息如下:
复制代码
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy
username=root
password=XDP
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8
#指定由连接池所创建的连接的自动提交(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
复制代码
如下图所示:

3、在获取数据库连接的工具类(如jdbcUtils)的静态代码块中创建池
复制代码
package me.gacl.util;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/**
* @ClassName: JdbcUtils_DBCP
* @Description: 数据库连接工具类
* @author: 孤傲苍狼
* @date: 2014-10-4 下午6:04:36
*
*/
public class JdbcUtils_DBCP {
/**
* 在java中,编写数据库连接池需实现java.sql.DataSource接口,每一种数据库连接池都是DataSource接口的实现
* DBCP连接池就是java.sql.DataSource接口的一个具体实现
*/
private static DataSource ds = null;
//在静态代码块中创建数据库连接池
static{
try{
//加载dbcpconfig.properties配置文件
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties prop = new Properties();
prop.load(in);
//创建数据源
ds = BasicDataSourceFactory.createDataSource(prop);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getConnection
* @Description: 从数据源中获取数据库连接
* @Anthor:孤傲苍狼
* @return Connection
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
//从数据源中获取数据库连接
return ds.getConnection();
}
/**
* @Method: release
* @Description: 释放资源,
* 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//将Connection连接对象还给数据库连接池
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
测试DBCP数据源
复制代码
package me.gacl.test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.junit.Test;
import me.gacl.util.JdbcUtils_DBCP;
public class DataSourceTest {
@Test
public void dbcpDataSourceTest() {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
//获取数据库连接
conn = JdbcUtils_DBCP.getConnection();
String sql = "insert into test1(name) values(?)";
st = conn.prepareStatement(sql);
st.setString(1, "gacl");
st.executeUpdate();
//获取数据库自动生成的主键
rs = st.getGeneratedKeys();
if(rs.next()){
System.out.println(rs.getInt(1));
}
}catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
JdbcUtils_DBCP.release(conn, st, rs);
}
}
}
复制代码
3.3、C3P0数据源
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0数据源在项目开发中使用得比较多。
c3p0与dbcp区别
dbcp没有自动回收空闲连接的功能
c3p0有自动回收空闲连接功能
3.4、在应用程序中加入C3P0连接池
1.导入相关jar包
c3p0-0.9.2-pre1.jar、mchange-commons-0.2.jar,如果操作的是Oracle数据库,那么还需要导入c3p0-oracle-thin-extras-0.9.2-pre1.jar
2、在类目录下加入C3P0的配置文件:c3p0-config.xml
c3p0-config.xml的配置信息如下:
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!--
c3p0-config.xml必须位于类路径下面
private static ComboPooledDataSource ds;
static{
try {
ds = new ComboPooledDataSource("MySQL");
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
-->
<c3p0-config>
<!--
C3P0的缺省(默认)配置,
如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource();”这样写就表示使用的是C3P0的缺省(默认)配置信息来创建数据源
-->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
<property name="user">root</property>
<property name="password">XDP</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
<!--
C3P0的命名配置,
如果在代码中“ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");”这样写就表示使用的是name是MySQL的配置信息来创建数据源
-->
<named-config name="MySQL">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
<property name="user">root</property>
<property name="password">XDP</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</named-config>
</c3p0-config>
复制代码
如下图所示:

3、在获取数据库连接的工具类(如jdbcUtils)的静态代码块中创建池
复制代码
package me.gacl.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* @ClassName: JdbcUtils_C3P0
* @Description: 数据库连接工具类
* @author: 孤傲苍狼
* @date: 2014-10-4 下午6:04:36
*
*/
public class JdbcUtils_C3P0 {
private static ComboPooledDataSource ds = null;
//在静态代码块中创建数据库连接池
static{
try{
//通过代码创建C3P0数据库连接池
/*ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");
ds.setUser("root");
ds.setPassword("XDP");
ds.setInitialPoolSize(10);
ds.setMinPoolSize(5);
ds.setMaxPoolSize(20);*/
//通过读取C3P0的xml配置文件创建数据源,C3P0的xml配置文件c3p0-config.xml必须放在src目录下
//ds = new ComboPooledDataSource();//使用C3P0的默认配置来创建数据源
ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置来创建数据源
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getConnection
* @Description: 从数据源中获取数据库连接
* @Anthor:孤傲苍狼
* @return Connection
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
//从数据源中获取数据库连接
return ds.getConnection();
}
/**
* @Method: release
* @Description: 释放资源,
* 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//将Connection连接对象还给数据库连接池
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
测试C3P0数据源
复制代码
package me.gacl.test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.junit.Test;
import me.gacl.util.JdbcUtils_C3P0;
import me.gacl.util.JdbcUtils_DBCP;
public class DataSourceTest {
@Test
public void c3p0DataSourceTest() {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
//获取数据库连接
conn = JdbcUtils_C3P0.getConnection();
String sql = "insert into test1(name) values(?)";
st = conn.prepareStatement(sql);
st.setString(1, "gacl");
st.executeUpdate();
//获取数据库自动生成的主键
rs = st.getGeneratedKeys();
if(rs.next()){
System.out.println(rs.getInt(1));
}
}catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
JdbcUtils_C3P0.release(conn, st, rs);
}
}
}
复制代码
四、配置Tomcat数据源
在实际开发中,我们有时候还会使用服务器提供给我们的数据库连接池,比如我们希望Tomcat服务器在启动的时候可以帮我们创建一个数据库连接池,那么我们在应用程序中就不需要手动去创建数据库连接池,直接使用Tomcat服务器创建好的数据库连接池即可。要想让Tomcat服务器在启动的时候帮我们创建一个数据库连接池,那么需要简单配置一下Tomcat服务器。
4.1、JNDI技术简介
JNDI(Java Naming and Directory Interface),Java命名和目录接口,它对应于J2SE中的javax.naming包,
这 套API的主要作用在于:它可以把Java对象放在一个容器中(JNDI容器),并为容器中的java对象取一个名称,以后程序想获得Java对象,只需 通过名称检索即可。其核心API为Context,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象。
Tomcat服务器创建的数据源是以JNDI资源的形式发布的,所以说在Tomat服务器中配置一个数据源实际上就是在配置一个JNDI资源,通过查看Tomcat文档,我们知道使用如下的方式配置tomcat服务器的数据源:
复制代码
<Context>
<Resource name="jdbc/datasource" auth="Container"
type="javax.sql.DataSource" username="root" password="XDP"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/jdbcstudy"
maxActive="8" maxIdle="4"/>
</Context>
复制代码
服务器创建好数据源之后,我们的应用程序又该怎么样得到这个数据源呢,Tomcat服务器创建好数据源之后是以JNDI的形式绑定到一个JNDI容器中的,我们可以把JNDI想象成一个大大的容器,我们可以往这个容器中存放一些对象,一些资源,JNDI容器中存放的对象和资源都会有一个独一无二的名称,应用程序想从JNDI容器中获取资源时,只需要告诉JNDI容器要获取的资源的名称,JNDI根据名称去找到对应的资源后返回给应用程序。我们平时做javaEE开发时,服务器会为我们的应用程序创建很多资源,比如request对象,response对象,服务器创建的这些资源有两种方式提供给我们的应用程序使用:第一种是通过方法参数的形式传递进来,比如我们在Servlet中写的doPost和doGet方法中使用到的request对象和response对象就是服务器以参数的形式传递给我们的。第二种就是JNDI的方式,服务器把创建好的资源绑定到JNDI容器中去,应用程序想要使用资源时,就直接从JNDI容器中获取相应的资源即可。
对于上面的name="jdbc/datasource"数据源资源,在应用程序中可以用如下的代码去获取
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
此种配置下,数据库的驱动jar文件需放置在tomcat的lib下

4.2、配置Tomcat数据源
1、在Web项目的WebRoot目录下的META-INF目录创建一个context.xml文件
如下图所示:

2、在context.xml文件配置tomcat服务器的数据源
复制代码
<Context>
<Resource
name="jdbc/datasource"
auth="Container"
type="javax.sql.DataSource"
username="root"
password="XDP"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/jdbcstudy"
maxActive="8"
maxIdle="4"/>
</Context>
复制代码
3、将数据库的驱动jar文件需放置在tomcat的lib下

4、在获取数据库连接的工具类(如jdbcUtils)的静态代码块中获取JNDI容器中的数据源
复制代码
package me.gacl.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
/**
* @ClassName: JdbcUtils_DBCP
* @Description: 数据库连接工具类
* @author: 孤傲苍狼
* @date: 2014-10-4 下午6:04:36
*
*/
public class JdbcUtils_JNDI {
private static DataSource ds = null;
//在静态代码块中创建数据库连接池
static{
try{
//初始化JNDI
Context initCtx = new InitialContext();
//得到JNDI容器
Context envCtx = (Context) initCtx.lookup("java:comp/env");
//从JNDI容器中检索name为jdbc/datasource的数据源
ds = (DataSource)envCtx.lookup("jdbc/datasource");
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getConnection
* @Description: 从数据源中获取数据库连接
* @Anthor:孤傲苍狼
* @return Connection
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
//从数据源中获取数据库连接
return ds.getConnection();
}
/**
* @Method: release
* @Description: 释放资源,
* 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//将Connection连接对象还给数据库连接池
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
写一个Servlet测试JNDI数据源
复制代码
package me.gacl.test;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.util.JdbcUtils_JNDI;
public class JNDITest extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
//获取数据库连接
conn = JdbcUtils_JNDI.getConnection();
String sql = "insert into test1(name) values(?)";
st = conn.prepareStatement(sql);
st.setString(1, "gacl");
st.executeUpdate();
//获取数据库自动生成的主键
rs = st.getGeneratedKeys();
if(rs.next()){
System.out.println(rs.getInt(1));
}
}catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
JdbcUtils_JNDI.release(conn, st, rs);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

(四十)——编写自己的JDBC框架
一、元数据介绍
元数据指的是"数据库"、"表"、"列"的定义信息。
1.1、DataBaseMetaData元数据
Connection.getDatabaseMetaData()获得代表DatabaseMetaData元数据的DatabaseMetaData对象。
DataBaseMetaData对象的常用方法:
getURL():返回一个String类对象,代表数据库的URL。
getUserName():返回连接当前数据库管理系统的用户名。
getDatabaseProductName():返回数据库的产品名称。
getDatabaseProductVersion():返回数据库的版本号。
getDriverName():返回驱动驱动程序的名称。
getDriverVersion():返回驱动程序的版本号。
isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
复制代码
/**
* @Method: testDataBaseMetaData
* @Description: 获取数据库的元信息
* @Anthor:孤傲苍狼
*
* @throws SQLException
*/
@Test
public void testDataBaseMetaData() throws SQLException {
Connection conn = JdbcUtils.getConnection();
DatabaseMetaData metadata = conn.getMetaData();
//getURL():返回一个String类对象,代表数据库的URL
System.out.println(metadata.getURL());
//getUserName():返回连接当前数据库管理系统的用户名
System.out.println(metadata.getUserName());
//getDatabaseProductName():返回数据库的产品名称
System.out.println(metadata.getDatabaseProductName());
//getDatabaseProductVersion():返回数据库的版本号
System.out.println(metadata.getDatabaseProductVersion());
//getDriverName():返回驱动驱动程序的名称
System.out.println(metadata.getDriverName());
//getDriverVersion():返回驱动程序的版本号
System.out.println(metadata.getDriverVersion());
//isReadOnly():返回一个boolean值,指示数据库是否只允许读操作
System.out.println(metadata.isReadOnly());
JdbcUtils.release(conn, null, null);
}
复制代码
运行结果如下:

1.2、ParameterMetaData元数据
PreparedStatement.getParameterMetaData() 获得代表PreparedStatement元数据的ParameterMetaData对象。
Select * from user where name=? And password=?
ParameterMetaData对象的常用方法:
getParameterCount(): 获得指定参数的个数
getParameterType(int param):获得指定参数的sql类型,MySQL数据库驱动不支持
复制代码
/**
* @Method: testParameterMetaData
* @Description: 获取参数元信息
* @Anthor:孤傲苍狼
*
* @throws SQLException
*/
@Test
public void testParameterMetaData() throws SQLException {
Connection conn = JdbcUtils.getConnection();
String sql = "select * from user wherer name=? and password=?";
//将SQL预编译一下
PreparedStatement st = conn.prepareStatement(sql);
ParameterMetaData pm = st.getParameterMetaData();
//getParameterCount() 获得指定参数的个数
System.out.println(pm.getParameterCount());
//getParameterType(int param):获得指定参数的sql类型,MySQL数据库驱动不支持
System.out.println(pm.getParameterType(1));
JdbcUtils.release(conn, null, null);
}
复制代码
1.3、ResultSetMetaData元数据
ResultSet.getMetaData() 获得代表ResultSet对象元数据的ResultSetMetaData对象。
ResultSetMetaData对象的常用方法:
getColumnCount() 返回resultset对象的列数
getColumnName(int column) 获得指定列的名称
getColumnTypeName(int column)获得指定列的类型
复制代码
/**
* @Method: testResultSetMetaData
* @Description: 结果集的元数据
* @Anthor:孤傲苍狼
*
* @throws Exception
*/
@Test
public void testResultSetMetaData() throws Exception {
Connection conn = JdbcUtils.getConnection();
String sql = "select * from account";
PreparedStatement st = conn.prepareStatement(sql);
ResultSet rs = st.executeQuery();
//ResultSet.getMetaData()获得代表ResultSet对象元数据的ResultSetMetaData对象
ResultSetMetaData metadata = rs.getMetaData();
//getColumnCount() 返回resultset对象的列数
System.out.println(metadata.getColumnCount());
//getColumnName(int column) 获得指定列的名称
System.out.println(metadata.getColumnName(1));
//getColumnTypeName(int column)获得指定列的类型
System.out.println(metadata.getColumnTypeName(1));
JdbcUtils.release(conn, st, rs);
}
复制代码
二、使用元数据封装简单的JDBC框架
系统中所有实体对象都涉及到基本的CRUD操作
所有实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句。
实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。
2.1、封装通用的update方法和qurey方法
定义一个JdbcUtils工具类,工具类负责获取数据库连接,释放资源,执行SQL的update和query操作,代码如下:
复制代码
package me.gacl.util;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static{
try{
//读取db.properties文件中的数据库连接信息
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(in);
//获取数据库连接驱动
driver = prop.getProperty("driver");
//获取数据库连接URL地址
url = prop.getProperty("url");
//获取数据库连接用户名
username = prop.getProperty("username");
//获取数据库连接密码
password = prop.getProperty("password");
//加载数据库驱动
Class.forName(driver);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getConnection
* @Description: 获取数据库连接对象
* @Anthor:孤傲苍狼
*
* @return Connection数据库连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url, username,password);
}
/**
* @Method: release
* @Description: 释放资源,
* 要释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
* @Anthor:孤傲苍狼
*
* @param conn
* @param st
* @param rs
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//关闭存储查询结果的ResultSet对象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//关闭负责执行SQL命令的Statement对象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//关闭Connection数据库连接对象
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @Method: update
* @Description: 万能更新
* 所有实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,
* 因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句
* @Anthor:孤傲苍狼
* @param sql 要执行的SQL
* @param params 执行SQL时使用的参数
* @throws SQLException
*/
public static void update(String sql,Object params[]) throws SQLException{
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = getConnection();
st = conn.prepareStatement(sql);
for(int i=0;i<params.length;i++){
st.setObject(i+1, params[i]);
}
st.executeUpdate();
}finally{
release(conn, st, rs);
}
}
/**
* @Method: query
* @Description:万能查询
* 实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,
* 因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。
* @Anthor:孤傲苍狼
*
* @param sql 要执行的SQL
* @param params 执行SQL时使用的参数
* @param rsh 查询返回的结果集处理器
* @return
* @throws SQLException
*/
public static Object query(String sql,Object params[],ResultSetHandler rsh) throws SQLException{
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = getConnection();
st = conn.prepareStatement(sql);
for(int i=0;i<params.length;i++){
st.setObject(i+1, params[i]);
}
rs = st.executeQuery();
/**
* 对于查询返回的结果集处理使用到了策略模式,
* 在设计query方法时,query方法事先是无法知道用户对返回的查询结果集如何进行处理的,即不知道结果集的处理策略,
* 那么这个结果集的处理策略就让用户自己提供,query方法内部就调用用户提交的结果集处理策略进行处理
* 为了能够让用户提供结果集的处理策略,需要对用户暴露出一个结果集处理接口ResultSetHandler
* 用户只要实现了ResultSetHandler接口,那么query方法内部就知道用户要如何处理结果集了
*/
return rsh.handler(rs);
}finally{
release(conn, st, rs);
}
}
}
复制代码
在设计query方法时,对于查询返回的结果集处理使用到了策略模式,query方法事先是无法知道用户对返回的查询结果集如何进行处理的,即不知道结果集的处理策略, 那么这个结果集的处理策略就让用户自己提供,query方法内部就调用用户提交的结果集处理策略进行处理, 为了能够让用户提供结果集的处理策略,需要对用户暴露出一个结果集处理接口ResultSetHandler, 结果集处理器接口ResultSetHandler的定义如下:
复制代码
package me.gacl.util;
import java.sql.ResultSet;
/**
* @ClassName: ResultSetHandler
* @Description:结果集处理器接口
* @author: 孤傲苍狼
* @date: 2014-10-5 下午12:01:27
*
*/
public interface ResultSetHandler {
/**
* @Method: handler
* @Description: 结果集处理方法
* @Anthor:孤傲苍狼
*
* @param rs 查询结果集
* @return
*/
public Object handler(ResultSet rs);
}
复制代码
用户只要实现了ResultSetHandler接口,那么就是针对查询结果集写了一个处理器,在query方法内部就调用用户自己写的处理器处理结果集。
2.2、编写常用的结果集处理器
为了提高框架的易用性,我们可以事先就针对结果集写好一些常用的处理器,比如将结果集转换成bean对象的处理器,将结果集转换成bean对象的list集合的处理器。
2.2.1、BeanHandler——将结果集转换成bean对象的处理器
复制代码
package me.gacl.util;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
/**
* @ClassName: BeanHandler
* @Description: 将结果集转换成bean对象的处理器
* @author: 孤傲苍狼
* @date: 2014-10-5 下午12:00:33
*
*/
public class BeanHandler implements ResultSetHandler {
private Class<?> clazz;
public BeanHandler(Class<?> clazz){
this.clazz = clazz;
}
public Object handler(ResultSet rs) {
try{
if(!rs.next()){
return null;
}
Object bean = clazz.newInstance();
//得到结果集元数据
ResultSetMetaData metadata = rs.getMetaData();
int columnCount = metadata.getColumnCount();//得到结果集中有几列数据
for(int i=0;i<columnCount;i++){
String coulmnName = metadata.getColumnName(i+1);//得到每列的列名
Object coulmnData = rs.getObject(i+1);
Field f = clazz.getDeclaredField(coulmnName);//反射出类上列名对应的属性
f.setAccessible(true);
f.set(bean, coulmnData);
}
return bean;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
复制代码
2.2.2、BeanListHandler——将结果集转换成bean对象的list集合的处理器
复制代码
package me.gacl.util;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: BeanListHandler
* @Description: 将结果集转换成bean对象的list集合的处理器
* @author: 孤傲苍狼
* @date: 2014-10-5 下午12:00:06
*
*/
public class BeanListHandler implements ResultSetHandler {
private Class<?> clazz;
public BeanListHandler(Class<?> clazz){
this.clazz = clazz;
}
public Object handler(ResultSet rs) {
try{
List<Object> list = new ArrayList<Object>();
while(rs.next()){
Object bean = clazz.newInstance();
ResultSetMetaData metadata = rs.getMetaData();
int count = metadata.getColumnCount();
for(int i=0;i<count;i++){
String name = metadata.getColumnName(i+1);
Object value = rs.getObject(name);
Field f = bean.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(bean, value);
}
list.add(bean);
}
return list.size()>0?list:null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
复制代码
当框架自身提供的结果集处理器不满足用户的要求时,那么用户就可以自己去实现ResultSetHandler接口,编写满足自己业务要求的结果集处理器。
有了上述的JdbcUtils框架之后,针对单个实体对象CRUD操作就非常方便了,如下所示:
复制代码
package me.gacl.dao;
import java.sql.SQLException;
import java.util.List;
import me.gacl.domain.Account;
import me.gacl.util.BeanHandler;
import me.gacl.util.BeanListHandler;
import me.gacl.util.JdbcUtils;
public class AccountDao {
public void add(Account account) throws SQLException{
String sql = "insert into account(name,money) values(?,?)";
Object params[] = {account.getName(),account.getMoney()};
JdbcUtils.update(sql, params);
}
public void delete(int id) throws SQLException{
String sql = "delete from account where id=?";
Object params[] = {id};
JdbcUtils.update(sql, params);
}
public void update(Account account) throws SQLException{
String sql = "update account set name=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()};
JdbcUtils.update(sql, params);
}
public Account find(int id) throws SQLException{
String sql = "select * from account where id=?";
Object params[] = {id};
return (Account) JdbcUtils.query(sql, params, new BeanHandler(Account.class));
}
public List<Account> getAll() throws SQLException{
String sql = "select * from account";
Object params[] = {};
return (List<Account>) JdbcUtils.query(sql, params,new BeanListHandler(Account.class));
}
}
复制代码
编写的这个JDBC框架就是模拟Apache的DBUtils框架的实现,下一篇将具体介绍Apache的DBUtils框架。

(四十一)——Apache的DBUtils框架学习
一、commons-dbutils简介 
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选。
commons-dbutilsAPI介绍:
org.apache.commons.dbutils.QueryRunner
org.apache.commons.dbutils.ResultSetHandler
工具类
org.apache.commons.dbutils.DbUtils
二、QueryRunner类使用讲解
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
QueryRunner类提供了两个构造方法:
默认的构造方法
需要一个 javax.sql.DataSource 来作参数的构造方法。
2.1、QueryRunner类的主要方法
public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException: 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException : 执行一个不需要置换参数的查询操作。
public int update(Connection conn, String sql, Object[] params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
public int update(Connection conn, String sql) throws SQLException:用来执行一个不需要置换参数的更新操作。
2.2、使用QueryRunner类实现CRUD
复制代码
package me.gacl.test;
import java.util.Date;
import java.util.List;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.SQLException;
import javax.sql.rowset.serial.SerialClob;
import me.gacl.domain.User;
import me.gacl.util.JdbcUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.Test;
/**
* @ClassName: DBUtilsCRUDTest
* @Description:使用dbutils框架的QueryRunner类完成CRUD,以及批处理
* @author: 孤傲苍狼
* @date: 2014-10-5 下午4:56:44
*
*/
public class QueryRunnerCRUDTest {
/*
*测试表
create table users(
id int primary key auto_increment,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
);
*/
@Test
public void add() throws SQLException {
//将数据源传递给QueryRunner,QueryRunner内部通过数据源获取数据库连接
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
Object params[] = {"孤傲苍狼","123", "gacl@sina.com", new Date()};
//Object params[] = {"白虎神皇","123", "gacl@sina.com", "1988-05-07"};
qr.update(sql, params);
}
@Test
public void delete() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "delete from users where id=?";
qr.update(sql, 1);
}
@Test
public void update() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "update users set name=? where id=?";
Object params[] = { "ddd", 5};
qr.update(sql, params);
}
@Test
public void find() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users where id=?";
Object params[] = {2};
User user = (User) qr.query(sql, params, new BeanHandler(User.class));
System.out.println(user.getBirthday());
}
@Test
public void getAll() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users";
List list = (List) qr.query(sql, new BeanListHandler(User.class));
System.out.println(list.size());
}
/**
* @Method: testBatch
* @Description:批处理
* @Anthor:孤傲苍狼
*
* @throws SQLException
*/
@Test
public void testBatch() throws SQLException {
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
Object params[][] = new Object[10][];
for (int i = 0; i < 10; i++) {
params[i] = new Object[] { "aa" + i, "123", "aa@sina.com",
new Date() };
}
qr.batch(sql, params);
}
//用dbutils完成大数据(不建议用)
/***************************************************************************
create table testclob
(
id int primary key auto_increment,
resume text
);
**************************************************************************/
@Test
public void testclob() throws SQLException, IOException{
QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
String sql = "insert into testclob(resume) values(?)"; //clob
//这种方式获取的路径,其中的空格会被使用“%20”代替
String path = QueryRunnerCRUDTest.class.getClassLoader().getResource("data.txt").getPath();
//将“%20”替换回空格
path = path.replaceAll("%20", " ");
FileReader in = new FileReader(path);
char[] buffer = new char[(int) new File(path).length()];
in.read(buffer);
SerialClob clob = new SerialClob(buffer);
Object params[] = {clob};
runner.update(sql, params);
}
}
复制代码
三、ResultSetHandler接口使用讲解
该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。
ResultSetHandler接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)
3.1、ResultSetHandler接口的实现类
ArrayHandler:把结果集中的第一行数据转成对象数组。
ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
ColumnListHandler:将结果集中某一列的数据存放到List中。
KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
3.2、测试dbutils各种类型的处理器
复制代码
package me.gacl.test;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import me.gacl.util.JdbcUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;
/**
* @ClassName: ResultSetHandlerTest
* @Description:测试dbutils各种类型的处理器
* @author: 孤傲苍狼
* @date: 2014-10-6 上午8:39:14
*
*/
public class ResultSetHandlerTest {
@Test
public void testArrayHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users";
Object result[] = (Object[]) qr.query(sql, new ArrayHandler());
System.out.println(Arrays.asList(result)); //list toString()
}
@Test
public void testArrayListHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users";
List<Object[]> list = (List) qr.query(sql, new ArrayListHandler());
for(Object[] o : list){
System.out.println(Arrays.asList(o));
}
}
@Test
public void testColumnListHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users";
List list = (List) qr.query(sql, new ColumnListHandler("id"));
System.out.println(list);
}
@Test
public void testKeyedHandler() throws Exception{
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users";
Map<Integer,Map> map = (Map) qr.query(sql, new KeyedHandler("id"));
for(Map.Entry<Integer, Map> me : map.entrySet()){
int id = me.getKey();
Map<String,Object> innermap = me.getValue();
for(Map.Entry<String, Object> innerme : innermap.entrySet()){
String columnName = innerme.getKey();
Object value = innerme.getValue();
System.out.println(columnName + "=" + value);
}
System.out.println("----------------");
}
}
@Test
public void testMapHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users";
Map<String,Object> map = (Map) qr.query(sql, new MapHandler());
for(Map.Entry<String, Object> me : map.entrySet())
{
System.out.println(me.getKey() + "=" + me.getValue());
}
}
@Test
public void testMapListHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select * from users";
List<Map> list = (List) qr.query(sql, new MapListHandler());
for(Map<String,Object> map :list){
for(Map.Entry<String, Object> me : map.entrySet())
{
System.out.println(me.getKey() + "=" + me.getValue());
}
}
}
@Test
public void testScalarHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
String sql = "select count(*) from users"; //[13] list[13]
int count = ((Long)qr.query(sql, new ScalarHandler(1))).intValue();
System.out.println(count);
}
}
复制代码
三、DbUtils类使用讲解
DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
四、JDBC开发中的事务处理
在开发中,对数据库的多个表或者对一个表中的多条数据执行更新操作时要保证对多个更新操作要么同时成功,要么都不成功,这就涉及到对多个更新操作的事务管理问题了。比如银行业务中的转账问题,A用户向B用户转账100元,假设A用户和B用户的钱都存储在Account表,那么A用户向B用户转账时就涉及到同时更新Account表中的A用户的钱和B用户的钱,用SQL来表示就是:
update account set money=money-100 where name=‘A‘
update account set money=money+100 where name=‘B‘
4.1、在数据访问层(Dao)中处理事务
对于这样的同时更新一个表中的多条数据的操作,那么必须保证要么同时成功,要么都不成功,所以需要保证这两个update操作在同一个事务中进行。在开发中,我们可能会在AccountDao写一个转账处理方法,如下:
复制代码
/**
* @Method: transfer
* @Description:这个方法是用来处理两个用户之间的转账业务
* 在开发中,DAO层的职责应该只涉及到CRUD,
* 而这个transfer方法是处理两个用户之间的转账业务的,已经涉及到具体的业务操作,应该在业务层中做,不应该出现在DAO层的
* 所以在开发中DAO层出现这样的业务处理方法是完全错误的
* @Anthor:孤傲苍狼
*
* @param sourceName
* @param targetName
* @param money
* @throws SQLException
*/
public void transfer(String sourceName,String targetName,float money) throws SQLException{
Connection conn = null;
try{
conn = JdbcUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
/**
* 在创建QueryRunner对象时,不传递数据源给它,是为了保证这两条SQL在同一个事务中进行,
* 我们手动获取数据库连接,然后让这两条SQL使用同一个数据库连接执行
*/
QueryRunner runner = new QueryRunner();
String sql1 = "update account set money=money-100 where name=?";
String sql2 = "update account set money=money+100 where name=?";
Object[] paramArr1 = {sourceName};
Object[] paramArr2 = {targetName};
runner.update(conn,sql1,paramArr1);
//模拟程序出现异常让事务回滚
int x = 1/0;
runner.update(conn,sql2,paramArr2);
//sql正常执行之后就提交事务
conn.commit();
}catch (Exception e) {
e.printStackTrace();
if(conn!=null){
//出现异常之后就回滚事务
conn.rollback();
}
}finally{
//关闭数据库连接
conn.close();
}
}
复制代码
然后我们在AccountService中再写一个同名方法,在方法内部调用AccountDao的transfer方法处理转账业务,如下:
public void transfer(String sourceName,String targetName,float money) throws SQLException{
AccountDao dao = new AccountDao();
dao.transfer(sourceName, targetName, money);
}
上面AccountDao的这个transfer方法可以处理转账业务,并且保证了在同一个事务中进行,但是AccountDao的这个transfer方法是处理两个用户之间的转账业务的,已经涉及到具体的业务操作,应该在业务层中做,不应该出现在DAO层的,在开发中,DAO层的职责应该只涉及到基本的CRUD,不涉及具体的业务操作,所以在开发中DAO层出现这样的业务处理方法是一种不好的设计。
4.2、在业务层(BusinessService)处理事务
由于上述AccountDao存在具体的业务处理方法,导致AccountDao的职责不够单一,下面我们对AccountDao进行改造,让AccountDao的职责只是做CRUD操作,将事务的处理挪到业务层(BusinessService),改造后的AccountDao如下:
复制代码
package me.gacl.dao;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import me.gacl.domain.Account;
import me.gacl.util.JdbcUtils;
/*account测试表
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(‘A‘,1000);
insert into account(name,money) values(‘B‘,1000);
insert into account(name,money) values(‘C‘,1000);
*/
/**
* @ClassName: AccountDao
* @Description: 针对Account对象的CRUD
* @author: 孤傲苍狼
* @date: 2014-10-6 下午4:00:42
*
*/
public class AccountDao {
//接收service层传递过来的Connection对象
private Connection conn = null;
public AccountDao(Connection conn){
this.conn = conn;
}
public AccountDao(){
}
/**
* @Method: update
* @Description:更新
* @Anthor:孤傲苍狼
*
* @param account
* @throws SQLException
*/
public void update(Account account) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "update account set name=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()};
//使用service层传递过来的Connection对象操作数据库
qr.update(conn,sql, params);
}
/**
* @Method: find
* @Description:查找
* @Anthor:孤傲苍狼
*
* @param id
* @return
* @throws SQLException
*/
public Account find(int id) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "select * from account where id=?";
//使用service层传递过来的Connection对象操作数据库
return (Account) qr.query(conn,sql, id, new BeanHandler(Account.class));
}
}
复制代码
接着对AccountService(业务层)中的transfer方法的改造,在业务层(BusinessService)中处理事务
复制代码
package me.gacl.service;
import java.sql.Connection;
import java.sql.SQLException;
import me.gacl.dao.AccountDao;
import me.gacl.domain.Account;
import me.gacl.util.JdbcUtils;
/**
* @ClassName: AccountService
* @Description: 业务逻辑处理层
* @author: 孤傲苍狼
* @date: 2014-10-6 下午5:30:15
*
*/
public class AccountService {
/**
* @Method: transfer
* @Description:这个方法是用来处理两个用户之间的转账业务
* @Anthor:孤傲苍狼
*
* @param sourceid
* @param tartgetid
* @param money
* @throws SQLException
*/
public void transfer(int sourceid,int tartgetid,float money) throws SQLException{
Connection conn = null;
try{
//获取数据库连接
conn = JdbcUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//将获取到的Connection传递给AccountDao,保证dao层使用的是同一个Connection对象操作数据库
AccountDao dao = new AccountDao(conn);
Account source = dao.find(sourceid);
Account target = dao.find(tartgetid);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
dao.update(source);
//模拟程序出现异常让事务回滚
int x = 1/0;
dao.update(target);
//提交事务
conn.commit();
}catch (Exception e) {
e.printStackTrace();
//出现异常之后就回滚事务
conn.rollback();
}finally{
conn.close();
}
}
}
复制代码
程序经过这样改造之后就比刚才好多了,AccountDao只负责CRUD,里面没有具体的业务处理方法了,职责就单一了,而AccountService则负责具体的业务逻辑和事务的处理,需要操作数据库时,就调用AccountDao层提供的CRUD方法操作数据库。
4.3、使用ThreadLocal进行更加优雅的事务处理
上面的在businessService层这种处理事务的方式依然不够优雅,为了能够让事务处理更加优雅,我们使用ThreadLocal类进行改造,ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了
ThreadLocal类的使用范例如下:
复制代码
package me.gacl.test;
public class ThreadLocalTest {
public static void main(String[] args) {
//得到程序运行时的当前线程
Thread currentThread = Thread.currentThread();
System.out.println(currentThread);
//ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来
ThreadLocal<String> t = new ThreadLocal<String>();
//把某个对象绑定到当前线程上 对象以键值对的形式存储到一个Map集合中,对象的的key是当前的线程,如: map(currentThread,"aaa")
t.set("aaa");
//获取绑定到当前线程中的对象
String value = t.get();
//输出value的值是aaa
System.out.println(value);
}
}
复制代码
使用使用ThreadLocal类进行改造数据库连接工具类JdbcUtils,改造后的代码如下:
复制代码
package me.gacl.util;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* @ClassName: JdbcUtils2
* @Description: 数据库连接工具类
* @author: 孤傲苍狼
* @date: 2014-10-4 下午6:04:36
*
*/
public class JdbcUtils2 {
private static ComboPooledDataSource ds = null;
//使用ThreadLocal存储当前线程中的Connection对象
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
//在静态代码块中创建数据库连接池
static{
try{
//通过代码创建C3P0数据库连接池
/*ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy");
ds.setUser("root");
ds.setPassword("XDP");
ds.setInitialPoolSize(10);
ds.setMinPoolSize(5);
ds.setMaxPoolSize(20);*/
//通过读取C3P0的xml配置文件创建数据源,C3P0的xml配置文件c3p0-config.xml必须放在src目录下
//ds = new ComboPooledDataSource();//使用C3P0的默认配置来创建数据源
ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置来创建数据源
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* @Method: getConnection
* @Description: 从数据源中获取数据库连接
* @Anthor:孤傲苍狼
* @return Connection
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
//从当前线程中获取Connection
Connection conn = threadLocal.get();
if(conn==null){
//从数据源中获取数据库连接
conn = getDataSource().getConnection();
//将conn绑定到当前线程
threadLocal.set(conn);
}
return conn;
}
/**
* @Method: startTransaction
* @Description: 开启事务
* @Anthor:孤傲苍狼
*
*/
public static void startTransaction(){
try{
Connection conn = threadLocal.get();
if(conn==null){
conn = getConnection();
//把 conn绑定到当前线程上
threadLocal.set(conn);
}
//开启事务
conn.setAutoCommit(false);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Method: rollback
* @Description:回滚事务
* @Anthor:孤傲苍狼
*
*/
public static void rollback(){
try{
//从当前线程中获取Connection
Connection conn = threadLocal.get();
if(conn!=null){
//回滚事务
conn.rollback();
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Method: commit
* @Description:提交事务
* @Anthor:孤傲苍狼
*
*/
public static void commit(){
try{
//从当前线程中获取Connection
Connection conn = threadLocal.get();
if(conn!=null){
//提交事务
conn.commit();
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Method: close
* @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池)
* @Anthor:孤傲苍狼
*
*/
public static void close(){
try{
//从当前线程中获取Connection
Connection conn = threadLocal.get();
if(conn!=null){
conn.close();
//解除当前线程上绑定conn
threadLocal.remove();
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Method: getDataSource
* @Description: 获取数据源
* @Anthor:孤傲苍狼
* @return DataSource
*/
public static DataSource getDataSource(){
//从数据源中获取数据库连接
return ds;
}
}
复制代码
对AccountDao进行改造,数据库连接对象不再需要service层传递过来,而是直接从JdbcUtils2提供的getConnection方法去获取,改造后的AccountDao如下:
复制代码
package me.gacl.dao;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import me.gacl.domain.Account;
import me.gacl.util.JdbcUtils;
import me.gacl.util.JdbcUtils2;
/*
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(‘A‘,1000);
insert into account(name,money) values(‘B‘,1000);
insert into account(name,money) values(‘C‘,1000);
*/
/**
* @ClassName: AccountDao
* @Description: 针对Account对象的CRUD
* @author: 孤傲苍狼
* @date: 2014-10-6 下午4:00:42
*
*/
public class AccountDao2 {
public void update(Account account) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "update account set name=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()};
//JdbcUtils2.getConnection()获取当前线程中的Connection对象
qr.update(JdbcUtils2.getConnection(),sql, params);
}
public Account find(int id) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "select * from account where id=?";
//JdbcUtils2.getConnection()获取当前线程中的Connection对象
return (Account) qr.query(JdbcUtils2.getConnection(),sql, id, new BeanHandler(Account.class));
}
}
复制代码
对AccountService进行改造,service层不再需要传递数据库连接Connection给Dao层,改造后的AccountService如下:
复制代码
package me.gacl.service;
import java.sql.SQLException;
import me.gacl.dao.AccountDao2;
import me.gacl.domain.Account;
import me.gacl.util.JdbcUtils2;
public class AccountService2 {
/**
* @Method: transfer
* @Description:在业务层处理两个账户之间的转账问题
* @Anthor:孤傲苍狼
*
* @param sourceid
* @param tartgetid
* @param money
* @throws SQLException
*/
public void transfer(int sourceid,int tartgetid,float money) throws SQLException{
try{
//开启事务,在业务层处理事务,保证dao层的多个操作在同一个事务中进行
JdbcUtils2.startTransaction();
AccountDao2 dao = new AccountDao2();
Account source = dao.find(sourceid);
Account target = dao.find(tartgetid);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
dao.update(source);
//模拟程序出现异常让事务回滚
int x = 1/0;
dao.update(target);
//SQL正常执行之后提交事务
JdbcUtils2.commit();
}catch (Exception e) {
e.printStackTrace();
//出现异常之后就回滚事务
JdbcUtils2.rollback();
}finally{
//关闭数据库连接
JdbcUtils2.close();
}
}
}
复制代码
这样在service层对事务的处理看起来就更加优雅了。ThreadLocal类在开发中使用得是比较多的,程序运行中产生的数据要想在一个线程范围内共享,只需要把数据使用ThreadLocal进行存储即可。
4.4、ThreadLocal + Filter 处理事务
上面介绍了JDBC开发中事务处理的3种方式,下面再介绍的一种使用ThreadLocal + Filter进行统一的事务处理,这种方式主要是使用过滤器进行统一的事务处理,如下图所示:

1、编写一个事务过滤器TransactionFilter
代码如下:
复制代码
package me.gac.web.filter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.util.JdbcUtils;
/**
* @ClassName: TransactionFilter
* @Description:ThreadLocal + Filter 统一处理数据库事务
* @author: 孤傲苍狼
* @date: 2014-10-6 下午11:36:58
*
*/
public class TransactionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
Connection connection = null;
try {
//1、获取数据库连接对象Connection
connection = JdbcUtils.getConnection();
//2、开启事务
connection.setAutoCommit(false);
//3、利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定
ConnectionContext.getInstance().bind(connection);
//4、把请求转发给目标Servlet
chain.doFilter(request, response);
//5、提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
//6、回滚事务
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
//req.setAttribute("errMsg", e.getMessage());
//req.getRequestDispatcher("/error.jsp").forward(req, res);
//出现异常之后跳转到错误页面
res.sendRedirect(req.getContextPath()+"/error.jsp");
}finally{
//7、解除绑定
ConnectionContext.getInstance().remove();
//8、关闭数据库连接
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void destroy() {
}
}
复制代码
我们在TransactionFilter中把获取到的数据库连接使用ThreadLocal绑定到当前线程之后,在DAO层还需要从ThreadLocal中取出数据库连接来操作数据库,因此需要编写一个ConnectionContext类来存储ThreadLocal,ConnectionContext类的代码如下:
复制代码
package me.gac.web.filter;
import java.sql.Connection;
/**
* @ClassName: ConnectionContext
* @Description:数据库连接上下文
* @author: 孤傲苍狼
* @date: 2014-10-7 上午8:36:01
*
*/
public class ConnectionContext {
/**
* 构造方法私有化,将ConnectionContext设计成单例
*/
private ConnectionContext(){
}
//创建ConnectionContext实例对象
private static ConnectionContext connectionContext = new ConnectionContext();
/**
* @Method: getInstance
* @Description:获取ConnectionContext实例对象
* @Anthor:孤傲苍狼
*
* @return
*/
public static ConnectionContext getInstance(){
return connectionContext;
}
/**
* @Field: connectionThreadLocal
* 使用ThreadLocal存储数据库连接对象
*/
private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();
/**
* @Method: bind
* @Description:利用ThreadLocal把获取数据库连接对象Connection和当前线程绑定
* @Anthor:孤傲苍狼
*
* @param connection
*/
public void bind(Connection connection){
connectionThreadLocal.set(connection);
}
/**
* @Method: getConnection
* @Description:从当前线程中取出Connection对象
* @Anthor:孤傲苍狼
*
* @return
*/
public Connection getConnection(){
return connectionThreadLocal.get();
}
/**
* @Method: remove
* @Description: 解除当前线程上绑定Connection
* @Anthor:孤傲苍狼
*
*/
public void remove(){
connectionThreadLocal.remove();
}
}
复制代码
在DAO层想获取数据库连接时,就可以使用ConnectionContext.getInstance().getConnection()来获取,如下所示:
复制代码
package me.gacl.dao;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import me.gac.web.filter.ConnectionContext;
import me.gacl.domain.Account;
/*
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(‘A‘,1000);
insert into account(name,money) values(‘B‘,1000);
insert into account(name,money) values(‘C‘,1000);
*/
/**
* @ClassName: AccountDao
* @Description: 针对Account对象的CRUD
* @author: 孤傲苍狼
* @date: 2014-10-6 下午4:00:42
*
*/
public class AccountDao3 {
public void update(Account account) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "update account set name=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()};
//ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象
qr.update(ConnectionContext.getInstance().getConnection(),sql, params);
}
public Account find(int id) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "select * from account where id=?";
//ConnectionContext.getInstance().getConnection()获取当前线程中的Connection对象
return (Account) qr.query(ConnectionContext.getInstance().getConnection(),sql, id, new BeanHandler(Account.class));
}
}
复制代码
businessService层也不用处理事务和数据库连接问题了,这些统一在TransactionFilter中统一管理了,businessService层只需要专注业务逻辑的处理即可,如下所示:
复制代码
package me.gacl.service;
import java.sql.SQLException;
import me.gacl.dao.AccountDao3;
import me.gacl.domain.Account;
public class AccountService3 {
/**
* @Method: transfer
* @Description:在业务层处理两个账户之间的转账问题
* @Anthor:孤傲苍狼
*
* @param sourceid
* @param tartgetid
* @param money
* @throws SQLException
*/
public void transfer(int sourceid, int tartgetid, float money)
throws SQLException {
AccountDao3 dao = new AccountDao3();
Account source = dao.find(sourceid);
Account target = dao.find(tartgetid);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
dao.update(source);
// 模拟程序出现异常让事务回滚
int x = 1 / 0;
dao.update(target);
}
}
复制代码
Web层的Servlet调用businessService层的业务方法处理用户请求,需要注意的是:调用businessService层的方法出异常之后,继续将异常抛出,这样在TransactionFilter就能捕获到抛出的异常,继而执行事务回滚操作,如下所示:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.service.AccountService3;
public class AccountServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
AccountService3 service = new AccountService3();
try {
service.transfer(1, 2, 100);
} catch (SQLException e) {
e.printStackTrace();
//注意:调用service层的方法出异常之后,继续将异常抛出,这样在TransactionFilter就能捕获到抛出的异常,继而执行事务回滚操作
throw new RuntimeException(e);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}

(四十二)——Filter(过滤器)学习
一、Filter简介
Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,如下所示:

二、Filter是如何实现拦截的?
Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
调用目标资源之前,让一段代码执行。
是否调用目标资源(即是否让用户访问web资源)。
调用目标资源之后,让一段代码执行。
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对 象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方 法,即web资源就会被访问,否则web资源不会被访问。
三、Filter开发入门
3.1、Filter开发步骤
Filter开发分为二个步骤:
编写java类实现Filter接口,并实现其doFilter方法。
在 web.xml 文件中使用<filter>和<filter-mapping>元素对编写的filter类进行注册,并设置它所能拦截的资源。
过滤器范例:
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @ClassName: FilterDemo01
* @Description:filter的三种典型应用:
* 1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,
* 即是否让目标资源执行
* 2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
* 3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
* @author: 孤傲苍狼
* @date: 2014-8-31 下午10:09:24
*/
public class FilterDemo01 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----过滤器初始化----");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//对request和response进行一些预处理
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("FilterDemo01执行前!!!");
chain.doFilter(request, response); //让目标资源执行,放行
System.out.println("FilterDemo01执行后!!!");
}
@Override
public void destroy() {
System.out.println("----过滤器销毁----");
}
}
复制代码
在web. xml中配置过滤器:
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
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_3_0.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!--配置过滤器-->
<filter>
<filter-name>FilterDemo01</filter-name>
<filter-class>me.gacl.web.filter.FilterDemo01</filter-class>
</filter>
<!--映射过滤器-->
<filter-mapping>
<filter-name>FilterDemo01</filter-name>
<!--"/*"表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
复制代码
3.2、Filter链
在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
四、Filter的生命周期
4.1、Filter的创建
Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
4.2、Filter的销毁
Web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。
4.3、FilterConfig接口
用户在配置filter时,可以使用<init-param>为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:
String getFilterName():得到filter的名称。
String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
public ServletContext getServletContext():返回Servlet上下文对象的引用。
范例:利用FilterConfig得到filter配置信息
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class FilterDemo02 implements Filter {
/* 过滤器初始化
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----过滤器初始化----");
/**
* <filter>
<filter-name>FilterDemo02</filter-name>
<filter-class>me.gacl.web.filter.FilterDemo02</filter-class>
<!--配置FilterDemo02过滤器的初始化参数-->
<init-param>
<description>配置FilterDemo02过滤器的初始化参数</description>
<param-name>name</param-name>
<param-value>gacl</param-value>
</init-param>
<init-param>
<description>配置FilterDemo02过滤器的初始化参数</description>
<param-name>like</param-name>
<param-value>java</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FilterDemo02</filter-name>
<!--“/*”表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
*/
//得到过滤器的名字
String filterName = filterConfig.getFilterName();
//得到在web.xml文件中配置的初始化参数
String initParam1 = filterConfig.getInitParameter("name");
String initParam2 = filterConfig.getInitParameter("like");
//返回过滤器的所有初始化参数的名字的枚举集合。
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
System.out.println(filterName);
System.out.println(initParam1);
System.out.println(initParam2);
while (initParameterNames.hasMoreElements()) {
String paramName = (String) initParameterNames.nextElement();
System.out.println(paramName);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo02执行前!!!");
chain.doFilter(request, response); //让目标资源执行,放行
System.out.println("FilterDemo02执行后!!!");
}
@Override
public void destroy() {
System.out.println("----过滤器销毁----");
}
}
复制代码
五、Filter的部署
Filter的部署分为两个步骤:
1、注册Filter
2、映射Filter
5.1、注册Filter
开发好Filter之后,需要在web.xml文件中进行注册,这样才能够被web服务器调用
在web.xml文件中注册Filter范例:
复制代码
<filter>
<description>FilterDemo02过滤器</description>
<filter-name>FilterDemo02</filter-name>
<filter-class>me.gacl.web.filter.FilterDemo02</filter-class>
<!--配置FilterDemo02过滤器的初始化参数-->
<init-param>
<description>配置FilterDemo02过滤器的初始化参数</description>
<param-name>name</param-name>
<param-value>gacl</param-value>
</init-param>
<init-param>
<description>配置FilterDemo02过滤器的初始化参数</description>
<param-name>like</param-name>
<param-value>java</param-value>
</init-param>
</filter>
复制代码
<description>用于添加描述信息,该元素的内容可为空,<description>可以不配置。
<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
<filter-class>元素用于指定过滤器的完整的限定类名。
<init-param>元素用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数。如果过滤器不需要指定初始化参数,那么<init-param>元素可以不配置。
5.2、映射Filter
在web.xml文件中注册了Filter之后,还要在web.xml文件中映射Filter
复制代码
<!--映射过滤器-->
<filter-mapping>
<filter-name>FilterDemo02</filter-name>
<!--“/*”表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
<filter-mapping>元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
<filter-name>子元素用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名字
<url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
<servlet-name>指定过滤器所拦截的Servlet名称。
<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。如下:
复制代码
<filter-mapping>
<filter-name>testFilter</filter-name>
<url-pattern>/index.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
复制代码
<dispatcher> 子元素可以设置的值及其意义:
REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

(四十三)——Filter高级开发
在filter中可以得到代表用户请求和响应的request、response对象,因此在编程中可以使用Decorator(装饰器)模式对request、response对象进行包装,再把包装对象传给目标资源,从而实现一些特殊需求。
一、Decorator设计模式
1.1、Decorator设计模式介绍
当某个对象的方法不适应业务需求时,通常有2种方式可以对方法进行增强:
编写子类,覆盖需增强的方法。
使用Decorator设计模式对方法进行增强。
在阎宏博士的《JAVA与模式》一书中开头是这样描述装饰(Decorator)模式的:装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
那么在实际应用中遇到需增强对象的方法时,到底选用哪种方式比较好呢?这个没有具体的定式,只能是根据具体的需求来采用具体的方式,不过有一种情况下,必须使用Decorator设计模式:即被增强的对象,开发人员只能得到它的对象,无法得到它的class文件。比如request、response对象,开发人员之所以在servlet中能通过sun公司定义的HttpServletRequest\response接口去操作这些对象,是因为Tomcat服务器厂商编写了request、response接口的实现类。web服务器在调用servlet时,会用这些接口的实现类创建出对象,然后传递给servlet程序。此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个?在程序中只能拿到服务器厂商提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。
1.2、Decorator设计模式的实现
1.首先看需要被增强对象继承了什么接口或父类,编写一个类也去继承这些接口或父类。
2.在类中定义一个变量,变量类型即需增强对象的类型。
3.在类中定义一个构造函数,接收需增强的对象。
4.覆盖需增强的方法,编写增强的代码。
二、使用Decorator设计模式增强request对象
Servlet API 中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper 类实现了request 接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request 对象的对应方法,以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
2.1、使用Decorator模式包装request对象解决get和post请求方式下的中文乱码问题
编写一个用于处理中文乱码的过滤器CharacterEncodingFilter,代码如下:
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: CharacterEncodingFilter
* @Description: 此过滤器用来解决解决get、post请求方式下的中文乱码问题
* @author: 孤傲苍狼
* @date: 2014-8-31 下午11:09:37
*
*/
public class CharacterEncodingFilter implements Filter {
private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-8";
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//得到在web.xml中配置的字符编码
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);
MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
chain.doFilter(requestWrapper, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
//得到过滤器的初始化配置信息
this.filterConfig = filterConfig;
}
public void destroy() {
}
}
/**
* @ClassName: MyCharacterEncodingRequest
* @Description: Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,
* (HttpServletRequestWrapper类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request对象的对应方法)
* 以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
* 所以当需要增强request对象时,只需要写一个类继承HttpServletRequestWrapper类,然后在重写需要增强的方法即可
* @author: 孤傲苍狼
* @date: 2014-9-2 下午10:42:57
* 1.实现与被增强对象相同的接口
2、定义一个变量记住被增强对象
3、定义一个构造函数,接收被增强对象
4、覆盖需要增强的方法
5、对于不想增强的方法,直接调用被增强对象(目标对象)的方法
*/
class MyCharacterEncodingRequest extends HttpServletRequestWrapper{
//定义一个变量记住被增强对象(request对象是需要被增强的对象)
private HttpServletRequest request;
//定义一个构造函数,接收被增强对象
public MyCharacterEncodingRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 覆盖需要增强的getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
try{
//获取参数的值
String value= this.request.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值
if(!this.request.getMethod().equalsIgnoreCase("get")) {
return value;
}else{
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
return value;
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
复制代码
在web.xml文件中配置CharacterEncodingFilter
复制代码
<!--配置字符过滤器,解决get、post请求方式下的中文乱码问题-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>me.gacl.web.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
编写jsp测试页面,如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
< %--引入jstl标签库 --%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML>
<html>
<head>
<title>使用字符过滤器解决解决get、post请求方式下的中文乱码问题</title>
</head>
<body>
< %--使用c:url标签构建url,构建好的url存储在servletDemo1变量中--%>
<c:url value="/servlet/ServletDemo1" scope="page" var="servletDemo1">
< %--构建的url的附带的中文参数 ,参数名是:username,值是:孤傲苍狼--%>
<c:param name="username" value="孤傲苍狼"></c:param>
</c:url>
< %--使用get的方式访问 --%>
<a href="${servletDemo1}">超链接(get方式请求)</a>
<hr/>
< %--使用post方式提交表单 --%>
<form action="${pageContext.request.contextPath}/servlet/ServletDemo1" method="post">
用户名:<input type="text" name="username" value="孤傲苍狼" />
<input type="submit" value="post方式提交">
</form>
</body>
</html>
复制代码
编写处理用户请求的ServletDemo1
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//接收参数
String username = request.getParameter("username");
//获取请求方式
String method = request.getMethod();
//获取输出流
PrintWriter out = response.getWriter();
out.write("请求的方式:"+method);
out.write("<br/>");
out.write("接收到的参数:"+username);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
测试结果如下:
(http://www.cnblogs.com/xdp-gacl/p/3952405.html )
从运行结果中可以看到,无论是get请求方式还是post请求方式,中文乱码问题都可以完美解决了。
2.2、使用Decorator模式包装request对象实现html标签转义功能
编写一个html转义过滤器,代码如下:
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: HtmlFilter
* @Description: html转义过滤器
* @author: 孤傲苍狼
* @date: 2014-9-2 下午11:28:41
*
*/
public class HtmlFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
MyHtmlRequest myrequest = new MyHtmlRequest(request);
chain.doFilter(myrequest, response);
}
public void destroy() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
/**
* @ClassName: MyHtmlRequest
* @Description: 使用Decorator模式包装request对象,实现html标签转义功能
* @author: 孤傲苍狼
* @date: 2014-9-2 下午11:29:09
*
*/
class MyHtmlRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
public MyHtmlRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 覆盖需要增强的getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
String value = this.request.getParameter(name);
if (value == null) {
return null;
}
//调用filter转义value中的html标签
return filter(value);
}
/**
* @Method: filter
* @Description: 过滤内容中的html标签
* @Anthor:孤傲苍狼
* @param message
* @return
*/
public String filter(String message) {
if (message == null){
return null;
}
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case ‘<‘:
result.append("&lt;");
break;
case ‘>‘:
result.append("&gt;");
break;
case ‘&‘:
result.append("&amp;");
break;
case ‘"‘:
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return result.toString();
}
}
复制代码
在web.xml文件中配置HtmlFilter
复制代码
<!--配置Html过滤器,转义内容中的html标签-->
<filter>
<filter-name>HtmlFilter</filter-name>
<filter-class>me.gacl.web.filter.HtmlFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HtmlFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
编写jsp测试页面,如下:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>html过滤器测试</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/servlet/ServletDemo2" method="post">
留言:
<textarea rows="8" cols="70" name="message">
<script type="text/javascript">
while(true){
alert("死循环了,我会不停地弹出了");
}
</script>
<a href="http://www.cnblogs.com">访问博客园</a>
</textarea>
<input type="submit" value="发表">
</form>
</body>
</html>
复制代码
编写处理用户请求的ServletDemo2
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServletDemo2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取用户输入的内容
String message = request.getParameter("message");
response.getWriter().write("您上次的留言是:<br/>" + message);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
测试结果如下:
( http://www.cnblogs.com/xdp-gacl/p/3952405.html )
从运行结果中可以看到,所有的html标签都被转义输出了。
2.3、使用Decorator模式包装request对象实现敏感字符过滤功能
编写一个敏感字符过滤器,代码如下:
复制代码
package me.gacl.web.filter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: DirtyFilter
* @Description: 敏感词过滤器
* @author: 孤傲苍狼
* @date: 2014-9-6 上午10:43:11
*
*/
public class DirtyFilter implements Filter {
private FilterConfig config = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig;
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
DirtyRequest dirtyrequest = new DirtyRequest(request);
chain.doFilter(dirtyrequest, response);
}
@Override
public void destroy() {
}
/**
* @Method: getDirtyWords
* @Description: 获取敏感字符
* @Anthor:孤傲苍狼
*
* @return
*/
private List<String> getDirtyWords(){
List<String> dirtyWords = new ArrayList<String>();
String dirtyWordPath = config.getInitParameter("dirtyWord");
InputStream inputStream = config.getServletContext().getResourceAsStream(dirtyWordPath);
InputStreamReader is = null;
try {
is = new InputStreamReader(inputStream,"UTF-8");
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
}
BufferedReader reader = new BufferedReader(is);
String line;
try {
while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
dirtyWords.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return dirtyWords;
}
/**
* @ClassName: DirtyRequest
* @Description: 使用Decorator模式包装request对象,实现敏感字符过滤功能
* @author: 孤傲苍狼
* @date: 2014-9-6 上午11:56:35
*
*/
class DirtyRequest extends HttpServletRequestWrapper{
private List<String> dirtyWords = getDirtyWords();
private HttpServletRequest request;
public DirtyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 重写getParameter方法,实现对敏感字符的过滤
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
String value = this.request.getParameter(name);
if(value==null){
return null;
}
for(String dirtyWord : dirtyWords){
if(value.contains(dirtyWord)){
System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
//替换敏感字符
value = value.replace(dirtyWord, "****");
}
}
return value;
}
}
}
复制代码
在web.xml文件中配置DirtyFilter
复制代码
<!--配置敏感字符过滤器-->
<filter>
<filter-name>DirtyFilter</filter-name>
<filter-class>me.gacl.web.filter.DirtyFilter</filter-class>
<!-- 配置要过滤的敏感字符文件 -->
<init-param>
<param-name>dirtyWord</param-name>
<param-value>/WEB-INF/DirtyWord.txt</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DirtyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
当用户填写的内容包含一些敏感字符时,在DirtyFilter过滤器中就会将这些敏感字符替换掉。
我们如果将上述的CharacterEncodingFilter、HtmlFilter、DirtyFilter这三个过滤器联合起来使用,那么就相当于是把request对象包装了3次,request对象的getParameter方法经过3次重写,使得getParameter方法的功能大大增强,可以同时解决中文乱码,html标签转义,敏感字符过滤这些需求。
在实际开发中完全可以将上述的三个过滤器合并成一个,让合并后的过滤器具有解决中文乱码,html标签转义,敏感字符过滤这些功能,例如:
复制代码
package me.gacl.web.filter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: AdvancedFilter
* @Description: 这个过滤器是用来解决中文乱码,转义内容中的html标签,过滤内容中的敏感字符的
* @author: 孤傲苍狼
* @date: 2014-9-6 下午6:17:37
*
*/
public class AdvancedFilter implements Filter {
private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-8";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//得到过滤器的初始化配置信息
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//得到在web.xml中配置的字符编码
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);
AdvancedRequest requestWrapper = new AdvancedRequest(request);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
class AdvancedRequest extends HttpServletRequestWrapper{
private List<String> dirtyWords = getDirtyWords();
//定义一个变量记住被增强对象(request对象是需要被增强的对象)
private HttpServletRequest request;
//定义一个构造函数,接收被增强对象
public AdvancedRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 覆盖需要增强的getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
try{
//获取参数的值
String value= this.request.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值
if(!this.request.getMethod().equalsIgnoreCase("get")) {
//调用filter转义value中的html标签
value= filter(value);
}else{
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
//调用filter转义value中的html标签
value= filter(value);
}
for(String dirtyWord : dirtyWords){
if(value.contains(dirtyWord)){
System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
//替换敏感字符
value = value.replace(dirtyWord, "****");
}
}
return value;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* @Method: filter
* @Description: 过滤内容中的html标签
* @Anthor:孤傲苍狼
* @param value
* @return
*/
public String filter(String value) {
if (value == null){
return null;
}
char content[] = new char[value.length()];
value.getChars(0, value.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case ‘<‘:
result.append("&lt;");
break;
case ‘>‘:
result.append("&gt;");
break;
case ‘&‘:
result.append("&amp;");
break;
case ‘"‘:
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
/**
* @Method: getDirtyWords
* @Description: 获取敏感字符
* @Anthor:孤傲苍狼
*
* @return
*/
private List<String> getDirtyWords(){
List<String> dirtyWords = new ArrayList<String>();
String dirtyWordPath = filterConfig.getInitParameter("dirtyWord");
InputStream inputStream = filterConfig.getServletContext().getResourceAsStream(dirtyWordPath);
InputStreamReader is = null;
try {
is = new InputStreamReader(inputStream,defaultCharset);
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
}
BufferedReader reader = new BufferedReader(is);
String line;
try {
while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
dirtyWords.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return dirtyWords;
}
}
复制代码
在web.xml文件中配置AdvancedFilter
复制代码
<filter>
<filter-name>AdvancedFilter</filter-name>
<filter-class>me.gacl.web.filter.AdvancedFilter</filter-class>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>dirtyWord</param-name>
<param-value>/WEB-INF/DirtyWord.txt</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AdvancedFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
AdvancedFilter过滤器同时具有解决中文乱码,转义内容中的html标签,过滤内容中的敏感字符这些功能。
三、使用Decorator设计模式增强response对象
Servlet API 中提供了response对象的Decorator设计模式的默认实现类HttpServletResponseWrapper ,HttpServletResponseWrapper类实现了response接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 response对象的对应方法,以避免用户在对response对象进行增强时需要实现response接口中的所有方法。
3.1、response增强案例——压缩响应正文内容
应用HttpServletResponseWrapper对象,压缩响应正文内容。
具体思路:通过filter向目标页面传递一个自定义的response对象。在自定义的response对象中,重写getOutputStream方法和getWriter方法,使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中。当页面完成输出后,在filter中就可得到页面写出的数据,从而我们可以调用GzipOuputStream对数据进行压缩后再写出给浏览器,以此完成响应正文件压缩功能。
编写压缩过滤器,代码如下:
复制代码
package me.gacl.web.filter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* @ClassName: GzipFilter
* @Description: 压缩过滤器,将web应用中的文本都经过压缩后再输出到浏览器
* @author: 孤傲苍狼
* @date: 2014-9-7 上午10:52:42
*
*/
public class GzipFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
BufferResponse myresponse = new BufferResponse(response);
chain.doFilter(request, myresponse);
//拿出缓存中的数据,压缩后再打给浏览器
byte out[] = myresponse.getBuffer();
System.out.println("原始大小:" + out.length);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
//压缩输出流中的数据
GZIPOutputStream gout = new GZIPOutputStream(bout);
gout.write(out);
gout.close();
byte gzip[] = bout.toByteArray();
System.out.println("压缩后的大小:" + gzip.length);
response.setHeader("content-encoding", "gzip");
response.setContentLength(gzip.length);
response.getOutputStream().write(gzip);
}
public void destroy() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
class BufferResponse extends HttpServletResponseWrapper{
private ByteArrayOutputStream bout = new ByteArrayOutputStream();
private PrintWriter pw;
private HttpServletResponse response;
public BufferResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream(bout);
}
@Override
public PrintWriter getWriter() throws IOException {
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}
public byte[] getBuffer(){
try{
if(pw!=null){
pw.close();
}
if(bout!=null){
bout.flush();
return bout.toByteArray();
}
return null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class MyServletOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bout;
public MyServletOutputStream(ByteArrayOutputStream bout){
this.bout = bout;
}
@Override
public void write(int b) throws IOException {
this.bout.write(b);
}
}
复制代码
在web.xml中配置压缩过滤器
复制代码
<filter>
<description>配置压缩过滤器</description>
<filter-name>GzipFilter</filter-name>
<filter-class>me.gacl.web.filter.GzipFilter</filter-class>
</filter>
<!--jsp文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<!-- 配置过滤器的拦截方式-->
<!-- 对于在Servlet中通过
request.getRequestDispatcher("jsp页面路径").forward(request, response)
方式访问的Jsp页面的要进行拦截 -->
<dispatcher>FORWARD</dispatcher>
<!--对于直接以URL方式访问的jsp页面进行拦截,过滤器的拦截方式默认就是 REQUEST-->
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<!--js文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<!--css文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
<!--html文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
复制代码
3.2、response增强案例——缓存数据到内存
对于页面中很少更新的数据,例如商品分类,为避免每次都要从数据库查询分类数据,因此可把分类数据缓存在内存或文件中,以此来减轻数据库压力,提高系统响应速度。
编写缓存数据的过滤器,代码如下:
复制代码
package me.gacl.web.filter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* @ClassName: WebResourceCachedFilter
* @Description: Web资源缓存过滤器
* @author: 孤傲苍狼
* @date: 2014-9-8 上午12:20:16
*
*/
public class WebResourceCachedFilter implements Filter {
/**
* @Field: map
* 缓存Web资源的Map容器
*/
private Map<String,byte[]> map = new HashMap<String,byte[]>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//1.得到用户请求的uri
String uri = request.getRequestURI();
//2.看缓存中有没有uri对应的数据
byte b[] = map.get(uri);
//3.如果缓存中有,直接拿缓存的数据打给浏览器,程序返回
if(b!=null){
//根据字节数组和指定的字符编码构建字符串
String webResourceHtmlStr = new String(b,response.getCharacterEncoding());
System.out.println(webResourceHtmlStr);
response.getOutputStream().write(b);
return;
}
//4.如果缓存没有,让目标资源执行,并捕获目标资源的输出
BufferResponse myresponse = new BufferResponse(response);
chain.doFilter(request, myresponse);
//获取缓冲流中的内容的字节数组
byte out[] = myresponse.getBuffer();
//5.把资源的数据以用户请求的uri为关键字保存到缓存中
map.put(uri, out);
//6.把数据打给浏览器
response.getOutputStream().write(out);
}
@Override
public void destroy() {
}
class BufferResponse extends HttpServletResponseWrapper{
private ByteArrayOutputStream bout = new ByteArrayOutputStream(); //捕获输出的缓存
private PrintWriter pw;
private HttpServletResponse response;
public BufferResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream(bout);
}
@Override
public PrintWriter getWriter() throws IOException {
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}
public byte[] getBuffer(){
try{
if(pw!=null){
pw.close();
}
return bout.toByteArray();
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class MyServletOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bout;
public MyServletOutputStream(ByteArrayOutputStream bout){ //接收数据写到哪里
this.bout = bout;
}
@Override
public void write(int b) throws IOException {
bout.write(b);
}
}
}
复制代码
在web.xml中配置Web资源缓存过滤器
复制代码
<filter>
<description>Web资源缓存过滤器</description>
<filter-name>WebResourceCachedFilter</filter-name>
<filter-class>me.gacl.web.filter.WebResourceCachedFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>WebResourceCachedFilter</filter-name>
<!-- 映射需要缓存输出的JSP页面,这几个页面都只是单纯作为输入UI,不会有太多的变化,因此可以缓存输出 -->
<url-pattern>/login.jsp</url-pattern>
<url-pattern>/test.jsp</url-pattern>
<url-pattern>/test2.jsp</url-pattern>
</filter-mapping>


(四十四)——监听器(Listener)学习
一、监听器介绍
1.1、监听器的概念

监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。
1.2、监听器案例——监听window窗口的事件监听器
复制代码
package me.gacl.listener.demo;
import java.awt.Frame;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
public class Demo1 {
/**
*java的事件监听机制
*1、事件监听涉及到三个组件:事件源、事件对象、事件监听器
*2、当事件源上发生某一个动作时,它会调用事件监听器的一个方法,并在调用该方法时把事件对象传递进去,
* 开发人员在监听器中通过事件对象,就可以拿到事件源,从而对事件源进行操作。
*/
public static void main(String[] args) {
Frame f = new Frame();
f.setSize(400, 400);
f.setVisible(true);
//注册事件监听器
f.addWindowListener(new WindowListener(){
public void windowActivated(WindowEvent e) {
}
public void windowClosed(WindowEvent e) {
}
/**
* 当window窗体关闭时就会WindowListener这个监听器监听到,
* 监听器就会调用windowClosing方法处理window窗体关闭时的动作
*/
public void windowClosing(WindowEvent e) {
//通过事件对象e来获取事件源对象
Frame f = (Frame) e.getSource();
System.out.println(f+"窗体正在关闭");
f.dispose();
}
public void windowDeactivated(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowOpened(WindowEvent e) {
}
});
}
}
复制代码
1.3、设计一个可以被别的对象监听的对象
我们平时做开发的时候,我们是写监听器去监听其他对象,那么我们如果想设计一个对象,让这个对象可以被别的对象监听又该怎么做呢,可以按照严格的事件处理模型来设计一个对象,这个对象就可以被别的对象监听,事件处理模型涉及到三个组件:事件源、事件对象、事件监听器。
下面我们来按照事件处理模型来设计一个Person对象,具体代码如下:
复制代码
package me.gacl.observer;
/**
* @ClassName: Person(事件源)
* @Description: 设计一个Person类作为事件源,这个类的对象的行为(比如吃饭、跑步)可以被其他的对象监听
* @author: 孤傲苍狼
* @date: 2014-9-9 下午9:26:06
*
*/
public class Person {
/**
* @Field: listener
* 在Person类中定义一个PersonListener变量来记住传递进来的监听器
*/
private PersonListener listener;
/**
* @Method: eat
* @Description: 设计Person的行为:吃
* @Anthor:孤傲苍狼
*
*/
public void eat() {
if (listener != null) {
/**
* 调用监听器的doeat方法监听Person类对象eat(吃)这个动作,将事件对象Event传递给doeat方法,
* 事件对象封装了事件源,new Event(this)中的this代表的就是事件源
*/
listener.doeat(new Event(this));
}
}
/**
* @Method: run
* @Description: 设计Person的行为:跑
* @Anthor:孤傲苍狼
*
*/
public void run() {
if (listener != null) {
/**
* 调用监听器的dorun方法监听Person类对象run(跑)这个动作,将事件对象Event传递给doeat方法,
* 事件对象封装了事件源,new Event(this)中的this代表的就是事件源
*/
listener.dorun(new Event(this));
}
}
/**
* @Method: registerListener
* @Description: 这个方法是用来注册对Person类对象的行为进行监听的监听器
* @Anthor:孤傲苍狼
*
* @param listener
*/
public void registerListener(PersonListener listener) {
this.listener = listener;
}
}
/**
* @ClassName: PersonListener(事件监听器)
* @Description: 设计Person类(事件源)的监听器接口
* @author: 孤傲苍狼
* @date: 2014-9-9 下午9:28:06
*
*/
interface PersonListener {
/**
* @Method: doeat
* @Description: 这个方法是用来监听Person对象eat(吃)这个行为动作,
* 当实现类实现doeat方法时就可以监听到Person类对象eat(吃)这个动作
* @Anthor:孤傲苍狼
*
* @param e
*/
void doeat(Event e);
/**
* @Method: dorun
* @Description: 这个方法是用来监听Person对象run(跑)这个行为动作,
* 当实现类实现dorun方法时就可以监听到Person类对象run(跑)这个动作
* @Anthor:孤傲苍狼
*
* @param e
*/
void dorun(Event e);
}
/**
* @ClassName: Event(事件对象)
* @Description:设计事件类,用来封装事件源
* @author: 孤傲苍狼
* @date: 2014-9-9 下午9:37:56
*
*/
class Event {
/**
* @Field: source
* 事件源(Person就是事件源)
*/
private Person source;
public Event() {
}
public Event(Person source) {
this.source = source;
}
public Person getSource() {
return source;
}
public void setSource(Person source) {
this.source = source;
}
}
复制代码
经过这样的设计之后,Peron类的对象就是可以被其他对象监听了。测试代码如下:
复制代码
package me.gacl.observer;
public class PersonTest {
/**
* @Method: main
* @Description: 测试Person类
* @Anthor:孤傲苍狼
*
* @param args
*/
public static void main(String[] args) {
//
Person p = new Person();
//注册监听p对象行为的监听器
p.registerListener(new PersonListener() {
//监听p吃东西这个行为
public void doeat(Event e) {
Person p = e.getSource();
System.out.println(p + "在吃东西");
}
//监听p跑步这个行为
public void dorun(Event e) {
Person p = e.getSource();
System.out.println(p + "在跑步");
}
});
//p在吃东西
p.eat();
//p在跑步
p.run();
}
}
复制代码
运行结果:
me.gacl.observer.Person@4a5ab2在吃东西
me.gacl.observer.Person@4a5ab2在跑步
二、JavaWeb中的监听器
2.1、基本概念
JavaWeb中的监听器是Servlet规范中定义的一种特殊类,它用于监听web应用程序中的ServletContext, HttpSession和 ServletRequest等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。
2.2、Servlet监听器的分类
在Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别为ServletContext,HttpSession和ServletRequest这三个域对象
Servlet规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型:
监听域对象自身的创建和销毁的事件监听器。
监听域对象中的属性的增加和删除的事件监听器。
监听绑定到HttpSession域中的某个对象的状态的事件监听器。
2.3、监听ServletContext域对象的创建和销毁
ServletContextListener接口用于监听ServletContext对象的创建和销毁事件。实现了ServletContextListener接口的类都可以对ServletContext对象的创建和销毁进行监听。
当ServletContext对象被创建时,激发contextInitialized (ServletContextEvent sce)方法。
当ServletContext对象被销毁时,激发contextDestroyed(ServletContextEvent sce)方法。
ServletContext域对象创建和销毁时机:
创建:服务器启动针对每一个Web应用创建ServletContext
销毁:服务器关闭前先关闭代表每一个web应用的ServletContext
范例:编写一个MyServletContextListener类,实现ServletContextListener接口,监听ServletContext对象的创建和销毁
1、编写监听器,代码如下:
复制代码
package me.gacl.web.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* @ClassName: MyServletContextListener
* @Description: MyServletContextListener类实现了ServletContextListener接口,
* 因此可以对ServletContext对象的创建和销毁这两个动作进行监听。
* @author: 孤傲苍狼
* @date: 2014-9-9 下午10:26:16
*
*/
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext对象创建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext对象销毁");
}
}
复制代码
2、在web.xml文件中注册监听器
我们在上面的中讲到,要想监听事件源,那么必须将监听器注册到事件源上才能够实现对事件源的行为动作进行监听,在JavaWeb中,监听的注册是在web.xml文件中进行配置的,如下:
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
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_3_0.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 注册针对ServletContext对象进行监听的监听器 -->
<listener>
<description>ServletContextListener监听器</description>
<!--实现了ServletContextListener接口的监听器类 -->
<listener-class>me.gacl.web.listener.MyServletContextListener</listener-class>
</listener>
</web-app>
复制代码
经过这两个步骤,我们就完成了监听器的编写和注册,Web服务器在启动时,就会自动把在web.xml中配置的监听器注册到ServletContext对象上,这样开发好的MyServletContextListener监听器就可以对ServletContext对象进行监听了。
2.4、监听HttpSession域对象的创建和销毁
HttpSessionListener 接口用于监听HttpSession对象的创建和销毁
创建一个Session时,激发sessionCreated (HttpSessionEvent se) 方法
销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se) 方法。
范例:编写一个MyHttpSessionListener类,实现HttpSessionListener接口,监听HttpSession对象的创建和销毁
1、编写监听器,代码如下:
复制代码
package me.gacl.web.listener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @ClassName: MyHttpSessionListener
* @Description: MyHttpSessionListener类实现了HttpSessionListener接口,
* 因此可以对HttpSession对象的创建和销毁这两个动作进行监听。
* @author: 孤傲苍狼
* @date: 2014-9-9 下午11:04:33
*
*/
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println( se.getSession() + "创建了!!");
}
/* HttpSession的销毁时机需要在web.xml中进行配置,如下:
* <session-config>
<session-timeout>1</session-timeout>
</session-config>
这样配置就表示session在1分钟之后就被销毁
*/
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session销毁了!!");
}
}
复制代码
2、在web.xml文件中注册监听器
复制代码
<!--注册针对HttpSession对象进行监听的监听器-->
<listener>
<description>HttpSessionListener监听器</description>
<listener-class>me.gacl.web.listener.MyHttpSessionListener</listener-class>
</listener>
<!-- 配置HttpSession对象的销毁时机 -->
<session-config>
<!--配置HttpSession对象的1分钟之后销毁 -->
<session-timeout>1</session-timeout>
</session-config>
复制代码
当我们访问jsp页面时,HttpSession对象就会创建,此时就可以在HttpSessionListener观察到HttpSession对象的创建过程了,我们可以写一个jsp页面观察HttpSession对象创建的过程。
如下:index.jsp
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<!DOCTYPE HTML>
<html>
<head>
<title>HttpSessionListener监听器监听HttpSession对象的创建</title>
</head>
<body>
一访问JSP页面,HttpSession就创建了,创建好的Session的Id是:${pageContext.session.id}
</body>
</html>
复制代码
运行结果如下:

2.5、监听ServletRequest域对象的创建和销毁
ServletRequestListener接口用于监听ServletRequest 对象的创建和销毁
Request对象被创建时,监听器的requestInitialized(ServletRequestEvent sre)方法将会被调用
Request对象被销毁时,监听器的requestDestroyed(ServletRequestEvent sre)方法将会被调用
ServletRequest域对象创建和销毁时机:
创建:用户每一次访问都会创建request对象
销毁:当前访问结束,request对象就会销毁
范例:编写一个MyServletRequestListener类,实现ServletRequestListener接口,监听ServletRequest对象的创建和销毁
1、编写监听器,代码如下:
复制代码
package me.gacl.web.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
/**
* @ClassName: MyServletRequestListener
* @Description: MyServletRequestListener类实现了ServletRequestListener接口,
* 因此可以对ServletRequest对象的创建和销毁这两个动作进行监听。
* @author: 孤傲苍狼
* @date: 2014-9-9 下午11:50:08
*
*/
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "销毁了!!");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println(sre.getServletRequest() + "创建了!!");
}
}
复制代码
2、在web.xml文件中注册监听器
<!--注册针对ServletRequest对象进行监听的监听器-->
<listener>
<description>ServletRequestListener监听器</description>
<listener-class>me.gacl.web.listener.MyServletRequestListener</listener-class>
</listener>
测试结果如下:

从运行结果中可以看到,用户每一次访问都会创建request对象,当访问结束后,request对象就会销毁。
以上就是对监听器的一些简单讲解。

(四十五)——监听器(Listener)学习二
一、监听域对象中属性的变更的监听器
域对象中属性的变更的事件监听器就是用来监听 ServletContext, HttpSession, HttpServletRequest 这三个对象中的属性变更信息事件的监听器。
这三个监听器接口分别是ServletContextAttributeListener, HttpSessionAttributeListener 和ServletRequestAttributeListener,这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。
1.1、attributeAdded 方法
当向被监听对象中增加一个属性时,web容器就调用事件监听器的attributeAdded方法进行响应,这个方法接收一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保存到域中的属性对象
各个域属性监听器中的完整语法定义为:
public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeReplaced(HttpSessionBindingEvent hsbe)
public void attributeRmoved(ServletRequestAttributeEvent srae)
1.2、attributeRemoved 方法
当删除被监听对象中的一个属性时,web容器调用事件监听器的attributeRemoved方法进行响应
各个域属性监听器中的完整语法定义为:
public void attributeRemoved(ServletContextAttributeEvent scae)
public void attributeRemoved (HttpSessionBindingEvent hsbe)
public void attributeRemoved (ServletRequestAttributeEvent srae)
1.3、attributeReplaced 方法
当监听器的域对象中的某个属性被替换时,web容器调用事件监听器的attributeReplaced方法进行响应
各个域属性监听器中的完整语法定义为:
public void attributeReplaced(ServletContextAttributeEvent scae)
public void attributeReplaced (HttpSessionBindingEvent hsbe)
public void attributeReplaced (ServletRequestAttributeEvent srae)
1.4、ServletContextAttributeListener监听器范例:
编写ServletContextAttributeListener监听器监听ServletContext域对象的属性值变化情况,代码如下:
复制代码
package me.gacl.web.listener;
import java.text.MessageFormat;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
/**
* @ClassName: MyServletContextAttributeListener
* @Description: ServletContext域对象中属性的变更的事件监听器
* @author: 孤傲苍狼
* @date: 2014-9-11 下午10:53:04
*
*/
public class MyServletContextAttributeListener implements
ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent scab) {
String str =MessageFormat.format(
"ServletContext域对象中添加了属性:{0},属性值是:{1}"
,scab.getName()
,scab.getValue());
System.out.println(str);
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scab) {
String str =MessageFormat.format(
"ServletContext域对象中删除属性:{0},属性值是:{1}"
,scab.getName()
,scab.getValue());
System.out.println(str);
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scab) {
String str =MessageFormat.format(
"ServletContext域对象中替换了属性:{0}的值"
,scab.getName());
System.out.println(str);
}
}
复制代码
在web.xml文件中注册监听器
<listener>
<description>MyServletContextAttributeListener监听器</description>
<listener-class>me.gacl.web.listener.MyServletContextAttributeListener</listener-class>
</listener>
编写ServletContextAttributeListenerTest.jsp测试页面
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>ServletContextAttributeListener监听器测试</title>
</head>
<body>
<%
//往application域对象中添加属性
application.setAttribute("name", "孤傲苍狼");
//替换application域对象中name属性的值
application.setAttribute("name", "gacl");
//移除application域对象中name属性
application.removeAttribute("name");
%>
</body>
</html>
复制代码
运行结果如下:

从运行结果中可以看到,ServletContextListener监听器成功监听到了ServletContext域对象(application)中的属性值的变化情况。
1.5、ServletRequestAttributeListener和HttpSessionAttributeListener监听器范例:
编写监听器监听HttpSession和HttpServletRequest域对象的属性值变化情况,代码如下:
复制代码
package me.gacl.web.listener;
import java.text.MessageFormat;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class MyRequestAndSessionAttributeListener implements
HttpSessionAttributeListener, ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
String str =MessageFormat.format(
"ServletRequest域对象中添加了属性:{0},属性值是:{1}"
,srae.getName()
,srae.getValue());
System.out.println(str);
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
String str =MessageFormat.format(
"ServletRequest域对象中删除属性:{0},属性值是:{1}"
,srae.getName()
,srae.getValue());
System.out.println(str);
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
String str =MessageFormat.format(
"ServletRequest域对象中替换了属性:{0}的值"
,srae.getName());
System.out.println(str);
}
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
String str =MessageFormat.format(
"HttpSession域对象中添加了属性:{0},属性值是:{1}"
,se.getName()
,se.getValue());
System.out.println(str);
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
String str =MessageFormat.format(
"HttpSession域对象中删除属性:{0},属性值是:{1}"
,se.getName()
,se.getValue());
System.out.println(str);
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
String str =MessageFormat.format(
"HttpSession域对象中替换了属性:{0}的值"
,se.getName());
System.out.println(str);
}
}
复制代码
在web.xml文件中注册监听器
<listener>
<description>MyRequestAndSessionAttributeListener监听器</description>
<listener-class>me.gacl.web.listener.MyRequestAndSessionAttributeListener</listener-class>
</listener>
编写RequestAndSessionAttributeListenerTest.jsp测试页面
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>RequestAndSessionAttributeListener监听器测试</title>
</head>
<body>
<%
//往session域对象中添加属性
session.setAttribute("aa", "bb");
//替换session域对象中aa属性的值
session.setAttribute("aa", "xx");
//移除session域对象中aa属性
session.removeAttribute("aa");
//往request域对象中添加属性
request.setAttribute("aa", "bb");
//替换request域对象中aa属性的值
request.setAttribute("aa", "xx");
//移除request域对象中aa属性
request.removeAttribute("aa");
%>
</body>
</html>
复制代码
运行结果如下:

从运行结果中可以看到,HttpSessionAttributeListener监听器和ServletRequestAttributeListener成功监听到了HttpSession域对象和HttpServletRequest域对象的属性值变化情况。
二、感知Session绑定的事件监听器
保存在Session域中的对象可以有多种状态:绑定(session.setAttribute("bean",Object))到Session中;从 Session域中解除(session.removeAttribute("bean"))绑定;随Session对象持久化到一个存储设备中;随Session对象从一个存储设备中恢复
Servlet 规范中定义了两个特殊的监听器接口"HttpSessionBindingListener和HttpSessionActivationListener"来帮助JavaBean 对象了解自己在Session域中的这些状态: ,实现这两个接口的类不需要 web.xml 文件中进行注册。
2.1、HttpSessionBindingListener接口
实现了HttpSessionBindingListener接口的JavaBean对象可以感知自己被绑定到Session中和 Session中删除的事件
当对象被绑定到HttpSession对象中时,web服务器调用该对象的void valueBound(HttpSessionBindingEvent event)方法
当对象从HttpSession对象中解除绑定时,web服务器调用该对象的void valueUnbound(HttpSessionBindingEvent event)方法
范例:
复制代码
package me.gacl.domain;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
/**
* @ClassName: JavaBeanDemo1
* @Description:
* 实现了HttpSessionBindingListener接口的 JavaBean对象可以感知自己被绑定到 Session中和从Session中删除的事件
当对象被绑定到 HttpSession 对象中时,web 服务器调用该对象的 void valueBound(HttpSessionBindingEvent event) 方法
当对象从 HttpSession 对象中解除绑定时,web 服务器调用该对象的 void valueUnbound(HttpSessionBindingEvent event)方法
* @author: 孤傲苍狼
* @date: 2014-9-11 下午11:14:54
*
*/
public class JavaBeanDemo1 implements HttpSessionBindingListener {
private String name;
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println(name+"被加到session中了");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println(name+"被session踢出来了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public JavaBeanDemo1(String name) {
this.name = name;
}
}
复制代码
上述的JavaBeanDemo1这个javabean实现了HttpSessionBindingListener接口,那么这个JavaBean对象可以感知自己被绑定到Session中和从Session中删除的这两个操作,测试代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<%@page import=" me.gacl.domain.JavaBeanDemo1"%>
<!DOCTYPE HTML>
<html>
<head>
<title></title>
</head>
<body>
<%
//将javabean对象绑定到Session中
session.setAttribute("bean",new JavaBeanDemo1("孤傲苍狼"));
//从Session中删除javabean对象
session.removeAttribute("bean");
%>
</body>
</html>
复制代码
运行结果如下:

2.2、HttpSessionActivationListener接口
实现了HttpSessionActivationListener接口的JavaBean对象可以感知自己被活化(反序列化)和钝化(序列化)的事件
当绑定到HttpSession对象中的javabean对象将要随HttpSession对象被钝化(序列化)之前,web服务器调用该javabean对象的void sessionWillPassivate(HttpSessionEvent event) 方法。这样javabean对象就可以知道自己将要和HttpSession对象一起被序列化(钝化)到硬盘中.
当绑定到HttpSession对象中的javabean对象将要随HttpSession对象被活化(反序列化)之后,web服务器调用该javabean对象的void sessionDidActive(HttpSessionEvent event)方法。这样javabean对象就可以知道自己将要和 HttpSession对象一起被反序列化(活化)回到内存中
范例:
复制代码
package me.gacl.domain;
import java.io.Serializable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
/**
* @ClassName: JavaBeanDemo2
* @Description:
实现了HttpSessionActivationListener接口的 JavaBean 对象可以感知自己被活化和钝化的事件
活化:javabean对象和Session一起被反序列化(活化)到内存中。
钝化:javabean对象存在Session中,当服务器把session序列化到硬盘上时,如果Session中的javabean对象实现了Serializable接口
那么服务器会把session中的javabean对象一起序列化到硬盘上,javabean对象和Session一起被序列化到硬盘中的这个操作称之为钝化
如果Session中的javabean对象没有实现Serializable接口,那么服务器会先把Session中没有实现Serializable接口的javabean对象移除
然后再把Session序列化(钝化)到硬盘中
当绑定到 HttpSession对象中的javabean对象将要随 HttpSession对象被钝化之前,
web服务器调用该javabean对象对象的 void sessionWillPassivate(HttpSessionEvent event)方法
这样javabean对象就可以知道自己将要和 HttpSession对象一起被序列化(钝化)到硬盘中
当绑定到HttpSession对象中的javabean对象将要随 HttpSession对象被活化之后,
web服务器调用该javabean对象的 void sessionDidActive(HttpSessionEvent event)方法
这样javabean对象就可以知道自己将要和 HttpSession对象一起被反序列化(活化)回到内存中
* @author: 孤傲苍狼
* @date: 2014-9-11 下午11:22:35
*
*/
public class JavaBeanDemo2 implements HttpSessionActivationListener,
Serializable {
private static final long serialVersionUID = 7589841135210272124L;
private String name;
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println(name+"和session一起被序列化(钝化)到硬盘了,session的id是:"+se.getSession().getId());
}
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println(name+"和session一起从硬盘反序列化(活化)回到内存了,session的id是:"+se.getSession().getId());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public JavaBeanDemo2(String name) {
this.name = name;
}
}
复制代码
为了观察绑定到HttpSession对象中的javabean对象随HttpSession对象一起被钝化到硬盘上和从硬盘上重新活化回到内存中的的过程,我们需要借助tomcat服务器帮助我们完成HttpSession对象的钝化和活化过程,具体做法如下:
在WebRoot\META-INF文件夹下创建一个context.xml文件,如下所示:

context.xml文件的内容如下:
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="gacl"/>
</Manager>
</Context>
在context.xml文件文件中配置了1分钟之后就将HttpSession对象钝化到本地硬盘的一个gacl文件夹中
jsp测试代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<%@page import="me.gacl.domain.JavaBeanDemo2"%>
<!DOCTYPE HTML>
<html>
<head>
<title></title>
</head>
<body>
一访问JSP页面,HttpSession就创建了,创建好的Session的Id是:${pageContext.session.id}
<hr/>
<%
session.setAttribute("bean",new JavaBeanDemo2("孤傲苍狼"));
%>
</body>
</html>
复制代码
访问这个jsp页面,服务器就会马上创建一个HttpSession对象,然后将实现了HttpSessionActivationListener接口的JavaBean对象绑定到session对象中,这个jsp页面在等待1分钟之后没有人再次访问,那么服务器就会自动将这个HttpSession对象钝化(序列化)到硬盘上,

我们可以在tomcat服务器的work\Catalina\localhost\JavaWeb_Listener_20140908\gacl文件夹下找到序列化到本地存储的session,如下图所示:

当再次访问这个Jsp页面时,服务器又会自动将已经钝化(序列化)到硬盘上HttpSession对象重新活化(反序列化)回到内存中。运行结果如下:

JavaWeb开发技术中的监听器技术的内容就这么多了,在平时的工作中,监听器技术在JavaWeb项目开发中用得是比较多,因此必须掌握这门技术。

(四十六)——Filter(过滤器)常见应用
一、统一全站字符编码
通过配置参数charset指明使用何种字符编码,以处理Html Form请求参数的中文问题
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: CharacterEncodingFilter
* @Description: 此过滤器用来解决全站中文乱码问题
* @author: 孤傲苍狼
* @date: 2014-8-31 下午11:09:37
*
*/
public class CharacterEncodingFilter implements Filter {
private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-8";
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);
MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
chain.doFilter(requestWrapper, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
//得到过滤器的初始化配置信息
this.filterConfig = filterConfig;
}
public void destroy() {
}
}
/*
1.实现与被增强对象相同的接口
2、定义一个变量记住被增强对象
3、定义一个构造器,接收被增强对象
4、覆盖需要增强的方法
5、对于不想增强的方法,直接调用被增强对象(目标对象)的方法
*/
class MyCharacterEncodingRequest extends HttpServletRequestWrapper{
private HttpServletRequest request;
public MyCharacterEncodingRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 重写getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
try{
//获取参数的值
String value= this.request.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值
if(!this.request.getMethod().equalsIgnoreCase("get")) {
return value;
}else{
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
return value;
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
复制代码
web.xml文件中的配置如下:
复制代码
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>me.gacl.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
复制代码
二、禁止浏览器缓存所有动态页面
有3 个HTTP 响应头字段都可以禁止浏览器缓存当前页面,它们在 Servlet 中的示例代码如下:
response.setDateHeader("Expires",-1);
response.setHeader("Cache-Control","no-cache");
response.setHeader("Pragma","no-cache");
并不是所有的浏览器都能完全支持上面的三个响应头,因此最好是同时使用上面的三个响应头。
Expires数据头:值为GMT时间值,为-1指浏览器不要缓存页面
Cache-Control响应头有两个常用值:
no-cache指浏览器不要缓存当前页面。
max-age:xxx指浏览器缓存页面xxx秒。
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: NoCacheFilter
* @Description: 禁止浏览器缓存所有动态页面
* @author: 孤傲苍狼
* @date: 2014-8-31 下午11:25:40
*
*/
public class NoCacheFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
//把ServletRequest强转成HttpServletRequest
HttpServletRequest request = (HttpServletRequest) req;
//把ServletResponse强转成HttpServletResponse
HttpServletResponse response = (HttpServletResponse) resp;
//禁止浏览器缓存所有动态页面
response.setDateHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}
复制代码
web.xml文件中的配置如下:
复制代码
<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>me.gacl.web.filter.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<!--只拦截Jsp请求-->
<servlet-name>*.jsp</servlet-name>
</filter-mapping>
复制代码
三、控制浏览器缓存页面中的静态资源
有些动态页面中引用了一些图片或css文件以修饰页面效果,这些图片和css文件经常是不变化的,所以为减轻服务器的压力,可以使用filter控制浏览器缓存这些文件,以提升服务器的性能。
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: CacheFilter
* @Description: 控制缓存的filter
* @author: 孤傲苍狼
* @date: 2014-9-1 下午9:39:38
*
*/
public class CacheFilter implements Filter {
private FilterConfig filterConfig;
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//1.获取用户想访问的资源
String uri = request.getRequestURI();
//2.得到用户想访问的资源的后缀名
String ext = uri.substring(uri.lastIndexOf(".")+1);
//得到资源需要缓存的时间
String time = filterConfig.getInitParameter(ext);
if(time!=null){
long t = Long.parseLong(time)*3600*1000;
//设置缓存
response.setDateHeader("expires", System.currentTimeMillis() + t);
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
}
}
复制代码
web.xml文件中的配置如下:
复制代码
<!-- 配置缓存过滤器 -->
<filter>
<filter-name>CacheFilter</filter-name>
<filter-class>me.gacl.web.filter.CacheFilter</filter-class>
<!-- 配置要缓存的web资源以及缓存时间,以小时为单位 -->
<init-param>
<param-name>css</param-name>
<param-value>4</param-value>
</init-param>
<init-param>
<param-name>jpg</param-name>
<param-value>1</param-value>
</init-param>
<init-param>
<param-name>js</param-name>
<param-value>4</param-value>
</init-param>
<init-param>
<param-name>png</param-name>
<param-value>4</param-value>
</init-param>
</filter>
<!-- 配置要缓存的web资源的后缀-->
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.jpg</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.png</url-pattern>
</filter-mapping>
复制代码
四、实现用户自动登陆
思路是这样的:
1、在用户登陆成功后,发送一个名称为user的cookie给客户端,cookie的值为用户名和md5加密后的密码。
2、编写一个AutoLoginFilter,这个filter检查用户是否带有名称为user的cookie来,如果有,则调用dao查询cookie的用户名和密码是否和数据库匹配,匹配则向session中存入user对象(即用户登陆标记),以实现程序完成自动登陆。
核心代码如下:
处理用户登录的控制器:LoginServlet
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.dao.UserDao;
import me.gacl.domain.User;
import me.gacl.util.WebUtils;
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
UserDao dao = new UserDao();
User user = dao.find(username, password);
if(user==null){
request.setAttribute("message", "用户名或密码不对!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
request.getSession().setAttribute("user", user);
//发送自动登陆cookie给客户端浏览器进行存储
sendAutoLoginCookie(request,response,user);
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
/**
* @Method: sendAutoLoginCookie
* @Description: 发送自动登录cookie给客户端浏览器
* @Anthor:孤傲苍狼
*
* @param request
* @param response
* @param user
*/
private void sendAutoLoginCookie(HttpServletRequest request, HttpServletResponse response, User user) {
if (request.getParameter("logintime")!=null) {
int logintime = Integer.parseInt(request.getParameter("logintime"));
//创建cookie,cookie的名字是autologin,值是用户登录的用户名和密码,用户名和密码之间使用.进行分割,密码经过md5加密处理
Cookie cookie = new Cookie("autologin",user.getUsername() + "." + WebUtils.md5(user.getPassword()));
//设置cookie的有效期
cookie.setMaxAge(logintime);
//设置cookie的有效路径
cookie.setPath(request.getContextPath());
//将cookie写入到客户端浏览器
response.addCookie(cookie);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
处理用户自动登录的过滤器:AutoLoginFilter
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.dao.UserDao;
import me.gacl.domain.User;
import me.gacl.util.WebUtils;
public class AutoLoginFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//如果已经登录了,就直接chain.doFilter(request, response)放行
if(request.getSession().getAttribute("user")!=null){
chain.doFilter(request, response);
return;
}
//1.得到用户带过来的authlogin的cookie
String value = null;
Cookie cookies[] = request.getCookies();
for(int i=0;cookies!=null && i<cookies.length;i++){
if(cookies[i].getName().equals("autologin")){
value = cookies[i].getValue();
}
}
//2.得到 cookie中的用户名和密码
if(value!=null){
String username = value.split("\\.")[0];
String password = value.split("\\.")[1];
//3.调用dao获取用户对应的密码
UserDao dao = new UserDao();
User user = dao.find(username);
String dbpassword = user.getPassword();
//4.检查用户带过来的md5的密码和数据库中的密码是否匹配,如匹配则自动登陆
if(password.equals(WebUtils.md5(dbpassword))){
request.getSession().setAttribute("user", user);
}
}
chain.doFilter(request, response);
}
public void destroy() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
复制代码
如果想取消自动登录,那么可以在用户注销时删除自动登录cookie,核心代码如下:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CancelAutoLoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//移除存储在session中的user
request.getSession().removeAttribute("user");
//移除自动登录的cookie
removeAutoLoginCookie(request,response);
//注销用户后跳转到登录页面
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
/**
* @Method: removeAutoLoginCookie
* @Description: 删除自动登录cookie,
* JavaWeb中删除cookie的方式就是新创建一个cookie,新创建的cookie与要删除的cookie同名,
* 设置新创建的cookie的cookie的有效期设置为0,有效路径与要删除的cookie的有效路径相同
* @Anthor:孤傲苍狼
*
* @param request
* @param response
*/
private void removeAutoLoginCookie(HttpServletRequest request, HttpServletResponse response) {
//创建一个名字为autologin的cookie
Cookie cookie = new Cookie("autologin","");
//将cookie的有效期设置为0,命令浏览器删除该cookie
cookie.setMaxAge(0);
//设置要删除的cookie的path
cookie.setPath(request.getContextPath());
response.addCookie(cookie);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
以上就是过滤器的几个常见应用场景。

(四十七)——监听器(Listener)在开发中的应用
监听器在JavaWeb开发中用得比较多,下面说一下监听器(Listener)在开发中的常见应用
一、统计当前在线人数
在JavaWeb应用开发中,有时候我们需要统计当前在线的用户数,此时就可以使用监听器技术来实现这个功能了。
复制代码
package me.gacl.web.listener;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @ClassName: OnLineCountListener
* @Description: 统计当前在线用户个数
* @author: 孤傲苍狼
* @date: 2014-9-10 下午10:01:26
*
*/
public class OnLineCountListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if(onLineCount==null){
context.setAttribute("onLineCount", 1);
}else{
onLineCount++;
context.setAttribute("onLineCount", onLineCount);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if(onLineCount==null){
context.setAttribute("onLineCount", 1);
}else{
onLineCount--;
context.setAttribute("onLineCount", onLineCount);
}
}
}
复制代码
二、自定义Session扫描器
当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的session销毁,那么此时也可以借助监听器技术来实现。
复制代码
package me.gacl.web.listener;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @ClassName: SessionScanerListener
* @Description: 自定义session扫描器
* @author: 孤傲苍狼
* @date: 2014-9-10 下午10:16:42
*
*/
public class SessionScanerListener implements HttpSessionListener,ServletContextListener {
/**
* @Field: list
* 定义一个集合存储服务器创建的HttpSession
* LinkedList不是一个线程安全的集合
*/
/**
* private List<HttpSession> list = new LinkedList<HttpSession>();
* 这样写涉及到线程安全问题,SessionScanerListener对象在内存中只有一个
* sessionCreated可能会被多个人同时调用,
* 当有多个人并发访问站点时,服务器同时为这些并发访问的人创建session
* 那么sessionCreated方法在某一时刻内会被几个线程同时调用,几个线程并发调用sessionCreated方法
* sessionCreated方法的内部处理是往一个集合中添加创建好的session,那么在加session的时候就会
* 涉及到几个Session同时抢夺集合中一个位置的情况,所以往集合中添加session时,一定要保证集合是线程安全的才行
* 如何把一个集合做成线程安全的集合呢?
* 可以使用使用 Collections.synchronizedList(List<T> list)方法将不是线程安全的list集合包装线程安全的list集合
*/
//使用 Collections.synchronizedList(List<T> list)方法将LinkedList包装成一个线程安全的集合
private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());
//定义一个对象,让这个对象充当一把锁,用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
private Object lock = new Object();
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("session被创建了!!");
HttpSession session = se.getSession();
synchronized (lock){
/**
*将该操作加锁进行锁定,当有一个thread-1(线程1)在调用这段代码时,会先拿到lock这把锁,然后往集合中添加session,
*在添加session的这个过程中假设有另外一个thread-2(线程2)来访问了,thread-2可能是执行定时器任务的,
*当thread-2要调用run方法遍历list集合中的session时,结果发现遍历list集合中的session的那段代码被锁住了,
*而这把锁正在被往集合中添加session的那个thread-1占用着,因此thread-2只能等待thread-1操作完成之后才能够进行操作
*当thread-1添加完session之后,就把lock放开了,此时thread-2拿到lock,就可以执行遍历list集合中的session的那段代码了
*通过这把锁就保证了往集合中添加session和变量集合中的session这两步操作不能同时进行,必须按照先来后到的顺序来进行。
*/
list.add(session);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session被销毁了了!!");
}
/* Web应用启动时触发这个事件
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("web应用初始化");
//创建定时器
Timer timer = new Timer();
//每隔30秒就定时执行任务
timer.schedule(new MyTask(list,lock), 0, 1000*30);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("web应用关闭");
}
}
/**
* @ClassName: MyTask
* @Description:定时器要定时执行的任务
* @author: 孤傲苍狼
* @date: 2014-9-11 上午12:02:36
*
*/
class MyTask extends TimerTask {
//存储HttpSession的list集合
private List<HttpSession> list;
//存储传递过来的锁
private Object lock;
public MyTask(List<HttpSession> list,Object lock){
this.list = list;
this.lock = lock;
}
/* run方法指明了任务要做的事情
* @see java.util.TimerTask#run()
*/
@Override
public void run() {
//将该操作加锁进行锁定
synchronized (lock) {
System.out.println("定时器执行!!");
ListIterator<HttpSession> it = list.listIterator();
/**
* 迭代list集合中的session,在迭代list集合中的session的过程中可能有别的用户来访问,
* 用户一访问,服务器就会为该用户创建一个session,此时就会调用sessionCreated往list集合中添加新的session,
* 然而定时器在定时执行扫描遍历list集合中的session时是无法知道正在遍历的list集合又添加的新的session进来了,
* 这样就导致了往list集合添加的新的session和遍历list集合中的session这两个操作无法达到同步
* 那么解决的办法就是把"list.add(session)和while(it.hasNext()){//迭代list集合}"这两段代码做成同步,
* 保证当有一个线程在访问"list.add(session)"这段代码时,另一个线程就不能访问"while(it.hasNext()){//迭代list集合}"这段代码
* 为了能够将这两段不相干的代码做成同步,只能定义一把锁(Object lock),然后给这两步操作加上同一把锁,
* 用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
* 当在执行往list集合添加的新的session操作时,就必须等添加完成之后才能够对list集合进行迭代操作,
* 当在执行对list集合进行迭代操作时,那么必须等到迭代操作结束之后才能够往往list集合添加的新的session
*/
while(it.hasNext()){
HttpSession session = (HttpSession) it.next();
/**
* 如果当前时间-session的最后访问时间>1000*15(15秒)
* session.getLastAccessedTime()获取session的最后访问时间
*/
if(System.currentTimeMillis()-session.getLastAccessedTime()>1000*30){
//手动销毁session
session.invalidate();
//移除集合中已经被销毁的session
it.remove();
}
}
}
}
}
复制代码
以上就是监听器的两个简单应用场景。

(四十八)——模拟Servlet3.0使用注解的方式配置Servlet
一、Servlet的传统配置方式
在JavaWeb开发中, 每次编写一个Servlet都需要在web.xml文件中进行配置,如下所示:
复制代码
<servlet>
<servlet-name>ActionServlet</servlet-name>
<servlet-class>me.gacl.web.controller.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ActionServlet</servlet-name>
<url-pattern>/servlet/ActionServlet</url-pattern>
</servlet-mapping>
复制代码
每开发一个Servlet,都要在web.xml中配置Servlet才能够使用,这实在是很头疼的事情,所以Servlet3.0之后提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署描述,简化开发流程。本文所讲的基于注解方式配置Servlet不是针对Servlet3.0的,而是基于Servlet2.5的,通过开发自定义注解和注解处理器来实现类似于Servlet3.0的注解方式配置Servlet。
二、基于注解的方式配置Servlet
JDK1. 5版本之后, JAVA提供了一种叫做Annotation的新数据类型,中文译为注解或标注,它的出现为铺天盖地的XML配置文件提供了一个完美的解决方案,让 JAVA EE开发更加方便快速,也更加干净了。不过Servlet2.5默认情况下是不支持注解方式的配置的,但是我们可以开发自定义注解,然后将注解标注到Servlet上,再针对我们自定义的注解写一个注解处理器,具体的做法如下:
2.1、开发用于配置Servlet的相关注解
1、开发WebServlet注解,用于标注处理请求的Servlet类
复制代码
package me.gacl.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义WebServlet注解,模拟Servlet3.0的WebServlet注解
* @Target 注解的属性值表明了 @WebServlet注解只能用于类或接口定义声明的前面,
* @WebServlet注解有一个必填的属性 value 。
* 调用方式为: @WebServlet(value="/xxxx") ,
* 因语法规定如果属性名为 value 且只填 value属性值时,可以省略 value属性名,即也可以写作:@WebServlet("/xxxx")
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {
//Servlet的访问URL
String value();
//Servlet的访问URL
String[] urlPatterns() default {""};
//Servlet的描述
String description() default "";
//Servlet的显示名称
String displayName() default "";
//Servlet的名称
String name() default "";
//Servlet的init参数
WebInitParam[] initParams() default {};
}
复制代码
将Servlet在web.xml中的配置信息使用WebServlet注解来表示,使用注解后,只需要在相应Servlet 类的前面使用类似@WebServlet("/servlet/LoginServlet") 注解就可以达到和上述 web.xml 文件中配置信息一样的目的。注解@WebServlet中的属性值"/servlet/LoginServlet"表示了web.xml 配置文件中 <servlet-mapping> 元素的子元素 <url-pattern> 里的值。通过这样的注解能简化在 XML 文件中配置 Servlet 信息,整个配置文件将会非常简洁干净,开发人员的工作也将大大减少。
2、开发WebInitParam注解,用于配置Servlet初始化时使用的参数
复制代码
package me.gacl.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName: WebInitParam
* @Description: 定义Servlet的初始化参数注解
* @author: 孤傲苍狼
* @date: 2014-10-1 下午3:25:53
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebInitParam {
//参数名
String paramName() default "";
//参数的值
String paramValue() default "";
}
复制代码
2.2、编写处理注解的处理器AnnotationHandleFilter
上面简要地介绍了注解的定义声明与使用方式,注解在后台需要一个处理器才能起作用,所以还得针对上面的注解编写处理器,在这里我们使用Filter作为注解的处理器,编写一个AnnotationHandleFilter,代码如下:
复制代码
package me.gacl.web.filter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.WebInitParam;
import me.gacl.annotation.WebServlet;
import me.gacl.util.ScanClassUtil;
/**
* @ClassName: AnnotationHandleFilter
* @Description: 使用Filter作为注解的处理器
* @author: 孤傲苍狼
* @date: 2014-11-12 下午10:15:19
*
*/
public class AnnotationHandleFilter implements Filter {
private ServletContext servletContext = null;
/* 过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("---AnnotationHandleFilter过滤器初始化开始---");
servletContext = filterConfig.getServletContext();
Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
//获取web.xml中配置的要扫描的包
String basePackage = filterConfig.getInitParameter("basePackage");
//如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
if (basePackage.indexOf(",")>0) {
//按逗号进行分隔
String[] packageNameArr = basePackage.split(",");
for (String packageName : packageNameArr) {
addServletClassToServletContext(packageName,classMap);
}
}else {
addServletClassToServletContext(basePackage,classMap);
}
System.out.println("----AnnotationHandleFilter过滤器初始化结束---");
}
/**
* @Method: addServletClassToServletContext
* @Description:添加ServletClass到ServletContext中
* @Anthor:孤傲苍狼
*
* @param packageName
* @param classMap
*/
private void addServletClassToServletContext(String packageName,Map<String, Class<?>> classMap){
Set<Class<?>> setClasses = ScanClassUtil.getClasses(packageName);
for (Class<?> clazz :setClasses) {
if (clazz.isAnnotationPresent(WebServlet.class)) {
//获取WebServlet这个Annotation的实例
WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
//获取Annotation的实例的value属性的值
String annotationAttrValue = annotationInstance.value();
if (!annotationAttrValue.equals("")) {
classMap.put(annotationAttrValue, clazz);
}
//获取Annotation的实例的urlPatterns属性的值
String[] urlPatterns = annotationInstance.urlPatterns();
for (String urlPattern : urlPatterns) {
classMap.put(urlPattern, clazz);
}
servletContext.setAttribute("servletClassMap", classMap);
System.out.println("annotationAttrValue:"+annotationAttrValue);
String targetClassName = annotationAttrValue.substring(annotationAttrValue.lastIndexOf("/")+1);
System.out.println("targetClassName:"+targetClassName);
System.out.println(clazz);
}
}
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("---进入注解处理过滤器---");
//将ServletRequest强制转换成HttpServletRequest
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
Map<String, Class<?>> classMap = (Map<String, Class<?>>) servletContext.getAttribute("servletClassMap");
//获取contextPath
String contextPath = req.getContextPath();
//获取用户请求的URI资源
String uri = req.getRequestURI();
//如果没有指明要调用Servlet类中的哪个方法
if (uri.indexOf("!")==-1) {
//获取用户使用的请求方式
String reqMethod = req.getMethod();
//获取要请求的servlet路径
String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("."));
//获取要使用的类
Class<?> clazz = classMap.get(requestServletName);
//创建类的实例
Object obj = null;
try {
obj = clazz.newInstance();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
Method targetMethod = null;
if (reqMethod.equalsIgnoreCase("get")) {
try {
targetMethod = clazz.getDeclaredMethod("doGet",HttpServletRequest.class,HttpServletResponse.class);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}else {
try {
targetMethod = clazz.getDeclaredMethod("doPost",HttpServletRequest.class,HttpServletResponse.class);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
try {
//调用对象的方法进行处理
targetMethod.invoke(obj,req,res);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}else {
//获取要请求的servlet路径
String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("!"));
//获取要调用的servlet的方法
String invokeMethodName = uri.substring(uri.lastIndexOf("!")+1,uri.lastIndexOf("."));
//获取要使用的类
Class<?> clazz = classMap.get(requestServletName);
//创建类的实例
Object obj = null;
try {
obj = clazz.newInstance();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
//获得clazz类定义的所有方法
Method[] methods = clazz.getDeclaredMethods();
//获取WebServlet这个Annotation的实例
WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
//获取注解上配置的初始化参数数组
WebInitParam[] initParamArr = annotationInstance.initParams();
Map<String, String> initParamMap = new HashMap<String, String>();
for (WebInitParam initParam : initParamArr) {
initParamMap.put(initParam.paramName(), initParam.paramValue());
}
//遍历clazz类中的方法
for (Method method : methods) {
//该方法的返回类型
Class<?> retType = method.getReturnType();
//获得方法名
String methodName = method.getName();
//打印方法修饰符
System.out.print(Modifier.toString(method.getModifiers()));
System.out.print(" "+retType.getName() + " " + methodName +"(");
//获得一个方法参数数组(getparameterTypes用于返回一个描述参数类型的Class对象数组)
Class<?>[] paramTypes = method.getParameterTypes();
for(int j = 0 ; j < paramTypes.length ; j++){
//如果有多个参数,中间则用逗号隔开,否则直接打印参数
if (j > 0){
System.out.print(",");
}
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
if (method.getName().equalsIgnoreCase("init")) {
try {
//调用Servlet的初始化方法
method.invoke(obj, initParamMap);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
//获取WebServlet这个Annotation的实例
System.out.println("invokeMethodName:"+invokeMethodName);
try {
try {
//利用反射获取方法实例,方法的签名必须符合:
//public void 方法名(HttpServletRequest request, HttpServletResponse response)
//例如:public void loginHandle(HttpServletRequest request, HttpServletResponse response)
Method targetMethod = clazz.getDeclaredMethod(invokeMethodName,HttpServletRequest.class,HttpServletResponse.class);
//调用对象的方法进行处理
targetMethod.invoke(obj,req,res);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
public void destroy() {
}
}
复制代码
AnnotationHandleFilter过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类,然后将类存储到一个Map集合中,再将Map集合存储到servletContext对象中。

在web.xml文件中配置AnnotationHandleFilter过滤器和需要扫描的包
复制代码
<filter>
<description>注解处理过滤器</description>
<filter-name>AnnotationHandleFilter</filter-name>
<filter-class>me.gacl.web.filter.AnnotationHandleFilter</filter-class>
<init-param>
<description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
<param-name>basePackage</param-name>
<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
<!-- <param-value>me.gacl.web.controller</param-value> -->
</init-param>
</filter>
<filter-mapping>
<filter-name>AnnotationHandleFilter</filter-name>
<!-- 拦截后缀是.do的请求 -->
<url-pattern>*.do</url-pattern>
</filter-mapping>
复制代码
工具类ScanClassUtil
AnnotationHandleFilter过滤器初始化方法init(FilterConfig filterConfig)使用到了一个用于扫描某个包下面的类的工具类ScanClassUtil,ScanClassUtil的代码如下:
复制代码
package me.gacl.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ScanClassUtil {
/**
* 从包package中获取所有的Class
*
* @param pack
* @return
*/
public static Set<Class<?>> getClasses(String pack) {
// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace(‘.‘, ‘/‘);
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath,
recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == ‘/‘) {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf(‘/‘);
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace(‘/‘, ‘.‘);
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
classes.add(Class
.forName(packageName + ‘.‘
+ className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
public static void findAndAddClassesInPackageByFile(String packageName,
String packagePath, final boolean recursive, Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory())
|| (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
//classes.add(Class.forName(packageName + ‘.‘ + className));
//经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + ‘.‘ + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
复制代码
经过以上两步,我们的自定义注解和针对注解的处理器都开发好了。
2.3、WebServlet注解简单测试LoginUIServlet
编写一个用于跳转到Login.jsp页面的LoginUIServlet,LoginUIServlet就是一个普通的java类,不是一个真正的Servlet,然后使用WebServlet注解标注LoginUIServlet类,代码如下:
复制代码
package me.gacl.web.UI;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.WebServlet;
@WebServlet("/servlet/LoginUI")
public class LoginUIServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
request.getRequestDispatcher("/Login.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
在浏览器中输入访问地址:http://gacl-pc:8080/AnnotationConfigServlet/servlet/Login.do (有误?),根据web.xml文件的配置,所有后缀名为 .do请求,都会经过AnnotationHandleFilter过滤器的doFilter方法,在doFilter方法的实现代码中,从HttpServletRequest请求对象中得到请求的方式类型(GET/POST)和请求的URI 。如有请求http://gacl-pc:8080/AnnotationConfigServlet/servlet/LoginUI.do,此时请求方法类型为GET, URI 值为/AnnotationConfigServlet/servlet/LoginUI.do。从ServletConext对象中获取到在过滤器中保存的Map结构,根据 URI 获得一个 Key=”/servlet/LoginUI” ,从 Map 结构中根据此Key得到Value ,此时Value就是要请求调用的那个Servlet类,根据Servlet类创建对象实例,再根据前面得到的请求方法类型,能决定调用此Servlet对象实例的 doGet 或 doPost 方法。最终客户端发生的后缀为.do请求,经由AnnotationHandleFilter对请求对象(HttpServletRequest)的分析,从而调用相应某Servlet的doGet或doPost方法,完成了一次客户端请求到服务器响应的过程。
使用注解后程序流程如下所示:

运行结果如下:

从运行结果中可以看到,我们的注解处理器成功调用了目标Servlet处理用户的请求,通过@WebServlet注解, Servlet不用再在web.xml 文件中进行繁冗的注册,这就是使用@WebServlet注解的方便之处。
2.3、WebServlet注解复杂测试LoginServlet
编写Login.jsp页面,代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<fieldset>
<legend>用户登录</legend>
<form action="${pageContext.request.contextPath}/servlet/LoginServlet!loginHandle.do" method="post">
用户名:<input type="text" value="${param.usename}" name="usename">
<br/>
密码:<input type="text" value="${param.pwd}" name="pwd">
<br/>
<input type="submit" value="登录"/>
</form>
</fieldset>
<hr/>
<label style="color: red;">${msg}</label>
</body>
</html>
复制代码
form表单中的action属性的URL="${pageContext.request.contextPath}/servlet/LoginServlet!loginHandle.do",/servlet/LoginServlet表示要访问的是LoginServlet,!后面的loginHandle表示要调用LoginServlet中的loginHandle方法处理此次的请求,也就是说,我们在访问Servlet时,可以在URL中指明要访问Servlet的哪个方法,AnnotationHandleFilter过滤器的doFilter方法在拦截到用户的请求之后,首先获取用户要访问的URI,根据URI判断用户要访问的Servlet,然后再判断URI中是否包含了"!",如果有,那么就说明用户显示指明了要访问Servlet的哪个方法,遍历Servlet类中定义的所有方法,如果找到了URI中的那个方法,那么就调用对应的方法处理用户请求!
LoginServlet的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.WebInitParam;
import me.gacl.annotation.WebServlet;
/**
*
* @ClassName: LoginServlet
* @Description:处理用户登录的Servlet,
* LoginServlet现在就是一个普通的java类,不是一个真正的Servlet
* @author: 孤傲苍狼
* @date: 2014-10-8 上午12:07:58
*
*/
//将开发好的WebServlet注解标注到LoginServlet类上
@WebServlet(
//Servlet的访问URL
value="/servlet/LoginServlet",
//Servlet的访问URL,可以使用数组的方式配置多个访问路径
urlPatterns={"/gacl/LoginServlet","/xdp/LoginServlet"},
//Servlet的初始化参数
initParams={
@WebInitParam(paramName="gacl",paramValue="孤傲苍狼"),
@WebInitParam(paramName="bhsh",paramValue="白虎神皇")
},
name="LoginServlet",
description="处理用户登录的Servlet"
)
public class LoginServlet {
public void loginHandle(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
String username = request.getParameter("usename");
String pwd = request.getParameter("pwd");
if (username.equals("gacl") && pwd.equals("xdp")) {
request.getSession().setAttribute("usename", username);
request.setAttribute("msg", "欢迎您!"+username);
request.getRequestDispatcher("/index.jsp").forward(request, response);
}else {
request.setAttribute("msg", "登录失败,请检查用户名和密码是否正确!");
request.getRequestDispatcher("/Login.jsp").forward(request, response);
}
}
/**
* @Method: init
* @Description: Servlet初始化
* @Anthor:孤傲苍狼
*
* @param config
*/
public void init(Map<String, String> initParamMap){
System.out.println("--LoginServlet初始化--");
System.out.println(initParamMap.get("gacl"));
System.out.println(initParamMap.get("bhsh"));
}
}
复制代码
运行结果如下:

可以看到,我们使用注解方式配置的Servlet已经成功调用了,loginHandle方法处理用户登录请求的完整处理过程如下图所示:

Servlet3.0是支持采用基于注解的方式配置Servlet的,在此我使用过滤器作为注解处理器模拟模拟出了类似Servlet3.0的注解处理方式,简化了Servlet的配置。这种使用自定义注解+注解处理器的方式山寨出来的Servlet3.0大家了解一下即可,了解一下这种处理思路,在实际应用中还是不要这么做了,要真想使用注解的方式配置Servlet还是直接用Servlet3.0吧。

(四十九)——简单模拟Sping MVC
在Spring MVC中,将一个普通的java类标注上Controller注解之后,再将类中的方法使用RequestMapping注解标注,那么这个普通的java类就够处理Web请求,示例代码如下:
复制代码
/**
* 使用Controller注解标注LoginUI类
*/
@Controller
public class LoginUI {
//使用RequestMapping注解指明forward1方法的访问路径
@RequestMapping("LoginUI/Login2")
public View forward1(){
//执行完forward1方法之后返回的视图
return new View("/login2.jsp");
}
//使用RequestMapping注解指明forward2方法的访问路径
@RequestMapping("LoginUI/Login3")
public View forward2(){
//执行完forward2方法之后返回的视图
return new View("/login3.jsp");
}
}
复制代码
spring通过java annotation就可以注释一个类为action ,在方法上添加上一个java annotation 就可以配置请求的路径了,那么这种机制是如何实现的呢,今天我们使用"自定义注解+Servlet"来简单模拟一下Spring MVC中的这种注解配置方式。
一、编写注解
1.1、Controller注解
开发Controller注解,这个注解只有一个value属性,默认值为空字符串,代码如下:
复制代码
package me.gacl.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName: Controller
* @Description: 自定义Controller注解
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:16:40
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
public String value() default "";
}
复制代码
1.2、RequestMapping注解
开发RequestMapping注解,用于定义请求路径,这个注解只有一个value属性,默认值为空字符串,代码如下:
复制代码
package me.gacl.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义请求路径的java annotation
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
public String value() default "";
}
复制代码
以上就是我们自定义的两个注解,注解的开发工作就算是完成了,有了注解之后,那么就必须针对注解来编写处理器,否则我们开发的注解配置到类或者方法上面是不起作用的,这里我们使用Servlet来作为注解的处理器。
二、编写核心的注解处理器
2.1、开发AnnotationHandleServlet
这里使用一个Servlet来作为注解处理器,编写一个AnnotationHandleServlet,代码如下:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.Controller;
import me.gacl.annotation.RequestMapping;
import me.gacl.util.BeanUtils;
import me.gacl.util.RequestMapingMap;
import me.gacl.util.ScanClassUtil;
import me.gacl.web.context.WebContext;
import me.gacl.web.view.DispatchActionConstant;
import me.gacl.web.view.View;
/**
* <p>ClassName: AnnotationHandleServlet<p>
* <p>Description: AnnotationHandleServlet作为自定义注解的核心处理器以及负责调用目标业务方法处理用户请求<p>
* @author xudp
* @version 1.0 V
*/
public class AnnotationHandleServlet extends HttpServlet {
private String pareRequestURI(HttpServletRequest request){
String path = request.getContextPath()+"/";
String requestUri = request.getRequestURI();
String midUrl = requestUri.replaceFirst(path, "");
String lasturl = midUrl.substring(0, midUrl.lastIndexOf("."));
return lasturl;
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.excute(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.excute(request, response);
}
private void excute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//将当前线程中HttpServletRequest对象存储到ThreadLocal中,以便在Controller类中使用
WebContext.requestHodler.set(request);
//将当前线程中HttpServletResponse对象存储到ThreadLocal中,以便在Controller类中使用
WebContext.responseHodler.set(response);
//解析url
String lasturl = pareRequestURI(request);
//获取要使用的类
Class<?> clazz = RequestMapingMap.getRequesetMap().get(lasturl);
//创建类的实例
Object classInstance = BeanUtils.instanceClass(clazz);
//获取类中定义的方法
Method [] methods = BeanUtils.findDeclaredMethods(clazz);
Method method = null;
for(Method m:methods){//循环方法,找匹配的方法进行执行
if(m.isAnnotationPresent(RequestMapping.class)){
String anoPath = m.getAnnotation(RequestMapping.class).value();
if(anoPath!=null && !"".equals(anoPath.trim()) && lasturl.equals(anoPath.trim())){
//找到要执行的目标方法
method = m;
break;
}
}
}
try {
if(method!=null){
//执行目标方法处理用户请求
Object retObject = method.invoke(classInstance);
//如果方法有返回值,那么就表示用户需要返回视图
if (retObject!=null) {
View view = (View)retObject;
//判断要使用的跳转方式
if(view.getDispathAction().equals(DispatchActionConstant.FORWARD)){
//使用服务器端跳转方式
request.getRequestDispatcher(view.getUrl()).forward(request, response);
}else if(view.getDispathAction().equals(DispatchActionConstant.REDIRECT)){
//使用客户端跳转方式
response.sendRedirect(request.getContextPath()+view.getUrl());
}else{
request.getRequestDispatcher(view.getUrl()).forward(request, response);
}
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public void init(ServletConfig config) throws ServletException {
/**
* 重写了Servlet的init方法后一定要记得调用父类的init方法,
* 否则在service/doGet/doPost方法中使用getServletContext()方法获取ServletContext对象时
* 就会出现java.lang.NullPointerException异常
*/
super.init(config);
System.out.println("---初始化开始---");
//获取web.xml中配置的要扫描的包
String basePackage = config.getInitParameter("basePackage");
//如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
if (basePackage.indexOf(",")>0) {
//按逗号进行分隔
String[] packageNameArr = basePackage.split(",");
for (String packageName : packageNameArr) {
initRequestMapingMap(packageName);
}
}else {
initRequestMapingMap(basePackage);
}
System.out.println("----初始化结束---");
}
/**
* @Method: initRequestMapingMap
* @Description:添加使用了Controller注解的Class到RequestMapingMap中
* @Anthor:孤傲苍狼
* @param packageName
*/
private void initRequestMapingMap(String packageName){
Set<Class<?>> setClasses = ScanClassUtil.getClasses(packageName);
for (Class<?> clazz :setClasses) {
if (clazz.isAnnotationPresent(Controller.class)) {
Method [] methods = BeanUtils.findDeclaredMethods(clazz);
for(Method m:methods){//循环方法,找匹配的方法进行执行
if(m.isAnnotationPresent(RequestMapping.class)){
String anoPath = m.getAnnotation(RequestMapping.class).value();
if(anoPath!=null && !"".equals(anoPath.trim())){
if (RequestMapingMap.getRequesetMap().containsKey(anoPath)) {
throw new RuntimeException("RequestMapping映射的地址不允许重复!");
}
RequestMapingMap.put(anoPath, clazz);
}
}
}
}
}
}
}
复制代码
这里说一下AnnotationHandleServlet的实现思路
1、AnnotationHandleServlet初始化(init)时扫描指定的包下面使用了Controller注解的类,如下图所示:

2、遍历类中的方法,找到类中使用了RequestMapping注解标注的那些方法,获取RequestMapping注解的value属性值,value属性值指明了该方法的访问路径,以RequestMapping注解的value属性值作为key,Class类作为value将存储到一个静态Map集合中。如下图所示:

当用户请求时(无论是get还是post请求),会调用封装好的execute方法 ,execute会先获取请求的url,然后解析该URL,根据解析好的URL从Map集合中取出要调用的目标类 ,再遍历目标类中定义的所有方法,找到类中使用了RequestMapping注解的那些方法,判断方法上面的RequestMapping注解的value属性值是否和解析出来的URL路径一致,如果一致,说明了这个就是要调用的目标方法,此时就可以利用java反射机制先实例化目标类对象,然后再通过实例化对象调用要执行的方法处理用户请求。服务器将以下图的方式与客户端进行交互

另外,方法处理完成之后需要给客户端发送响应信息,比如告诉客户端要跳转到哪一个页面,采用的是服务器端跳转还是客户端方式跳转,或者发送一些数据到客户端显示,那么该如何发送响应信息给客户端呢,在此,我们可以设计一个View(视图)类,对这些操作属性进行封装,其中包括跳转的路径 、展现到页面的数据、跳转方式。这就是AnnotationHandleServlet的实现思路。
2.2、在Web.xml文件中注册AnnotationHandleServlet
在web.xml文件中配置AnnotationHandleServlet和需要扫描的包
复制代码
<servlet>
<servlet-name>AnnotationHandleServlet</servlet-name>
<servlet-class>me.gacl.web.controller.AnnotationHandleServlet</servlet-class>
<init-param>
<description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
<param-name>basePackage</param-name>
<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
<!-- <param-value>me.gacl.web.controller</param-value> -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AnnotationHandleServlet</servlet-name>
<!-- 拦截所有以.do后缀结尾的请求 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
复制代码
三、相关代码讲解
3.1、BeanUtils
BeanUtils工具类主要是用来处理一些反射的操作
复制代码
package me.gacl.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 对java反射中操作的一些封装
*/
public class BeanUtils {
/**
* 实例化一个class
* @param <T>
* @param clazz Person.class
* @return
*/
public static <T> T instanceClass(Class<T> clazz){
if(!clazz.isInterface()){
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 通过构造函数实例化
* @param <T>
* @param ctor
* @param args
* @return
* @throws IllegalArgumentException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public static <T> T instanceClass(Constructor<T> ctor, Object... args)
throws IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException{
makeAccessible(ctor);
return ctor.newInstance(args);//调用构造方法实例化
}
/**
* 查找某个class的方法
* @param clazz
* @param methodName
* @param paramTypes
* @return
* @throws SecurityException
* @throws NoSuchMethodException
*/
public static Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes){
try {
return clazz.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
return findDeclaredMethod(clazz, methodName, paramTypes);//返回共有的方法
}
}
public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes){
try {
return clazz.getDeclaredMethod(methodName, paramTypes);
}
catch (NoSuchMethodException ex) {
if (clazz.getSuperclass() != null) {
return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
}
return null;
}
}
public static Method [] findDeclaredMethods(Class<?> clazz){
return clazz.getDeclaredMethods();
}
public static void makeAccessible(Constructor<?> ctor) {
if ((!Modifier.isPublic(ctor.getModifiers())
|| !Modifier.isPublic(ctor.getDeclaringClass().getModifiers()))
&& !ctor.isAccessible()) {
ctor.setAccessible(true);//如果是私有的 设置为true 使其可以访问
}
}
public static Field[] findDeclaredFields(Class<?> clazz){
return clazz.getDeclaredFields();
}
}
复制代码
3.2、RequestMapingMap
该类是用于存储方法的访问路径,AnnotationHandleServlet初始化时会将类(使用Controller注解标注的那些类)中使用了RequestMapping注解标注的那些方法的访问路径存储到RequestMapingMap中。
复制代码
package me.gacl.util;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: RequestMapingMap
* @Description: 存储方法的访问路径
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:31:43
*
*/
public class RequestMapingMap {
/**
* @Field: requesetMap
* 用于存储方法的访问路径
*/
private static Map<String, Class<?>> requesetMap = new HashMap<String, Class<?>>();
public static Class<?> getClassName(String path) {
return requesetMap.get(path);
}
public static void put(String path, Class<?> className) {
requesetMap.put(path, className);
}
public static Map<String, Class<?>> getRequesetMap() {
return requesetMap;
}
}
复制代码
3.3、ScanClassUtil
扫描某个包下面的类的工具类
复制代码
package me.gacl.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @ClassName: ScanClassUtil
* @Description: 扫描指定包或者jar包下面的class
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:34:10
*
*/
public class ScanClassUtil {
/**
* 从包package中获取所有的Class
*
* @param pack
* @return
*/
public static Set<Class<?>> getClasses(String pack) {
// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace(‘.‘, ‘/‘);
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath,
recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == ‘/‘) {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf(‘/‘);
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace(‘/‘, ‘.‘);
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
classes.add(Class
.forName(packageName + ‘.‘
+ className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
public static void findAndAddClassesInPackageByFile(String packageName,
String packagePath, final boolean recursive, Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory())
|| (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
//classes.add(Class.forName(packageName + ‘.‘ + className));
//经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + ‘.‘ + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
复制代码
3.4、WebContext
WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse,当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取,通过WebContext.java这个类 ,我们可以在作为Controller的普通java类中获取当前请求的request、response或者session相关请求类的实例变量,并且线程间互不干扰的,因为用到了ThreadLocal这个类。
复制代码
package me.gacl.web.context;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse
* 当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取
**/
public class WebContext {
public static ThreadLocal<HttpServletRequest> requestHodler = new ThreadLocal<HttpServletRequest>();
public static ThreadLocal<HttpServletResponse> responseHodler = new ThreadLocal<HttpServletResponse>();
public HttpServletRequest getRequest(){
return requestHodler.get();
}
public HttpSession getSession(){
return requestHodler.get().getSession();
}
public ServletContext getServletContext(){
return requestHodler.get().getSession().getServletContext();
}
public HttpServletResponse getResponse(){
return responseHodler.get();
}
}
复制代码
3.5、View
一个视图类,对一些客户端响应操作进行封装,其中包括跳转的路径 、展现到页面的数据、跳转方式
复制代码
package me.gacl.web.view;
/**
* 视图模型
**/
public class View {
private String url;//跳转路径
private String dispathAction = DispatchActionConstant.FORWARD;//跳转方式
public View(String url) {
this.url = url;
}
public View(String url,String name,Object value) {
this.url = url;
ViewData view = new ViewData();
view.put(name, value);
}
public View(String url,String name,String dispathAction ,Object value) {
this.dispathAction = dispathAction;
this.url = url;
ViewData view = new ViewData();//请看后面的代码
view.put(name, value);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDispathAction() {
return dispathAction;
}
public void setDispathAction(String dispathAction) {
this.dispathAction = dispathAction;
}
}
复制代码
3.6、ViewData
request范围的数据存储类,当需要发送数据到客户端显示时,就可以将要显示的数据存储到ViewData类中。使用ViewData.put(String name,Object value)方法往request对象中存数据。
复制代码
package me.gacl.web.view;
import javax.servlet.http.HttpServletRequest;
import me.gacl.web.context.WebContext;
/**
* 需要发送到客户端显示的数据模型
*/
public class ViewData {
private HttpServletRequest request;
public ViewData() {
initRequest();
}
private void initRequest(){
//从requestHodler中获取request对象
this.request = WebContext.requestHodler.get();
}
public void put(String name,Object value){
this.request.setAttribute(name, value);
}
}
复制代码
3.7、DispatchActionConstant
一个跳转方式的常量类
复制代码
package me.gacl.web.view;
/**
* 跳转常量
*/
public class DispatchActionConstant {
public static String FORWARD = "forward";//服务器跳转
public static String REDIRECT = "redirect";//客户端跳转
}
复制代码
四、Controller注解和RequestMapping注解测试
4.1、简单测试
编写一个LoginUI类,用于跳转到具体的jsp页面,代码如下:
复制代码
package me.gacl.web.UI;
import me.gacl.annotation.Controller;
import me.gacl.annotation.RequestMapping;
import me.gacl.web.view.View;
/**
* 使用Controller注解标注LoginUI类
*/
@Controller
public class LoginUI {
//使用RequestMapping注解指明forward1方法的访问路径
@RequestMapping("LoginUI/Login2")
public View forward1(){
//执行完forward1方法之后返回的视图
return new View("/login2.jsp");
}
//使用RequestMapping注解指明forward2方法的访问路径
@RequestMapping("LoginUI/Login3")
public View forward2(){
//执行完forward2方法之后返回的视图
return new View("/login3.jsp");
}
}
复制代码
运行结果如下所示:

4.2、复杂测试
编写用于处理用户登录请求的Controller,代码如下:
复制代码
package me.gacl.web.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.Controller;
import me.gacl.annotation.RequestMapping;
import me.gacl.web.context.WebContext;
import me.gacl.web.view.View;
import me.gacl.web.view.ViewData;
/**
*
* @ClassName: LoginServlet2
* @Description:处理用户登录的Servlet,
* LoginServlet现在就是一个普通的java类,不是一个真正的Servlet
* @author: 孤傲苍狼
* @date: 2014-10-8 上午12:07:58
*
*/
@Controller //使用Controller注解标注LoginServlet2
public class LoginServlet2 {
/**
* @Method: loginHandle
* @Description:处理以普通方式提交的请求
* @Anthor:孤傲苍狼
*
* @return View
*/
//使用RequestMapping注解标注loginHandle方法,指明loginHandle方法的访问路径是login/handle
@RequestMapping("login/handle")
public View loginHandle(){
//创建一个ViewData对象,用于存储需要发送到客户端的响应数据
ViewData viewData = new ViewData();
//通过WebContext类获取当前线程中的HttpServletRequest对象
HttpServletRequest request = WebContext.requestHodler.get();
//接收提交上来的参数
String username =request.getParameter("usename");
String pwd = request.getParameter("pwd");
if (username.equals("gacl") && pwd.equals("xdp")) {
request.getSession().setAttribute("usename", username);
//将响应数据存储到ViewData对象中
viewData.put("msg", "欢迎您!"+username);
//返回一个View对象,指明要跳转的视图的路径
return new View("/index.jsp");
}else {
//将响应数据存储到ViewData对象中
viewData.put("msg", "登录失败,请检查用户名和密码是否正确!");
//返回一个View对象,指明要跳转的视图的路径
return new View("/login2.jsp");
}
}
/**
* @Method: ajaxLoginHandle
* @Description: 处理以AJAX方式提交的请求
* @Anthor:孤傲苍狼
*
* @throws IOException
*/
//使用RequestMapping注解标注ajaxLoginHandle方法,指明ajaxLoginHandle方法的访问路径是ajaxLogin/handle
@RequestMapping("ajaxLogin/handle")
public void ajaxLoginHandle() throws IOException{
//通过WebContext类获取当前线程中的HttpServletRequest对象
HttpServletRequest request = WebContext.requestHodler.get();
//接收提交上来的参数
String username =request.getParameter("usename");
String pwd = request.getParameter("pwd");
//通过WebContext类获取当前线程中的HttpServletResponse对象
HttpServletResponse response = WebContext.responseHodler.get();
if (username.equals("gacl") && pwd.equals("xdp")) {
request.getSession().setAttribute("usename", username);
response.getWriter().write("success");
}else {
response.getWriter().write("fail");
}
}
}
复制代码
编写用于测试的jsp页面,代码如下所示:
Login2.jsp登录页面
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>login2.jsp登录页面</title>
</head>
<body>
<fieldset>
<legend>用户登录</legend>
<form action="${pageContext.request.contextPath}/login/handle.do" method="post">
用户名:<input type="text" value="${param.usename}" name="usename">
<br/>
密码:<input type="text" value="${param.pwd}" name="pwd">
<br/>
<input type="submit" value="登录"/>
</form>
</fieldset>
<hr/>
<label style="color: red;">${msg}</label>
</body>
</html>
复制代码
login3.jsp登录页面
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>login3登录页面</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/ajaxUtil.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/Utils.js"></script>
<script type="text/javascript">
function login(){
Ajax.request({
url : "${pageContext.request.contextPath}/ajaxLogin/handle.do",
data : {
"usename" : document.getElementById("usename").value,
"pwd" : document.getElementById("pwd").value
},
success : function(xhr) {
onData(xhr.responseText);
},
error : function(xhr) {
}
});
}
function onData(responseText){
if(responseText=="success"){
//window.location.href="index.jsp";//改变url地址
/*
window.location.replace("url"):将地址替换成新url,
该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,
你不能通过“前进”和“后 退”来访问已经被替换的URL,这个特点对于做一些过渡页面非常有用!
*/
location.replace(g_basePath+"/index.jsp");
}else{
alert("用户名和密码错误");
}
}
</script>
</head>
<body>
<fieldset>
<legend>用户登录</legend>
<form>
用户名:<input type="text" name="usename" id="usename">
<br/>
密码:<input type="text" name="pwd" id="pwd">
<br/>
<input type="button" value="登录" onclick="login()"/>
</form>
</fieldset>
</body>
</html>
复制代码
index.jsp页面代码如下:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>首页</title>
</head>
<body>
登录的用户名:${usename}
<br/>
${msg}
</body>
</html>
复制代码
jsp页面中使用到的Utils.js代码如下:
复制代码
//立即执行的js
(function() {
//获取contextPath
var contextPath = getContextPath();
//获取basePath
var basePath = getBasePath();
//将获取到contextPath和basePath分别赋值给window对象的g_contextPath属性和g_basePath属性
window.g_contextPath = contextPath;
window.g_basePath = basePath;
})();
/**
* @author 孤傲苍狼
* 获得项目根路径,等价于jsp页面中
* <%
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
* 使用方法:getBasePath();
* @returns 项目的根路径
*
*/
function getBasePath() {
var curWwwPath = window.document.location.href;
var pathName = window.document.location.pathname;
var pos = curWwwPath.indexOf(pathName);
var localhostPath = curWwwPath.substring(0, pos);
var projectName = pathName.substring(0, pathName.substr(1).indexOf(‘/‘) + 1);
return (localhostPath + projectName);
}
/**
* @author 孤傲苍狼
* 获取Web应用的contextPath,等价于jsp页面中
* <%
String path = request.getContextPath();
%>
* 使用方法:getContextPath();
* @returns /项目名称(/EasyUIStudy_20141104)
*/
function getContextPath() {
return window.document.location.pathname.substring(0, window.document.location.pathname.indexOf(‘\/‘, 1));
};
复制代码
测试结果如下:


以上就是对Spring MVC的简单模拟。

(五十)——文件上传和下载
在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现。
对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般选择采用apache的开源工具common-fileupload这个文件上传组件。这个common-fileupload上传组件的jar包可以去apache官网上面下载,也可以在struts的lib文件夹下面找到,struts上传的功能就是基于这个实现的。common-fileupload是依赖于common-io这个包的,所以还需要下载这个包。
一、开发环境搭建
创建一个FileUploadAndDownLoad项目,加入Apache的commons-fileupload文件上传组件的相关Jar包,如下图所示:

二、实现文件上传
2.1、文件上传页面和消息提示页面
upload.jsp页面的代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/servlet/UploadHandleServlet" enctype="multipart/form-data" method="post">
上传用户:<input type="text" name="username"><br/>
上传文件1:<input type="file" name="file1"><br/>
上传文件2:<input type="file" name="file2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
复制代码
message.jsp的代码如下:
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>消息提示</title>
</head>
<body>
${message}
</body>
</html>
复制代码
2.2、处理文件上传的Servlet
UploadHandleServlet的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class UploadHandleServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
File file = new File(savePath);
//判断上传文件的保存目录是否存在
if (!file.exists() && !file.isDirectory()) {
System.out.println(savePath+"目录不存在,需要创建");
//创建目录
file.mkdir();
}
//消息提示
String message = "";
try{
//使用Apache文件上传组件处理文件上传步骤:
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//2、创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//解决上传文件名的中文乱码
upload.setHeaderEncoding("UTF-8");
//3、判断提交上来的数据是否是上传表单的数据
if(!ServletFileUpload.isMultipartContent(request)){
//按照传统方式获取数据
return;
}
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
List<FileItem> list = upload.parseRequest(request);
for(FileItem item : list){
//如果fileitem中封装的是普通输入项的数据
if(item.isFormField()){
String name = item.getFieldName();
//解决普通输入项的数据的中文乱码问题
String value = item.getString("UTF-8");
//value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
}else{//如果fileitem中封装的是上传文件
//得到上传的文件名称,
String filename = item.getName();
System.out.println(filename);
if(filename==null || filename.trim().equals("")){
continue;
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
filename = filename.substring(filename.lastIndexOf("\\")+1);
//获取item中的上传文件的输入流
InputStream in = item.getInputStream();
//创建一个文件输出流
FileOutputStream out = new FileOutputStream(savePath + "\\" + filename);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((len=in.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
out.write(buffer, 0, len);
}
//关闭输入流
in.close();
//关闭输出流
out.close();
//删除处理文件上传时生成的临时文件
item.delete();
message = "文件上传成功!";
}
}
}catch (Exception e) {
message= "文件上传失败!";
e.printStackTrace();
}
request.setAttribute("message",message);
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
在Web.xml文件中注册UploadHandleServlet
复制代码
<servlet>
<servlet-name>UploadHandleServlet</servlet-name>
<servlet-class>me.gacl.web.controller.UploadHandleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadHandleServlet</servlet-name>
<url-pattern>/servlet/UploadHandleServlet</url-pattern>
</servlet-mapping>
复制代码
运行效果如下:

文件上传成功之后,上传的文件保存在了WEB-INF目录下的upload目录,如下图所示:

2.3、文件上传的细节
上述的代码虽然可以成功将文件上传到服务器上面的指定目录当中,但是文件上传功能有许多需要注意的小细节问题,以下列出的几点需要特别注意的
1、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
2、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
3、为防止一个目录下面出现太多文件,要使用hash算法打散存储。
4、要限制上传文件的最大值。
5、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
针对上述提出的5点细节问题,我们来改进一下UploadHandleServlet,改进后的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
/**
* @ClassName: UploadHandleServlet
* @Description: TODO(这里用一句话描述这个类的作用)
* @author: 孤傲苍狼
* @date: 2015-1-3 下午11:35:50
*
*/
public class UploadHandleServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
//上传时生成的临时文件保存目录
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
File tmpFile = new File(tempPath);
if (!tmpFile.exists()) {
//创建临时目录
tmpFile.mkdir();
}
//消息提示
String message = "";
try{
//使用Apache文件上传组件处理文件上传步骤:
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。
factory.setSizeThreshold(1024*100);//设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB
//设置上传时生成的临时文件的保存目录
factory.setRepository(tmpFile);
//2、创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//监听文件上传进度
upload.setProgressListener(new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int arg2) {
System.out.println("文件大小为:" + pContentLength + ",当前已处理:" + pBytesRead);
/**
* 文件大小为:14608,当前已处理:4096
文件大小为:14608,当前已处理:7367
文件大小为:14608,当前已处理:11419
文件大小为:14608,当前已处理:14608
*/
}
});
//解决上传文件名的中文乱码
upload.setHeaderEncoding("UTF-8");
//3、判断提交上来的数据是否是上传表单的数据
if(!ServletFileUpload.isMultipartContent(request)){
//按照传统方式获取数据
return;
}
//设置上传单个文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB
upload.setFileSizeMax(1024*1024);
//设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10MB
upload.setSizeMax(1024*1024*10);
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
List<FileItem> list = upload.parseRequest(request);
for(FileItem item : list){
//如果fileitem中封装的是普通输入项的数据
if(item.isFormField()){
String name = item.getFieldName();
//解决普通输入项的数据的中文乱码问题
String value = item.getString("UTF-8");
//value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
}else{//如果fileitem中封装的是上传文件
//得到上传的文件名称,
String filename = item.getName();
System.out.println(filename);
if(filename==null || filename.trim().equals("")){
continue;
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
filename = filename.substring(filename.lastIndexOf("\\")+1);
//得到上传文件的扩展名
String fileExtName = filename.substring(filename.lastIndexOf(".")+1);
//如果需要限制上传的文件类型,那么可以通过文件的扩展名来判断上传的文件类型是否合法
System.out.println("上传的文件的扩展名是:"+fileExtName);
//获取item中的上传文件的输入流
InputStream in = item.getInputStream();
//得到文件保存的名称
String saveFilename = makeFileName(filename);
//得到文件的保存目录
String realSavePath = makePath(saveFilename, savePath);
//创建一个文件输出流
FileOutputStream out = new FileOutputStream(realSavePath + "\\" + saveFilename);
//创建一个缓冲区
byte buffer[] = new byte[1024];
//判断输入流中的数据是否已经读完的标识
int len = 0;
//循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据
while((len=in.read(buffer))>0){
//使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中
out.write(buffer, 0, len);
}
//关闭输入流
in.close();
//关闭输出流
out.close();
//删除处理文件上传时生成的临时文件
//item.delete();
message = "文件上传成功!";
}
}
}catch (FileUploadBase.FileSizeLimitExceededException e) {
e.printStackTrace();
request.setAttribute("message", "单个文件超出最大值!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}catch (FileUploadBase.SizeLimitExceededException e) {
e.printStackTrace();
request.setAttribute("message", "上传文件的总的大小超出限制的最大值!!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}catch (Exception e) {
message= "文件上传失败!";
e.printStackTrace();
}
request.setAttribute("message",message);
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
/**
* @Method: makeFileName
* @Description: 生成上传文件的文件名,文件名以:uuid+"_"+文件的原始名称
* @Anthor:孤傲苍狼
* @param filename 文件的原始名称
* @return uuid+"_"+文件的原始名称
*/
private String makeFileName(String filename){ //2.jpg
//为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
return UUID.randomUUID().toString() + "_" + filename;
}
/**
* 为防止一个目录下面出现太多文件,要使用hash算法打散存储
* @Method: makePath
* @Description:
* @Anthor:孤傲苍狼
*
* @param filename 文件名,要根据文件名生成存储目录
* @param savePath 文件存储路径
* @return 新的存储目录
*/
private String makePath(String filename,String savePath){
//得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址
int hashcode = filename.hashCode();
int dir1 = hashcode&0xf; //0--15
int dir2 = (hashcode&0xf0)>>4; //0-15
//构造新的保存目录
String dir = savePath + "\\" + dir1 + "\\" + dir2; //upload\2\3 upload\3\5
//File既可以代表文件也可以代表目录
File file = new File(dir);
//如果目录不存在
if(!file.exists()){
//创建目录
file.mkdirs();
}
return dir;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
针对上述提出的5点小细节问题进行改进之后,我们的文件上传功能就算是做得比较完善了。
三、文件下载
3.1、列出提供下载的文件资源
我们要将Web应用系统中的文件资源提供给用户进行下载,首先我们要有一个页面列出上传文件目录下的所有文件,当用户点击文件下载超链接时就进行下载操作,编写一个ListFileServlet,用于列出Web应用系统中所有下载文件。
ListFileServlet的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: ListFileServlet
* @Description: 列出Web系统中所有下载文件
* @author: 孤傲苍狼
* @date: 2015-1-4 下午9:54:40
*
*/
public class ListFileServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取上传文件的目录
String uploadFilePath = this.getServletContext().getRealPath("/WEB-INF/upload");
//存储要下载的文件名
Map<String,String> fileNameMap = new HashMap<String,String>();
//递归遍历filepath目录下的所有文件和目录,将文件的文件名存储到map集合中
listfile(new File(uploadFilePath),fileNameMap);//File既可以代表一个文件也可以代表一个目录
//将Map集合发送到listfile.jsp页面进行显示
request.setAttribute("fileNameMap", fileNameMap);
request.getRequestDispatcher("/listfile.jsp").forward(request, response);
}
/**
* @Method: listfile
* @Description: 递归遍历指定目录下的所有文件
* @Anthor:孤傲苍狼
* @param file 即代表一个文件,也代表一个文件目录
* @param map 存储文件名的Map集合
*/
public void listfile(File file,Map<String,String> map){
//如果file代表的不是一个文件,而是一个目录
if(!file.isFile()){
//列出该目录下的所有文件和目录
File files[] = file.listFiles();
//遍历files[]数组
for(File f : files){
//递归
listfile(f,map);
}
}else{
/**
* 处理文件名,上传后的文件是以uuid_文件名的形式去重新命名的,去除文件名的uuid_部分
file.getName().indexOf("_")检索字符串中第一次出现"_"字符的位置,如果文件名类似于:9349249849-88343-8344_阿_凡_达.avi
那么file.getName().substring(file.getName().indexOf("_")+1)处理之后就可以得到阿_凡_达.avi部分
*/
String realName = file.getName().substring(file.getName().indexOf("_")+1);
//file.getName()得到的是文件的原始名称,这个名称是唯一的,因此可以作为key,realName是处理过后的名称,有可能会重复
map.put(file.getName(), realName);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
这里简单说一下ListFileServlet中listfile方法,listfile方法是用来列出目录下的所有文件的,listfile方法内部用到了递归,在实际开发当中,我们肯定会在数据库创建一张表,里面会存储上传的文件名以及文件的具体存放目录,我们通过查询表就可以知道文件的具体存放目录,是不需要用到递归操作的,这个例子是因为没有使用数据库存储上传的文件名和文件的具体存放位置,而上传文件的存放位置又使用了散列算法打散存放,所以需要用到递归,在递归时,将获取到的文件名存放到从外面传递到listfile方法里面的Map集合当中,这样就可以保证所有的文件都存放在同一个Map集合当中。
在Web.xml文件中配置ListFileServlet
复制代码
<servlet>
<servlet-name>ListFileServlet</servlet-name>
<servlet-class>me.gacl.web.controller.ListFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListFileServlet</servlet-name>
<url-pattern>/servlet/ListFileServlet</url-pattern>
</servlet-mapping>
复制代码
展示下载文件的listfile.jsp页面如下:
复制代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML>
<html>
<head>
<title>下载文件显示页面</title>
</head>
<body>
<!-- 遍历Map集合 -->
<c:forEach var="me" items="${fileNameMap}">
<c:url value="/servlet/DownLoadServlet" var="downurl">
<c:param name="filename" value="${me.key}"></c:param>
</c:url>
${me.value}<a href="${downurl}">下载</a>
<br/>
</c:forEach>
</body>
</html>
复制代码
访问ListFileServlet,就可以在listfile.jsp页面中显示提供给用户下载的文件资源,如下图所示:

3.2、实现文件下载
DownLoadServlet编写一个用于处理文件下载的Servlet,DownLoadServlet的代码如下:
复制代码
package me.gacl.web.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DownLoadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到要下载的文件名
String fileName = request.getParameter("filename"); //23239283-92489-阿凡达.avi
fileName = new String(fileName.getBytes("iso8859-1"),"UTF-8");
//上传的文件都是保存在/WEB-INF/upload目录下的子目录当中
String fileSaveRootPath=this.getServletContext().getRealPath("/WEB-INF/upload");
//通过文件名找出文件的所在目录
String path = findFileSavePathByFileName(fileName,fileSaveRootPath);
//得到要下载的文件
File file = new File(path + "\\" + fileName);
//如果文件不存在
if(!file.exists()){
request.setAttribute("message", "您要下载的资源已被删除!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
//处理文件名
String realname = fileName.substring(fileName.indexOf("_")+1);
//设置响应头,控制浏览器下载该文件
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
//读取要下载的文件,保存到文件输入流
FileInputStream in = new FileInputStream(path + "\\" + fileName);
//创建输出流
OutputStream out = response.getOutputStream();
//创建缓冲区
byte buffer[] = new byte[1024];
int len = 0;
//循环将输入流中的内容读取到缓冲区当中
while((len=in.read(buffer))>0){
//输出缓冲区的内容到浏览器,实现文件下载
out.write(buffer, 0, len);
}
//关闭文件输入流
in.close();
//关闭输出流
out.close();
}
/**
* @Method: findFileSavePathByFileName
* @Description: 通过文件名和存储上传文件根目录找出要下载的文件的所在路径
* @Anthor:孤傲苍狼
* @param filename 要下载的文件名
* @param saveRootPath 上传文件保存的根目录,也就是/WEB-INF/upload目录
* @return 要下载的文件的存储目录
*/
public String findFileSavePathByFileName(String filename,String saveRootPath){
int hashcode = filename.hashCode();
int dir1 = hashcode&0xf; //0--15
int dir2 = (hashcode&0xf0)>>4; //0-15
String dir = saveRootPath + "\\" + dir1 + "\\" + dir2; //upload\2\3 upload\3\5
File file = new File(dir);
if(!file.exists()){
//创建目录
file.mkdirs();
}
return dir;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
在Web.xml文件中配置DownLoadServlet
复制代码
<servlet>
<servlet-name>DownLoadServlet</servlet-name>
<servlet-class>me.gacl.web.controller.DownLoadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownLoadServlet</servlet-name>
<url-pattern>/servlet/DownLoadServlet</url-pattern>
</servlet-mapping>
复制代码
点击【下载】超链接,将请求提交到DownLoadServlet就行处理就可以实现文件下载了,运行效果如下图所示:

从运行结果可以看到,我们的文件下载功能已经可以正常下载文件了。
关于JavaWeb中的文件上传和下载功能的内容就这么多。

(五十一)——邮件的发送与接收原理
一、 邮件开发涉及到的一些基本概念
1.1、邮件服务器和电子邮箱
要在Internet上提供电子邮件功能,必须有专门的电子邮件服务器。例如现在Internet很多提供邮件服务的厂商:sina、sohu、163等等他们都有自己的邮件服务器。
这些邮件服务器类似于现实生活中的邮局,它主要负责接收用户投递过来的邮件,并把邮件投递到邮件接收者的电子邮箱中。
电子邮箱(E-Mail地址)的获得需要在邮件服务器上进行申请,确切地说,电子邮箱其实就是用户在邮件服务器上申请的一个账户,用户在邮件服务器上申请了一个帐号后,邮件服务器就会为这个账号分配一定的空间,用户从而可以使用这个帐号以及空间发送电子邮件和保存别人发送过来的电子邮件。
1.2、邮件传输协议
1.2.1、SMTP协议
用户连接上邮件服务器之后,要想给它发送一封电子邮件,需要遵循一定的通讯规则,SMTP协议就是用来定义这种通讯规则的。因此,我们通常也把处理用户smtp请求(邮件发送请求)的服务器称之为SMTP服务器(邮件发送服务器)。
1.2.2、POP3协议
同样,用户若想从邮件服务器管理的电子邮箱当中接收一封电子邮件话,它连上邮件服务器后,也要遵循一定的通讯格式,POP3协议就是用来定义这种通讯格式的。因此,我们通常也把处理用户pop3请求(邮件接收请求)的服务器称之为POP3服务器(邮件接收服务器)。
1.3、电子邮件的发送和接收过程
通过一张图来说明一封邮件的发送和接收过程,如下图所示:

简单说一下这个邮件收发过程:
1、xdp@sohu.com 用户写好一封Email发送到sohu的Smtp服务器。对应于上图的步骤①
2、sohu的Smtp服务器开始处理xdp@sohu.com 用户的请求,它会根据收件人的地址判断,当前收件人是不是自己管辖的用户,如果是,就直接将Email存放到为该收件人的分配的邮箱空间当中。sohu的Smtp服务器判断收件人地址发现,这一封Email的收件人gacl@sina.com 是Sina的邮件服务器管理的,于是又将Email转发给Sina的Smtp服务器。对应于上图的步骤②
3、Sina的Smtp服务器开始处理sohu的Smtp服务器发送过来的Email,Sina的Smtp服务器根据收件人的地址判断,发现收件人自己管辖的用户,于是就直接将Email存放到为gacl@sina.com 用户的分配的邮箱空间当中。对应于上图的步骤③。
4、xdp@sohu.com 用户将邮件发出去之后,就通知gacl@sina.com 用户去收取。gacl@sina.com 用户于是就连接上Sina的POP3服务器收取邮件,对应于上图的步骤④。
5、POP3服务器从gacl@sina.com 用户的邮箱空间当中取出Email,对应于步骤⑤。
6、POP3服务器将取出来的Email发给gacl@sina.com 用户,对应于步骤⑥。
二、使用Smtp协议发送邮件
2.1、Smtp协议讲解
使用smtp协议发送邮件给邮件服务器时规定了要做以下几件事
1、使用"ehlo"命令和连接上的smtp服务器打声招呼,例如:
ehlo gacl
2、使用"auth login"命令登录到Smtp服务器,登录使用的用户名和密码必须经过Base64加密,例如:  
①、输入命令:auth login
②、输入使用Base64加密过后的用户名:Z2FjbA==
③、输入Base64加密过后的密码:MTIzNDU2
3、指明邮件的发件人和收件人
mail from:<gacl@sohu.com>
rcpt to:<xdp_gacl@sina.cn>
4、编写要发送的邮件内容,邮件的编写格式是有一定的规则的,一封格式良好的邮件应该包含邮件头和邮件的主体内容。
邮件头使用下面的三个字段来指明
from字段用于指明邮件的发送人
to字段用于指明邮件的收件人
subject字段用于指明邮件的主题
邮件的内容包含了这些信息之后才是一封格式良好的邮件。
①、输入"data"命令
data
②、编写邮件内容
from:<gacl@sohu.com> ----邮件头
to:<xdp_gacl@sina.cn> ----邮件头
subject:hello ----邮件头
-----空行
hello gacl  ----邮件的具体内容
5、输入一个.告诉邮件服务器邮件内容已经写完了
.
6、输入quit命令断开与邮件服务器的连接
quit
以上的6个步骤就是Smtp协议规定的发送一封Email必须要做的事情。
2.2、使用Smtp协议手工发送邮件
在对Smtp协议有一定的了解之后,我们就可以使用Smtp协议来发送邮件了。下面演示一下使用Telnet客户端连接上搜狐的邮件服务器然后发一封Email到新浪的邮箱中去
为了能够对Smtp协议有一个直观的认识,这里我们不借助任何第三方邮件客户端工具,而是使用最原始的的Telnet客户端来完成邮件的发送过程,Telnet是一个Window自带的网络客户端程序,通过它可以连接上互联网上面的任意一台主机。
使用telnet客户端连接到搜狐的smtp服务器,如下图所示:

通过telnet客户端发Email,如下图所示:

我们登录到<xdp_gacl@sina.cn>邮箱当中,就可以收取到由<gacl@sohu.com>发送的Email了,如下图所示:

这就是使用Smtp协议发送邮件的过程。
三、使用POP3协议接收邮件
3.1、POP3协议讲解
POP3协议规定了收取邮件时要做以下几件事
①、输入用户名和密码登录到POP3服务器,用户名和密码不需要经过Base64加密
user username --登录邮箱的用户名
pass password --登录邮箱使用的密码
②、使用retr命令收取邮件
retr 数字 收取邮件,retr 1表示收取邮箱当中的第一封邮件,这是POP3协议里面最重要的一个命令。
在使用retr命令收取邮件之前,可以使用如下的两个命令查看一下邮箱里面的邮件的一些相关信息。
stat
返回邮箱里面的邮件数量以及邮件占用的空间大小信息
list 数字
返回某一封邮件的统计信息
③、邮件收取完成之后使用quit命令断开与POP3服务器的连接。
quit 断开与POP3服务器的连接
3.2、使用POP3协议手工接收邮件
收取邮件我们也不借助任何第三方客户端工具,而是使用Telnet客户端连接到POP3服务器进行收取。
例如:现在我的搜狐邮箱当中有这样的一封邮件,如下图所示:

现在我们不用使用foxmail或者outLook这样的客户端工具去收取,而是使用Telnet客户端连接到搜狐的POP3服务器去手工收取。
1、使用Telnet连接上搜狐的POP3服务器,使用命令:telnet pop3.sohu.com 110,如下图所示:

根据POP3协议规定的邮件收取步骤来收取邮件。如下图所示:

可以看到,我们POP3协议纯手工从搜狐的POP3服务器当中收取回来了一封Email,Email里面的内容都经过了Base64编码处理,下面我们写一个小程序将经过Base64编码后的邮件内容进行解码,还原回邮件的内容,代码如下:
复制代码
package me.gacl.encrypt;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class Base64Encrypt {
public static void main(String args[]) throws IOException{
/*System.out.print("请输入用户名:");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String userName = in.readLine();
System.out.print("请输入密码:");
String password = in.readLine();
BASE64Encoder encoder = new BASE64Encoder();
System.out.println("编码后的用户名为:" + encoder.encode(userName.getBytes()));
System.out.println("编码后的密码为:" + encoder.encode(password.getBytes()));*/
BASE64Decoder decoder = new BASE64Decoder();
//邮件主题的Base64编码
String emailSubject = "=?GBK?B?08q8/rLiytQ=?=";
//邮件文本内容的Base64编码
String emailPlainContent = "vPK1pbXE08q8/reiy82y4srUo6E=";
//带html标签和邮件内容的Base64编码
String emailHtmlContent = "PFA+vPK1pbXE08q8/reiy82y4srUo6E8L1A+";
//将使用Base64编码过后的文本内容再使用Base64来解码
emailSubject = new String(decoder.decodeBuffer(emailSubject),"GBK");
emailPlainContent = new String(decoder.decodeBuffer(emailPlainContent),"GBK");
emailHtmlContent = new String(decoder.decodeBuffer(emailHtmlContent),"GBK");
System.out.println("邮件标题:"+emailSubject);
System.out.println("邮件内容:"+emailPlainContent);
System.out.println("带html标签的邮件内容:"+emailHtmlContent);
}
}
复制代码
运行结果如下:

这就是使用POP3协议收取邮件的过程。
以上就是邮件的发送与接收原理的相关内容,这一篇文章主要是介绍邮件发送和接收过程中使用到的smtp协议和pop3协议。没有涉及到太多代码方面的东西,后面会具体介绍使用JavaMail来进行邮件发送。

(五十二)——使用JavaMail创建邮件和发送邮件
一、RFC882文档简单说明
RFC882文档规定了如何编写一封简单的邮件(纯文本邮件),一封简单的邮件包含邮件头和邮件体两个部分,邮件头和邮件体之间使用空行分隔。
邮件头包含的内容有:
from字段 --用于指明发件人
to字段 --用于指明收件人
subject字段 --用于说明邮件主题
cc字段 -- 抄送,将邮件发送给收件人的同时抄送给另一个收件人,收件人可以看到邮件抄送给了谁
bcc字段 -- 密送,将邮件发送给收件人的同时将邮件秘密发送给另一个收件人,收件人无法看到邮件密送给了谁
邮件体指的就是邮件的具体内容。
二、MIME协议简单介绍
在我们的实际开发当中,一封邮件既可能包含图片,又可能包含有附件,在这样的情况下,RFC882文档规定的邮件格式就无法满足要求了。
MIME协议是对RFC822文档的升级和补充,它描述了如何生产一封复杂的邮件。通常我们把MIME协议描述的邮件称之为MIME邮件。MIME协议描述的数据称之为MIME消息。
对于一封复杂邮件,如果包含了多个不同的数据,MIME协议规定了要使用分隔线对多段数据进行分隔,并使用Content-Type头字段对数据的类型、以及多个数据之间的关系进行描述。
三、使用JavaMail创建邮件和发送邮件
JavaMail创建的邮件是基于MIME协议的。因此可以使用JavaMail创建出包含图片,包含附件的复杂邮件。
3.1、JavaMail API的简单介绍



3.2、创建邮件发送测试项目

3.3、发送一封只包含文本的简单邮件
复制代码
package me.gacl.main;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
/**
* @ClassName: Sendmail
* @Description: 发送Email
* @author: 孤傲苍狼
* @date: 2015-1-12 下午9:42:56
*
*/
public class Sendmail {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.host", "smtp.sohu.com");
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
//使用JavaMail发送邮件的5个步骤
//1、创建session
Session session = Session.getInstance(prop);
//开启Session的debug模式,这样就可以查看到程序发送Email的运行状态
session.setDebug(true);
//2、通过session得到transport对象
Transport ts = session.getTransport();
//3、使用邮箱的用户名和密码连上邮件服务器,发送邮件时,发件人需要提交邮箱的用户名和密码给smtp服务器,用户名和密码都通过验证之后才能够正常发送邮件给收件人。
ts.connect("smtp.sohu.com", "gacl", "邮箱密码");
//4、创建邮件
Message message = createSimpleMail(session);
//5、发送邮件
ts.sendMessage(message, message.getAllRecipients());
ts.close();
}
/**
* @Method: createSimpleMail
* @Description: 创建一封只包含文本的邮件
* @Anthor:孤傲苍狼
*
* @param session
* @return
* @throws Exception
*/
public static MimeMessage createSimpleMail(Session session)
throws Exception {
//创建邮件对象
MimeMessage message = new MimeMessage(session);
//指明邮件的发件人
message.setFrom(new InternetAddress("gacl@sohu.com"));
//指明邮件的收件人,现在发件人和收件人是一样的,那就是自己给自己发
message.setRecipient(Message.RecipientType.TO, new InternetAddress("gacl@sohu.com"));
//邮件的标题
message.setSubject("只包含文本的简单邮件");
//邮件的文本内容
message.setContent("你好啊!", "text/html;charset=UTF-8");
//返回创建好的邮件对象
return message;
}
}
复制代码
3.4、发送包含内嵌图片的邮件
复制代码
package me.gacl.main;
import java.io.FileOutputStream;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
/**
* @ClassName: Sendmail
* @Description: 发送Email
* @author: 孤傲苍狼
* @date: 2015-1-12 下午9:42:56
*
*/
public class Sendmail {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.host", "smtp.sohu.com");
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
//使用JavaMail发送邮件的5个步骤
//1、创建session
Session session = Session.getInstance(prop);
//开启Session的debug模式,这样就可以查看到程序发送Email的运行状态
session.setDebug(true);
//2、通过session得到transport对象
Transport ts = session.getTransport();
//3、连上邮件服务器,需要发件人提供邮箱的用户名和密码进行验证
ts.connect("smtp.sohu.com", "gacl", "邮箱密码");
//4、创建邮件
Message message = createImageMail(session);
//5、发送邮件
ts.sendMessage(message, message.getAllRecipients());
ts.close();
}
/**
* @Method: createImageMail
* @Description: 生成一封邮件正文带图片的邮件
* @Anthor:孤傲苍狼
*
* @param session
* @return
* @throws Exception
*/
public static MimeMessage createImageMail(Session session) throws Exception {
//创建邮件
MimeMessage message = new MimeMessage(session);
// 设置邮件的基本信息
//发件人
message.setFrom(new InternetAddress("gacl@sohu.com"));
//收件人
message.setRecipient(Message.RecipientType.TO, new InternetAddress("xdp_gacl@sina.cn"));
//邮件标题
message.setSubject("带图片的邮件");
// 准备邮件数据
// 准备邮件正文数据
MimeBodyPart text = new MimeBodyPart();
text.setContent("这是一封邮件正文带图片<img src=‘cid:xxx.jpg‘>的邮件", "text/html;charset=UTF-8");
// 准备图片数据
MimeBodyPart image = new MimeBodyPart();
DataHandler dh = new DataHandler(new FileDataSource("src\\1.jpg"));
image.setDataHandler(dh);
image.setContentID("xxx.jpg");
// 描述数据关系
MimeMultipart mm = new MimeMultipart();
mm.addBodyPart(text);
mm.addBodyPart(image);
mm.setSubType("related");
message.setContent(mm);
message.saveChanges();
//将创建好的邮件写入到E盘以文件的形式进行保存
message.writeTo(new FileOutputStream("E:\\ImageMail.eml"));
//返回创建好的邮件
return message;
}
}
复制代码
3.5、发送包含附件的邮件
复制代码
package me.gacl.main;
import java.io.FileOutputStream;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
/**
* @ClassName: Sendmail
* @Description: 发送Email
* @author: 孤傲苍狼
* @date: 2015-1-12 下午9:42:56
*
*/
public class Sendmail {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.host", "smtp.sohu.com");
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
//使用JavaMail发送邮件的5个步骤
//1、创建session
Session session = Session.getInstance(prop);
//开启Session的debug模式,这样就可以查看到程序发送Email的运行状态
session.setDebug(true);
//2、通过session得到transport对象
Transport ts = session.getTransport();
//3、连上邮件服务器
ts.connect("smtp.sohu.com", "gacl", "邮箱密码");
//4、创建邮件
Message message = createAttachMail(session);
//5、发送邮件
ts.sendMessage(message, message.getAllRecipients());
ts.close();
}
/**
* @Method: createAttachMail
* @Description: 创建一封带附件的邮件
* @Anthor:孤傲苍狼
*
* @param session
* @return
* @throws Exception
*/
public static MimeMessage createAttachMail(Session session) throws Exception{
MimeMessage message = new MimeMessage(session);
//设置邮件的基本信息
//发件人
message.setFrom(new InternetAddress("gacl@sohu.com"));
//收件人
message.setRecipient(Message.RecipientType.TO, new InternetAddress("xdp_gacl@sina.cn"));
//邮件标题
message.setSubject("JavaMail邮件发送测试");
//创建邮件正文,为了避免邮件正文中文乱码问题,需要使用charset=UTF-8指明字符编码
MimeBodyPart text = new MimeBodyPart();
text.setContent("使用JavaMail创建的带附件的邮件", "text/html;charset=UTF-8");
//创建邮件附件
MimeBodyPart attach = new MimeBodyPart();
DataHandler dh = new DataHandler(new FileDataSource("src\\2.jpg"));
attach.setDataHandler(dh);
attach.setFileName(dh.getName()); //
//创建容器描述数据关系
MimeMultipart mp = new MimeMultipart();
mp.addBodyPart(text);
mp.addBodyPart(attach);
mp.setSubType("mixed");
message.setContent(mp);
message.saveChanges();
//将创建的Email写入到E盘存储
message.writeTo(new FileOutputStream("E:\\attachMail.eml"));
//返回生成的邮件
return message;
}
}
复制代码
3.6、发送包含内嵌图片和附件的复杂邮件
复制代码
package me.gacl.main;
import java.io.FileOutputStream;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
/**
* @ClassName: Sendmail
* @Description: 发送Email
* @author: 孤傲苍狼
* @date: 2015-1-12 下午9:42:56
*
*/
public class Sendmail {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("mail.host", "smtp.sohu.com");
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
//使用JavaMail发送邮件的5个步骤
//1、创建session
Session session = Session.getInstance(prop);
//开启Session的debug模式,这样就可以查看到程序发送Email的运行状态
session.setDebug(true);
//2、通过session得到transport对象
Transport ts = session.getTransport();
//3、连上邮件服务器
ts.connect("smtp.sohu.com", "gacl", "邮箱密码");
//4、创建邮件
Message message = createMixedMail(session);
//5、发送邮件
ts.sendMessage(message, message.getAllRecipients());
ts.close();
}
/**
* @Method: createMixedMail
* @Description: 生成一封带附件和带图片的邮件
* @Anthor:孤傲苍狼
*
* @param session
* @return
* @throws Exception
*/
public static MimeMessage createMixedMail(Session session) throws Exception {
//创建邮件
MimeMessage message = new MimeMessage(session);
//设置邮件的基本信息
message.setFrom(new InternetAddress("gacl@sohu.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress("xdp_gacl@sina.cn"));
message.setSubject("带附件和带图片的的邮件");
//正文
MimeBodyPart text = new MimeBodyPart();
text.setContent("xxx这是女的xxxx<br/><img src=‘cid:aaa.jpg‘>","text/html;charset=UTF-8");
//图片
MimeBodyPart image = new MimeBodyPart();
image.setDataHandler(new DataHandler(new FileDataSource("src\\3.jpg")));
image.setContentID("aaa.jpg");
//附件1
MimeBodyPart attach = new MimeBodyPart();
DataHandler dh = new DataHandler(new FileDataSource("src\\4.zip"));
attach.setDataHandler(dh);
attach.setFileName(dh.getName());
//附件2
MimeBodyPart attach2 = new MimeBodyPart();
DataHandler dh2 = new DataHandler(new FileDataSource("src\\波子.zip"));
attach2.setDataHandler(dh2);
attach2.setFileName(MimeUtility.encodeText(dh2.getName()));
//描述关系:正文和图片
MimeMultipart mp1 = new MimeMultipart();
mp1.addBodyPart(text);
mp1.addBodyPart(image);
mp1.setSubType("related");
//描述关系:正文和附件
MimeMultipart mp2 = new MimeMultipart();
mp2.addBodyPart(attach);
mp2.addBodyPart(attach2);
//代表正文的bodypart
MimeBodyPart content = new MimeBodyPart();
content.setContent(mp1);
mp2.addBodyPart(content);
mp2.setSubType("mixed");
message.setContent(mp2);
message.saveChanges();
message.writeTo(new FileOutputStream("E:\\MixedMail.eml"));
//返回创建好的的邮件
return message;
}
}
复制代码
以上就是使用JavaMail的API创建邮件和发送邮件的全部内容。

(五十三)——Web应用中使用JavaMail发送邮件
现在很多的网站都提供有用户注册功能, 通常我们注册成功之后就会收到一封来自注册网站的邮件。邮件里面的内容可能包含了我们的注册的用户名和密码以及一个激活账户的超链接等信息。今天我们也来实现一个这样的功能,用户注册成功之后,就将用户的注册信息以Email的形式发送到用户的注册邮箱当中,实现发送邮件功能就得借助于JavaMail了。
一、搭建开发环境
1.1、创建Web项目

1.2、用户注册的Jsp页面
register.jsp
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>注册页面</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/servlet/RegisterServlet" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
邮箱:<input type="text" name="email"><br/>
<input type="submit" value="注册">
</form>
</body>
</html>
复制代码
1.3、消息提示页面
message.jsp
复制代码
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>消息提示页面</title>
</head>
<body>
${message}
</body>
</html>
复制代码
二、编写处理用户注册处理程序
2.1、开发封装用户注册信息的domain
User.java
复制代码
package me.gacl.domain;
public class User {
private String username;
private String password;
private String email;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
复制代码
2.2、编写邮件发送功能
Sendmail发送邮件是一件非常耗时的事情,因此这里设计一个线程类来发送邮件
复制代码
package me.gacl.web.controller;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import me.gacl.domain.User;
/**
* @ClassName: Sendmail
* @Description: Sendmail类继承Thread,因此Sendmail就是一个线程类,这个线程类用于给指定的用户发送Email
* @author: 孤傲苍狼
* @date: 2015-1-12 下午10:43:48
*
*/
public class Sendmail extends Thread {
//用于给用户发送邮件的邮箱
private String from = "gacl@sohu.com";
//邮箱的用户名
private String username = "gacl";
//邮箱的密码
private String password = "邮箱密码";
//发送邮件的服务器地址
private String host = "smtp.sohu.com";
private User user;
public Sendmail(User user){
this.user = user;
}
/* 重写run方法的实现,在run方法中发送邮件给指定的用户
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try{
Properties prop = new Properties();
prop.setProperty("mail.host", host);
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
Session session = Session.getInstance(prop);
session.setDebug(true);
Transport ts = session.getTransport();
ts.connect(host, username, password);
Message message = createEmail(session,user);
ts.sendMessage(message, message.getAllRecipients());
ts.close();
}catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Method: createEmail
* @Description: 创建要发送的邮件
* @Anthor:孤傲苍狼
*
* @param session
* @param user
* @return
* @throws Exception
*/
public Message createEmail(Session session,User user) throws Exception{
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail()));
message.setSubject("用户注册邮件");
String info = "恭喜您注册成功,您的用户名:" + user.getUsername() + ",您的密码:" + user.getPassword() + ",请妥善保管,如有问题请联系网站客服!!";
message.setContent(info, "text/html;charset=UTF-8");
message.saveChanges();
return message;
}
}
复制代码
2.3、编写处理用户注册的Servlet
RegisterServlet复制代码
package me.gacl.web.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.domain.User;
import me.gacl.service.UserService;
public class RegisterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try{
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
User user = new User();
user.setEmail(email);
user.setPassword(password);
user.setUsername(username);
System.out.println("把用户信息注册到数据库中");
//用户注册成功之后就使用用户注册时的邮箱给用户发送一封Email
//发送邮件是一件非常耗时的事情,因此这里开辟了另一个线程来专门发送邮件
Sendmail send = new Sendmail(user);
//启动线程,线程启动之后就会执行run方法来发送邮件
send.start();
//注册用户
//new UserService().registerUser(user);
request.setAttribute("message", "恭喜您,注册成功,我们已经发了一封带了注册信息的电子邮件,请查收,如果没有收到,可能是网络原因,过一会儿就收到了!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "注册失败!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
复制代码
程序运行效果如下:

现在很多网站都有这样的功能,用户注册完成之后,网站根据我们注册时填写的邮箱给我们一封Email,然后点击Email中的超链接去激活我们的用户。这种功能就是这样实现的。
在总结使用JavaMail发送邮件时发现,将邮件发送到sina或者sohu的邮箱时,不一定能够马上收取得到邮件,总是有延迟,有时甚至会延迟很长的时间,甚至会被当成垃圾邮件来处理掉,或者干脆就拒绝接收,有时候为了看到邮件发送成功的效果,要等半天,实属无奈啊。


中文乱码
request接收表单提交中文参数乱码问题
1 以POST方式提交表单中文参数的乱码问题
例如有如下的form表单页面:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>request接收中文参数乱码问题</title>
</head>

<body>
<form action="<%=request.getContextPath()%>/servlet/RequestDemo04" method="post">
用户名:<input type="text" name="userName"/>
<input type="submit" value="post方式提交表单">
</form>
</body>
</html>

此时在服务器端接收中文参数时就会出现中文乱码,如下所示:

2 post方式提交中文数据乱码产生的原因和解决办法

可以看到,之所以会产生乱码,就是因为服务器和客户端沟通的编码不一致造成的,因此解决的办法是:在客户端和服务器之间设置一个统一的编码,之后就按照此编码进行数据的传输和接收。
由于客户端是以UTF-8字符编码将表单数据传输到服务器端的,因此服务器也需要设置以UTF-8字符编码进行接收,要想完成此操作,服务器可以直接使用从ServletRequest接口继承而来的"setCharacterEncoding(charset)"方法进行统一的编码设置。修改后的代码如下:
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 客户端是以UTF-8编码传输数据到服务器端的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码
*/
request.setCharacterEncoding("UTF-8");
String userName = request.getParameter("userName");
System.out.println("userName:"+userName);
}
使用request.setCharacterEncoding("UTF-8");设置服务器以UTF-8的编码接收数据后,此时就不会产生中文乱码问题了,如下所示:

3 以GET方式提交表单中文参数的乱码问题
例如有如下的form表单页面:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>request接收中文参数乱码问题</title>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/RequestDemo04" method="get">
姓名:<input type="text" name="name"/>
<input type="submit" value="get方式提交表单">
</form>
</body>
</html>

此时在服务器端接收中文参数时就会出现中文乱码,如下所示:

那么这个中文乱码问题又该如何解决呢,是否可以通过request.setCharacterEncoding("UTF-8");设置服务器以UTF-8的编码进行接收这种方式来解决中文乱码问题呢,注意,对于以get方式传输的中文数据,通过request.setCharacterEncoding("UTF-8");这种方式是解决不了中文乱码问题,如下所示:

4 get方式提交中文数据乱码产生的原因和解决办法
对于以get方式传输的数据,request即使设置了以指定的编码接收数据也是无效的(至于为什么无效我也没有弄明白),默认的还是使用ISO8859-1这个字符编码来接收数据,客户端以UTF-8的编码传输数据到服务器端,而服务器端的request对象使用的是ISO8859-1这个字符编码来接收数据,服务器和客户端沟通的编码不一致因此才会产生中文乱码的。解决办法:在接收到数据后,先获取request对象以ISO8859-1字符编码接收到的原始数据的字节数组,然后通过字节数组以指定的编码构建字符串,解决乱码问题。代码如下:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
*
* 对于以get方式传输的数据,request即使设置了以指定的编码接收数据也是无效的,默认的还是使用ISO8859-1这个字符编码来接收数据
*/
String name = request.getParameter("name");//接收数据
name =new String(name.getBytes("ISO8859-1"), "UTF-8") ;//获取request对象以ISO8859-1字符编码接收到的原始数据的字节数组,然后通过字节数组以指定的编码构建字符串,解决乱码问题
System.out.println("name:"+name);
}
运行结果如下:

5 以超链接形式传递中文参数的乱码问题
客户端想传输数据到服务器,可以通过表单提交的形式,也可以通过超链接后面加参数的形式,例如:
<a href="${pageContext.request.contextPath}/servlet/RequestDemo05?userName=gacl&name=徐达沛">点击</a>
点击超链接,数据是以get的方式传输到服务器的,所以接收中文数据时也会产生中文乱码问题,而解决中文乱码问题的方式与上述的以get方式提交表单中文数据乱码处理问题的方式一致,如下所示:
String name = request.getParameter("name");
name =new String(name.getBytes("ISO8859-1"), "UTF-8");
另外,需要提的一点就是URL地址后面如果跟了中文数据,那么中文参数最好使用URL编码进行处理,如下所示:
<a href="${pageContext.request.contextPath}/servlet/RequestDemo05?userName=gacl&name=<%=URLEncoder.encode("徐达沛", "UTF-8")%>">点击</a>
6 提交中文数据乱码问题总结
1 如果提交方式为post,想不乱码,只需要在服务器端设置request对象的编码即可,客户端以哪种编码提交的,服务器端的request对象就以对应的编码接收,比如客户端是以UTF-8编码提交的,那么服务器端request对象就以UTF-8编码接收(request.setCharacterEncoding("UTF-8"))
2 如果提交方式为get,设置request对象的编码是无效的,request对象还是以默认的ISO8859-1编码接收数据,因此要想不乱码,只能在接收到数据后再手工转换,步骤如下:
1).获取获取客户端提交上来的数据,得到的是乱码字符串,data="???è?????"
String data = request.getParameter("paramName");
2).查找ISO8859-1码表,得到客户机提交的原始数据的字节数组
byte[] source = data.getBytes("ISO8859-1");
3).通过字节数组以指定的编码构建字符串,解决乱码
data = new String(source, "UTF-8");
通过字节数组以指定的编码构建字符串,这里指定的编码是根据客户端那边提交数据时使用的字符编码来定的,如果是GB2312,那么就设置成data = new String(source, "GB2312"),如果是UTF-8,那么就设置成data = new String(source, "UTF-8")

web工程中URL地址的推荐写法
在JavaWeb开发中,只要是写URL地址,那么建议最好以"/"开头,也就是使用绝对路径的方式,那么这个"/"到底代表什么呢?可以用如下的方式来记忆"/":如果"/"是给服务器用的,则代表当前的web工程,如果"/"是给浏览器用的,则代表webapps目录。

"/"代表当前web工程的常见应用场景
1 ServletContext.getRealPath(String path)获取资源的绝对路径

/**
* 1.ServletContext.getRealPath("/download/1.JPG")是用来获取服务器上的某个资源,
* 那么这个"/"就是给服务器用的,"/"此时代表的就是web工程
* ServletContext.getRealPath("/download/1.JPG")表示的就是读取web工程下的download文件夹中的1.JPG这个资源
* 只要明白了"/"代表的具体含义,就可以很快写出要访问的web资源的绝对路径
*/
this.getServletContext().getRealPath("/download/1.JPG");

2 在服务器端forward到其他页面

/**
* 2.forward
* 客户端请求某个web资源,服务器跳转到另外一个web资源,这个forward也是给服务器用的,
* 那么这个"/"就是给服务器用的,所以此时"/"代表的就是web工程
*/
this.getServletContext().getRequestDispatcher("/index.jsp").forward(request, response);

3 使用include指令或者<jsp:include>标签引入页面
<%@include file="/jspfragments/head.jspf" %>
<jsp:include page="/jspfragments/demo.jsp" />
  此时"/"代表的都是web工程。

"/"代表webapps目录的常见应用场景
1 使用sendRedirect实现请求重定向
response.sendRedirect("/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");
  服务器发送一个URL地址给浏览器,浏览器拿到URL地址之后,再去请求服务器,所以这个"/"是给浏览器使用的,此时"/"代表的就是webapps目录,"/JavaWeb_HttpServletResponse_Study_20140615/index.jsp"这个地址指的就是"webapps\JavaWeb_HttpServletResponse_Study_20140615\index.jsp"
  response.sendRedirect("/项目名称/文件夹目录/页面");这种写法是将项目名称写死在程序中的做法,不灵活,万一哪天项目名称变了,此时就得改程序,所以推荐使用下面的灵活写法:

response.sendRedirect("/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");
  这种写法改成
response.sendRedirect(request.getContextPath()+"/index.jsp");
  request.getContextPath()获取到的内容就是"/JavaWeb_HttpServletResponse_Study_20140615",这样就比较灵活了,使用request.getContextPath()代替"/项目名称",推荐使用这种方式,灵活方便!
2 使用超链接跳转
<a href="/JavaWeb_HttpServletResponse_Study_20140615/index.jsp">跳转到首页</a>
  这是客户端浏览器使用的超链接跳转,这个"/"是给浏览器使用的,此时"/"代表的就是webapps目录。
  使用超链接访问web资源,绝对路径的写法推荐使用下面的写法改进:
<a href="${pageContext.request.contextPath}/index.jsp">跳转到首页</a>
  这样就可以避免在路径中出现项目的名称,使用${pageContext.request.contextPath}取代"/JavaWeb_HttpServletResponse_Study_20140615"
3 Form表单提交
<form action="/JavaWeb_HttpServletResponse_Study_20140615/servlet/CheckServlet" method="post">
<input type="submit" value="提交">
</form>
  这是客户端浏览器将form表单提交到服务器,所以这个"/"是给浏览器使用的,此时"/"代表的就是webapps目录。
对于form表单提交中action属性绝对路径的写法,也推荐使用如下的方式改进:
<form action="${pageContext.request.contextPath}/servlet/CheckServlet" method="post">
<input type="submit" value="提交">
</form>
  ${pageContext.request.contextPath}得到的就是"/JavaWeb_HttpServletResponse_Study_20140615"
  ${pageContext.request.contextPath}的效果等同于request.getContextPath(),两者获取到的都是"/项目名称"
4 js脚本和css样式文件的引用
<%--使用绝对路径的方式引用js脚本--%>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/index.js"></script>
<%--${pageContext.request.contextPath}与request.getContextPath()写法是得到的效果是一样的--%>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/login.js"></script>
<%--使用绝对路径的方式引用css样式--%>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/index.css" type="text/css"/>
综合范例:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>"/"代表webapps目录的常见应用场景</title>
<%--使用绝对路径的方式引用js脚本--%>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/index.js"></script>
<%--${pageContext.request.contextPath}与request.getContextPath()写法是得到的效果是一样的--%>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/login.js"></script>
<%--使用绝对路径的方式引用css样式--%>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/index.css" type="text/css"/>
</head>

<body>
<%--form表单提交--%>
<form action="${pageContext.request.contextPath}/servlet/CheckServlet" method="post">
<input type="submit" value="提交">
</form>
<%--超链接跳转页面--%>
<a href="${pageContext.request.contextPath}/index.jsp">跳转到首页</a>
</body>
</html>


Request getParameter() 和getAttribute() 区别
getParameter 是用来接受用post个get方法传递过来的参数的. 一般通过表单和链接传递的参数使用getParameter
getAttribute 必须先setAttribute.

一般可以用getParameter得到页面字符串参数
getAttribute()可以得到对象
getParameter可以得到页面传来的参数如?id=123之类的。
getAttribute()常用于servlet页面传递参数给jsp

getParameter()得到的值如果下次不提交或保存起来的话,下次重定向后就没了

(1)request.getParameter() 取得是通过容器的实现来取得通过类似post,get等方式传入的数据,request.setAttribute()和getAttribute()只是在web容器内部流转,仅仅是请求处理阶段。

(2)request.getParameter() 方法传递的数据,会从Web客户端传到Web服务器端,代表HTTP请求数据。request.getParameter()方法返回String类型的数据。

request.setAttribute() 和 getAttribute() 方法传递的数据只会存在于Web容器内部

还有一点就是,HttpServletRequest 类有 setAttribute() 方法,而没有setParameter() 方法。

路径
1 取出部署的应用程序路径
使用<%=request.getContextPath()%>和使用${pageContext.request.contextPath}达到同样的效果

<!--使用绝对路径的方式引入CSS文件-->
<link rel="stylesheet" href="${pageContext.request.contextPath}/themes/default/css/ueditor.css" type="text/css"/>
<!--使用绝对路径的方式引入JavaScript脚本-->
<script type="text/javascript" src="${pageContext.request.contextPath}/ueditor1_3_6-gbk-jsp/ueditor.config.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/ueditor1_3_6-gbk-jsp/ueditor.all.js"></script>

getResourceAsStream
/**
* 通过ServletContext对象读取项目根目录下的db1.properties配置文件
*/
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db1.properties");
this.getClass().getClassLoader().getResource("/").getPath();
XmlUtils.class.getClassLoader().getResource("DB.xml");
request.getContextPath()+"/servlet/LoginUIServlet"
Thread.currentThread().getContextClassLoader().getResources(packageDirName);
String realPath = this.getServletContext().getRealPath("/download/1.JPG");


Eclipse 设置
项目输出路径 : Chapter6/WebContent/WEB-INF/classes
WebContent\WEB-INF\classes
Tomcat
1 Tomcat 无法运行
org.apache.catalina.LifecycleException:Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext

@WebServlet("/Example6_2") //一定要加这个 , 这个重复会导致tomcat 无法启动

2 Tomcat 无法运行
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>Chapter5</display-name>

<servlet>
<servlet-name>Example1</servlet-name>
<servlet-class>com.examples.Example1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Example1</servlet-name>
<url-pattern>/servlet/Example1</url-pattern> <!-- 此处如果不加servlet,Tomcat 无法运行 -->
</servlet-mapping>
</web-app>

结尾

 

java web -- gacl 汇总

标签:mvc设计模式   sources   针对   源代码   phi   super   动画   最大数   coder   

原文地址:http://www.cnblogs.com/c0liu/p/6120515.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!