《JavaWeb从入门到改行》那些年一起学习的Servlet

Servlet是什么?

Servlet是JavaWeb的三大组件之一。 作用类似银行前台接待:

  • 接收请求数据 (询问来办业务的客户)
  • 处理请求 (把客户办理的业务丢给后台服务器执行)
  • 完成响应 (办理好了和客户说一声)

每个Servlet(接待)都不同,是Tomcat(银行大厅内的提示语)负责把不同的请求(客户)发送给(引导给)不同的Servlet(前台接待)。如下图所示:

20170429

Servlet类由我们来编写,但是对象是服务器创建好了的,对象的方法也是由服务器调用。并且一个Servlet类只有一个对象。

Servlet API中各个接口之间的关系

,Servlet API中一共有4个Java包

20170426

如何让浏览器访问Servlet ?

在web.xml文件中指定Servlet的访问路径

<servlet>
  <servlet-name>自己起的名字1</servlet-name>
  <servlet-class>包.类</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>自己起的名字1</servlet-name>
  <url-pattern>/自己起的名字2</url-pattern>
</servlet-mapping>

浏览器访问 ; http://localhost:8080/项目名字/自己起的名字2 。通过和url-pattern匹配获得servlet-class的一个字符串,然后服务器(Tomcat)通过反射Class.forName来得到一个类的对象c ,然后c.newInstance()来获得一个实例。 通过c.getMethod("service",ServletRequest.class,ServletResoponse.class) 来得到一个Method对象m 。然后通过m.invokie()来完成对service方法的调用 。

如果有form表单,则action="/项目名/自己起的名字2" 。

一个Servlet的生命周期

一个Servlet在服务器(tomcat)开启的时或者第一次被访问时被创建,随着tomcat的关闭而销毁。

	   // Servlet创建后执行一次
	   public void init(ServletConfig servletConfig) throws ServletException {}
	   //每次处理请求都会执行一次
	   public void service(ServletRequest request, ServletResponse response)
			throws ServletException, IOException {}
	  //Servlet销毁之前执行一次
	   public void destroy() {}

为什么继承HttpServlet类是实现Servlet最主流的方式 ?

实现Servlet的方式一共有三种:

  • 实现javax.servlet.Servlet接口;
  • 继承javax.servlet.GenericServlet类
  • 继承javax.servlet.http.HttpServlet类;

每创建一个Servlet类都需要手工覆写Servlet接口中的所有方法,而我们需要每次覆写的只是service(ServletRequest req, ServletResponse res),所以其他的覆写会成为程序员的累赘,GenericServlet 抽象类简化了这种麻烦的工作,这个类为Servlet接口和ServletConfig接口提供所有的默认方法实现,并且能够在init()中保存ServletConfig对象。这样,程序员除了覆写需要覆写的方法外,其他方法都可以不用手工编写,一键继承方法都被写好的GenericServlet抽象类就可以了。但是,浏览器与服务器交互,最主流的是http协议,且有7种提交方式,常见的有get和post两种。所以自己创建的Serlvet需要程序员手工编写有对应的get和post方法,这又成了程序员的累赘。于是中间类HttpServlet就应运而生,HttpServlet类为我们提供了这两种方法。

类学习(一):HttpServlet 类

HttpServlet类继承了GenericServlet类,所以拥有Init()、destroy()等生命周期方法。使用HttpServlet类,还需要使用HttpServletRequest对象和HttpServletResponse对象。

根据API提供,HttpServlet类有如下的几种方法:

void doDelete(HttpServletRequest req, HttpServletResponse resp) 
		  Called by the server (via the service method) to allow a servlet to handle a DELETE request. 
protected  void doGet(HttpServletRequest req, HttpServletResponse resp) 
		  Called by the server (via the service method) to allow a servlet to handle a GET request. 
protected  void doHead(HttpServletRequest req, HttpServletResponse resp) 
		  Receives an HTTP HEAD request from the protected service method and handles the request. 
protected  void doOptions(HttpServletRequest req, HttpServletResponse resp) 
		  Called by the server (via the service method) to allow a servlet to handle a OPTIONS request. 
protected  void doPost(HttpServletRequest req, HttpServletResponse resp) 
		  Called by the server (via the service method) to allow a servlet to handle a POST request. 
protected  void doPut(HttpServletRequest req, HttpServletResponse resp) 
		  Called by the server (via the service method) to allow a servlet to handle a PUT request. 
protected  void doTrace(HttpServletRequest req, HttpServletResponse resp) 
		  Called by the server (via the service method) to allow a servlet to handle a TRACE request. 
protected  long getLastModified(HttpServletRequest req) 
		  Returns the time the HttpServletRequest object was last modified, in milliseconds since midnight January 1, 1970 GMT. 
protected  void service(HttpServletRequest req, HttpServletResponse resp) 
		  Receives standard HTTP requests from the public service method and dispatches them to the doXXX methods defined in this class. 
 void service(ServletRequest req, ServletResponse res) 
		  Dispatches client requests to the protected service method. 

HttpServlet的运作机理是服务器调用生命周期方法service(ServletRequest req, ServletResponse res) ,将参数req和res强转为http协议相关的类型,然后调用本类的 service(HttpServletRequest req, HttpServletResponse resp) ,这个方法会通过request得到当前请求的请求方式是get(超链接)还是post(表单),然后根据请求方式再调用doGet()方法或者doPost()方法。如下图时序图所示:

20170427

接口学习(一):ServletConfig

一个Servlet的配置信息如下

	<servlet>
		<servlet-name>xxx</servlet-name>
		<servlet-class>cn.itcast.web.servlet.AServlet</servlet-class>
		<init-param> //初始化参数1
			<param-name>p1</param-name> //初始化参数名字
			<param-value>v1</param-value>//初始化参数值
		</init-param>
		<init-param>//初始化参数2
			<param-name>p2</param-name>
			<param-value>v2</param-value>
		</init-param>
	</servlet>

Servlet容器在初始化期间将的以上配置信息加载到ServletConfig对象。ServletConfig对象是由服务器创建的,然后传递给Servlet的init()方法。所以ServletConfig对象可以使用ServletConfig接口中的方法在init()方法中返回配置信息。根据API提供的ServletConfig接口的方法有如下:

  1. String getServletName() 返回servlet-name中的内容 (无用)
  2. ServletContext getServletContext() 返回Servlet上下文内容 (有用)
  3. String getInitParameter(String name) 通过参数名称返回对应的值 (无用)
  4. Enumeration getInitParameterName() 返回所有初始化参数的名称集合 (无用)
public void init(ServletConfig servletConfig) throws ServletException {	
		// 获取初始化参数
		System.out.println(servletConfig.getInitParameter("p1"));
		System.out.println(servletConfig.getInitParameter("p2"));		
		// 获取所有初始化参数的名称,集合打印
		Enumeration e = servletConfig.getInitParameterNames();
		while(e.hasMoreElements()) {
			System.out.println(e.nextElement());
		}

接口学习(二)_域对象学习(一): ServletContext

ServletlContext是javaWeb 四大域对象之一,常称为application域。 一个项目中只有一个ServletContext对象 ,一个项目中的多个Servlet共用一个ServletContext对象。

ServletContext对象与Tomcat同生共死,在Tomcat启动时创建,在Tomcat关闭时死亡。常称为application(应用程序)

获取ServletContext :

  • HttpServlet继承了GenericServlet,GenericServlet继承了Servlet,而Servlet中有个方法 getServletContext(), 所以在HttpServelt中,可以用this.getServletContext()来获取ServletContext
  • 在init()中通过ServletConfig对象的getServletContext()方法来获取ServletContext
  • 在HttpSession接口中,有getServletContext()来获取ServletContext
  • 在ServletContextEvent类中,提供了getServletContext()来获取ServletContext

ServletContext接口中的一些方法

 java.lang.String getRealPath(java.lang.String path)   //获取带有盘符的项目资源路径
		  Gets the real path corresponding to the given virtual path. 
 java.util.Set<java.lang.String> getResourcePaths(java.lang.String path) //获取当前项目某一路径下的所有资源
		  Returns a directory-like listing of all the paths to resources within the web application whose longest sub-path matches the supplied path argument.          
 java.io.InputStream getResourceAsStream(java.lang.String path)  //获取资源的路径,再创建输入流对象
		  Returns the resource located at the named path as an InputStream object. 

application域存取数据功能 代码演示:

/*
 * AServlet把数据存在域对象ServletContext中,BServlet从域对象中取出数据。
 *  由此可见,ServletContext对象一个项目只有一个,多个Servlet共用一个ServletContext对象,可以在两个Servlet之间传递数据,成为Servlet们之间交流的桥梁
*/
public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/*
		 * 1. 获取ServletContext对象
		 * 2. 调用其setAttribute()方法完成保存数据
		 */
		ServletContext application = this.getServletContext();
		application.setAttribute("name", "张三");
	}
}
public class BServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/*
		 * 1. 获取ServletContext对象
		 * 2. 调用其getAttribute()方法完成获取数据
		 */
		ServletContext application = this.getServletContext();
		String name = (String)application.getAttribute("name");
		System.out.println(name);
	}
}

application域获取项目文件路径 代码演示:

public class DServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/*
		 * 获取项目index.jsp的带有盘符的路径
		 */
		String path = this.getServletContext().getRealPath("/index.jsp");
		System.out.println(path);		
		/*
		 * 获取资源的路径后,再创建出输入流对象!
		 */
		InputStream input = this.getServletContext().getResourceAsStream("/index.jsp");		
		/*
		 * 获取WEB-INF路径下所有资源的路径!
		 */
		Set<String> paths = this.getServletContext().getResourcePaths("/WEB-INF");
		System.out.println(paths);
	}
}

F:\apache-tomcat-7.0.73\webapps\ServletDemo_01\index.jsp
[/WEB-INF/lib/, /WEB-INF/classes/, /WEB-INF/web.xml]

接口学习(三)_域对象学习(二)客户的请求:HttpServletRequest

HttpServletRequest接口继承了ServletRequest接口,与ServletRequest不同的是,HttpServletRequest是与 Http协议 相关的接口 。

在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给service()方法,这说明在service()方法中可以通过request对象来获取请求数据。

ServletlRequest是javaWeb 四大域对象之一 。 封装了客户端所有的请求数据(请求行、请求头、空行、请求体(GET没有请求体)),通过接口中的方法,可以获取这些请求数据

HttpServletRequest提供了请求转发和请求包含功能。

API中对HttpServletRequest接口提供的有关获取信息的方法 :

获取常用信息相关的方法

 java.lang.String getRemoteAddr()  //获取客户端IP  ,在父接口ServletRequest中
		  Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. 
  java.lang.String getMethod()          //获取请求方式,Servlet用获取的请求方法来判断是调用doGet还是doPost方法
		  Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.

获取请求头相关的方法

 java.lang.String getHeader(java.lang.String name)   //获取Http请求头,适用于单值
		  Returns the value of the specified request header as a String. 
 java.util.Enumeration<java.lang.String> getHeaders(java.lang.String name)   //获取Http请求头,适用于多值请求头
		  Returns all the values of the specified request header as an Enumeration of String objects.          

获取URL相关的方法

http://localhost:8080/servletTest/AServlet?username=xxxx&password=xxx
 java.lang.String getQueryString()  //获取参数部分 username=xxxx&password=xxx
		  Returns the query string that is contained in the request URL after the path. 
 java.lang.String getRequestURI() //获取请求URL  servletTest/AServlet
		  Returns the part of this request  URL from the protocol name up to the query string in the first line of the HTTP request. 
 java.lang.StringBuffer getRequestURL() //获取请求URLhttp://localhost:8080/servletTest/AServlet
		  Reconstructs the URL the client used to make the request.   
 /**
 * 以下方法来自于父接口 ServletRequest
 */        
 java.lang.String getScheme()  //获取协议,http
		  Returns the name of the scheme used to make this request, for example, http, https, or ftp. 
 java.lang.String getServerName() //获取服务器名,localhost
		  Returns the host name of the server to which the request was sent. 
 int getServerPort() //获取服务器端口,8080
		  Returns the port number to which the request was sent. 
 ServletContext getServletContext() //获取项目名,/servletTest
		  Gets the servlet context to which this ServletRequest was last dispatched. 

获取请求参数相关的方法 ,请求参数是客户端发送给服务器的参数,如果在请求体中就是post请求,如果在URL之后,就是get请求

 java.lang.String getParameter(java.lang.String name)   //获取指定名称的请求参数值,适用于单值请求参数
		  Returns the value of a request parameter as a String, or null if the parameter does not exist.          
 java.util.Map<java.lang.String,java.lang.String[]> getParameterMap() //获取所有请求参数封装到Map中,其中key为参数名,value为参数值。
		  Returns a java.util.Map of the parameters of this request. 
 java.util.Enumeration<java.lang.String> getParameterNames() //获取所有请求参数名称
		  Returns an Enumeration of String objects containing the names of the parameters contained in this request. 
 java.lang.String[] getParameterValues(java.lang.String name) //获取指定名称的请求参数值,适用于多值请求参数,如表单中多个爱好
		  Returns an array of String objects containing all of the values the given request parameter has, or null if the parameter does not exist. 

小案列 :

/**
*  获取常用信息案例,并通过User-Agent识别用户浏览器类型
*/
public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String addr = request.getRemoteAddr(); //获取客户端的IP地址				
						   System.out.println("请求方式:" + request.getMethod());//获取请求方式
						   /**
						   * 通过User-Agent识别用户浏览器类型
						   */
		String userAgent = request.getHeader("User-Agent");//获取名为User-Agent的请求头!	
		// 是否包含字符串Chrome,如果包含,说明用户使用的是google浏览器
		if(userAgent.toLowerCase().contains("chrome")) {
			System.out.println("您好:" + addr + ", 你用的是谷歌");
		} else if(userAgent.toLowerCase().contains("firefox")) {
			System.out.println("您好:" + addr + ", 你用的是火狐");
		} else if(userAgent.toLowerCase().contains("msie")) {
			System.out.println("您好:" + addr + ", 你用的是IE");
		}
	}
}
/**
*  Referer请求头实现防盗链
* 防盗链 : 防止一个网站的超链接,链接到别的网站的资源上。
*/
public class BServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//使用Referer请求头,来防盗链
				 String referer = request.getHeader("Referer");//输出链接网站的来源
		//System.out.println(referer);    
		if(referer == null || !referer.contains("localhost")) {
			response.sendRedirect("http://www.baidu.com");
		} else {
			System.out.println("hello!");
		}	
	}
}
/**
*  演示 服务器获取客户端的参数
*/
/**
* 客户端对服务器的get请求(超链接)和post请求(表单)
*/
<a href="/servletTest/CServlet?username=admin&password=123456">点击这里</a>  
<form action="/servletTest/CServlet" method="post">
		  用户名:<input type="text" name="username"/><br/>
		  密 码:<input type="password" name="password"/><br/>
		  爱 好:<input type="checkbox" name="hobby" value="lq"/>篮球
						 <input type="checkbox" name="hobby" value="ymq"/>羽毛球
						<input type="checkbox" name="hobby" value="ppq"/>乒乓球
  <br/>
  <input type="submit" value="提交"/>
</form>
/**
* Servlet获取请求参数
* 
*/
public class CServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/**
		*  在doGet方法中获取get请求方法的参数
		*/		
		System.out.println("GET: " + request.getParameter("username"));
		System.out.println("GET: " + request.getParameter("password"));
	}
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String username = request.getParameter("username");//获取单值
		String password = request.getParameter("password");
		String[] hobby = request.getParameterValues("hobby");//获取多值		
		System.out.println(username + ", " + password + ", " + Arrays.toString(hobby));		
		/*
		 *  获取所有请求参数的名称
		 */
		Enumeration names = request.getParameterNames();
		while(names.hasMoreElements()) {
			System.out.println(names.nextElement());
		}		
		/*
		 * 获取所有请求参数,封装到Map中
		 */
		Map<String,String[]> map = request.getParameterMap();
		for(String name : map.keySet()) {   //获取参数名字
			String[] values = map.get(name);   //根据参数名字获取参数值
			System.out.println(name + "=" + Arrays.toString(values));
		}
	}
}

请求转发和请求包含

请求转发/包含是对于一个请求来说,如果一个Serlvet 完成不了的话,需要转发给另一个Servlet帮助完成。即,一个请求 跨多个Servlet 。无论是请求转发还是请求包含,都在一个请求和一个响应范围内! 使用同一个request和response!如下图所示

20170430

请求转发和请求包含的区别是: 请求转发中,AServlet把响应体(要处理的任务)全部交给BServelt去做 ,AServlet即使做了工作,也是无效的。所以 ,虽然AServlet可以设置响应体,但是没有用,不能在客户端输出。 BServlet设置的响应体才能才客户端输出 。 两者都可以设置响应头,并且都对客户端有用。注意: 当AServlet做的工作非常多的时候,会抛出异常,而且能在客户端有效。
请求包含中,AServelt与BServlet共同完成响应体,所以既能在客户端输出响应头,也可以输出响应体。 在实际项目中,如果是请求转发,则AServlet不应该再做任何工作,完全让BServlet完成。

 RequestDispatcher getRequestDispatcher(java.lang.String path) //使用request获取RequestDispatcher对象,方法的参数是被转发或包含的Servlet的Servlet路径
		  Returns a RequestDispatcher object that acts as a wrapper for the resource located at the given path. 
/**
* 在RequestDispatcher接口中有如下方法:
*/          
void forward(ServletRequest request, ServletResponse response) //转发
		  Forwards a request from a servlet to another resource (servlet, JSP file, or HTML file) on the server. 
 void include(ServletRequest request, ServletResponse response) //包含
		  Includes the content of a resource (servlet, JSP page, HTML file) in the response. 

ServletRequest域对象(request域) :

ServletRequest是域对象,常称为request域。 请求转发和请求包含都需要在多个Servlet之间进行,request域正好是多个的Servlet之间交流的桥梁。request域通常用在两个Servlet的请求转发和请求包含中。

小案列 :

/**
 * 请求转发
 * OneServlet转发到TwoServlet中
 */
public class OneServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setHeader("aaa", "AAA");//设置响应头	
				  // 设置响应体,这个响应体是无效的,因为在后面有个转发
		response.getWriter().print("a");		
		/*
		 * 向reuqest域中添加一个属性
		 */
		request.setAttribute("username", "zhangsan");
		/**
		* 请求转发 
		*等同与调用TwoServlet的service()方法。
		*/
		request.getRequestDispatcher("/TwoServlet").forward(request, response);		
	}
}
public class TwoServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		System.out.println(request.getAttribute("username"));
		/**
						   *  设置响应体,有效,可以在客户端显示 
		*/
		response.getWriter().print("hello TwoServlet!");//设置响应体
	}
}

请求编码:

浏览器所用的超链接和表单的请求参数一般是utf-8编码,但是服务器默认使用iso-8859-1来处理参数,产生乱码。

 void setCharacterEncoding(java.lang.String env) 
		  Overrides the name of the character encoding used in the body of this request. 

对于post请求,只需要在获取参数getparameter()之前调用 request.setCharacterEncoding("utf-8")即可。对于get请求,则用如下方式:

String name = request.getParameter("name");//假设得到的是name 
/**
* 反编
*/
byte[] bytes = name.getBytes("ISO-8859-1");
name =  new String (bytes,"utf-8");

接口学习(四)_服务器的响应:HttpServletResponse

HttpServletResponse接口继承了ServletResponse接口,与ServletResponse不同的是,HttpServletResponse是与 Http协议 相关的接口 。

HttpServletResponse对象是客户端向服务器请求时,服务器创建的一个对象,然后传入到HttpServlet类的service(HttpServletRequest req, HttpServletResponse resp) 方法中,是Servlet用来向客户端响应的"有效工具" 。 响应的对象是客户端,也就是给浏览器发送信息。以下的方法都是与浏览器打交道。

API中对HttpServletResponse接口提供某些重要方法 :

发送状态码有关的方法, 发送状态码是Servlet利用response向浏览器发送错误/成功的状态 :

 void sendError(int sc)   //发送错误状态码 ,如404
		  Sends an error response to the client using the specified status code and clears the buffer. 
 void sendError(int sc, java.lang.String msg)   // 发送错误状态码 ,可以带一个错误信息
		  Sends an error response to the client using the specified status and clears the buffer. 
 void setStatus(int sc)    // 发送成功的状态码 
		  Sets the status code for this response. 

响应头有关的方法,响应头是Servlet利用response向浏览器发送怎样的响应,浏览器能认识这些头 :

 void setHeader(java.lang.String name, java.lang.String value)   // 适用于单值的响应头
		  Sets a response header with the given name and value. 
 void setIntHeader(java.lang.String name, int value)     //适用于单值的Int类型的响应头
		  Sets a response header with the given name and integer value. 
void setDateHeader(java.lang.String name, long date)  //适用于单值的毫秒类型的响应头
		  Sets a response header with the given name and date-value.           

<meta>标签可以代替响应头

重定向相关的方法,重定向是Servlet利用response向浏览器重定向信息 :

 void sendRedirect(java.lang.String location) 
		  Sends a temporary redirect response to the client using the specified redirect location URL and clears the buffer. 

响应正文(响应体)有关的方法,响应体是Servlet利用response向浏览器发送内容:

既然是发送内容,所以用到response提供的两个响应流,根据异常的提示,可以发现在一个请求中,不能同时使用这两个流!这两个响应流的方法在HttpServletResponse的父类ServletResponse中。

 ServletOutputStream getOutputStream()    //用来向客户端发送字节数据
		  Returns a ServletOutputStream suitable for writing binary data in the response. 
		  Throws:              //用这个方法需要抛出一个异常
					  UnsupportedEncodingException - if the character encoding returned by getCharacterEncoding cannot be used 
					  IllegalStateException - if the getOutputStream method has already been called for this response object 
					  java.io.IOException - if an input or output exception occurred
					  java.io.PrintWriter getWriter() 
					  Returns a PrintWriter object that can send character text to the client.        
 java.io.PrintWriter getWriter()  //用来向客户端发送字符数据!需要设置编码
		  Returns a PrintWriter object that can send character text to the client.                        

小案列 :

/*
 *  利用响应头实现重定向
 * 重定向 : 本来访问的http;//localhost:8080/test/AServlet , 但是会访问到http;//localhost:8080/test/BServlet 
*/
public class BServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		System.out.println("BServlet");		
		/*
		 * 重定向:
		 * 1. 设置Location头
		 * 2. 发送302状态码
		 */
		response.setHeader("Location", "/项目名/要重定向的页面");
		response.setStatus(302);  		
		/*
		 *快捷的重定向方法
		 */
		response.sendRedirect("/项目名/要重定向的页面");
		/*
		 * 定时重定向,5秒后跳转到要重定向的页面
		 * 设置名为Refresh的响应头
		 */
		response.setHeader("Refresh", "5;URL=/项目名/要重定向的页面");
	}
}

/**
* 禁用浏览器缓存
* 浏览器缓存: 当代码变化后,浏览器访问到内容依然是缓存的内容(上一次访问的内容)
*/
public class FServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/*
		 * 利用Cache-Control、pragma、expires这三个头
		 */
		response.setHeader("Cache-Control", "no-cache");
		response.setHeader("pragma", "no-cache");
		response.setDateHeader("expires", -1);
	}
}
public class BServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/**
						   * 响应字符数据 
		*/
		PrintWriter writer = response.getWriter();
		writer.print("world");
		/**
							*响应字节数据 1 _字符数据的另一种实现方式,等同于上一个的响应字符数据 的功能
							* 1. 将字符串变成字节
							* 2. 用字节流输出
						   */
		String s = "Hello World";
		byte[] bytes = s.getBytes();
		response.getOutputStream().write(bytes);
		/**
		 * 响应字节数据 2_把一张图片读取到字节数组中
		 * IOUtils类是自己编写的一个小工具,需要导入commons-io-1.4.jar, 可以到文章尾部寻找下载链接
		 */
		String path = "F:/a.jpg";
		FileInputStream in = new FileInputStream(path);
				 byte[] bytes = IOUtils.toByteArray(in);//读取输入流内容的字节到字节数组中。
				 response.getOutputStream().write(bytes);
		//IOUtils.copy(in, response.getOutputStream());
	}
}

响应编码 :

服务器通过response.getWriter()向客户端发送响应字符数据默认编码是iso-8859-1,但是一般浏览器会把数据当成gbk来解码。 所以服务器要设置编码为utf-8。 并且用响应头来告诉浏览器,浏览器就会用urf-8来解码。这样两边都是utf-8。就不会产生乱码 。

void setCharacterEncoding(java.lang.String charset)  //设置编码
		  Sets the character encoding (MIME charset) of the response being sent to the client, for example, to UTF-8. 
response.setHeader("Content-type","text/html;charset=utf-8")或者response.setContentType("text/html;charset=utf-8")   //    设置响应头   

值得注意的是,设置响应头的方法已经包含了对编码的设置,所以在用getWriter()方法给客户端响应之前,都调用一次response.setContentType("text/html;charset=utf-8") 即可 。

类学习(二):Cookie

Cookie是HTTP协议制定的!由服务器创建保存到客户端浏览器的一个键值对!是保存在客户端

浏览器第一次访问服务器时,服务器会随着对客户端的响应将Cookie发送给浏览器,浏览器把Cookie保存起来,下次浏览器请求服务器时会将上一次请求中得到的Cookie随着请求归还给服务器,服务器会获取这个浏览器的Cookie,这时服务器就知道你就是上次的那个浏览器。在这个过程中,服务器通过Set-Cookie响应头将Cookie的键值发送给浏览器,代码是response.addHeader("Set-Cookie", "键=值") 。浏览器通过Cookie请求头归还给服务器,代码是request.getHeader("Cookie")。 但是这种有头的代码都不是当下流行的方式,在HttpServletRequest 和 HttpServletResponse中有专门的两个便捷的方法来替代以上的两个方法: repsonse.addCookie()向浏览器保存Cookie和request.getCookies()获取浏览器归还的Cookie 。在API中,对这两个方法的定义如下:

/**
* 方法来自于HttpServletRequest接口和HttpServletResponse接口
*/
 void addCookie(Cookie cookie) 
		  Adds the specified cookie to the response. 
 Cookie[] getCookies()   //返回的是Cookie数组
		  Returns an array containing all of the Cookie objects the client sent with this request. 

另外,由于浏览器的速度与空间占有问题,1个Cookie的大小是有限制的(小于4KB),1个服务器最多向1个浏览器保存20个Cookie,1个浏览器最多可以保存300个Cookie ,现在的浏览器会多多少少的超出一点。

由于Cookie的特性,服务器可以使用Cookie来跟踪客户端的状态,所以多用来实现购物车系统,显示上次登录名等功能 。

	/**
	 * Cookie的保存
	 */
	Cookie cookie1 = new Cookie("aaa", "AAA");
	response.addCookie(cookie1);
	/**
	* 获取Cookie
	*/
	Cookie[] cookies = request.getCookies();
	if(cookies != null) {
		for(Cookie c : cookies) {  //遍历获取Cookiede 键值
			out.print(c.getName() + "=" + c.getValue() + "<br/>");
		}
	}

Cookie的生命周期

Cookie的存活是有时长的,可以通过如下的方法设置Cookie的存活时常 。(以秒为单位)

 void setMaxAge(int expiry) 
		  Sets the maximum age in seconds for this Cookie. 
		   maxAge>0:浏览器会把Cookie保存到客户机硬盘上,有效时长为maxAge的值决定。
		   maxAge<0:Cookie只在浏览器内存中存在,当用户关闭浏览器时,浏览器进程结束,同时Cookie也就死亡了。
		   maxAge=0:浏览器会马上删除这个Cookie!原理是把浏览器保存的同名Cookie给覆盖掉

Cookie的路径;

Cookie的路径并不是设置这个Cookie在客户端的保存路径,而是由服务器创建Cookie时设置 。当浏览器访问服务器某个路径时,需要归还哪些Cookie给服务器呢?这由Cookie的path决定。浏览器访问服务器的路径,如果包含某个Cookie的路径,那么就会归还这个Cookie。

比如浏览器保存某个Cookie的页面是path1(http://localhost:8080/CookieTest/cookie/cookieDemo_01.jsp) ,那么这个Cookie的路径就是path2(http://localhost:8080/CookieTest/cookie),而浏览器访问服务器的path3(http://localhost:8080/CookieTest/cookie/a.jsp ),很明显,path3包含path2,那么当浏览器访问服务器这个路径时,需要归还这个Cookie 。

接口学习(五)_域对象学习(三):HttpSession

什么是一次会话? 什么是会话跟踪?

会话是客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。类似于路人甲碰到路人乙,会话开始,期间的谈话可以有多次提问和回答,直到两人离开,会话结束 。 下一次再碰到,即使另一个会话了。在JavaWeb中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束,也就是说会话范围是某个用户从首次访问服务器开始,到该用户关闭浏览器结束!(需要注意的是: 并不是重启浏览器,比如打开一个浏览器,session域开始生效,然后再打开相同的浏览器,还是能够获取到这个session,但是不能用其他浏览器,不支持跨浏览器 。只要第一个浏览器不关闭,那就不会死亡)

会话跟踪技术是在一个会话的多个请求中共享数据。这也是Session域的作用。会话路径技术使用Cookie或session完成。

HttpSession是什么 ?HttpSession为什么依赖Cookie?原理又是什么?

区别于Cookie,HttpSession是由JavaWeb提供的,用来会话跟踪的类。session是服务器端对象,保存在服务器端!而Cookie是服务器保存在客户端浏览器中的类 。 HttpSession锁定的是用户,一个Session只有一个用户 。HttpSession 底层依赖Cookie,或是URL重写!

为什么说底层依赖Cookie ? 当浏览器访问服务器时,服务器在第一次获取session时(request.getSession()) , 会根据sessionId来选择要不要创建session,规则如下:

  • 如果sessionId不存在,创建session,把session保存起来,把新创建的sessionId保存到Cookie中
  • 如果sessionId存在,通过sessionId查找session对象,如果没有查找到,创建session,把session保存起来,把新创建的sessionId保存到Cookie中
  • 如果sessionId存在,通过sessionId查找到了session对象,那么就不会再创建session对象了。

创建的session是保存在服务器端,而给客户端的是session的id(一个cookie中保存了sessionId)。客户端通过Cookie带走的是sessionId,而数据是保存在session中。当客户端再次访问服务器时,在请求中会带上sessionId,而服务器会通过sessionId找到对应的session,而无需再创建新的session。

Servlet通过request.getSession();来获得Session对象。该方法如下:

 HttpSession getSession() 
		  Returns the current session associated with this request, or if the request does not have a session, creates one. 

session域对象

这里要区分JSP中得到的session对象,session是jsp的内置对象,不用创建就可以直接使用!

HttpSession是javaWeb 四大域对象 之一,作用就是在一次会话期间对多个请求之间起着桥梁作用。在项目工程中,最大的作用就是保存用户的登陆信息 。

小案例: 验证码验证登陆

项目示意图如下:

20170503

项目下载地址

/**
*  login.jsp
*/
 <head>
<script type="text/javascript">
/**
 * 
 *      看不清楚换一张
 */
function _change() {
	/*
	1. 得到img元素
	2. 修改其src为/servletDemo/VerifyCodeServlet
	*/
	var imgEle = document.getElementById("img");
	imgEle.src = "/servletTest/VerifyCodeServlet?a=" + new Date().getTime();//加每回都不一样的参数的意思是取消浏览器的缓存
}
</script>
  </head>  
  <body>
	 <h1>登录</h1>
<%
	/**
	 *      读取名为uname的Cookie!
	 *       如果为空显示:""
	 *      如果不为空显示:Cookie的值
	 */
   String uname = "";
   Cookie[] cs = request.getCookies();//获取请求中所有的cookie
   if(cs != null) {// 如果存在cookie
	   for(Cookie c : cs) {//循环遍历所有的cookie
		   if("uname".equals(c.getName())) {//查找名为uname的cookie
			   uname = c.getValue();//获取这个cookie的值,给uname这个变量
		   }
	  }
   }
%>
<%
   /*
			  获取request域中保存的错误信息
			  用巧妙地方法显示在页面中显示错误信息 ,防止第一次登陆就显示内容。因为如果msg =null,页面显示空字符串,对用户来说,什么都没有
   */
	String message = "";
	String msg = (String)request.getAttribute("msg");//获取request域中的名为msg的属性
	if(msg != null) {
		message = msg;
	}
%>
  <font color="red"><b><%=message %> </b></font>
   <form action="/servletTest/LoginServlet" method="post">
	<%-- 把cookie中的用户名显示到用户名文本框中 --%>
	用户名:<input type="text" name="username" value="<%=uname%>"/><br/>
	密 码:<input type="password" name="password"/><br/>
	验证码:<input type="text" name="verifyCode" size="3"/>
			<img id="img" src="/servletTest/VerifyCodeServlet"/>
			<a href="javascript:_change()">换一张</a>
	<br/>
	<input type="submit" value="登录"/>
   </form> 
  </body>
/**
* succ1.jsp 
*/
<body>
<h1>succ1</h1>
<%
String username = (String)session.getAttribute("username");
if(username == null) {
	/*
	1. 向request域中保存错误信息,转发到login.jsp
	*/
	request.setAttribute("msg", "您还没有登录!请先进行登陆");
	request.getRequestDispatcher("/login.jsp").forward(request, response);
	return;
}
%>
欢迎<%=username %>进入succ1 页面!</br>
你可以进入succ2页面,<a href="/servletTest/succ2.jsp">点击这儿</a>
  </body>
  /**
* succ2.jsp 
*/
  <body>
<h1>succ2</h1>
<%
String username = (String)session.getAttribute("username");
if(username == null) {
	/*
	1. 向request域中保存错误信息,转发到login.jsp
	*/
	request.setAttribute("msg", "您还没有登录!请先进行登陆");
	request.getRequestDispatcher("/login.jsp").forward(request, response);
	return;
}
%>

欢迎<%=username %>进入succ2 页面!
  </body>
/**
*  VerifyCode类
*/
public class VerifyCode {
	private int w = 70;
	private int h = 35;
	private Random r = new Random();
	// {"宋体", "华文楷体", "黑体", "华文新魏", "华文隶书", "微软雅黑", "楷体_GB2312"}
	private String[] fontNames  = {"宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312"};
	// 可选字符,1和L、0和O太相似,不写入
	private String codes  = "23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ";
	// 背景色
	private Color bgColor  = new Color(255, 255, 255);
	// 验证码上的文本
	private String text ;	
	// 生成随机的颜色
	private Color randomColor () {
		int red = r.nextInt(150);
		int green = r.nextInt(150);
		int blue = r.nextInt(150);
		return new Color(red, green, blue);
	}	
	// 生成随机的字体
	private Font randomFont () {
		int index = r.nextInt(fontNames.length);
		String fontName = fontNames[index];//生成随机的字体名称
		int style = r.nextInt(4);//生成随机的样式, 0(无样式), 1(粗体), 2(斜体), 3(粗体+斜体)
		int size = r.nextInt(5) + 24; //生成随机字号, 24 ~ 28
		return new Font(fontName, style, size);
	}	
	// 画干扰线
	private void drawLine (BufferedImage image) {
		int num  = 3;//一共画3条
		Graphics2D g2 = (Graphics2D)image.getGraphics();
		for(int i = 0; i < num; i++) {//生成两个点的坐标,即4个值
			int x1 = r.nextInt(w);
			int y1 = r.nextInt(h);
			int x2 = r.nextInt(w);
			int y2 = r.nextInt(h); 
			g2.setStroke(new BasicStroke(1.5F)); 
			g2.setColor(Color.BLUE); //干扰线是蓝色
			g2.drawLine(x1, y1, x2, y2);//画线
		}
	}	
	// 随机生成一个字符
	private char randomChar () {
		int index = r.nextInt(codes.length());
		return codes.charAt(index);
	}	
	// 创建BufferedImage
	private BufferedImage createImage () {
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); 
		Graphics2D g2 = (Graphics2D)image.getGraphics(); 
		g2.setColor(this.bgColor);
		g2.fillRect(0, 0, w, h);
		return image;
	}	
	// 调用这个方法得到验证码
	public BufferedImage getImage () {
		BufferedImage image = createImage();//创建图片缓冲区 
		Graphics2D g2 = (Graphics2D)image.getGraphics();//得到绘制环境
		StringBuilder sb = new StringBuilder();//用来装载生成的验证码文本
		// 向图片中画4个字符
		for(int i = 0; i < 4; i++)  {//循环四次,每次生成一个字符
			String s = randomChar() + "";//随机生成一个字母 
			sb.append(s); //把字母添加到sb中
			float x = i * 1.0F * w / 4; //设置当前字符的x轴坐标
			g2.setFont(randomFont()); //设置随机字体
			g2.setColor(randomColor()); //设置随机颜色
			g2.drawString(s, x, h-5); //画图
		}
		this.text = sb.toString(); //把生成的字符串赋给了this.text
		drawLine(image); //添加干扰线
		return image;		
	}	
	// 返回验证码图片上的文本
	public String getText () {
		return text;
	}	
	// 保存图片到指定的输出流
	public static void output (BufferedImage image, OutputStream out) 
				throws IOException {
		ImageIO.write(image, "JPEG", out);
	}
}
/**
*  VerifyCodeServlet
*/
public class VerifyCodeServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/*
		 * 1. 生成图片
		 * 2. 保存图片上的文本到session域中
		 * 3. 把图片响应给客户端
		 */
		VerifyCode vc = new VerifyCode();
		BufferedImage image = vc.getImage();
		request.getSession().setAttribute("session_vcode", vc.getText());//保存图片上的文本到session域		
		VerifyCode.output(image, response.getOutputStream());
	}
}
/**
* LoginServlet
*/
public class LoginServlet extends HttpServlet {
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/*
		 * 1. 校验验证码
		 *       从session中获取正确的验证码
		 *       从表单中获取用户填写的验证码
		 *       进行比较!
		 *       如果相同,向下运行,否则保存错误信息到request域,转发到login.jsp
		 */
		String sessionCode = (String) request.getSession().getAttribute("session_vcode");
		String paramCode = request.getParameter("verifyCode");
		
		if(!paramCode.equalsIgnoreCase(sessionCode)) {
			request.setAttribute("msg", "验证码错误!");
			request.getRequestDispatcher("/login.jsp").forward(request, response);
			return;  //如果不加return ,则后面的代码会执行。 验证码错误,就没有必要继续进行用户和密码的判断了 
		}		
		/*
		 * 2. 校验用户名和密码是否正确
		 *      处理乱码
		 *      获取表单的数据
		 *      进行比较,用户名只要不是admin就算登陆成功
		 */		
		request.setCharacterEncoding("utf-8");
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		if(!"admin".equalsIgnoreCase(username)) {//登录成功
			/*
			 * 5. 添加Cookie,让浏览器记住上一次输入的用户名
			 *      把用户名保存到cookie中,发送给客户端浏览器
			 *      当再次打开login.jsp时,login.jsp中会读取request中的cookie,把它显示到用户名文本框中
			 */
			Cookie cookie = new Cookie("uname", username);//创建Cookie
			cookie.setMaxAge(60*60*24);//设置cookie命长为1天
			response.addCookie(cookie);//保存cookie		
			/*
			 * 3. 如果成功
			 *      获取session对象
			 *      保存用户信息到session中
			 *      采用重定向技术链接到succ1.jsp,(路径带项目名),使用重定向是另一个响应,即使是多个请求和多个响应,session域中的内容也不会丢失。
			 */
			HttpSession session = request.getSession();//获取session
			session.setAttribute("username", username);//向session域中保存用户名
			response.sendRedirect("/servletTest/succ1.jsp");
		} else {//登录失败
			/*
			 * 4. 如果失败
			 *    保存错误信息到request域中
			 *    采用请求转发技术链接到login.jsp,(路径不带项目名),注意,不能使用重定向,因为重定向是另外一个响应,不属于一次请求范围了,request域中的内容是丢失
			 */
			request.setAttribute("msg", "用户名或密码错误!");
			RequestDispatcher qr = request.getRequestDispatcher("/login.jsp");//得到转发器
			qr.forward(request, response);//转发
		}
	}
}

补充知识1、 认识http协议

http协议,即超文本传输协议。它规定了浏览器与服务器之间的通讯规则。http是基于请求/响应模式的,所以分为请求协议和响应协议

请求协议

  • 格式:
  请求首行  (请求方法 请求路径 请求协议及版本) 
  请求头    (请求头就是一些键值)
  空行  (就是一个空行,用来与请求体分隔)
  请求体(或称之为请求正文)(GET方法没有请求体,POST才有请求体,请求体内容为:参数名=参数值&参数名=参数值,其中参数值为中文,会使用URL编码。)
  • 常见请求方法有GET和POST
  在浏览器地址栏中发送请求,以及点击超链接都是GET请求
  提交表单可以发送GET请求,以及POST请求
  GET请求没有请求体,但空行是存在的
  POST请求是存在请求体的
  • 常见请求头
 Host:请求的服务器主机名
  User-Agent:客户端浏览器与操作系统相关信息
  Accept-Encoding:客户端支持的数据压缩格式
 Connection:客户端支持的连接方式
  Cookie:客户端发送给服务器的“小甜点”,它服务器寄存在客户端的。如果当前访问的服务器没有在客户端寄存东西,那么就不会存在它!
  Content-Length:请求体的长度
  Referer:当前发出请求的地址,例如在浏览器地址栏直接访问服务器,那么没有这个请求头。如果是在www.baidu.com页面上点击链接访问的服务器,那么这个头的值就是www.baidu.com
    作用1:统计来源
     作用2:防盗链
   Content-Type:如果是POST请求,会有这个头,默认值为application/x-www-form-urlencoded,表示请求体内容使用url编码。

响应协议

  • 格式:
   响应首行
 响应头
  空行
  响应体(或称之为响应正文)
  • 状态码
   200:请求成功
   302:请求重定向
   304:请求资源没有改变
   404:请求资源不存在,属性客户端错误
   500:服务器内部错误
  • 常见响应头
  Content-Type:响应正文的MIME类型,例如image/jpeg表示响应正文为jpg图片,例如text/html;charset=utf-8表示响应正文为html,并且编码为utf-8编码。浏览器会通过这一信息来显示响应数据
  Content-Length:响应正文的长度
  Set-Cookie:服务器寄存在客户端的“小甜点”,当客户端再次访问服务器时会把这个“小甜点”还给服务器
  Date:响应时间,可能会有8小时的误差,因为中国的时区问题

通知客户端浏览器不要缓存页面的响应头:
  Expires:-1
   Cache-Control: no-cache
  Pragma: no-cache
自动刷新响应头,浏览器会在3秒钟后自动重定向到传智主页
   Refresh: 3;url=http://www.itcast.cn

补充知识2、 javaweb四大域对象

PageContext(page域)、ServletRequest(request域)、HttpSession(session域)、ServletContext(application域)。其中request、session、application域名是Sevlet三大域。page域是用在JSP中。 简单来说, 域对象简单说就是能在Servlet之间(page域使用在JSP中)传递参数,因为要降低耦合度,所以我们创建的每个Servlet之间都是不能相互交流的,可以说,域对象是串联起多个Servlet的线,可以为多个Servlet之间的交流传递数据,这就显得比较重要。域对象内部有个Map,用来存取数据。

所有的域对象都有如下的方法:

 void setAttribute(java.lang.String name, java.lang.Object o)  //保存值
		  Stores an attribute in this request. 
 java.lang.Object getAttribute(java.lang.String name)  //获取值
		  Returns the value of the named attribute as an Object, or null if no attribute of the given name exists.          
 void removeAttribute(java.lang.String name)  //移除值
		  Removes an attribute from this request.          
```	

这四个域的范围不同:

PageContext : 这个范围是最小的,在JSP中只能在同一个页面中传递传递参数。(不属于本文章讲解范围)
HttpServletRequest:一个请求创建一个request对象,所以在同一个请求中可以共享request,例如一个请求从AServlet转发到BServlet,那么AServlet和BServlet可以共享request域中的数据;
HttpSession:一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享session中的数据;浏览器不关,不会死亡,在一个浏览器不关闭的状态下,再打开相同的浏览器,还是不会死亡,但是打开不同的浏览器就不可以访问 。 
ServletContext:范围最大的一个域 ,一个应用只创建一个ServletContext对象,所以在ServletContext中的数据可以在整个应用中共享,只要不启动服务器,那么ServletContext中的数据就可以共享;服务器不关,就不会死亡

# <a name="neizhiduixiang">补充知识3、JSP九大内置对象  </a>

在JSP中有九大内置对象,分别是request对象、response对象、session对象、application对象、out 对象、pageContext 对象、config 对象、page 对象、exception 对象。本文章不涉及到JSP内容,所以不详细展开,只需要知道内置对象可以不加声明就在JSP页面脚本(Java程序片和Java表达式)中使用的成员变量 。要与四大域对象有所区分,四大域对象有三个是用在Servlet中。


# <a name=""> 补充知识2、 请求转发与重定向的区别</a>

 请求转发是一个请求一次响应,而重定向是两次请求两次响应


请求转发地址栏不变化,而重定向会显示后一个请求的地址

请求转发只能转发到本项目其他Servlet,而重定向不只能重定向到本项目的其他Servlet,还能定向到其他项目


请求转发是服务器端行为,只需给出转发的Servlet路径,而重定向需要给出requestURI,即包含项目名!


请求转发和重定向效率是转发高!因为是一个请求!

需要地址栏发生变化,那么必须使用重定向!

需要在下一个Servlet中获取request域中的数据,必须要使用转发!


--------------------------

文章下载 ;

commons-io-1.4.jar下载地址:[http://download.csdn.net/detail/g425680992/9832418](http://download.csdn.net/detail/g425680992/9832418)

<a name="download">项目</a>下载地址:[http://download.csdn.net/detail/g425680992/9832411](http://download.csdn.net/detail/g425680992/9832411)
posted @ 2017-05-03 18:32  阿斯兰。līōń  阅读(1130)  评论(5编辑  收藏  举报