Tomcat源码分析

main项目与web项目

main方法是项目的入口,通过main方法启动项目,而web项目是没有main方法,如何让web项目启动起来,这时候就需要tomcat了,tomcat是一个servlet容器,处理http请求,把开发好的类打包成war包,然后放在tomcat的webapps下面,tomcat会自己解压war包,并且去运行程序

手撕Tomcat

原理

http协议实际是使用的TCP协议,底层是socket实现的

基本思路

背景

  1. tomcat--中间件
  2. web无main方法
  3. java--反射实现类动态加载
  4. web项目是通过http协议 tcp--socket

步骤

分析

tomcat需要main方法

tomcat需要监听本机上的某个端口

tomcat需要抓取此端口上来自客户端连接并且获取请求调用的方法与参数

tomcat需要根据请求调用方法,动态加载方法所在的类,完成类的实例化并通过该实例获取方法最终将请求传入方法

将结果返回给客户端(jsp/json/html/xml)

详细步骤

  1. 提供服务类Server类

  2. 处理请求信息Handler类

  3. 封装Request Response

  4. 实现Servlet

    HttpServlet抽象类,HttpServlet的实现类,web.xml文件的Servlet类,web.xml文件的ServletMapping类

  5. 分发请求 doGet或doPost或404或500

  6. 返回响应结果 response.write()

package com.yy.tomcat;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
    private static ServerSocket serverSocket;
    //JAVA中对线程池定义的一个接口
    private static ExecutorService executorService;
    //线程池最大连接数
    private final static int POOL_SIZE=15;
    private static int port=8090;
    public static void start(){
    try {
        serverSocket=new ServerSocket(port);
        Socket socket;
        System.out.println("starting"+port);
        //通过看源码确定newFixedThreadPool方法返回的类ThreadPoolExecutor(实现了ExecutorService接口)
        executorService = Executors.newFixedThreadPool(POOL_SIZE);
        while(true){
            socket = serverSocket.accept();
            //放进execute的线程会自动执行start方法
            executorService.execute(new Handler(socket));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
    public static void main(String[] args) {
        start();
    }
}

package com.yy.tomcat;
import java.io.*;
import java.net.Socket;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class Handler implements Runnable {
    private Socket socket;
    public Handler(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        //处理请求信息
        PrintWriter pw= null;
        try {
            pw = new PrintWriter(socket.getOutputStream());
            //http请求固定格式
            pw.println("HTTP/1.1 200 OK");
            pw.println("Content-Type: text/html;charset=UTF-8");
            pw.println();
            //用于接收浏览器的请求
            Request request = new Request();
            //用于返回信息到浏览器
            Response response=new Response(pw);
            //读取请求信息
            InputStream inputStream = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            while (true){
                //浏览器发送的请求第一行是GET /index?name=zeb&pwd=pwd HTTP/1.1
                String msg=reader.readLine();
                //String.Trim()方法会去除字符串两端,不仅仅是空格字符,它总共能去除25种字符:
                //Trim删除的过程为从外到内,直到碰到一个非空白的字符为止,所以不管前后有多少个连续的空白字符都会被删除掉。
                if(null == msg || "".equals(msg.trim())){
                    break;
                }
                String[] msgs=msg.split(" ");
                //msg
                if(3 == msgs.length && "HTTP/1.1".equalsIgnoreCase(msgs[2])){
                    //get or post
                    request.setMethod(msgs[0]);
                    //获取参数name与pwd
                    //   /index?name=zhangsan&pwd=123456
                    //split方法工作原理是利用正则表达式,而在正则表达式中, "?"有特殊意思
                    //所以匹配"?"时要用转义字符"\",所以在正则表达式中匹配"?"的表达式是"\?", 而在Java中,\又是特殊字符, 所以还要进行转义, 所以最终变成"\\?"
                    //正则中匹配前面的子表达式零次或一次。
                    String[] attributesPath=msgs[1].split("\\?");
                    // /index
                    request.setPath(attributesPath[0]);
                    if(attributesPath.length>1) {
                        Map<String,String> attributeMap=new HashMap<>();
                        String[] params=attributesPath[1].split("&");// name=zhangsan&pwd=123456
                        for(int i=0;i<params.length;i++) {
                            attributeMap.put(params[i].split("=")[0], params[i].split("=")[1]);// name,zhangsan
                        }
                        //放入request的map里面,map的key就是name和pwd value就是zhangsan和123456
                        request.setMap(attributeMap);
                    }
                    break;
                }
            }
        //如果是图标请求,直接返回    
if(request.getPath().endsWith("ico")){
    return;
}
//加载servlet
            HttpServlet httpServlet = request.initServlet();
//请求分发
            dispatcher(httpServlet,request,response);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            pw.close();
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private void dispatcher(HttpServlet httpServlet, Request request, Response response) {
        try {
            if (null == httpServlet){
            response.write("<h1>404</h1>");
        }else{
                if ("GET".equalsIgnoreCase(request.getMethod())){
                    httpServlet.doGet(request,response);
                }else if("POST".equalsIgnoreCase(request.getMethod())){
                    httpServlet.doPost(request,response);
                }
            }
    }catch (Exception e) {
            response.write("<h1>500</h1>"+ Arrays.toString(e.getStackTrace()));
            e.printStackTrace();
        }
    }
}
package com.yy.tomcat;
import java.util.Map;
//模拟HttpServletRequest
public class Request {
    private String path;
    private String method;
    private Map<String,String> map;
    //省去了get和set方法
public HttpServlet  initServlet(){
    return ServletContainer.getHttpServlet(path);
}
    public String getParameter(String name)
    {
        String parameter=map.get(name);
        return parameter;
    }
}
package com.yy.tomcat;
import java.io.PrintWriter;
//模拟HttpServletResponse
public class Response {
    private PrintWriter writer;
    public Response(PrintWriter writer) {
        this.writer = writer;
    }
    public void write(String msg){
        writer.write(msg);
        writer.flush();
    }
}

package com.yy.tomcat;
import java.io.IOException;
//模拟HttpServlet,自己的写的Servlet类都要继承这个抽象类
public abstract class HttpServlet {
    public void doGet(Request request,Response response) throws IOException {
this.service(request,response);
    }
    public void doPost(Request request,Response response) throws IOException {
        this.service(request,response);
    }
    public void service(Request request,Response response) throws IOException {
        if("GET".equalsIgnoreCase(request.getMethod())){
doGet(request,response);
        }else{
            doPost(request,response);
        }
    }
}
package com.yy.tomcat;
import java.io.IOException;
//自己写的servlet
public class MyServlet extends HttpServlet {
    @Override
    public void doGet(Request request, Response response) throws IOException {
response.write("<h1>Servlet GET response!</h1>"+
        "name: "+request.getParameter("name")+" , pwd: "+request.getParameter("pwd"));
    }
    @Override
    public void doPost(Request request, Response response) throws IOException {
response.write("<h1>Servlet POST response!</h1>"+
        "name: "+request.getParameter("name")+" , pwd: "+request.getParameter("pwd"));
    }
}
package com.yy.tomcat.model;
//解析web.xml用到的实体
public class Servlet {
private String name;
private String clazz;
//省去了set和get方法,本来是class,因为class是关键字用clazz代替
}
package com.yy.tomcat.model;
//解析web.xml用到的实体
public class ServletMapping {
    private String name;
    private String url;
}
package com.yy.tomcat.util;
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import com.yy.tomcat.model.Servlet;
import com.yy.tomcat.model.ServletMapping;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
//解析web.xml 了解即可
public class XMLUtil {
    public static Map<Integer,Map<String,Object>> parseWebXML() throws Exception{
        Map<Integer,Map<String,Object>> result=new HashMap<Integer,Map<String,Object>>();
        DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
        DocumentBuilder db=dbf.newDocumentBuilder();        
        
        InputStream in=XMLUtil.class.getClassLoader().getResourceAsStream("web.xml");
        Document document=db.parse(in);
        Element root=document.getDocumentElement();
        System.out.println("rootName: "+root.getTagName());//web-app
        
        NodeList xmlNodes=root.getChildNodes();     
        for(int i=0;i<xmlNodes.getLength();i++) {
            Node config=xmlNodes.item(i);           
            if(null!=config && config.getNodeType()== Node.ELEMENT_NODE) {
                String nodeName1=config.getNodeName();
                System.out.println("nodeName1: "+nodeName1);//servlet servlet-mapping               
                if("servlet".equals(nodeName1)) {
                    Map<String,Object> servletMaps=null;
                    if(result.containsKey(0)) {
                        servletMaps=result.get(0);
                    }else {
                        servletMaps=new HashMap<String,Object>();
                    }                   
                    NodeList childNodes=config.getChildNodes();
                    Servlet servlet=new Servlet();
                    for(int j=0;j<childNodes.getLength();j++) {                     
                        Node node =childNodes.item(j);                      
                        if(null!=node && node.getNodeType()== Node.ELEMENT_NODE) {              
                            String nodeName2=node.getNodeName();
                            System.out.println("nodeName2: "+nodeName2);//servlet-name servlet-class
                            String textContext=node.getTextContent();
                            System.out.println("textContext: "+textContext);
                            if("servlet-name".equals(nodeName2)) {
                                servlet.setName(textContext);
                            }else if("servlet-class".equals(nodeName2)) {
                                servlet.setClazz(textContext);
                            }
                        }
                    }
                    servletMaps.put(servlet.getName(), servlet);
                    result.put(0, servletMaps);                 
                    
                }else if("servlet-mapping".equals(nodeName1)) {
                    Map<String,Object> servletMappingMaps=null;
                    if(result.containsKey(1)) {
                        servletMappingMaps=result.get(1);
                    }else {
                        servletMappingMaps=new HashMap<String,Object>();
                    }
                    
                    NodeList childNodes=config.getChildNodes();
                    ServletMapping servletMapping=new ServletMapping();
                    
                    for(int j=0;j<childNodes.getLength();j++) {
                        Node node =childNodes.item(j);
                        if(null!=node && node.getNodeType()==Node.ELEMENT_NODE) {
                            String nodeName2=node.getNodeName();
                            System.out.println("nodeName2: "+nodeName2);//servlet-name url-pattern
                            String textContext=node.getTextContent();
                            System.out.println("textContext: "+textContext);
                            if("servlet-name".equals(nodeName2)) {
                                servletMapping.setName(textContext);
                            }else if("url-pattern".equals(nodeName2)) {
                                servletMapping.setUrl(textContext);
                            }
                        }
                    }
                    servletMappingMaps.put(servletMapping.getUrl(), servletMapping);
                    result.put(1, servletMappingMaps);
                }
            }
        }
        return result;
    }
    public static void main(String[] args) throws Exception {
        System.out.println(parseWebXML());
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!--注册一个前端控制器,dispatchServlet-->
    <servlet>
        <servlet-name>myServlet</servlet-name>
        <servlet-class>com.yy.tomcat.MyServlet</servlet-class>
    </servlet>
    <!--servlet映射配置-->
    <servlet-mapping>
        <servlet-name>myServlet</servlet-name>
        <!--先统一写斜杠-->
        <url-pattern>/index</url-pattern>
    </servlet-mapping>
</web-app>
package com.yy.tomcat;
import com.yy.tomcat.model.Servlet;
import com.yy.tomcat.model.ServletMapping;
import com.yy.tomcat.util.XMLUtil;
import java.util.HashMap;
import java.util.Map;
//Servlet容器,解析web.xml根据url信息拿到响应的servlet
public class ServletContainer {
    //3个servlet容器
    private static Map<String,Object> servletMaps=new HashMap<>();
    private static Map<String,Object> servletMappingMaps=new HashMap<>();
    private static Map<String,HttpServlet> servletContainer=new HashMap<>();
    //通过静态块去加载配置文件中映射对应的model实体
    static{
        try {
            Map<Integer, Map<String, Object>> maps = XMLUtil.parseWebXML();
            if(null != maps&&2 ==maps.size()){
                servletMaps=maps.get(0);
                servletMappingMaps=maps.get(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//通过工具类加载,从servlet容器中获取对应的servlet
    public static HttpServlet getHttpServlet(String path){
//访问跟路径/时修改为index路径
        if(null == path ||"".equals(path.trim()) || "/".equals(path)){
path="/index";
        }
        //如果servletContainer容器中有就直接取走
        if (servletContainer.containsKey(path)){
            return servletContainer.get(path);
        }
        if (!servletMappingMaps.containsKey(path)){
            return null;
        }
        ServletMapping servletMapping= (ServletMapping) servletMappingMaps.get(path);
        String name=servletMapping.getName();
        if(!servletMaps.containsKey(name)) {
            return null;
        }
        Servlet servlet=(Servlet) servletMaps.get(name);
        String clazz=servlet.getClazz();
        if(null==clazz || "".equals(clazz.trim())) {
            return null;
        }
        HttpServlet httpServlet=null;
        try {
            httpServlet=(HttpServlet) Class.forName(clazz).newInstance();//通过反射获取servlet实体
            servletContainer.put(path, httpServlet);//添加到servlet容器中
        } catch (Exception e) {
            e.printStackTrace();
        }
        return httpServlet;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容