Web开发基础

前言

MVC模式是软件工程中的一种软件架构模式,该模式把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

Controller:对请求进行处理,负责请求转发;
View:界面设计人员进行图形界面设计;
Model: 程序编写程序应用的功能(实现算法等) 、数据库管理。

MVC不是Java的东西,几乎所有B/S结构的软件都采用了MVC设计模式。
JavaWeb的三层:

WEB层:包含JSP和Servlet等与Web相关的内容;
业务层:业务层中不包含JavaWeb API,它只关心业务逻辑;
数据层:封装了对数据库的访问细节。


Web服务器的作用是接收客户端的请求,给客户端作出响应。对于JavaWeb程序而言,还需要有Servlet容器,Servlet容器的基本功能是把动态资源转换成静态资源。我们需要使用的是Web服务器和Servlet容器,通常这两者会集于一 身。
一些JavaWeb服务器: Tomcat、Weblogic等。

一、Servlet

Servlet是JavaWeb的三大组件之一,它的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:接收请求数据、处理请求、完成响应。

1.1 Servlet的生命周期

生命周期相关的方法:

	//初始化
	void init(ServletConfig)
	//处理请求
	void service(ServletRequest request, ServletResponse response)
	//销毁
	void destroy()

Servlet的特点:

  1. 单例,一个具体的Servlet类只有一个对象(通过反射来创建对象),当然可能存在多个Servlet类;
  2. 多线程的,所以它的效率是高的,不是线程安全的。

Servlet类由开发者来写,但对象由服务器来创建,并且由服务器来调用相应的方法。

1.1.1 init方法

服务器会在Servlet第一次被访问时创建Servlet,或者是在服务器启动时创建Servlet。如果服务器启动时就创建Servlet,那么还需要在web.xml文件中配置。默认情况下,Servlet是在第一次被访问时由服务器创建的
在Servlet被创建后,服务器会马上调用Servlet的void init(ServletConfig)方法,并且整个生命周期只调用一次。
ServletConfig:Servlet的配置信息,即web.xml文件中的<servlet>。一个ServletConfig对象,对应着一个servlet元素的配置信息(servlet-name,servlet-class)。
ServletConfig中的方法:

看一个<servlet>的例子:

	<servlet>
		<servlet-name>One</servlet-name>
		<servlet-class>cn.itcast.servlet.OneServlet</servlet-class>
		<init-param>
			<param-name>paramName1</param-name>
			<param-value>paramValue1</param-value>
		</init-param>
		<init-param>
			<param-name>paramName2</param-name>
			<param-value>paramValue2</param-value>
		</init-param>
	</servlet>

<init-param>代表初始化参数。
ServletConfig中4个方法的解释:

	//获取Servlet在web.xml文件中的配置名称,即<servlet-name>指定的名称
	String getServletName()
	//用来获取ServletContext对象
	ServletContext getServletContext()
	//用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值
	String getInitParameter(String name)
	//用来获取在web.xml中配置的所有初始化参数名称
	Enumeration getInitParameterNames()

1.1.2 service方法

当服务器每次接收到请求时,都会去调用Servlet的service()方法来处理请求。服务器接收到一次请求,就会调用service()方法一次。
ServletRequest和ServletResponse是service()方法的两个参数,代表请求对象和响应对象。开发者可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。
ServletRequest和ServletResponse的实例由服务器创建,然后传递给service()方法。如果在service()方法中希望使用HTTP相关的功能,那么可以把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse。
HttpServletRequest中的方法:

	//获取指定请求参数的值
	String getParameter(String paramName)
	//获取请求方法,例如GET或POST
	String getMethod()
	//获取指定请求头的值
	String getHeader(String name)
	//设置请求体的编码。因为GET请求没有请求体,所以这个方法只对POST请求有效。当调用
	//request.setCharacterEncoding("utf-8")之后,再通过getParameter()方法获取参数
	//值时,那么参数值都已经通过了转码,即转换成了UTF-8编码。所以,这个方法必须在调用
	//getParameter()方法之前调用
	void setCharacterEncoding(String encoding)

HttpServletResponse中的方法:

	//获取字符响应流,使用该流可以向客户端输出响应信息
	PrintWriter getWriter()
	//获取字节响应流,当需要向客户端响应字节数据时,需要使用这个流,例如要向
	//客户端响应图片
	ServletOutputStream getOutputStream()
	//用来设置字符响应流的编码,例如在调用setCharacterEncoding("utf-8")之后,
	//再response.getWriter()获取字符响应流对象,这时的响应流的编码为utf-8,使
	//用 response.getWriter()输出的中文都会转换成utf-8编码后发送给客户端
	void setCharacterEncoding(String encoding)
	//向客户端添加响应头信息
	void setHeader(String name, String value)
	//是setHeader("content-type", "xxx")的简便写法,即用来添加名为content-type响应头的方法 
	void setContentType(String contentType)
	//向客户端发送状态码,以及错误消息
	void sendError(int code, String errorMsg)

1.1.3 destroy方法

在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,该方法内可以写一些释放资源之类的代码。
一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁。即destroy方法,该方法被调用的两个常见场景:

  • 1、关闭Tomcat的时候。
  • 2、server.xml中将reloadable配置为"true" 就表示有任何类发生的更新,web应用会自动重启,当web应用自动重启的时候,destroy()方法就会被调用

1.2 Servlet的实现类

实现Servlet有三种方式:

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

通常会去继承HttpServlet类来完成自定义Servlet。

1.2.1 GenericServlet

GenericServlet是Servlet接口的实现类,在GenericServlet源码中,可以看下init相关的方法:

	@Override
	public void init(ServletConfig config) throws ServletException {
		this.config = config;
		this.init();
	}
	
	//这个init方法是为了拓展init而设置的,子类可以重写这个无参数的init
	public void init() throws ServletException {}

在GenericServlet中,定义了一个ServletConfig实例变量,并在init(ServletConfig)方法中把参数ServletConfig赋给了实例变量。然后在该类的很多方法中使用了实例变量config。
如果子类希望完成一些初始化操作,那么应该去覆盖GenericServlet的提供的init()方法,它是没有参数的init()方法,它会在init(ServletConfig)方法中被调用。
GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getServletContext()等ServletConfig中的方法。

1.2.2 HttpServlet

HttpServlet类是GenericServlet的子类,它提供了对HTTP请求的特殊支持,所以通常都会通过继承HttpServlet来完成自定义的Servlet。
HttpServlet类中提供了service(HttpServletRequest,HttpServletResponse)方法,这个方法是HttpServlet自己的方法,不是从Servlet继承来的。
在HttpServlet的service(ServletRequest,ServletResponse)方法中,会把 ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse,然后调用service(HttpServletRequest,HttpServletResponse)方法。相关源码:

public abstract class HttpServlet extends GenericServlet {
	protected void service(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
		//……
	}
	
	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		HttpServletRequest request;
		HttpServletResponse response;
		try {
			request = (HttpServletRequest) req;
			response = (HttpServletResponse) res;
		} catch (ClassCastException e) {
			throw new ServletException("non-HTTP request or response");
		}
		service(request, response);
	}
}
  • doGet()和doPost()
    在HttpServlet的 service(HttpServletRequest,HttpServletResponse)方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的 doGet()方法,如果是POST请求会去调用doPost()方法,因此在子类中去覆盖doGet()或doPost()方法即可。示例:
public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		System.out.println("hello doGet()...");
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		System.out.println("hello doPost()...");
	}
}

1.3 ServletContext

一个Web应用只有一个ServletContext对象,该对象在Tomcat启动时就创建,在Tomcat关闭时被销毁。
ServletContext对象的作用是在整个Web应用的动态资源之间共享数据。比如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值。
常见的获取ServletContext的方式:

	//在void init(ServletConfig config)中
	ServletContext context = config.getServletContext();
	//在GenericeServlet中
	ServletContext context = this.getServletContext();

1.3.1 域对象的功能

JavaWeb四大域对象:PageContext、ServletRequest、HttpSession、ServletContext。所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据。
ServletContext对象用来操作数据的方法:

	//用来存储一个对象,也可以称之为存储一个域属性。如果多次调用该方法,并且
	//使用相同的name,那么会覆盖上一次的值
	void setAttribute(String name, Object value)
	//用来获取ServletContext中的数据
	Object getAttribute(String name)
	//用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么什么都不做
	void removeAttribute(String name)
	//获取所有域属性的名称
	Enumeration getAttributeNames()

1.3.2 获取初始化参数

Servlet也可以获取初始化参数,但它是局部的参数,即一个Servlet只能获取自己的初始化参数。
使用ServletContext可以获取全局(比如在web.xml文件中配置的全局参数)的初始化参数。示例:

	<web-app ...>
		<!--...-->
		<!--为ServletContext 设置的公共初始化参数-->
		<context-param> 
			<param-name>paramName1</param-name>
			<param-value>paramValue1</param-value>
		</context-param>
		<!--...-->
	</web-app>
	ServletContext context = this.getServletContext();
	String value1 = context.getInitParameter("paramName1");

1.4 Servlet的特点

1.4.1 线程不安全

由于Servlet是单例的,当多个用户访问Servlet的时候,服务器会为每个用户创建一个线程。当多个用户并发访问Servlet共享资源的时候就会出现线程安全问题。
原则:

  • 1、如果一个变量需要多个用户共享,则应当在访问该变量的时候,加同步机制synchronized (对象){}
  • 2、如果一个变量不需要共享,则直接在doGet()或者doPost()定义,这样不会存在线程安全问题。
  • 3、避免使用实例变量
    线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量(尽量都定义局部变量),那么该Servlet就是线程安全的。

1.4.2 Servlet的创建顺序

默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。示例:

<servlet>
	<servlet-name>hello1</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello1Servlet</servlet-class>
	<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>hello1</servlet-name>
	<url-pattern>/hello1</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>hello2</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello2Servlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>hello2</servlet-name>
	<url-pattern>/hello2</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>hello3</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello3Servlet</servlet-class>
	<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>hello3</servlet-name>
	<url-pattern>/hello3</url-pattern>
</servlet-mapping>

<servlet>元素中配置<load-on-startup>元素,可以让服务器在启动时就创建该 Servlet。其中<load-on-startup>元素的值必须是大于等于0的整数,它表示服务器启动时创建Servlet的顺序。正数的值越小,启动时加载该Servlet的优先级越高。如果为负数,则容器启动时不会加载该Servlet,只有该Servlet被访问时才会加载。
上面例子中,根据<load-on-startup>的值可以得知服务器创建Servlet的顺序为Hello1Servlet 、Hello2Servlet 、Hello3Servlet。

1.4.3 路径匹配

<url-pattern><servlet-mapping>的子元素,用来指定Servlet的访问路径,必须是以“/”开头。
可以在<servlet-mapping>中给出多个<url-pattern>,示例:

<servlet-mapping>
	<servlet-name>AServlet</servlet-name>
	<url-pattern>/AServlet</url-pattern>
	<url-pattern>/BServlet</url-pattern>
</servlet-mapping>

上述配置表示一个Servlet绑定了两个URL,无论访问/AServlet是/BServlet,访问的都是AServlet。
也可以在<url-pattern>中使用通配符,所谓通配符就是星号“*”,星号可以匹配任何URL前缀或后缀,例如:

	<!--/servlet/a、/servlet/b,都匹配/servlet/*-->
	<url-pattern>/servlet/*<url-patter>
	<!--/abc/def/ghi.do、/a.do,都匹配*.do-->
	<url-pattern>*.do</url-pattern>
	<!--匹配所有URL-->
	<url-pattern>/*<url-pattern>

配通配符是一种模糊匹配URL的方式,如果存在更具体的<url-pattern>,那么访问路径会去匹配具体的<url-pattern>。例如:

<servlet>
	<servlet-name>hello1</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello1Servlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>hello1</servlet-name>
	<url-pattern>/servlet/hello1</url-pattern>
</servlet-mapping>

<servlet>
	<servlet-name>hello2</servlet-name>
	<servlet-class>cn.itcast.servlet.Hello2Servlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>hello2</servlet-name>
	<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>

当访问http://localhost:8080/hello/servlet/hello1时,因为访问路径即匹配hello1的<url-pattern>,又匹配hello2的<url-pattern>,但因为hello1的<url-pattern>中没有通配符,所以优先匹配,即设置 hello1。

1.4.4 web.xml文件的继承

${CATALINA_HOME}\conf\web.xml中的内容,相当于写到了每个项目的web.xml中,它是所有web.xml的父文件。
每个完整的JavaWeb应用中都需要有web.xml,所有的web.xml文件都有一个共同的父文件,即Tomcat的conf/web.xml。

1.5 Servlet的相关问题

1.5.1 tomcat容器是如何创建Servlet类实例

当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件。
然后,对xml文件进行解析,并读取Servlet注册信息。
最后,将每个应用中注册的Servlet类都进行加载,并通过反射的方式实例化。

1.5.2 Servlet的使用流程

1)Tomcat容器中通过web.xml加载所有的Servlet。
2)用户在浏览器输入不同的地址,向Tomcat容器请求资源。
3)Tomcat容器根据地址首先在容器内找到应用对应的项目。
4)Tomcat容器再根据地址去web.xml找到相应的Servlet地址。
5)Tomcat容器根据找到的Servlet地址,去web.xml找到相应的Servlet类,并实例化。
6)Tomcat容器实例化相应的Servlet,首先调用init方法。
7)Tomcat容器实例化相应的Servlet,首先调用service方法处理用户请求,比如post或者是get。
8)Servlet处理完成之后,现将数据给Tomcat容器,Tomcat容器再把处理结果给浏览器客户端。
9)Tomcat容器调用servlet实例的destory方法销毁这个实例。

二、Request

request是Servlet.service()方法的一个参数,类型为javax.servlet.http.HttpServletRequest。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给service()方法,这说明在service()方法中可以通过request对象来获取请求数据。
request 的功能可以分为以下几种:

  1. 封装了请求头数据;
  2. 封装了请求正文数据,如果是GET请求,那么就没有正文;
  3. request是一个域对象,可以把它当成Map,来添加获取数据;
  4. request提供了请求转发和请求包含功能。

2.1 Request中的方法

  • 1、域方法
    request是域对象。一个请求会创建一个request对象,如果在一个请求中经历了多个Servlet ,那么多个Servlet就可以使用request来共享数据。
    request的域方法:
	//用来存储一个对象,也可以称之为存储一个域属性
	void setAttribute(String name, Object value)
	//用来获取request中的数据
	Object getAttribute(String name)
	//用来移除request中的域属性,如果参数name指定的域属性不存在,则什么都不做
	void removeAttribute(String name)
	//获取所有域属性的名称
	Enumeration getAttributeNames()
  • 2、获取请求头数据
    request中,与请求头相关的方法有:
	//获取指定名称的请求头
	String getHeader(String name)
	//返回所有request header的名字
	Enumeration getHeaderNames()
	//获取指定名称请求头的int值
	int getIntHeader(String name)
	//获取指定的头名称的构建Date对象的long值
	long getDateHeader(String name)
  • 3、获取请求相关的其它方法
	//获取请求体的字节数,GET 请求没有请求体,没有请求体返回-1
	int getContentLength()
	//获取请求类型,如果请求是GET,那么这个方法返回null;
	//如果是POST请求,那么默认为application/x-www-form-urlencoded,表示请求体内
	//容使用了URL编码
	String getContentType()
	//返回请求方法,例如:GET/POST
	String getMethod() 
	//返回当前客户端浏览器的 Locale。java.util.Locale表示国家和言语
	Locale getLocale()
	//获取请求编码,如果没有setCharacterEncoding(),那么返回null,表示使用ISO-8859-1编码
	String getCharacterEncoding()
	//设置请求编码,只对请求体有效。对于GET而言,没有请求体,所以此方法只能对POST请求中的参数有效
	void setCharacterEncoding(String code)
	//返回上下文路径(/项目名),例如:/hello
	String getContextPath()
	//返回请求URL中的参数,例如:name=zhangSan
	String getQueryString()
	//返回请求URI路径,例如:/hello/oneServlet
	String getRequestURI()
	//返回请求URL路径,例如:http://localhost/hello/oneServlet,即返回除了参数以外的路径信息
	StringBuffer getRequestURL()
	//返回Servlet路径,例如:/oneServlet
	String getServletPath()
	//返回当前客户端的IP地址
	String getRemoteAddr() 
	//返回当前客户端的主机名,但这个方法的实现还是获取IP地址
	String getRemoteHost()
	//返回请求协议,例如:http
	String getScheme()
	//返回主机名,例如:localhost
	String getServerName()
	//返回服务器端口号,例如:8080
	int getServerPort()

图示:

URL=协议名+主机名+端口号+URI
示例:

	/**
	 *访问 http://localhost:8080/Request&Response/RequestHeaderServlet?username=sxj&password=sxj
	 */

	//IP:127.0.0.1
	System.out.println("IP:"+request.getRemoteAddr());
	//请求方式:GET
	System.out.println("请求方式:"+request.getMethod());
	//User-Agent请求头:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
	System.out.println("User-Agent请求头:"+request.getHeader("User-Agent"));
	//协议名:http
	System.out.println("协议名:"+request.getScheme());
	//主机名:localhost
	System.out.println("主机名:"+request.getServerName());
	//端口号:8080
	System.out.println("端口号:"+request.getServerPort());
	//项目:/Request&Response
	System.out.println("项目:"+request.getContextPath());
	//Servlet路径:/RequestHeaderServlet
	System.out.println("Servlet路径:"+request.getServletPath());
	//请求参数: username=sxj&password=sxj
	System.out.println("请求参数:"+request.getQueryString());
	//URI:/Request&Response/RequestHeaderServlet(RequestURI)
	System.out.println("URI:"+request.getRequestURI());
	//URL:http://localhost:8080/Request&Response/RequestHeaderServlet
	System.out.println("URL:"+request.getRequestURL());

2.2 GET和POST

最为常见的客户端传递参数方式是get请求。
浏览器地址栏直接输入URL和超链接都是get请求。如果不指定名method,则也是get请求。
表单提交:可以是GET,也可以是POST,这取决于<form>的method属性值。

GET和POST请求的简单比较:

GETPOST
请求参数的位置请求参数放在URL地址后面,以?的方式来进行拼接请求参数放在HTTP请求体中
安全性请求参数会在浏览器的地址栏中显示,所以不安全请求参数不会显示浏览器的地址栏,相对安全
参数长度是否有限制请求参数长度限制长度在1K之内请求参数长度没有限制
是否可以设置编码格式没有请求体,无法通过 request.setCharacterEncoding()来设置参数的编码可以设置
用途一般用来获取数据一般用来提交数据
速度

无论是GET,还是POST请求,都可以使用相同的API来获取请求参数。请求参数有一个key一个value的,也有一个key多个value的。示例:

	//通过指定名称获取参数值
	String getParameter(String name)
	//当多个参数名称相同时,可以使用方法来获取
	String[] getParameterValues(String name) 
	//获取所有参数的名字
	Enumeration getParameterNames()
	//获取所有参数封装到Map中,其中key为参数名,value为参数值,因为一个参数名称可能
	//有多个值,所以参数值是String[],而不是String
	Map getParameterMap()

2.3 转发

在AServlet中,把请求转发到BServlet,参数是Servlet路径,示例:

public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		System.out.println("AServlet");
		RequestDispatcher rd = request.getRequestDispatcher("/BServlet");
		rd.forward(request, response);
	}
}

三、Response

在客户端发出每个请求时,服务器都会创建一个response对象,并传入给 Servlet.service()方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。
response对象的功能分为以下四种:设置响应头信息、发送状态码、设置响应正文、重定向

3.1 响应正文

response是响应对象,向客户端输出响应正文(响应体)可以使用response的响应流,repsonse一共提供了两个响应流对象:

	//获取字符流
	PrintWriter out = response.getWriter();
	//获取字节流
	ServletOutputStream out = response.getOutputStream();

如果响应正文内容为字符(html) ,那么使用 response.getWriter() ;如果响应内容是字节(图片等) ,例如下载时,那么可以使用 response.getOutputStream()。
在一个请求中,不能同时使用这两个流 。

  • 字符响应流
    使用response.getWriter()时,默认字符编码为ISO-8859-1,如果希望输出给客户端的字符都是使用UTF-8编码,可以使用response.setCharaceterEncoding(“utf-8”)来设置。
    但如果仅使用上述方法,客户端浏览器并不知道响应数据是什么编码的。如果希望通知客户端使用UTF-8格式来解读响应数据,那么还是使用response.setContentType("text/html;charset=utf-8")方法比较好。因为这个方法不只会调用response.setCharaceterEncoding(“utf-8”),还会设置content-type响应头,客户端浏览器会使用 content-type 头来解读响应数据。
    response.getWriter()是PrintWriter 类型,所以它有缓冲区,缓冲区的默认大小为 8KB。即在响应数据没有输出8KB之前,数据都是存放在缓冲区中,而不会立刻发送到客户端。当 Servlet 执行结束后,服务器才会去刷新流,使缓冲区中的数据发送到客户端。
    如果希望响应数据马上发送给客户端:
  1. 向流中写入大于8KB的数据;
  2. 调用 response.flushBuffer()方法来手动刷新缓冲区。

3.2 响应头和状态码

响应头是键值对的形式。可以使用response对象的setHeader()方法来设置响应头。使用该方法设置的响应头最终会发送给客户端浏览器,示例:

	//设置content-type响应头,该头的作用是告诉浏览器响应内容为html类型,编码为utf-8,
	//而且同时会设置response的字符流编码为utf-8
	response.setHeader("content-type", "text/html;charset=utf-8")
	//5秒后自动跳转到指定的URL
	response.setHeader("Refresh","5; URL=http://www.itcast.cn")

设置状态码的相关方法:

	//等同于调用response.setHeader(“content-type”, “text/html;charset=utf-8”);
	response.setContentType("text/html;charset=utf-8")
	//设置字符响应流的字符编码为utf-8
	response.setCharacterEncoding(“utf-8)
	//设置状态码
	response.setStatus(200) 

3.3 重定向

重定向是服务器通知浏览器去访问另一个地址,即再发出另一个请求。
响应码为200,表示响应成功。响应码为302,表示重定向。
因为重定向是通知浏览器发出第二个请求,所以浏览器需要知道第二个请求的URL,所以完成重定向的第二步是设置Location头,指定第二个请求的URL地址。示例:

public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
	    throws ServletException, IOException {
		response.setStatus(302);
		response.setHeader("Location", "http://www.itcast.cn");
	}
}

sendRedirect是一种简单的重定向方式:

	//通用方式
	response.sendRedirect("http://www.itcast.cn");
	//如果跳转到同一个项目内,可以使用相对路径,即"/项目名/路径",以下代码会
	//重定向到http://localhost:8080/hello/BServlet
	response.sendRedirect("/hello/BServlet");

重定向的特点:

  1. 重定向是两次请求;
  2. 重定向的URL可以是其他应用,不局限于当前应用;
  3. 重定向的响应头为302,并且必须要有Location响应头;
  4. 重定向就不要再使用response.getWriter()或response.getOutputStream() 输出数据 ,不然可能会出现异常。

3.4 转发和重定向的区别

  • 1、实际发生位置不同,地址栏不同
    转发是由服务器进行跳转的,在转发的时候,浏览器的地址栏是没有发生变化的,浏览器是不知道该跳转的动作。实现转发只是一次的http请求,一次转发中request和response对象都是同一个。这也解释了,为什么可以使用request作为域对象进行Servlet之间的通讯。
    重定向是由浏览器进行跳转的,进行重定向跳转的时候,浏览器的地址会发生变化的。实现重定向的原理是由response的状态码和Location头组合而实现的。这是由浏览器进行的页面跳转实现重定向会发出两个http请求,request域对象是无效的,因为它不是同一个request对象。
  • 2、用法
    给服务器(转发)用的直接从资源名开始写,给浏览器(重定向)用的要把应用名写上
    转发:在返回值前面加"forward:“,示例:“forward:user.do?name=method4”。
    重定向:在返回值前面加"redirect:”,示例:“redirect:http://www.baidu.com”。
  • 3、能够去往的URL的范围
    转发是服务器跳转只能去往当前web应用的资源。
    重定向是服务器跳转,可以去往任何的资源。
  • 4、传递数据的类型
    转发的request对象可以传递各种类型的数据,包括对象
    重定向只能传递字符串
  • 5、跳转的时间
    转发时:执行到跳转语句时就会立刻跳转
    重定向:整个页面执行完之后才执行跳转
  • 6、效率
    转发:高。
    重定向:低。

转发是带着转发前的请求的参数的。重定向是新的请求

四、Filter

过滤器是Java Web三大组件之一,它与Servlet很相似。不过过滤器是用来拦截请求的 ,而不是处理请求的。
当用户请求某个Servlet时,会先执行部署在这个请求上的Filter,如果Filter“ 放行” ,那么会继承执行用户请求的Servlet;如果Filter不 “ 放行”,那么就不会执行用户请求的Servlet。
Filter简单使用示例:

public class HelloFilter implements Filter {
	public void init(FilterConfig filterConfig) throws ServletException {}
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		System.out.println("filter start...");
		chain.doFilter(request, response);
		System.out.println("filter end...");
	}
	public void destroy() {}
}

在web.xml文件中配置Filter:

<filter>
	<filter-name>helloFilter</filter-name>
	<filter-class>cn.itcast.filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>helloFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

在经过上面的配置后,在访问项目中的任何一个url时,都会经过HelloFilter。

4.1 过滤器的生命周期

过滤器也是单例的。

4.1.1 init(FilterConfig)

在服务器(如Tomcat)启动时建会创建Filter实例,并且每个类型的Filter只创建一个实例 。在创建完Filter实例后,会马上调用init()方法完成初始化工作,这个方法只会被执行一次。
FilterConfig的功能与ServletConfig相似,与web.xml文件中的配置信息对应。常用方法:

	//获取ServletContext
	ServletContext getServletContext()
	//获取Filter的配置名称,与<filter-name>元素对应
	String getFilterName()
	//获取Filter的初始化配置,与<init-param>元素对应
	String getInitParameter(String name)
	//获取所有初始化参数的名称
	Enumeration getInitParameterNames()

4.1.2 doFilter(ServletRequest req,ServletResponse res,FilterChain chain)

过滤方法,如果需要“放行”,那么需要调用FilterChain的doFilter(ServletRequest,ServletResponse)方法,如果不调用FilterChain的doFilter() 方法,那么目标资源将无法执行。
FilterChain的doFilter() 方法的含义:

  1. 如果当前过滤器是最后一个过滤器,那么调用chain.doFilter()方法表示执行目标资源;
  2. 如果不是最后一个过滤器,那么chain.doFilter()表示执行下一个过滤器的doFilter()方法。

4.1.3 destroy()

服务器会在创建Filter对象之后,把Filter放到缓存中一直使用,通常不会销毁它,一般会在服务器(如Tomcat)关闭时销毁Filter对象。在销毁Filter对象之前,服务器会调用Filter对象的destory()方法。

4.2 多个过滤器的执行顺序

一个目标资源可以指定多个过滤器在 ,过滤器的执行顺序是在 web.xml 文件中的部署顺序。示例:

<filter>
	<filter-name>myFilter1</filter-name>
	<filter-class>cn.itcast.filter.MyFilter1</filter-class>
</filter>
<filter-mapping>
	<filter-name>myFilter1</filter-name>
	<url-pattern>/index.jsp</url-pattern>
</filter-mapping>

<filter>
	<filter-name>myFilter2</filter-name>
	<filter-class>cn.itcast.filter.MyFilter2</filter-class>
</filter>
<filter-mapping>
	<filter-name>myFilter2</filter-name>
	<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
public class MyFilter1 extends HttpFilter {
	public void doFilter(HttpServletRequest request, HttpServletResponse response,FilterChain chain) throws IOException, ServletException {
		System.out.println("filter1 start...");
		chain.doFilter(request, response);//放行,执行MyFilter2的doFilter()方法
		System.out.println("filter1 end...");
	}
}

public class MyFilter2 extends HttpFilter {
	public void doFilter(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws IOException, ServletException {
		System.out.println("filter2 start...");
		chain.doFilter(request, response);//放行,执行目标资源
		System.out.println("filter2 end...");
	}
}

当有用户访问index.jsp页面时,输出结果:

filter1 start…
filter2 start…
index.jsp
filter2 end…
filter1 end…

4.3 过滤器的应用场景

  • 1、执行目标资源之前做预处理工作
    例如设置编码,这种通常都会放行,只是在目标资源执行之前做一些准备工作。几乎是所有的Sevlet中,都需要写request.setCharacterEndoing()可以把它放入到
    一个 Filter 中。
  • 2、实现URL级别的权限认证
    例如校验当前用户是否已经登录,或者用户IP是否已经被禁用。
  • 3、在目标资源执行后,做一些后续的特殊处理工作
    例如把目标资源输出的数据进行处理。

五、Cookie

5.1 Cookie简介

网页之间的交互是通过HTTP协议传输数据的,而Http协议是无状态的协议
无状态的协议是什么意思呢?一旦数据提交完后,浏览器和服务器的连接就会关闭,再次交互的时候需要重新建立新的连接。服务器无法确认用户的信息,于是W3C就提出了:给每一个用户都发一个通行证,无论谁访问的时候都需要携带通行证,这样服务器就可以从通行证上确认用户的信息。通行证就是Cookie。
Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie) 。当客户端向服务器发出请求时,会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端。

  • 1、Cookie规范
    HTTP的Cookie规范:
  1. Cookie大小上限为4KB;
  2. 一个服务器最多在客户端浏览器上保存20个Cookie;
  3. 一个浏览器最多保存300个Cookie。

一些浏览器可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等。
不同浏览器之间是不共享Cookie的

  • 2、Cookie与HTTP头
    Cookie是通过HTTP请求头和响应头在客户端和服务器端传递的。
    在客户端发送给服务器端的请求头,Cookie格式:Cookie: a=A; b=B; c=C 。即多个Cookie用分号分开;一个Cookie对应多个键值对
    在服务器端发送给客户端的响应头中,一个Cookie对象一个Set-Cookie,一个键值对:Set-Cookie: a=ASet-Cookie: b=BSet-Cookie: c=C。比如可以用 response.addHeader("Set-Cookie","a=A");发送一个Cookie。
    获取Cookie的方式:
	//HttpServletRequest中获取Cookie
	getCookies()
	//HttpServletResponse中添加Cookie
	addCookie(Cookie cookie)
  • 3、Cookie的覆盖
    如果服务器端发送重复的Cookie,那么会覆盖原有的Cookie,例如客户端的第一个请求服务器端发送的Cookie是Set-Cookie: a=A。第二请求服务器端发送的是Set-Cookie: a=AA,那么客户端只留下一个Cookie,即a=AA

5.2 Cookie中的属性

  • 1、HttpOnly
    如果Cookie中设置了HttpOnly属性,那么通过JS脚本将无法读取到Cookie信息,这样能有效的防止XSS攻击,窃取Cookie内容,这样就增加了Cookie的安全性,即便是这样,也不要将重要信息存入 cookie。
    Servlet3.0支持setHttpOnly(boolean httpOnly)
    非Servlet3.0的JAVAEE项目也可以通过设置Header进行设置:
    response.setHeader("Set-Cookie", "cookiename=value;Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly")
  • 2、max-age
    expires/Max-Age 字段为此Cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此Cookie失效。不设置的话默认值是Session,意思是Cookie会和Session一起失效。
    当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此Cookie失效。
  • 3、secure
    设置是否只能通过https来传递此cookie。

5.3 Cookie的生命周期

所谓生命周期就是Cookie在客户端的有效时间,可以通过setMaxAge(int)来设置Cookie的有效时间。示例:

  • cookie.setMaxAge(-1)
    Cookie的maxAge属性的默认值就是-1,表示只在浏览器内存中存活。一旦关闭浏览器窗口,那么Cookie就会消失。
  • cookie.setMaxAge(60*60)
    表示Cookie对象可存活1小时 。当生命大于0时,浏览器会把Cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,Cookie也会存活1小时。
  • cookie.setMaxAge(0)
    0是一个特殊的值,它表示Cookie被作废。也就是说,如果原来浏览器已经保存了这个Cookie,那么可以通过Cookie的setMaxAge(0) 来删除这个Cookie。无论是在浏览器内存中,还是在客户端硬盘上都会删除这个 Cookie。

5.4 Cookie的路径

现在有Web应用A,向客户端发送了10个Cookie,这就说明客户端无论访问应用A的哪个Servlet 都会把这10个Cookie包含在请求中!但是也许只有AServlet需要读取请求中的Cookie,而其他Servlet根本就不会获取请求中的Cookie。这说明客户端浏览器有时发送这些Cookie是多余的。
可以通过设置Cookie的path来指定浏览器,在访问什么样的路径时,包含什么样的Cookie。

  • 1、Cookie路径与请求路径的关系
    客户端浏览器保存的3个Cookie的路径示例:

a: /cookietest;
b: /cookietest/servlet;
c: /cookietest/jsp;

浏览器请求的URL:

A: http://localhost:8080/cookietest/AServlet;
B: http://localhost:8080/cookietest/servlet/BServlet;
C: http://localhost:8080/cookietest/jsp/a.jsp;

请求A时,会在请求中包含 a;请求B时,会在请求中包含 a、b;请求C时,会在请求中包含 a、c.
请求路径如果包含了Cookie路径,那么会在请求中包含这个Cookie
1)A请求的URL包含了“/cookietest”,所以会在请求中包含路径为“/cookietest”的Cookie。
2)B请求的URL包含了“/cookietest”,以及“/cookietest/servlet”,所以请求中包含路径为“/cookietest”和“/cookietest/servlet”两个Cookie。
3)C请求的URL包含了“/cookietest”,以及“/cookietest/jsp”,所以请求中包含路径为“/cookietest”和“/cookietest/jsp”两个Cookie。

  • 2、设置Cookie的路径
    设置Cookie的路径需要使用setPath()方法,例如:
    cookie.setPath("/cookietest/servlet");
    默认路径是访问资源的上一级路径
    如果没有设置Cookie的路径,那么Cookie路径的默认值当前访问资源所在路径( 上一级),例如:
  1. 访问http://localhost:8080/cookietest/AServlet时,添加的Cookie默认路径为/cookietest;
  2. 访问 http://localhost:8080/cookietest/servlet/BServlet时,添加的Cookie默认路径为
    /cookietest/servlet;
  3. 访问http://localhost:8080/cookietest/jsp/BServlet时,添加的Cookie默认路径为/cookietest/jsp。

5.5 Cookie的domain

Cookie的domain属性,可以让网站中二级域共享Cookie 。
比如一些网址:

http://www.baidu.com
http://zhidao.baidu.com
http://news.baidu.com
http://tieba.baidu.com

假如希望在这些主机之间共享Cookie(例如在www.baidu.com中响应的Cookie,可以在news.baidu.com请求中包含)。这是主机的问题,即域名的问题。处理这一问题其实很简单,只需要下面两步:

  • 1、设置Cookie的path为"/",示例:cookie.setPath("/")
  • 2、设置Cookie的domain为".baidu.com",示例:cookie.setDomain(".baidu.com")

5.6 Cookie保存中文

Cookie的name和value都不能使用中文,如果希望在Cookie中使用中文,那么需要先对中文进行URL编码,然后把编码后的字符串放到Cookie中。
向客户端响应中添加Cookie:

	String name = URLEncoder.encode("姓名", "UTF-8");
	String value = URLEncoder.encode("张三", "UTF-8");
	Cookie c = new Cookie(name, value);
	c.setMaxAge(3600);
	response.addCookie(c);

从客户端请求中获取 Cookie:

	response.setContentType("text/html;charset=utf-8");
	Cookie[] cs = request.getCookies();
	if(cs != null) {
		for(Cookie c : cs) {
			String name = URLDecoder.decode(c.getName(), "UTF-8");
			String value = URLDecoder.decode(c.getValue(),"UTF-8");
			String s = name + ": " + value + "<br/>";
			response.getWriter().print(s);
		}
	}

六、Session

6.1 HttpSession概述

javax.servlet.http.HttpSession接口表示一个会话,可以把一个会话内需要共享的数据保存到HttSession对象中。
会话:会话范围是某个用户从首次访问服务器开始,到该用户关闭浏览器结束。
获取HttpSession对象的方式:

	//如果当前会话已经有了session对象,那么直接返回
	//如果当前会话还不存在会话,那么创建session并返回
	HttpSession request.getSesssion()
	//当参数为true时,与requeset.getSession()相同
	//如果参数为false,那么如果当前会话中存在session则返回,不存在返回null
	HttpSession request.getSession(boolean)

HttpSession和HttpServletRequest 、ServletContext相似,都是域对象。
HttpServletRequest:一个请求创建一个request对象,所以在同一个请求中可以共享request,例如一个请求从AServlet转发到BServlet,那么AServlet和BServlet可以共享request域中的数据。
ServletContext:一个应用只创建一个ServletContext对象,所以在ServletContext中的数据可以在整个应用中共享,只要不关闭服务器,那么ServletContext中的数据就可以共享。
HttpSession:一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享Session中的数据。

Session的域方法:

	//用来存储一个对象,也可以称之为存储一个域属性
	void setAttribute(String name, Object value)
	//用来获取session中的数据
	Object getAttribute(String name)
	//用来移除HttpSession中的域属性,如果参数name指定的域属性
	//不存在,那么本方法什么都不做
	void removeAttribute(String name)
	//获取所有域属性的名称
	Enumeration getAttributeNames()

6.2 Session的实现原理

当首次使用Session时,服务器端要创建Session,Session是保存在服务器端,而给客户端的Session的id(一个Cookie中保存了sessionId) 。客户端带走的是sessionId,而数据 是保存在Session中。
当客户端再次访问服务器时,在请求中会带上sessionId,而服务器会通过sessionId找到对应的Session,而无需再创建新的Session。
如果Servlet中没有创建Session,那么响应头中就不会有sessionid的Cookie。但是所有的JSP页面中,Session可以直接使用,也就是说每个JSP页面都会自动获取Session,无论是否使用,访问JSP页面一定会带回来一个sessionid。

6.3 Session的失效时间

Session 保存在服务器,而sessionId通过Cookie发送给客户端,但这个Cookie的生命不是-1,即只在浏览器内存中存在,也就是说如果用户关闭了浏览器,那么这个Cookie就丢失了。
当用户再次打开浏览器访问服务器时,就不会有sessionId发送给服务器,那么服务器会认为你没有session,所以服务器会创建一个Session,并在响应中把sessionId中的Cookie中发送给客户端。
当一个Session长时间没人使用的话,服务器会把Session删除。在这个时长在Tomcat中,配置是30分钟,可以在${CATALANA}/conf/web.xml找到这个配置,当然也可以在自己的web.xml中覆盖这个配置:

<session-config>
	<!-- 设置session失效时间,单位是min -->
	<session-timeout>30</session-timeout>
</session-config>

6.4 Session其他常用API

	//获取sessionId,32 位长,16进制字符串 ,使用UUID生成随机字符串
	String getId()
	//获取Session可以的最大不活动时间(秒) ,默认为30分钟。当Session在30分钟
	//内没有使用,那么Tomcat会在Session池中移除这个session
	int getMaxInactiveInterval()
	//设置Session允许的最大不活动时间(秒),如果设置为1 秒,那么只要Session
	//在1秒内不被使用,那么Session就会被移除
	void setMaxInactiveInterval(int interval)
	//返回Session的创建时间,返回值为当前时间的毫秒值
	long getCreationTime()
	//返回Session的最后活动时间,返回值为当前时间的毫秒值
	long getLastAccessedTime()
	//让Session失效.调用这个方法会让Session失效,当Session失效后,客户端再次
	//请求,服务器会给客户端创建一个新的Session,并在响应中给客户端新session的
	//sessionId;(比如退出按钮退出时,让Session失效)
	void invalidate() 
	//查看Session是否为新。当客户端第一次请求时,服务器为客户端创建Session,
	//但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,
	//这时Session的状态为新。 
	//比如可以用request.getSession().isNew(),判断这个Session是新的还是旧的
	boolean isNew()

6.5 设置过期时间的方式

一般Web系统都需要控制session自动失效的时间,从而控制用户访问系统超时。设置Session失效有以下三种方式:

  • 1、在主页面或者公共页面中配置
    示例:session.setMaxInactiveInterval(3600);,参数单位是秒,即在没有活动1小时后,Session将失效。注意:这里Session设置的时间是根据服务器来计算的,而不是客户端。所以如果是在调试程序,应该是修改服务器端时间来测试,而不是客户端。
  • 2、较通用的设置Session失效时间的方法是在项目的web.xml中设置。
  • 3、直接在应用服务器中设置,例如:若容器是tomcat,可以在tomcat目录下conf/web.xml中设置。

如果上述三个地方如果都设置了,优先级从高到低:(1)>(2)>(3)。

6.6 Session和Cookie的区别

SessionCookie
存储方式可以存储任何类型的数据只能存储字符串,如果要存储非ASCII字符串还要对其编码
隐私安全存储在服务器,安全性高存储在浏览器,安全性低
有效期保存在服务器中,设置maxInactiveInterval属性值来确定Session的有效期。并且Session依赖于名为JSESSIONID的Cookie,该Cookie默认的maxAge属性为-1。如果关闭了浏览器,该Session虽然没有从服务器中消亡,但也就失效了保存在硬盘中,只需要设置maxAge属性为比较大的正整数,即使关闭浏览器,Cookie还是存在的
对服务器的负担保存在服务器的,每个用户都会产生一个Session,如果是并发访问的用户非常多,是不能使用Session的,Session会消耗大量的内存保存在客户端的,不占用服务器的资源
是否能跨域名只在当前的域名内有效,不可跨域名可以设置domain属性来实现跨域名
大小限制没有大小限制,和服务器的内存大小有关有大小限制,以及浏览器在存Cookie的个数也有限制

Cookie是Web服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个Web服务器存储Cookie。以后浏览器在给特定的Web服务器发请求的时候,同时会发送所有为该服务器存储的Cookie。


版权声明:本文为m0_37741420原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。