目录

源码地址

https://github.com/CoderXiaohui/mini-tomcat

一,分析

Mini版Tomcat需要实现的功能

作为一个服务器软件提供服务(通过浏览器客户端发送Http请求,它可以接收到请求进行处理,处理之后的结果返回浏览器客户端)。

  1. 提供服务,接收请求(socket通信)
  2. 请求信息封装成Request对象,封装响应信息Response对象
  3. 客户端请求资源,资源分为静态资源(html)和动态资源(servlet)
  4. 资源返回给客户端浏览器

*Tomcat的入口就是一个main函数

二,开发——准备工作

2.1 新建Maven工程

2.2 定义编译级别

<?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.dxh</groupId>
<artifactId>MiniCat</artifactId>
<version>1.0-SNAPSHOT</version> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

2.3 新建主类编写启动入口和端口

这里我们把socket监听的端口号定义在主类中。

package server;

/**
* Minicat的主类
*/
public class Bootstrap {
/**
* 定义Socket监听的端口号
*/
private int port = 8080; public int getPort() {
return port;
} public void setPort(int port) {
this.port = port;
} /**
* Minicat的启动入口
* @param args
*/
public static void main(String[] args) { }
}

三,开发——1.0版本

循序渐进,一点一点的完善,1.0版本我们需要的需求是:

  • 浏览器请求http://localhost:8080,返回一个固定的字符串到页面“Hello Minicat”

3.1 编写start方法以及遇到的问题

start方法主要就是监听上面配置的端口,然后得到其输出流,最后写出。

/**
* MiniCat启动需要初始化展开的一些操作
*/
public void start() throws IOException {
/*
完成Minicat 1.0版本
需求:浏览器请求http://localhost:8080,返回一个固定的字符串到页面“Hello Minicat!”
*/
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("========>>Minicat start on port:"+port); while(true){
Socket socket = serverSocket.accept();
//有了socket,接收到请求,获取输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello Minicat!".getBytes());
socket.close();
}
}

完整的代码:


/**
* Minicat的主类
*/
public class Bootstrap {
/**
* 定义Socket监听的端口号
*/
private int port = 8080; public int getPort() {
return port;
} public void setPort(int port) {
this.port = port;
} /**
* Minicat的启动入口
* @param args
*/
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
//启动Minicat
bootstrap.start();
} catch (IOException e) {
e.printStackTrace();
}
} /**
* MiniCat启动需要初始化展开的一些操作
*/
public void start() throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("========>>Minicat start on port:"+port); while(true){
Socket socket = serverSocket.accept();
//有了socket,接收到请求,获取输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello Minicat!".getBytes());
socket.close();
}
} }

此时,如果启动项目,从浏览器中输入http://localhost:8080/,能够正常接收到请求吗?

不能!

问题分析:

启动项目,从浏览器中输入http://localhost:8080/,可看到返回结果如下图:

因为Http协议是一个应用层协议,其规定了请求头、请求体、响应同样,如果没有这些东西的话浏览器无法正常显示。代码中直接把”Hello Minicat!“直接输出了,

3.2 解决问题,修改代码:

  1. 新建一个工具类,主要提供响应头信息

    package server;
    
    /**
    * http协议工具类,主要提供响应头信息,这里我们只提供200和404的情况
    */
    public class HttpProtocolUtil { /**
    * 为响应码200提供请求头信息
    */
    public static String getHttpHeader200(long contentLength){
    return "HTTP/1.1 200 OK \n" +
    "Content-Type: text/html \n" +
    "Content-Length: "+contentLength +"\n"+
    "\r\n";
    } /**
    * 为响应码404提供请求头信息(也包含了数据内容)
    */
    public static String getHttpHeader404(){
    String str404="<h1>404 not found</h1>";
    return "HTTP/1.1 404 NOT Found \n" +
    "Content-Type: text/html \n" +
    "Content-Length: "+str404.getBytes().length +"\n"+
    "\r\n" + str404;
    } }
  2. 修改start方法

        public void start() throws IOException {
    
            ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("========>>Minicat start on port:"+port); while(true){
    Socket socket = serverSocket.accept(); OutputStream outputStream = socket.getOutputStream();
    String data = "Hello Minicat!";
    String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length)+data;
    outputStream.write(responseText.getBytes());
    socket.close();
    }
    }
  3. 访问~

成功。

四,开发——2.0版本

需求:

  • 封装Request和Response对象
  • 返回html静态资源文件

4.1 封装前准备

新建一个类,Bootstrap2 (为了方便与1.0版本做对比)。获得输入流,并打印出来看看。

package server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; /**
* Minicat的主类
*/
public class Bootstrap2 {
/**
* 定义Socket监听的端口号
*/
private int port = 8080; public int getPort() {
return port;
} public void setPort(int port) {
this.port = port;
} /**
* Minicat的启动入口
* @param args
*/
public static void main(String[] args) {
Bootstrap2 bootstrap = new Bootstrap2();
try {
//启动Minicat
bootstrap.start();
} catch (IOException e) {
e.printStackTrace();
}
} /**
* MiniCat启动需要初始化展开的一些操作
*/
public void start() throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("========>>Minicat start on port:"+port); while (true){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
//从输入流中获取请求信息
int count = 0 ;
while (count==0){
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
System.out.println("请求信息=====>>"+new String(bytes));
socket.close();
} } }

打印出来的信息:

这里我们需要得到的是 请求方式(GET) 和 url (/) ,接下来封装Request的时候也是只封装这两个属性

4.2封装Request、Response对象

4.2.1 封装Request

只封装两个参数——method和url

  1. 新建Request类

  2. 该类有三个属性(String methodString urlInputStream inputStream

    method和url都是从input流中解析出来的。

  3. GET SET方法

  4. 编写有参构造

    /**
    * 构造器 输入流传入
    */
    public Request(InputStream inputStream) throws IOException {
    this.inputStream = inputStream;
    //从输入流中获取请求信息
    int count = 0 ;
    while (count==0){
    count = inputStream.available();
    }
    byte[] bytes = new byte[count];
    inputStream.read(bytes);
    String inputsStr = new String(bytes);
    //获取第一行数据
    String firstLineStr = inputsStr.split("\\n")[0]; //GET / HTTP/1.1
    String[] strings = firstLineStr.split(" ");
    //把解析出来的数据赋值
    this.method=strings[0];
    this.url= strings[1]; System.out.println("method=====>>"+method);
    System.out.println("url=====>>"+url);
    }
  5. 无参构造

完整的Request.java

package server;

import java.io.IOException;
import java.io.InputStream; /**
* 把我们用到的请求信息,封装成Response对象 (根据inputSteam输入流封装)
*/
public class Request {
/**
* 请求方式 例如:GET/POST
*/
private String method; /**
* / , /index.html
*/
private String url; /**
* 其他的属性都是通过inputStream解析出来的。
*/
private InputStream inputStream; /**
* 构造器 输入流传入
*/
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
//从输入流中获取请求信息
int count = 0 ;
while (count==0){
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String inputsStr = new String(bytes);
//获取第一行数据
String firstLineStr = inputsStr.split("\\n")[0]; //GET / HTTP/1.1
String[] strings = firstLineStr.split(" ");
this.method=strings[0];
this.url= strings[1]; System.out.println("method=====>>"+method);
System.out.println("url=====>>"+url);
} public Request() {
} public String getMethod() {
return method;
} public void setMethod(String method) {
this.method = method;
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
}
a
public InputStream getInputStream() {
return inputStream;
} public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
}

4.2.2 封装Response

package server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream; /**
* 封装Response对象,需要依赖于OutputStream
*
*/
public class Response{
private OutputStream outputStream; public Response(OutputStream outputStream) {
this.outputStream = outputStream;
} public Response() {
} /**
* @param path 指的就是 Request中的url ,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过输出流输出
*/
public void outputHtml(String path) throws IOException {
//获取静态资源的绝对路径
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path); //输出静态资源文件
File file = new File(absoluteResourcePath);
if (file.exists() && file.isFile()){
//读取静态资源文件,输出静态资源
StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
}else{
//输出404
output(HttpProtocolUtil.getHttpHeader404());
}
} //使用输出流输出指定字符串
public void output(String context) throws IOException {
outputStream.write(context.getBytes());
}
}

2.0版本只考虑输出静态资源文件

我们来分析一下outputHtml(String path)这个方法

首先,path就指 Request中的url,我们要用这个url找到该资源的绝对路径:

  1. 根据path,获取静态资源的绝对路径

    public static String getAbsolutePath(String path){
    String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
    return absolutePath.replaceAll("\\\\","/")+path;
    }
  2. 判断静态资源是否存在

    • 不存在:输出404
  3. 存在:读取静态资源文件,输出静态资源

    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
    int count = 0 ;
    while (count==0){
    count=inputStream.available();
    }
    //静态资源长度
    int resourceSize = count;
    //输出Http请求头 , 然后再输出具体的内容
    outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes()); //读取内容输出
    long written = 0; //已经读取的内容长度
    int byteSize = 1024; //计划每次缓冲的长度
    byte[] bytes = new byte[byteSize]; while (written<resourceSize){
    if (written+byteSize >resourceSize){ //剩余未读取大小不足一个1024长度,那就按照真实长度处理
    byteSize= (int)(resourceSize-written); //剩余的文件内容长度
    bytes=new byte[byteSize];
    }
    inputStream.read(bytes);
    outputStream.write(bytes);
    outputStream.flush(); written+=byteSize;
    }
    }

把上述的第一步和第三步的方法封装到一个类中:

package server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class StaticResourceUtil { /**
* 获取静态资源方法的绝对路径
*/
public static String getAbsolutePath(String path){
String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
return absolutePath.replaceAll("\\\\","/")+path;
} /**
* 读取静态资源文件输入流,通过输出流输出
*/
public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
int count = 0 ;
while (count==0){
count=inputStream.available();
}
//静态资源长度
int resourceSize = count;
//输出Http请求头 , 然后再输出具体的内容
outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes()); //读取内容输出
long written = 0; //已经读取的内容长度
int byteSize = 1024; //计划每次缓冲的长度
byte[] bytes = new byte[byteSize]; while (written<resourceSize){
if (written+byteSize >resourceSize){ //剩余未读取大小不足一个1024长度,那就按照真实长度处理
byteSize= (int)(resourceSize-written); //剩余的文件内容长度
bytes=new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush(); written+=byteSize;
}
} }

测试:

  1. 修改Bootstrap2.java中的start()方法

    public void start() throws IOException {
    ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("========>>Minicat start on port:"+port); while (true){
    Socket socket = serverSocket.accept();
    InputStream inputStream = socket.getInputStream();
    //封装Resuest对象和Response对象
    Request request = new Request(inputStream);
    Response response = new Response(socket.getOutputStream());
    response.outputHtml(request.getUrl());
    socket.close();
    }
    }
  2. 在项目的resources文件夹新建index.html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Static resource </title>
    </head>
    <body>
    Hello ~ Static resource
    </body>
    </html>
  3. 运行main方法

  4. 浏览器输入:http://localhost:8080/index.html

  5. 结果展现:

五,开发——3.0版本

3.0版本就要定义Servlet了,大致分为以下几步:

  1. 定义servlet规范
  2. 编写Servlet
  3. 加载解析Servlet配置

5.1 定义servlet规范

public interface Servlet {
void init() throws Exception;
void destroy() throws Exception;
void service(Request request,Response response) throws Exception;
}

定义一个抽象类,实现Servlet,并且增加两个抽象方法doGet , doPost.

public abstract class HttpServlet implements Servlet{

    public abstract void doGet(Request request,Response response);
public abstract void doPost(Request request,Response response); @Override
public void init() throws Exception { } @Override
public void destroy() throws Exception { } @Override
public void service(Request request, Response response) throws Exception {
if ("GET".equals(request.getMethod())){
doGet(request, response);
}else{
doPost(request, response);
}
}
}

5.2 编写Servlet继承HttpServlet

新建DxhServlet.java,并继承HttpServlet重写doGet和doPost方法

package server;

import java.io.IOException;

public class DxhServlet extends HttpServlet{
@Override
public void doGet(Request request, Response response) {
String content="<h1>DxhServlet get</h1>";
try {
response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void doPost(Request request, Response response) {
String content="<h1>DxhServlet post</h1>";
try {
response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void init() throws Exception {
super.init();
} @Override
public void destroy() throws Exception {
super.destroy();
}
}

接下来要把DxhServlet配置到一个配置文件中,当MiniCat启动时,加载进去。

5.3 加载解析Servlet配置

5.3.1 配置文件

resources目录下,新建web.xml

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<servlet>
<servlet-name>dxh</servlet-name>
<servlet-class>server.DxhServlet</servlet-class>
</servlet> <servlet-mapping>
<servlet-name>dxh</servlet-name>
<url-pattern>/dxh</url-pattern>
</servlet-mapping>
</web-app>

标准的配置Servlet的标签。servlet-class改成自己写的Servlet全限定类名,url-pattern/dxh,一会请求http://localhost:8080/dxh,来访问这个servlet

5.3.2 解析配置文件

复制一份Bootstrap2.java,命名为Bootstrap3.java

  1. 加载解析相关的配置 ,web.xml

    引入dom4j和jaxen的jar包

    <dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
    </dependency>
    <dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
    </dependency>
  2. Bootstrap3.java中增加一个方法

    //用于下面存储url-pattern以及其对应的servlet-class的实例化对象
    private Map<String,HttpServlet> servletMap = new HashMap<>(); private void loadServlet(){
    InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
    SAXReader saxReader = new SAXReader();
    try {
    Document document = saxReader.read(resourceAsStream);
    //根元素
    Element rootElement = document.getRootElement();
    /**
    * 1, 找到所有的servlet标签,找到servlet-name和servlet-class
    * 2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>
    */
    List<Element> selectNodes = rootElement.selectNodes("//servlet");
    for (int i = 0; i < selectNodes.size(); i++) {
    Element element = selectNodes.get(i);
    /**
    * 1, 找到所有的servlet标签,找到servlet-name和servlet-class
    */
    //<servlet-name>dxh</servlet-name>
    Element servletNameElement =(Element)element.selectSingleNode("servlet-name");
    String servletName = servletNameElement.getStringValue();
    //<servlet-class>server.DxhServlet</servlet-class>
    Element servletClassElement =(Element)element.selectSingleNode("servlet-class");
    String servletClass = servletClassElement.getStringValue();
    /**
    * 2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>
    */
    //Xpath表达式:从/web-app/servlet-mapping下查询,查询出servlet-name=servletName的元素
    Element servletMapping =(Element)rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']'");
    // /dxh
    String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
    servletMap.put(urlPattern,(HttpServlet) Class.forName(servletClass).newInstance());
    }
    } catch (DocumentException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    }

    这段代码的意思就是读取web.xml转换成Document,然后遍历根元素内中的servlet标签(servlet是可以配置多个的),通过XPath表达式获得servlet-nameservlet-class,以及与其对应的<servlet-mapping>标签下的url-pattern,然后存在Map中。注意,这里Map的Key是url-patternValue是servlet-class的实例化对象

5.4 接收请求,处理请求改造

这里进行了判断,判断servletMap中是否存在url所对应的value,如果没有,当作静态资源访问,如果有,取出并调用service方法,在HttpServlet的service方法中已经做了根据request判断具体调用的是doGet还是doPost方法。

测试:

在浏览器中输入:

http://localhost:8080/index.html,可以访问静态资源

输入:http://localhost:8080/dxh

可以访问【5.2中编写的Servlet】动态资源~

到此位置,一个简单的Tomcat Demo已经完成。

六,优化——多线程改造(不使用线程池)

6.1 问题分析

在现有的代码中,接收请求这部分它是一个IO模型——BIO,阻塞IO。

它存在一个问题,当一个请求还未处理完成时,再次访问,会出现阻塞的情况。

可以在DxhServletdoGet方法中加入Thread.sleep(10000);然后访问http://localhost:8080/dxhhttp://localhost:8080/index.html做个测试

那么我们可以使用多线程对其进行改造。

把上述代码放到一个新的线程中处理。

6.2 复制Bootstrap3

复制Bootstrap3,命名为Bootstrap4。把start()方法中上图的部分(包括socket.close()剪切到下面的线程处理类的run方法中:

6.3 定义一个线程处理类

package server;

import java.io.InputStream;
import java.net.Socket;
import java.util.Map; /**
* 线程处理类
*/
public class RequestProcessor extends Thread{
private Socket socket;
private Map<String,HttpServlet> servletMap; public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
this.socket = socket;
this.servletMap = servletMap;
} @Override
public void run() {
try{
InputStream inputStream = socket.getInputStream();
//封装Resuest对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
String url = request.getUrl();
//静态资源处理
if (servletMap.get(url)==null){
response.outputHtml(request.getUrl());
}else{
//动态资源处理
HttpServlet httpServlet = servletMap.get(url);
httpServlet.service(request,response);
}
socket.close();
}catch (Exception e){ }
}
}

6.4 修改Bootstrap4的start()方法

public void start() throws Exception {
//加载解析相关的配置 ,web.xml,把配置的servlet存入servletMap中
loadServlet(); ServerSocket serverSocket = new ServerSocket(port);
System.out.println("========>>Minicat start on port:"+port);
/**
* 可以请求动态资源
*/
while (true){
Socket socket = serverSocket.accept();
//使用多线程处理
RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
requestProcessor.start();
}
}

再次做6.1章节的测试, OK 没有问题了。

七,优化——多线程改造(使用线程池)

这一步,我们使用线程池进行改造。

复制Bootstrap4,命名为Bootstrap5。

修改start()方法。线程池的使用不再赘述。代码如下:

public void start() throws Exception {
//加载解析相关的配置 ,web.xml,把配置的servlet存入servletMap中
loadServlet(); /**
* 定义线程池
*/
//基本大小
int corePoolSize = 10;
//最大
int maxPoolSize = 50;
//如果线程空闲的话,超过多久进行销毁
long keepAliveTime = 100L;
//上面keepAliveTime的单位
TimeUnit unit = TimeUnit.SECONDS;
//请求队列
BlockingQueue<Runnable> workerQueue = new ArrayBlockingQueue<>(50);
//线程工厂,使用默认的即可
ThreadFactory threadFactory = Executors.defaultThreadFactory();
//拒绝策略,如果任务太多处理不过来了,如何拒绝
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize
,maxPoolSize
,keepAliveTime
,unit
,workerQueue
,threadFactory
,handler); ServerSocket serverSocket = new ServerSocket(port);
System.out.println("========>>Minicat start on port(多线程):"+port);
/**
* 可以请求动态资源
*/
while (true){
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
threadPoolExecutor.execute(requestProcessor);
}
}

OK ,再次测试,成功~

MINI版Tomcat到此完成。

八,总结

总结一下编写一个MINI版本的Tomcat都需要做些什么:

  1. 定义一个入口类,需要监听的端口号和入口方法——main方法
  2. 定义servlet规范(接口),并实现它——HttpServlet
  3. 编写http协议工具类,主要提供响应头信息
  4. 在main方法中调用start()方法用于启动初始化和请求进来时的操作
  5. 加载解析配置文件(web.xml)
  6. 当请求进来时,解析inputStream,并封装为Request和Response对象。
  7. 判断请求资源的方式(动态资源还是静态资源)

【Tomcat】手写迷你版Tomcat的更多相关文章

  1. Netty核心组件介绍及手写简易版Tomcat

    Netty是什么: 异步事件驱动框架,用于快速开发高i性能服务端和客户端 封装了JDK底层BIO和NIO模型,提供高度可用的API 自带编码解码器解决拆包粘包问题,用户只用关心业务逻辑 精心设计的Re ...

  2. 手写迷你Tomcat

    手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat ...

  3. 我手写的简易tomcat

    前述 自己手写的简易的tomcat,实现了tomcat的基本响应功能,项目代码已经上传到我的Github,刚刚开始学习这里,当前还存在很多问题 项目简述及代码 当我们的Web运行的时候,从浏览器发出的 ...

  4. 手写一个简化版Tomcat

    一.Tomcat工作原理 我们启动Tomcat时双击的startup.bat文件的主要作用是找到catalina.bat,并且把参数传递给它,而catalina.bat中有这样一段话: Bootstr ...

  5. 手写迷你SpringMVC框架

    前言 学习如何使用Spring,SpringMVC是很快的,但是在往后使用的过程中难免会想探究一下框架背后的原理是什么,本文将通过讲解如何手写一个简单版的springMVC框架,直接从代码上看框架中请 ...

  6. 手写mini版MVC框架

    目录 1, Springmvc基本原理流程 2,注解开发 编写测试代码: 目录结构: 3,编写自定义DispatcherServlet中的初始化流程: 3.1 加载配置文件 3.2 扫描相关的类,扫描 ...

  7. 手写简易版RPC框架基于Socket

    什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...

  8. mybatis(八)手写简易版mybatis

    一.画出流程图 二.设计核心类 二.V1.0 的实现 创建一个全新的 maven 工程,命名为 mebatis,引入 mysql 的依赖. <dependency> <groupId ...

  9. 手写简易版Promise

    实现一个简易版 Promise 在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise 基本可以过关了. ...

随机推荐

  1. Java基础教程——包装类

    Java出道之时,自诩为"纯面向对象的语言",意思是之前的所谓"面向对象语言"不纯. 但是,有人指责Java也不纯--8种基本类型并非类类型.为此,Java为他 ...

  2. framework中的sentinel

    引入切面: 切面+sentinel-web-servlet private void initDataSource() { String zkUrl = zaSentinelConfig.getDat ...

  3. 【mq读书笔记】定时消息

    mq不支持任意的时间京都,如果要支持,不可避免的需要在Broker层做消息排序,加上持久化方面的考量,将不可避免地带来巨大的性能消耗,所以rocketMQ只支持特定级别的延迟消息. 在Broker短通 ...

  4. python中字符串的编码和解码

    1. 常用的编码 ASCII:只能表示一些字母,数字和特殊的字符,占一个字节 GBK:国家简体中文字符集和繁体字符集,兼容ASCII,占两个字节 Unicode:能够表示全世界上所有的字符,Unico ...

  5. 20200322_【转载】关于C#中使用SQLite自适应Any CPU程序的说明

    本文转载, 原文链接: http://luyutao.com/2016/09/14/csharp-sqlite-any-cpu.html 在C#中如果要使程序自适应32位和64位系统,只需要将项目的& ...

  6. Fiddler 4的安装

    1.双击FiddlerSetup_2.0.20194.413.exe安装包 2点击同意 3.傻瓜式安装即可 4.安装完就是这个样子 5.然后点这个 然后不管出现什么都点yes(或者是),最后点击ok ...

  7. OpenCV击中击不中HMTxingt变换最容易理解的解释

    OpenCV击中击不中变换是几个形态变换中相对比较拗口.不容易理解的,给初学者理解带来了很多困难,虽然网上也有许多的公开资料,原理和算法基本上介绍比较清晰,但是是要OpenCV进行形态变换大多还是说得 ...

  8. 如何使用 K8s 两大利器"审计"和"事件"帮你摆脱运维困境?

    概述 下面几个问题,相信广大 K8s 用户在日常集群运维中都曾经遇到过: 集群中的某个应用被删除了,谁干的? Apiserver 的负载突然变高,大量访问失败,集群中到底发生了什么? 集群节点 Not ...

  9. IntelliJ IDEA 学习笔记

    之前一直用Eclipse,最近尝试使用IDEA,相较于 Eclipse 而言,IDEA强大的整合能力,比如: Git. Maven. Spring 等:提示功能的快速. 便捷:提示功能的范围广:好用的 ...

  10. [亲测可用]BCompare文件比较对比工具4.3.4绿色版

    程序员必备良品 Beyond Compare 灰常好用,一直使用的文本 文件比较工具,用来对比两个文件文本的差异,可以用于各种代码,文本对比 使用方法: 解开压缩后:添加右键的 !添加右键.bat,运 ...