servlet原理
1.什么是servlet?
servlet是用java编写的服务器端程序,采用请求——响应模式提供web服务,是运行在服务器端,
2.servlet与JSP有什么关系?
JSP在运行时首先会编译一个servlet,因此,servlet是JSP的基础,
3.前台页面的创建【form表单的编写】:
3.1 form表单的编写方法有doget和dopost方法
两者的区别在于
Servlet的doGet/doPost 是在 javax.servlet.http.HttpServlet 中实现的
doGet:处理GET请求
doPost:处理POST请求
当发出客户端请求的时候,调用service 方法并传递一个请求和响应对象。Servlet首先判断该请求是GET 操作还是POST 操作。然后它调用下面的一个方法:doGet 或 doPost。如果请求是GET就调用doGet方法,如果请求是POST就调用doPost方法。doGet和doPost都接受请求(HttpServletRequest)和响应(HttpServletResponse)。
get只有一个流,参数附加在url后,地址行显示要传送的信息,大小个数有严格限制且只能是字符串,大小限制在1024KB。post的参数是通过另外的流传递的, 不通过url,所以可以很大,也可以传递二进制数据,如文件的上传。
get通过URL提交的参数会显示在地址栏中,这在系统的安全方面可能带来问题;post提交的参数不会显示在地址栏中。这样post就可以提高get的安全性能,避免数据的泄露。
当form框里面的method为get时,执行doGet方法,使用get提交就必须在服务器端用doGet()方法接收;当form框里面的method为post时,执行doPost方法,使用post提交就必须在服务器端用doPost()方法接收。
在request请求里面,编码转换;get方法得到的内容每一个都要进行编码转换,而post方法则只要设置request.setCharacterEncoding("UTF-8")就可以,不要再从request得到的每个数据进行编码转换了。
4.servlet的生命周期?
1)加载和实例化
加载和实例化servlet是由servlet容器实现的,加载servlet之后,容器会通过java的反射机制来创建servlet的实例。
2)初始化
在servlet的实例创建后,容器会调用servlet的init()方法来初始化该servlet对象。初始化的目的是让servlet对象在处理客户端请求前先完成一些初始化工作。对于每个servlet实例,只会调用一次init()方法。
3)服务/执行
当客户端请求到来后,servlet容器首先针对该请求创建servletRequest和servletResponse两个对象,然后servlet容器会自动调用servlet的service()方法来响应客户端的请求,同时把servletRequest和servletResponse两个对象传给service()方法,通过servletRequest对象,servlet实例可以获得客户端的请求信息,处理完请求后则将响应信息放在servletResponse对象中,最后销毁servletResponse和servletResponse对象。
注意:在service()方法调用前,init()方法必须已经成功执行。
4)销毁
当servlet实例需要从服务中移除时,容器会调用destory()方法,让该实例释放掉它所使用的资源,并将实例中的数据保存到持久的存储设备中,之后,servlet实例便会被java的垃圾回收器所回收。
在servlet的整个生命周期中,其初始化和销毁都只发生一次,service()方法的执行次数取决于servlet被客户端所访问的次数。
构造方法:
init():
该方法在javax.servlet.Servlet接口中定义。创建servlet实例时会调用init()方法,在init()方法中完成类似于构造方法的初始化功能,其参数为servletConfig的实例。init()方法结束后,servlet就可以接受客户端请求。
在servlet的整个生命周期中,只执行一次init()方法。
service():
该方法用来响应客户端发出的请求,service()方法使用servletResquest接口和servletResponset接口的对象作为参数,其中,servletRequest对象用来处理请求,servletResponse对象用来发送响应。
service()方法执行时会检查HTTP请求的类型,并相应地调用doGet()、doPost()等方法,因此通常的做法是,不使用service()方法直接使用doGet()和doPost()等方法来处理请求。
service()方法的语法形式如下:
public void service (servletRequest request,ServletResponse response)throws ServletException ,IOEception
其中,resquest是ServletRequest接口的对象,它作为参数来接收和存储客户端请求,response是ServletResponse接口的对象,它包含了servlet做出的响应。
doGet()/doPost():
destroy():
当不再需要servlet实例或重新装入时,destory()方法被调用。使用destory()方法可以释放掉所有在init()方法中申请的资源,一个servlet实例一旦终止,就不允许再次被调用。只能等待被卸载。
destory()方法通常用来执行一些清理任务,在destory()方法中一般安排释放资源的代码。
原理:从Servlet的第一次请求开始(因为这时Servlet对象还没有创建),先执行new的操作(构造方法),再调用init()进行初始化。接着等待请求的到来,一旦有请求到来,service()方法被调用,根据请求类型决定调用doGet()/doPost()。只要有请求到来就会重复service()-->doGet()/doPost()这一过程。当服务器重启或者关闭时,destroy()方法被调用,进行销毁Servlet对象工作
注意:修改了Servlet文件中的代码后要重启Tomcat。
5.数据的传递方式:
<servlet>
<servlet-name>name</servlet-name>
<servlet-class>classname</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>name</servlet-name>
<url-path>/xxxx</url-path>
</servlet-mapping>
7.页面跳转有那几种方式?
内部跳转和外部跳转
对于内部跳转,地址栏没有变化,对于外部跳转,地址栏有变化
请求转发(内部跳转)与重定向(外部跳转)
请求转发:由服务器内部将请求转发给另一个Servlet/JSP资源,由其他资源去处理。在整个过程中,客户端(浏览器)没有意识到请求已转向,请求对象和响应对象都只有一个,浏览器的地址栏没有变化
页面重定向:由服务器向客户端发出一个特殊的响应,指导客户端重新向另一个资源发出新的请求。在这一过程中,客户端是清楚重定向的目标地址的,请求对象和响应对象是重新创建的,地址栏有变化
8.重定向和转发
请求转发,又称内部跳转,用ResquestDispatcher对象的forward()方法。
请求重定向,又称外部跳转,用Response对象的sendRedirect()方法。
通常接收到一个请求并处理后会让客户端访问一个指定的页面,这个过程可以通过两种方式来完成。
建立一个登陆页面如果用户名密码正确则返回登陆成功页面,否则返回登陆失败页面
方式一:
方式二:
请求转发器:通过request对象的getRequestDispatcher(“demo.html”);获取请求转发器对象RequestDispatcher,使用该对象的forward()方法进行转发。
原理是客户端向服务端发一次登陆请求,服务端对其进行处理后,通过应答对象告诉客户端要去的目的,那么客户端就再一次向服务器发一个请求,请求目的页面。
请求完的结果:
在地址栏中显示最后一次服务端返回的请求地址。该地址可以是应用程序内部的资源,也可以是任意一个网络资源。 比如:可以定一个绝对地址百度。也就是地址栏发生变化。
因为是多次请求,所以每一次的请求和应答对象都不同。
请求转发与页面重定向的区别:
1、 请求转发只能将请求转发给同一个WEB应用程序中的其他资源,而重定向不仅可以重定向到同一个WEB应用程序中的其他资源,还可以重定向到其他的应用程序的资源。
2、 请求转发浏览器的URL不会改变,而重定向浏览器的URL会改变
3、 请求转发在页面跳转时使用的是相同的request和response,而重定向在页面跳转时是不同的request和response
4、 请求转发在页面跳转时一共只有1次请求和1次响应,而重定向在页面跳转时一共有2次请求和2次响应
9.解决乱码问题的方法?
解决乱码问题中,设置请求内容的字符编码?
request.setCharacterEncoding(“utf-8”);
解决乱码问题中,设置输出内容及字符编码
response.setContentType(“text/html;charset=utf-8”);
关键代码:
request.setCharacterEncoding(“utf-8”);
response.setContentType(“text/html;charset=utf-8”)或者response.setCharacterEncoding(“utf-8”);
---------------------------------------------------------------------------------------------------------------------
Servlet3.0中Servlet的使用
目录
1.注解配置
2.异步调用
3.文件上传
相对于之前的版本,Servlet3.0中的Servlet有以下改进:
l 支持注解配置。
l 支持异步调用。
l 直接有对文件上传的支持。
在这篇文章中我将主要讲这三方面的应用示例。
1.注解配置
在以往我们的Servlet都需要在web.xml文件中进行配置(Servlet3.0同样支持),但是在Servlet3.0中引入了注解,我们只需要在对应的Servlet类上使用@WebServlet注解进行标记,我们的应用启动之后就可以访问到该Servlet。对于一个@WebServlet而言,有一个属性是必须要的,那就是它的访问路径。@WebServlet中有两个属性可以用来表示Servlet的访问路径,分别是value和urlPatterns。value和urlPatterns都是数组形式,表示我们可以把一个Servlet映射到多个访问路径,但是value和urlPatterns不能同时使用。如果同时使用了value和urlPatterns,我们的Servlet是无法访问到的。下面是一个使用@WebServlet的简单Servlet示例。
- import java.io.IOException;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- *
- * Servlet3.0支持使用注解配置Servlet。我们只需在Servlet对应的类上使用@WebServlet进行标注,
- * 我们就可以访问到该Servlet了,而不需要再在web.xml文件中进行配置。@WebServlet的urlPatterns
- * 和value属性都可以用来表示Servlet的部署路径,它们都是对应的一个数组。
- */
- @WebServlet(name="exampleServlet", urlPatterns="/servlet/example")
- public class ExampleServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
- @Override
- protected void doGet(HttpServletRequest request,
- HttpServletResponse response) throws ServletException, IOException {
- this.doPost(request, response);
- }
- @Override
- protected void doPost(HttpServletRequest request,
- HttpServletResponse response) throws ServletException, IOException {
- response.getWriter().write("Hello User.");
- }
- }
使用@WebServlet时也可以配置初始化参数,它是通过@WebServlet的initParams参数来指定的。initParams是一个@WebInitParam数组,每一个@WebInitParam代表一个初始化参数。
- import java.io.IOException;
- import java.util.Enumeration;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.WebInitParam;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- * 带初始化参数的Servlet
- * WebServlet的属性initParams可以用来指定当前Servlet的初始化参数,它是一个数组,
- * 里面每一个@WebInitParam表示一个参数。
- */
- @WebServlet(value="/servlet/init-param", initParams={@WebInitParam(name="param1", value="value1")})
- public class WebInitParamServlet extends HttpServlet {
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- this.doPost(req, resp);
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- Enumeration<String> paramNames = this.getServletConfig().getInitParameterNames();
- String paramName;
- while (paramNames.hasMoreElements()) {
- paramName = paramNames.nextElement();
- resp.getWriter().append(paramName + " = " + this.getServletConfig().getInitParameter(paramName));
- }
- resp.getWriter().close();
- }
- }
2.异步调用
在Servlet3.0中,在Servlet内部支持异步处理。它的逻辑是当我们请求一个Servlet时,我们的Servlet可以先返回一部分内容给客户端。然后在Servlet内部异步处理另外一段逻辑,等到异步处理完成之后,再把异步处理的结果返回给客户端。这意味着当我们的Servlet在处理一段比较费时的业务逻辑时,我们可以先返回一部分信息给客户端,然后异步处理费时的业务,而不必让客户端一直等待所有的业务逻辑处理完。等到异步处理完之后,再把对应的处理结果返回给客户端。
异步调用是通过当前HttpServletRequest的startAsync()方法开始的,它返回一个AsyncContext。之后我们可以调用AsyncContext的start()方法来新起一个线程进行异步调用。在新线程内部程序的最后我们最好是调用一下当前AsyncContext的complete()方法,否则异步调用的结果需要等到设置的超时时间过后才会返回到客户端。另外当异步调用超时以后会接着调用异步任务,即新起的线程。
- import java.io.IOException;
- import java.io.PrintWriter;
- import javax.servlet.AsyncContext;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- * 支持异步返回的Servlet
- * 对于Servlet的异步返回,首先我们必须指定@WebServlet的asyncSupported属性为true(默认是false),同时在它之前的Filter
- * 的asyncSupported属性也必须是true,否则传递过来的request就是不支持异步调用的。
- *
- */
- @WebServlet(value="/servlet/async", asyncSupported=true)
- public class AsyncServlet extends HttpServlet {
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- this.doPost(req, resp);
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.setContentType("text/plain;charset=UTF-8");
- final PrintWriter writer = resp.getWriter();
- writer.println("异步之前输出的内容。");
- writer.flush();
- //开始异步调用,获取对应的AsyncContext。
- final AsyncContext asyncContext = req.startAsync();
- //设置超时时间,当超时之后程序会尝试重新执行异步任务,即我们新起的线程。
- asyncContext.setTimeout(10*1000L);
- //新起线程开始异步调用,start方法不是阻塞式的,它会新起一个线程来启动Runnable接口,之后主程序会继续执行
- asyncContext.start(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(5*1000L);
- writer.println("异步调用之后输出的内容。");
- writer.flush();
- //异步调用完成,如果异步调用完成后不调用complete()方法的话,异步调用的结果需要等到设置的超时
- //时间过了之后才能返回到客户端。
- asyncContext.complete();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- writer.println("可能在异步调用前输出,也可能在异步调用之后输出,因为异步调用会新起一个线程。");
- writer.flush();
- }
- }
对于一个Servlet如果要支持异步调用的话我们必须指定其asyncSupported属性为true(默认是false)。使用@WebServlet注解标注的Servlet我们可以直接指定其asyncSupported属性的值为true,如:
@WebServlet(value=”/servlet/async”, asyncSupported=true)。而对于在web.xml文件中进行配置的Servlet来说,我们需要在配置的时候指定其asyncSupported属性为true。
- <servlet>
- <servlet-name>xxx</servlet-name>
- <servlet-class>xxx</servlet-class>
- <async-supported>true</async-supported>
- </servlet>
- <servlet-mapping>
- <servlet-name>xxx</servlet-name>
- <url-pattern>xxx</url-pattern>
- </servlet-mapping>
Servlet的异步调用程序的关键是要调用当前HttpServletRequest的startAsync()方法。至于利用返回的AsyncContext来新起一个线程进行异步处理就不是那么的必须了,因为在HttpServletRequest startAsync()之后,我们可以自己新起线程进行异步处理。
- @WebServlet(value="/servlet/async", asyncSupported=true)
- public class AsyncServlet extends HttpServlet {
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- this.doPost(req, resp);
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.setContentType("text/plain;charset=UTF-8");
- final PrintWriter writer = resp.getWriter();
- writer.println("异步之前输出的内容。");
- writer.flush();
- //开始异步调用,获取对应的AsyncContext。
- final AsyncContext asyncContext = req.startAsync();
- //设置超时时间,当超时之后程序会尝试重新执行异步任务,即我们新起的线程。
- asyncContext.setTimeout(10*1000L);
- Runnable r = new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(5*1000L);
- writer.println("异步调用之后输出的内容。");
- writer.flush();
- //异步调用完成
- asyncContext.complete();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- };
- Thread t = new Thread(r);
- //开启自己的线程进行异步处理
- t.start();
- writer.println("可能在异步调用前输出,也可能在异步调用之后输出,因为异步调用会新起一个线程。");
- writer.flush();
- }
- }
异步调用监听器
当我们需要对异步调用做一个详细的监听的时候,比如监听它是否超时,我们可以通过给AsyncContext设置对应的监听器AsyncListener来实现这一功能。AsyncListener是一个接口,里面定义了四个方法,分别是针对于异步调用开始、结束、出错和超时的。
- import java.io.IOException;
- import java.io.PrintWriter;
- import javax.servlet.AsyncContext;
- import javax.servlet.AsyncEvent;
- import javax.servlet.AsyncListener;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- * 支持异步返回的Servlet
- * 对于Servlet的异步返回,首先我们必须指定@WebServlet的asyncSupported属性为true(默认是false),同时在它之前的Filter
- * 的asyncSupported属性也必须是true,否则传递过来的request就是不支持异步调用的。
- *
- */
- @WebServlet(value="/servlet/async2", asyncSupported=true)
- public class AsyncServlet2 extends HttpServlet {
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- this.doPost(req, resp);
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.setContentType("text/plain;charset=UTF-8");
- final PrintWriter writer = resp.getWriter();
- writer.println("异步之前输出的内容。");
- writer.flush();
- //开始异步调用,获取对应的AsyncContext。
- final AsyncContext asyncContext = req.startAsync();
- //设置当前异步调用对应的监听器
- asyncContext.addListener(new MyAsyncListener());
- //设置超时时间,当超时之后程序会尝试重新执行异步任务,即我们新起的线程。
- asyncContext.setTimeout(10*1000L);
- //新起线程开始异步调用,start方法不是阻塞式的,它会新起一个线程来启动Runnable接口,之后主程序会继续执行
- asyncContext.start(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(5*1000L);
- writer.println("异步调用之后输出的内容。");
- writer.flush();
- //异步调用完成
- asyncContext.complete();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- writer.println("可能在异步调用前输出,也可能在异步调用之后输出,因为异步调用会新起一个线程。");
- writer.flush();
- }
- /**
- * 异步调用对应的监听器
- * @author Yeelim
- * @date 2014-2-8
- * @mail yeelim-zhang@todaytech.com.cn
- */
- private class MyAsyncListener implements AsyncListener {
- @Override
- public void onComplete(AsyncEvent event) throws IOException {
- System.out.println("异步调用完成……");
- event.getSuppliedResponse().getWriter().println("异步调用完成……");
- }
- @Override
- public void onError(AsyncEvent event) throws IOException {
- System.out.println("异步调用出错……");
- event.getSuppliedResponse().getWriter().println("异步调用出错……");
- }
- @Override
- public void onStartAsync(AsyncEvent event) throws IOException {
- System.out.println("异步调用开始……");
- event.getSuppliedResponse().getWriter().println("异步调用开始……");
- }
- @Override
- public void onTimeout(AsyncEvent event) throws IOException {
- System.out.println("异步调用超时……");
- event.getSuppliedResponse().getWriter().println("异步调用超时……");
- }
- }
- }
注:
对于正常执行的异步调用而言上述代码中开始是没有监听到的,只有在异步调用超时,重新执行异步任务的时候才有监听到异步调用的开始。不过如果需要监听异步第一次开始的话,我们可以在异步调用开始的时候做相应的监听器监听到异步调用开始时需要做的内容。
3.文件上传
在Servlet3.0中上传文件变得非常简单。我们只需通过request的getPart(String partName)获取到上传的对应文件对应的Part或者通过getParts()方法获取到所有上传文件对应的Part。之后我们就可以通过part的write(String fileName)方法把对应文件写入到磁盘。或者通过part的getInputStream()方法获取文件对应的输入流,然后再对该输入流进行操作。要使用request的getPart()或getParts()方法对上传的文件进行操作的话,有两个要注意的地方。首先,用于上传文件的form表单的enctype必须为multipart/form-data;其次,对于使用注解声明的Servlet,我们必须在其对应类上使用@MultipartConfig进行标注,而对于在web.xml文件进行配置的Servlet我们也需要指定其multipart-config属性,如:
- <servlet>
- <servlet-name>xxx</servlet-name>
- <servlet-class>xxx.xxx</servlet-class>
- <multipart-config></multipart-config>
- </servlet>
- <servlet-mapping>
- <servlet-name>xxx</servlet-name>
- <url-pattern>/servlet/xxx</url-pattern>
- </servlet-mapping>
不管是基于注解的@MultipartConfig,还是基于web.xml文件配置的multipart-config,我们都可以给它们设置几个属性。
l file-size-threshold:数字类型,当文件大小超过指定的大小后将写入到硬盘上。默认是0,表示所有大小的文件上传后都会作为一个临时文件写入到硬盘上。
l location:指定上传文件存放的目录。当我们指定了location后,我们在调用Part的write(String fileName)方法把文件写入到硬盘的时候可以,文件名称可以不用带路径,但是如果fileName带了绝对路径,那将以fileName所带路径为准把文件写入磁盘。
l max-file-size:数值类型,表示单个文件的最大大小。默认为-1,表示不限制。当有单个文件的大小超过了max-file-size指定的值时将抛出IllegalStateException异常。
l max-request-size:数值类型,表示一次上传文件的最大大小。默认为-1,表示不限制。当上传时所有文件的大小超过了max-request-size时也将抛出IllegalStateException异常。
上面的属性是针对于web.xml中配置Servlet而言的,其中的每一个属性都对应了multipart-config元素下的一个子元素。对于基于注解配置的Servlet而言,@MultipartConfig的属性是类型的,我们只需把上述对应属性中间的杠去掉,然后把对应字母大写即可,如maxFileSize。
下面给出Servlet3.0中文件上传的一个示例。
Html:
- <form method="post" action="servlet/upload" enctype="multipart/form-data">
- <input type="file" name="upload"/>
- <input type="submit" value="upload"/>
- </form>
对应Servlet:
- @WebServlet("/servlet/upload")
- @MultipartConfig
- public class FileUploadServlet extends HttpServlet {
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- req.setCharacterEncoding("UTF-8");
- Part part = req.getPart("upload");
- //格式如:form-data; name="upload"; filename="YNote.exe"
- String disposition = part.getHeader("content-disposition");
- System.out.println(disposition);
- String fileName = disposition.substring(disposition.lastIndexOf("=")+2, disposition.length()-1);
- String fileType = part.getContentType();
- long fileSize = part.getSize();
- System.out.println("fileName: " + fileName);
- System.out.println("fileType: " + fileType);
- System.out.println("fileSize: " + fileSize);
- String uploadPath = req.getServletContext().getRealPath("/upload");
- System.out.println("uploadPath" + uploadPath);
- part.write(uploadPath + File.separator +fileName);
- }
- }
对于Servlet3.0中的文件上传还有一个需要注意的地方,当我们把Part写入到硬盘以后,我们原先的Part(也就是之前的临时文件)可能已经删了,这个时候如果我们再次去访问Part的内容的话,那它就是空的,系统会抛出异常说找不到对应的文件。