很多时候我们需要处理客户端发起的网络请求,比如:用户登录。服务端需要拿到用户的账号与密码进行匹配。服务端如何接受用户的网络请求呢?
使用Servlet处理用户请求。
Servlet
Service Applet的简称。译为“小型服务程序”,用于响应客户端的请求。
使用步骤
a) 创建类继承HttpServlet,比如login类
public class login extends HttpServlet {}
b) 实现如下方法
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {}
或者实现service方法,service方法会根据请求是get或者post转发到上面的dogGet或者doPost
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {}
c) 通过@WebServlet注解设置请求路径
@WebServlet("/login")public class login extends HttpServlet {}
d) 获取请求参数get和post都是通过这种方式获取
String userName = request.getParameter("username");String password = request.getParameter("password");
e) 通过response对象给客户端响应
response.getWriter().write("test");
我们在项目里面的webapp下面创建login.html进行测试
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="/demo1/login" method="get"><div><span>用户名:</span><input type="text" name="username"></div><div><span>密码:</span><input type="password" name="password"></div><button type="submit">提交</button></form></body></html>
在form表单中,只要点击submit类型的标签就会发送网络请求,把form表单里的action里面的URL做为网络请求的路径,method的值作为请求的方式,form表单里面的标签带有name属性的值作为请求参数。
注意
form表单里面的action路径
- 以/开始
- 要从项目的路径开始。项目的路径可以查看Application context的值。本例配置的为demo1

所以上面html中的action配置的值为/demo1/login
访问http://localhost:8080/demo1/login.html后输入用户名、密码后,发现报如下界面
而我们的代码实现是这样的
@WebServlet("/login") //请求时使用http(s)://ip:port/项目部署的application context/loginpublic class login extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {super.doGet(request, response);System.out.println("do get");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}}
排查了好久发现把super.doGet(request, response);去掉就好了。
如果发现乱码问题,需要设置编码方式
客户端的请求数据乱码
request.setCharacterEncoding("UTF-8");
服务器返回到客户端乱码
response.setContentType("text/plain;charset=UTF-8");
text/plain为MIMEType类型,根据情况而定。详情查看Tomact目录下的conf/web.xml
如何返回网页到客户端
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {response.setContentType("text/html;charset=UTF-8");PrintWriter printWriter = response.getWriter();printWriter.write("<h1>这是标题</h1>");printWriter.write("<ul>");printWriter.write("<li>读书</li>");printWriter.write("<li>电影</li>");printWriter.write("<li>喜剧</li>");printWriter.write("<li>音乐</li>");printWriter.write("</ul>");}
直接使用response.getWriter().write()一个一个写。这种方式很麻烦,做很多字符串拼接工作而且修改html内容比较麻烦。
默认情况下,当第一次请求到相应的Servlet的时候才会创建Servlet,并且只会创建一次。JSP
由于上面的方式比较麻烦,慢慢的就产生了JSP技术。
这里的菜鸟教程可以大致了解一下。
在项目里面的webapp下创建一个jsp文件,名称叫test.jsp ```cpp <%@ page contentType=”text/html;charset=UTF-8” language=”java” %>这是一个JSP文件
可以在浏览器直接访问[http://localhost:8080/demo1/test.jsp](http://localhost:8080/demo1/test.jsp)<br />访问的时候test.jsp里面的jsp不能省略。<a name="Aikko"></a>#### 最重要的特点就是JSP里面可以写Java代码使用<% %>把Java代码嵌套里面即可,注释使用<%-- --%><br />示例感受一下```cpp<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>Title</title></head><body><%-- 这是Java代码 --%><%System.out.println(123);System.out.println(456);%><%-- 这是Java代码 --%><%for (int j = 0; j < 10; j++) {%><h3>我是标题<h3><%-- 这是Java代码 --%><%}%></body></html>
java代码里面还可以嵌套HTML代码,HTML里面嵌套Java代码,比较麻烦,一点都不友好。
JSP的本质是Servlet
当我们新建项目时,默认会创建一个index.jsp文件,当进行部署编译后会生成一个index_jsp.java文件,可以通过部署生成的Tomcat的log日志,查看位置
CATALINA_BASE: /Users/didi/Library/Caches/JetBrains/IntelliJIdea2021.2/tomcat/9ee4a72e-7f92-4d47-8ed2-bd032564e996
一路往下找
~/Library/Caches/JetBrains/IntelliJIdea2021.2/tomcat/9ee4a72e-7f92-4d47-8ed2-bd032564e996/work/Catalina/localhost/demo1/org/apache/jsp/index_jsp.java ~
就能看到了。打开该文件
package org.apache.jsp;import javax.servlet.*;import javax.servlet.http.*;import javax.servlet.jsp.*;public final class index_jsp extends org.apache.jasper.runtime.HttpJspBaseimplements org.apache.jasper.runtime.JspSourceDependent,org.apache.jasper.runtime.JspSourceImports {private static final javax.servlet.jsp.JspFactory _jspxFactory =javax.servlet.jsp.JspFactory.getDefaultFactory();private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;private static final java.util.Set<java.lang.String> _jspx_imports_packages;private static final java.util.Set<java.lang.String> _jspx_imports_classes;static {_jspx_imports_packages = new java.util.HashSet<>();_jspx_imports_packages.add("javax.servlet");_jspx_imports_packages.add("javax.servlet.http");_jspx_imports_packages.add("javax.servlet.jsp");_jspx_imports_classes = null;}private volatile javax.el.ExpressionFactory _el_expressionfactory;private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;public java.util.Map<java.lang.String,java.lang.Long> getDependants() {return _jspx_dependants;}public java.util.Set<java.lang.String> getPackageImports() {return _jspx_imports_packages;}public java.util.Set<java.lang.String> getClassImports() {return _jspx_imports_classes;}public javax.el.ExpressionFactory _jsp_getExpressionFactory() {if (_el_expressionfactory == null) {synchronized (this) {if (_el_expressionfactory == null) {_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();}}}return _el_expressionfactory;}public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {if (_jsp_instancemanager == null) {synchronized (this) {if (_jsp_instancemanager == null) {_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());}}}return _jsp_instancemanager;}public void _jspInit() {}public void _jspDestroy() {}public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)throws java.io.IOException, javax.servlet.ServletException {if (!javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {final java.lang.String _jspx_method = request.getMethod();if ("OPTIONS".equals(_jspx_method)) {response.setHeader("Allow","GET, HEAD, POST, OPTIONS");return;}if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) {response.setHeader("Allow","GET, HEAD, POST, OPTIONS");response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS");return;}}final javax.servlet.jsp.PageContext pageContext;javax.servlet.http.HttpSession session = null;final javax.servlet.ServletContext application;final javax.servlet.ServletConfig config;javax.servlet.jsp.JspWriter out = null;final java.lang.Object page = this;javax.servlet.jsp.JspWriter _jspx_out = null;javax.servlet.jsp.PageContext _jspx_page_context = null;try {response.setContentType("text/html; charset=UTF-8");pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);_jspx_page_context = pageContext;application = pageContext.getServletContext();config = pageContext.getServletConfig();session = pageContext.getSession();out = pageContext.getOut();_jspx_out = out;out.write("\n");out.write("<!DOCTYPE html>\n");out.write("<html>\n");out.write("<head>\n");out.write(" <title>JSP - Hello World</title>\n");out.write("</head>\n");out.write("<body>\n");out.write("<h1>");out.print( "Hello World!" );out.write("</h1>\n");out.write("<br/>\n");out.write("<a href=\"hello-servlet\">Hello Servlet</a>\n");out.write("</body>\n");out.write("</html>");} catch (java.lang.Throwable t) {if (!(t instanceof javax.servlet.jsp.SkipPageException)){out = _jspx_out;if (out != null && out.getBufferSize() != 0)try {if (response.isCommitted()) {out.flush();} else {out.clearBuffer();}} catch (java.io.IOException e) {}if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);else throw new ServletException(t);}} finally {_jspxFactory.releasePageContext(_jspx_page_context);}}}
index_jsp类继承org.apache.jasper.runtime.HttpJspBase而该类继承HttpServlet
�所以JSP的本质是Servlet.可以看到先调用_jspService方法该方法里面进行HTML的拼接
out.write("\n");out.write("<!DOCTYPE html>\n");out.write("<html>\n");out.write("<head>\n");out.write(" <title>JSP - Hello World</title>\n");out.write("</head>\n");out.write("<body>\n");out.write("<h1>");out.print( "Hello World!" );out.write("</h1>\n");out.write("<br/>\n");out.write("<a href=\"hello-servlet\">Hello Servlet</a>\n");out.write("</body>\n");out.write("</html>");
JSP里面遇到Java代码原生不动的复制过去,如果是HTML代码就直接write进行写。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
上面的编码会转化成
response.setContentType("text/html; charset=UTF-8");
所以JSP帮助我们进行HTML的拼接处理工作。既然JSP的本质是Servlet那么在JSP页面里面应该能拿到request、response对象
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %><!DOCTYPE html><html><head><title>JSP - Hello World</title></head><body><h1><%= "Hello World!" %></h1><br/><a href="hello-servlet">Hello Servlet</a><!-- 拿到request对象 --><% request.getMethod() %><!-- 拿到response对象 --><% response.setContentType("text/html; charset=UTF-8"); %></body></html>
JSP之所以称为动态网页,是因为里面可以写Java代码动态的生成一个网页。
这种方式缺点也很明显
- 可读性很差,Java代码和HTML混合在一起
- 由于前端人员基本上不会Java代码,所以JSP只能有后端人员来写。
- 现在基本流行前后端分离,前端写前端,后端写后端。
Servlet和JSP搭配使用
由于上面的种种疼点,我们又演化了一种稍微简单的使用方式,这种方式能够让JSP不需要编写Java代码,Servlet不需要拼接HTML代码。
整体流程
客户端发送请求到Servlet,Servlet从数据库中获取或插入数据,然后将所需数据转发给JSP,在JSP里面接受获取Servlet传过来的数据。此时JSP使用标签库(类似HTML标签的写法)来写业务逻辑。
这种模式也称为MVC模式
M:model数据
V:view 视图(这里指的是JSP)
C:controller 控制器(这里指Servlet)
我们新建一个项目
新建一个ListServlet类处理用户请求、一个User类做模型数据。
User类很简单做假数据的
public class User {private String name;private Integer age;private String telephone;User(String name,Integer age,String telephone){this.name = name;this.age = age;this.telephone = telephone;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getTelephone() {return telephone;}public void setTelephone(String telephone) {this.telephone = telephone;}}
ListServlet类作为用户请求的路口
@WebServlet("/list")public class ListServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {ArrayList<User> listUser = getUser();request.setAttribute("users",listUser);//转发路径是相对于webapp的路径request.getRequestDispatcher("/userList.jsp").forward(request,response);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doGet(request,response);}//扮演数据库的角色private ArrayList<User> getUser(){ArrayList<User> list = new ArrayList<User>();list.add(new User("张三",30,"17610159306"));list.add(new User("李四",18,"17610159346"));list.add(new User("王二",35,"17610159396"));return list;}}
�关键点
需要使用下面的方式存储数据,request有个map类型attributes属性
request.setAttribute("users",listUser);
JSP中获取存储的数据
怎么转发
//转发路径是相对于webapp的路径request.getRequestDispatcher("/userList.jsp").forward(request,response);

新建userList.jsp文件,内容如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>Title</title></head><body><ul><li>姓名:张三</li><li>年龄:30</li><li>电话:17610159306</li></ul><br><br></body></html>
应该在上面文件里面获取Servlet保存的数据,并做页面处理,因为不使用Java处理业务,而是使用JST标签库处理。
JSTL: JSP Standard Tag Library简称,JST标准标签库的简。
下载地址http://tomcat.apache.org/download-taglibs.cgi
在WEB-INF目录下面创建lib目录,注意名称不要乱起,就是lib 把上面下载的jar包copy到该目录下,并右击选择Add as Library。
在JSP页面里面导入JSTL核心标签库
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
prefix:为前缀,后续使用标签里面的任何东西都要加此前缀,可以随便写,一般写c即可因为使用的是core库。
uri: 标签库的地址
示例代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><html><head><title>Title</title></head><body><c:forEach items="${users}" begin="0" end="2" var="user"><ul><li>姓名:${user.name}</li><li>年龄:${user.age}</li><li>电话:${user.telephone}</li></ul><br></c:forEach><br></body></html>
items=”${users}”为取出在Servlet保存的数据users要与保存的相同。
var: 为每取出一条数据的内容
${user.name}为调用user的getName方法。${}为Expression Language表达式简称EL。
常见的取值方式为有:
${obj.property}${obj["property"]}${obj[property]}
一个Servlet如何处理多个请求路径
痛点:
在项目开发中,如果一个请求对应一个Servlet会发生什么?
比如在进行登录、注册时,注册请求对应一个Servlet、登录请求对应一个Servlet,用户登录成功后还可能会编辑用户信息、删除、添加等,这样编辑、删除、添加还需要对应不同的Servlet,造成一个项目里面可能非常多的Servlet。有没有什么好的方式,把多个请求收归到一个Servlet里面?
处理办法挺多的。
首先需要提前规定好用户怎么发送请求。如下格式
http://ip:port/项目/servlet?method="xxx"
可以参考如下链接
一个Servlet处理多个请求
this.getClass().getMethods();//获取当前类以及父类中所有的public方法this.getClass().getDeclaredMethods();//获取当前类中所有的方法包括private修饰的
关于转发
转发只能在同一个项目(context)里面进行转发,不能转发到其它context的Servlet里面。
比如服务器里面部署了两个项目,分别为Context1、Context2

请求如果到达Context1里面,就不能转发到Context2里面,同样当请求到达Context2里面就不能被转发到Context1里面。
因为转发请求不能转发到其它Context里面,所以下面的/userList.jsp路径前面不需要写Context名称
//转发路径是相对于webapp的路径request.getRequestDispatcher("/userList.jsp").forward(request,response);
以后如果Servlet转发到其他Servlet或JSP都不需要加Context Path
转发链条
在同一个请求中,可以转发多次,形成一个转发链条。

- 每次转发都会创建一个新的request对象,用成员变量指向前一个request对象。
可以使用下面的方式来共享数据
request.setAttribute("users",listUser);request.getAttribute("users")
共享的数据都保存在头结点Request中
重定向
重定向:服务器通知客户端重新发送请求到新的任意的URL地址。

重定向服务器会返回客户端一个响应,响应码为302,响应的URL包含在响应头的Location字段中,客户端收到响应后根据响应码和Location字段重新发送请求,可以重定向到其他服务器下也可以重定向到同一个服务器的其它项目中。
服务器端的代码
response.sendRedirect("/demo1/other");
重定向到同一台服务器的项目中的其它页面,demo1为项目的Context必须写
response.sendRedirect("http://www.baidu.com");
可以定位到其他服务器中,如百度。
重定向和转发的区别
| 代码写法 | 请求次数 | 路径写法 | 地址栏变化 | |
|---|---|---|---|---|
| 重定向 | response.sendRedirect(“路径”); | 客户端发送两次请求 | 可以重定向到任意位置,如果重定向到同一个Context下,路径中需要包含Context Path | 浏览器的地址栏URL会发生变化 |
| 转发 | request.getRequestDispatcher(“/路径”).forward(request,response); � |
客户端只发送了一次请求 | 只能转发到同一个Context下,路径中不用包含Context Path | 浏览器的地址栏URL不会发生变化 |
小知识
- 很多JSP页面我们是不希望用户直接访问的,有时我们希望用户先访问我们的servlet,在servlet做完业务逻辑后,通过request.setAttribute(“users”,listUser);保存相关的数据后,再重定向到JSP页面。怎么办到用户不能直接访问JSP呢?很简单,我们只需把JSP的位置放到 webapp/WEB-INF文件夹下面就可以了。一般资源图片、CSS、JS这些东西还是需要直接访问的,不要放在webapp/WEB-INF下面
