文章目录
- Servlet
- 创建Servlet
- 探究Servlet的生命周期
- HttpServlet
- @WebServlet注解详解
- 重定向与请求转发
- ServletContext
- ServletContext中的接口
- HttpServletRequest
- HttpServletResponse
- 状态码解释
- Cookie
Servlet
Q:它能做什么呢?
A:我们可以通过实现Servlet来进行动态网页响应,使用Servlet,不再是直接由Tomcat服务器发送我们编写好的静态网页内容(HTML文件),而是由我们通过Java代码进行动态拼接的结果,它能够很好地实现动态网页的返回。
当然,Servlet并不是专用于HTTP协议通信,也可以用于其他的通信,但是一般都是用于HTTP。
创建Servlet
我们只需要实现Servlet
类即可,并添加注解@WebServlet
来进行注册。
@WebServlet("/test")
public class TestServlet implements Servlet {
...实现接口方法
}
除了直接编写一个类,我们也可以在web.xml
中进行注册,现将类上@WebServlet
的注解去掉:
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>com.example.webtest.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
这样的方式也能注册Servlet
实际上,Tomcat服务器会为我们提供一些默认的Servlet,也就是说在服务器启动后,即使我们什么都不编写,Tomcat也自带了几个默认的Servlet,他们编写在conf目录下的web.xml中:
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
我们发现,默认的Servlet实际上可以帮助我们去访问一些静态资源,这也是为什么我们启动Tomcat服务器之后,能够直接访问webapp目录下的静态页面。
探究Servlet的生命周期
Q:一个Servlet是如何运行的。
A:首先我们需要了解,Servlet中的方法各自是在什么时候被调用的,我们先编写一个打印语句来看看:
public class TestServlet implements Servlet {
public TestServlet(){
System.out.println("我是构造方法!");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("我是init");
}
@Override
public ServletConfig getServletConfig() {
System.out.println("我是getServletConfig");
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("我是service");
}
@Override
public String getServletInfo() {
System.out.println("我是getServletInfo");
return null;
}
@Override
public void destroy() {
System.out.println("我是destroy");
}
}
我们首先启动一次服务器,然后访问我们定义的页面,然后再关闭服务器,得到如下的顺序:
我是构造方法!
我是init
我是service
我是service(出现两次是因为浏览器请求了2次,是因为有一次是请求favicon.ico,浏览器通病)我是destroy
我们可以多次尝试去访问此页面,但是init和构造方法只会执行一次,而每次访问都会执行的是service
方法,因此,一个Servlet的生命周期为:
- 首先执行构造方法完成 Servlet 初始化
- Servlet 初始化后调用 init () 方法。
- Servlet 调用 service() 方法来处理客户端的请求。
- Servlet 销毁前调用 destroy() 方法。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
现在我们发现,实际上在Web应用程序运行时,每当浏览器向服务器发起一个请求时,都会创建一个线程执行一次service
方法,来让我们处理用户的请求,并将结果响应给用户。
我们发现service
方法中,还有两个参数,ServletRequest
和ServletResponse
,实际上,用户发起的HTTP请求,就被Tomcat服务器封装为了一个ServletRequest
对象,我们得到是其实是Tomcat服务器帮助我们创建的一个实现类,HTTP请求报文中的所有内容,都可以从ServletRequest
对象中获取,同理,ServletResponse
就是我们需要返回给浏览器的HTTP响应报文实体类封装。
那么我们来看看ServletRequest
中有哪些内容,我们可以获取请求的一些信息:
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//首先将其转换为HttpServletRequest(继承自ServletRequest,一般是此接口实现)
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println(request.getProtocol()); //获取协议版本
System.out.println(request.getRemoteAddr()); //获取访问者的IP地址
System.out.println(request.getMethod()); //获取请求方法
//获取头部信息
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()){
String name = enumeration.nextElement();
System.out.println(name + ": " + request.getHeader(name));
}
}
我们发现,整个HTTP请求报文中的所有内容,都可以通过HttpServletRequest
对象来获取,当然,它的作用肯定不仅仅是获取头部信息
那么我们再来看看ServletResponse
,这个是服务端的响应内容,我们可以在这里填写我们想要发送给浏览器显示的内容:
//转换为HttpServletResponse(同上)
HttpServletResponse response = (HttpServletResponse) servletResponse;
//设定内容类型以及编码格式(普通HTML文本使用text/html,之后会讲解文件传输)
response.setHeader("Content-type", "text/html;charset=UTF-8");
//获取Writer直接写入内容
response.getWriter().write("我是响应内容!");
//所有内容写入完成之后,再发送给浏览器
现在我们在浏览器中打开此页面,就能够收到服务器发来的响应内容了。其中,响应头部分,是由Tomcat帮助我们生成的一个默认响应头。
HttpServlet
Servlet
有一个直接实现抽象类GenericServlet
,那么我们来看看此类做了什么事情。
我们发现,这个类完善了配置文件读取和Servlet信息相关的的操作,但是依然没有去实现service方法,因此此类仅仅是用于完善一个Servlet的基本操作,那么我们接着来看HttpServlet
,它是遵循HTTP协议的一种Servlet,继承自GenericServlet
,它根据HTTP协议的规则,完善了service方法。
在阅读了HttpServlet源码之后,我们发现,其实我们只需要继承HttpServlet来编写我们的Servlet就可以了,并且它已经帮助我们提前实现了一些操作,这样就会给我们省去很多的时间。
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h1>恭喜你解锁了全新玩法</h1>");
}
}
现在,我们只需要重写对应的请求方式,就可以快速完成Servlet的编写。
@WebServlet注解详解
我们接着来看WebServlet注解,我们前面已经得知,可以直接使用此注解来快速注册一个Servlet,那么我们来想细看看此注解还有什么其他的玩法。
首先name属性就是Servlet名称,而urlPatterns和value实际上是同样功能,就是代表当前Servlet的访问路径,它不仅仅可以是一个固定值,还可以进行通配符匹配:
@WebServlet("/test/*")
上面的路径表示,所有匹配/test/随便什么
的路径名称,都可以访问此Servlet,我们可以在浏览器中尝试一下。
也可以进行某个扩展名称的匹配:
@WebServlet("*.js")
这样的话,获取任何以js结尾的文件,都会由我们自己定义的Servlet处理。
那么如果我们的路径为/
呢?
@WebServlet("/")
此路径和Tomcat默认为我们提供的Servlet冲突,会直接替换掉默认的,而使用我们的,此路径的意思为,如果没有找到匹配当前访问路径的Servlet,那么久会使用此Servlet进行处理。
我们还可以为一个Servlet配置多个访问路径:
@WebServlet({"/test1", "/test2"})
我们接着来看loadOnStartup属性,此属性决定了是否在Tomcat启动时就加载此Servlet,默认情况下,Servlet只有在被访问时才会加载,它的默认值为-1,表示不在启动时加载,我们可以将其修改为大于等于0的数,来开启启动时加载。并且数字的大小决定了此Servlet的启动优先级。
@WebServlet(value = "/test", loadOnStartup = 1)
public class TestServlet extends HttpServlet {
@Override
public void init() throws ServletException {
super.init();
log.info("我被初始化了!");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h1>balbalaba</h1>");
}
}
重定向与请求转发
resp.sendRedirect("time");//重定向
req.getRequestDispatcher("/time").forward(req, resp);//请求转发
Q:请求转发有什么好处呢?
A:它可以携带数据!
req.setAttribute("test", "我是请求转发前的数据");
req.getRequestDispatcher("/time").forward(req, resp);
System.out.println(req.getAttribute("test"));
通过setAttribute
方法来给当前请求添加一个附加数据,在请求转发后,我们可以直接获取到该数据。
两者的区别为:
- 请求转发是一次请求,重定向是两次请求
- 请求转发地址栏不会发生改变, 重定向地址栏会发生改变
- 请求转发可以共享请求参数 ,重定向之后,就获取不了共享参数了
- 请求转发只能转发给内部的Servlet
ServletContext
ServletContext全局唯一,它是属于整个Web应用程序的,我们可以通过getServletContext()
来获取到此对象。
此对象也能设置附加值:
ServletContext context = getServletContext();
context.setAttribute("test", "我是重定向之前的数据");
resp.sendRedirect("time");
System.out.println(getServletContext().getAttribute("test"));
因为无论在哪里,无论什么时间,获取到的ServletContext始终是同一个对象,因此我们可以随时随地获取我们添加的属性。
它不仅仅可以用来进行数据传递,还可以做一些其他的事情,比如请求转发:
context.getRequestDispatcher("/time").forward(req, resp);
它还可以获取根目录下的资源文件(注意是webapp根目录下的,不是resource中的资源)
ServletContext中的接口
Enumeration<String> enumeration = getServletContext().getAttributeNames();//包含了ServletContext中的所有域属性名
Object object=getServletContext().getAttribute("name");//返回与之匹配的域属性值
getServletContext().removeAttribute("name");//删除该属性名对应的域属性
getServletContext().setAttribute("la","巴拉巴拉");//设置ServletContext的域属性,前者为属性名,后者为域属性值
上面的为全局的属性,也可以通过请求转发来实现一写信息的转发(下面还有解释)
req.setAttribute("name", "value");//设置域属性
String name=req.getAttribute("name");//获取域属性值
也可以获取web应用下的资源文件
其他方法遇到了再查,再记录
HttpServletRequest
-
HttpServletRequest
对象代表客户端的请求 -
当客户端/浏览器通过 HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中
HttpServletRequest
常用方法:
-
getRequestURI()
获取请求的资源路径 -
getRequestURL()
获取请求的统一资源定位符(绝对路径 -
getRemoteHost()
获取客户端的 主机, getRemoteAddr() -
getHeader()
获取请求头 -
getParameter()
获取请求的参数 -
getParameterValues()
获取请求的参数(多个值的时候使用), 比如 checkbox, 返回的数组 -
getMethod()
获取请求的方式 GET 或 POST -
setAttribute(key, value)
设置域数据 -
getAttribute(key)
获取域数据 -
getRequestDispatcher()
获取请求转发对象, 请求转发的核心对象
因为浏览器地址栏会停止在第一个 servlet ,如果你刷新页面,会再次发出请求(并且会 带数据), 所以在支付页面情况下,不要使用请求转发,否则会造成重复支付
HttpServletResponse
他是通过流来给客户端传递数据的
字节流 getOutputStream() 常用于下载(传递二进制数据)
字符流 getWriter() 常用于回传字符串(非常常用)
// 两个流同时只能使用一个,也就是用了字节流就不能用字符流了,用了字符流就不能用字节流了
对于getWriter
:
他获取的对象为PrintWriter
类型的数据
resp.getWriter().write("hello servlet");
//或者
PrintWriter writer=resp.getWriter();
writer.write("hello ");
在向客户端写入东西时,要设置编码规则!!!
resp.setContentType("text/html;charset=UTF-8");
还有一种方法:setHeader()
,应该没上面的好用,就不写了
状态码解释
状态码 | 状态码描述 | 说明 |
---|---|---|
200 | OK | 请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态。 |
302 | Move temporarily | 重定向,请求的资源临时从不同的 地址响应请求。 |
304 | Not Modified | 从浏览器缓存中读取数据,不从服务器重新获取数据。例如,用户第一次从浏览器访问服务器端图片资源,以后在访问该图片资源的时候就不会再从服务器上加载而直接到浏览器缓存中加载,这样效率更高。 |
404 | Not Found | 请求资源不存在。通常是用户路径编写错误,也可能是服务器资源已删除。 |
403 | Forbidden | 服务器已经理解请求,但是拒绝执行它 |
405 | Method Not Allowed | 请求行中指定的请求方法不能被用于请求相应的资源 |
500 | Internal Server Error | 服务器内部错误。通常程序抛异常 |
Cookie
什么是Cookie?它可以在浏览器中保存一些信息,并且在下次请求时,请求头中会携带这些信息。
我们可以编写一个测试用例来看看:
Cookie cookie = new Cookie("test", "yyds");
resp.addCookie(cookie);
resp.sendRedirect("time");
for (Cookie cookie : req.getCookies()) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}