servlet简介
一般的网页程序,是由我们通过浏览器访问来实现的,在这一过程中浏览器发送请求,服务器接收请求,并对请求做出响应,这就是我们熟悉的B/S模型(浏览器-服务器),而servlet就是对请求做出处理的组件,运行于支持java的应用服务器中。
servlet的作用
上图就是最经典的MVC模式:jsp+JavaBean+servlet,其中servlet就扮演者对数据进行处理的控制器,我们的Web应用完全基于Http协议,http协议有请求报文(request)和响应报文(response),响应报文就是服务器向浏览器发送的数据形成的信息,而请求报文就是我们常说的GET、POST请求两种形式(当然http请求还包含DELETE,OPTIONS,PUT和TRACE),从浏览器向服务器发送。
怎么使用servlet
首先我们需要创建一个web项目,我用的idea,区别不大。
servlet就是一个接口,我们只需要实现这个接口就OK,特别要注意的是,我们需要用到第三方jar包,在本地tomcat文件夹的lib文件夹里有一个servlet-api.jar,这个jar包需要导入web项目。
public class Myservlet implements Servlet{
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
我们待会再仔细讨论servlet里的一些方法,现在我们要说的是,既然我们说servlet是一个控制器,那么浏览器是如何知道的,又如何找到这个java类呢?学过安卓的人应该知道安卓的四大组件都需要在清单文件里申明,同样的道理,实现了servlet的这个java类也需要在web.xml里面进行配置注册。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>hello Servlet</servlet-name>
<!-- servlet的全类名 -->
<servlet-class>com.hbust.xiaoyu.test1.Myservlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 和某一个servlet节点的servlet-name节点一致 -->
<servlet-name>hello Servlet</servlet-name>
<!-- 具体访问路径,代表当前web节点的根目录 -->
<url-pattern>/.*</url-pattern>
</servlet-mapping>
</web-app>
我们待会来具体讲web.xml,现在我们已经配置好了一个servlet,每一个servlet都是这样配置。
servlet的生命周期
所有servlet都是由一个servlet容器控制,servlet容器还运行jsp、Filter等等,servlet容器就是一个创建servlet并调用servlet的生命周期的软件环境,我们实现servlet只是覆盖他里面的方法,而最后的调用是由servlet容器来控制。
我们回过头来看看servlet接口里面有哪些方法。
①
init(ServletConfig servletConfig)
②
service(ServletRequest servletRequest, ServletResponse servletResponse)
③
destroy()
④
ServletConfig getServletConfig()
⑤
getServletInfo()
其中①②③是servlet生命周期有关的方法,有安卓开发经验的人应该很好理解生命周期
init()方法:当servlet第一次被创建才会调用,整个生命周期内仅调用一次。
service()方法:每次调用service,该方法都会被调用。
destroy()方法:当service被销毁之前调用,整个生命周期内仅调用一次
④是servlet的一个内置接口,下面会讲
⑤方法是一个可选的方法,它提供有关servlet的信息,如作者、版本、版权
执行顺序:MyServlet的构造方法→①→②→…②…→③
问题那么问题来了,既然每次调用servlet,都会调用该servlet对应的java类的构造方法,那还需要servic()方法干嘛?
因为service方法里传入了
ServletRequest
,
ServletResponse
两个对象,他们都是接口,有很多API我们可以有,这是构造方法无法替代的。
service的接口API
①
ServletConfig
接口
:
封装了servlet配置信息,并且可以获取servletContext对象。
接口是有服务器自己实现,我们可以直接用他提供的四种方法:
|
|
|
|
|
|
|
|
例1
getInitParameter(String name)方法使用
如果在web.xml中注册的servlet中声明了一些初始化参数,如user和password。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>hello Servlet</servlet-name>
<!-- servlet的全类名 -->
<servlet-class>com.hbust.xiaoyu.test1.Myservlet</servlet-class>
<!-- 初始化参数 -->
<init-param>
<param-name>user</param-name>
<param-value>Jim</param-value>
</init-param>
<!-- 初始化参数 -->
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!-- 和某一个servlet节点的servlet-name节点一致 -->
<servlet-name>hello Servlet</servlet-name>
<!-- 具体访问路径,代表当前web节点的根目录 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
如果我们想获取这些参数,就可以用到getInitParamete方法
public class Myservlet implements Servlet{
@Override
public void init(ServletConfig servletConfig) throws ServletException {
String user=servletConfig.getInitParameter("user");
String pass=servletConfig.getInitParameter("password");
System.out.println("user:"+user+"---"+"pass:"+pass);
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
因为我在web.xml对这个servlet的 访问路径<url-pattern>/hello</url-pattern>是这样配置的,所以我们在浏览器里也要在更目录下加hello才能访问到。
结果如下:
例2
getInitParameterNames()方法的使用
首先我们讲解一下Enumeration对象,这是方法的返回值。这是一个很少使用但是还未遗弃的枚举接口,比迭代器更早使用,基本上被迭代器取代。接口中有两个方法:
boolean hasMoreElements():该枚举是否包含更多的元素
Object nextElement():返回下一个对象
如例1一样,部分代码如下:
public void init(ServletConfig servletConfig) throws ServletException {
Enumeration<String> enumeration=servletConfig.getInitParameterNames();
while (enumeration.hasMoreElements()){
//获取初始化参数的key
String key=enumeration.nextElement();
//通过key,获取初始化参数的值
String value=servletConfig.getInitParameter(key);
//打印
System.out.println("---"+key+":"+value);
}
}
结果如下:
喜欢追剧的朋友可以关注一下微信公众号 沉沉影视
任何电影网络剧发名字就有百度云链接,还有大量学习资料免费提供哦
②ServletContext接口
:Servlet引擎为每一个web程序都创建了一个对应的ServletContext对象,ServletContext对象被包含在ServletConfig对象中,调用ServletConfig的getServletContext()方法就可以得到该对象的引用。由于一个web程序中所有servlet共享一个ServletContext对象,所以也称之为Application(上下文)对象,也叫Web应用程序对象。
ServletContext的功能:
>获取web应用程序的初始化参数
>记录日志
>application域范围内的属性
>访问资源文件
>获取虚拟路径所映射的本地路径
>web应用程序之间的访问
ServletContext的API:
String getInitParameter (String name) |
获取 web.xml 文 件 的 中 指 定 名 称 的上下文参数值 |
Enumeration getInitParameterNames() |
获取 web.xml 文件的中的所有的上下文参数名称。其返回值为枚举类型 Enumeration |
void setAttribute(String name, Object object) |
在 ServletContext 的公共数据空间中,也称为域属性空间,放入数据。这些数据对于 Web应用来说,是全局性的,与整个应用的生命周期相同。当然,放入其中的数据是有名称的,通过名称来访问该数据 |
Object getAttribute(String name) |
从 ServletContext 的域属性空间中获取指定名称的数据 |
void removeAttribute(String name) |
从 ServletContext 的域属性空间中删除指定名称的数据。 String getRealPath(String path):获取当前 Web 应用中指定文件或目录在本地文件系统中的路径 |
String getContextPath() |
获取当前应用在 Web 容器中的名称 |
RequestDispatcher getRequestDispatcher(String s) |
获取请求转发对象 |
例1 获取web应用程序的初始化参数
首先看web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 配置web应用的初始化参数 -->
<context-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>hello Servlet</servlet-name>
<!-- servlet的全类名 -->
<servlet-class>com.hbust.xiaoyu.test1.Myservlet</servlet-class>
<!-- 初始化参数 -->
<init-param>
<param-name>user</param-name>
<param-value>Jim</param-value>
</init-param>
<!-- 初始化参数 -->
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!-- 和某一个servlet节点的servlet-name节点一致 -->
<servlet-name>hello Servlet</servlet-name>
<!-- 具体访问路径,代表当前web节点的根目录 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
看到这里,你可能有疑问,context-param节点和init-param节点都是配置初始化参数,那这两种有什么区别呢?
很显然,context-param节点是web-app的子节点,而init-param节点在servlet节点内部,很显然前者就是全局变量,对每一个servlet都有效,而后者是局部变量,只对当前servlet有效。
再来看java代码:
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取ServletContext对象
ServletContext context=servletConfig.getServletContext();
//获取全局变量的driver
String driver=context.getInitParameter("driver");
System.out.println("--"+"driver:"+driver);
//获取局部变量的user
String user=context.getInitParameter("user");
System.out.println("--"+"user:"+user);
}
结果如下:
很遗憾,只能获取全局变量,获取不到局部变量。
③
ServletRequest
接口
:封装了请求信息,可以从中获取到任何请求信息,也就是GET,POST请求提交过来的参数。
常用API如下:
String getParameter(String name) |
根据请求参数的名字返回参数值,如果请求参数有多个值(多选),只能获取第一个值 |
String[] getParameterValues(String name) |
根据请求参数的名字返回对应的数组 |
Enumeration getParameterNames() |
返回参数名对应的Enumeration 对象 |
Map getParameterMap() |
返回请求参数的键值对 Key:参数名,Value:参数值 |
void setCharacterEncoding(String s) |
设置字符编码 |
例1
部分java代码如下:
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
//String getParameter(String name)中的name对应form表单input标签里的name属性
String user=request.getParameter("user");
String pass=request.getParameter("pass");
String favorite=request.getParameter("favorite");
System.out.println("user:"+user+"--"+"pass:"+pass+"--"+"favorite:"+favorite);
//String[] getParameterValues(String name)
String[] favorite2=request.getParameterValues("favorite");
for(String s:favorite2){
System.out.println("**"+"favorite:"+s);
}
//Enumeration getParameterNames()
Enumeration<String> enumeration=request.getParameterNames();
while (enumeration.hasMoreElements()){
String key=enumeration.nextElement();
System.out.println("^^"+key);
}
//Map getParameterMap(),遍历map的方法如下。
Map<String,String[]> map=request.getParameterMap();
for(Map.Entry<String,String[]> entry:map.entrySet()){
System.out.println(">>"+entry.getKey()+":"+ Arrays.asList(entry.getValue()));
}
}
index.jsp代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<form action="/hello" method="post">
user:<input name="user" type="text"> <br>
pass:<input name="pass" type="password"> <br>
favorite:<br>
<input name="favorite" type="checkbox" value="java">Java
<input name="favorite" type="checkbox" value="Python">Python
<input name="favorite" type="checkbox" value="C#">C#
<br>
<input type="submit" value="提交">
</form>
</body>
</html>
这里form提交的是post请求,action对应web.xml里的url-param,也就是说这个请求由这个servlet来处理。
<servlet-mapping>
<!-- 和某一个servlet节点的servlet-name节点一致 -->
<servlet-name>hello Servlet</servlet-name>
<!-- 具体访问路径,代表当前web节点的根目录 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
jsp视图:
结果:
④
ServletResponse
接口
:封装了响应信息,如果具体要给用户什么响应,可以使用该接口。
API如下:
setCharacterEncoding(String charset) |
设置响应正文的字符编码。响应正文的默认字符编码为ISO-8859-1 |
setContentLength(int len) |
设置响应正文的长度 |
setContentType(String type) |
设置响应正文的MIME类型 |
getCharacterEncoding() |
返回响应正文的字符编码 |
getContentType() |
返回响应正文的MIME类型 |
setBufferSize(int size) |
设置用于存放响应正文数据的缓存区的大小 |
getBufferSize() |
获得用于存放正文数据的缓存区的大小 |
reset() |
清空缓存区内的正文数据,并且清空响应状态代码及响应头 |
resetBuffer() |
仅仅清空缓存区内的正文数据,不清空响应状态代码及响应头 |
flushBuffer() |
强制性地把缓存区内的响应正文数据发送到客户端 |
isCommitted() |
返回一个boolean类型的值。如果为true,表示缓存区内的数据已经提交给客户,即数据已经发送到客户端 |
getOutputStream() |
返回一个ServletOutputStream对象,Servlet用它来输出二进制的正文数据 |
getWriter() |
返回一个PrintWriter对象,Servlet用它来输出字符串形式的正文数据 |
web.xml详解
①当启动一个WEB项目时,容器包括(JBoss、Tomcat等)首先会读取项目web.xml配置文件里的配置,当这一步骤没有出错并且完成之后,项目才能正常地被启动起来。
②启动WEB项目的时候,容器首先会去它的配置文件web.xml读取两个节点: <listener></listener>和<context-param></context-param>。一个是监听器,下面会讲,一个是上面讲到的初始化参数。
③紧接着,容器创建一个ServletContext(application),这个WEB项目所有部分都将共享这个上下文
④容器以<context-param></context-param>的name作为键,value作为值,将其转化为键值对,存入ServletContext
⑤ 容器创建<listener></listener>中的类实例,根据配置的class类路径<listener-class>来创建监听,在监听中会有contextInitialized(ServletContextEvent args)初始化方法,启动Web应用时,系统调用Listener的该方法,在这个方法中获得:context-param的值就是application.getInitParameter(“context-param的键”);得到这个context-param的值之后,你就可以做一些操作了。
你可能想在项目启动之前就打开数据库,那么这里就可以在<context-param>中设置数据库的连接方式(驱动、url、user、password),在监听类中初始化数据库的连接。这个监听是自己写的一个类,除了初始化方法,它还有销毁方法,用于关闭应用前释放资源。比如:说数据库连接的关闭,此时,调用contextDestroyed(ServletContextEvent args),关闭Web应用时,系统调用Listener的该方法。(这里下面会讲的,大致了解就可以)
⑥接着,容器会读取<filter></filter>,根据指定的类路径来实例化过滤器(后面也会介绍)
注意: 总的来说,web.xml的加载顺序是:<context-param>-><listener>-><filter>-><servlet>。Servlet是在第一次发起请求的时候被实例化的,而且一般不会被容器销毁,它可以服务于多个用户的请求。其中,如果web.xml中出现了相同的元素,则按照在配置文件中出现的先后顺序来加载。
web.xml节点:
<display-name></display-name> |
定义web应用的名称 |
<context-param></context-param> |
设置上下文初始化参数(全局) |
<context-param></context-param> |
设置servlet初始化参数(局部) |
<session-config></session-config> |
用于设置容器的session参数 |
<listener></listener> |
监听器 |
<filter></filter> |
过滤器 |
<servlet></servlet> |
注册servlet |
<welcome-file-list></welcome-file-list> |
指定欢迎页 |
其中<welcome-file-list></welcome-file-list>是第一个显示的页面,按顺序寻找下面的页面,只要找到了就停止并显示。
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
<welcome-file>/frist.jsp</welcome-file>
<welcome-file>/index.html</welcome-file>
</welcome-file-list>
<servlet></servlet>中的<load-on-startup></load-on-startup>
load-on-startup 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。
它的值必须是一个整数,表示servlet被加载的先后顺序。
①如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
②如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
注意:web.xml不是web程序必须的,但是不得不说web.xml在开发中方便了不少。这里我们也可以用注解来配置<servlet></servlet>。
看如下代码,有些配置可有可无:
@WebServlet(
name = "hello Servlet", //servlet名称
urlPatterns = { "/hello" }, //url
loadOnStartup = -1,
initParams = { @WebInitParam(name = "user", value = "Jim") }
)//初始化参数
public class MyServlet extends HttpServlet {
}
这样是不是方便多了,取代了如下内容:
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>hello Servlet</servlet-name>
<!-- servlet的全类名 -->
<servlet-class>com.hbust.xiaoyu.test1.Myservlet</servlet-class>
//启动项
<load-on-startup>-1</load-on-startup>
<!-- 初始化参数 -->
<init-param>
<param-name>user</param-name>
<param-value>Jim</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!-- 和某一个servlet节点的servlet-name节点一致 -->
<servlet-name>hello Servlet</servlet-name>
<!-- 具体访问路径,代表当前web节点的根目录 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
GenericServlet
讲解
很显然,我们在开发者,每个java类都需要实现servlet接口的所有方法,可想而知是很麻烦的,GenericServlet这个类也实现了Servlet这个接口,还实现了ServletConfig接口,并提供了getInitParameter(String name)、getInitParameterNames()、getServletContext()、getServletName()等方法,这都是ServletConfig对象的方法,我们就不用再重复写一些代码。所以,我们只需要继承这个类就可以,ServletConfig对象的4个方法就可以直接调用。
我们来看看他的源代码,非常简单,所以很有必要看。
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
}
GenericServlet的service()方法是一个抽象方法,所以我们继承这个类,还需要实现这个方法。当然我们也可以重写destory()方法,但是我们重写的init()方法已经不是生命周期方法了,是GenericServlet()重载的init()方法。如果我们重写GenericServlet()的init(ServletConfig config)方法,这样就覆盖了GenericServlet()在init(ServletConfig config)方法中把config对象赋值给全局变量的代码。如果实在想在servlet被创建时做一些事情,我们要么重写init()方法,要么重写init(ServletConfig config)方法并且在里面写一行代码:super(),这样父类方法就不会被覆盖。
public class Myservlet extends GenericServlet{
@Override
public void destroy() {
}
@Override
public void init() throws ServletException {
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//ServletConfig对象里的api直接用,this可省略
String user=this.getInitParameter("user");
String driver=this.getServletContext().getInitParameter("driver");
}
}
HttpServlet讲解
大家都知道Servlet,但是不一定很清楚servlet框架,这个框架是由两个Java包组成:javax.servlet和javax.servlet.http. 在javax.servlet包中定义了所有的Servlet类都必须实现或扩展的的通用接口和类.在javax.servlet.http包中定义了采用HTTP通信协议的HttpServlet类.HttpServlet首先必须读取Http请求的内容。Servlet容器负责创建HttpServlet对象,并把Http请求直接封装到HttpServlet对象中,大大简化了HttpServlet解析请求数据的工作量。
我们只需要继承httpServlet这个类就可以。查看源码可以发现,httpServlet继承了GenericServlet这个类,我们只需要重写doGet()和doPost()这两个常用方法处理get和post。
public class Myservlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.doGet(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.doPost(request, response);
}
/*
我们仍然可以覆盖HttpServlet类父类的GenericServlet类下的destroy()和init()方法
*/
@Override
public void destroy() {
}
@Override
public void init() throws ServletException {
}
}
HttpServletRequest
接口
httpServletRequest接口继承了ServletRequest接口,所有他不仅包含ServletRequest接口的方法,还包含如下几种常用方法。
①获得客户机信息
getRequestURL方法返回客户端发出请求时的完整URL。
getRequestURI方法返回请求行中的资源名部分。
getQueryString 方法返回请求行中的参数部分。
getPathInfo方法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头。
getRemoteAddr方法返回发出请求的客户机的IP地址。
getRemoteHost方法返回发出请求的客户机的完整主机名。
getRemotePort方法返回客户机所使用的网络端口号。
getLocalAddr方法返回WEB服务器的IP地址。
getLocalName方法返回WEB服务器的主机名。
②获得客户机请求头
getHeader(string name)方法:String
getHeaders(String name)方法:Enumeration
getHeaderNames()方法
HttpServletResponse
接口
httpServletResponse接口继承了ServletServletResponse接口,他不仅包含ServletResponse的一些方法,还包含一下几个常用方法:
》 public void addHeader(Sreing name,String value):增加一个cookie到响应中,这个方法可多次调用,投置多个cookie
》 public void addHeader(String name,String value):将一个名称为name,值为value的响应报头添加到响应中
》 public void sendRedirect(String location):发送一个临时的重定向响应到客户端,以便客户端访问新的URL
》 public void encodeURL(String url):使用sessionID对用于重定向的URL进行编码
servlet的转发和重定向
转发:当浏览器向服务器发送一次request请求,一个servlet把原来的request请求交给另一个servlet,最后服务器发一个response响应给浏览器。
举例你去银行A窗口办理业务,并且把你的身份证等信息递进去并且告诉他你办理什么业务,然后A窗口发现办不了,所以他把请求直接递给B窗口并且告诉他办理什么业务,B窗口办理完之后,把结果发给你。
重定向:浏览器向服务器发送一次请求,servlet1处理请求并向浏览器发送响应,然后浏览器再向服务器发送请求,servlet2处理请求。
举例你去银行A窗口办理业务,并且把你的身份证等信息递进去并且告诉他你办理什么业务,然后A窗口发现办不了并且直接告诉你办不了,把你的东西递给你,告诉你B窗口可以办理。所以你把身份证等信息重新递给B窗口并且告诉他办理什么业务。(至于B窗口是转发还是重定向就不一定了。)
HttpServlet实现转发和重定向的实现:
例1 index.jsp代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<form action="/hello" method="post">
user:<input name="user" type="text"> <br>
pass:<input name="pass" type="password"> <br>
<input type="submit" value="提交">
</form>
</body>
</html>
java代码如下:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置字符编码
request.setCharacterEncoding("UTF-8");
request.getRequestDispatcher("/WEB-INF/pages/main.jsp").forward(request,response);
}
这里的
getRequestDispatcher
()方法并不是HttpServletRequest的方法,而是ServletRequest的方法,返回
Dispatcher
对象。
main.jsp代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主页</title>
</head>
<body>
<h3>登入成功</h3>
<h6>转发</h6>
user:<%=request.getParameter("user")%><br>
pass:<%=request.getParameter("pass")%>
</body>
</html>
jsp嵌套java代码以及取值后面会讲到。
通过在index.jsp输入的值传到另一个页面,是通过转发实现的,main.jsp页面所得到的request对象仍然是通过index.jsp从浏览器向服务器发送的post请求封装成request对象,那么MyServlet将这个请求转发,main.jsp仍然能取到这个请求的参数。结果如下:
例2 重定向需要用到sendRedirect()方法,这个方法是HttpServletResponse提供的,ServletResponse接口没有。
其他代码不变,java代码如下:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.sendRedirect("main2.jsp");
}
这里有一个小问题搞了半天都没弄明白,网上查了一下sendRedirect()方法的路径问题,当我的main2.jsp页面和index.jsp同级时,我的路径 response.sendRedirect(“main2.jsp”);这样写,结果如图,两个值为空,这是正确结果。因为重定向之后,携带了参数的request已经失效了,而现在的request是一个新请求,所有user和pass的值肯定是null
但是如果我的路径是response.sendRedirect(“WEB-INF/pages/main2.jsp”)就会报404错误。很无语,完全不知道怎么回事,而且网上的一些方法也不对。
解决
幸运的是在整理mybatis是解决了这个问题,是idea的原因,idea不像eclipse,当你自己创建一个pages文件夹的时候,eclipse会让你选择是什么类型的文件夹,而idea需要手动设置,快捷键shift+Ctrl+Alt+S,点击你创建的module,点击Source,选择你创建的文件夹右键,选择类型
Source:用于存放一些jsp,HTML,或者一些代码等,这样idea才会加载。
Tests:存放测试类等
Resources:存放配置文件等信息
这样idea才能将自定义的文件夹加载为source folder
拦截器Filter
我们知道,浏览器向服务器发送request请求,然后服务器会把请求交给servlet容器,servlet容器会把请求交给servlet,servlet处理请求之后,会返回一个结果,servlet容器就会把结果通过服务端返回给客户端,也就是所谓的响应。
但是我们可能需要在每一个servlet接收到请求或者每一个servlet返回响应的这个切点做一些事情,比如设置编码、检查用户是否登入等等,这个时候拦截器Filter就站出来了,他也是有servlet容器来管理的。
Filter可以在一个请求到达Servlet之前预处理用户请求,也可以在离开Servlet 时处理http响应。Filter拦截处理之后会交给下一个Filter,直到交给servlet。
我们之前讲过web.xml的加载顺序,Filter需要在web.xml里配置。
<filter>
<filter-name>my filter</filter-name>
<filter-class>com.hbust.xiaoyu.test1.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>my filter</filter-name>
<!-- 拦截哪些路径, -->
<url-pattern>/pages/*</url-pattern>
</filter-mapping>
我们只需要实现javax.servlet包Filter接口的三个方法
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//加载:启动服务器时加载过滤器的实例,并调用init()方法,这个与web.xml加载顺序有关
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//每次拦截的请求或者响应都会执行这个方法
}
@Override
public void destroy() {
//当服务器关闭前调用destroy()方法,销毁实例;
}
}
例1
检查是否登入代码如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//每次拦截的请求或者响应都会执行这个方法
//获取HttpSession对象,判断是否登陆
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession();
if(session.getAttribute("user")==null){
//非法访问,没有登陆,跳转到登陆页面
session.setAttribute("error", "非法访问");
// 保存客户想要去的地址, 登录成功后则直接跳转,而不是到首页
String goURL = req.getServletPath();//(获取到地址不包括参数)
//判断参数是否为空,不null就获取参数
if(req.getQueryString()!=null){
goURL+="?"+req.getQueryString();
}
session.setAttribute("goURL", goURL);
res.sendRedirect(req.getContextPath() + "/user/userLogin.jsp");
}else{
// 如果有下一个过滤器则跳转到下一个过滤器否则目标页面
chain.doFilter(request, response);
}
}
Listener监听器
Filter与URL有关,而Listener与对象的创建、销毁、属性改变事件有关,也就是说我们需要在某个对象创建、改变、销毁的这个切点去做一件事,那么就可以交给Listener,他可以做统计在线人数、记录访问日志等很多事。
8个listener接口,可以将其分为三类,分别如下;
①与HttpContext有关的listener接口,包括:ServletContextListener、ServletContextAttributeListener
②与HttpSession有关的listner接口。包括:HttpSessionListener、HttpSessionAttributeListener、
HttpSessionBindingListener、 HttpSessionActivationListener、
③与ServletRequest有关的Listener接口,包括:ServletRequestListener、ServletRequestAttributeListener
这里只做一个简单了解,具体要实现的时候再细谈吧!
JSP讲解
jsp其实就是一个servlet,他封装了9个内置对象,我们在jsp里面可以直接去用这9个对象。
1、request对象
request 对象是 javax.servlet.httpServletRequest类型的对象。 该对象代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的数据。(包括头信息、系统信息、请求方式以及请求参数等)。request对象的作用域为一次请求。
2、response对象
response 代表的是对客户端的响应,主要是将JSP容器处理过的对象传回到客户端。response对象也具有作用域,它只在JSP页面内有效。
3、session对象
session 对象是由服务器自动创建的与用户请求相关的对象。服务器为每个用户都生成一个session对象,用于保存该用户的信息,跟踪用户的操作状态。session对象内部使用Map类来保存数据,因此保存数据的格式为 “Key/value”。 session对象的value可以使复杂的对象类型,而不仅仅局限于字符串类型。
4、application对象
application 对象可将信息保存在服务器中,直到服务器关闭,否则application对象中保存的信息会在整个应用中都有效。与session对象相比,application对象生命周期更长,类似于系统的“全局变量”。
5、out 对象
out 对象用于在Web浏览器内输出信息,并且管理应用服务器上的输出缓冲区。在使用 out 对象输出数据时,可以对数据缓冲区进行操作,及时清除缓冲区中的残余数据,为其他的输出让出缓冲空间。待数据输出完毕后,要及时关闭输出流。
6、pageContext 对象
pageContext 对象的作用是取得任何范围的参数,通过它可以获取 JSP页面的out、request、reponse、session、application 等对象。pageContext对象的创建和初始化都是由容器来完成的,在JSP页面中可以直接使用 pageContext对象。
7、config 对象
config 对象的主要作用是取得服务器的配置信息。通过 pageConext对象的 getServletConfig() 方法可以获取一个config对象。当一个Servlet 初始化时,容器把某些信息通过 config对象传递给这个 Servlet。 开发者可以在web.xml 文件中为应用程序环境中的Servlet程序和JSP页面提供初始化参数。
8、page 对象
page 对象代表JSP本身,只有在JSP页面内才是合法的。 page隐含对象本质上包含当前 Servlet接口引用的变量,类似于Java编程中的 this 指针。
9、exception 对象
exception 对象的作用是显示异常信息,只有在包含 isErrorPage=”true” 的页面中才可以被使用,在一般的JSP页面中使用该对象将无法编译JSP文件。excepation对象和Java的所有对象一样,都具有系统提供的继承结构。exception 对象几乎定义了所有异常情况。在Java程序中,可以使用try/catch关键字来处理异常情况; 如果在JSP页面中出现没有捕获到的异常,就会生成 exception 对象,并把 exception 对象传送到在page指令中设定的错误页面中,然后在错误页面中处理相应的 exception 对象。
四大作用域
四大作用域分别是
①page域:作用在一个页面中
②request域:作用在一个请求中,只要request不重定向,数据就不会失效
③session域:作用在一个会话中,只要用户不退出,数据就不会失效
④application域:我们之前谈到了ServletContext,他就是application域,范围贯穿整个web应用
不管是page、request、session、application四个对象都可以在各自的作用域里存值(setAttribute()方法)、删值(removeAttribute()方法)、取值(getAttribute())。