标签:des style blog http java color
JSP笔记
Tomcat服务器
端口:
端口就是指的某一个程序网络入口,Tomcat的初始化端口为:8080;
端口的个数:256*256=65536个;
一般常见协议的缺省端口为:
http 80
smtp 25
pop3 110
ftp 23
https 443
端口占用查看命令:dos中运行netstat –ano命令
conf文件夹下的server.xml配置文件中的<Connectorport="8080"protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/> 标签用于配置Tomcat服务器的端口
环境变量配置:
新增一个JAVA_HOME环境变量配置为JDK的根目录,因为Tomcat服务器是java写的所有也需要JVM来运行
文件共享:<Context />标签中的reloadable=”true”属性,设置为true 表示只要文件被修改,就重新发布并加载(不建议使用)(context.xml配置文件中)
第一种:
tomcat服务器会自动扫描webapps这个文件夹,所以我们要共享的web应用目录,就可以放在此文件夹下,也就是说Tomcat服务器webapp目录中的web应用,外界可以直接访问,不需要配置
第二种:
也可以修改conf文件夹下的server.xml配置文件,在Host标签中添加<Contextpath="/hello" docBase="E:/Demo/Test" />标签,hello为E:/Demo/Test映射;也就是说hello是一个虚拟的web应用目录地址,而E:/Demo/Test是我们的实际web应用目录地址,如果path=””;就表示是缺省的;
第三种:
也可以在\conf\Catalina\localhost\目录下面 创建一个任意文件名的.xml文件,在里面配置<ContextdocBase="E:/Demo/Test" />标签,那么这个web的虚拟目录就是这个文件的名称,实际的目录地址就是docBase所配置的地址;但实际的目录地址不能是\webapps这个目录中的,此方法的好处是配置好了不需要重启;如果这个.xml文件名为ROOT.xml就表示缺省默认的;这样的关系称为映射关系
Tomcat的目录层次结构:
bin目录 存放的是启动和关闭Tomcat的脚步文件
conf 存放Tomcat服务器的各种配置文件
lib 存放Tomcat服务器的支持jar包
logs 存放Tomcat服务器的日志文件
temp 存放Tomcat运行时产生的临时文件
webapps web应用所在目录,即可以供外界访问的web资源的存放目录
work Tomcat的工作目录
Tomcat服务器的应用管理:(适用于Tomcat7.0,Tomcat6.0角色名用的是manager)
修改\conf文件中的tomcat-users.xml位置文件, 在<tomcat-users> </tomcat-users>标签中加上<rolerolename="manager-gui"/><user username="admin"password="admin" roles="manager-gui"/>
manager-gui是角色 然后在设置账号和密码
然后在Tomcat首页打开ManagerApp输入账号密码进入应用管理页面;Start 开始;Stop 停止;Reload 重装;Undeploy 卸载
当我们打开www.mybaidu.com时,运行E:\practice\JSP\practice1\Test\Demo\1.html文件
第一步:
设置Demo工程下WEB-INF中的web.xml配置文件;将1.html设置为缺省页,<welcome-file-list> <welcome-file>1.html</welcome-file> </welcome-file-list>
第二步:
配置\conf中server.xml文件;在后面添加<Contextpath="" docBase="E:\practice\JSP\practice1\Test\Demo" />;将path设置为””表示缺省
第三步:
设置端口为80端口(只限定HTTP协议);配置\conf中server.xml文件;<Connectorport="8080" protocol="HTTP/1.1"connectionTimeout="20000" redirectPort="8443" />; port=”80”
新建一个主机
在conf文件夹下的server.xml配置文件中,添加一个<Hostname=”主机名” appBase=”web工程位置”> <Contextpath=”映射路径” docBase=”访问路径” /> </Host>
Tomcat体系结构
Tomcat服务器启动时会启动一个Server服务,Server服务启动时会启动多个连接器(Connector);
浏览器发送一个HTTP协议然后找到Server服务中与HTTP协议对应的连接器(Connector),然后通过连接器(Connector)找到引擎(Engine),然后再找主机,主机再找WEB应用,最后找到WEB资源
配置https连接器
web工程(Project)
注意:每次创建一个新的工程都需发布到Tomcat服务器,修改代码后需重新部署(Redeploy)一下
war包
jar –vcf 新的文件名.war 要打包的文件夹;用此命令将web工程打包为war包
当war包的文件,放入Tomcat的webapps文件夹中时,会自动解压
目录层次结构
Myeclipse 2013 工程目录结构
工程目录 |
存放java文件的目录 |
存放于java文件对应的class文件 |
存放Web应用目录 |
存放jar包 |
Web应用的配置文件 |
索引页 |
Tomcat工程物理结构
web.xml 配置文件
web.xml只能放在WEB-INF这个文件夹中;
设置项目的缺省首页(不能直接将servlet映射为首页,如果首页必须要经过serlvet转发,那么可以在index.jsp文件中转发至serlvet,再通过此servlet转发至首页)
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
在web应用中注册com.scxh.Test这个serlvet,并取名为Test
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>com.scxh.Test</servlet-class>
</servlet>
当出现此链接到此工程后,出现.com的后缀就交给Test这个类处理;
<servlet-mapping>
<servlet-name>Test</servlet-name>
<url-pattern>*.com</url-pattern>//不能在前面加字符了,如:<url-pattern>aa/*.com</url-pattern>;这样也是不允许的
</servlet-mapping>
这个Test可以映射成多个URL(伪静态)这就是servlet的映射,如:
<servlet-mapping>
<servlet-name>Test</servlet-name>
<url-pattern>/*</url-pattern>//表示‘/‘后面不管跟什么都访问这个类(aa/*;这种方式也可以)
</servlet-mapping>
一般Servlet在配置映射时,不要将url-pattern标签配置为”/”;因为’/’通配符表示所有缺省的(找不到的资源)都访问此Servlet;在Conf/web.xml文件中,系统向所有的Servlet都配置了此’/’,当我们访问所有静态资源(如直接访问web工程中的1.html)都会被系统配的’/’ 所捕获到,都是访问的系统自带的Servlet对象;然后再通过这个对象去找你web应用中是否有这个静态资源,有就直接返回,没有就抛404异常(如果我们再设置url-pattern标签配置为’/’就会覆盖系统的设置,当我们再访问有些静态资源时就访问不到)
修饰符的优先级(谁最符合就匹配谁;’*’号越在前面,优先级越低)
HTTP协议
请求头(客户端与服务器交互)
客户端连上服务器后,向服务器请求某个web资源,称之为客户端向服务器发生了一个HTTP请求,一个完整的HTTP请求包括:一个请求行、若干请求头、及实体内容
示例:
GET/books/java.html HTTP/1.1 请求行(请求行用于描述客户端的请求方式、请求的资源名称、及使用的HTTP协议版本)
请求方式:GET(缺省,特点:显示提交,在URL地址后附带的参数是有限的,其数据容量通常不能超过1K; 提交数据显示在url栏中”?”后面);
POST(一般用于表单的提交,则可以再请求的实体内容中向发武器发送数据,传送的数据量无限制,隐式提交);
Accept:*/*
Accept-Language:en-ue
Connection:Keep-Alive 多个请求头(用于描述客户端请求的主机
Host:localhost ,及客户端的一些环境信息等)
Referer:thp://localhost/links.asp
User-Agent:Mozilla/4.0
Accept-Encoding:gzip,deflate
Hello world! 实体内容(一般是表单所提交的内容)
请求头的描述:
Accept: 告诉服务器,客户机所支持的数据类型,如:text/html,image/*; 就是说支持text的html,支持所有的image; 如为:*/*;表示什么都支持
Accept-Charset:ISO-8859-1 告诉服务器,客户机所采用的编码格式
Accept-Encoding:gzip,compress 告诉服务器,客户机所支持的压缩格式
Accept-Language: en-us,zh-cn 告诉服务器,客户机的语言环境
Host:localhost:8080 客户机要访问的主机
If-Modified-Since:Tue,11 jul 2000 18:23:51 GMT 告诉服务器,资源的缓存时间; 我们上一次浏览此网页的时间,发送给服务器,服务器用于比对原网站的更新时间,如果原网站在此时间内更新过数据,就重新返回数据,如没有更新就直接用客户机中缓存内的所缓存的网页
Referer:www.baidu.com 告诉服务器,他是从哪个资源来访问服务器的; 这里表示这是从百度访问的该资源(可以用于防盗链)
User-Agent:Mozilla/4.0(compatible;MSIE 5.5;Windows NT 5.0) 告诉服务器,客户机的软件环境(系统,及浏览器版本等)
Cookie: 客户机通过这个可以向服务器带数据
Connection:close/Keep-Alive 告诉服务器,当这个请求完毕后,是保持连接还是关闭连接; close是关闭连接, Keep-Alive是保持连接
Date:Tue,11 jul 2000 18:23:51 GMT 告诉服务器,客户机当前的时间
UA-CPU:X86 告诉服务器,windows的运行平台,如64位,32位等
If-None-Match:W/* 182-1303717494234
响应头(服务器与客户端交互)
一个http相应代表服务器向客户端回送的数据它包括:一个状态行、若干消息头、以及实体内容
示例:
HTTP/1.1 200 OK 状态行(状态行用于描述服务器对请求的处理结果)
Server:Microsoft-IIS/5.0
Date:Tue,11 jul 2000 18:23:51 GMT 多个消息头(消息头用于描述服务器的基本信息,
Content-Length:2291 以及数据的描述,服务器通过这写数据的描述信息,
Content-Type:text/html 可以通知客户端如何处理等一会它(服务器)回送的数据。)
Cache-control:private
<html>
<body></body> 实体内容(一般是网页代码)
</html>
响应头的描述
HTTP/1.1 200 OK 描述返回数据的协议版本号、状态码、原因叙述
状态码用于表示服务器对请求的处理结果,它是一个三位的十进制数.相应状态码分为5类,如:
100~199 表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程
200~299 表示成功接收请求并已完成整个处理过程, 常用200
300~399 为完成请求,客户需进一步细化请求.例如,请求的资源已经移动到一个新的地址,常用302、307、304(307和304表示服务器的资源位做修改,可以用客户机上的缓存,302表示去访问Location: 这个头所返回的地址)
400~499 客户端的请求有误,常用404(404表示服务器没有这个资源;403表示服务器拒绝你访问这个资源,请检查是否有权限访问)
500~599 服务器端出现错误, 常用500,(如同一个response对象同时使用字符流和字节流时服务器就会返回此异常,因为它们是互斥的)
Location:www.baidu.com (请求重定向)这个头配合302状态码使用,用于告诉客户端应该转到的地址
Server:Apache-Coyote/1.1 服务器通过这个头,告诉客户端服务器的类型
Content-Encoding:gzip,compress 服务器通过这个头,告诉客户端数据的压缩格式
Content-Length: 服务器通过这个头,告诉客户端压缩后数据的大小
Content-Type:text/html; charset=UTF-8 服务器通过这个头,告诉客户端数据的类型(如要查询要返回的文件类型的详细请查询conf/web.xml)
Last-Modified:Tue,11 jul 2000 18:23:51 GMT 告诉客户端,当前资源最后发布时间
Refresh:1;[url=’http://www.baidu.com’] 告诉浏览器隔多长时间刷新一次[如果在分号后面加上一个地址,表示隔多久时间跳转到这个网站]
Content-Disposition:attachment;filename=文件名 告诉浏览器通过这个头,告诉浏览器以下载方式打开
Transfer-Encoding:chunked 告诉浏览器文件的传送格式
Set-Cookie:ss=Q0=5Lb_nQ;path=/search
Etag:W/”7777-1242234904000” 缓存相关的头
Expires:-1 告诉浏览器,这个资源的缓存时间,-1或0表示不缓存; 如果要缓存,设置为要缓存到的时间
也是控制浏览器要不要缓存数据(浏览器的内核不同,所以响应头比较多)如果要缓存, 设置为要缓存到的时间 |
Cache-Control:no-cache
Pragma:no-cache
Connection:close/Keep-Alive 告诉浏览器请求完毕后是否断开连接
Date:Tue,11 jul 2000 18:23:51 GMT 告诉浏览器,服务器当前的时间
断点下载
请求头
Range头指示服务器只传输一部分Web资源.这个头可以用来实现断点续传功能,Range字段可以通过三种格式设置要传输的字节范围:
Range: bytes=1000-2000
表示请求服务器发送这个资源中1000字节到2000字节之间的内容
Range: bytes=1000-
表示请求服务器发送这个资源中1000字节以后的所有内容
Range: bytes=1000
表示请求服务器发送这个资源中最后1000字节的内容
响应头
Accept-Ranges: 这个头告诉浏览器这个资源是否支持Ranges(断点下载) 支持返回:bytes 不支持返回:none
Content-Ranges:1000-3000/5000指定了返回的Web资源的字节范围。这里表示返回的数据是1000-3000字节之间的内容,这个资源总共有5000个字节
JSP中的路径(java中的路径相对于src目录)
访问资源:
访问时,如果从客户端访问服务器的资源: /工程名/要访问的资源路径; 一般在前端超链接时,后台从定向时使用
访问时,如果直接在服务器端直接访问资源: /要访问的资源路径; 一般在服务器端转发时使用;
服务器访问文件:
在web开发中如果要获取到用到一个文件的位置,只有用绝对路径,比如创建文件流、文件对象、等。
通过 类加载器的 getClassLoader().getResource("相对路径").getPath();返回此文件的绝对路径;或者用getClassLoader().getResourceAsStream("相对路径”) 直接将文件加载到内存中,并返回一个字节输入流对象。这里的相对路径,相对的是WEB-INF/classes 目录。
如果一个要在一个jar包中访问这个jar包中的文件,那么只能将此文件加载到内存中,通过字节输入流对象来获取此文件的数据,因为文件在jar包中,不能获取到绝对路径。
Servlet开发
Servlet生命周期:
l Servlet一般只会在第一次访问时创建,然后一直停留在内存中,直到服务器停止才会被释放(或者这个web应用被删除时),每产生一次请求都会创建一个request和response对象但它们的生命周期很短,当请求和响应完成后就销毁。但Servlet对象只会创建一个
l 服务器启动时创建servlet,需加上<load-on-startup>标签(一般不会这样用的,加载框架文件时就是这样用的)
l servlet中尽量使用局部变量,如果要用static全局变量,并要对static变量进行修改的话,就要考虑线程安全问题了,解决线程安全需用线程同步代码块,同步代码块中的代码不能太多,不然会影响性能
<servlet>
<servlet-name>ServletDemo1</servlet-name>
<servlet-class>cn.webapp.ServletDemo1</servlet-class>
<!--加入了以下的标记,此servlet就会在服务器启动时创建-->
<load-on-startup>1</load-on-startup>//如果在web.xml配置文件中在当前Servlet注册标签中加上了当前标签,就会在服务器启动时创建当前的Servlet对象,并调用Servlet对象的init方法;中间这个1表示如果有多个Servlet对象要在服务器启动时加载时,这个值将控制Servlet的加载顺序;越小优先级越高
</servlet>
l Servlet创建的时候会调用init方法结束时会调destroy方法
新建一个Servlet类它必须继承HttpServlet类或GenericServlet类
继承GenericServlet类需重写service(ServletRequestarg0, ServletResponse arg1)这个方法
继承HttpServlet类需重写doGet或doPost方法是(修改模板路径:MyEclipse\plugins\com.genuitec.eclipse.wizards_11.0.0.me201303311935.jar中templates\Servlet.java文件中修改doGet和doPost)
地址的用法(最好都在前面加上/)
当给浏览器用时,是相对于当前的主机(要在最前面加上项目名称)
当给服务器用时, 是相对于当前的web应用
线程安全
//线程安全
/*SingleThreadModel接口是一个空的接口;如果实现了此接口就表示此对象时一个线程安全的对象;
当一个用户访问当前的Servlet后,下一个用户访问当前的Servlet时发现Servlet有用户,
就会创建出一个新的Servlet对象,这也是在一个服务器中创建两个Servlet的方法
(此方法不推荐使用;太消耗资源,因为Servlet创建后只有关闭服务器才会销毁)
*/
public
class ThreadSafe extends HttpServlet
implements SingleThreadModel{
public int i = 0;
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
i++;
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
System.out.println(i);
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
Servlet中的六大对象
ServletConfig(servlet配置对象及servlet对象域)
用于封装Servlet的配置信息
可以在工程下的web.xml配置文件中,注册Servlet标签中加上:<init-param>标签;
<servlet>
<servlet-name>ConfigDemo</servlet-name>
<servlet-class>cn.java.ConfigDemo</servlet-class>
<init-param><!—此标签的作用域只能在cn.java.ConfigDemo这个servlet中-->
<param-name>name1</param-name> <!-- 这是相当于通过"name1"可以映射到"张三" -->
<param-value>张三</param-value>
</init-param>
<init-param>
<param-name>name2</param-name> <!-- 这是相当于通过"name2"可以映射到"李四" -->
<param-value>李四</param-value>
</init-param>
</servlet>
获取方法
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//获取ServletConfig对象中的值(此值需要在web.xml中注册此servlet标签中加)
public class Test extends HttpServlet {
//privateServletConfig config;//方式一所需定义的全局变量
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//第一种方式获取值
//输出config对象中name1和name2中所映射的值
// System.out.println(config.getInitParameter("name1"));
// System.out.println(config.getInitParameter("name2"));
/*
//第二种方式获取(用第二种方式时不能重写init(ServletConfig config)方法)
//先通过this.getServletConfig()获取到config对象,再通过getInitParameter("name1")获取到name1和name2中所映射的值
System.out.println(this.getServletConfig().getInitParameter("name1"));
System.out.println(this.getServletConfig().getInitParameter("name2"));
*/
/*
//第三种方式,使用迭代(获取全部的config值)
ServletConfigconfig1 = this.getServletConfig();//获取config对象
Enumeration en =config1.getInitParameterNames();//获取config1对象中所有的name,并返回一个列表
while(en.hasMoreElements()){//用这个列表的迭代器进行遍历
String key = (String) en.nextElement();
String value =this.getServletConfig().getInitParameter(key);
System.out.println(value);
}
*/
//第四种直接获取
System.out.println(this.getInitParameter("name1"));
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
/*
//第一种方式:重写HttpServet父类GenericServlet中的init(ServletConfig config)方法
//此方法会在服务器启动时调用,并将config对象传入进来
@Override
public void init(ServletConfig config)throws ServletException {//获取config对象
// TODO Auto-generated method stub
this.config=config;
}
*/
}
servletContext(代表一个web应用(域))此对象的生命周期为:当web应用发布时创建,卸载web应用时销毁,如果在servlet中要对servletContext域对象中的key值进行更改的话,需考虑线程安全问题
ServletContext.removeAttribute(Stringkey); 删除servletContext对象域中键值的映射关系,不然此键值会一直存活到服务器关闭,会浪费资源
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表”当前web应用”。
SerletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过servletConfig.getServletContext方法获得ServletContext对象
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,所以多个Servlet可以通过servletContext对象实现数据共享(ServletContext对象通常也被称之为context域(域就是一个范围,Context域是一个应用范围的域)对象)
n 获取web应用的参数(配置数据库)
<context-param><!—这是配置整个web应用初始化参数的标签;一个web应用中可以有多个初始化参数的标签-->
<param-name>name</param-name>
<param-value>haoren</param-value>
</context-param>
Stringsex = this.getServletContext().getInitParameter("name");//取出context-param标签中的值;也可以用this.getServletContext().getInitParameterNames();方法获取全部的key;然后再迭代取出value(在ServletConfig对象中有讲过取出value的方式)
System.out.print(sex);
n 数据共享
ServeltDemo1:
this.getServletContext().setAttribute("address", "chengdu");//存入servletContext对象中
ServeltDemo2:
String address =(String) this.getServletContext().getAttribute("address");//取出servletContext对象中的值
n 利用ServletContext对象读取源文件
读取properties文件
//第一种方式
ServletContext servletContext = this.getServletContext();//获取到servletContext对象
InputStream inStream = servletContext.getResourceAsStream("/config.properties");//把/config.properties资源作为一个输入流返回(这里"/" 路径指的是web工程的根目录)
//如果此处的路径写的是相对路径,相对的是Tomcat的bin目录,因为Tomcat的是在此目录中启动的
Properties properties = new Properties();//创建一个properties对象(properties对象中底层是用map集合维护的)
properties.load(inStream);//properties对象中提供了load方法,从流中获取properties文件的值
String name = properties.getProperty("name");//获取properties对象中key是name的value
String url = properties.getProperty("url");//获取properties对象中key是url的value
System.out.println(name+" "+url);
//第二种方式(通过硬盘上的绝对路径访问config.properties文件),此方法可以获取到文件的名称
ServletContext servletContext = this.getServletContext();//获取到servletContext对象
String path = servletContext.getRealPath("/config.properties");//会返回这个路径在硬盘上的绝对路径;这里会返回
//E:\practice\JSP\practice2\Tomcat7.0\webapps\servletContext\config.properties这个路径
String fileName = path.substring(path.lastIndexOf("\\")+1);//这样可以获取到文件的名称;在下载时需要用到
System.out.println("文件名称为:"+fileName);//打印这个文件名称
FileInputStream in = new FileInputStream(path);//有了绝对路径后就可以创建 文件字节流了
Properties properties = new Properties();//创建一个properties对象(properties对象中底层是用map集合维护的)
properties.load(in);//properties对象中提供了load方法,从流中获取properties文件的值
in.close();//关闭流
String name = properties.getProperty("name");//获取properties对象中key是name的value
String url = properties.getProperty("url");//获取properties对象中key是url的value
System.out.println(name+" "+url);
//第三种方式(通过一个普通类加载config.properties文件的方式)
//如果在一个普通类中用ServletContext对象来获取;就是前台应用耦合了后台应用;所以我们应该避免这个用法
//在一个普通类中读取config.properties文件;
public class Test4 {
public static void print() throws IOException {
/*
//此方法获取的输入流(in)不能实时更新(因为是类加载器加载器只加载一次到内存中)
InputStream in = Test4.class.getClassLoader().getResourceAsStream("../../config.properties");
//这里相对的是Tomcat中webapps\servletContext\WEB-INF\classes这个文件夹(如果用类加载器加载文件,这个文件不能太大)
*/
//此方法获取的输入流(in)可以实现实时更新(此方法是得到绝对路径后在创建输出流)
URL url1 = Test4.class.getClassLoader().getResource("../../config.properties");//获取到这个文件的URL
String path = url1.getPath();//通过url获取到这个config.properties文件的绝对路径
InputStream in = new FileInputStream(path);//通过这个绝对路径来创建输入流
Properties properties = new Properties();//创建一个Properties对象
properties.load(in);//properties对象中提供了load方法,从流中获取properties文件的值
in.close();//关闭流
String name = properties.getProperty("name");//获取properties对象中key是name的value
String url = properties.getProperty("url");//获取properties对象中key是url的value
System.out.println("Test4:"+name+" "+url);
}
public static void main(String[] args) {
try {
Test4.print();
} catch (IOException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
}
request(请求头对象)用于获取客户端传入的数据
获取表单中的值:
//获取表单提交的数据
public class Test extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)//request:请求;response:响应
throws ServletException, IOException{
response.setContentType("text/html; charser=utf-8");//设置浏览器的编码格式
response.setCharacterEncoding("utf-8");//设置响应回传数据的编码格式
request.setCharacterEncoding("utf-8");//设置请求数据的编码格式
//获取表单的参数,除多选框外,其它表单的取值方式都一样
String username = request.getParameter("username");
String password = request.getParameter("password");
String sex = request.getParameter("sex");
String country = request.getParameter("country");
String [] enjoy = request.getParameterValues("enjoy");//多选框用getParameterValues(String arg0)这个方法,并会返回一个String []的数组
String remark = request.getParameter("remark");
PrintWriter out = response.getWriter();//获取到响应对象的输出流
out.print("你的用户名为:"+username+";你的密码为:"+password+";你的性别为:"+sex+";你的国籍为:"+country+";备注为:"+remark);
out.println(";你的爱好为:");
for(int i=0; i<enjoy.length; i++){
out.print(enjoy[i]+" ");
}
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
//解决提交表单方式为get时出现的乱码问题
public class getMessyCodeextends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException{
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
String text = request.getParameter("text");//获取提交表单的参数
String temp = new String(text.getBytes("ISO8859-1"),"UTF-8");//将ISO8859-1的参数,转换为字节数组后,再传入一个新的字符串,并重新指定编码格式
response.getWriter().print("你提交的为:"+temp);//将数据传送给响应对象的输出流
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException{
doGet(request, response);
}
}
通过request对象实现转发:
/*转发
请求重定向与转发的区别
请求重定向: 是向客户端发回一个网址,让浏览器重新去访问(向服务器发出了两次请求;并且网址会变。重定向完成后,再刷新,刷新的是重定向后的网页)
转发: 是通过服务器直接转发到某一个网址(向服务器只发出一次请求;网址不会变。当转发完成后,再刷新,由于网址没变,刷新的还是没转发前的资源,并会再次转发(提交数据时要注意));
总结:所以一般不用请求重定向,一般用转发完成,因为请求重定向会向服务器发出两次请求;转发时要避免因刷新而导致数据重复提交的问题;
*/
//通过request对象实现转发,
public class Test1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException,IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//获取表单中的值
String text = request.getParameter("text");
//向request对象域中存入值
request.setAttribute("text", text);
//如果用转发时不能关闭response对象的流,不然调用forward方法时会抛出异常(但可以设置response响应对象的头,并且转发后还是有效的)
// PrintWriter out = response.getWriter();//Error
//out.print(“asdf”); //Error
//out.close();//Error
//转发到Test12.jsp,并将request, response对象提交过去(最好在跳转完成之后return)
request.getRequestDispatcher("/Test12.jsp").forward(request, response);
return;
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
通过request对象获取请求头的请求资源和所有请求头的值
public class Test2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//获取请求方式(结果为:GET或POST等)
System.out.println(request.getMethod());
//当后缀名为/com/*时,都会访问到此servlet(我输入的是:http://localhost:8080/request/com/1.html )
//获取请求资源的URI(结果为:/request/com/1.html )
System.out.println(request.getRequestURI());
//获取请求资源的URL(结果为:http://localhost:8080/request/com/1.html )
System.out.println(request.getRequestURL());
//URL代表的是互联网上面的资源地址
//URI代表的是任意一个资源的地址(可以理解为某个服务器上的地址)
//通过request请求头对象,获取一个头的值(这里获取的是客户端所支持的压缩格式)
System.out.println(request.getHeader("Accept-Encoding"));
System.out.println("获取所有的头");
//通过request请求对象得到所有头的名称
Enumeration<String> headerNames =request.getHeaderNames();
while(headerNames.hasMoreElements()){//判断此枚举是否还有下一个值
String headerName = headerNames.nextElement();//返回它的下一个值
System.out.println(headerName+":"+request.getHeader(headerName));
}
//request.getHeaders("");//当一个头设置了两个值,就用此方法获取
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
通过request对象防止盗链
//防止盗链
public class Test5 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//防止重复提交,此处是验证提交的次数
System.out.println("提交的次数");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
//获取来访者的url
String path = request.getHeader("referer");
//获取本服务器的主机名
String server = "http://"+request.getServerName();
//判断path的前端是否是server(也就是判断主机名是否相同)
if(path != null && path.startsWith(server)){
//将path和server转入request对象域
request.setAttribute("path", path);
request.setAttribute("server", server);
//转发至Test52.jsp
request.getRequestDispatcher("/Test52.jsp").forward(request, response);
}else{
//不转发
response.getWriter().print("不是本网页的连接");
}
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
比较常用的方法:
request.getRequestURL(); 获取完整的URL
request.getRequestURI(); 只获取请求资源的地址
request.getQueryString(); 获取在地址栏中’?’号后面的所有字符串(所以它只针对get的提交方式有效)
request.getRemoteAddr(); 获取客户机的IP地址
request.getRemoteHost(); 获取客户机的完整主机名(但是客户机的IP要在DNS上注册后才能显示主机名,不然还是显示IP地址)
request.getRemotePort(); 获取浏览器所使用的端口号(当浏览器关闭后再重新打开时端口号会随机变化)
request.getLocalAddr(); 获取WEB服务器的IP地址
request.getLocalName(); 获取WEB服务的主机名
request.getServerName(); 获取WEB服务器的主机名
request.getServerPort(); 货物WEB服务器的端口
request.getMethod(); 获取浏览器的请求方式
request.getHeader(headerName); 根据相应的响应头名字来获取响应的值
request.getHeaders(headerName); 如果同一个响应头有多个值,就用此方法获取,并返回一个Enumeration
request.getContextPath(); 得到当前的工程名:如:/dy19
request.getServletContext().getContextPath(); 得到当前的工程名:如:/dy19
request.getServletPath(); 得到web配置文件<url-pattern>/servlet/UploadServlet</url-pattern>此标签中的值
URL与URI的区别
www.sina.com/news/1.html 这就是一个URL
news/1.html 这就是一个URI
URL代表的是互联网上面的资源地址
URI代表的是任意一个资源的地址(可以理解为某个服务器上的地址)
通过request对象获取客户端所提交的数据(通过get或post等,提交的数据)(在上面request对象的第一个知识点中已做过笔记)
//通过request对象获取客户端所提交的数据(通过get或post等,提交的数据)(先要检查用户提交的数据中是否有str这个key)
public class Test3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
System.out.println("-------------------直接通过key获取提交的value--------------------------------");
/*
//客户端用get或post方式提交的数据,都可以用以下方式获取
//如我们在访问时在后面加上数据如:http://localhost:8080/request/servlet/Test3?name=abc&password=123
//或者用表单提交都能获取到
String value = request.getParameter("name");//获取指定名称的值
if(value!=null && !value.trim().equals("")){//判断是否是没有输入或输入的全部为空格(检查提交的数据合格再使用)
System.out.println("name="+value);//value.trim()方法会删除字符串两边的空格,如果字符串全部都是空格,那么返回的字符串相等于new Stirng("");
}
System.out.println("-------------------先获取到所有请求数据key,再通过迭代取出请求的值--------------------------------");
//获取所有的数据的key,再通过key遍历所有的值
Enumeration<String> names =request.getParameterNames();
while(names.hasMoreElements()){//判断此枚举是否还有下一个key
String name = names.nextElement();//获取下一个key
String value1 = request.getParameter(name);//通过key遍历所有的value
System.out.println(name+"="+value1);
}
System.out.println("--------------------获取同一个key有两个value的方法-------------------------------");
//如果提交的数据key有多个相同如:http://localhost:8080/request/servlet/Test3?str=abc&str=123
//这里的str有两个值,就用request.getParameterValues("str");来获取
String[] values = request.getParameterValues("str");//返回str中的多个值
for(int i=0; values!=null && i<values.length; i++){//遍历这些值(先要检查用户提交的数据中是否有str这个key)
System.out.println(values[i]);
}
*/
System.out.println("----------------------将请求的数据以一个map集合返回-----------------------------");
//将提交的数据返回到一个Map集合中提交方式为:http://localhost:8080/request/servlet/Test3?name=abc&name=111&password=123
Map<String,String[]> map =request.getParameterMap();
//遍历输出这个map集合
//获取map中所有的key,以Set集合的方式返回
Set<String> set = map.keySet();
//获取set集合的迭代器
Iterator<String> it = set.iterator();
//遍历这个迭代器
while(it.hasNext()){//判断迭代器是否还有下一个key值
String key = it.next();//获取下一个key
//输出key
System.out.print(key);
//通过key返回所对应的value;返回一个数组,因为同一个key可能有两个以上value
String [] values = map.get(key);
//遍历输入这个values
for(String s:values){
System.out.print(":"+s);
}
//换行
System.out.println();
}
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
通过request.getRequestDispatcher("").include(request,response);方法实现多个页面动态包含(也就是将多个页面的数据同时传给前台)
public class Test6 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//在response对象的返回数据的尾部加上了/Test61.jsp中的内容
request.getRequestDispatcher("/Test61.jsp").include(request, response);
//在response对象的返回数据的尾部添加了我是servlet<br/>
response.getWriter().print("我是servlet<br/>");
//在response对象的返回数据的尾部添加了/Test62.jsp中的内容
request.getRequestDispatcher("/Test62.jsp").include(request, response);
/*
所以输出的结果为:
This is my Test61.JSP page.
我是servlet
This is my Test62.JSP page.
*/
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
response(响应头对象)用于向客户端返回数据
根据response对象向浏览器发送数据
//通过response对象的字节流返回数据
response.setHeader("Content-type","text/thml;charset=UTF-8"); //设置头的值,这是是设置Content-type告诉浏览器回送的数据,为html格式,它编码格式为UTF-8
response.setContentType("text/html;charser=utf-8");//设置ContentType(返回数据类型)响应头的数据类型为html格式,编码格式为utf-8
//也相当于是告诉浏览器以什么样的编码格式打开这段数据
response.setCharacterEncoding("utf-8");//设置response对象的编码格式(response对象里面也有对数据进行转码,所以也需要编码格式)
request.setCharacterEncoding("utf-8");//设置request对象的编码格式(request对象里面也有对数据进行转码,所以也需要编码格式)
PrintWriter out = response.getWriter();//获取到字符输出流
out.write("中国");//向字符流中写入数据
out.flush();//移除流中的缓存
out.close();//关闭流
//通过response对象的字符流返回数据
public class Test2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");//设置ContentType(返回数据类型)响应头的数据类型为html格式,编码格式为utf-8
//也相当于是告诉浏览器以什么样的编码格式打开这段数据
response.setCharacterEncoding("utf-8");//设置response对象的编码格式(response对象里面也有对数据进行转码,所以也需要编码格式)
request.setCharacterEncoding("utf-8");//设置request对象的编码格式(request对象里面也有对数据进行转码,所以也需要编码格式)
String data = "中国";
PrintWriter out = response.getWriter();//获取到字符输出流
out.write(data);//向字符流中写入数据
out.flush();//移除流中的缓存
out.close();//关闭流
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
提示: 字符流和字节流是互斥的,如果同一个response对象,用了字符流就不能用字节流了,最好都用字节流(从response或request对象中获取的流不需要关闭,因为当servlet对象在销毁时会自动检测这些流是否调用了close方法,然后关闭,但是如果是我们自己new的流必须关闭它,不然会浪费资源)
从浏览器中下载web应用中的资源
String path = this.getServletContext().getRealPath("/image/美女.zip");//获取这个文件的绝对路径
String fileName = path.substring(path.lastIndexOf(File.separator)+1);//获取到这个文件的名字
fileName = URLEncoder.encode(fileName, "UTF-8");//如果为中文名字需要进行转码
response.setHeader("Content-Disposition", "attachment;filename="+fileName);//设置响应头Content-Disposition的值为attachment表示以下载方式打开,后面跟文件名字
FileInputStream in = new FileInputStream(path);//创建一个字节输入流,并指向这个图片
byte[] buffer = new byte[1024];//创建一个缓冲字节数组
int len=0;//定义一个缓冲大小的变量
OutputStream out = response.getOutputStream();//获取response的字节输出流
while((len=in.read(buffer)) > 0){//判断从输入流中获取的字节数,如果小于或等于0表示没有读取到字节
out.write(buffer,0,len);//将buffer中的缓存写入到 response的字节输出流中
}
//关闭流
in.close();
out.flush();
out.close();
请求重定向
/*请求重定向
请求重定向与转发的区别
请求重定向: 是向客户端发回一个网址,让浏览器重新去访问(向服务器发出了两次请求,并且网址会变)
转发: 是通过服务器直接转发到某一个网址(向服务器只发出一次请求,网址不会变)
总结:所以一般不用请求重定向,一般用转发完成,因为请求重定向会向服务器发出两次请求;
*/
public class Test7 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
/*
response.setStatus(302);//设置response对象中状态行的状态码为302,表示请求重定向
response.setHeader("Location","/response/welcome.jsp");//Location此头为请求重定向头,重定向到welcomem.jsp文件
*/
//此方法也可以直接重定向到welcomem.jsp文件
response.sendRedirect("/response/welcome.jsp");
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
设置浏览器定时反复刷新页面或定时重定向
public class Test5 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
/*
//不停的刷新
response.setHeader("refresh","3");//表示每隔3秒钟就访问一次此servlet
//产生一个随机数并通过字符流返回
PrintWriter out = response.getWriter();
out.write(new Random().nextInt()+"");
*/
/*
//3秒钟以后可以重定向到其他的网页
response.setHeader("refresh", "3;url=‘http://www.baidu.com‘");//设置refresh(刷新头,设置其值,表示3秒钟后跳转至百度)
PrintWriter out = response.getWriter();
out.write("本网页将在3秒钟后重定向到百度;如果未跳转,情点击<a href=‘http://www.baidu.com‘>百度</a>");
*/
//最实用的方式,用jsp文件控制页面跳转
//通过meta表示设置jsp文件response对象的响应头,隔3秒跳转至/response/welcome.jsp网页
String str = "<meta http-equiv=‘refresh‘ content=‘3;url=/response/welcome.jsp‘>" +
"本网页将在3秒钟后跳转;如果未跳转,情点击<a href=‘/response/welcome.jsp‘>超链接</a>";
//将数据存入ServletContext应用域对象中
this.getServletContext().setAttribute("str", str);
//跳转至MyJsp网页;但先要在web工程下建一个MyJsp.jsp文件,并在body标签中用java代码获取ServletContext应用域对象str中的value如:
//<%//获取servletContext应用域对象中的值String str =(String)application.getAttribute("str"); //通过流向当前的位置输出数据 out.write(str);%>
this.getServletContext().getRequestDispatcher("/MyJsp.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
请求重定向时,通过url方式将数据提交给Test41.jsp
Servlet中的代码
//请求重定向时,通过url方式将数据提交给Test41.jsp
public class Test4 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
/*
//第一种方式:
//数据
String str = new String("中国");
str = new String(str.getBytes("utf-8"),"ISO-8859-1");
//请求重定向,并将数据传过去
response.sendRedirect("/request/Test41.jsp?str="+str);
*/
/*
//第二种方式:
String value = new String("四川");//数据
String value1 = URLEncoder.encode(value, "UTF-8");//加码
response.sendRedirect("/request/Test41.jsp?value="+value1); //如果直接在url后面跟中文数据,需要对中文进行转码
*/
//加码与解码
String temp = new String("中文数据");
//加码(就是将temp这个UTF-8编码的字符串转换为ISO-8859-1;当某些时候进行中文数据传输时,需要进行加码与解码)
String temp1 = URLEncoder.encode(temp, "UTF-8");
//也就是说加码过后,将temp的值的存储格式变为了默认的ISO-8859-1编码格式
System.out.println(temp1);//打印为:%E4%B8%AD%E6%96%87%E6%95%B0%E6%8D%AE
//解码(将加码过的字符串(ISO-8859-1),转换为对应的编码格式)
String temp2 = URLDecoder.decode(temp1, "UTF-8");
System.out.println(temp2);//打印为:中文数据
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
Jsp中的代码
<body>
<!--第一种方式 :< %=new String(request.getParameter("str").getBytes("ISO-8859-1"),"UTF-8") %> -->
<!-- 第二种方式的解码与第一种一样(只限于servlet传值给jsp时) --><%=new String(request.getParameter("value").getBytes("ISO-8859-1"), "UTF-8") %>
</body>
通过修改服务器配置文件,来设置服务器默认的编码格式(但此方法一般不用)
在apache-tomcat-7.0.6\conf\servlet.xml文件中的<Connector port="8080"protocol="HTTP/1.1" connectionTimeout="20000"redirectPort="8443" />标签末尾添加一个属性: URIEncoding=”UTF-8”;
或者在末尾添加一个useBodyEncodingForURI=”true”;这个属性是设置URL地址的默认编码格式
控制浏览器对数据的缓存
public class Test6 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
/*
//设置浏览器不要缓存(为了使更多的浏览器兼容,所以设置的头比较多)
response.setHeader("expires","-1");
response.setHeader("cache-Control","no-cache");
response.setHeader("pragma","no-cache");
*/
//设置浏览器缓存数据
//设置数据缓存的时间为当前时间加上1小时
response.setDateHeader("expires", System.currentTimeMillis()+(1000*3600));//1000毫秒*3600 ==一小时
String data = new String("aaaaaaaaa");//数据
response.getWriter().write(data);//data的数据会被保存1小时
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
将数据压缩后才传给客户端
public class Test8 extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponseresponse)//request为请求;response为响应头
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
String str = new String("要压缩的数据");
//创建一个字节缓存输出流
ByteArrayOutputStream bout = newByteArrayOutputStream();
//把输出流作为参数,创建一个gzip压缩对象,将压缩后的数据放到缓存流中
GZIPOutputStream gout = new GZIPOutputStream(bout);
//将要压缩数据的字节数组传入压缩对象的write方法中进行压缩
gout.write(str.getBytes());
//关闭压缩
gout.close();
//从缓存输出流中获取压缩后的byte数组数据
byte[] gzip = bout.toByteArray();
//关闭缓存输出流
bout.close();
//设置Content-Encoding(服务器返回数据的压缩格式)响应头的返回值
response.setHeader("Content-Encoding", "gzip");
//设置Content-Length(服务器返回数据压缩后的长度)响应头的返回值
response.setHeader("Content-Length", String.valueOf(gzip.length));
//设置response的传出数据
response.getOutputStream().write(gzip);
//传送过去后浏览器会自动解压显示
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
cookie(缓存数据)
把数据缓存在客户端的硬盘上,一个Cookie只能表示一种信息,它至少含有一个键值
一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个浏览器也可以存储多个WEB站点提供的Cookie
可以调用Cookie对象的setMaxAge()方法设置此Cookie的存储时间即生命周期(单位秒),设置为0表示删除该Cookie;如果不设置保存时间,此Cookie只会保存在用户的内存中,当浏览器关闭释放内存,此Cookie会被删除,删除cookie时,path必须一致,否则不会被删除
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB
输出用户访问此servlet的次数
public class Test1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//获得本web应用(/cookie)存储在客户端的cookie
Cookie [] cookies = request.getCookies();
//标记访问的次数
int time = 1;
//判断本web应用是否在客户端存有cookie
if(cookies!=null && cookies.length>0){
//遍历这些cookie
for(int i=0; i<cookies.length; i++){
//判断是否是numberOfTimes(次数)这个key
if("numberOfTimes".equals(cookies[i].getName())){
//获得numberOfTimes这个key的值(上次访问的次数)
String value = cookies[i].getValue();
//并删除当前的Cookie
cookies[i].setMaxAge(0);//将存储时间设置为0表示删除此cookie
//将获取的值转换为int类型
int temp = Integer.valueOf(value);
//上次访问的次数加上1,并赋值给time标记
time = ++temp;
}
}
}
//创建一个新的cookie并把numberOfTimes的值设为本次的次数
Cookie cookie = new Cookie("numberOfTimes",time+"");
//设置cookie的保留时间,单位为秒
cookie.setMaxAge(3600);
//设置标识(表示当前cookie是/cookieWEB工程的)
cookie.setPath("/cookie");
//向浏览器返回此cookie(保存在客户机的硬盘上)
response.addCookie(cookie);
//打印用户访问的次数
response.getWriter().print("你是第"+time+"次访问本servlet");
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
session(会话对象)
把数据缓存在服务器端
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),也就是说一个浏览器独占一个session对象(默认情况下).因此,在需要保存用户数据时,服务器可以把用户数据写到用户浏览器独占的session对象中,当用户用浏览器访问其他程序时,其他程序可以从用户的session中取出该用户的数据,为用户服务
Session对象是由服务器创建,我们可以调用request对象的getSession方法得到用户的session对象
Session与cookie的区别:
Cookie是把用户的数据写在用户本地硬盘上
Session是把用户的数据写到服务器为每个用户单独分配的session对象中
当session对象超过30分钟(默认)无访问,就销毁此session对象;调用session对象的getLastAccessedTime()方法返回此session对象最后的访问时间(时间戳)
Session对象的生命周期设置
Session也是依附于cookie的,当服务器新建了一个session对象,就会生成一个id,然后通过cookie将此id返回到客户端上id的key为”JSESSIONID”,然后当用户下次来访问此web应用时是带着session对象的id来的,通过此id我们就可以找到与之对应的session对象 ; 但此cookie对象没有设置保存时间,所以并没有写到客户机的硬盘上,而是在客户机的内存中,当用户关闭浏览器时,此cookie会被自动释放,再打开浏览器就访问不到原有的session对象了; 为了解决此问题我们需要重写一个key为”JSESSIONID”,并设置了保存时间(一般与在web.xml配置文件中配置了的时间一致)的cookie 覆盖掉原有的cookie;session的id可以通过session对象的getId()方法获得; 作用域:此session对象只在本工程中有效
在WEB工程的配置文件web.xml文件中添加一个标签: <session-config>
<session-timeout>10</session-timeout>
</session-config>
表示session对象在10分钟无人使用的情况下,将被自动销毁
也可以直接调用session对象的invalidate();方法,立即销毁此用户的session对象
通过一个servlet向session对象中存值,一个servlet输出session对象中的值的例题:
Jsp中的代码:
<body>
<a href="servlet/Test1">存</a><br/>
<a href="servlet/Test2">取</a>
</body>
存值的servlet的代码:
//用户访问此Test1 servlet时,向此用户的session对象中存入一个值,当用户访问Test2 servlet时取出用户的session对象所存入的值
//也就是说,当我们第一此获取用户的session对象时,服务器如果没有给此用户创建session对象,那么就给用户新建一个
//session对象的生命周期是:服务器第一次获取用户的session对象 到 此session对象30分钟(默认)不用时就自动释放
public class Test1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//获取在服务器中获取用户的session对象,如果有就返回,没有就创建一个新的session对象再返回
HttpSession session = request.getSession();
//向此用户的session对象总存入值
String str = "hello";
session.setAttribute("data", str);
//打印此值
response.getWriter().print("存入了:"+str);
/*
Session也是依附于cookie的,当服务器新建了一个session对象,就会生成一个id,
然后通过cookie将此id返回到客户端上id的key为”JSESSIONID”,然后当用户下次来
访问此web应用时是带着session对象的id来的,通过此id我们就可以找到与之对应的session对象 ;
但此cookie对象没有设置保存时间,所以并没有写到客户机的硬盘上,而是在客户机的内存中,
当用户关闭浏览器时,此cookie会被自动释放,再打开浏览器就访问不到原有的session对象了;
为了解决此问题我们需要重写一个key为”JSESSIONID”,并设置了保存时间(一般与在web.xml配置文件中配置了的时间一致)
的cookie 覆盖掉原有的cookie;session的id可以通过session对象的getId()方法获得;
*/
//下面就是覆盖原有cookie的代码
//获取到session对象的id
String sessionId = session.getId();
//创建一个新的cookie对象(并设置一个键值,key必须为JSESSIONID才能覆盖原有的cookie,value为当前session对象的id)
Cookie cookie = new Cookie("JSESSIONID",sessionId);
//设置cookie的存活时间(一般和web.xml配置的session对象的存活时间一致,缺省为30分钟)
cookie.setMaxAge(1800);//存活时间为30分钟,不调用此方法设置值,此cookie将不会被写入客户机的硬盘上
//设置此cookie的站点
cookie.setPath("/session");
//返回给客户机
response.addCookie(cookie);
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
取值的servlet的代码:
//用户访问取出Test1 servlet存在session对象中的值
public class Test2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//取出此用户的session对象
HttpSession session = request.getSession(false);//传入一个false,表示此方法只获取session对象,并不创建session对象(当没有session对象时)
//HttpSessionsession = request.getSession();//此方法虽然也可以,但如果此用户没有session对象时,此处并不需要创建一个session对象,所以最好用上面这种方法,(可以增加效率)
//再取出此session对象中的data的值
String str = "no";
//判断是否获取到了session对象
if(session != null){
str = (String)session.getAttribute("data");
}
//打印此值
response.getWriter().print("取出了:"+str);
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
当用户禁用cookie后怎么解决传入session对象的id问题(通过url想servlet带入session对象的id,但用户关闭浏览器后将失去session对象,无法避免(上面用cookie可以避免))
上面的servlet中的代码不变 jsp中代码为:
<body>
<%
//当访问首页时就创建此session对象
request.getSession();
//encodeURL()是本应用级别的,encodeRedirectURL()是跨应用的
//此方法会在servlet/Test1后面自动加上session对象的id(重要:如果在servlet重定向跳转时,这里重写url的方法是:response.encodeRedirectURL("servlet/Test1")
Stringurl1 = response.encodeURL("servlet/Test1"); //这里是重写在jsp中跳转页面的url 所以用response.encodeURL("servlet/Test1"))
//此方法会在servlet/Test2后面自动加上session对象的id
Stringurl2 = response.encodeURL("servlet/Test2");
%>
<a href="<%=url1 %>">存</a><br/>
<a href="<%=url2 %>">取</a>
</body>
还有在做登录时需要用到session对象,当用户登录成功后就把user对象存入session中,当注销时就需要调用session对象的invalidate();方法,立即销毁此session对象,也可用session.removeAttribute("user");值删除指定的映射关系(推荐)
base64编码
如果一个字符串用dase64编码, 会将这个字符串的二进制代码的3个字节转换为4个字节,在最高位补0的形式;
如 :
10100110 11110010 00010100
dase64编码后:
00101001 00101111 00001000 00010100
这样每个字节的第值就在0-63之间了,再经过dase64的码表转换过来后就是只有包括了键盘上的所有字符
在java中获得dase64编码对象
sun.misc.BASE64Encoder base64Encoder = new sun.misc.BASE64Encoder();//获得dase64编码对象
String str = base64Encoder.encode(byte[] arg0);//传入一个byte[]数组,将将这个数组用dase64编码,并返回一个新的字符串
在java 中获得dase64解码对象
sun.misc.BASE64Decoderbase64Decoder = new BASE64Decoder();//创建解码对象
byte[] buff =base64Decoder.decodeBuffer(str);//将要解码字符串传入,返回一个byte数组
String token = new String(buff);//通过这个byte数组创建一个字符串
一般在MyEclipse不能用,需用以下方法进行设置
只需要在projectbuild path中的JRE System Library中的Accessible,再重新添加库JRE System Library 的Accessible,Rule Pattarn为**,重新编译后就一切正常了(如下图)
JSP开发
jsp是SUN公司定义的一种用于开发动态web页面的计算,和html不同的是,jsp允许在页面中编写java代码及在页面中获取request、response等对象实现与浏览器交互,所以jsp也是一种web资源开发技术
jsp文件在访问时服务器会将其转换为servlet的java文件(转换后文件的位置Tomcat7.0\work\Catalina\localhost\工程名\org\apache\jsp;
一、jsp中的代码转换后,会被放到此java文件中的_jspService( )方法中
二、因为第一次访问一个jsp文件时,会将此jsp翻译成servlet,所以 jsp文件在第一次访问时会很慢,第二次访问时引擎(就是将jsp转换为servlet的程序)发现jsp文件没有改变就不在翻译,直接访问此servlet所以速度快
三、并且JSP引擎在调用_jspService( )方法时,会传入9个隐式对象(下面JSP隐式对象中讲)
JSP模版元素
JSP中的模板元素定义了网页的基本骨架,即定义了页面的结构和外观
JSP表达式
JSP表达式用于将程序数据输出到客户端 语法<%=变量或表达式 %> 也就是直接在此位置输出数据
服务器会将此语法直接转换为 out.print(变量或表达式);
JSP脚本片断
就是在jsp文件中嵌套<% java代码 %>这就是脚本片断, 可以出现在jsp中的任意位置,脚本片断中只能出现java代码;
Jsp中可以出现多个脚本片断,在两个脚本片断之间可以嵌套文本、HTML标签及其他jsp元素
多个脚本片断中的代码可以相互访问,单个脚本片断中的java语句可以是不完整的,但是多个脚本片断组合后的结果必须是完整的java语句,例如:
<%
for(inti=0; i<5; i++){
%>
<p>hello world!</p>
<%
}
%>
这表示把<p>hello world!</p>输出5遍
JSP声明
如果在用以下方式定义脚本片断,表示把此脚本片断中的代码放到_jspService( )方法的外面去,例如:
<%!
Publicstatic void fun(){
System.out.println(“helloworld!”);
}
%>
,如果不将此java代码放到_jspService( )方法的外面去,相当于在_jspService()方法的内部定义了一个fun()方法,这样java语法是不允许的;这样我们就也可以在JSP中定义一个成员变量或静态域等功能了
JSP注释
在JSP中的注释方法是<%--被注释的内容 --%>
此方法注释是,当服务器将jsp文件转换为servlet时,会丢弃此注释,并不会向用户返回此注释的代码;如果用<!- - 注释的内容- -> 此方法注释,服务器会将此代码发送给客户,会增加网络数据;用此<!---->种注释,被注释的javaBean标签也会被servlet翻译,所以在jsp中尽量注意
JSP模板更改
修改模板路径:MyEclipse\plugins\com.genuitec.eclipse.wizards_11.0.0.me201303311935.jar中templates\jsp\Jsp.vtl文件中修改
JSP指令
Jsp指令是为了JSP引擎(就是将jsp转换为servlet的程序)而设计的,它们并不是直接生产任何可见输出,而只告诉引擎如何将jsp页面翻译成servlet,指令只是用于传给引擎,并不会发送给客户机,在jsp2.0中共定义了三个指令:
page指令
语法<%@ 指令 属性名=”值” %>
示例:
<%@ page contentType=”text/html;charset=gb2312” %>
表示告诉引擎 此文件的格式及编码
多个属性可以写在一个指令中(用空格分隔),也可以写在多个指令中
示例:
<%@page contentType=”text/html; charset=gb2312” %>
<%@page import=”java.Util.Date” %>
和
<%@ pagecontentType=”text/html; charset=gb2312” import=”java.Util.Date” %>
是一样的效果
page指令和存放在jsp中的任意位置,但最好放在JSP页面的起始位置
page指令的属性
language=”java” 表示在JSP脚本片断中的代码是java代码
extends=”package.class” 表示此jsp翻译成servlet后继承谁(一般不改)
import=”java.util.Date,java.sql.* ” 表示在此jsp中的脚本片断中的java代码需要导入的包,多个包可以用”,”隔开,也可以写多个page指令导入
jsp中以下包会自动导入
java.lang.*;
javax.servlet.*;
javax.servlet.jsp.*;
javax.servlet.http.*;
session=”true| false” 是否获取此用户的session对象(默认值为true, 获取),如果设为不自动获取, 我们也可以手动通过request对象来获取session对象
buffer=”none| 20kb” 设置out隐式对象的缓存,缺省默认为8KB的缓存,none为不缓存
autoFlush=”true| false” 设置out隐式对象的缓存满后是否自动刷新,一般不改(默认为true, 自动刷新)
isThreadSafe=”true| false” 设置此jsp的线程是否是安全的(默认为true, 是线程安全的),如果设置为false 那么此jsp翻译成servlet后会实现SingleThreadModel接口以确保线程安全(SingleThreadModel接口上面已讲过);
info=”text” 可以通过此属性带信息
errorPage=”relative_url” 指定当此jsp页面出现异常后,转向哪个页面,此路径必须使用相对路径,如果以”/”开头表示相对于当前应用的程序的根目录, 否则相对于当前的页面位置
此属性只是设置此jsp异常时跳转的页面,也可以再web.xml文件中配置全局的异常跳转页面,如:
<error-page>
<exception-type>java.lang.ArithmeticException</exception-type>
<location>/error.jsp</location>
</error-page>
表示当前web应用中所有的serlvet及jsp文件抛出java.lang.ArithmeticException异常后将跳转至/error.jsp这个jsp文件
<error-page>
<error-code>404</error-code>
<location>/error.jsp</location>
</error-page>
表示当前web应用抛出404(找不到资源)异常后,就跳转至/error.jsp这个jsp文件
如果在一个jsp文件中设置了errorPage的属性,那么在web.xml文件中设置的异常跳转,将不对此jsp页面起作用
isErrorPage=”true | false” 设置当前页面是否为错误处理页面(默认值为false,不是错误处理页面),如果将此属性设为true,那么此jsp会接收一个异常对象(exception)
contentType=”text/html;charset=ISO-8859-1” 告诉告诉浏览器打开此文件的格式及编码 (也就是设置response的Content-Type头属性的值)
pageEncoding=”characterSet |ISO-8859-1” 也是设置编码格式的
isELIgnored="true | false" 是否忽略EL表达式,默认为false(不忽略);
include指令
语法<%@ includefile=” /Test1/head.jsp” %>
用此指令包含 属于静态包含
静态包含,在将jsp转换为servlet时就将要包含的jsp文件整合成一个servlet,在返回给客户机,所以静态包含只有一个serlvet,效率高
用request.getRequestDispatcher("/Test1/head.jsp").include(request,response);方法包含就是动态包含
动态包含,就是包含者需要访问被包含者时,再去访问被包含者的servlet,所以包含了多少个jsp文件就会多出多少个servlet,效率低
注意事项: 被包含者不能有外部的框架(框架代码) ,因为包含时会将被包含者文件中的所有字符都包含到其中
taglib指令
taglib指令用于在JSP页面中导入标签库,语法:<%@ taglib url=” http://java.sun.com/jsp/jstl/core” prifix=”c”%>
JSP隐式对象
JSP引擎在调用_jspService()方法时,会传入9个隐式对象(注意cookie不是一个隐式对象)
request 请求对象(已讲)
response 响应对象(已讲)
session 回话对象(已讲)
application servletContext对象(已讲)
config servletConfig对象(已讲)
page 当前的servlet(this)对象,因接收时转换成了Object对象,所以用时要强转为org.apache.jasper.runtime.HttpJspBase(已讲)
exception 错误对象,当只有page指令的isErrorPage属性设置为”true”(表示此页面是错误处理页面)时才会传入此对象(此对象继承了Exception对象, 已讲)
out
out 隐式对象用于向客户发送字符数据
out对象通过pageContext对象的getOut方法获得,其作用和用法与response.getWriter()方法非常相似。
此out隐式对象的类型为JspWriter,JspWriter相当于一种带缓存功能的PringtWriter,设置JSP页面的page指令的buffer属性可以调整此out对象的缓存大小,也可以关闭缓存
只有向此out对象中写入了内容,且满足以下任何一个条件, out对象才去调用response.getWriter()方法,并通过该方法返回的PrintWriter输出流对象将out对象的缓冲区的内容正真写入到Servlet引擎提供的缓冲区域中;
1、设置page指令的buffer属性的值为:none,关闭out对象的缓存
2、out对象的缓存区已满
3、jsp页面结束
注意事项: 此out对象不要和response.getWriter流对象一起使用 ,不然会出现输出顺序不一致的异常
pageContext
1、 pageContext对象是JSP技术中最中意的一个对象,它代表JSP页面的运行环境
2、 pageContext对象封装了其他8大隐式对象的引用
3、 pageContext对象它自身就是一个域对象,可以用来存储数据
4、 pageContext对象还封装了web开发中经常涉及到的一些常用的操作, 如包含和转发到其他资源, 检索其他域对象中的属性等
5、 生命周期: 当jsp文件执行完成后,此域就被销毁了
获取其它隐式对象的方法
pageContext.getEcception(); 获取exception隐式对象
pageContext.getPage(); 获取page隐式对象(this)
pageContext.getRequest(); 获取request(请求)隐式对象
pageContext.getResponse(); 获取response(响应)隐式对象
pageContext.getServletConfig(); 获取servletConfig隐式对象
pageContext.getServletContext(); 获取servletContext隐式对象
pageContext.getSession(); 获取session隐式对象
pageContext.getOut(); 获取out隐式对象
pageContext对象中域方法
pageContext.setAttribute(Stringkey,String value); 向pageContext域中存入值,注意pageContext对象的生命周期是:当前的jsp文件执行完成后就消失了,所以要注意pageContext的使用范围
pageContext.getAttribute(Stringkey); 获取pageContext对象域中的映射值
pageContext.removeAttribute(Stringkey); 删除pageContext对象中的映射关系
pageContext对象中访问其它三个域的方法
pageContext对象中有代表各个域的常量(int 类型)
pageContext.APPLICATION_SCOPE 表示application (servletContext)对象
pageContext.SESSION_SCOPE 表示session对象
pageContext.REQUEST_SCOPE 表示request对象
pageContext.PAGE_SCOPE 表示当前的pageContext对象
pageContext对象中有获取其他域中键值的方法
pageContext.getAttribute(Stringkey, pageContext.SESSION_SCOPE); 表示获取session对象中的映射关系pageContext.SESSION_SCOPE这个常量代表session对象
pageContext.setAttribute(Stringkey, String value, pageContext.SESSION_SCOPE); 表示设置session对象中的映射关系
pageContext.removeAttribute(Stringkey, pageContext.SESSION_SCOPE); 表示删除session对象中的映射关系
pageContext.findAttribute方法
pageContext.findAttribute(String key); 此方法是遍历获取所有域中的键值关系(顺序: pageContext-> request -> session -> application(servletContext))如果在request对象中找到了此映射关系的值,那么将不会在向下继续寻找,如四个域中都没找到此键值关系,就返回一个“”字符串,并不是“null”
pageContext对象中定义了forward(转发)方法和include(动态包含)方法分别用来简化和代替request.getRequestDispatcher(“url”).forward(request, response)方法;参数是要转发的url(加”/”表示当前的web应用根目录)。但这两个方法,会将除了pageContext这个域对象以外的所有对象转发或包含过去
servlet中的四大域对象有:
pageContext 当jsp文件执行完成后,此域就被销毁了
request 当request对象销毁是,此域就被销毁了
session session对象域的存活时间是可以调整的,默认情况下30分钟无操作自动销毁
servletContext 当前web应用中的所有资源都可以访问此域,此对象的存活时间为, 当服务器启动就创建, 当服务器关闭时销毁(一般存储在此域中的键值在不用时都要用.removeAttribute(String key)方法来删除此键值,不然会浪费资源)
JSP标签
JSP标签页称为JSP Action(JSP动作)元素,它用于在jsp页面中提供业务逻辑功能,避免在JSP页面中直接编写java代码造成jsp页面难以维护
jsp中有如下三个常用的标签:
<jsp:include>标签
语法:<jsp:include page=”/url”><jsp:include>(动态包含和pageContext.include(“/url”)是一样的)会将除了pageContext这个域对象以外的所有对象转发或包含过去
<jsp:forward>标签
语法:<jsp:forwardpage=”/url”></jsp:forward>(转发和pageContext.forward(“url”)是一样的)会将除了pageContext这个域对象以外的所有对象转发或包含过去
<jsp:param>标签(配合前面两个标签一起使用)
语法:
<jsp:paramname=”key” value=”value”>
用法:
<jsp:forwardpage=”/servlet”>
<jsp:param name=”key” value=”value”/>
<jsp:paramname=”key1” value=”value1” />//可传入多个键值
</jsp:forward>
这样我们就可以在/servlet中用request. getParameter("key");方法来获取key的值, 也就是说可以通过<jsp:param>标签传值给转发或包含的jsp或serlvet
JSP映射与serlvet的映射
servlet的映射(前面已讲过)
在web应用中注册com.scxh.Test这个serlvet,并取名为Test
<servlet>
<servlet-name>Test</servlet-name>
<servlet-class>com.scxh.Test</servlet-class>//不加“/”相对于classes目录,加上相对于当前的web目录,此处不用加
<!--加入了以下的标记,此servlet就会在服务器启动时创建-->
<load-on-startup>1</load-on-startup>//如果在web.xml配置文件中在当前Servlet注册标签中加上了当前标签,就会在服务器启动时创建当前的Servlet对象,并调用Servlet对象的init方法;
</servlet>
serlvet的映射
<servlet-mapping>
<servlet-name>Test</servlet-name>
<url-pattern>*.com</url-pattern>//不能在前面加字符了,如:<url-pattern>aa/*.com</url-pattern>;这样也是不允许的
</servlet-mapping>
JSP的映射
在web应用中注册这个head.jsp文件并取名为head
<servlet>
<servlet-name>head</servlet-name>
<jsp-file>/Test1/head.jsp</jsp-file>//不加“/”相对于classes目录,加上相对于当前的web目录,此处需加上
<!--加入了以下的标记,此servlet就会在服务器启动时创建-->
<load-on-startup>1</load-on-startup>//如果在web.xml配置文件中在当前JSP注册标签中加上了当前标签,就会在服务器启动时创建当前的JSP所对应的Servlet对象,并调用Servlet对象的init方法;
</servlet>
映射这个jsp
<servlet-mapping>
<servlet-name>head</servlet-name>
<url-pattern>/head.htlm</url-pattern>//这样我们就可以再浏览器值直接工程名/head.htlm 访问head.jsp文件了
</servlet-mapping>
JavaBean
JavaBean一般用于封装数据的实体,JavaBean是一个遵循特定写法的java类, 它通常具有如下特点:
1. 这个java类必须具有一个无参的构造函数
2. 属性必须私有化
3. 私有化的属性必须有public类型的set和get方法(set方法称为对属性的修改器,get方法称为属性的访问)
4. 必须放在一个java文件中并且此类必须为public类型的
JSP中提供了三个标签用于操作javaBean
<jsp:useBean>
示例:
JSP中的代码:
<jsp:useBean id="ren" class="cn.Demo1" scope="page"></jsp:useBean> <%--如果这个scope属性不缺省,默认值为“page“--%>
<%=ren.getName() %>
转换为servlet后的代码
cn.Demo1ren= null; //创建一个cn.Demo1这个类的引用变量,取名为
//到pageContext域对象中去取ren这个映射关系
ren =(cn.Demo1)_jspx_page_context.getAttribute("ren", PageContext.PAGE_SCOPE);
//判断是否取到,没有取到就在内存中创建个新的对象
if (ren == null){
//创建一个新的对象
ren = new cn.Demo1();
//并把此对象存入pageContext域中,key值为引用的变量名
_jspx_page_context.setAttribute("ren",ren, PageContext.PAGE_SCOPE);
}
也就是说当我们在<jsp:useBean>标签中把id设置”ren” 并把class设置为一个类(带有包名的完整名称)时,那么系统会自动创建这个类名为ren的引用变量, 如果scope属性的值为page,表示到pageContext域中去获取key为ren的Bean对象,如果没有获取到就创建一个这个类的新对象,并存入scope域对象中key的值就是id的值(“ren”);scope的值可以使四大域中的任何一个;
注意事项:
<jsp:useBean id="ren"class="cn.Demo1" scope="session">
嵌套的代码
</jsp:useBean>
在<jsp:useBean > 标签中嵌套的代码只有当这个标签创建一个新对象时,此代码才会执行;如果在域中获取到了此对象(此标签就不创建新对象),中间嵌套的代码将不会执行
<jsp:setProperty>
通过Bean对象的属性名来给此属性赋值(配合<jsp:useBean>标签来用)
<%--<jsp:setProperty>标签的用法(配合<jsp:useBean>标签来用) --%>
<%-- 通过Bean为"ren"对象中的"gender"属性赋值为”女“ --%>
<jsp:setProperty name="ren" property="gender"value="女"/>
<%=ren.getGender() %><%-- 打印的值为:”女“ --%>
<%="<br/>1-----------------------------------------------------------------<br/>" %>
<%-- 可以用字符串为8大基本类型赋值(会自动将字符串转换为向对应的类) --%>
<jsp:setProperty name="ren" property="id"value="13"/><%--这里的id属性为int类型,赋值字符串会自动转换 --%>
<%=ren.getId() %><%-- 打印的值为:”13“ --%>
<%="<br/>2-----------------------------------------------------------------<br/>" %>
<%-- 通过请求参数为ren对象的password赋值 --%>
<%-- 提交的方式为:http://localhost:8080/javaBean/Test1/Demo1.jsp?password=abc123 --%>
<jsp:setProperty name="ren" property="password"param="password"/>
<%=ren.getPassword() %><%-- 打印的值为:”abc123“ --%>
<%--param="password" 这句话会被翻译成 request.getParameter("password");
也就是所从我们提交的数据中去找password这可以key的值,找到了就赋值,没有这个key就不赋值--%>
<%="<br/>3-----------------------------------------------------------------<br/>" %>
<%-- 通过请求参数为ren对象的date赋值(date为Date日期对象) --%>
<%-- 提交的方式为:http://localhost:8080/javaBean/Test1/Demo1.jsp?date=2012-5-19 --%>
<%-- Date date = newSimpleDateFormat("yyyy-MM-dd").parse(request.getParameter("date"));--%>
<%-- <jsp:setPropertyname="ren" property="date" value="<%=date%>"/>--%>
<%=ren.getDate() %><%--打印的值为:Sat May 19 00:00:00 CST 2012 --%>
<%--从以上value="<%=date %>代码出可以看出,value的值可以引用java代码所返回的值 --%>
<%="<br/>4-----------------------------------------------------------------<br/>" %>
<%-- 通过请求参数自动为ren对象对应的属性赋值 --%>
<%-- 提交的方式为:http://localhost:8080/javaBean/Test1/Demo1.jsp?name=lishi&gender=nan
注意此处用中文会出现get方式提交乱码问题(前面已讲过怎么解决)--%>
<jsp:setProperty name="ren"property="*" /><%--还可以写为<jsp:setProperty name="ren"property="name" />表示从请求中找到name这个key,并赋值给ren这个对象-->
<%=ren.getName() %><%--打印的值为:lishi --%>
<%=ren.getGender() %><%--打印的值为:nan --%>
<%-- 如果property="*"的话,那么表示根据名称自动匹配请求参数的key给ren对象中和此key一样的属性赋值--%>
<jsp:getProperty>
此标签用于读取JavaBean对象的属性,也就是调用属性的get方法,然后将读取属性的值转换成字符串后插入响应的正文中,如果某个属性的值为null,那么输出的内容为字符串”null”
语法:
<jsp:getProperty name="ren" property="name"/>
<%--表示直接输出ren这个Bean对象的name属性,如果此属性的值为null,那么将输出"null"字符串 --%>
JSP设计模式
JSP+JavaBean模式
此模式适合开发逻辑不太复杂的web应用程序,这种模式下,JavaBean用于封装业务数据,JSP即负责处理用户请求,又显示数据
Servlet(controller)+JSP(view)+JavaBean(model)称为MVC模式
此模式适合开发复杂的web应用,在这种模式下,serlvet复杂处理用户请求,JSP负责显示数据,JavaBean负责封装数据,采用此MVC模式各个模块之间层次清晰,web开发推荐采用此模式
此模式分为三层:web层、业务逻辑层(service)、数据访问层(dao)
Web层分
处理用户请求
Service(业务逻辑层)
处理dao层返回的数据,及封装这些数据
Dao(数据访问层)
访问数据库
包名规范
cn.itcast.domain 存放javaBean的类
cn.itcast.dao 存放Dao层的类
cn.itcast.dao.impl 存放Dao层的接口
cn.itcast.servce 存放Service层的类
cn.itcast.servce.impl 存放servlce层的接口
cn.itcast.web.serlvet名 存放处理用户请求的servlet+JSP
cn.itcast.web.listener
cn.itcast.web.filter
cn.itcast.utils 存放工具类的包
Junit.test 存放测试的程序
在MVC模式下的jsp文件必须放在WEB-INF/jsp目录中防止客户端直接访问
MVC模式开发的流程图:
EL表达式
EL表达式语法:${date }如果在jsp中用此语句,那么表示到每个域中去找date的映射关系,找到了就返回此date的value,如果没有找到就返回一个“”为空的字符串;
此表达式会翻译成servlet的代码:pageContext.findAttribute(date); 此方法是遍历获取所有域中的键值关系(顺序: pageContext-> request -> session -> application(servletContext))如果在request对象中找到了此映射关系的值,那么将不会在向下继续寻找,如四个域中都没找到此键值关系,就返回一个“”字符串,并不是“null”(上面已讲过)(可以从指定域中获取指定的映射关系)
如果存入的是一个key键所对应的是一个Bean对象,EL表达式语法为:${date.name}; 那么当取出date对象后会去调用name属性的get方法,并返回其值
在WEB开发中,如果JS中要用到EL表达式,在JSP引入JS文件时,可以把js文件命名为.jsp文件就可以了,这样里面el就能运行,也就是服务器可以执行这个文件了。无非页面引用的时候引用jsp就可以了。
如:<script src="myjs.jsp"type="text/javascript></script>;但取值范围只能取到session和application域中的值
通过EL表达式从指定域中获取数据
<%="<br/>-------------------从指定域中取出数据---------------------<br/>"%>
<%
pageContext.setAttribute("name", "page");
request.setAttribute("name", "request");
session.setAttribute("name", "session");
application.setAttribute("name", "application");
%>
${pageScope.name }
${requestScope.name }
${sessionScope.name }
${applicationScope.name}
通过EL表达式获取请求的数据
<%-- 通过EL表达式,获取请求的数据 --%>
<body>
<%="<br/>-------------------获取请求的数据---------------------<br/>"%>
<%-- 如访问的url为:http://localhost:8080/javaBean/Test1/Demo4.jsp?name=zhangshan--%>
${param.name }
<%--以上代码就相当于:request.getParameter("name")--%>
<%="<br/>-------------------获取复选框中请求的数据---------------------<br/>"%>
${paramValues.name[0] }
<%--以上代码就相当于:
String[] str = request.getParameterValues("name");
System.out.println(str[0]);
获取的是复选框中第下标为0的值
--%>
通过EL表达式获取集合中的数据
list集合
<%-- 用EL表达式取出list集合中的数据 --%>
<%
//向list集合中存入Demo1的对象
List<Demo1> list = new ArrayList<Demo1>();
list.add(new Demo1("aaa")); //构造方法会为此对象的name属性赋值
list.add(new Demo1("bbb"));
list.add(new Demo1("ccc"));
request.setAttribute("list", list);
%>
${list[0].name}<%--表示取出域中list的集合对象后,在取出这个集合的第0个对象再调用这个对象的getName方法 --%>
map集合
<%-- 用EL表达式取出map集合中的数据 --%>
<%
//向map集合中以键值对的方式存入Demo1对象
Map<String, Demo1> map = new HashMap<String, Demo1>();
map.put("d1", new Demo1("d111111")); //构造方法会为此对象的name属性赋值
map.put("d2", new Demo1("d222222"));
map.put("d3", new Demo1("d333333"));
map.put("d4", new Demo1("d444444"));
request.setAttribute("map", map);
%>
${map.d2.name}<%--表示取出域中取出map的集合对象后再通过d2这个key找出其value(Demo1对象),再调用name属性的get方法 --%>
输出当前工程的名称,如:/project
<%--输出当前所在的工程名称 --%>
${pageContext.request.contextPath}<%--得到pageContext对象后再通过request对象的contextPath属性获取本工程的名称 --%>
<%--在跳转页面时需要用到:如<a href="${pageContext.request.contextPath }/Test1/Demo1.jsp">跳转</a> 这样我们就可以直接跳转至本工程中的其他页面了--%>
JSTL标签
EL表达式可以通过JSTL标签迭代集合等功能
JSTL是SUN公司开发的一套标签库,使用JSTL可以在页面中实现一些简单的逻辑,从而替换页面中的脚本代码
在JSP中使用JSTL标签需完成以下两个步骤
1、 导入jstl.jar和standerd.jar这两个JSTL的jar包
2、 在JSP页面使用<%@ tagliburl=” http://java.sun.com/jsp/jstl/core” prifix=”c” %> 元素导入标签库
url的地址是standerd.jar包中的/META-INF/c.tld文件中的<uri>http://java.sun.com/jsp/jstl/core</uri>标签中的地址
常用的JSTL标签
<c:foreach var=”” items=””>(迭代标签)
<%="<br/>-----------------------通过EL表达式配合JSTL来迭代list集合-------------------------------<br/>" %>
<%
//向list集合中存入Demo1的对象
List<Demo1> list = newArrayList<Demo1>();
list.add(new Demo1("aaa"));//构造方法会为此对象的name属性赋值
list.add(new Demo1("bbb"));
list.add(new Demo1("ccc"));
request.setAttribute("list", list);
%>
<c:forEach var="demo1"items="${list }">
${demo1.name }
</c:forEach>
<%--items="${list }" 通过EL表达式中取出这个集合,并将遍历的出的对象放入demo1变量中,
然后在通过EL表达式调用li这个对象的getName方法
--%>
<%="<br/>-----------------------通过EL表达式配合JSTL来迭代map集合-------------------------------<br/>" %>
<%
//向map集合中以键值对的方式存入Demo1对象
Map<String, Demo1> map = new HashMap<String,Demo1>();
map.put("d1", new Demo1("d111111"));//构造方法会为此对象的name属性赋值
map.put("d2", new Demo1("d222222"));
map.put("d3", new Demo1("d333333"));
map.put("d4", new Demo1("d444444"));
request.setAttribute("map", map);
%>
<c:forEach var="entry"items="${map }">
${entry.key } : ${entry.value.name }
</c:forEach>
<%--
//上面entry变量得到的就相当于下面it得到的
Set<Map.Entry<String,Demo1>> set = map.entrySet();
Iterator<Map.Entry<String,Demo1>> it = set.iterator();
//上面EL表达式所输出的就相当于下面out对象所输出的
while(it.hasNext()){
Map.Entry<String, Demo1>entry = it.next();
out.print(entry.getKey()+":"+entry.getValue().getName());
}
也就是说当map集合对象被遍历出来的对象就是一个Map.Entry对象
${entry.key} : ${entry.value.name }就是在在调用Map.Entry的getKey方法
及调用 Map.Entry的getValue方法再通过其返回对象,调用其对象的getName方法
--%>
<c:if test=””>(测试标签,判断)
<%="<br />-----------------------测试(判断)标签的使用-------------------------------<br />" %>
<%
//pageContext.setAttribute("user", "张三");
%>
<c:if test="${user!=null }">
pageContext域中的user值为:${user }
</c:if>
<c:if test="${user==null }"><!—EL表达式也可以用${empty user};表示如果user为null的话就返回ture,empty关键字还可以检测集合如:${empty map},意思是当map集合等于null,或者map的isEmpty()方法返回为true(也就是map中没有映射关系时) ,那么此EL表达式就返回false -->
pageContext域中没有值
</c:if>
<%--当在四个域中取到user的值(映射关系)后就输出第一句话,否则输出第二句话,也就是说当test的值为true时此标签中的内容才会被执行 --%>
自定义标签
传统标签
自定义标签主要用于移除JSP页面中的java代码
生命周期:当jsp执行到此标签时,创建此标签类的对象,然后此标签类对象会在关闭服务器时销毁
要使用自定义标签移除jsp页面中的java代码,只需要完成以下两个步骤:
1、编写一个实现Tag接口的java类(或者继承Tag的实现类TagSupport,把页面java代码移到这个java类中(标签处理器);
2、编写标签库描述符(tld)文件,在tld文件中把标签处理器类描述成一个标签
Tag接口中的方法
doEndTag();
当读取到标签结束符时,会调用此方法
doStartTag();
当读取到标签开始符时,会调用此方法
getParent();
得到此标签的父节点
release();
释放运行当前标签所占的资源
setPageContext(
PageContext pc);
设置当前标签的pageConotext对象(此标签会由执行引擎调用,并传入当前jsp标签中的pageContext对象)
示例:(用标签在jsp页面获取IP地址)
标签类
//定义一个标签类,必须实现Tag接口,或者继承Tag接口的TagSupport实现类
/*
//第一种方法:实现Tag接口(介绍了此接口中方法的调用顺序, 也可以查看与调用此标签的JSP文件所对应的servlet中的代码)
public class Test1 implements Tag{
private PageContext pageContext = null;
private Tag tag = null;
//当读取完当前标签开始符时,会调用此方法
@Override
public int doStartTag() throws JspException {
// TODO Auto-generatedmethod stub
HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
String ip = request.getRemoteAddr();
JspWriter out = pageContext.getOut();
try {
out.write(ip);
} catch (IOException e) {
// TODO Auto-generatedcatch block
throw new RuntimeException(e);//此异常不会再调用时不会抛给调用者,会直接抛给JVM
}
return 0;
}
//当读取完当前标签结束符时会调用此方法
@Override
public int doEndTag() throws JspException {
// TODO Auto-generatedmethod stub
return 0;
}
//设置当前标签的pageContext对象(系统调用,此方法会最先被调用)
@Override
public void setPageContext(PageContext pageContext) {
// TODO Auto-generatedmethod stub
this.pageContext = pageContext;
}
//设置当前标签的父节点(系统调用,当调用完setPageContext方法后就会调用此方法,如果没有父节点会传入一个null)
@Override
public void setParent(Tag tag) {
// TODO Auto-generatedmethod stub
this.tag = tag;
}
//得到此标签的父节点
@Override
public Tag getParent() {
// TODO Auto-generatedmethod stub
return this.tag;
}
//关闭标签所占用的资源(系统调用,此方法一般用于释放标签工作时所生成的资源,此方法会时最后被调用,比doEndTag()方法还要后调用)
@Override
public void release() {
// TODO Auto-generatedmethod stub
}
}
*/
//第二种方法:继承Tag接口的TagSupport实现类(推荐,因为继承此方法不需要重写其他的方法)
public class Test1 extends TagSupport{
//当读取到标签开始符时,会调用此方法
@Override
public int doStartTag() throws JspException {
// TODO Auto-generatedmethod stub
//通过pageContext对象来获取request对象
HttpServletRequest request = (HttpServletRequest)this.pageContext.getRequest();
//通过request对象来获取到客户机的IP地址
String ip = request.getRemoteAddr();
//通过pageContext对象获取到out缓存输出对象
JspWriter out = this.pageContext.getOut();
try {
//向缓存对象中添加此ip
out.write(ip);
} catch (IOException e) {
// TODO Auto-generatedcatch block
throw new RuntimeException(e);
}
return super.doStartTag();
}
}
在tld文件中注册标签类(一般放在WEB-INF目录下,因为当jsp文件用tld文件的uri名导入标签库时,jsp会默认到WEB-INF目录下去找此文件)
<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/j2eehttp://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<!-- 标签的描述 -->
<description>A tag libraryexercising SimpleTag handlers.</description>
<!-- 版本 -->
<tlib-version>1.0</tlib-version>
<!-- 名称(可以不修改) -->
<short-name>scxh</short-name>
<!-- 将此文件中标签绑定到一个uri上(映射名称) -->
<uri>http://www.scxh.cn</uri>
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>printIP</name>
<!-- 标签类的完整名称 -->
<tag-class>cn.scxh.Test1</tag-class>
<!-- 标签内容为空 -->
<body-content>empty</body-content>
</tag>
</taglib>
在JSP中调用此标签
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<%--导入标签,并为此标签取一个代号 --%>
<%--第一种方式:直接导入标签uri --%>
<%--@taglib uri="http://www.scxh.cn"prefix="test1"--%>
<%--第二种方式:直接导入标签文件的路径 --%>
<%--@taglib uri="WEB-INF/test1.tld"prefix="test1"--%>
<%--第三种方式:导入在web.xml文件中配置的变量名(推荐) --%>
<%@taglib uri="testtab" prefix="test1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP ‘Test1.jsp‘starting page</title>
</head>
<body>
你的IP地址为:<test1:printIP/>
</body>
</html>
使用第三种方式导入标签需在xml文件中设置
<jsp-config>
<taglib>
//取一个映射名称(uri)
<taglib-uri>testtab</taglib-uri>
//设置tld文件的路径(必须是路径,不能是tld文件的uri)
<taglib-location>/WEB-INF/test1.tld</taglib-location>
</taglib>
</jsp-config>
自定义标签的扩张功能
Tag接口中的常量
EVAL_BODY_INCLUDE 表示执行标签体中的内容(doStartTag()方法返回值引用)
SKIP_BODY 表示不执行标签体中内容(doStartTag()方法返回值引用)
EVAL_PAGE 表示继续执行余下的JSP内容(doEndTag()方法返回值引用)
SKIP_PAGE 表示不执行余下的JSP内容(doEndTag()方法返回值引用)
控制Jsp页面某一部分内容是否执行
调用doStartTag()(此方法会在读取完标签开始符时调用)方法.如果此方法的返回值为Tag.SKIP_BODY表示不执行标签中的内容;如果返回的是Tag. EVAL_BODY_INCLUDE表示执行此标签中的内容
示例:
标签类
//控制标签体的内容是否输出
public class Test2 extends TagSupport{
@Override
public int doStartTag() throws JspException {
// TODO Auto-generatedmethod stub
return Tag.SKIP_BODY;//表示不输出标签中的内容
//return Tag.EVAL_BODY_INCLUDE;//表示输出标签中的内容
}
}
在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/j2eehttp://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<!-- 此文件一般放在WEB-INF目录下,因为当jsp文件用tld文件的uri名导入标签库时,jsp会默认到WEB-INF目录下去找此文件 -->
<!-- 标签的描述 -->
<description>A tag libraryexercising SimpleTag handlers.</description>
<!-- 版本 -->
<tlib-version>1.0</tlib-version>
<!-- 名称(可以不修改) -->
<short-name>scxh</short-name>
<!-- 将此文件中标签绑定到一个uri上(映射名称) -->
<uri>http://www.scxh.cn</uri>
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>print</name>
<!-- 标签类的完整名称 -->
<tag-class>cn.scxh.Test2</tag-class>
<!-- 标签内容为JSP,此值需大写-->
<body-content>JSP</body-content>
</tag>
</taglib>
JSP中调用此标签
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="labelTag"prefix="label" %>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP ‘Test2.jsp‘starting page</title>
</head>
<body>
<%-- 控制标签中的内容是否被执行 --%>
<label:print>
<p>控制此内容是否被输出</p>
</label:print>
</body>
</html>
控制整个jsp页面是否执行
调用doEndTag()(此方法会在读取完标签结束符时调用)方法.如果此方法的返回值为Tag.SKIP_PAGE表示不执行此标签后面的JSP代码;如果返回的是Tag. EVAL_PAGE表示执行此标签后面的JSP代码
示例:
标签类
//控制此标签后的JSP是否还执行
public class Test3 extends TagSupport{
//当读取完当前标签结束符时会调用此方法
@Override
public int doEndTag()throws JspException {
//returnTag.SKIP_PAGE;//表示不执行此标签后面的JSP代码
return Tag. EVAL_PAGE;//表示执行此标签后面的JSP代码
}
}
在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/j2eehttp://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<!-- 此文件一般放在WEB-INF目录下,因为当jsp文件用tld文件的uri名导入标签库时,jsp会默认到WEB-INF目录下去找此文件 -->
<!-- 标签的描述 -->
<description>A tag libraryexercising SimpleTag handlers.</description>
<!-- 版本 -->
<tlib-version>1.0</tlib-version>
<!-- 名称(可以不修改) -->
<short-name>scxh</short-name>
<!-- 将此文件中标签绑定到一个uri上(映射名称) -->
<uri>http://www.scxh.cn</uri>
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>execution</name>
<!-- 标签类的完整名称 -->
<tag-class>cn.scxh.Test3</tag-class>
<!-- 标签内容为空 -->
<body-content>empty</body-content>
</tag>
</taglib>
JSP中调用此标签
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="labelTag"prefix="label"%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP ‘Test3.jsp‘starting page</title>
</head>
<body>
<label:execution/>
This ismy JSP page. <br>
<p>测试label:execution标签后面的JSP是否执行</p>
</body>
</html>
控制标签内容是否重复执行
此方法需要用到TagSupport父类中的public int doAfterBody();方法,此方法会在读取完当前内容后调用,在执行doEndTag()方法前被调用,如果doAfterBody();方法返回的是IterationTag. EVAL_BODY_AGAIN的话,表示此方法中的内容继续被输出,如果返回的是IterationTag.SKIP_BODY,表示此方法中的内容不被输出;但一定要让doStartTag()方法的返回值为Tag.EVAL_BODY_INCLUDE;表示执行标签中的内容
示例:
标签类
//控制标签中内容重复输出5此
public class Test4 extends TagSupport{
private int i = 5;
//一定要让此方法的返回值为Tag.EVAL_BODY_INCLUDE;表示执行标签中的内容
@Override
public int doStartTag() throws JspException {
// TODO Auto-generatedmethod stub
return Tag.EVAL_BODY_INCLUDE;
}
@Override
public int doAfterBody() throws JspException {
i--;
if(i>0){
return IterationTag.EVAL_BODY_AGAIN;//返回此常量表示继续输出标签中的内容
}else{
return IterationTag.SKIP_BODY;//返回此常量表示不输出标签中的内容
}
}
}
在tld文件中注册标签类
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>print5</name>
<!-- 标签类的完整名称 -->
<tag-class>cn.scxh.Test4</tag-class>
<!-- 标签内容为空 -->
<body-content>JSP</body-content>
</tag>
JSP中调用此标签
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="labelTag"prefix="label"%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP ‘Test4.jsp‘starting page</title>
</head>
<%--让标签中的内容输出5次 --%>
<body>
<label:print5>
<p>此处会被输出5次</p>
</label:print5>
</body>
</html>
修改标签内容后再输出
标签类需要继承BodyTagSupport类,并重写doStartTag();方法,让其返回值为BodyTag.EVAL_BODY_BUFFERED;让此标签的内容封装成一个对象,系统再通过调用此标签对象的setBodyContent(BodyContent b)方法将标签内容对象传入; 然后我们再重写doEndTag();方法,getBodyContent()方法通过获取到内容对象,再对此对象修改后通过out缓存输出到jsp
示例:
标签类
//将标签中的字母转换为大写
public class Test5 extends BodyTagSupport{
@Override
public int doStartTag() throws JspException {
// TODO Auto-generatedmethod stub
return BodyTag.EVAL_BODY_BUFFERED;//让此标签的内容封装成一个对象,系统再通过调用此标签对象的
//setBodyContent(BodyContent b)方法将标签内容对象传入;
}
@Override
public int doEndTag() throws JspException {
BodyContent content = this.getBodyContent();//通过此方法得到内容对象
String str = content.getString();//将内容对象以字符串返回
str = str.toUpperCase();//将字符串中的所有字母都变为大写
try {
pageContext.getOut().write(str);//将转换过后的字符串输出到JSP
} catch (IOException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
return super.doEndTag();
}
}
在tld文件中注册标签类
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>uppercase</name>
<!-- 标签类的完整名称 -->
<tag-class>cn.scxh.Test5</tag-class>
<!-- 标签内容为空 -->
<body-content>JSP</body-content>
</tag>
JSP中调用此标签
<%@ pagelanguage="java" import="java.util.*"pageEncoding="UTF-8"%>
<%@taglib uri="labelTag"prefix="label"%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP ‘Test5.jsp‘starting page</title>
</head>
<%--将标签中的字母转换大写 --%>
<body>
<label:uppercase>
<p>此处的字母会被转换为大写:abcdef</p>
</label:uppercase>
</body>
</html>
简单标签
简单标签需要实现SimpleTag接口或继承它的实现类SimpleTagSupport,通过这个接口或这个它的实现类就可以完成上面复杂标签的所有功能
生命周期:当jsp执行到此标签时,创建此标签类的对象,当整个jsp(servlet)执行完成后这个标签类对象就被释放了
SimpleTagSupport类的方法(按执行顺过排序);当读到一个标签后会调用标签类的这些方法
setJspContext(
JspContext pc);
此方法由JSP的serlvet最先调用(可以看着是系统调用),将pageContext对象传入;
setParent(JspTag parent); 系统调用,会将此标签的父节点当做一个对象传入,如果没有父节点,就传入null
setJspBody(
JspFragment jspBody);
此方法会由JSP的servlet调用,并将标签的内容封装成对象后传入;
doTag();
此方法会在上面标签执行完成后才被执行(由程序员写操作语句)
getJspBody();
得到标签的内容对象(由程序员调用)
getJspContext(); 得到调用此标签的pageContext对象(由程序员调用)
getParent();
得到当前标签的父节点标签(由程序员调用)
案例:
控制标签体中的内容是否被执行
java标签类中的内容
//控制标签体中的内容是否被执行
public class Test1 extends SimpleTagSupport{
//此方法会在系统调用完父类的所有set*方法后,才调用此方法
@Override
public void doTag()throws JspException, IOException {
//如果要输出标签中的内容就使用以下方法,如果不输出标签内容就什么也不做
//得到标签中的内容对象
JspFragment body = this.getJspBody();
//得到JSP的pageContext对象
JspContext pageContext = this.getJspContext();
//将内容对象输入到JSP的out对象中
body.invoke(pageContext.getOut());//也可以写为body.invoke(null);此内容将默认输出到pageContext.getOut()对象中
}
}
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/j2eehttp://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<!-- 此文件一般放在WEB-INF目录下,因为当jsp文件用tld文件的uri名导入标签库时,jsp会默认到WEB-INF目录下去找此文件 -->
<!-- 标签的描述 -->
<description>A tag libraryexercising SimpleTag handlers.</description>
<!-- 版本 -->
<tlib-version>1.0</tlib-version>
<!-- 名称(可以不修改) -->
<short-name>scxh</short-name>
<!-- 将此文件中标签绑定到一个uri上(映射名称) -->
<uri>http://www.scxh.cn</uri>
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>print</name>
<!-- 标签类的完整名称 -->
<tag-class>com.scxh.Test1</tag-class>
<!-- 表示标签内容不能为脚本代码(java代码),还可以有JSP表示可以java代码,empty表示此标签没有标签内容 -->
<body-content>scriptless</body-content>
</tag>
</taglib>
JSP文件中的内容
<%@ pagelanguage="java" import="java.util.*"pageEncoding="UTF-8"%>
<%@taglib uri="labelSimpleTag"prefix="label" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP ‘index.jsp‘starting page</title>
<meta http-equiv="pragma"content="no-cache">
<meta http-equiv="cache-control"content="no-cache">
<meta http-equiv="expires"content="0">
<meta http-equiv="keywords"content="keyword1,keyword2,keyword3">
<meta http-equiv="description"content="This is my page">
<!--
<link rel="stylesheet"type="text/css" href="styles.css">
-->
</head>
<%--控制标签体中的内容是否被执行 --%>
<body>
<label:print>
<p>控制此内容是否被执行</p>
</label:print>
</body>
</html>
控制标签体中的内容重复执行5次
只需将上面java标签类中
//将内容对象输入到JSP的out对象中
body.invoke(pageContext.getOut());
的此代码用for循环执行5次即可
将标签体中的字母变为大写(修改标签体的内容)
只需要将上面java标签类中doTag()方法中的代码改为
//将标签体内容中的字母变为大写
@Override
public void doTag() throws JspException, IOException {
//得到标签中的内容对象
JspFragment body = this.getJspBody();
//建一个缓存流对象
StringWriter buffer = new StringWriter();
//将内容对象当做一个字符串输出到一个缓冲对象中
body.invoke(buffer);
//取出缓存流对象中的内容
String content = buffer.toString();
//关闭缓冲流
buf.flush();
buf.close();
//再将内容中的小写转为大写
String content1 = content.toUpperCase();
//再将此内容放入到out对象中
this.getJspContext().getOut().write(content1);
}
控制此标签后的JSP是否被执行
只需要将上面java标签类中doTag()方法中的代码改为
//控制标签后的JSP是否继续被执行
@Override
public void doTag() throws JspException, IOException {
//只需要在此方法中抛出此异常,那么此标签后面的JSP将不会被执行
throw new SkipPageException();
}
标签的属性
如果想要自定义标签具有属性,需要在标签处理器中编写每个属性对应成员变量及它的的set方法和在tld文件中描述标签的属性;系统当读取到标签属性时会自动调用此标签类与之对应的成员变量名的set方法将属性的值传入;
示例:
java标签类中的代码
//让标签中的内容输出属性count值所有指定的次数
public class Test2 extends SimpleTagSupport{
private int count = 0;
//当读取到与标签中count对象时会调用此方法(按名称对应,通过javaBean实现)
public void setCount(int count) {
this.count = count;
}
@Override
public void doTag() throws JspException, IOException {
// TODO Auto-generatedmethod stub
//将内容输出conut这么多次
for(int i=0; i<count; i++){
this.getJspBody().invoke(null);
}
}
}
tld文件中的内容
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>printCount</name>
<!-- 标签类的完整名称 -->
<tag-class>com.scxh.Test2</tag-class>
<!-- 表示标签内容不能为脚本代码(java代码),还可以有JSP表示可以java代码,empty表示此标签没有标签内容 -->
<body-content>scriptless</body-content>
<!-- 表示为此标签声明一个属性 -->
<attribute>
<!-- 指定属性的名称 -->
<name>count</name>
<!-- 表示此属性是否是必须的 -->
<required>true</required>
<!-- 表示此属性的值是否可以是一个表达式(是否可以是LE表达式等) -->
<rtexprvalue>true</rtexprvalue>
<!-- 还可以指定属性值的类型(必须是java的完整类名),不写此标签表示接收一个Object类型 -->
<!-- <type>java.lang.Integer</type> -->
</attribute>
</tag>
JSP文件中的内容
<%@ pagelanguage="java" import="java.util.*"pageEncoding="UTF-8"%>
<%@taglib uri="labelSimpleTag"prefix="label"%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP ‘Test2.jsp‘starting page</title>
</head>
<%--让标签中的内容输出属性count值所有指定的次数 --%>
<body>
<label:printCount count="5">
<p>标签中的内容</p>
</label:printCount>
</body>
</html>
用自定义标签遍历所有的集合及所有的数组
java标签类中的代码
public class ForEach extends SimpleTagSupport{
//所有单列集合的父接口
private Collection items = null;
//用于存储在域对象中的key
private String var = null;
//将传入的对象都转换为一个单列集合
public void setItems(Object items) throws Exception{
if(items==null){//如果传入的是null
return;
}else if(items instanceof Collection){//如果传入的对象时一个单例集合
this.items = (Collection)items;//就将这个对象强转换Collection(所有单列集合的父接口)对象
}else if(items instanceof Map){//如果传入的是一个map集合
Map map = (Map)items;//就将其强转换Map(所有双列集合的父接口)对象
this.items = map.entrySet();//将其转换为一个entrySet的单例集合中
}else if(items.getClass().isArray()){//如果传入的是一个数组
List list = new LinkedList();//就将其装入一个list集合中
int len = Array.getLength(items);//通过Array类反射出此数组对象的长度
for(int i=0; i<len; i++){//遍历数组,将值存入list集中
list.add(Array.get(items,i));//通过Array类反射出items数组对象下标为i的值
}
this.items = list;//将list集合赋给成员变量
}else{
throw new Exception("你传入的不是一个集合也不是一个数组");
}
}
public void setVar(String var) {//接收用于存储在域中的key
this.var = var;
}
@Override
public void doTag() throws JspException, IOException {
if(this.items==null){
return;
}
//获取单列集合的迭代器
Iterator it = items.iterator();
//迭代这个集合
while(it.hasNext()){
Objectvalue = it.next();
this.getJspContext().setAttribute(var, value);//迭代出来的值向pageContext域中存
this.getJspBody().invoke(null);//执行此标签的内容,(让EL表达式从域中去取key为“var”的值)
}
}
}
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/j2eehttp://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<!-- 此文件一般放在WEB-INF目录下,因为当jsp文件用tld文件的uri名导入标签库时,jsp会默认到WEB-INF目录下去找此文件 -->
<!-- 标签的描述 -->
<description>A tag libraryexercising SimpleTag handlers.</description>
<!-- 版本 -->
<tlib-version>1.0</tlib-version>
<!-- 名称(可以不修改) -->
<short-name>scxh</short-name>
<!-- 将此文件中标签绑定到一个uri上(映射名称) -->
<uri>http://www.scxh.com.cn</uri>
<!-- 注册标签 -->
<tag>
<!-- 此标签类的映射名称 -->
<name>forEach</name>
<!-- 标签类的完整名称 -->
<tag-class>com.foreach.ForEach</tag-class>
<!-- 表示标签内容不能为脚本代码(java代码),还可以有JSP表示可以java代码,empty表示此标签没有标签内容 -->
<body-content>scriptless</body-content>
<!-- 表示为此标签声明一个属性 -->
<attribute>
<!-- 指定属性的名称 -->
<name>var</name>
<!-- 表示此属性是否是必须的 -->
<required>true</required>
<!-- 表示此属性的值是否可以是一个表达式(是否可以是LE表达式等) -->
<rtexprvalue>true</rtexprvalue>
<!-- 表示此属性值得类型 -->
<type>java.lang.String</type>
</attribute>
<!-- 表示为此标签声明一个属性 -->
<attribute>
<!-- 指定属性的名称 -->
<name>items</name>
<!-- 表示此属性是否是必须的 -->
<required>true</required>
<!-- 表示此属性的值是否可以是一个表达式(是否可以是LE表达式等) -->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
JSP文件中的内容
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://www.scxh.com.cn"prefix="c"%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP ‘Test1.jsp‘starting page</title>
</head>
<!-- 重写JSTL标签中的forEach标签,(遍历数组、集合) -->
<body>
<%
List list = new ArrayList();
list.add("list1");
list.add("list2");
list.add("list2");
list.add("list4");
list.add("list5");
pageContext.setAttribute("list", list);
%>
<%
Map map = new HashMap();
map.put("1", "map1");
map.put("2", "map2");
map.put("3", "map3");
map.put("4", "map4");
map.put("5", "map5");
pageContext.setAttribute("map", map);
%>
<%
int[] nums = new int[]{1,2,3,4,5,6};
pageContext.setAttribute("nums", nums);
%>
<%
boolean[] bools = new boolean[]{true,false,true};
pageContext.setAttribute("bools", bools);
%>
<br/>------------------------遍历list集合---------------------------------<br/>
<c:forEach var="str"items="${list }" >
${str }
</c:forEach>
<br/>------------------------遍历map集合---------------------------------<br/>
<c:forEach var="entry"items="${map }">
key: ${entry.key } ,value: ${entry.value }
</c:forEach>
<br/>------------------------遍历int数组---------------------------------<br/>
<c:forEach var="i"items="${nums }">
${i }
</c:forEach>
<br/>------------------------遍历boolean数组---------------------------------<br/>
<c:forEach var="bool"items="${bools }">
${bool }
</c:forEach>
</body>
</html>
属性值的类型说明(属性的值必须用””引起来,EL表达式和<%= %>也一样)
如果在标签属性中传入一个字符串”5.5”,而在标签类中与此属性名对应成员变量是一个float类型的话,系统会自动将字符串转换为Float类型,但只会自动转换八大基本类型;
如果标签类中的成员变量是一个对象,那么标签属性在传入值时必须要是一个对象,如:标签类中的属性是一个Date对象,引用变量名为date;那么标签属性的值必须是date=”<%=new Date()%>”;或者用EL表达式传入一个Date对象,不然系统不能自动转换
JSTL标签
EL表达式可以通过JSTL标签,迭代集合等功能
JSTL是SUN公司开发的一套标签库,使用JSTL可以在页面中实现一些简单的逻辑,从而替换页面中的脚本代码
在JSP中使用JSTL标签需完成以下两个步骤
1、 导入jstl.jar和standerd.jar这两个JSTL的jar包
2、 在JSP页面使用<%@taglib url=” http://java.sun.com/jsp/jstl/core” prifix=”c” %> 元素导入标签库
url的地址是standerd.jar包中的/META-INF/c.tld文件中的<uri>http://java.sun.com/jsp/jstl/core </uri>标签中的地址
核心标签
<%@page import="java.net.URLEncoder"%>
<%@page import="cn.scxh.Person"%>
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<%--导入JSTL包--%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core"prefix="c"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP ‘index.jsp‘starting page</title>
<meta http-equiv="pragma"content="no-cache">
<meta http-equiv="cache-control"content="no-cache">
<meta http-equiv="expires"content="0">
<meta http-equiv="keywords"content="keyword1,keyword2,keyword3">
<meta http-equiv="description"content="This is my page">
<!--
<link rel="stylesheet"type="text/css" href="styles.css">
-->
</head>
<body>
<br/>----------------------------out标签的使用--------------------------------<br/>
<%-- out标签用于向浏览器输出内容 --%>
<%-- value的值为null时,输出default的值 --%>
<c:out value="${aaaaa }" default="aaa"></c:out>
<%-- escapeXml="true"表示将输出的内容转义后输出 --%>
<c:out value="此标签会原样输出<a></a>" escapeXml="true"></c:out>
<br/>----------------------------set标签的使用--------------------------------<br/>
<%--set标签用于把某一个对象存在指定的域范围,
或者设置Web域中的Map类型的属性对象或者javaBean类型的的属性对象的属性 --%>
<%-- 给data这个key赋值为:“xxx”,并存入pageContext对象中--%>
<c:set var="data" value="xxx"scope="page"></c:set>
${data }<%--输出此date,查看data的值是否是xxx --%>
<%--将值存入map集合中 --%>
<%
Map map = new HashMap();
pageContext.setAttribute("map", map);
%>
<%-- 设置key 设置value 设置要存入的map集合 --%>
<c:set property="m1" value="map1"target="${map }"></c:set>
${map.m1 }<%--输出map中m1中的值测试是否存入 --%>
<%--将值赋值给对应的javaBean对象 --%>
<%
Person person = new Person();
pageContext.setAttribute("person", person);
%>
<%--设置javaBean的属性 设置要此属性赋的值 设置要赋值的javaBean对象 --%>
<c:set property="name" value="张三" target="${person }"></c:set>
${person.name }<%--输出此对象的name,测试是否被赋值 --%>
<br/>----------------------------catch标签的使用--------------------------------<br/>
<%--用于捕获异常对象,并存入pageContext对象中 --%>
<c:catch var="exception"><%--var是设置此异常对象存入pageContext中的key --%>
<%
int i = 1/0;
%>
</c:catch>
${exception.message }<%--输出此错误对象的异常描述,测试pageContext对象中是否有此对象 --%>
<br/>----------------------------if标签的使用--------------------------------<br/>
<%-- 设置存储在域中的key 判断的表达式 设置此表达式的值的存储域--%>
<c:if var="aaa" test="${user==null }"scope="page">
你还没登陆
</c:if>
${aaa }<%--输出表达式的结果 --%>
<br/>----------------------------choose标签的使用--------------------------------<br/>
<%--和if else一样 --%>
<c:choose>
<c:when test="${1+1==2 }">
你算对了
</c:when>
<c:otherwise>
你算错了
</c:otherwise>
</c:choose>
<br/>----------------------------foreach标签的使用--------------------------------<br/>
<%--遍历数组或集合 --%>
<%
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
pageContext.setAttribute("list", list);
%>
<%--设置存储在域中的key 设置要遍历的集合或数组--%>
<c:forEach var="str" items="${list }">
${str }
</c:forEach><br/>
<%--还可以当做for循环使用 --%>
<%--设置存储在域中的key 设置开始的数 设置结束的数 设置循环的步长--%>
<c:forEach var="num" begin="1" end="9" step="1">
${num }
</c:forEach>
<br/>
<%-- 用forEach实现表格间色显示 --%>
<%
List list1 = new ArrayList();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
list1.add("ddd");
list1.add("ddd");
list1.add("ddd");
pageContext.setAttribute("list1", list1);
%>
<style type="text/css">
.odd{ background-color: #E6B489}
.even{background-color: #EF6756}
tr:hover{background-color: #D7DBE5}
</style>
<table border="1" width="20%"><%--varStatus="n"这个"n"用于记住当前遍历的次数(n.count) --%>
<c:forEach var="str"items="${list1 }" varStatus="status">
<tr class="${status.count%2==0? ‘even‘ : ‘odd‘ }">
<td>${str }</td>
</tr>
</c:forEach>
</table>
<br/>----------------------------url标签的使用--------------------------------<br/>
<%--重新构建URL的地址,在URL后面加上session的id,并且此url前面没有加工程名,重构过后会自动加上 --%>
<c:url var="ur" value="/Test1.jsp"><%--重构过后的超链接:/jstl/Test1.jsp;jsessionid=CC2C99E7E3C1B3255B647C67502096EA?name=%e4%b8%ad%e5%9b%bd--%>
<%--如果在url标签内嵌套此标签,表示在url后面添加传递参数:?name=zhangsan --%>
<c:param name="name"value="中国"></c:param><%--如果value的值是中文,此标签会自动调用
URLEncoder.encode(temp,"UTF-8");进行加码--%>
</c:url>
<a href="${ur }">此超链接的url后面跟有session对象的id</a>
<br/>----------------------------forTokens标签的使用--------------------------------<br/>
<%-- 用于分割字符串 --%>
<%
String str = "aaa,bbb,cccc,ddd,eeee";
pageContext.setAttribute("str", str);
%>
<%--存储每次分割字符到域的key 要被分割的字符串 以什么分割--%>
<c:forTokens var="ss" items="${str }" delims=",">
${ss }<br/>
</c:forTokens>
<br/>----------------------------remove标签的使用--------------------------------<br/>
<%
pageContext.setAttribute("page1", "page1");
%>
${page1 }
<c:remove var="page1" scope="page"/><%--移除page域中的映射关系 --%>
${page1 }
</body>
</html>
<br/>--------------------import标签和jsp:include标签一样使用(同样是动态包含)--- ----------------<br/>
<br/>--------------------redirect标签是请求重定向--------------------------<br/>
软件国际化(i18n:internationalization)
软件开发时,要使它能同时因对世界不同地区和国家的访问,并针对不同地区和国家的访问,提供相应的、符合来访者阅读习惯的页面或数据,
此执行文件JDK_1.7_X64\jdk\ben\native2ascii.exe用于将中文转换成unicode编码
国际化所具备的特征:
对程序中国固定的文本元素或错误提示信息,状态信息等需要根据来访者的地区和国家选择不同语言的文本为之服务
对程序动态产生的数据,如(日期,货币等)软件应用能根据当前所在的国家或地址的文化习惯进行显示
资源包(功能名称国际化)
对于软件中的菜单栏、导航条、错误提示信息,状态信息等这些固定的文本信息,可以把它们写在一个properties文件中,并根据不同的国家编写不同的properties文件,这一组properties文件称之为一个资源包
资源文件的名称规范
基名_语言_国家 如:mag_zh_CN mag就表示:基名 , zh表示:汉语,CN表示:中国; i18n_en_US i18n表示:基名,en表示:英语,US表示:美国
Locale类是一个区域信息类,里面有很多代表国家和语言的常量
如果一个文件名就为mag,那么这个文件就是一个默认的文件,当没有找到语言包时就找此文件
在javaAPI中提供了一个ResourceBundle类用于描述一个资源包,并且ResourceBundle类提供了相应的方法getBundle,这个方法可以根据来访者的国家地区自动获取与之对应的资源文件予以显示
示例:(当浏览器请求的是zh_CN,就将i18n_zh_CN.properties文件返回,如果浏览器请求的是en_US就将i18n_en_US.properties文件返回)
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>登录</title>
</head>
<%
Locale locale = request.getLocale();//获取浏览器的语言(此类中有很多代表国家和语言的常量)
ResourceBundle rb = ResourceBundle.getBundle("lang.i18n",locale);//通过语言来获取对应的基名为i18n的文件
%>
<body>
<p><%=rb.getString("i18n.Test1.title") %></p><%--获取此文件中“i18n.Test1.title”这个key 的值,下面其他的都一样 --%>
<form action="#">
<%=rb.getString("i18n.Test1.username") %><input type="text"/><br/>
<%=rb.getString("i18n.Test1.password") %><input type="text"/><br/>
<input type="submit"value="<%=rb.getString("i18n.Test1.submit") %>"/>
</form>
</body>
</html>
数字国际化
int i = 1000000;
NumberFormat number = NumberFormat.getCurrencyInstance();
String str = number.format(i);
System.out.println(str);//打印为:¥ 1,000,000.00
number = NumberFormat.getCurrencyInstance(Locale.US);
str = number.format(i);
System.out.println(str);//打印为:$1,000,000.00
float f = 0.5f;
number = NumberFormat.getPercentInstance();
str = number.format(f);
System.out.println(str);//打印为:50%
过滤器(Filter)
WEB开发人员通过Filter(过滤器)技术,对web服务器管理的所有WEB资源,如:JSP,Servlet,静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL基本的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。如果一个资源被两个过滤器同时过滤,先后顺序看在web.xml配置文件中<filter-mapping>标签的先后顺序
ServletAPI中提供了一个Filter接口,如果一个类实现了Filter接口,那么这个类就是一个过滤器类;通过这个Filter,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截
过滤器的声明周期:在servlet创建时创建,并永久驻留在内存中,在servlet销毁时销毁,当过滤器创建时会调用init(FilterConfig arg0);方法;销毁时会调用destroy();方法
示例:
通过过滤器让用户访问的所有资源的编码格式为UTF-8
过滤器中的代码
public class FilterDemo1 implements Filter{
private String code = null;
//当有用于访问/*(此web下资源时)就会访问此过滤器
@Override
public void doFilter(ServletRequest request, ServletResponseresponse,
FilterChain arg2) throws IOException,ServletException {
System.out.println("过滤器被执行了");
//将code设置到到request和response及浏览器的打开模式中
request.setCharacterEncoding(code);
response.setCharacterEncoding(code);
response.setContentType("text/html;charset="+code);
//表示转发到用户所访问的资源上面去
arg2.doFilter(request,response);
}
//此方法会在过滤器创建时调用
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generatedmethod stub
//获取xml文件<init-param>标签中code的value
String code = arg0.getInitParameter("code");
if(code==null)//如果没有配置,就使用UTF-8默认编码
code="UTF-8";
this.code = code;
}
//此方法在过滤器销毁时调用
@Override
public void destroy() {
// TODO Auto-generatedmethod stub
}
}
servlet中代码
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
//接收JSP传入的值
String username = request.getParameter("username");
//打印这个值,查看是否是乱码
response.getWriter().write(username);
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
web.xml文件中注册过滤器
<!-- 注册过滤器 -->
<filter>
<!—给过滤器取一个名字-->
<filter-name>FilterDemo1</filter-name>
<!—过滤器类的完整名称-->
<filter-class>cn.scxh.FilterDemo1</filter-class>
<!-- 设置init方法接收的参数 -->
<init-param>
<param-name>code</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<!-- 当访问此web应用中所有的资源时,都用过滤器拦截 -->
<filter-mapping>
<!—当访问”/*”下面的资源时,都用FilterDemo1这个过滤器拦截-->
<filter-name>FilterDemo1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
JSP中代码
<body>
<!—将text中的中文字符传入Test1这个servlet-->
<form action="${pageContext.request.contextPath }/servlet/Test1" method="post">
<input type="text"name="username"/>
<input type="submit"/>
</form>
</body>
过滤器默认只拦截用户提交的请求,如果为服务器转发,那么默认将不会拦截(但可以手动设置下面的标签进行拦截)
<filter-mapping>
<!—当访问”/*”下面的资源时,都用FilterDemo1这个过滤器拦截-->
<filter-name>FilterDemo1</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher><!—缺省值是request,表示只对用户提交的请求拦截,如果写了其他值,此缺省值将不在生效,但可以手动像这样再定义一次就也可了-->
<dispatcher>FORWARD</dispatcher><!—表示可以拦截转发的请求-->
<dispatcher>INCLUDE</dispatcher><!—表示可以拦截动态包含的请求-->
<dispatcher>ERROR</dispatcher><!—表示可以拦截错误时跳转页面时拦截(在web.xml文件中配置错误页面这种)-->
</filter-mapping>
监听器(8个监听4个事件)
在serlvet中定义了多种类型的监听器,它们用于监听的事件源分别为serlvetContext,HttpSession和ServletRequest这三个对象
servlet规范针对这三个域对象(ServletContext,session,request)上的操作,又把这多种类型的监听器划分为三种类型:
监听三个域对象创建和销毁的事件监听器
监听域对象中的属性的增加和删除的事件监听器
监听绑定到HttpSession域中的某个对象的状态的时间监听器
每个域对象的监听器中传入的ServletContextEvent类型的参数都可以得到当前的域对象,比如session的监听器可以通过传入的参数获取到当前的session域对象。监听域对象属性(映射关系)的事件监听器还可以通过传入参数的getName和getValue方法获取当前操作属性(映射关系)的key和value
servletContext监听器
servletContext对象的创建,当web应用被发布时创建,当卸载web应用时销毁,并会被实现了ServletContextListener接口的监听器所监听到
//监听servletContext域对象创建和销毁的监听器
public class MyServletContextListener implements ServletContextListener{
//当servletContext域对象销毁时此方法会被执行
@Override
public void contextDestroyed(ServletContextEvent event) {
// TODO Auto-generated method stub
System.out.println("servletContext域对象被销毁了");
}
//当servletContext域对象创建时此方法会被执行
@Override
public void contextInitialized(ServletContextEvent arg0) {
// TODO Auto-generatedmethod stub
System.out.println("servletContext域对象被创建了");
}
}
//监听servletContext域对象的属性的监听器(当对sevletContext对象域中的映射关系进行增,删,改时,都会被实现了ServletContextAttributeListener接口的监听器所监听到)
public class MyServletContextAttributeListener implementsServletContextAttributeListener{
//当servletContext域对象中添加了新的映射关系就调用此方法
@Override
public void attributeAdded(ServletContextAttributeEvent event) {
// TODO Auto-generatedmethod stub
System.out.println("servletContext域中增加了此映射关系:"+event.getName()+"="+event.getValue());
}
//当servletContext域对象中删除了映射关系就调用此方法
@Override
public void attributeRemoved(ServletContextAttributeEvent event) {
// TODO Auto-generatedmethod stub
System.out.println("servletContext域中删除了此映射关系:"+event.getName()+"="+event.getValue());
}
//当servletContext域对象中修改了映射关系的值,就调用此方法
@Override
public void attributeReplaced(ServletContextAttributeEvent event) {
// TODO Auto-generatedmethod stub
System.out.println("servletContext域中修改了此映射关系:"+event.getName()+"="+event.getValue());
}
}
<!-- 注册监听器 -->
<listener>
<listener-class>cn.listener.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>cn.listener.MyServletContextAttributeListener</listener-class>
</listener>
session域对象和erquest对象的监听器和上面的方法都一样,不过需要实现的接口不同
监听session对象的创建和销毁需要实现HttpSessionListener接口,监听此域中属性的监听器需要实现HttpSessionAttribteListener接口
session对象的创建,当第一次调用request.getSession()方法时创建session对象;
session对象的销毁,手动调用session对象的invalidate()方法和超时(session对象默认不操作情况下存活30分钟)session对象会被销毁,并会被实现了HttpSessionListener接口的监听器所监听到;
当对session对象域中的映射关系进行 增,删,改时,都会被实现了HttpSessionAttribteListener接口的监听器所监听到
监听request对象的创建和销毁需要实现ServletRequestListener接口,监听此域中属性的监听器需要实现ServletRequestAttributeListener接口
request对象的创建和销毁,当用户每发送一次请求时,request对象就被创建一次,当服务器返回了response对象后此request对象将被销毁,并会被实现了ServletRequestListener接口的监听器所监听到;
当对request对象域中的映射关系进行 增,删,改 时,都会被实现了ServletRequestAttributeListener接口的监听器所监听到
监听绑定到HttpSession域中的某个对象的状态的时间监听器
感知型监听器,谁实现了这个些接口,就可以获取在session域对象中的状态,这两种监听器不需要注册
HttpSessionActivationListener实现了此接口就可以感知自己何时随着HttpSession对象钝化和活化(钝化和活化的意思是当在服务器中暂停了当前的web应用,那么此web应用中的所有域和域中的对象就钝化了,继续当前web应用,那么此web应用中的所有域和域中的对象就活化了)
sessionDidActivate(HttpSessionEvent event);当此对象和session域对象一起活化了时就会调用此方法
sessionWillPassivate(HttpSessionEvent event) ;当此对象和session域对象一起钝化了时就会调用此方法
HttpSessionBindingListener实现了此接口就可以感知自己何时被添加到了session对象,和被session对象删除了
valueBound(HttpSessionBindingEventevent);当自己被添加到了session域中时就会调用此方法
valueUnbound(HttpSessionBindingEventevent);当自己被session域对象移除时就会调用此方法
JDBC大数据(MySQL)
大数据分页
就是指定每次查询的条数
select* from emp limit m,n;
limit关键字写在查询语句的最后面
m表示 从第几行开始显示(0开始算)
n表示 一次显示多少行
大数据存储
在实际开发中,一般不需要把大文本或二进制数据保存到数据库中(会降低查询效率)
大数据也称为LOB,LOB又分为clob和blob
clob用于 存储大文本(字符文本)
blob用于存储二进制数据(图片,声音,二进制文件等)
在MySQL中的clob是Text;在MySQL中的Text和blob的类型又分为:在MySQL中LOB的类型最大的存储大小为4G,所以一般要存储的文件最大不能超过4G
Text:(存储文本文件)
TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT
blob:(存储二进制文件)
TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB
下面说明了每种类型的最大存储大小
列类型 |
存储需求 |
CHAR(M) |
M个字节,0 <= M <= 255 |
VARCHAR(M) |
L+1个字节,其中L <= M 且0 <= M <= 65535(参见下面的注释) |
BINARY(M) |
M个字节,0 <= M <= 255 |
VARBINARY(M) |
L+1个字节,其中L <= M 且0 <= M <= 255 |
TINYBLOB, TINYTEXT |
L+1个字节,其中L < 28 为256B |
BLOB, TEXT |
L+2个字节,其中L < 216 为64KB |
MEDIUMBLOB, MEDIUMTEXT |
L+3个字节,其中L < 224 为16M |
LONGBLOB, LONGTEXT |
L+4个字节,其中L < 232 为4G |
ENUM(‘value1‘,‘value2‘,...) |
1或2个字节,取决于枚举值的个数(最多65,535个值) |
SET(‘value1‘,‘value2‘,...) |
1、2、3、4或者8个字节,取决于set成员的数目(最多64个成员) |
示例1:(向数据库中存储一个.txt的文本文件)
MySQL代码
create table t_temp1(
idint primary key,
data TEXT
)
java代码
//向数据库中存取文本文件
public class Test1 {
//设置数据库配置文件的位置
static{
JDBCUtil3.setPropertiesUrl("db.properties");
}
public static void main(String[] args) {
//把一个文本文件存储数据库中
fun1();
//把这个文本文件取出,并存放到指定的目录
fun2();
}
private static void fun1() {
//获取数据库连接
Connection cn = JDBCUtil3.getNewConnection();
//判断连接是否为null
if(cn == null)
return;
PreparedStatement ps = null;
//传入要存入的文本文件
File file = new File("J:"+File.separator+"1.txt");
//判断这个文件是否存储在
if(!file.exists()){
throw new RuntimeException("没有文件可以存入到数据库中");
}
//创建一个字符流
Reader reader = null;
try {
//设置sql语句
ps = cn.prepareStatement("insert into t_temp1(id, data) values(?,?)");
ps.setInt(1, 1);
//通过文件创建一个输入文件字符流
reader = new FileReader(file);
//将这个流设置为sql的第二个参数
ps.setCharacterStream(2, reader,file.length());
//执行这个sql语句
ps.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
//关闭数据库
JDBCUtil3.close(null, ps, cn);
}
//取出数据中的这个文本文件
private static void fun2() {
//获取数据库连接
Connection cn = JDBCUtil3.getNewConnection();
//判断连接是否为null
if(cn == null)
return;
//创建一个数据库对象
PreparedStatement ps = null;
//创建一个结果集
ResultSet rs = null;
//创建一个输入字符流
Reader reader = null;
//创建一个输出字符流
Writer writer = null;
try {
//或的数据库对象,传入一个sql语句
ps = cn.prepareStatement("select data from t_temp1");
//执行这个sql语句,获得一个结果集
rs = ps.executeQuery();
//遍历这个结果集
while(rs.next()){
//以字符输入流的方式取出data字段中的字符
reader = rs.getCharacterStream(1);
//创建一个输出字符流,并指定要输出到的文件
writer = new FileWriter("J:"+File.separator+"2.txt",true);
//把字符输入流的字符,放入到字符输出流中(就是从数据库中取出数据,放入某个文件中)
//创建一个字符缓存
char[] c = new char[1024];
int len = -1;
//把输入流中取出字符放入字符缓存中,然后输出流就从字符缓存中去取出字符
while((len = reader.read(c))>0){
writer.write(c, 0, len);
}
//刷新一下,好让输出流将字符完全输出到文件中
writer.flush();
}
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
try {
//关闭结果集、字符输入流、字符输出流
rs.close();
reader.close();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
示例2:(向数据库中存储一张.jpg图片的二进制文件)
MySQL代码
createtable t_temp2(
idint primary key,
dataMEDIUMBLOB
)
java代码
//向数据库中存取二进制文件(图片)
public class Test2 {
//设置数据库配置文件的位置
static{
JDBCUtil3.setPropertiesUrl("db.properties");
}
public static void main(String[] args) {
//把一个二进制文件存储数据库中
//fun1();
//把这个二进制文件取出,并存放到指定的目录
fun2();
}
//把一个二进制文件存储数据库中
private static void fun1() {
//获取数据库连接
Connection cn = JDBCUtil3.getNewConnection();
PreparedStatement ps = null;
//创建一个输入字节流
InputStream in = null;
try {
//通过sql语句获取到数据库对象
ps = cn.prepareStatement("insert into t_temp2(id,data) values(?,?)");
//设置id
ps.setInt(1, 1);
//创建一个文件输入字节流
in = new FileInputStream("J:"+File.separator+"1.jpg");
//将这个流设置为sql的第二个参数,in.available()获取流中的字节大小
ps.setBinaryStream(2, in,in.available());
//执行这个sql语句
ps.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
try {
//关闭文件输入字节流
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//取出数据中的这个二进制文件
private static void fun2() {
//获取数据库连接
Connection cn = JDBCUtil3.getNewConnection();
PreparedStatement ps = null;
InputStream in = null;
OutputStream out = null;
ResultSet rs = null;
try {
//通过sql语句获取数据库对象
ps = cn.prepareStatement("select data from t_temp2");
//执行sql语句,并返回一个结果集
rs = ps.executeQuery();
if(rs.next()){
//以字节输入流的方式获取结果集中数据
in = rs.getBinaryStream("data");
//创建一个文件字节输出流,并指定要输出的路径
out = new FileOutputStream("J:"+File.separator+"2.jpg");
//创建一个字节缓存
byte[] b = new byte[1024];
int len = -1;
//让字节输入流的输出缓存到字节缓存区,然后字节输出流就从缓存中取出放入字节输出流中
while((len = in.read(b))>0){
out.write(b,0,len);
}
}
//关闭流
in.close();
//刷新输出流,好让流中的数据全部写入到文件中
out.flush();
out.close();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
//关闭数据库
JDBCUtil3.close(rs, ps, cn);
}
}
}
批处理
当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,提升执行效率
第一种方式:(操作方式不一致,用此方式。如:即有增也有删的多条sql语句)
Statement类 此Statement对象中有一个list集合,将传入的sql缓存起来(通过Connection数据库连接对象的 createStatement()方法获取此对象)
Statement.addBatch( sql ); 将sql语句缓存至Statement的list集合中
Statement.executeBatch(); 依次执行Statement类中list中缓存的所有sql语句
Statement.clearBathc(); 清除list集合中的所有sql语句
第二种方式:(操作方式一致,用此方式。如:只有向数据库中增加的多条sql语句)
Statement的子类PreparedStatement类
可以根据?向sql设置值
示例1:(操作方式不一致,用此方式。如:即有增也有删的多条sql语句)
MySQL代码
createtable temp1(
id int primary key,
data varchar(100)
)
java代码
//批处理(让sql语句一次提交)
public class Test1 {
//设置数据库配置文件的位置
static{
JDBCUtil3.setPropertiesUrl("db.properties");
}
public static void main(String[] args) {
//向temp1表中插入两条数据,并删除第一条
fun1();
}
//向temp1表中插入两条数据,并删除第一条
private static void fun1() {
//准备好三条sql语句
String sql1 = "insert into temp1(id, data) values(1, ‘aa‘)";
String sql2 = "insert into temp1(id, data) values(2, ‘bb‘)";
String sql3 = "delete from temp1 where id=1";
Connectioncn = JDBCUtil3.getNewConnection();
Statement s = null;
try {
//通过cn获取到Statement对象
s = cn.createStatement();
//将sql缓存到Statement对象中的list缓存集合中
s.addBatch(sql1);
s.addBatch(sql2);
s.addBatch(sql3);
//按顺序依次执行list缓存集合中的所有sql语句,并返回的是每条sql影响了的行数 int数组
int[] count = s.executeBatch();
//输出每条sql影响了的行数
for(int i : count){
System.out.println(i);
}
//关闭Statement对象
s.close();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
JDBCUtil3.close(null, null, cn);
}
}
}
示例2:(操作方式一致,用此方式。如:只有向数据库中增加的多条sql语句)
MySQL代码
createtable temp1(
idint primary key,
namevarchar(20)
)
java代码
//批处理(让sql语句一次提交)
//操作方式一致,用此方式。如:只有向数据库中增加的多条sql语句
public class Test2 {
//设置数据库配置文件的位置
static{
JDBCUtil3.setPropertiesUrl("db.properties");
}
public static void main(String[] args) {
//向数据库中添加1000001条记录
fun1();
}
//向数据库中添加1000001条记录
/*
Oracle只需要8秒
MySQL需要4个小时左右
*/
private static void fun1() {
//记住当前的时间
long date = System.currentTimeMillis();
Connection cn = null;
PreparedStatement ps = null;
try {
//获取连接
cn = JDBCUtil3.getNewConnection();
//获取数据库对象
ps = cn.prepareStatement("insert into temp1(id, name) values(?, ?)");
//遍历1000001
for(int i=1;i<=1000001; i++){
//以i变量设置id
ps.setInt(1, i);
//设置name
ps.setString(2, "aaaaa"+i);
//将sql语句存入ps中的list集合缓存中
ps.addBatch();
//如果缓存有2000条sql语句就执行(因为内存不够)
if(i%2000==0){
//执行list集合中的所有sql语句
ps.executeBatch();
//当执行完成后清除list中的所有sql语句
ps.clearBatch();
}
}
//执行后面多出来的1条sql,并清除它
ps.executeBatch();
ps.clearBatch();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
//关闭数据库
JDBCUtil3.close(null, ps, cn);
//输出此次运行共消耗了多少秒
System.out.println("共用:"+(System.currentTimeMillis()-date)/1000+"秒");
}
}
}
当insert插入数据时获取此条记录的主键的值
当此表中的id字段为自增长时,我们在插入一条数据时可以用以下方式获取到插入数据的主键的值
ps = cn.prepareStatement("insert intotemp1(name) values( ?)");
ps.executeUpdate();
//获取到此记录的主键的值,并以结果集返回,只指针对insert语句有效
ResultSetrs = ps.getGeneratedKeys();
System.out.println(rs.getInt(1));//输出此条记录的主键的值
JDBC调用存储过程
接口CallableStatement; 它的父接口为PreparedStatement
获取此接口需调用Connection连接对象的prepareCall(String str);方法来获取;
str参数的规范:“{call 存储过程名(?,?)}”;
用Connection对象的setString(1,”abc”);//来给占位符”?”赋值,对于要输出的参数用registerOutParameter(2,Types.VARCHAR2);来设置,第二个参数用于指定输出参数的类型,Types类中有与数据库数据类型匹配的常量
示例:
存储过程:
delimiter$$//分界的意思,$$符号是敲回车或遇到”;”号不执行,直到再读取到两个$$时执行
CREATEPROCEDURE dempSp(IN inputParam VARCHAR(100), INOUT inOutParam VARCHAR(1000))
begin
select concat(‘welcome-----’, inputParam) into inOutParam; //将“welcome-----”字符串,连接到传入参数inputParam的前面,并赋值给inOutParam,返回
end$$
delimiter;
java代码调用存储过程
//获取数据库连接
cn= JDBCUtil3.getNewConnection();
//获取存储过程对象
CallableStatementcs = cn.prepareCall(“{call demoSp(?, ?)}”);
//给第一个参数设置,如果此参数不用返回的话,就直接传入要传入的参数即可
cs.setString(1,”abc”);
//因第二个参数要返回,所以要指定参数的类型,Types类中有与数据库类型对应的常量
cs.registerOutParameter(2,Types.VARCHAR);
//执行存储过程
cs.execute();
//获取第二个有返回值的参数
Stringvalue = cs.getString(2);
事务
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功
在java中事务是默认自动提交的,当我们只想一条sql语句时,事务直接就提交了
事务的特性
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
一致性(Consistency)
事务必须使数据库从一个一执性状态变换到另外一个一致性状态 (比如转账,A转账给B,转账前和转账后A和B的钱的总和必须一致)
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用于开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要互相隔离
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
隔离性(必须用在事务之中)
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性,不然会引发脏读、不可重复读、幻读
脏读:
脏读是指一个事务读到了另一个事务中未提交的数据
不可重复读:
针对一条记录,每次读取记录前后不一致
虚读:
同一张表,读取记录前后的记录数不一致
隔离级别的分类(级别越高,消耗性能就越高)
READUNCOMMITTED: 此级别 脏读、不可重复读、虚读,都有可能发生(级别最低)
READCOMMITTED: 此级别 能避免发生脏读,但不可重复读、虚读,有可能发生
REPEATABLEREAD: 此级别 能避免发生脏读、不可重复读,但虚读,有可能发生(默认级别,一般也是用此级别,所以一般不用设置隔离级别)
SERIALIZABLE: 此级别 能避免脏读、不可重复读、虚读,但消耗性能最高
MySQL中控制事务隔离级别的语句:
select@@tx_isolation; //查看当前的事务隔离级别
settransaction isolation level 要设置的级别(四种之一); //设置隔离级别,必须在开启事务之前进行设置级别,否则无效
java中控制事务隔离级别的语句:
Connection连接对象的.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); 设置事务的隔离级别,但 必须在开开启手动提交事务(下面这句)之前设置,否则无效;隔离的四大级别在Connection连接类中有与之对应的常量
Connection连接对象的.setAutoCommit(false); 设置数据库不自动提交事务
总结:隔离性,就是事务没提交前,所读取到的数据不能有 脏读、不可重复读、虚读 等异常发生。也就是所当我的事务还没提交前,查询出别人对数据更新后的数据,那么这是不对的,所以就需要更改级别来控制,还没提交事务前读取的数据必须保持一致
数据库中处理事务的关键字是:
start transaction 开启手动提交事务
rollback 回滚事务
commit 提交事务
java中处理事务的方法是:
Connection连接对象的.setAutoCommit(false); 设置数据库不自动提交事务
PreparedStatement数据库对象的.commit(); 提交事务
PreparedStatement数据库对象的.rollback(); 回滚事务
sp = Connection连接对象的.setSavepoint(); 设置回滚点
PreparedStatement数据库对象的.rollback(sp ); 回滚事务到回滚点
示例1:
//当id为1的用户给id为2的用户转账,设置统一提交事务
Connection cn = null;
PreparedStatement ps = null;
try{
//获取连接
cn = JDBC.getConnection();
//设置事务不自动提交
cn.setAutoCommit(false);
//获取数据库操作对象
ps = cn.prepareStatement("update account set money=money-100 where id=1");
//执行sql语句
ps.executeUpdate();
//获取数据库操作对象
ps = cn.prepareStatement("update account set money=money+100 where id=2");
//执行sql语句
ps.executeUpdate();
}catch(Exception e){
e.printStackTrace();
try {
//当上面语句执行异常时回滚事务
cn.rollback();
} catch (SQLException e1) {
// TODO Auto-generatedcatch block
e1.printStackTrace();
}
}finally{
try {
//如果没有异常就直接提交
cn.commit();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
//关闭数据库连接
JDBC.close(null, ps, cn);
}
}
示例2:设置回滚点
//id1给id2转账100,id2又给id3转账100;当id2给id3转账时,发生异常,id1给id2转账照常执行
Connection cn = null;
PreparedStatement ps = null;
Savepoint sp = null;
try{
//获取连接
cn = JDBC.getConnection();
//设置事务不自动提交
cn.setAutoCommit(false);
//获取数据库操作对象
ps = cn.prepareStatement("update account set money=money-100 where id=1");
//执行sql语句
ps.executeUpdate();
//获取数据库操作对象
ps = cn.prepareStatement("update account set money=money+100 where id=2");
//执行sql语句
ps.executeUpdate();
sp = cn.setSavepoint();//设置回滚点
//获取数据库操作对象
ps = cn.prepareStatement("update account set money=money-100 where id=2");
//执行sql语句
ps.executeUpdate();
//int i =1/0;//当此处出错时,回滚点上面的sql照常执行
//获取数据库操作对象
ps = cn.prepareStatement("update account set money=money+100 where id=3");
//执行sql语句
ps.executeUpdate();
}catch(Exception e){
e.printStackTrace();
try {
//当上面语句执行异常时回滚事务
cn.rollback(sp);
} catch (SQLException e1) {
// TODO Auto-generatedcatch block
e1.printStackTrace();
}
}finally{
try {
//如果没有异常就直接提交
cn.commit();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
//关闭数据库连接
JDBC.close(null, ps, cn);
}
}
连接池
就是先创建多个数据库的连接存储在一个容器中,当要用时直接取出,用完后不用关闭,直接再放回到容器中
Tomcat自带的连接池
2.打开apache-tomcat-6.0.29\conf目录中的content.xml文件.
在此文件中配置Resource
1 <?xml version=‘1.0‘encoding=‘utf-8‘?>
2 <Context>
3 <!-- Default set of monitored resources-->
4 <WatchedResource>WEB-INF/web.xml</WatchedResource>
5
6 <!--数据源-->
7 <Resource
8 name="jdbc/DBSource" <!--数据源名称,格式通常为jdbc/xxx名称-->
9 type="javax.sql.DataSource" <!--数据源类型-->
10 username="scott" <!--连接数据库用户名-->
11 password="tiger" <!--连接数据库密码-->
12 maxIdle="2" <!--最大空闲数,数据库连接的最大空闲时间。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制。-->
13 maxWait="5000" <!--最大的等待时间,单位毫秒。如果超过此时间将接到异常。设为-1表示无限制-->
14 url="jdbc:oracle:thin:@localhost:1521:orcl" <!--数据库的连接-->
15 -- driverClassName="oracle.jdbc.driver.OracleDriver" <!—驱动类-->
16 maxActive="10" <!--连接池的最大数据库连接数。设为0表示无限制-->
17 />
18 </Context>
3.在Web项目中的web.xml里面需要引用数据源:
19 <!-- 引用数据源; -->
20 <resource-ref>
21 <description>Oracle dataSource</description>
22 <res-ref-name>jdbc/DBSource</res-ref-name> <!--数据源类型-->
23 <res-type>javax.sql.DataSource</res-type>
24 <res-auth>Container</res-auth>
25 <res-sharing-scope>Shareable</res-sharing-scope>
26 </resource-ref>
4.在java代码中,写一个方法调用一下.
比如:在一个DataSourceDemo
27 package pack.java.datasource.demo;
28
29 import java.sql.Connection;
30 import java.sql.SQLException;
31 import javax.naming.InitialContext;
32 import javax.naming.NamingException;
33 /**
34 * 数据源实例;
35 * @authorzhouhaitao
36 *
37 */
38 public classDataSourceDemo {
39 /**
40 * 根据datasourceName获取数据源;
41 * @paramdsName
42 * @return
43 */
44 public Connection getConnection(){ // String dsName
45 Connectionconnection = null;
46 try {
47 //初始化,获取上下文对象;
48 InitialContext context = new InitialContext();
49
50 //根据datasourceName获取dataSource;
51 javax.sql.DataSource dataSource =(javax.sql.DataSource) context.lookup("java:comp/env"); //+dsName
52
53 try {
54 //从数据源中获取连接;
55 connection =dataSource.getConnection();
56 } catch (SQLException e) {
57 //TODO Auto-generated catch block
58 e.printStackTrace();
59 }
60 } catch (NamingException e) {
61 // TODOAuto-generated catch block
62 e.printStackTrace();
63 }
64 return connection;
65 }
66 }
自定义连接池
其他链接池到E:\practice\MySpace\javautil工作空间中去参考
/*
此类是一个数据库连接池,初始化连接池数量可以再配置文件中设置
此类是一个线程安全的,当线程池中没有链接时,休眠该线程,当连接返回时再唤醒休眠的线程
*/
public class ConnectionPool {
//数据库的用户名
private static String user = null;
//数据库的密码
private static String password = null;
//数据库的连接地址
private static String url = null;
//数据的的驱动地址
private static String driverClassName = null;
//最大存活的连接数量
private static int maxActiveConnectionNumber = 0;
//存储连接的List集合
private static List<Connection> pool = new LinkedList<Connection>();
//标识为,标志pool集合中是否还有连接
private static boolean bool = true;
//初始化数据库连接,数量
static{
//获取文件的输入流
InputStream in = ConnectionPool.class.getClassLoader().getResourceAsStream("db.properties");
//创建一个Properties文件
Properties properties = new Properties();
try {
//从输入流中获取Properties文件的内容
properties.load(in);
//获取Properties文件中的各种信息
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
maxActiveConnectionNumber = Integer.parseInt(properties.getProperty("maxActiveConnectionNumber"));
driverClassName = properties.getProperty("driverClassName");
//加载驱动
Class.forName(driverClassName);
//创建连接,并存储到pool集合中
for(int i=0; i<maxActiveConnectionNumber; i++){
Connection cn = null;
cn = DriverManager.getConnection(url, user, password);
System.out.println(cn);
pool.add(cn);
}
}catch(IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
//此方法由于获取、pool集合中的连接
//此方法是一个线程安全的,当线程池中没有链接时,休眠该线程,当连接返回时再唤醒休眠的线程
public static Connection getConnection(){
//连接
Connection cn = null;
//加锁
ThreadLock.getLock().lock();
//获取状态对象
Condition c = ThreadLock.getCondition();
try{
//判断pool集合中是否有连接
while(!bool){
//如果没有就休眠当前的线程
c.await();//此休眠语句必须放在加了锁的域中
}
//取出pool集合中的一个连接,并在pool集合中移除它
cn = pool.remove(0);
//判断集合中是否还有连接
if(pool.size() <= 0){
//如果没有就将标志位设置为false
bool = false;
}
} catch (InterruptedException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
//解锁
ThreadLock.getLock().unlock();
}
return cn;
}
//此方法用于返回连接,并存储到pool集合中
public static void release(Connection cn){
//加锁
ThreadLock.getLock().lock();
try{
//向pool集合中添加此连接
pool.add(cn);
//并把标识位设为true,表示pool中已有连接
bool = true;
//唤醒状态对象中的某一个线程
ThreadLock.getCondition().signal();//此唤醒语句必须放在加了锁的域中
}finally{
//解锁
ThreadLock.getLock().unlock();
}
}
}
//此类主要用于提供锁和线程状态对象
class ThreadLock{
private static Lock lock = new ReentrantLock();
private static Condition c = lock.newCondition();
//返回一个锁对象
public static Lock getLock(){
return lock;
}
//返回一个线程状态对象
public static Condition getCondition(){
return c;
}
}
DBCP链接池(推荐使用)
DBCP是Apache软件基金组织下的开源链接池实现,用此连接池需导入Commons-dbcp.jar包(连接此的实现)和Commons-pool.jar包(连接池的依赖库)
dbcpconfig.properties文件的配置
#连接设置
#驱动位置
driverClassName=com.mysql.jdbc.Driver
#连接
url=jdbc:mysql://localhost:3306/jdbc
#账户密码
username=root
password=
#<!--初始化连接 -->
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
#driverdefault 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix);(一般不配)
defaultReadOnly=
#driverdefault 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
java代码,用户获取连接和返回连接的工具类
/*
用DBCP连接池获取连接的工具类
*/
public class DBCPUtils {
//定义一个数据源
private static DataSource ds = null;
static{
//将dbcpconfig.properties配置文件加载进来
InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("DBCPConnectionPool/dbcpconfig.properties");
Properties pro = new Properties();
try {
pro.load(in);
//通过将配置文件传入以下类中,从而获得数据源对象
ds = BasicDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
// TODO Auto-generatedcatch block
throw new RuntimeException(e);
}
}
//获取连接
public static Connection getConnection(){
try {
//通过数据源将连接池中的连接取出
return ds.getConnection();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
throw new RuntimeException(e);
}
}
//关闭连接;用DBCP连接池,Connection的方法已被改写,所以调用Connection对象的close()方法时是将连接放回连接池中
public static void close(Connection conn,Statement ps,ResultSet rs){
try {
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
try{
if(ps!=null){
ps.close();
}
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
try{
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
C3P0链接池(推荐使用)
用此连接池需导入c3p0-0.9.1.2.jar包c3p0-0.9.1.2-jdk1.3.jar及c3p0-oracle-thin-extras-0.9.1.2.jar包(此包是Orclase连接时需要的,但一般都导入)
此工具设置配置参数可有在对ComboPooledDataSource象中设置,也可以在当前src或web应用的WEB-INF/classes目录下建立一个c3p0-config.xml (推荐)配置文件进行配置,也可以建立一个c3p0.properties映射文件进行配置,
c3p0的配置文件说明:
示例:
c3p0-config.xml文件的配置
<?xml version="1.0"encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">oracle.jdbc.OracleDriver</property>
<property name="jdbcUrl">jdbc:oracle:thin:@127.0.0.1:1521:orcl</property>
<property name="user">scott</property>
<property name="password">tiger</property>
<property name="acquireIncrement">50</property>
<property name="initialPoolSize">100</property>
<property name="minPoolSize">50</property>
<property name="maxPoolSize">1000</property>
</default-config>
<!-- 当创建 ComboPooledDataSource数据源对象时,传入的参数是“mysql”那么调用的就是下面named-config标签中的配置,
如果没有传入任何参数就调用上面default-config默认配置中的配置-->
<named-config name="mysql">
<!-- 驱动位置 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 设置连接 -->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/db_mydatabase_20140509</property>
<!-- 设置用户 -->
<property name="user">root</property>
<property name="password">123</property>
<!-- 设置初始化连接数量 -->
<property name="initialPoolSize">10</property>
<!-- 设置最大的最大空闲时间 -->
<property name="maxIdleTime">30</property>
<!-- 设置连接的最大数量 -->
<property name="maxPoolSize">100</property>
<!-- 设置连接的最小数量 -->
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</named-config>
</c3p0-config>
java代码,用户获取连接和返回连接的工具类
/*
通过C3P0获取连接的工具类
*/
public class C3P0Utils {
//如果创建数据源是传入的是“mysql”,那么配置文件调用的是名为“mysql”的named-config标签下的配置,
//如果不写调用的default-config(默认)的配置
private static DataSource ds = new ComboPooledDataSource("mysql");
public static Connection getConnection(){
try {
return ds.getConnection();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
throw new RuntimeException(e);
}
}
//关闭连接;用C3P0连接池,Connection的方法已被改写,所以调用Connection对象的close()方法时是将连接放回连接池中
public static void close(Connection conn,Statement ps,ResultSet rs){
try {
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}finally{
try{
if(ps!=null){
ps.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try{
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
元信息
通过元信息;我们可以获取到数据库、结果集、占位符的所有信息
元信息有以下几种:其接口的方法,查看JDK_API
数据库元信息(DatabaseMetaData接口) 通过Connection(连接对象)的getMetaData()方法获取此数据库元信息对象 ;通过数据库元信息对象,可以获取到数据库的名字、连接、驱动名,版本、连接的用户名、是不是只读的,等所有信息
占位符(?)元信息(ParameterMetaData接口) 通过PreparedStatement (数据库操作对象)的getParameterMetaData();方法获取到此占位符的元信息对象;获取此对象前必须设置PreparedStatement(数据库操作对象)的sql语句;通过此占位符元信息可以获取到sql语句中的”?”号占位符的个数;
getParameterCount(); 获取到”?”号占位符的个数
结果集元信息(ResultSetMetaData接口) 通过ResultSet(结果集对象)的getMetaData();方法获取到此结果集元信息对象;通过此对象可以获取到结果的列数、列名、列的类型(返回为int类型与Types中的常量相匹配)等;
示例:
//传入你的ResultSet
public static void printRS(ResultSet rs) throws SQLException
{
//检索此 ResultSet 对象的列的编号、类型和属性。
ResultSetMetaData rsmd = rs.getMetaData();
//得到当前的列数
int colCount = rsmd.getColumnCount();
while(rs.next()) { //while控制行数
for(int i = 1; i <= colCount; i++) {//for循环控制列数
if(i > 1) {
System.out.print(",");
}
//得到当前列的列名
String name = rsmd.getColumnName(i);
//得到当前列的类型,此处得到的是int类型,与Types中的常量相对应
int type = rsmd.getColumnType(i);
//得到当前列的值
String value = rs.getString(i);
System.out.print(name"=" value+”:”+type);
}
System.out.println();
}
}
DBUtils(数据库操作工具)的使用
需要导入commons-dbutils-1.4.jar包,此包中的org.apache.commons.dbutils.QueryRunner类对数库进行增、删、改、查、批处理。其他请查看文档
要创建此类对象,如果用的是Oracle数据库,此构造方需要传入一个true参数 ; 创建此类对象时,可以传入一个数据源(javax.sql. DataSource)对象,如果传入数据源,那么在调用增、删、改、查、批处理,方法时就不需要传入Connection(数据库连接对象了),此org.apache.commons.dbutils.QueryRunner对象会自动到数据源中去获取连接(Connection);否则在调用增、删、改、查、批处理,方法时就需要传入一个数据库连接对象(Connection);
BDUtils的简单操作
增、删、改
//通过DBUtils工具对数据进行增删改查(CRUD)
/*表
create table t_person(
id int(3) primary key auto_increment,
name varchar(7),
age int(3),
gender varchar(2)
);
*/
public class DBCRUD {
/*
要创建此org.apache.commons.dbutils.QueryRunner类对象,如果用的是Oracle数据库,此构造方需要传入一个true参数 ;
创建此类对象时,可以传入一个数据源(javax.sql.DataSource)对象,如果传入数据源,
那么在调用增、删、改、查、批处理,方法时就不需要传入Connection(数据库连接对象了),
此org.apache.commons.dbutils.QueryRunner对象会自动到数据源中去获取连接(Connection);
否则在调用增、删、改、查、批处理,方法时就需要传入一个数据库连接对象(Connection);
*/
//这里直接传入一个数据源对象
private static QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
//对DBUtils工具的简单操作
public static void main(String[] args) throws SQLException, IOException {
System.out.println("--------------------------------增、删、改---------------------------------");
//因为在创建qr对象时,传入了数据源,所以这里不需要才传入Connection(连接对象)了,
//qr会自动去调用数据源的getConnection方法,获取数据库连接对象
//增(如果数据库的字段为日期类型,并且这里的参数传入的是“2014-06-10”这样格式的字符串,DBUtils会自动将字符串转换为日期类型并存入数据库)
qr.update("insert intot_person(name,age,gender) values(?,?,?)", "张三", 20,"男");
//其他的删、改、操作都和上面一样,只不过sql语句不一样,和参数不一样
System.out.println("--------------------------------批处理---------------------------------");
//批处理(向数据库插入10条记录)
//先准备10个参数的二维数组
Object[][] obj = new Object[10][];
for(int i=0; i<10; i++){
obj[i] = new Object[]{"name"+(i+1), i+1, "男"};
}
qr.batch("insert intot_person(name,age,gender) values(?,?,?)", obj);
System.out.println("--------------------------------存入一个文本文件---------------------------------");
//存入一个文本文件
//需要用到Clob接口和SerialClob类(不推荐使用,因为存入时要将文件加载到内存中,大文件有可能导致内存溢出)
//表
//create tablet1(
// id int(3) primary key auto_increment,
// text longtext
//);
//创建一个文件对象
File file = new File("src/cn/com/1.txt");
//通过这个文件对象创建一个文件字符输入流
Reader r = new FileReader(file);
//创一个缓存的字符数组,长度就是文件的长度
char[] c = new char[(int)file.length()];
//将字符输入流的字符,输入到缓存字符数组中
r.read(c);
r.close();
//创建一个SerialClob对象(文本)
Clob clob = new SerialClob(c);
qr.update("insert intot1(text) values(?)", clob);
System.out.println("--------------------------------存入一个二进制文件---------------------------------");
//存入一个二进制文件
//需要用到Blob接口和SerialBlob类(不推荐使用,因为存入时要将文件加载到内存中,大文件有可能导致内存溢出)
//表
// create table t2(
// id int(3) primary keyauto_increment,
// binarySystem longblob
// );
//创建一个字节输入流对象
InputStream in = new FileInputStream("src/cn/com/1.jpg");
//创一个缓存的字节数组,长度就是文件的长度(通过字节流的available()方法也可以获取到文件的字节大小)
byte[] b = new byte[in.available()];
//将字节输入流的数据,输入到缓存字节数组中
in.read(b);
in.close();
//创建一个SerialBlob(二进制)对象
Blob bolb = new SerialBlob(b);
qr.update("insert intot2(binarySystem) values(?)", bolb);
}
}
查
表
createtable t_person(
id int(3) primary key auto_increment,
name varchar(7) not null,
age int(3) not null,
gender varchar(2) not null
)auto_increment=1001;
Bean对象
public class T_person {
private int id = 0;
private String name = null;
private int age = 0;
private String gender = null;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "t_person[id=" + id + ",name=" + name + ",age=" + age
+", gender=" + gender + "]";
}
}
查询操作
//"--------------------------------查(结果处理器)---------------------------------"
/*表
create table t_person(
id int(3) primary key auto_increment,
name varchar(7) not null,
age int(3) not null,
gender varchar(2) not null
)auto_increment=1001;
*/
@Test//BeanHandler将查询的一条结果封装到一个Bean对象中,只适合查询一条结果
public void test1() throws SQLException{
T_person a = qr.query("select * from t_person where id=?", new BeanHandler<T_person>(T_person.class), 1001);//最后一个参数是一个可变参数,依次输入占位符的值
System.out.println("asfd"+a);
}
@Test//BeanListHandler将查询的多条结果封装到一个Bean对象的List集合中,适合查询多条结果
public void test2() throws SQLException{
List<T_person> list = qr.query("select * from t_person", new BeanListHandler<T_person>(T_person.class));
for(T_person a:list)
System.out.println(a);
}
@Test//ArrayHandler:把结果集中的第一行数据转成对象数组。只适合结果集有一条记录的情况
public void test3() throws SQLException{
//该数组中每个元素就是记录的每列的值
Object objs[] = qr.query("select * from t_person where id=?", new ArrayHandler(),1001);//最后一个参数是一个可变参数,依次输入占位符的值
if(objs!=null)
for(Object o:objs)
System.out.println(o);
}
@Test//ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
public void test4() throws SQLException{
//该数组中每个元素就是记录的每列的值
List<Object[]> list = qr.query("select * from t_person", new ArrayListHandler());
for(Object[] objs:list){
System.out.println("-----------------");
for(Object o:objs)
System.out.println(o);
}
}
@Test//ColumnListHandler:将结果集中某一列的数据存放到List中(只能是一列,可以传入字段名,也可以传入数字)
public void test5() throws SQLException{
List<Object> list = qr.query("select * from t_person", new ColumnListHandler("name"));
for(Object o:list)
System.out.println(o);
}
@Test//KeyedHandler(name):将结果集中的每一行数据都封装到一个Map<列名,列值>里,再把这些map再存到一个map里,其“id”列的值为指定的key。
public void test6() throws SQLException{
Map<Object, Map<String,Object>>bmap= qr.query("select * from t_person", new KeyedHandler("id"));
for(Map.Entry<Object, Map<String,Object>> bme:bmap.entrySet()){
Map<String,Object> lmap =bme.getValue();
System.out.println("-----------------");
System.out.println("key:"+bme.getKey());
for(Map.Entry<String,Object>lme:lmap.entrySet()){
System.out.println(lme.getKey()+"="+lme.getValue());
}
/*每次循环输出的结果:
key:1001
-----------------
id=1001
age=20
name=张三
gender=男
*/
}
}
@Test//MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
public void test7() throws SQLException{
Map<String,Object> map= qr.query("select * from t_person where id=?", new MapHandler(), 1001);
for(Map.Entry<String, Object> me:map.entrySet()){
System.out.println(me.getKey()+"="+me.getValue());
}
/*
输出结果:
id=1001
age=20
name=张三
gender=男
*/
}
@Test//MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List集合中
public void test8() throws SQLException{
List<Map<String,Object>> list= qr.query("select * from t_person", new MapListHandler());
for(Map<String,Object> map:list){
System.out.println("-----------------");
for(Map.Entry<String, Object>me:map.entrySet()){
System.out.println(me.getKey()+"="+me.getValue());
}
}
}
@Test//ScalarHandler 适合取一条一列的记录。比如记录总数
public void test9() throws SQLException{
//返回的是一个Long包装类对象(整型数字) 传入的1表示结果集中第1行第1列的值,如果没有返回值,返回的结果就为null,如果不是数字类型就会抛异常
Object obj = qr.query("select count(*) from t_person", new ScalarHandler(1));
//将Long类型的整型数字通过intValue()转换成一个int类型
int num = ((Long)obj).intValue();
System.out.println(num);
}
DBUtils中的事务控制
如果要控制事务,创建org.apache.commons.dbutils.QueryRunner对象时就不能传入源数据对象;在调用QueryRunner方法时,传入Connection连接对象,在外面进行设置连接对象的事务提交,conn.setAutoCommit(false);表示系统不自动提交事务,需我们手动提交事务
案例:A向B转账100元,如果不控制事务,那么当A转出100元钱后,发生了异常,那么B的账户还没加上转出去的钱,那么就会发生数据异常
表:
create table account(
id int(3) primary key auto_increment,
name varchar(7),
money float(10,3)
);
java代码
Dao层(操作数据库)
//DAO层不能牵扯任何的业务逻辑
public class AccountDaoImpl {
//创建一个数据库操作对象
QueryRunner qr = new QueryRunner();
//通过名称找出用户,并返回一个Bean对象
public Account findAccount(String name){
try {
return qr.query(TransactionUtil.getConntcion(),"select * from account wherename=?", new BeanHandler<Account>(Account.class), name);
} catch (SQLException e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
//通过Bean对象,更新这个对象在数据库中的Money
public int update(Account account){
try {
return qr.update(TransactionUtil.getConntcion(),"update account set money=? wherename=?",account.getMoney(),account.getName());
} catch (SQLException e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
}
service层(逻辑处理)
public class AccountServiceImpl {
//创建一个Dao层的对象
AccountDaoImpl dao = new AccountDaoImpl();
//此方法用于转账,传入转出者名称和转入者名称,及转出的钱
public boolean transfer(String sourceAccountName,StringtargetAccountName,float money){
//设置标识位,表示转账是否成功
boolean bool = true;
//设置事务为手动提交
TransactionUtil.startTransaction();
//查找出转出者和转入者的对象
Account sourceAccount = dao.findAccount(sourceAccountName);
Account targetAccount = dao.findAccount(targetAccountName);
//设置转出对象的money(减去要转出的钱)
sourceAccount.setMoney(sourceAccount.getMoney()-money);
//设置转入对象的money(加上要转入的钱)
targetAccount.setMoney(targetAccount.getMoney()+money);
try{
//通过dao对象,对转出对象进行更新操作(在数据库中减去要转出的钱,但没提交事务)
dao.update(sourceAccount);
//int i = 1/0;//模拟异常,如此处发生异常,转出对象的money不会被转出
//通过dao对象,对转入对象进行更新操作(在数据库中加上要转入的钱,但没提交事务)
dao.update(targetAccount);
}catch(Exception e){
//如果发生异常,就回滚事务,并设置标识位为false
TransactionUtil.rollback();
bool = false;
e.printStackTrace();
}finally{
//必须执行事务提交
TransactionUtil.commit();
//将连接返回到连接池中
TransactionUtil.relase();
}
return bool;
}
}
service工具类
//把得到连接及事务有关的方法写到此类中(此类是获取Connection对象的工具类)
public class TransactionUtil {
//创建一个数据源
private static DataSource ds = null;
//创建一个线程范围内的变量
private static ThreadLocal<Connection> tl = newThreadLocal<Connection>();
static{
//读取配置文件,并将dbcpconfig.properties文件中的信息写入到p这个properties文件对象中
InputStream in = TransactionUtil.class.getClassLoader().getResourceAsStream("cn/service/impl1/dbcpconfig.properties");
Properties p = new Properties();
try {
p.load(in);
//通过DBCP连接池,将p配置文件中的信息创建一个数据源对象
ds = BasicDataSourceFactory.createDataSource(p);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//获取数据源对象
public static DataSource getDataSource(){
return ds;
}
//获取连接
public static Connection getConntcion(){
//获取当前线程中的Connection对象
Connection conn = tl.get();
//如果当前线程没有Connection对象就获取一个
if(conn == null){
try {
//通过数据源从线程中获取一个Connection对象
conn = ds.getConnection();
//将获取到的Connection对象保存到
tl.set(conn);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//返回此连接
return conn;
}
//设置事务为手动提交
public static Connection startTransaction(){
//获取到当前线程的Connection对象
Connection conn = TransactionUtil.getConntcion();
try {
//设置这个Connection连接对象的事务为手动提交
conn.setAutoCommit(false);
return conn;
} catch (SQLException e) {
// TODO Auto-generated catch block
throw new RuntimeException();
}
}
//回滚当前线程的Connection连接对象的事务
public static void rollback(){
//获取到当前线程的连接对象
Connection conn = TransactionUtil.getConntcion();
try {
//回滚当前线程的连接对象的事务
conn.rollback();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//提交当前线程的Connection连接对象的事务
public static void commit(){
//获取到当前线程的连接对象
Connection conn = TransactionUtil.getConntcion();
try{
//提交当前线程的Connection连接对象的事务
conn.commit();
}catch(SQLException e){
e.printStackTrace();
}
}
//关闭连接
public static void relase(){
//获取到当前线程的Connection对象
Connection conn = tl.get();
//如果获取到了
if(conn!=null){
try {
//就把连接放回到连接池中
conn.close();
//在当前线程中删除此Connection连接对象
tl.remove();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
测试
//是transaction_control的改进
public class Client {
//转账
public static void main(String[] args) {
AccountServiceImpl asi = newAccountServiceImpl();
//张三向李四转100元
asi.transfer("张三", "李四", 100);
}
}
关系的转换
在java中一对多、多对多及一对一的关系很好提现,然而在数据库中的关系和在java中全然不同,所以在关系转换时一定要注意其细节
一对多关系:
多对多关系:
代码示例在:E:\practice\JSP\practice2\数据库关系;中查看
文件的上传与下载
文件的上传与下载有两个工具可以进行操作smartupload.jar工具和commons-fileupload-1.2.2.jar及依赖包commons-io-2.0.1.jar
文件上传的前提:
1、 form表单的method属性值必须是post
2、 必须设置from表单enctype的值为”multipart/form-data”;(此值决定了post请求方式,请求正文的数据类型)
注意当设置了此属性时在serlvet中用request.getParameter(" ");已经无效,因为是以二进制文件提交的。必须用工具中的request对象的getParameter("");方法来获取
3、 form中提供input表单的type必须是“file”,并且还必须为此表单取一个名字
smartupload.jar工具操作
需要导入smartupload.jar包
jsp代码:
<body>
<%--只能通过http://127.0.0.1:8080/upload/index.jsp 访问,不然IP地址不正确,必须在此处加上enctype="multipart/form-data"表示以二进制方式提交--%>
<form action="${pageContext.servletContext.contextPath}/servlet/UploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name= "username"><br/>
<input type="file"name="file"/><br/>
<input type="submit"value="上传"/>
</form>
</body>
servlet代码:
//访问时只能通过http://127.0.0.1:8080/upload/servlet/UploadServlet不然IP地址不正确
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//创建一个上传对象
SmartUpload su = new SmartUpload();
//初始化上传对象
su.initialize(this.getServletConfig(), request, response);
try {
//将文件加载到内存中
su.upload();
//创建一个文件名称工具类,将客户机的IP地址传入进去
FileNameUtils fn = new FileNameUtils(request.getRemoteAddr());
//fn.getIPTimeRandom()获取一个通过IP和当前时间生成的一个字符串
String fileName = fn.getIPTimeRandom();
//获取到内存中二进制文件对象
File file = su.getFiles().getFile(0);
//将此文件另存为,传入一个目录加文件名;file.getFileExt()获取到此文件的后缀名
file.saveAs("/file/"+fileName+"."+file.getFileExt());
//如果要获取username这个文本中的值,需要用工具中的SmartUpload对象的request对象来获取,此处是获取到username文本框中的值
String username = su.getRequest().getParameter("username");
//打印此用户上传文件名称及后缀名上传成功
response.getWriter().write(username+"上传"+fileName+file.getFileExt()+"成功!");
} catch (SmartUploadException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
java工具代码(通过IP生成一个IP+当前时间+随机数的一个字符串)
public class FileNameUtils {
private String IP = null;
public FileNameUtils(String IP){
this.IP = IP;
}
//通过IP和时间和随机数返回一个字符串
public String getIPTimeRandom(){
//将IP的.去掉
String[] nums = IP.split("\\.");
StringBuffer sb = new StringBuffer();
for(int i = 0; i<nums.length; i++){
//不够三位的在前面补0
sb.append(addZero(nums[i], 3));
}
//增加一个时间字符串
sb.append(getTimeStamp());
//增加一个9以内的随机数
sb.append(new Random().nextInt(9));
//返回此字符串
return sb.toString();
}
//根据当前的时间返回一个字符串
public String getTimeStamp(){
//创建一个格式对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
//创建一个新的时间,并以sdf格式返回一个字符串
return sdf.format(new Date());
}
//将一个字符串,如果此字符串的长度不够len这么长,就在前面补‘0’
private String addZero(String str,int len) {
if(IP==null){
return null;
}
StringBuffersb = new StringBuffer();
sb.append(str);
//判断sb中的长度是否小于len
while(sb.length()<len){
//是的话就在前面加‘0’
sb.insert(0, ‘0‘);
}
//并返回此字符串
return sb.toString();
}
}
commons-fileupload-1.2.2.jar工具操作
需导入commons-fileupload-1.2.2.jar及依赖包commons-io-2.0.1.jar(支持中文名)
上传示例1(直接上传):
jsp代码:
<body>
<!-- 设置提交方式为post,提交的格式为二进制方式 -->
<form action="${pageContext.servletContext.contextPath}/servlet/UploadServlet2"method="post" enctype="multipart/form-data">
用户名:<input type="text" name="name"><br/><!-- 第一个文本域 -->
<input type="file"name="file1"><br/><!-- 第二个是文件上传域 -->
<input type="file"name="file2"><br/><!-- 第三个是文件上传域 -->
<input type="submit"value="提交">
</form>
</body>
Servlet代码:
public voiddoGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//判断enctype的值是否是:"multipart/form-data";是返回true,不是返回false
if(!ServletFileUpload.isMultipartContent(request)){
response.getWriter().write("你的提交方式不是:enctype=\"multipart/form-data\"");
return;
}
//创建工厂,设置环境
DiskFileItemFactory factory = newDiskFileItemFactory();
//通过工厂创建一个上传对象
ServletFileUpload upload = newServletFileUpload(factory);
try {
//获取到request对象中所有的域对象(此处的域表示一个Input标签,但不包括提交标签)
List<FileItem> fileItems = upload.parseRequest(request);
//遍历这些域对象
Iterator<FileItem> it =fileItems.iterator();
while(it.hasNext()){
//获取到当前的域对象
FileItem item = it.next();
//判断item这个域是否是表单字段(文本框)
if(item.isFormField()){
//得到input标签中的name属性的名称(这里是name)
String name = item.getFieldName();
//得到input标签中name属性的值,并设置其编码格式
String value = item.getString("utf-8");
//打印这两个值
System.out.println(name+":"+value);
}else{//也可以判断其是否是一个文件类型:item.isInMemory()
//否则就是一个文件类型
//以输入流的方式获取此域中的内容
InputStream in = item.getInputStream();
//获取此文件的名称(这个名称有可能在前面加上了‘\‘)
String fileName = item.getName();
//切掉此文件名中的’\‘
fileName =fileName.substring(fileName.lastIndexOf("\\")+1);
//获取到file目录的绝对路径(用于存储上传的文件)
String path = this.getServletContext().getRealPath("/file");
//创建一个字节输出流,路径设置为当前的目录+文件名称
OutputStream out = newFileOutputStream(path+"/"+fileName);
//创建一个字节缓存
byte[] b = new byte[1024];
int len = -1;
//将字节输入流中的数据写入到字节输出流中
while((len=in.read(b))>0){
out.write(b,0, len);
}
//刷新和关闭流
out.flush();
in.close();
out.close();
}
}
} catch (FileUploadException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
文件上传中要注意的9个问题
1、 如何保证服务器的安全
把保存上传文件的目录放到WEB-INF目录中。以防止别人上传了一个带有<%Runtime.getRuntime().exe(“shutdown –t 0”);%>代码的JSP文件,要是别人访问这个文件,将会导致服务器关机;此处的红色部分是执行cmd窗口中执行的命令。
2、中文乱码问题
2.1普通字段的中文请求参数
Stringvalue = FileItem.getString("UTF-8");
2.2上传的文件名是中文
解决办法:request.setCharacterEncoding("UTF-8");
3、重名文件被覆盖的问题
System.currentMillions()+"_"+a.txt(乐观)
UUID+"_"+a.txt:保证文件名唯一
4、分目录存储上传的文件
方式一:当前日期建立一个文件夹,当前上传的文件都放到此文件夹中。
方式二:利用文件名的hash码打散目录来存储。
int hashCode = fileName.hashCode();
1001 1010 1101 0010 1101 1100 1101 1010
hashCode&0xf; 0000 0000 0000 0000 0000 0000 0000 1111&
--------------------------------------------------------------
00000000 0000 0000 0000 0000 0000 1010 取hashCode的后4位
0000~1111:整数0~15共16个
10011010 1101 0010 1101 1100 1101 1010
(hashCode&0xf0) 0000 0000 0000 0000 0000 0000 11110000 &
--------------------------------------------------------------
00000000 0000 0000 0000 0000 1101 0000 >>4
--------------------------------------------------------------
00000000 0000 0000 0000 0000 0000 1101
0000~1111:整数0~15共16个
在原有的目录下再更具hash码来创建两个二级目录
由于存储时是通过文件名的hash值来确定文件存储的位置的,所以也需要将其文件名存储到数据库中,以便获取时好通过文件名来获取到此文件的存储目录,及文件名
表的设计格式:
ID(PK) 老文件名 新文件名 user_id
1 1.txt 64f84973-a52b-4b96-af1d-97bd1a03157f_1.txt 1001
当我们取出此新文件名后,我们就可以通过其hash值来获取其存储的一级目录和二级目录的名字了;
5、限制用户上传的文件类型
通过判断文件的扩展名来限制是不可取的。
通过判断其Mime类型才靠谱。FileItem.getContentType();
6、如何限制用户上传文件的大小
6.1单个文件大小限制。超出了大小友好提示
抓异常进行提示:org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException
6.2总文件大小限制。超出了大小友好提示
抓异常进行提示:org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException
7、临时文件的问题
commons-fileupload组件不会删除超出缓存的临时文件。
FileItem.delete()方法删除临时文件。但一定要在关闭流之后。
8、多个文件上传时,没有上传内容的问题
if(fileName==null||"".equals(fileName.trim())){
continue;
}
9、上传进度检测
给ServletFileUpload注册一个进度监听器即可,把上传进度传递给页面去显示
//pBytesRead:当前以读取到的字节数
//pContentLength:文件的长度
//pItems:第几项
public void update(longpBytesRead, long pContentLength,
int pItems){
System.out.println("已读取:"+pBytesRead+",文件大小:"+pContentLength+",第几项:"+pItems);
}
上传示例2(解决了上面9个注意事项后):
JSP代码:
<body>
<!-- 必须设置提交方式为post,提交的格式为二进制方式 -->
<form action="${pageContext.servletContext.contextPath}/servlet/UploadServlet1"method="post" enctype="multipart/form-data">
用户名:<input type="text" name="name"><br/><!-- 第一个文本域 -->
<input type="file"name="file1"><br/><!-- 第二个是文件上传域 -->
<input type="file"name="file2"><br/><!-- 第三个是文件上传域 -->
<input type="submit"value="提交">
</form>
</body>
Servlet代码:
public class UploadServlet1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//判断上传的方式是否是enctype="multipart/form-data" 二进制方式提交的
if(!ServletFileUpload.isMultipartContent(request)){
response.getWriter().write("你提交的方式不是enctype=\"multipart/form-data\"");
return;
}
//创建工厂,设置环境
DiskFileItemFactory factory = newDiskFileItemFactory();
//获取到临时文件"temp"目录的绝对路径
String tempPath = this.getServletContext().getRealPath("/WEB-INF/tempFile");
//创建一个文件作为临时文件的存放位置
File tempFile = new File(tempPath);
//设置临时文件的存放位置,系统默认有10KB的缓存位置,当文件操作10KB,那么将把缓存的文件存入设置的临时目录中
factory.setRepository(tempFile);
//通过工厂创建一个上传对象
ServletFileUploadupload = new ServletFileUpload(factory);
//为此上传对象创建一个监听器
upload.setProgressListener(new ProgressListener(){
//当上传时会调用此方法
@Override//第一个参数为:当前读取到的字节数;第二个参数为:所有文件总共的字节数; 第三个参数是:当前的文件时第几项(第几个表单域)
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("已读取:"+pBytesRead+"字节,所有文件大小:"+pContentLength+"字节,第几项:"+pItems);
//获取到以读取的百分比
double d = (double)pBytesRead/(double)pContentLength;
//设置一个格式,以四舍五入的方式保留三位小数的格式
String format = ".###";
//通过这个格式,创建一个格式对象
DecimalFormat df = newDecimalFormat(format);
//将这个double类型的d只保留3位小数
String str = df.format(d);
//再将其转换为double类型
d = Double.parseDouble(str);
//再乘100,那么就是他的百分比了
System.out.println("已读取:"+d*100+"%");
}
});
try{
upload.setFileSizeMax(1024*1024*2);//设置上传单个文件的大小,单位是资字节(这里表示2M)
upload.setSizeMax(1024*1024*3);//设置上传的所有文件的总大小,单位是字节(这里表示3M)
//获取到request对象中所有的表单域对象
List<FileItem> fileItems = upload.parseRequest(request);
//通过迭代器遍历这些表单域对象
Iterator<FileItem> it =fileItems.iterator();
//记录文件的个数
int fileNumber = 0;
while(it.hasNext()){
FileItem fileItem = it.next();
//判断当前的表单域对象是否是一个普通文本
if(fileItem.isFormField()){
//如果是普通的文本域,就直接打印出提交的值
String name =fileItem.getFieldName();
String value = fileItem.getString("UTF-8");
System.out.println(name+":"+value);
}else{//也可以判断其是否是一个文件类型:item.isInMemory()
//否则就是一个文件类型
fileNumber++;
//判断是否没有提交文件
if(fileItem.getName()==null || "".equals(fileItem.getName())){
System.out.println("第"+fileNumber+"个文件为NULL");
continue;
}
//得到这个文件的类型(一般值为text/plain表示txt文件,image/JPEG 表示图片文件)
String fileType =fileItem.getContentType();
//判断这个类型是不是以“image”开头,以此来判断此文件是否是一张图片
if(!fileType.startsWith("image")){
response.getWriter().write("第"+fileNumber+"个文件不是图片,上传失败!<br/>");
//并继续上传下一张图片
continue;
}
//获取此文件的名称(这个名称有可能在前面加上了‘\‘)
String fileName = fileItem.getName();
//切掉文件名中的‘\’
fileName =fileName.substring(fileName.lastIndexOf("\\")+1);
//以流的方式获取此文件中的内容
InputStream in =fileItem.getInputStream();
//设置存储的位置,得到存储位置的绝对路径
String fileUploadPath = this.getServletContext().getRealPath("/WEB-INF/files");
//产生一个唯一的字符串名字
fileName = UUID.randomUUID()+"_"+fileName;
//通过文件名字,的hash值来设置此文件的存储位置,并获取到一个新的存储路径
String newFileUploadPath =makeStorePath(fileUploadPath, fileName);
//通过存储目录的绝对路径创建一个输出流
OutputStreamout = new FileOutputStream(newFileUploadPath+"\\"+fileName);
//创建一个字节缓存
byte[] b = new byte[1024];
int len = -1;
//将输入流中的数据输出到输出流中
while((len = in.read(b))>0){
out.write(b, 0, len);
}
//将输出流中的数据刷出到文件中
out.flush();
//关闭流
out.close();
in.close();
//一定要在关闭流后清除temp临时目录的这个临时文件,不然此临时文件会一直存储在硬盘上
fileItem.delete();
response.getWriter().write("第"+fileNumber+"图片文件上传成功!<br/>");
}
}
//当单个文件超出设置的大小时,捕获此异常
}catch(org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededExceptione){
response.getWriter().write("上传失败,单个文件不能超过2M<br/>");
//当总文件超出设置的大小时,捕获此异常
}catch(org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededExceptione){
response.getWriter().write("上传失败,总文件大小不能超过3M<br/>");
}catch (FileUploadException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} finally{
//3秒钟后跳转到显示图片页面
response.setHeader("refresh", "3;url=‘"+request.getContextPath()+"/servlet/ShowFile‘");
}
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
//通过文件名字,的hash值来设置此文件的存储位置
private String makeStorePath(String fileUploadPath, StringfileName) {
//获取到此文件名的hash值
int hashValue = fileName.hashCode();
//通过hash值算出第一级目录是什么
int dir1 = hashValue&0xf;
//通过hash值算出第二级目录时什么
int dir2 = (hashValue&0xf0) >> 4;
//将这两个目录拼凑到目录中
String newFileUploadPath = fileUploadPath+"\\"+dir1+"\\"+dir2;
File file = new File(newFileUploadPath);
//判断此目录是否在硬盘是真实存在
if(!file.exists()){
//如果存在就创建此目录(连子目录一起创建)
file.mkdirs();
}
//返回此路径
return newFileUploadPath;
}
}
下载示例:
获取服务中/WEB-INF/files 目录中的所有文件名的servlet
//显示WEB-INF/files目录中的所有文件,并存入到一个map集合中(key存的是加了UUID的名字,value存的是用户提交过来的原名字)
public class ShowFile extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//获取到files目录的绝对路径
String sourceFileName = this.getServletContext().getRealPath("/WEB-INF/files");
//通过这个绝对路径来创建一个File对象
FilesourceFile = new File(sourceFileName);
//创建一个map集合(key存的是加了UUID的名字,value存的是用户提交过来的原名字)
Map<String,String> map = newHashMap<String,String>();
//将这个map集合存储到session域对象中
request.getSession().setAttribute("fileNames", map);
//将所有的文件名都存储到map中
filenamebasename(sourceFile, map);
//转发到显示的jsp页面
request.getRequestDispatcher("/showfilename.jsp").forward(request, response);
}
//将sourceFile目录下的所有文件的名字都存储到map集合中
private void filenamebasename(File sourceFile, Map<String,String> map) {
//判断此File对象是否是一个文件
if(sourceFile.isFile()){
//如果是就将其名称获取到
String UUIDFileName = sourceFile.getName();
//在获取到用户提交的原名字
String oldFileName = UUIDFileName.substring(UUIDFileName.indexOf("_")+1);
//将其存入到map集合中
map.put(UUIDFileName, oldFileName);
}else{//如果是一个文件目录
//就获取到此目录的所有子文件和目录
File[] sourceFiles = sourceFile.listFiles();
//遍历它们
for(File file : sourceFiles){
//再用递归调用
filenamebasename(file, map);
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
显示所有文件的jsp
<%@ page language="java"import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core"prefix="c"%>
<!DOCTYPE HTMLPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP‘showfilename.jsp‘ starting page</title>
</head>
<%--此jsp文件用于显示map中的所有名称 --%>
<body>
<h1>服务器中的图片有:</h1>
<%--遍历session中的map集合 --%>
<c:forEach items="${fileNames }" var="entry">
<%--输出其用户提交的名字 --%>
${entry.value }
<%--重写url,因为要传的UUID名称 可能有中文,所以要重写此url--%>
<c:url value="/servlet/Download"var="url">
<%-- 将其UUID名称当传入传入进去 --%>
<c:param name="UUIDFileName"value="${entry.key }"></c:param>
</c:url>
<%--跳转到处理下载的servlet --%>
<a href="${url }">下载</a>
<br/>
</c:forEach>
</body>
</html>
处理下载请求的servlet
public class Download extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
response.setContentType("text/html; charser=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
//获取到UUIDFileName
String UUIDFileName =(String)request.getParameter("UUIDFileName");
//将其转换为utf-8编码(因为前面在重写url时,已经将参数转换为了ISO-8859-1)
UUIDFileName = new String(UUIDFileName.getBytes("ISO-8859-1"),"utf-8");
//获取到存储文件(files)目录的绝对路径
String sourceFileName = this.getServletContext().getRealPath("/WEB-INF/files");
//通过 UUID名称 得到此文件的数据,在加上此UUID名称,那么就是此文件的绝对路径了
String filePath = getFilePath(sourceFileName,UUIDFileName)+"/"+UUIDFileName;
//通过这个绝对路径创建一个File对象
File file = new File(filePath);
//判断此文件在硬盘上是否存在
if(!file.exists()){
//如果不存在就输入以下字符串,并返回
response.getOutputStream().write("要下载的资源已经不在了!".getBytes("utf-8"));
return;
}
//如果有这个文件
//通过UUID名称 将用提交的原,名称获取到
String oldFileName =UUIDFileName.substring(UUIDFileName.indexOf("_")+1);
//设置此文件为下载,并设置下载的名称;因为下载的名称有可能是中文,所以需要URL加密
response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(oldFileName,"UTF-8"));
//通过此文件的创建一个输入流
InputStream in = new FileInputStream(file);
//获取到response对象的字节输出流
OutputStream out = response.getOutputStream();
//将输入流中的数据,放入到输出流中
byte[] b = new byte[1024];
int len = -1;
while((len=in.read(b))>0){
out.write(b,0,len);
}
//关闭输入流
in.close();
}
//通过UUID文件名,获取到此文件的路径
private String getFilePath(String sourceFileName,StringUUIDFileName) {
//获取到此文件名的hash值
int hash = UUIDFileName.hashCode();
//获取到此hash值的二进制的最后4位
int dir1 = hash&0xf;
//获取到此hash值的二进制的最后5-8位
int dir2 = (hash&0xf0)>>4;
//就获取到了此文件的存储目录了
sourceFileName += "/"+dir1+"/"+dir2;
//返回此目录的绝对路径
return sourceFileName;
}
public void doPost(HttpServletRequest request, HttpServletResponseresponse)
throws ServletException, IOException {
doGet(request, response);
}
}
Email邮件开发
邮件开发用到的协议:SMTP、POP、RFC822、MIME
SMTP: Simle Message Transfer Protocal 简单的传输协议,发送邮件时使用的协议.描述了数据该如何表示, 默认使用的端口:25
POP3: Post Office Protocal 邮局协议,接收邮件时使用的协议,默认使用的端口:110
手动发邮件:
可以在cmd中运行:telnet SMTP服务器名端口号 //telnet是cmd中的一个客户端(和浏览器一样 只不过它是一个文本浏览器,不能查看图片)
示例,连接QQ邮箱:telnet smtp.qq.com587 //如果telnet命令无法使用,就在 控制面板\程序和功能\启用或关闭windows功能 中 打开Telnet客户端功能
邮箱服务器的地址:
如:163邮箱的地址 接收邮件服务器(POP服务器): pop.163.com 发送邮件服务器(SMTP服务器):smtp.163.com
QQ邮箱的地址 POP3服务器(端口995):pop.qq.com SMTP服务器(端口465或587):smtp.qq.com
备注:任何邮箱的SMTP和POP3服务器地址都会在帮助中客户端中找到
邮件发送流程图:
SMTP协议(简单的传输协议,发送邮件时使用,默认使用的端口:25 )
ehlo主机名 //向服务器交互(相当于打开一个入口)
authlogin //服务器返回此语句 表示让输入用户和密码
输入base64编码后的账号和密码 //经过base64编码后的用户名和密码
mailfrom: <oiva@163.com> //发件人邮箱
rcptto: <oiva@163.com> //接收人邮箱
data //表示内容的开始
内容 //内容(正文)
. //‘.’号代表邮件内容的结束(但要单独占一行)
quit //退出
发送邮件内容所遵循的格式:RFC822规范
邮件分为邮件头和邮件体,两者用空行(就是中间再空一行)分割
邮件头:
from //发送者
to //接收者
subject //主题
cc、bcc //cc是抄送(就是给别人也发),bcc是密送(就是秘密的发给谁)
邮件体:
邮件内容
POP3协议(接收邮件时使用的协议,默认使用的端口:110 )
user 用户名 //pop是属于收邮件的协议,登录时的用户和密码不需要base64加密
pass 密码 //密码
stat //统计所有邮件的信息
list 邮件编号 //返回某一个邮件的统计信息
quit //退出
手动发邮件过程(QQ邮箱,一条一条的在cmd中执行)
telnetsmtp.qq.com 587
ehloy460 //与服务器交互
STARTTLS //
authlogin //登录命名
MTAyNzM2MDY5NQ== //这是用户名的base64编码
bGFuMTM2NjgxMjU1MTchIw== //这是密码的base64编码
mailfrom:<1027360695@qq.com> //发件者
rcptto:<1773123249@qq.com> //收件者
data //表示要发送数据
form:1027360695@qq.com //发送者
to:1773123249@qq.com //接收者
subject:这是标题 //标题
cc:110@qq.com //cc给谁(可以不写)
//必须要有个空行
这是内容 //邮件的内容
. //内容结束符
quit //退出
手动接收邮件
telnetpop.qq.com 995
user1773123249 //pop是属于收邮件的协议,登录时的用户和密码不需要base64加密
pass邮箱密码 //密码
stat //统计所有邮件的信息
list1 //返回某一个邮件的统计信息
retr1 //显示某一个邮件的内容
quit //退出
在java中发邮件
//java手动发送电子邮件(用的136邮箱)
public class JavaSendEmail {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("smtp.163.com", 25);//连接136邮箱的smtp服务器,并获取到服务器对象
InputStream in = socket.getInputStream();//获取服务用于返回数据的输入流
InputStreamReader readerIn = newInputStreamReader(in);//将其转换为字符输入流
BufferedReader bufRea = new BufferedReader(readerIn);//因为这里要一行一行的取数据,所以要转换为bufferedReader字符输入缓冲流
//获取向服务器提交数据的字节输出流
OutputStream out = socket.getOutputStream();
System.out.println(bufRea.readLine());//输出服务器返回的第一句话
out.write("ehloy460\r\n".getBytes());//与服务器交互
//服务器返回所有操作命令
System.out.println(bufRea.readLine());
System.out.println(bufRea.readLine());
System.out.println(bufRea.readLine());
System.out.println(bufRea.readLine());
System.out.println(bufRea.readLine());
System.out.println(bufRea.readLine());
System.out.println(bufRea.readLine());
//向服务器提交 我要登录
out.write("authlogin\r\n".getBytes());
//服务器返回 表示 请输入通过base64编码过后的用户名
System.out.println(bufRea.readLine());
//向服务器提交用户名
out.write("MTM2NjgxMjU1MTdAMTYzLmNvbQ==\r\n".getBytes());
//服务器返回 表示 请输入通过base64编码过后的密码
System.out.println(bufRea.readLine());
//向服务器提交密码
out.write("bGFuMTM2NjgxMjU1MTchIw==\r\n".getBytes());
//服务器返回数据,意思是输入发件者
System.out.println(bufRea.readLine());
//向服务器提交发件者
out.write("mailfrom:<13668125517@163.com>\r\n".getBytes());
//服务器返回数据,意思是输入接收者
System.out.println(bufRea.readLine());
//向服务器提交收件者
out.write("rcptto:<1773123249@qq.com>\r\n".getBytes());
//输出服务器返回的数据,
System.out.println(bufRea.readLine());
//向服务器提交data,表示要发送数据
out.write("data\r\n".getBytes());
System.out.println(bufRea.readLine());
//发件的内容必须遵循:RFC822规范
//提交发件者
out.write("form:13668125517@163.com\r\n".getBytes());
//提交接收者
out.write("to:1773123249@qq.com\r\n".getBytes());
//提交标题
out.write("subject:这是标题\r\n".getBytes());
//提交一个空行
out.write("\r\n".getBytes());
//提交邮件的内容
out.write("这是内容.\r\n".getBytes());
//提交. 表示数据提交完毕,注意此处的.表示完成发送邮件,并且此.必须单独占一行
out.write(".\r\n".getBytes());
//关闭socket对象
socket.close();
}
}
用java中JavaMail工具来发送邮件
需要导入mail.jar(注意:Javamail的API依赖jaf (javaActivation Framewrk)框架,还需要导入jaf的jar包,如果用的是JDK1.6及以上版本就不需要导入此包了)
JavaMail中的几大对象
Session //代表邮件的环境
Message //代表邮件对象
BodyPart //代表复杂邮件中的每一部分
Multipart //描述由多个BodyPart组成的邮件的关系
javaMail中需要通过Properties文件配置环境
以下是Properties文件的key的说明
属性名 |
属性类型 |
说明 |
mail.stmp.host |
String |
SMTP服务器地址,如smtp.sina.com.cn |
mail.stmp.port |
int |
SMTP服务器端口号,默认为25 |
mail.stmp.auth |
boolean |
SMTP服务器是否需要用户认证,默认为false |
mail.stmp.user |
String |
SMTP默认的登陆用户名 |
mail.stmp.from |
String |
默认的邮件发送源地址 |
mail.stmp.socketFactory.class |
String |
socket工厂类类名,通过设置该属性可以覆盖提供者默认的实现,必须实现javax.net.SocketFactory接口 |
mail.stmp.socketFactory.port |
int |
指定socket工厂类所用的端口号,如果没有规定,则使用默认的端口号 |
mail.smtp.socketFactory.fallback |
boolean |
设置为true时,当使用指定的socket类创建socket失败后,将使用java.net.Socket创建socket,默认为true |
mail.stmp.timeout |
int |
I/O连接超时时间,单位为毫秒,默认为永不超时 |
代码示例:
//最终版(只能通过QQ服务器进行发送带图片和附件的邮件,用163的服务器发送邮会被163的垃圾邮件过滤器拦截,并导致发送不成功)
//发送一个复杂的邮件
public static void main(String[] args) throws Exception{
Properties props = new Properties();//配置环境
//设置发送邮件的环境的配置文件(如果只将邮件保存到本地,就不需要设置此环境)
props.setProperty("mail.transport.protocol", "smtp");//发送邮件所使用的协议
props.setProperty("mail.host", "smtp.qq.com");//发送服务器的服务器地址
props.setProperty("mail.stmp.socketFactory.port", "25");//设置服务器的端口号,可以不设置,因为默认也为25
props.setProperty("mail.smtp.auth", "true");//请求身份验证
props.setProperty("mail.debug", "true");//调试模式(在控制台输出与服务器交互的过程)
//通过上面的props创建一个环境对象
Session session = Session.getDefaultInstance(props);
//通过环境变量对象创建一个邮件对象
MimeMessage message = new MimeMessage(session);
//创建一个发送对象
Address from = new InternetAddress("1027360695@qq.com");
message.setFrom(from);//将送对象添加到邮件中
//创建一个接收对象
Address to = new InternetAddress("hechunlin1993@163.com");
//Message.RecipientType类中还有几个常量如:CC,BCC等,表示抄送和密送
message.setRecipient(Message.RecipientType.TO, to);//将收件者添加到邮件中
message.setSubject("这是通过javamail发送的一封复杂邮件");
//创建一个内容对象,由于存储文本
MimeBodyPart txt = new MimeBodyPart();
//给此内容中添加值,并设置其打开方式及编码格式
txt.setContent("这是一封复杂的邮件,含有附件和图片等:<img src=‘cid:image‘>这是图片", "text/html;charset=utf-8");
//创建一个内容对象,用于存储图片
MimeBodyPart img = new MimeBodyPart();
//创建一个数据源,图片的数据源
DataSource imgDs = new FileDataSource("C:\\Users\\hcl\\Desktop\\美女.jpg");
//通过数据源创建一个数据处理对象
DataHandler imgDh = new DataHandler(imgDs);
//将数据处理对象添加到内容对象中
img.setDataHandler(imgDh);
//设置这个内容的id,好方便上面的内容引用
img.setContentID("image");
img.setHeader("Content-Type", "image/jpeg;");
//创建一个描述上面文本和图片关系的对象
MimeMultipart mm1 = new MimeMultipart();
mm1.addBodyPart(txt);//在关系中添加txt文本内容对象
mm1.addBodyPart(img);//在关系中添加img图片内容对象
mm1.setSubType("related");//设置正文与图片之间的关系
//将上面的mm1关系看着一体;所以创建一个新的 内容 对象将上面的mm1关键封装成一个整体
MimeBodyPart txtImg = new MimeBodyPart();//创建一个内容对象,用于存放上面txt和img内容(只需要存入上面的txt和img的关系就OK(mm1这个关系))
txtImg.setContent(mm1);//将mm1关系添加到此内容中
//创建一个内容对象,用于存放附件
MimeBodyPart annex = new MimeBodyPart();
//创建一个数据源,附件的数据源
DataSource annexDs = new FileDataSource("C:\\Users\\hcl\\Desktop\\附件.zip");
//创建一个数据处理对象
DataHandler annexDh = new DataHandler(annexDs);
//将数据处理对象添加到内容对象中
annex.setDataHandler(annexDh);
//将数据处理对象的文件名称获取出来
String fileName = annexDh.getName();
//进行编码,以防止附件名中文乱码
fileName = MimeUtility.encodeText(fileName);
//将编码号的文件名称重写设置到内容对象中
annex.setFileName(fileName);
//创建一个描述附件和上面txtImg内容对象的关系的对象
MimeMultipart mm2 = new MimeMultipart();
mm2.addBodyPart(txtImg);//在此关系中,添加txtImg内容对象
mm2.addBodyPart(annex);//在此关系中,添加 附件 内容对象
mm2.setSubType("mixed");//设置正文与附件之间的关系
//这样整个关键就创建出来了,然后再添加到邮件对象中就ok
message.setContent(mm2);
message.saveChanges();//保存以上的修改
//创建发送邮件的对象
Transport t = session.getTransport();
//设置邮箱的账号和密码
t.connect("1027360695@qq.com", "lan13668125517!#");
//将邮件对象添加到发送对象中,并设置发送的对象message.getAllRecipients()是获取message对象中所有的发送对象
t.sendMessage(message, message.getAllRecipients());
//关闭
t.close();
}
标签:des style blog http java color
原文地址:http://blog.csdn.net/u013032887/article/details/37508629