Servlet:
当用户通过浏览器单击某个链接的时候会向指定的服器发送一段文本,告诉服务器请求的是哪个页面,这样的一段文本就是遵循HTTP协议,当服务器得知了网址后就会返回相应的结果给浏览器。
GET方式进行查询:
http://www.baidu.com/s?tn=baidu&ie=utf-8&bs=Java&f=8&rsv_bp=1&rsv_spt=3&wd=Java&rsv_sug3=13&rsv_sug=0&rsv_sug4=1142&rsv_sug1=4&inputT=5016
看如上的地址就是一个以GET方式发送查询请求的过程各个请求参数事面以&进行分隔
GET方式进行请求的时候其提交的数据是不能多于256个字符的
POST提交大量的数据:
其提交的数据是不会显示在浏览器的地址栏中的。
提交的数据类型有两种:
一种:普通文本(ASCII码数据):“application/x-www-form-urlencoded”
二种:文件数据(二进制数据):”multipart/form-data”
一般来说在程序中常常要处理只会是上面的两种
其它的访问方式:
HEADE,DELETE,TRACE,PUT,OPTIONS
Servlet工作流程:
一般来说一个服务器中会有多个servlet,一个数据库
客户端(浏览器)对服务器发起请求(request),这时服务器找到请求的相应的Servlet同时创建一个request对象和一个response对象作为参数调用Servlet的doGet()/doPost(),这时候在doGet()/doPost()当中可能会去数据库中查询相应的数据最后在doGet()/doPost()使用PrintWriter对象生成相应的html数据返回给浏览器
Servlet接口:javax.servlet.Servlet
规定了使用特定的方法来处理特定的请求,作为一个开发人员只需要实现相关的Setvlet方法,在访问Web程序的时候Tomact会去调用这些方法来处理相关的业务
Web程序的目录结构:
根目录下有: META-INF、WEB-INF、…
在WEB-INF下有两个目录:classes(Java类文件夹)、lib(对外部的jar文件的引用文件夹)、web.xml(web程序的配置文件)
WEB-INF:这个文件夹下的文件会被Tomcat隐藏,不能通过浏览器进行直接的访问
Java编写Servlet:
法一:实现Servlet接口:需要实现接口中的很多方法不方便
法二:继承HttpServlet,只需要覆盖需要的方法即可,一般来说是覆盖doGet()、doPost()
对于GET请求:
1,执行getLastModified,如果浏览器发现getLastModified返回值与上一次的返回值是一样的则认为文档没有更新直接从cache中取页面数据,不执行doGet中的逻辑
2,如果getLastModified返回的值是-1则表明文档是时时更新的,每次都会执行doGet中的逻辑
如果是POST请求:
首先不会去执行getLastModified方法,而是直接执行doPost方法中的逻辑
request变量:
对于客户端浏览器发出的请求会被封装成为一个HttpServletRequest对象
这些被封装的数据包括:请求的地址、提交的数据、上传的文件、客户端的IP操作系统…
//返回用于保护servlet认证方案名称
String authType = request.getAuthType();
//获得本地IP,也就是服务器的IP地址
String localAddr = request.getLocalAddr();
//获得本地名称,也就是服务器的名称
String localName = request.getLocalName();
//获得本地端口,也就是Tomcat监听的端口
int localPort = request.getLocalPort();
//获得用户的语言环境
Locale locale = request.getLocale();
//context路径
String contextPath = request.getContextPath();
//获得请求的方式
String method = request.getMethod();
//获得路径信息
String pathInfo = request.getPathInfo();
String pathTranslated = request.getPathTranslated();
//协议名称,这时是HTTP
String protocol = request.getProtocol();
//获得查询字符串,也就是?后的字符串
String queryString = request.getQueryString();
//获得远程IP
String remoteAddr = request.getRemoteAddr();
//获得远程端口,客户端端口
int remotePort = request.getRemotePort();
//远程用户
String remoteUser = request.getRemoteUser();
//客户的SessionID
String requestedSessionId = request.getRequestedSessionId();
//用户请求URI
String requestURI = request.getRequestURI();
//用户请求URL,注意这里返回的是一个StringBuffer,所以需要使用toString()转为String
String requestURL = request.getRequestURL().toString();
//协议头,这里是http
String scheme = request.getScheme();
//服务器的名称
String serverName = request.getServerName();
//服务器的端口
int serverPort = request.getServerPort();
//servlet路径
String servletPath = request.getServletPath();
//返回一个java.security.principal对象包含当前身份验证的用户的名称。如果用户还未被验证,该方法返回null。
Principal userPrincipal = request.getUserPrincipal();
//浏览器支持的格式
String accept = request.getHeader("accept");
//从哪个页面链接到这个页面
String referer = request.getHeader("referer");
//获得User-Agent信息
String userAgent = request.getHeader("user-agent");
//server的信息,getServletContext()会返回一个servletContext对象
StringserverInfo = this.getServletContext().getServerInfo();
服务器对客户端浏览器做出的响应会封装在一个HttpServletResponse对象中
要对浏览器进行相应的操作只需要操作HttpServletResponse对象就可以了,其中有一个getWriter()方法可以获得一个PrintWriter对象,这个对象是一个OutputStream的子类,可以使用这个对象在相应的方法中输入信息即可。
注意:对于PrintWriter对象来说它只能是输出字符型数据,如果要向浏览器输出二进制的数据可以使用HttpServletResponse.getOutputStream()得到一个OutputStream对象,这个对象可以向浏览器输出二进制文件
何为图片验证码:
1, 服务器生成一个包含随机字符串的图片发给客户端
2, 客户端提交相应的数据给服务器
3, 服务器对客户端提交的信息进行相应的验证
一,要生成图片给浏览器则要使用到getOutputStream()生成的对象,把图片输出给浏览器
在web.xml相应的servlet当中我们可以定义一下
<init-param>
<param-name>identityLenparam-name>
<param-value>6param-value>
init-param>
在程序中可以取到这个定义的字符串的长度
int len = Integer.parseInt(this.getInitParameter("identityLen"));
二,客户端请求这个Servlet
三,服务端会生成图片
首先要定义返回给浏览器的文档的类型
// 设置要输出的文档的类型为一个图片
response.setCharacterEncoding("image/jpeg");
调用相应的方法取相应的字符串的方法
// 调用相应的生成随机数的方法取随机数
String identityString = getRandomString(len);
同时会把这个字符串保存到一个session当中以便在后面用户提交数据的时候进行验证
// 把字符串放到session当中
request.getSession(true).setAttribute("identityString", identityString);
大致Servlet的写法如下:
package com.xiaoxie.servlet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.sun.image.codec.jpeg.JPEGCodec;
importcom.sun.image.codec.jpeg.JPEGImageEncoder;
public class IdentityServlet extendsHttpServlet {
privatestatic final long serialVersionUID = 1L;
// 定义一个随机数对象
publicstatic Random random = new Random();
/**
* 生成一个随机的大写字母随机数,要排除掉O,I这两个字母
*
* @param len
* 传入参数指定要获得随机数的长度
* @return 返回随字的指定长度的大写字母的字符串
*/
publicstatic String getRandomString(int len) {
StringBuffersb = new StringBuffer();
while(sb.length() != len) {
//获取一个随机数
if(random.nextInt(2) == 0) {
charch = (char) ('A' + random.nextInt(26));
if(ch == 'O' || ch == 'I')
continue;
sb.append(ch);
}else {
charch = (char) ('0' + random.nextInt(10));
if(ch == '0' || ch == '1')
continue;
sb.append(ch);
}
}
returnsb.toString();
}
/**
* 获得一个随机的颜色
*
* @return 返回一个随机的Color对象
*/
publicstatic Color getRandomColor() {
returnnew Color(random.nextInt(255), random.nextInt(255),
random.nextInt(255));
}
/**
* 返回某个颜色的反色
*
* @param c
* 指定要取反色的颜色
* @return 返回指定颜色的反色
*/
publicstatic Color getReverseColor(Color c) {
returnnew Color(255 - c.getRed(), 255 - c.getGreen(),
255- c.getBlue());
}
publicvoid doGet(HttpServletRequest request, HttpServletResponse response)
throwsServletException, IOException {
//设置要输出的文档的类型为一个图片
response.setCharacterEncoding("image/jpeg");
//获得随机字符串
// 1,获得在web.xml中配置的随机字符的个数
int len = this.getInitParameter("identityLen")==null?6:Integer.parseInt(this.getInitParameter("identityLen"));
//调用相应的生成随机数的方法取随机数
StringidentityString = getRandomString(len);
//把字符串放到session当中
request.getSession(true).setAttribute("identityString",identityString);
//定义图片的宽度和高度
intwidth = 100;
intheight = 30;
//图片的背景色,随机颜色
Colorcolor = getRandomColor();
//获得背景色对应的前景色
Colorreverse = getReverseColor(color);
//创建一个图片,RGB模式
BufferedImagebi = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
//获得一个绘图对象
Graphics2Dg = bi.createGraphics();
//设置字体样式及大小
g.setFont(newFont(Font.SANS_SERIF, Font.BOLD, 20));
//设置颜色(背景色)
g.setColor(color);
//对这个绘图区域进行填充
g.fillRect(0,0, width, height);
//设置要绘的字符的颜色
g.setColor(reverse);
//绘制随机字符串
g.drawString(identityString,14, 22);
//在图片上加上一些干扰点,其像素的大小是一个像素
for(int i = 0, n = random.nextInt(100); i < n; i++) {
g.drawRect(random.nextInt(width),random.nextInt(height), 1, 1);
}
//定义一个对二进制进行输出的对象 ServletOutputStream,注意这里不是PrintWriter(只能输出文本字符串)
ServletOutputStreamout = response.getOutputStream();
//定义一个JPEG编码器
JPEGImageEncoderencoder = JPEGCodec.createJPEGEncoder(out);
//对图片进行编码
encoder.encode(bi);
out.flush();
out.close();
}
}
当访问这个Servlet的时候会显示一个图片在文档当中
关于初始化参数:
在
在servlet当中可以通过getInitParameter(String param)来获得初始化参数的值
如果在获取的时候没有配置指定的param对应的value则会返回null
可以使用getInitParameterNames()返回所有的参数名称,它的结果是一个枚举
对于这些初始化的参数也可以使用ServletConfig对象获得,在Servlet当中可以使用getServletConfig()返回ServletConfig对象
简单的页面访问权限控制:
1, 把要进行访问控制的页面放到WEB-INF文件夹下面,这样可以防止直接通过浏览器输入相应的地址来访问,这个时候直接访问服务器会报404错误
2, 但是对于这个位置的文件可以通过跳转来实现对其的访问
使用请求转发到这个位置则可以对其进行访问
request.getRequestDispatcher(“/WEB-INF/xxx.html”).forward(request,response);
关于上下文参数:
context-param可以使用ServletContext对象来读取
getServletConfig().getServletContext()-àServletContext对象,接下来使用这个对象的getInitParameter(String param)取得指定参数对应的值
getInitParameterNames()取得所有的参数
通过上面的方法可以读取web.xml中的初始化参数(servlet,servletContext)
在Java EE 5之后还会常用一种方法叫资源注入:
在Tomcat启动的时候会把web.xml中配置的信息主动去注入到servlet当中去,而不需要servlet在程序中去主动读web.xml中初始化内容
通过注解@的方式完成
注入在servlet当中有两种写法:一般采用第二种清晰明了
//资源注入一行写法:表示在web.xml中有一个名为hello的注入配置,它会注入给这个servlet中的hello属性
private @Resource(name="hello") String hello;
//注意:注入的时候可以注入一个整数
private @Resource(name="i") int i;
//资源注入分行写法:把注解部分与代码分开为两行来写
@Resource(name="persons")
private String persons;
注入的信息在web.xml中要进行配置
<env-entry>
<env-entry-name>helloenv-entry-name>
<env-entry-type>java.lang.Stringenv-entry-type>
<env-entry-value>注入字符串env-entry-value>
env-entry>
<env-entry>
<env-entry-name>ienv-entry-name>
<env-entry-type>java.lang.Integerenv-entry-type>
<env-entry-value>20env-entry-value>
env-entry>
<env-entry>
<env-entry-name>personsenv-entry-name>
<env-entry-type>java.lang.Stringenv-entry-type>
<env-entry-value>xiaoxie,advent,advnet86env-entry-value>
env-entry>
配置完成后我们可以在响应中进行读取:
//设置与响应的编码:
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//设置响应的文档类型
response.setContentType("text/html");
//获得输出对象
PrintWriter out = response.getWriter();
out.println("");
out.println("");
out.println("
out.println(" ");
//给定一个样式
out.println("");
out.println("注入字符串:\t"+hello+"
");
out.println("注入整数:\t"+i+"
");
out.println("注入字符串数组:\t");
for(String person:persons.split(",")){
out.println("\t"+person);
}
out.println(" ");
out.println("");
out.flush();
out.close();
Web处理提交信息:
客户端提交信息的方式包括GET,POST,它们分别会触发Servlet的doGet,doPost
GET用于从服务器获取到相关信息
POST分为两种:普通的POST提交,文件上传
GET既然是用来获得相关信息的则可以实现简单的搜索引擎
使用GET方式进行提交数据的时候会把提交的name,value加到访问的Servlet路径后面以如下的形式http://serverName/webname/someservlet?name1=value1&name2=value2
在问号后的一串就是查询字符串,使用getQueryString()获得==》name1=value1&name2=value2
要获得相应的name对应的value则可以通过方法getParameter(“name1”)这种方式
GET方式的提交数据有几种方式:
一,通过表单以GET方式提交数据
二,在浏览器中直接写上查询字符串
三,通过指定的链接中带上查询字符串
GET方式提交的数据都会显示在浏览器的地址栏中
GET天生就是用来作为查询用的
页面的主要作用:交互界面
一,显示用户需要查看的内容
二,为用户提供一个环境来提交数据到服务端
Servlet主要作用:处理服务端的业务逻辑
对于GET提交搜索数据如果是中文的默认是会乱码的,因为默认GET的编码方式是IOS-8859-1,此时需要修改server.xml
connectionTimeout=”20000” redirectPort=”8443”URIEncoding=”UTF-8”/> POST提交信息: 提交的相信息是不会显示在浏览器地址栏中的,地址栏中只是显要请求的servlet的路径,并且对于提交的数据是没有长度限制的 可以通过HttpServletRequest的getParameter(Sring param)取得参数param对应的参数值 对于POST提交数据的时候getQueryString()返回的是null的因为对于POST提交时在浏览器的地址栏中是不会有查询串的 鉴于上面的可以知道POST一般是用来提交客户端的数据给服务端的 POST提交数据 除了常见的文本数据还有一个常见的操作就是上传文件 在上会传文件的时候要注意如下: 1, form的enctype属性为multipart/form-data(使用二进制上传),默认的属性值是application/x-www-form-urlencoded(ASCII向服务器发送数据) 2, 使用文件域 html页面可以形如: <html> <head> <title>上传文件title> <meta http-equiv="keywords"content="keyword1,keyword2,keyword3"> <meta http-equiv="description"content="this is my page"> <meta http-equiv="content-type"content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="css/style.css"> head> <body> <div align="center"> <br/> <fieldset style="width: 80%"> <legend>上传文件legend> <br/> <div class='line'> <div align="left" class="leftDiv"> 上传文件一 div> <div align="left" class="rightDiv"> <input type="file" name="file1"class="file"> div> div> <div class='line'> <div align="left" class="leftDiv"> 上传文件二 div> <div align="left" class="rightDiv"> <input type="file" name="file2"class="file"> div> div> <div class='line'> <div align="left" class="leftDiv">上传文件说明一div> <div align='left' class="rightDiv"> <input type="text" name="description1"class="text" /> div> div> <div class='line'> <div align="left" class="leftDiv">上传文件说明二div> <div align='left' class="rightDiv"> <input type="text" name="description2"class="text" /> div> div> <div clas='line'> <div align='left' class='leftDiv'>div> <div align='left' class='RightDiv'><br/> <input type="submit" value="上传文件" class="button"/> div> div> fieldset> div> form> body> html> 服务端实现如下: 首先要知道的是上传文件是以二进制的形式发送数据到服务端的,因而在Servlet中使用getParameter(String param)是没有作用的 要得到正确的数据则需要根据HTTP协议的规定对数据进行解析 常常用到开源框架是:Apache Commons Fileupload /***********************使用Apache Commons Fileupload进行解析***********************/ //使用DiskFileUpload对象解析request(此时这个时候在request是二进制数据) //1,先创建这样一个对象 try { //2,把解析的结果放到一个request当中 List //3,编历所有的fileItem for(FileItem fileItem:list){ if(fileItem.isFormField()){ //如果是文本域 if("description1".equals(fileItem.getFieldName())){ //这个时候就遍历到了名称为description1的文本域 //把文件域中的内容写到相应的list当中 //取文本的时候使用UTF-8进行编码,防止乱码 description.add(fileItem.getString("UTF-8")); } if("description2".equals(fileItem.getFieldName())){ //这个时候就遍历到了名称为description2的文本域 //把文件域中的内容写到相应的list当中 //取文本的时候使用UTF-8进行编码,防止乱码 description.add(fileItem.getString("UTF-8")); } }else{ //文件域 if("file1".equals(fileItem.getFieldName())){ //如果是遍历到了名称为file1的文件上传控件时 //通过客户端文件路径构建File File remoteFile = new File(new String(fileItem.getName().getBytes(),"UTF-8")); //服务端文件 file.add(new File(this.getServletContext().getRealPath("attachment"),remoteFile.getName())); //创建目录 File ftmp = file.get(file.size()-1); ftmp.getParentFile().mkdirs(); //创建文件 ftmp.createNewFile(); //写文件,把FileItem的文件内容写到文件中 InputStream ins = fileItem.getInputStream(); OutputStream ous = new FileOutputStream(ftmp); byte[] buffer = new byte[1024]; int len = 0; while((len = ins.read(buffer))>-1) ous.write(buffer,0,len); ous.close(); ins.close(); } if("file2".equals(fileItem.getFieldName())){ //如果是遍历到了名称为file2的文件上传控件时 //通过客户端文件路径构建File File remoteFile = new File(new String(fileItem.getName().getBytes(),"UTF-8")); //服务端文件 file.add(new File(this.getServletContext().getRealPath("attachment"),remoteFile.getName())); //创建目录 File ftmp = file.get(file.size()-1); ftmp.getParentFile().mkdirs(); //创建文件 ftmp.createNewFile(); //写文件,把FileItem的文件内容写到文件中 InputStream ins = fileItem.getInputStream(); OutputStream ous = new FileOutputStream(ftmp); byte[] buffer = new byte[1024]; int len = 0; while((len = ins.read(buffer))>-1) ous.write(buffer,0,len); ous.close(); ins.close(); } } } } catch (FileUploadException e) { e.printStackTrace(); } out.println("DiskFileUpload dfu = new DiskFileUpload();parseRequest(request);
out.println("
/*********************************Apache Commons Fileupload解析完成********************************************/
Servlet生命周期:
对于古老的CGI来说,每次请求一次CGI程序,服务端会单独开辟一个进程来处理进程,处理完成后再把这个进程销毁。
对于CGI来说这样反复的进行开辟、销毁会占用很大的系统开销,并且如果并发过多的话则很可能使服务端力不从心
Servlet解决CGI这个问题的办法如下:
服务器会在启动(load-on-startup=1)或第一次请求Servlet时(load-on-startup=0)初始化一个Servlet对象,然后我们会使用这个Servlet来处理所有的客户端的请求。当服务器关闭的时候才会去销毁这个Servlet对象,这样就避免了多次去开辟、销毁Servlet的开销
当Servlet对象创建后则表明了Servlet的生命周期开始了至到服务器结束的时候生命周期才结束。在这个过程中无论有多少次的请求过来都只会有这么一个Servlet对象,当多个请求并发的时候服务端会启动多个线程分别执行Servlet的service()方法
生命周期过程如下:
加载Servlet(init(ServletConfig conf))à处理请求(service(req,res))à卸载Servlet(destory())
关于init(ServletConfig conf)
在这个方法中我们可以把初始化资源的代码放在这个方法当中,在HttpServlet类当中还提供了一个更简单的写法init()没有加上参数,HttpServlet在加载的时候会调用这个不带参数的init()方法,对于参数ServletConfig对象可以通过getServletConfig()方法获得,可以在这个方法当中获得全局变量的值这样只需要取一次就可以了之后就不用再去取这个全局的值了
关于destory()
这个方法在销毁资源的时候可以把相关的代码放在这里面
上面的这两个方法在Servlet中只会执行一次
service()方法:在每次请求到来的时候就会去调用这个方法,在这个方法中再通过getMethod()获得是需调用doGet()还是doPost()
@PostConstruct修饰符修饰的方法会在服务器加载Servlet的时候运行,并且只会被调用一次,它会在构造函数之后在init()方法之前调用
@PreDestory修饰符修饰的方法会在destory()方法之后运行但是会在Servlet整个完全卸载之前调用
服务器加载ServletàServlet构造函数—>@PostConstruct修饰的方法àinit(ServletConfigconf)—>service()àdesotry()à@PerDestory修饰的方法à服务器卸载Servlet完毕
Servlet之间的跳转:
1, 转向forward
通过RequestDispatcher对象的forward(req,res)方法来实现
RequestDispatcher对象可以通过HttpServletRequest的getRequestDispatcher()方法来获得
注意:getRequestDispatcher()的参数必须是以”/”这个开头的,”/”:表示的是应用程序的根目录(http://localhost:端口号/应用名)
forward可以跳转到一个Servlet,jsp,一个文件,WEB-INF下的文件,其中前面的两种是非常常见的,也是MVC框架常用的跳转方法
在转发之前可以在request上绑定需要的数据 reqeust.setAttribute(“绑定名”,”绑定值”)
到了指定的servlet或是JSP可以通过request.getAttribute(“绑定名”)来取得,可能需要类型转换
当Servlet进行forward跳转的时候地址栏会显示跳前的地址不会显示跳转过去的那个地址这是由于forward进行跳转是在服务器的内部进行的对于客户端来说它是无法知道的
注意在进行forward进行跳转的时候不能有任何的输出到客户端,也就是不能有任何的out.println()输出
重定向(Redirect):
重定向是利用服务器返回的状态码来实现的,浏览器在请求服务器的时候会返回一个状态码
服务器端会使用HttpServletResponse的setStatus(int status)来设置这个状态码,如果服务器返回的是301或是302则会去重新请求资源
301:表示的是永久的重定向
302:表示的是临时性重定向
通过类似如下的方法进行重定向:response.sendRedirect(request.getContextPath()+filename);
Servlet不安全隐患:
在多用户并发请求的时候只有一个Servlet实例,这个时候Tomcat会产生多个线程来执行Servlet代码因而Servlet会有线程不安全的隐患
Servlet不是线程安全的,多线程并发读写会导致数据不同步的情况发生,解决的办法是尽量不要定义全局的属生而把需要的变量放到doGet()或是doPost()当中去,使用synchronized(属性){}可以解决,但这种解决办法会导致线程的等待
如果有全局的只读属性要加上final进行修饰