Servlet原理及其使用
什么是Servlet
是JAVA的基于WEB服务器的完成动态网页开发的一种技术
Servlet的使用
编写一个Servlet,使用继承HttpServlet的方式
package com.it.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
public class MyFirstServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取表单提交的参数,填写的参数是表单对应数据的name属性
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username.equals("dqx") && password.equals("123")) {
// 转发到/success.jsp
req.getRequestDispatcher("/success.jsp").forward(req,resp);
}else {
// 在request作用域中存入一个 key value
req.setAttribute("msg", "用户名或密码错误");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
}
配置web.xml
<servlet>
<servlet-name>loginServlet</servlet-name>
<servlet-class>com.it.servlet.MyFirstServlet</servlet-class>
<!-- 配置在服务器启动时加载Servlet -->
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>loginServlet</servlet-name>
<!-- 处理的请求 -->
<url-pattern>/doLogin</url-pattern>
</servlet-mapping>
很简单的几个JSP文件
- login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<base href="<%=basePath%>">
</head>
<body>
<form action="doLogin" method="post">
<%
String msg = (String) request.getAttribute("msg");
System.out.println("---------");
if(msg != null) {
out.print(msg);
}
%>
<hr>
用户名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>
- success.jsp
<h1>登录成功</h1>
小提示,如果继承HttpServlet发现找不到类的时候,引入一个jar包添加到类路径(必须放在WEB-INF目录下)
——————————————————————————————————
Servlet读取配置(自身Servlet配置、全局配置)
为什么需要读取配置?
答:因为有些配置是固定的(例如编码),许多地方都要使用到,而如果硬编码的话,一旦编码改变,则需要更改多处,影响维护。
获得Servlet自己的配置信息
- 在web.xml中配置参数
<servlet>
<servlet-name>myConfigServlet</servlet-name>
<servlet-class>com.it.servlet.MyConfigServlet</servlet-class>
<!-- 配置Servlet配置参数 -->
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
- 在Servlet中获取配置信息(可以在构造函数、init()方法、等等地方都能获取到)
public class MyConfigServlet extends HttpServlet {
private String charset;
@Override
public void init() throws ServletException {
// 获得Servlet参数,这两种方式是一样的
charset = this.getInitParameter("charset");
charset = this.getServletConfig().getInitParameter("charset");
}
获得全局配置信息
- 在web.xml中配置全局参数
<context-param>
<param-name>globalCharset</param-name>
<param-value>GBK</param-value>
</context-param>
- 在Servlet中获取
public class MyConfigServlet extends HttpServlet {
private String globalCharset;
@Override
public void init() throws ServletException {
// 获得全局配置参数
globalCharset = this.getServletContext().getInitParameter("globalCharset");
System.out.println(globalCharset);
}
解决HTTP请求当中的乱码问题(get、post、response)
response响应乱码
- response响应乱码出现的原因?
答:由于浏览器不知道服务器响应的内容是什么编码的,浏览器默认使用GBK编码,而如果服务器响应的数据是不是GBK编码的时候,浏览器就会乱码 - 解决方法
答:在响应中告知浏览器以什么编码来解析数据
// 注意,这里是因为演示,所以硬编码了,实际编码应该从配置中读取
// 这句话就是告知浏览器接收到响应数据时以何种类型处理,以何种编码解析
resp.setContentType("text/html;charset=UTF-8");
post请求时接收参数乱码
- post请求出现乱码的原因
答:浏览器进行post请求时(表单提交),浏览器的页面编码与服务器接收处理参数的编码不一致时,就会出现乱码。 - 解决方法,设置request处理请求参数的编码
request.setCharacterEncoding("UTF-8");
get请求时,接收浏览器的url中的参数时乱码
- get请求出现乱码的原因
答:浏览器进行get请求(参数是在URI上)时,浏览器的页面编码(例如浏览器该页面是UTF-8编码)与服务器接收处理URI参数的编码(tomcat默认是IOS-8895-1)不一致时,就会出现乱码。 - 解决方案有2种
- 通过修改tomcat服务器中的conf目录中的server.xml文件,添加URIEncoding配置
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="utf-8" />- 在接受参数后,将数据转换为与页面提交的数据一致的编码
byte[] bytes = username.getBytes("ISO-8859-1");
String newUsername = new String(bytes,"UTF-8");
Servlet的注解支持(代替xml配置servlet方式)
可以在Servlet类上使用注解的方式代替在web.xml中的配置
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
@WebServlet(loadOnStartup = 0,
initParams = {@WebInitParam(name = "charset",value = "UTF-8"),@WebInitParam(name = "key2",value = "value2")},
urlPatterns = "/AnnoServlet")
public class AnnoServlet extends HttpServlet {
}
Servlet的原理
Servlet的响应原理
- 浏览器发送请求到服务器
- 服务器通过web.xml中配置的servlet-mapping找到对应的映射
- 通过匹配到映射找到对应的servlet,通过反射创建其对象
- 调用该对象中的service方法处理请求
- 处理请求后使用将响应结果传输给浏览器
Servlet的生命周期
- 类加载(Servlet加载)
- 实例化(利用反射创建对象,单例)
- init初始化
- service 处理请求
- destroy 销毁
Servlet的加载时机
- 在Servlet第一次处理请求时加载(默认)
- 在服务器启动时就进行加载,在web.xml中对应的servlet标签中添加load-on-startup标签(一般值为0-10),优先级从大到小
Servlet的API关系
- javax.servlet.Servlet接口
- javax.servlet.GenericServlet 抽象类,实现了Servlet接口
- javax.servlet.http.HttpServlet 普通类,继承了GenericServlet抽象类
所以,想要编写一个Servlet必须要实现或者间接实现Servlet接口,一般使用最多的就是继承HttpServlet - 常用方法
// Servlet接口中的方法
void service(ServletRequest var1, ServletResponse var2);//核心方法,用于处理浏览器发送的请求
ServletConfig getServletConfig(); //用于获取Servlet的配置信息
为什么继承了HTTPServlet不重写Service访问时会出现405报错?
答:因为继承了HTTPServlet方法没有重写service()方法或doGet()、doPost()方法
HTTPServlet为什么要这么处理?
答:因为本身我们开发者编写一个Servlet类就是为了处理请求,而既没有service()方法处理请求,也没有doGet()、doPost(),那这个Servlet存在的意义就缺失了。所以HttpServlet处理时就直接报错,是为了告知调用者重写service()或者doGet()、doPost()等方法
为什么service()方法是处理请求的,但是重写doGet、doPost()等方法也有效?
- HttpServlet中通过获得请求的方式(get、post)后,还是会调用doGet、doPost等方法
- 如果继承了HttpServlet的类重写了doGet方法,那么最后HttpServlet中的service方法在执行时判断了请求的方式是get请求后,会执行子类重写的doGet方法(多态)。
Servlet运行的Web项目中的4种路径问题
1、绝对路径(访问任何资源)
// 格式: 协议://IP地址:端口号/资源路径
<a href="http://127.0.0.1:8080/myProject1/path1/update.jsp">同一个项目下同一个文件夹中的文件update.jsp</a> <br/>
<a href="http://127.0.0.1:8080/myProject1/path1/subpath/delete.jsp">同一个项目下同子文件夹中的文件delete.jsp</a> <br/>
<a href="http://127.0.0.1:8080/myProject1/path2/add.jsp">同一个项目下同级文件夹中的文件add.jsp</a> <br/>
<a href="http://127.0.0.1:8080/myProject1/index.jsp">同一个项目下父级文件夹中的文件index.jsp</a> <br/>
<a href="http://127.0.0.1:8080/myProject1/servlet/DoLogin2">访问servlet的路径</a> <br/>
<a href="http://127.0.0.1:8080/myProject1/index.jsp">同一个服务器中不同项目中的文件servlet01/index.jsp</a> <br/>
<a href="http://192.168.121.33:8080/demo/a.jsp">同一个局域网中不同服务器中的项目</a> <br/>
<a href="http://www.baidu.com">外网服务器的项目</a> <br/>
2、根路径(访问当前服务器的任何资源)
提示:只能访问当前服务器的任何资源
<a href="/webdemo01/path1/update.jsp">同一个项目下同一个文件夹中的文件update.jsp</a> <br/>
<a href="/webdemo01/path1/subpath/delete.jsp">同一个项目下同子文件夹中的文件delete.jsp</a> <br/>
<a href="/webdemo01/path2/add.jsp">同一个项目下同级文件夹中的文件add.jsp</a> <br/>
<a href="/webdemo01/index.jsp">同一个项目下父级文件夹中的文件index.jsp</a> <br/>
<a href="/webdemo01/servlet/DoLogin2">访问servlet的路径</a> <br/>
<a href="/webdemo01/index.jsp">同一个服务器中不同项目中的文件servlet01/index.jsp</a> <br/>
<%--不行--%>
<a href="/demo/a.jsp">同一个局域网中不同服务器中的项目</a> <br/>
3、相对路径(访问当前服务器的资源)、不推荐
<a href="update.jsp">同一个项目下同一个文件夹中的文件update.jsp</a> <br/>
<a href="subpath/delete.jsp">同一个项目下同子文件夹中的文件delete.jsp</a> <br/>
<a href="../path2/add.jsp">同一个项目下同级文件夹中的文件add.jsp</a> <br/>
<a href="../index.jsp">同一个项目下父级文件夹中的文件index.jsp</a> <br/>
<a href="../servlet/DoLogin2">访问servlet的路径</a> <br/>
<a href="../../myProject2/index.jsp">同一个服务器中不同项目中的文件
4、相对路径(基于base标签,访问当前项目,其实实质还是绝对路径或根路径)
<head>
<%
// 这是绝对路径的base标签
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
// 根路径的base标签
String genBasePath = request.getContextPath() + "/";
%>
<base href="<%=genBasePath%>">
</head>
<body>
<%-- 注意,这里不要在写一个/了,不然就变成根路径与base标签无关了了 --%>
<a href="index.jsp">访问index.jsp</a>
</body>
使用idea配置模板
不同路径的使用场景
- 路径适用于jsp页面中的 a、form、src、link等等
- 访问其他服务器时:绝对路径
- 访问自己服务器其他项目时: 根路径
- 访问当前项目:base路径
转发与重定向的原理及区别
原理不同
转发,指的是在服务器跳转
服务器在内部接收到第一个请求时,转发到其他servlet或页面处理,然后返回数据给用户。
这个过程用户是不可见的,所以转发时,浏览器只会向服务器发出一个请求重定向,客户端跳转
服务器在运行到重定向的语句时,将重定向请求发回给浏览器,其状态码为302(并携带者需要重定向的资源路径)
浏览器在接收到这个302的重定向响应后,用其中的资源路径地址再次向服务器发送一个请求。
所以这个过程中涉及到了2个请求。所以重定向是用户可见的。
语法实现方式不同
// 转发(这里使用的是相对路径)
req.getRequestDispatcher("index.jsp").forward(req, resp);
// 重定向(这里使用的是相对路径)
resp.sendRedirect("index.jsp");
获取request作用域数据问题
转发能够获取request作用域中的数据,因为其转发的原理就是至始至终都只有一个请求,而request作用域的范围就是一个请求当中,所以能够获取
重定向不能获取request中的数据,因为重定向时,涉及到了2个请求,所以第二个请求已经不在第一个请求的作用范围内了,所以无法获取。
跳转页面后,网址的显示不同
转发跳转后,由于是服务器内部跳转,浏览器无法察觉,所以还是显示的浏览器访问的资源路径。
重定向跳转后,由于是客户端跳转,跳转对浏览器是可见的,所以显示的路径是重定向的路径。
书写代码时,使用“ / ”的意义不同
转发时候的字符串时候由于路径最前面会加上/项目名,所以写 / 代表着当前项目,这也意味着。转发只能转发到当前的项目资源。
支持书写的路径不同
- 绝对路径
- 转发:不支持
- 重定向:支持
- 相对路径(不推荐)
- 转发:支持
- 重定向:支持
- 根路径
- 转发:支持(当前项目)
- 重定向:支持(代表当前服务器,与在jsp中使用根路径是一样的)
使用表单时重复提交问题
转发:会出现表单重复提交的问题,在提交表单时使用转发,在转发后的页面,如果刷新网页,就会重复提交表单。(所以提交表单推荐使用重定向)
重定向:不会出现重复提交表单的问题
效率问题
转发:效率更高(服务器只用处理一个请求)
重定向:效率低,因为浏览器发送了2个请求,服务器响应2个请求
是否经过配置的过滤器
转发:不会,因为是在服务器内部,请求刚进来的时候就已经经过了过滤器,不会再重复执行过滤器
重定向:会,因为是2个请求。
Request请求与Response响应的更多方法
Request的更多方法
/* 获取请求头信息******/
Enumeration headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = req.getHeader(headerName);
System.out.println(headerName + "----" + headerValue);
}
System.out.println("-----------------------------------------");
/*****获得URL地址信息******/
// 获得协议:HTTP
String scheme = req.getScheme();
System.out.println(scheme);
// 获得服务器IP : 127.0.0.1
String serverName = req.getServerName();
System.out.println(serverName);
// 获得端口 : 8080
int serverPort = req.getServerPort();
System.out.println(serverPort);
// 获得项目名 /servlet01
String contextPath = req.getContextPath();
System.out.println(contextPath);
// 获得请求的服务路径,用根路径表示: /info
String servletPath = req.getServletPath();
System.out.println(servletPath);
// 请求方式:GET
String method = req.getMethod();
System.out.println(method);
// http://localhost:8080/servlet01/info
String s = req.getRequestURL().toString();
System.out.println(s);
// :/servlet01/info
String requestURI = req.getRequestURI();
System.out.println(requestURI);
/*****获取数据******/
// 获取表单提交的数据
String username = req.getParameter("username");
// 获取request作用域中的数据
Object user = req.getAttribute("user");
// 获取多个name相同的参数
String[] hobbies = req.getParameterValues("hobby");
/*****获得浏览器信息和服务器信息******/
// 获得远程访问服务器的客户端IP
String remoteAddr = req.getRemoteAddr();
System.out.println("访问者的IP为:" + remoteAddr);
// 获得远程访问服务器的主机名
String remoteHost = req.getRemoteHost();
System.out.println(remoteHost);
// 获得远程访问服务器的端口
int remotePort = req.getRemotePort();
System.out.println(remotePort);
// 以下为获取本地
String localAddr = req.getLocalAddr();
String localName = req.getLocalName();
int localPort = req.getLocalPort();
/*****获取其他对象******(重点)*/
// 获得session对象
HttpSession session = req.getSession();
// 获取请求Cookie
Cookie[] cookies = req.getCookies();
// 获得Servlet全局对象
ServletContext servletContext = req.getServletContext();
ServletContext servletContext1 = this.getServletContext();
Response的更多方法
/******response的更多方法*******/
// 设置contentType解决响应乱码
resp.setContentType("text/html;charset=utf-8");
// 设置响应编码
resp.setContentType("UTF-8");
// 设置文本长度(文件下载时用到)
resp.setContentLength(1024);
// 添加响应头信息,重复的头信息不会覆盖
resp.addHeader("head1","value1");
resp.addHeader("head1","value1");
// 设置响应头信息,name一致会覆盖,这就是与addHeader的区别
resp.setHeader("head2","hah");
resp.setHeader("head2","hah2");
// 添加Cookie
resp.addCookie(new Cookie("cookie1","value1"));
小提示,如何在项目中访问到Servlet
其实每编写一个Servlet并使用web.xml配置好、或者使用注解的方式配置了一个Servlet时。
就相当于在Web根路径下放入了这个Servlet。