前言 本篇主要记录Java后端开发学习过程中关于Servlet相关技术路线的梳理和总结
按技术迭代顺序依次从下述几个Web技术开展梳理:
ServerSocket编程
Web服务器
Servlet规范
SpringMCV框架
ServerSocket编程 本部分通过手撸一个基于Java的简易HTTP服务器来体会最原初的JavaWeb后端开发过程
客户端 这里客户端就不使用Java代码编写了,可以考虑使用最基本的浏览器或者后端测试工具如PostMan。
服务端
第一步我们先创建ServerSocket,监听8080端口
第二步接收到请求后把流在控制台进行输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class HttpServer { public static void main (String[] args) throws IOException { ServerSocket serverSocket = null ; Socket socket = null ; InputStream is = null ; InputStreamReader isr = null ; BufferedReader br = null ; OutputStream os = null ; PrintWriter pw = null ; try { serverSocket = new ServerSocket(8080 ); while ((socket = serverSocket.accept()) != null ){ List<String> lines = IOUtils.readLines(socket.getInputStream(), "utf-8" ); for (String line : lines) { System.out.println(line); } } } catch (Exception e) { e.printStackTrace(); } finally { pw.close(); os.close(); br.close(); isr.close(); is.close(); socket.close(); serverSocket.close(); } } }
打印到控制台是以下这段文本:
解释一下: 上述代码监听了8080端口并将客户端的访问请求打印为字符串,即控制台中的文本信息,只要我们的服务器解析这段字符串然后拼接成request,就能封装HTTP请求。但是这时观察浏览器界面发现没有任何响应。因为我们编写的HTTP服务器什么都没返回就关闭了连接。如果要浏览器正确的显示我们想要看到的helloword。同样也要拼接成浏览器能理解的http协议响应格式才能正确被解析。
下面继续改动代码,让http返回200成功,并返回helloword:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class HttpServer { public static void main (String[] args) throws IOException { ServerSocket serverSocket = null ; Socket socket = null ; InputStream is = null ; InputStreamReader isr = null ; BufferedReader br = null ; OutputStream os = null ; PrintWriter pw = null ; try { serverSocket = new ServerSocket(8080 ); while ((socket = serverSocket.accept()) != null ){ os = socket.getOutputStream(); pw = new PrintWriter(os); pw.write("HTTP/1.1 200 OK\n" + "Date: Fri, 7 Sept 2022 06:07:21 GMT\n" + "Content-Type: text/html; charset=UTF-8\n" + "\n" + "<html>\n" + " <head></head>\n" + " <body>\n" + " helloWord!\n" + " </body>\n" + "</html>" ); pw.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { pw.close(); os.close(); br.close(); isr.close(); is.close(); socket.close(); serverSocket.close(); } } }
浏览器访问:localhost:8080 可见:
总结 上述示例实现了一个最简单的基于HTTP协议的Java服务器后端,里面并不包含对request请求字符串的处理,仅仅是打印了它,response也仅仅是按HTTP协议的规定封装了一句“helloworld!”。如此简易原始的HTTP服务器的整个访问和回复过程是如此繁琐,可见新技术出现的必要性。
Web服务器 Web服务器是一个应该程序(软件),对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是”提供网上信息浏览服务”
下面以Apache Tomcat服务器为例展示一个最简单的HTTP Web服务。
创建展示页面 在Tomcat根目录的Webapps 下创建一个test文件夹,内部创建hello.html文件用于返回请求页面👇👇
启动服务器并请求资源
启动Tomcat根目录下的bin/startup.bat
使用Post访问localhost:8080/test/hello.html
总结
Web服务器作用? ➢封装HTTP协议操作,简化开发 ➢可以将Web项目部署到服务器中,对外提供网上浏览服务
什么是Tomcat? Tomcat是一个轻量级的Web服务器, 支持Servlet/JSP少量JavaEE规范,也称为Web容器,Servlet容器,Servlet依赖于它或其他Web服务器才能正常运行。
Servlet规范 Servlet规范简介 Servlet(Server Applet,服务端小程序,是服务端的一小部分),全称Java Servlet,未有中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。
补充:Servlet和Web服务器的关系 首先要明白我们从来不会在Servlet中写什么监听8080端口的代码,Servlet不会直接和客户端打交道!这些全被Web服务器封装了。
那请求是怎么来到Servlet的呢?答案是Servlet容器,比如我们最常用的Tomcat。Servlet都是部署在一个容器中的,不然你的Servlet根本不起作用。
Tomcat才是与客户端直接打交道的家伙,它监听了端口,请求过来后,根据URL等信息,确定要将请求交给哪个Servlet去处理,然后调用那个Servlet的service方法,service方法返回一个response对象,Tomcat再把这个respond返回给客户端。
接口代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package Javax.Servlet;import Java.io.IOException;public interface Servlet { void init (ServletConfig var1) throws ServletException ; ServletConfig getServletConfig () ; void service (ServletRequest var1, ServletResponse var2) throws ServletException, IOException ; String getServletInfo () ; void destroy () ; }
Servlet接口定义的是一套处理网络请求的规范,所有实现Servlet的类,都需要实现它的那五个方法,其中最主要的是两个声明周期方法init()和destory(),还有一个处理请求的service()。也就是说,所有实现Servlet接口的类,或者说,所有想要处理网络请求的类,都需要回答这三个问题:
你初始化时要做什么?
你销毁时要做什么?
你接收到请求时要做什么?
生命周期 1、客户端请求该 Servlet;
2、Tomcat加载 Servlet 类到内存;
3、Tomcat实例化并调用init()方法初始化该 Servlet;
4、Tomcat调用service()(根据请求方法不同调用doGet() 或者 doPost(),此外还有doHead()、doPut()、doTrace()、doDelete()、doOptions());
5、destroy();
6、加载和实例化 Servlet。这项操作一般是动态执行的。然而,Server 通常会提供一个管理的选项,用于在 Server 启动时强制装载和初始化特定的 Servlet;
7、Server 创建一个 Servlet的实例;
8、第一个客户端的请求到达 Server;
9、Server 调用 Servlet 的 init() 方法(可配置为 Server 创建 Servlet 实例时调用);
10、一个客户端的请求到达 Server;
11、Server 创建一个请求对象,处理客户端请求;
12、Server 创建一个响应对象,响应客户端请求;
13、Server 激活 Servlet 的 service() 方法,传递请求和响应对象作为参数;
14、service() 方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息;
15、service() 方法使用响应对象的方法,将响应传回Server,最终到达客户端。service()方法可能激活其它方法以处理请求,如 doGet() 或 doPost() 或程序员自己开发的新的方法;
继承体系
HttpServlet源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 package javax.servlet.http;import java.io.IOException;import java.io.Serializable;import java.lang.reflect.Method;import java.text.MessageFormat;import java.util.Enumeration;import java.util.ResourceBundle;import javax.servlet.GenericServlet;import javax.servlet.ServletException;import javax.servlet.ServletOutputStream;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;public abstract class HttpServlet extends GenericServlet implements Serializable { private static final String METHOD_DELETE = "DELETE" ; private static final String METHOD_HEAD = "HEAD" ; private static final String METHOD_GET = "GET" ; private static final String METHOD_OPTIONS = "OPTIONS" ; private static final String METHOD_POST = "POST" ; private static final String METHOD_PUT = "PUT" ; private static final String METHOD_TRACE = "TRACE" ; private static final String HEADER_IFMODSINCE = "If-Modified-Since" ; private static final String HEADER_LASTMOD = "Last-Modified" ; private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings" ; private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings" ); public HttpServlet () { } protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported" ); if (protocol.endsWith("1.1" )) { resp.sendError(405 , msg); } else { resp.sendError(400 , msg); } } protected long getLastModified (HttpServletRequest req) { return -1L ; } protected void doHead (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { NoBodyResponse response = new NoBodyResponse(resp); this .doGet(req, response); response.setContentLength(); } protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported" ); if (protocol.endsWith("1.1" )) { resp.sendError(405 , msg); } else { resp.sendError(400 , msg); } } protected void doPut (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_put_not_supported" ); if (protocol.endsWith("1.1" )) { resp.sendError(405 , msg); } else { resp.sendError(400 , msg); } } protected void doDelete (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_delete_not_supported" ); if (protocol.endsWith("1.1" )) { resp.sendError(405 , msg); } else { resp.sendError(400 , msg); } } private Method[] getAllDeclaredMethods(Class c) { if (c.equals(HttpServlet.class)) { return null ; } else { Method[] parentMethods = this .getAllDeclaredMethods(c.getSuperclass()); Method[] thisMethods = c.getDeclaredMethods(); if (parentMethods != null && parentMethods.length > 0 ) { Method[] allMethods = new Method[parentMethods.length + thisMethods.length]; System.arraycopy(parentMethods, 0 , allMethods, 0 , parentMethods.length); System.arraycopy(thisMethods, 0 , allMethods, parentMethods.length, thisMethods.length); thisMethods = allMethods; } return thisMethods; } } protected void doOptions (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method[] methods = this .getAllDeclaredMethods(this .getClass()); boolean ALLOW_GET = false ; boolean ALLOW_HEAD = false ; boolean ALLOW_POST = false ; boolean ALLOW_PUT = false ; boolean ALLOW_DELETE = false ; boolean ALLOW_TRACE = true ; boolean ALLOW_OPTIONS = true ; for (int i = 0 ; i < methods.length; ++i) { Method m = methods[i]; if (m.getName().equals("doGet" )) { ALLOW_GET = true ; ALLOW_HEAD = true ; } if (m.getName().equals("doPost" )) { ALLOW_POST = true ; } if (m.getName().equals("doPut" )) { ALLOW_PUT = true ; } if (m.getName().equals("doDelete" )) { ALLOW_DELETE = true ; } } String allow = null ; if (ALLOW_GET && allow == null ) { allow = "GET" ; } if (ALLOW_HEAD) { if (allow == null ) { allow = "HEAD" ; } else { allow = allow + ", HEAD" ; } } if (ALLOW_POST) { if (allow == null ) { allow = "POST" ; } else { allow = allow + ", POST" ; } } if (ALLOW_PUT) { if (allow == null ) { allow = "PUT" ; } else { allow = allow + ", PUT" ; } } if (ALLOW_DELETE) { if (allow == null ) { allow = "DELETE" ; } else { allow = allow + ", DELETE" ; } } if (ALLOW_TRACE) { if (allow == null ) { allow = "TRACE" ; } else { allow = allow + ", TRACE" ; } } if (ALLOW_OPTIONS) { if (allow == null ) { allow = "OPTIONS" ; } else { allow = allow + ", OPTIONS" ; } } resp.setHeader("Allow" , allow); } protected void doTrace (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String CRLF = "\r\n" ; String responseString = "TRACE " + req.getRequestURI() + " " + req.getProtocol(); String headerName; for (Enumeration reqHeaderEnum = req.getHeaderNames(); reqHeaderEnum.hasMoreElements(); responseString = responseString + CRLF + headerName + ": " + req.getHeader(headerName)) { headerName = (String)reqHeaderEnum.nextElement(); } responseString = responseString + CRLF; int responseLength = responseString.length(); resp.setContentType("message/http" ); resp.setContentLength(responseLength); ServletOutputStream out = resp.getOutputStream(); out.print(responseString); out.close(); } protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET" )) { lastModified = this .getLastModified(req); if (lastModified == -1L ) { this .doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader("If-Modified-Since" ); if (ifModifiedSince < lastModified / 1000L * 1000L ) { this .maybeSetLastModified(resp, lastModified); this .doGet(req, resp); } else { resp.setStatus(304 ); } } } else if (method.equals("HEAD" )) { lastModified = this .getLastModified(req); this .maybeSetLastModified(resp, lastModified); this .doHead(req, resp); } else if (method.equals("POST" )) { this .doPost(req, resp); } else if (method.equals("PUT" )) { this .doPut(req, resp); } else if (method.equals("DELETE" )) { this .doDelete(req, resp); } else if (method.equals("OPTIONS" )) { this .doOptions(req, resp); } else if (method.equals("TRACE" )) { this .doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented" ); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501 , errMsg); } } private void maybeSetLastModified (HttpServletResponse resp, long lastModified) { if (!resp.containsHeader("Last-Modified" )) { if (lastModified >= 0L ) { resp.setDateHeader("Last-Modified" , lastModified); } } } public void service (ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch (ClassCastException var6) { throw new ServletException("non-HTTP request or response" ); } this .service(request, response); } }
Servlet示例 👇此处展示一段处理登录业务的Servlet代码👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.geyu.web;import com.geyu.web.mapper.UserMapper;import com.geyu.web.pojo.User;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import javax.servlet.*;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.io.InputStream;import java.io.PrintWriter;import java.util.Map;@WebServlet ("/loginServlet" )public class ServletDemo extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username" ); String password = req.getParameter("password" ); String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.select(username, password); sqlSession.close(); resp.setContentType("text/html;charset=utf-8" ); PrintWriter writer = resp.getWriter(); if (user != null ){ writer.write("登陆成功" ); }else { writer.write("登录失败" ); } } }
总结 讲了这么多废话,总结来说Servlet就是一群人来制定Java应用中使用Web时的各种规范,统一接口,其他内部实现由厂商自己实现,tomcat jetty jboss等等应运而生。
关于他如何工作的:一个http请求到来,容器将请求封装成Servlet中的request对象,在request中你可以得到所有的http信息,然后你可以取出来操作,最后你再把数据封装成Servlet的response对象,应用容器将respose对象解析之后封装成一个http response。
Web服务器习惯处理静态页面,所以需要一个程序来帮忙处理动态请求(如当前时间)。Web服务器程序会将动态请求转发给帮助程序,帮助程序处理后,返回处理后的静态结果给Web服务器程序。这样就避免了Web服务器程序处理动态页面。
SpringMVC框架 简介 Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
SpringMVC和Servlet的区别与联系
这两者的关系,就如同MyBatis和JDBC,一个性能好,一个开发效率高,是对另一个的封装。
Controller层示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Controller @RequestMapping ("/user" )public class UserController { @Autowired IUserService service; @RequestMapping (value = "/login.do" , method = {RequestMethod.GET}) public String login (User user) { User result = service.login(user); if (result != null ) { return "redirect:/pages/success.html" ; } else { return "redirect:/pages/error.html" ; } } }
👀开发过程肉眼可见的简介♥