一、Maven的使用
Java传统项目:
Maven的Java项目:
创建Maven的Web项目:
创建Project
选择Maven
改名字
修改maven依赖的配置
补充:如何配置阿里maven镜像
- 把D:\program\JavaIDEA 2020.2\plugins\maven\lib\maven3\conf\settings.xml拷贝默认的maven配置目录
- C:\Users\Administrator.m2目录settings.xml
- 修改C:\Users\Administrator.m2\settings.xm,增加下面的部分
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The
repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror.
IDs are used
| for inheritance and direct lookup purposes, and must be unique across the
set of mirrors. |
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
韩顺平 Java 工程师
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
pom.xml文件应该按照如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hspedu</groupId>
<artifactId>hsp-tomcat</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>hsp-tomcat Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--
1. dependency 表示依赖, 也就是我们这个项目需要依赖的 jar 包
2. groupId 和 artifactId 被统称为坐标, 是为了去定位这个项目/jar
3. groupId: 一般是公司 比如 com.baidu , 这里是 avax.servlet
4. artifactId 一般是项目名, 这里是 javax.servlet-api
5. 这样的化就可以定位一个 jar 包
6. version 表示你引入到我们项目的 jar 包的版本是 3.1.0
7. scope: 表示作用域,也就是你引入的 jar 包的作用范围
8. provided 表示在 tomcat 本身是有这个 jar 的,因此在编译,测试使用,但是在打包发布就不用要带上
-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
写计算器的html页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算器</title>
</head>
<body>
<h1>计算器</h1>
<form action="/cal/calServlet" method="get">
num1:<input type="text" name="num1"><br/>
num2:<input type="text" name="num2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
Servlet页面:
public class CalServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int num1 = Integer.parseInt(req.getParameter("num1"));
int num2 = Integer.parseInt(req.getParameter("num2"));
int result = num1 + num2;
resp.setContentType("text/html; charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.write("<h1>" + result + "</h1>");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
二、Tomcat整体架构分析
目标是:不用Tomcat,不用系统提供的Servlet,模拟Tomcat底层实现并能调用我们自己设计的Servlet,也能完成相同的功能
1. 任务阶段1
编写Tomcat,要求能返回hello xxx
基于Socket开发的服务流程
1.ServerSocket:在服务器监听指定端口,如果浏览器/客户端连接该端口,则建立连接,返回Socket对象
2.Socket:表示服务器和客户端/浏览器的连接,通过Socket可以得到InputStream和OutputStream流对象
实现第一阶段
需要模仿这个相信消息
public class OlivierTomcatVersion1 {
public static void main(String[] args) throws IOException {
//1.创建ServerSocket, 监听8080端口
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("OlivierTomcatVersion1在8080端口监听");
while (!serverSocket.isClosed()){
//如果serverSocket没有关闭,就等待客户端/服务器连接
//如果有连接请求,就创建一个Socket对象
//Socket就是服务器端和浏览器端的连接和通道
Socket socket = serverSocket.accept();
//先接收浏览器发送的数据
//InputStream是字节流,要转换为BufferedReader字符流
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
String msg = null;
System.out.println("======接收到浏览器发送的数据===");
//循环读取
while ((msg = bufferedReader.readLine())!= null){
if (msg.length() == 0) {
break;
}
System.out.println(msg);
}
//自定义Tomcat回送--http响应方式
OutputStream outputStream = socket.getOutputStream();
//构建一个http响应的消息头
String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type:text/html;charset=utf-8\r\n\r\n";
//http响应体需要有两个换行\r\n
String resp = respHeader + "<h1>hello tomcat</h1>";
System.out.println("======我们的Tomcat给浏览器回送的数据===");
System.out.println(resp);
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
}
}
}
2. 任务阶段2
使用BIO线程模型去支持多线程
BIO线程模型介绍
我们使用HspRequestHandler,请求过来,猫会创建HspRequestHandler,Socket对象持有的输入流和输出流,将Socket传给HspRequestHandler,它不实际操作输入流和输出流,只起到一个分发作用
HspRequestHandler代码:
/**
* 这个对象是一个线程对象
* 它是处理一个http请求的
*/
public class HspRequestHandler implements Runnable {
//定义一个Socket对象
private Socket socket = null;
public HspRequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//这里我们可以对客户端/浏览器进行IO交互
try {
InputStream inputStream = socket.getInputStream();
//将inputStream转换成bufferedReader,方便进行数据接收,也就是按行读取
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
System.out.println("-----OlivierTomcatVersion2接收到如下数据-----");
String msg = null;
while ((msg = bufferedReader.readLine()) != null){
if (msg.length() == 0) {
break; //确实读取到了,但是是个空串,就跳出循环
}
System.out.println(msg);
}
//构建以下http响应头
String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type:text/html;charset=utf-8\r\n\r\n";
String resp = respHeader + "<h1>hello tomcat2</h1>";
//返回数据给浏览器,将其封装成http响应
OutputStream outputStream = socket.getOutputStream();
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
自己的Tomcat的代码:
public class OlivierTomcatVersion2 {
public static void main(String[] args) throws IOException {
//监听8080端口
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("OlivierTomcatVersion2在8080端口监听");
while (!serverSocket.isClosed()){
Socket socket = serverSocket.accept();
Thread thread = new Thread(new HspRequestHandler(socket));
thread.start();
}
}
}
依然存在的问题:现在确实有多个线程处理请求了,但是没有和Servlet和web.xml相关联
3. 任务阶段3
处理Servlet
先要回顾Servlet的生命周期
新需求:浏览器请求http://localhost:8080/OlivierCalServlet,提交数据,完成计算任务,如果Servlet不存在,返回404
实现代码:
/**
* HspRequest作用是封装http请求的数据
* 封装其中的参数比如,get还是post,uti,还有参数列表num1=10&num2=20...
* HspRequest作用就是等价于原生的Servlet中的HttpRequest
*/
public class HspRequest {
private String method;
private String uri;
private HashMap<String, String> parametersMapping = new HashMap<>();
public HspRequest(InputStream inputStream) throws IOException {
//inputStream是和对应的http请求的socket相关联
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
//读取第一行,类似这个形式GET /olivierServlet?num1=10&num2=25 HTTP/1.1
String requestLine = bufferedReader.readLine();
String[] requestLineArr = requestLine.split(" ");
System.out.println("requestLineArr = " + requestLineArr);
//这样获取的就是上面的GET,将这个参数放入method属性里
method = requestLineArr[0];
//解析出/olivierServlet uri,先看看有没有参数列表,放入uri中
int index = requestLineArr[1].indexOf("?");
System.out.println("index = " + index);
if (index == -1) {
uri = requestLineArr[1];
} else {
uri = requestLineArr[1].substring(0, index);
//获取参数列表,放入parametersMapping
String parameters = requestLine.split(" ")[1].substring(index + 1);
String[] parametersPair = parameters.split("&");
if (null != parametersPair && !"".equals(parametersPair)) {
for (String parameterPair : parametersPair) {
String[] parameterVal = parameterPair.split("=");
if (parameterVal.length == 2) {
parametersMapping.put(parameterVal[0], parameterVal[1]);
}
}
}
}
}
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;
}
public String getParameter(String name) {
if (parametersMapping.containsKey(name)) {
return parametersMapping.get(name);
}else {
return "";
}
}
}
添加HspResponse,添加处理结果的逻辑:
/**
* HspResponse对象可以封装OutputStream,也是和Socket相关联
* 也就是通过HspResponse对象,返回http响应给浏览器
* HspResponse对象的作用等价于原生的servlet的HttpServletResponse
*/
public class HspResponse {
private OutputStream outputStream = null;
//http的响应头,这里只写成功的格式,如果愿意可以提供一个灵活的set方法
public static final String RESPONSE_HEADER = "HTTP/1.1 200 OK\r\n" +
"Content-Type:text/html;charset=utf-8\r\n\r\n";
public HspResponse(OutputStream outputStream){
this.outputStream = outputStream;
}
//当我们需要给浏览器返回数据时,可以通过HspResponse的输出流来完成
public OutputStream getOutputStream() {
return outputStream;
}
}
修改handler代码
/**
* 这个对象是一个线程对象
* 它是处理一个http请求的
*/
public class HspRequestHandler implements Runnable {
//定义一个Socket对象
private Socket socket = null;
public HspRequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//这里我们可以对客户端/浏览器进行IO交互
try {
InputStream inputStream = socket.getInputStream();
HspRequest hspRequest = new HspRequest(inputStream);
String num1 = hspRequest.getParameter("num1");
String num2 = hspRequest.getParameter("num2");
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
//构建以下http响应头
String resp = HspResponse.RESPONSE_HEADER + "<h1>hspResponse返回的hi MyTomcat3</h1>";
OutputStream outputStream = socket.getOutputStream();
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.flush();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4. 任务阶段4
该要添加Servlet处理请求的逻辑了
需求,仿照Servlet代码实现Servlet的三个主要方法:
- init方法
- service方法
- destroy方法
public void init() throws Exception;
public void service(HspRequest req, HspResponse res) throws Exception;
public void destroy();
第一步:先按顺序写好HspServlet,HspHttpServlet和HspCalServlet
HspServlet
public interface HspServlet {
public void init() throws Exception;
public void service(HspRequest req, HspResponse res) throws Exception;
public void destroy();
}
HspHttpServlet
public abstract class HspHttpServlet implements HspServlet {
@Override
public void service(HspRequest req, HspResponse res) throws Exception {
if ("GET".equalsIgnoreCase(req.getMethod())) {
this.doGet(req, res);
} else if ("POST".equalsIgnoreCase(req.getMethod())) {
this.doPost(req, res);
}
}
/**
* 这里只是一个模板设计模式,让HspHttpServlet的子类去实现这个方法
*
* @param hspRequest
* @param hspResponse
*/
public abstract void doGet(HspRequest hspRequest, HspResponse hspResponse) throws IOException;
/**
* 这里只是一个模板设计模式,让HspHttpServlet的子类去实现这个方法
*
* @param hspRequest
* @param hspResponse
*/
public abstract void doPost(HspRequest hspRequest, HspResponse hspResponse);
}
HspCalServlet
public class HspCalServlet extends HspHttpServlet{
@Override
public void doGet(HspRequest hspRequest, HspResponse hspResponse) throws IOException {
int number1 = WebUtils.parseInt(hspRequest.getParameter("num1"), 0);
int number2 = WebUtils.parseInt(hspRequest.getParameter("num2"), 0);
int result = number1 + number2;
//返回数据
OutputStream outputStream = hspResponse.getOutputStream();
String mes = HspResponse.RESPONSE_HEADER + "<h1>" + number1 + " + " + number2 + " = " + result + " in HspCalServlet3</h1>";
outputStream.write(mes.getBytes());
outputStream.flush();
outputStream.close();
}
@Override
public void doPost(HspRequest hspRequest, HspResponse hspResponse) {
doPost(hspRequest, hspResponse);
}
@Override
public void init() throws Exception {
}
@Override
public void destroy() {
}
}
HspRequestHandler
public class HspRequestHandler implements Runnable {
//定义一个Socket对象
private Socket socket = null;
public HspRequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//这里我们可以对客户端/浏览器进行IO交互
try {
InputStream inputStream = socket.getInputStream();
HspRequest hspRequest = new HspRequest(inputStream);
OutputStream outputStream = socket.getOutputStream();
HspResponse hspResponse = new HspResponse(outputStream);
//获取uri
String uri = hspRequest.getUri();
//获取servletName
String servletName = OlivierTomcatVersion3.servletUrlMapping.get(uri);
//或缺servlet实例
HspHttpServlet servlet = OlivierTomcatVersion3.servletMapping.get(servletName);
if (servlet != null) {
servlet.service(hspRequest, hspResponse);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
写一个OlivierTomcatVersion3功能
/**
* 通过xml+反射初始化容器
*/
public class OlivierTomcatVersion3 {
//1. 存放ServletMapping的容器
public static final ConcurrentHashMap<String, HspHttpServlet> servletMapping = new ConcurrentHashMap<>();
//2. 存放url-pattern和hashmap映射的
public static final ConcurrentHashMap<String, String> servletUrlMapping = new ConcurrentHashMap<>();
//直接对两个容器进行初始化
public static void init() {
//需要使用dom4j技术,去读取web.xml
//这是找到target下文件资源的路径
String path = OlivierTomcatVersion3.class.getResource("/").getPath();
path = "D:\\Knowledge Files\\code in here\\hsp-tomcat\\target\\classes\\";
System.out.println("path = " + path);
//使用dom4j技术
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new File(path + "web.xml"));
//得到根元素
Element rootElement = document.getRootElement();
//得到根元素下的所以元素
List<Element> elements = rootElement.elements();
//遍历并过滤,检查servlet和servletMapping
for (Element element : elements) {
if ("servlet".equalsIgnoreCase(element.getName())) {
//发现servlet以后,用反射将该servlet的实例放入到servletMapping中
Element servletName = element.element("servlet-name");
Element servletClass = element.element("servlet-class");
servletMapping.put(servletName.getText(), (HspHttpServlet) Class.forName(servletClass.getText()).newInstance());
} else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
Element servletName = element.element("servlet-name");
Element urlPattern = element.element("url-pattern");
servletUrlMapping.put(urlPattern.getText(), servletName.getText().trim());
}
}
} catch (Exception e) {
e.printStackTrace();
}
//验证两个容器是否初始化成功
System.out.println("servletMapping -- " + servletMapping);
System.out.println("servletUrlMapping -- " + servletUrlMapping);
}
//启动容器
public static void run() throws IOException {
init();
//监听8080端口
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("OlivierTomcatVersion3在8080端口监听");
while (!serverSocket.isClosed()) {
Socket socket = serverSocket.accept();
Thread thread = new Thread(new HspRequestHandler(socket));
thread.start();
}
}
public static void main(String[] args) throws IOException {
run();
}
}
WebUtils
public class WebUtils {
//将字符串转成数字
public static int parseInt(String strNum, int defaultVal){
try {
return Integer.parseInt(strNum);
}catch (NumberFormatException e){
System.out.println(strNum + "不能转成数字");
}
return defaultVal;
}
}