【JavaWeb学习笔记】10 - 手写Tomcat底层,Maven的初步使用
一、Maven
1.Maven示意图
类似Java访问数据库
2.创建Maven案例演示
配置阿里镜像
找到setting目录
但一开始配置不存在该文件
需要去Maven主目录下的conf拿到settings拷贝到上述目录
拷贝到admin/.m2后打开该settings
在<mirrors>内输入镜像地址
<mirror>
? ? ??? ?<id>alimaven</id>
? ? ??? ?<name>aliyun maven</name>
? ? ??? ?<url>https://maven.aliyun.com/nexus/content/groups/publichttps://maven.aliyun.com/repository/publichttps://maven.aliyun.com/nexus/content/groups/public</url>
? ? ??? ?<mirrorOf>central</mirrorOf>
? ? ?</mirror>
配置pom.xml文件
</dependency>
<!--引入servlet.jar包-->
<!--
1.入servlet-api.jar ,为J开发servlet
2. dependency 标签是表示引入-一个包
3. groupId包的公司/ 组织/开发团队/个人信息javax. servlet
4. artifactId :项目名javax .servlet-api
5. version 版本
6. scope 表示引入的包的作用范围
7. provided: 表示tomcat 本身有jar包,这里你引入的jar包,在编译,测试有效
但是在打包的时候不要带上这个jar包
8.下载的包在你指定的目录:C:\Users\Administrator\.m2\repository
9.可以去修改我们要下载包的位置->
10.我们可以去指定maven仓库,即配置maven镜像C:\Users\Administratorl.m2\settings.xml
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
3.实现计算器效果
创建Tomcat的时候不要使用xxx_war包而要使用explore的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算器</title>
</head>
<body>
<form action="/yhtomcat/calServlet" method="post">
num1:<input type="text" name="num1"><br/>
num2:<input type="text" name="num2"><br/>
<input type="submit" value="submit">
</form>
</body>
</html>
@WebServlet(name = "CalServlet",urlPatterns = "/calServlet")
public class CalServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String strnum1 = request.getParameter("num1");
String strnum2 = request.getParameter("num2");
int num1 = 0;
int num2 = 0;
int sum = -1;
try {
num1 = Integer.parseInt(strnum1);
num2 = Integer.parseInt(strnum2);
System.out.println("res = " + num1 + num2);
sum = num1 + num2;
} catch (NumberFormatException e) {
System.out.println("form wrong , continue");
}
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
if (!(sum == -1)) {
writer.print("<h1> res = " + sum + "</h1>");
}else{
writer.print("<h1> wrong date please try again!! </h1>");
}
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
二、Tomcat架构分析
我们的目标:不用Tomcat,不用系统提供的Servlet,
模拟Tomcat底层实现并能调用我们自己设计的Servle,也能完成相同的功能
说明: Tomcat有三种运行模式(BIO, NIO, APR) ,因为老师核心讲解的是Tomcat如何接收客户端请求,解析请求,调用Servlet并返回结果的机制流程,采用BIO线程模型来模拟.
模拟Tomcat底层机制
一、编写自己Tomcat
1.基于socket开发服务端流程
1. ServerSocket
在服务端监听指定端口,如果浏览器/客户端连接该端口,则建立连接,返回Socket对象
2. Socket
表示服务端和客户端/浏览器间的连接,通过Socket可以得到InputStream和OutputStream流对象。
?
public class YhTomcatV1 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("======yhtomcat 在8080端口监听");
while (!serverSocket.isClosed()){
//等待连接
//如果有连接来,就创建一个socket
//这socket就是服务端和浏觉器端的连接/通道
Socket socket = serverSocket.accept();
//先接受浏览器发来的数据
//字节流
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader
(new InputStreamReader(inputStream,"utf-8"));
String mes = null;
System.out.println("=====接受到浏览器发送的数据======");
while ((mes = bufferedReader.readLine()) != null){
if(mes.length() == 0){//读到空字符串
break;
}
System.out.println(mes);
}
//我们的tomcat会送-http响应方式
OutputStream outputStream = socket.getOutputStream();
//构建一个http响应的头
//\r\n 表示回车换行
//http响应体,需要前面有两个换行 \r\n\r\n
String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
String resp = respHeader + "<h1>hi, 这是模拟Tomcat</h1>";
System.out.println("========我们的tomcat 给浏览器会送的数据======");
System.out.println(resp);
outputStream.write(resp.getBytes());//将resp字符串以byte[] 方式返回
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
}
}
}
2.使用BIO线程模型,支持多线程
BIO线程模型介绍
需求分析
浏览器请求http:/ /localhost:8080,服务端返回hi , hspedu,后台hsptomcat使用BIO线程模型,支持多线程=>对前面的开发模式进行改造
一个持有线程的对象
public class YhRequestHandler extends Thread {
/*
* 1. HspRequestHandler 对象是一个线程对象
* 2. 处理一个http请求的
*/
//定义Socket
private Socket socket = null;
public YhRequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//这里我们可以对客户端/浏览器进行IO编程/交互
try {
//1.使用BIO线程模型,支持多线程
InputStream inputStream = socket.getInputStream();
// //把inputStream -> BufferedReader -> 方便进行按行读取
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
//
// //不同的线程在和浏览器和客户端交互
System.out.println("当前线程= " + Thread.currentThread().getName());
System.out.println("=========hsptomcatv2 接收到的数据如下=========");
String mes = null;
// io - 网络 - 线程 - 反射 - 注解 - OOP [都会学会,也会学好]
//
while ((mes = bufferedReader.readLine()) != null) {
//如果长度为0 ""
if (mes.length() == 0) {
break; //退出
}
System.out.println(mes);
}
//构建一下http响应头
//返回的http的响应体和响应头之间有两个换行 \r\n\r\n
String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
String resp = respHeader + "<h1>hi this is ThreadServlet</h1>";
System.out.println("========Yhtomcatv2返回的数据是=========");
System.out.println(resp);
//返回数据给我们的浏览器/客户端-> 封装成http响应
OutputStream outputStream = socket.getOutputStream();
//resp.getBytes() 是把字符串转成字节数组
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
//最后一定确保socket要关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Tomcat
public class YhTomcatV2 {
public static void main(String[] args) throws IOException {
//在8080端口监听
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("=======hsptomcatV2 在8080监听=======");
//只要 serverSocket没有关闭,就一直等待浏览器/客户端的连接
while (!serverSocket.isClosed()) {
//1. 接收到浏览器的连接后,如果成功,就会得到socket
//2. 这个socket 就是 服务器和 浏览器的数据通道
Socket socket = serverSocket.accept();
//3. 创建一个线程对象,并且把socket给该线程
// 这个是java线程基础
YhRequestHandler hspRequestHandler =
new YhRequestHandler(socket);
new Thread(hspRequestHandler).start();
}
}
}
问题分析: MyT omcat只是简单返回结果,没有和Servlet,web.xml关联
3.处理 Servlet?
Request处理请求信息
public class YhRequest {
/**
* 1. YhRequest 作用是封装http请求的数据
* get /hspCalServlet?num1=10&num2=30
* 2. 比如 method(get) 、 uri(/hspCalServlet) 、 还有参数列表 (num1=10&num2=30)
* 3. HspRequest 作用就等价原生的servlet 中的HttpServletRequest 这里考虑的是GET请求
*/
private String method;
private String uri;
//存放参数列表 参数名-参数值 => HashMap
private HashMap<String, String> parametersMapping =
new HashMap<>();
private InputStream inputStream = null;
//构造器=> 对http请求进行封装 => 可以将老师写的代码封装成方法
//inputStream 是和 对应http请求的socket关联
public YhRequest(InputStream inputStream) {
this.inputStream = inputStream;
//完成对http请求数据的封装..
encapHttpRequest();
}
/**
* 将http请求的相关数据,进行封装,然后提供相关的方法,进行获取
*/
private void encapHttpRequest() {
System.out.println("yhRequest init()");
try {
//inputstream -> BufferedReader
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
//读取第一行
/**
* GET /hspCalServlet?num1=10&num2=30 HTTP/1.1
* Host: localhost:8080
* User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Fi
*/
String requestLine = bufferedReader.readLine();
//GET - /hspCalServlet?num1=10&num2=30 - HTTP/1.1
String[] requestLineArr = requestLine.split(" ");
//得到method
method = requestLineArr[0];
//解析得到 /hspCalServlet
//1. 先看看uri 有没有参数列表
int index = requestLineArr[1].indexOf("?");
if (index == -1) { //说明没有参数列表
uri = requestLineArr[1];
} else {
//[0,index)
uri = requestLineArr[1].substring(0, index);
//获取参数列表->parametersMapping
//parameters => num1=10&num2=30
String parameters = requestLineArr[1].substring(index + 1);
//num1=10 , num2=30 .... parametersPair= ["num1=10","num2=30" ]
String[] parametersPair = parameters.split("&");
//防止用户提交时 /hspCalServlet?
if (null != parametersPair && !"".equals(parametersPair)) {
//再次分割 parameterPair = num1=10
for (String parameterPair : parametersPair) {
//parameterVal ["num1", "10"]
String[] parameterVal = parameterPair.split("=");
if (parameterVal.length == 2) {
//放入到 parametersMapping
parametersMapping.put(parameterVal[0], parameterVal[1]);
}
}
}
}
//这里不能关闭流 inputStream 和 socket关联
//inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//request对象有一个特别重要方法
public String getParameter(String name) {
if (parametersMapping.containsKey(name)) {
return parametersMapping.get(name);
} else {
return "";
}
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
@Override
public String toString() {
return "HspRequest{" +
"method='" + method + '\'' +
", uri='" + uri + '\'' +
", parametersMapping=" + parametersMapping +
'}';
}
}
注意?这里不能关闭流 inputStream 和 socket关联
Response对象处理响应?持有socket
public class YhResponse {
/**
* 1. HspResponse对象可以封装OutputStream(是socket关联)
* 2. 即可以通过 HspResponse对象 返回Http响应给浏览器/客户端
* 3. HspResponse对象 的作用等价于原生的servlet的 HttpServletResponse
*/
private OutputStream outputStream = null;
//写一个http的响应头 => 先死后活
public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
//说明同学们如果有兴趣, 在编写更多的方法
//比如 setContentType
//在创建 YhResponse 对象时,传入的outputStream是和Socket关联的
public YhResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
//当我们需要给浏览器返回数据时,可以通过HspResponse 的输出流完成
//
public OutputStream getOutputStream() {
return outputStream;
}
}
设计Servlet规范类以及Servlet接口
public interface YhServlet {
void init() throws Exception;
void service(YhRequest request, YhResponse response) throws IOException;
void destroy();
}
public abstract class YhHttpServlet implements YhServlet {
@Override
public void service(YhRequest request, YhResponse response) throws IOException {
//老师说明 equalsIgnoreCase 比较字符串内容是相同,不区别大小写
if("GET".equalsIgnoreCase(request.getMethod())) {
//这里会有动态绑定
this.doGet(request,response);
} else if("POST".equalsIgnoreCase(request.getMethod())) {
this.doPost(request,response);
}
}
//这里我们使用的了模板设计模式 => java 基础的 抽象类专门讲过模板设计模式
//让HspHttpServlet 子类 HspCalServlet 实现
public abstract void doGet(YhRequest request, YhResponse response);
public abstract void doPost(YhRequest request, YhResponse response);
}
YhCalServlet实现该Servlet并写自己的业务代码?
public class YhCalServlet extends YhHttpServlet {
@Override
public void doGet(YhRequest request, YhResponse response) throws IOException {
doPost(request,response);
}
@Override
public void doPost(YhRequest request, YhResponse response) throws IOException {
String strnum1 = request.getParameter("num1");
String strnum2 = request.getParameter("num2");
int num1 = 0;
int num2 = 0;
int sum = -1;
try {
num1 = Integer.parseInt(strnum1);
num2 = Integer.parseInt(strnum2);
System.out.println("res = " + num1 + num2);
sum = num1 + num2;
} catch (NumberFormatException e) {
System.out.println("form wrong , continue");
}
// response.setContentType("text/html;charset=utf-8"); response内已经做了
OutputStream outputStream = response.getOutputStream();
if (!(sum == -1)) {
outputStream.write((YhResponse.respHeader + "<h1> res = " + sum + "</h1>").getBytes());
}else{
outputStream.write((YhResponse.respHeader + "<h1> wrong date please try again!! </h1>").getBytes());
}
outputStream.flush();
outputStream.close();
}
@Override
public void init() {
}
@Override
public void destroy() {
}
}
4.使用反射去处理查找哪个calServlet
handler管理线程代码?
//=====================通过反射来实现==========
// 先说明一把实现思路->【停一下】 -> 如果你自己完成?10min
// 1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
YhRequest yhRequest = new YhRequest(socket.getInputStream());
YhResponse yhResponse = new YhResponse(socket.getOutputStream());
String uri = yhRequest.getUri();
String servletName = YhTomcatV3.servletUrlMapping.get(uri);
if(servletName == null){
servletName = "";
}
//2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet
YhHttpServlet yhHttpServlet =
YhTomcatV3.servletMapping.
get(servletName);
//3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost
if (yhHttpServlet != null) {//得到
yhHttpServlet.service(yhRequest, yhResponse);
} else {
//没有这个servlet , 返回404的提示信息
String resp = YhResponse.respHeader + "<h1>404 Not Found</h1>";
OutputStream outputStream = yhResponse.getOutputStream();
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
}
模拟Tomcat利用反射和dom4j处理xml文件获取Servlet
public class YhTomcatV3 {
//1. 存放容器 servletMapping
// -ConcurrentHashMap
// -HashMap
// key - value
// ServletName 对应的实例
public static final ConcurrentHashMap<String, YhHttpServlet>
servletMapping = new ConcurrentHashMap<>();
//2容器 servletUrlMapping
// -ConcurrentHashMap
// -HashMap
// key - value
// url-pattern ServletName
public static final ConcurrentHashMap<String, String>
servletUrlMapping = new ConcurrentHashMap<>();
//你可以这里理解session, tomcat还维护一个容器
public static final ConcurrentHashMap<String, HttpSession>
sessionMapping = new ConcurrentHashMap<>();
// //你可以这里理解filter, tomcat还维护了filter的容器
// public static final ConcurrentHashMap<String, String>
// filterUrlMapping = new ConcurrentHashMap<>();
//
// public static final ConcurrentHashMap<String, Filter>
// filterMapping = new ConcurrentHashMap<>();
public static void main(String[] args) throws MalformedURLException, DocumentException {
YhTomcatV3 yhTomcatV3 = new YhTomcatV3();
yhTomcatV3.init();
//启动hsptomcat容器
yhTomcatV3.startTomcatV3();
}
//启动HspTomcatV3容器
public void startTomcatV3() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("=====hsptomcatv3在8080监听======");
while (!serverSocket.isClosed()) {
Socket socket = serverSocket.accept();
YhRequestHandler yhRequestHandler =
new YhRequestHandler(socket);
new Thread(yhRequestHandler).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//直接对两个容器进行初始化
@Test
public void init() throws MalformedURLException, DocumentException {
//读取web.xml => dom4j =>
//得到web.xml文件的路径 => 拷贝一份.
String path = YhTomcatV3.class.getResource("/").getPath();
System.out.println("path= " + path);
//使用dom4j技术完成读取
SAXReader saxReader = new SAXReader();
//困难->真的掌握
try {
Document document = saxReader.read(new File(path + "web.xml"));
System.out.println("document= " + document);
//得到根元素
Element rootElement = document.getRootElement();
//得到根元素下面的所有元素
List<Element> elements = rootElement.elements();
//遍历并过滤到 servlet servlet-mapping
for (Element element : elements) {
if ("servlet".equalsIgnoreCase(element.getName())) {
//这是一个servlet配置
//System.out.println("发现 servlet");
//使用反射将该servlet实例放入到servletMapping
Element servletName = element.element("servlet-name");
Element servletClass = element.element("servlet-class");
servletMapping.put(servletName.getText(),
(YhHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
} else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
//这是一个servlet-mapping
//System.out.println("发现 servlet-mapping");
Element servletName = element.element("servlet-name");
Element urlPatter = element.element("url-pattern");
servletUrlMapping.put(urlPatter.getText(), servletName.getText());
}
}
} catch (Exception e) {
e.printStackTrace();
}
//老韩验证,这两个容器是否初始化成功
System.out.println("servletMapping= " + servletMapping);
System.out.println("servletUrlMapping= " + servletUrlMapping);
}
}
二、课后作业
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算器</title>
</head>
<body>
<form action="/yhCalServlet" method="GET">
num1:<input type="text" name="num1"><br/>
num2:<input type="text" name="num2"><br/>
<input type="submit" value="submit">
</form>
</body>
</html>
在工具类内写方法判断??如果不是servlet?就判断是不是html
public static String readHtml(String filename) {
String path = com.yinhai.utils.WebUtils.class.getResource("/").getPath();
StringBuilder stringBuilder = new StringBuilder();
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader(path + filename));
String buf = "";
while ((buf = bufferedReader.readLine()) != null) {
stringBuilder.append(buf);
}
} catch (Exception e) {
e.printStackTrace();
}
return stringBuilder.toString();
}
如果是html?就走该if体?将方法返回?
// ====================新增业务逻辑===========
//(1) 判断uri是什么资源 => 工具方法
//(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
//(3) 因为目前老师并没有起到tomcat, 不是一个标准的web项目
//(4) 把读取的静态资源放到 target/classes/cal.html
//过滤,拦截 , 权限等待 => Handler.... => 分发
if(WebUtils.isHtml(uri)) {//就是静态页面
String content = WebUtils.readHtml(uri.substring(1));
content = yhResponse.respHeader + content;
//得到outputstream , 返回信息(静态页面)给浏览器
OutputStream outputStream = yhResponse.getOutputStream();
outputStream.write(content.getBytes());
outputStream.flush();
outputStream.close();
socket.close();
return;
}
//===========================================
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!