什么是Maven:
Maven是一个构建工具,帮助程序猿来构建,测试,打包一个项目;
在工作中,因为某个项目比较庞大,并不是在IDE中点一下运行就直接运行的,一些项目对哪些文件先编译后编译有特殊的要求等等,而Maven就是帮助我们组织整个庞大项目的,与此类似的还有Ant(一个老牌构建工具),Gradle(主要用于安卓生态)
引入Servlet依赖:
Servlet是tomcat的一组api,在开发阶段并不在标准库内,需要引入依赖,可以从Maven中央仓库下载,注意依赖库的版本要对应tomcat的版本,如tomcat 8.5版本对应Servlet3.1.0的版本
创建目录:
这里的目录创建一定要准确,和下图一模一样(当然你也可以自定义配置)
web.xml模板:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
开始写代码:
以hello world为例:
要创建一个继承了HttpServlet的类,然后重写doGet方法,如果HttpServlet没有代码提示,可以刷新一下,或者尝试清除缓存(File-Invalidate caches),如果仍然不行,说明可能依赖库没有设置好
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这是调用父类的doGet,其会触发一个发送405错误
//super.doGet(req, resp);
//这个是打印在服务器的控制台中,客户端是看不到的
System.out.println("hello,world");
//这个是在resp的body中写入hello,world,然后这个响应返回给浏览器后,会显示在浏览器的页面上
//resp.getWriter()是获取resp内部的Writer对象
resp.getWriter().write("hello,world");
}
}
如何运行
打包与部署:
当前写的程序不能直接独立运行,必须部署到tomcat上才能运行
部署的前提就是打包:
在Java中使用的压缩包是.jar和.war.其中jar是普通Java程序打成的jar,而war是部署给tomcat打成的war,是tomcat专属的格式,其内部有一些特定的目录结构和文件,比如web.xml等,后续tomcat就要识别这些内容,来加载webapp
打包的过程:
1.检查代码中是否存在依赖,检查依赖是否下载好
2.将代码进行编译,生成一对.class文件
3.把这些.class文件以及web.xml按照一定的格式进行打包
如果打.war包失败,可能是BOM导致的:java: 非法字符: ‘\ufeff‘解决方法_非法字符: '\ufeff_云边的快乐猫的博客-CSDN博客
检验程序:
所谓doGet,就是在触发一个get请求后触发的方法
启动tomcat后,在同一个电脑打开浏览器输入127.0.0.1:8080/hello_servlet/hello就可以看到效果(输入URL就会触发GET方法)
其中hello_servlet这被称作Context Path/Application Path,标志了这个webapp(也就是这个webapp的目录名/war包名)
hello称作Servlet Path,标识了当前请求要调用哪个Servlet类的doGet
抓个包看看:
将打包与部署简化为一键式完成:
每次修改代码都要重新打包,重新部署,比较麻烦,我们可以一个叫smart tomcat的插件实现简化,如果你使用的是专业版的IDEA,其内置了Tomcat Server,可以代替这个插件的功能,但是tomcat Server使用麻烦,还是建议使用smart tomcat
使用smart tomcat和普通的将war包拷贝的webapps中这两种运行方式存在本质区别,smart tomcat其实是tomcat的另一种运行方式,其通过特定的参数来指定tomcat加载某个特定目录中的webapp,因此使用smart tomcat的时候不会存在打包也不会存在拷贝,所以打开webapps会发现没有什么变化
使用smart tomcat一般是在开发和调试阶段,如果是部署到生产环境,还得是需要打包拷贝
如何使用 smart tomcat:
使用tomcat时常出现的一些问题:
404:
1.请求路径写的不对,Context Path 或者 Servlet Path写的不对
2.war包没有被正确加载,比如web.xml写的不对,或者两个Servlet中的Servlet Path相同,也会导致不能正确加载,如果未能加载成功,日志会有提示
405:
1.发送请求的方法和代码不匹配,比如代码写的是doPost,但发送的请求是GET
2.没有注释掉或者删掉 super.XXX 比如super.doGet
500:
服务器代码抛出异常比如:
空白页面:
服务器没有返回任何数据
无法访问此网站:
没有启动tomcat
让浏览器识别编码格式:
如果不设置编码格式,如果在body中写一个中文,可能在浏览器中是乱码,在Java中String类型是utf8编码的,而浏览器的编码格式是默认和操作系统一样的,如果是win10简体中文版,其默认的编码是gbk,编码格式对不上就会出现乱码
需要注意的是在同一个响应中,不要同时使用两种输出流,会出现一些不可预料的错误
三组重要API:
HttpServlet:
方法名称 | 调用时机 |
init | 在HttpServlet实例化后被调用一次 |
destroy | 在 HttpServlet 实例不再使⽤的时候调⽤⼀次 |
service | 收到 HTTP 请求的时候调⽤ |
doGet | 收到 GET 请求的时候调⽤(由 service ⽅法调⽤) |
doPost | 收到 POST 请求的时候调⽤(由 service ⽅法调⽤) |
doPut/doDelete/doOptions/.... | 收到其他请求的时候调⽤(由 service ⽅法调⽤) |
init:
HttpServlet被实例化之后,会调用一次,使用这个方法是要做一些初始化相关的工作。在首次收到请求的时候,HttpServlet就会被实例化(即使是405也会触发init),比如输入URL后,就会触发HelloServlet的doGet,而在调用doGet前会先调用init
注意,init只执行一次,后续则不再执行:
destroy:
destroy是在webapp被卸载(销毁)之前执行一次,用于做一些结束之前的工作
关闭服务器有两种方式:
1.直接杀死进程(不能触发destroy)
2.使用8005管理端口停止服务器,此时destroy能执行
但是在日常工作中,大多是都是使用高效率的直接杀死进程的方式停止服务器,因此不建议使用destroy
service:
每次收到路径匹配的请求,都会执行,doGet/doPost....等方法都是由service调用的
HttpServletRequest:
tomcat解析一个http请求后经过解析,就换产出一个这个类的对象,其中包含了诸多http请求的属性
方法 | 描述 |
String getProtocol() | 返回请求协议的名称和版本。 |
String getMethod() | 返回请求的 HTTP ⽅法的名称,例如,GET、POST 或 PUT。 |
String getRequestURI() | 从协议名称直到 HTTP 请求的第⼀⾏的查询字符串中,返回该请求的 URL 的⼀部分。 URI是唯一资源标识符;URL是唯一资源定位符,两者概念非常接近,以至于有时候混着用 |
String getContextPath() | 返回指示请求上下⽂的请求 URI 部分。 |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串。 |
Enumeration getParameterNames() | 返回⼀个 String 对象的枚举,包含在该请求中包含的参数的名称 |
String getParameter(String name) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。 |
String[] getParameterValues(String name) | 返回⼀个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null。 |
Enumeration getHeaderNames() | 返回⼀个枚举,包含在该请求中包含的所有的头名。 |
String getHeader(String name) | 以字符串形式返回指定的请求头的值 |
String getCharacterEncoding() | 返回请求主体中使⽤的字符编码的名称 |
String getContentType() | 返回请求主体的 MIME 类型,如果不知道类型则返回 null。 |
int getContentLength() | 以字节为单位返回请求主体的⻓度,并提供输⼊流,或者如果⻓度未知则返回 -1。 |
InputStream getInputStream() | ⽤于读取请求的 body 内容. 返回⼀个InputStream 对象 |
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
@WebServlet("/show")
public class showRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf8");
StringBuilder builder = new StringBuilder();
builder.append(req.getProtocol());//获取请求协议的名称与版本
builder.append("<br>");
builder.append(req.getMethod());//获取请求方法
builder.append("<br>");
builder.append(req.getRequestURI());//获取URI
builder.append("<br>");
builder.append(req.getQueryString());//获取query String
builder.append("<br>");
builder.append(req.getContextPath());//获取Context Path
builder.append("<br><hr>");
Enumeration<String> names = req.getHeaderNames();//获取header所有键
while (names.hasMoreElements()){
String name = names.nextElement();
String value = req.getHeader(name);//根据键获取header值
builder.append(name + " : " + value + "<br>");
}
resp.getWriter().write(builder.toString());
}
}
getParameter(最常用的API之一):
getParameter常用于前后端数据传递的桥梁,后端常用三种方式进行数据获取:
1.通过 query String:
query string中的键值对都是程序猿自定义的,比如前后端约定通过query string传递 userName和passWord:
等tomcat收到一个请求后,其会将query string中的键值对存放在请求中
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
if (userName == null){
System.out.println("query string中没有userName");
}
System.out.println(userName);
String passWord = req.getParameter("passWord");
if (passWord == null){
System.out.println("query string中没有password");
}
System.out.println(passWord);
resp.getWriter().write("finally");
}
}
因为有的浏览器或者http服务器对中文或者特殊字符的支持性不好,需要注意的是如果在URL中存在中文或者其他特殊字符,需要进行Urlencode编码,将编好的码替换中文或者特殊字符,如"大"的编码就是%E5%A4%A7
一般前端负责编码,后端负责解码
注意这种机制只针对URL/URI
UrlEncode编码和UrlDecode解码-在线URL编码解码工具
2.通过 body(from表单):
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
if (userName == null){
System.out.println("query string中没有userName");
}
System.out.println(userName);
String passWord = req.getParameter("passWord");
if (passWord == null){
System.out.println("query string中没有password");
}
System.out.println(passWord);
resp.getWriter().write("finally");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
if (userName == null){
System.out.println("body中没有userName");
}
System.out.println(userName);
String passWord = req.getParameter("passWord");
if (passWord == null){
System.out.println("body中没有password");
}
System.out.println(passWord);
resp.getWriter().write("finally");
}
}
form不好构造,这里用postman直接构造请求:
如果在请求body中存在中文或者特殊字符,需要在相应的doXXX方法内设置认为请求的编码格式,不然可能因为中文或者特殊字符出现乱码:
req.setCharacterEncoding("utf8");
注意这里的设置认为请求的编码格式只针对请求体,也就是body,这和URL的编码节码并不冲突
3.通过body(json):
这是日常开发中最常用的方式
Servlet内部没有json的解析功能,所以这里需要引入第三方库,这里使用Jackson,因为jackson是spring官方指定的产品
Maven Repository: com.fasterxml.jackson.core » jackson-databind » 2.15.0 (mvnrepository.com)
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
class User{
//这里的属性名字要和json中键值对的键一样
public String userName;
public String passWord;
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
private ObjectMapper mapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这里是通过获取请求的输入流和类对象,通过反射创建一个User对象,将json中的value写到该对象的属性中
User user = mapper.readValue(req.getInputStream(),User.class);
System.out.println(user.userName);
System.out.println(user.passWord);
resp.getWriter().write("finally");
}
}
json数据格式整体采用的是utf8和Unicode编码格式,utf8包含了Unicode能标识的所有数据,所以说这里不会存在前后端数据交互时产生的乱码问题,这也是json是最好的跨语言跨平台的数据格式之一的原因
需要注意的一点是,如果后端设置了application/json;charset=utf8的格式,但没有在resp中传递任何数据,前端的ajax中的success方法可能难以执行
HttpServletRespone:
方法 | 描述 |
void setStatus(int sc) | 为该响应设置状态码。 |
void setHeader(String name, Stringvalue) | 设置⼀个带有给定的名称和值的 header. 如果name 已经存在, 则覆盖旧的值 |
void addHeader(String name, Stringvalue) | 添加⼀个带有给定的名称和值的 header. 如果name 已经存在, 不覆盖旧的值, 并列添加新的键值对 |
void setContentType(String type) | 设置被发送到客户端的响应的内容类型 |
void setCharacterEncoding(Stringcharset) | 设置被发送到客户端的响应的字符编码(MIME字符集)例如,UTF-8 |
void sendRedirect(String location) | 使⽤指定的重定向位置 URL 发送临时重定向响应到客户端。 |
PrintWriter getWriter() | ⽤于往 body 中写⼊⽂本格式数据. |
OutputStream getOutputStream() | ⽤于往 body 中写⼊⼆进制格式数据 |
这是对setStatus的应用
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/status")
public class StatusServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(404);
resp.setContentType("text/html;charset = utf8");
resp.getWriter().write("这是一个404相应");
}
}
利用setHeader对refresh进行设置,让其每过一秒就刷新一下界面
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("refresh","1");
resp.setContentType("text/html;charset = utf8");
resp.getWriter().write("<h1> time = " + System.currentTimeMillis() + "</h1>");
}
}
对重定向的应用:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("location","https://www.sogou.com");
resp.setStatus(302);
}
}
目录