网站建设 企业 资质 等级,杭州做网站博客,手机网站怎么做域名解析,做阿里巴巴网站店铺装修费用【吃透Java手写】Tomcat-简易版-源码解析 1 准备工作1.1 引入依赖1.2 创建一个Tomcat的启动类 2 线程池技术回顾2.1 线程池的使用流程2.2 线程池的参数2.2.1 任务队列#xff08;workQueue#xff09;2.2.2 线程工厂#xff08;threadFactory#xff09;2.2.3 拒绝策略workQueue2.2.2 线程工厂threadFactory2.2.3 拒绝策略handler 2.3 功能线程池2.3.1 定长线程池FixedThreadPool2.3.2 定时线程池ScheduledThreadPool 2.3.3 可缓存线程池CachedThreadPool2.3.4 单线程化线程池SingleThreadExecutor2.3.5 对比 3 Tomcat逻辑3.1 请求3.1.1 单次请求3.1.2 多次请求3.1.3 线程池处理请求 3.2 处理socket连接3.2.1 http协议格式3.2.2 具体解析3.2.3 Request 3.3 请求类Request3.4 响应类Response3.4.1 请求体输出流ResponseOutputStream 3.5 servlet SJBServlet3.6 测试3.7 Tomcat的部署应用3.7.1 WebServlet 注解 和 web.xml 的区别3.7.2 目录存放3.7.3 部署3.7.4 部署测试 1 准备工作
1.1 引入依赖
dependenciesdependencygroupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion3.0.1/version/dependencydependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion3.8.1/versionscopetest/scope/dependency
/dependencies1.2 创建一个Tomcat的启动类
创建com.sjb.Tomcat
public class Tomcat {public void start(){}public static void main(String[] args) {Tomcat tomcatApplication new Tomcat();tomcatApplication.start();}
}2 线程池技术回顾
线程池的真正实现类是 ThreadPoolExecutor
2.1 线程池的使用流程
// 创建线程池
ThreadPoolExecutor threadPool new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {Overridepublic void run() {... // 线程执行的任务}
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP然后尝试停止所有的正在执行或暂停任务的线程并返回等待执行任务的列表2.2 线程池的参数
主要参数 corePoolSize必需) 核心线程数。默认情况下核心线程会一直存活但是当将 allowCoreThreadTimeout 设置为 true 时核心线程也会超时回收。 maximumPoolSize必需 线程池所能容纳的最大线程数。当活跃线程数达到该数值后后续的新任务将会阻塞 keepAliveTime必需 线程闲置超时时长。如果超过该时长非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时核心线程也会超时回收。 unit必需 指定 keepAliveTime 参数的时间单位。常用的有TimeUnit.MILLISECONDS毫秒、TimeUnit.SECONDS秒、TimeUnit.MINUTES分。 workQueue必需 任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。 threadFactory可选 线程工厂。用于指定为线程池创建新线程的方式。 handler可选 拒绝策略。当达到最大线程数时需要执行的饱和策略。
2.2.1 任务队列workQueue
任务队列是基于阻塞队列实现的即采用生产者消费者模式在 Java 中需要实现 BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现
ArrayBlockingQueue一个由数组结构组成的有界阻塞队列数组结构可配合指针实现一个环形队列。LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列在未指明容量时容量默认为 Integer.MAX_VALUE。PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列对元素没有要求可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系仅仅是按照优先级取任务。DelayQueue 类似于PriorityBlockingQueue是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口通过执行时延从队列中提取任务时间没到任务取不出来。SynchronousQueue 一个不存储元素的阻塞队列消费者线程调用 take() 方法的时候就会发生阻塞直到有一个生产者线程生产了一个元素消费者线程就可以拿到这个元素并返回生产者线程调用 put() 方法的时候也会发生阻塞直到有一个消费者线程消费了一个元素生产者才会返回。LinkedBlockingDeque 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO先进先出也可以像栈一样 FILO先进后出。LinkedTransferQueue 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体但是把它用在 ThreadPoolExecutor 中和 LinkedBlockingQueue 行为一致但是是无界的阻塞队列。
注意有界队列和无界队列的区别如果使用有界队列当队列饱和时并超过最大线程数时就会执行拒绝策略而如果使用无界队列因为任务队列永远都可以添加任务所以设置 maximumPoolSize 没有任何意义。
2.2.2 线程工厂threadFactory
线程工厂指定创建线程的方式需要实现 ThreadFactory 接口并实现 newThread(Runnable r) 方法。该参数可以不用指定Executors 框架已经为我们实现了一个默认的线程工厂
2.2.3 拒绝策略handler
当线程池的线程数达到最大线程数时需要执行拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。不过 Executors 框架已经为我们实现了 4 种拒绝策略
AbortPolicy默认丢弃任务并抛出 RejectedExecutionException 异常。CallerRunsPolicy由调用线程处理该任务。DiscardPolicy丢弃任务但是不抛出异常。可以配合这种模式进行自定义的处理方式。DiscardOldestPolicy丢弃队列最早的未处理任务然后重新尝试执行任务。
2.3 功能线程池
Executors已经为我们封装好了 4 种常见的功能线程池如下
定长线程池FixedThreadPool定时线程池ScheduledThreadPool 可缓存线程池CachedThreadPool单线程化线程池SingleThreadExecutor
2.3.1 定长线程池FixedThreadPool
源码
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable(),threadFactory);
}特点只有核心线程线程数量固定执行完立即回收任务队列为链表结构的有界队列。应用场景控制线程最大并发数。
使用示例
// 1. 创建定长线程池对象 设置线程池线程数量固定为3
ExecutorService fixedThreadPool Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 需执行的任务
Runnable task new Runnable(){public void run() {System.out.println(执行任务啦);}
};
// 3. 向线程池提交任务
fixedThreadPool.execute(task);2.3.2 定时线程池ScheduledThreadPool
源码
private static final long DEFAULT_KEEPALIVE_MILLIS 10L;public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue());
}public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue(), threadFactory);
}特点核心线程数量固定非核心线程数量无限执行完闲置 10ms 后回收任务队列为延时阻塞队列。应用场景执行定时或周期性的任务。
// 1. 创建 定时线程池对象 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 需执行的任务
Runnable task new Runnable(){public void run() {System.out.println(执行任务啦);}
};
// 3. 向线程池提交任务
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务2.3.3 可缓存线程池CachedThreadPool
源码
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueueRunnable());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueueRunnable(),threadFactory);
}特点无核心线程非核心线程数量无限执行完闲置 60s 后回收任务队列为不存储元素的阻塞队列。应用场景执行大量、耗时少的任务。
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 需执行的任务
Runnable task new Runnable(){public void run() {System.out.println(执行任务啦);}
};
// 3. 向线程池提交任务
cachedThreadPool.execute(task);2.3.4 单线程化线程池SingleThreadExecutor
源码
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable(),threadFactory));
}特点只有 1 个核心线程无非核心线程执行完立即回收任务队列为链表结构的有界队列。应用场景不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作如数据库操作、文件操作等。
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 需执行的任务
Runnable task new Runnable(){public void run() {System.out.println(执行任务啦);}
};
// 3. 向线程池提交任务
singleThreadExecutor.execute(task);2.3.5 对比 3 Tomcat逻辑
3.1 请求
3.1.1 单次请求
在com.sjb.Tomcat#start
public class Tomcat {public void start(){//socket连接TCPtry {ServerSocket serverSocket new ServerSocket(8080);Socket socket serverSocket.accept();processSocket(socket);}} catch (IOException e) {throw new RuntimeException(e);}}private void processSocket(Socket socket) {//处理socket请求}public static void main(String[] args) {Tomcat tomcatApplication new Tomcat();tomcatApplication.start();}
}使用了 ServerSocket 类来监听8080端口上的连接。
通过serverSocket.accept()阻塞监听8080端口一直等待直到有一个客户端连接请求到达。一旦有连接请求到达accept() 方法会返回一个新的 Socket 对象该对象表示服务器和客户端之间建立的连接。然后你就可以使用这个 Socket 对象来进行通信发送和接收数据。
每当有一个连接到达它会调用 processSocket() 方法来处理该连接。
3.1.2 多次请求
但是这个有个只处理单次请求我们需要加上一个while来不断地进行阻塞监听
try {ServerSocket serverSocket new ServerSocket(8080);while(true){Socket socket serverSocket.accept();processSocket(socket);}
} catch (IOException e) {throw new RuntimeException(e);
}但是这个是线性的一次只能处理一个
3.1.3 线程池处理请求
引入线程池提升并行处理能力
public class Tomcat {public void start(){//socket连接TCPtry {ExecutorService executorService Executors.newFixedThreadPool(20);ServerSocket serverSocket new ServerSocket(8080);while(true){Socket socket serverSocket.accept();executorService.execute(new SocketProcessor(socket));}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) {Tomcat tomcatApplication new Tomcat();tomcatApplication.start();}
}创建com.sjb.SocketProcessor实现Runnable
public class SocketProcessor implements Runnable{private Socket socket;public SocketProcessor(Socket socket) {this.socket socket;}Overridepublic void run() {processSocket(socket);}private void processSocket(Socket socket) {//处理socket}
}3.2 处理socket连接
3.2.1 http协议格式 tomcat需要按照这个格式进行解析这里先把他全部输出就不解析了。
在com.sjb.SocketProcessor#processSocket中
private void processSocket(Socket socket) {//处理sockettry {InputStream inputStream socket.getInputStream();byte[] bytes new byte[1024];//循环读取数据while (true){int read inputStream.read(bytes);if(read -1){break;}System.out.println(new String(bytes,0,read));}} catch (IOException e) {throw new RuntimeException(e);}
}访问http://localhost:8080/输出
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age0
sec-ch-ua: Not_A Brand;v8, Chromium;v120, Google Chrome;v120
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: Windows
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,image/apng,*/*;q0.8,application/signed-exchange;vb3;q0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q0.9
Cookie: Idea-7b055fc6c09dc312-ccaa-43d4-bf1f-95a39406b2e6; username-localhost-605242|1:0|10:1713800933|24:username-localhost-60524|196:eyJ1c2VybmFtZSI6ICJjMTdjZmFhYjA0MGQ0Njg3YWFjNzZiYzc1MmQ3Zjc0ZCIsICJuYW1lIjogIkFub255bW91cyBUaHlvbmUiLCAiZGlzcGxheV9uYW1lIjogIkFub255bW91cyBUaHlvbmUiLCAiaW5pdGlhbHMiOiAiQVQiLCAiY29sb3IiOiBudWxsfQ|d3
42be023b8e3ab274c8019dd4a41dc7f3102301b8faf7ea83435c06dd9b614b; _xsrf2|0e634e08|aaad0368070a9f2267481456f73e2dd6|1713800933; username-localhost-610852|1:0|10:1713801376|24:username-localhost-61085|204:eyJ1c2VybmFtZSI6ICJhM2IzOTQwNjhjYzM0OWM4OTYyYzUxODQ2Y2IwNzU1YiIsICJuYW1lIjogIkFub255bW91cyBDYWxsaXJyaG9lIiwgImRpc3BsYXlfbmFtZSI6ICJBbm9ueW1vdXMgQ2FsbGlycmhvZSIsICJpbml0aWFscyI6ICJBQyIsICJjb2xvciI6IG51bGx9|e49adff6d260df7ee49848807369b565e82e36906318b9546796c968f21a9361; username-localhost-611382|1:0|10:1713801443|24:username-localhost-61138|200:eyJ1c2VybmFtZSI6ICI4YzVkOWRkYTBmNGE0YTk1YjQ0MjFkMzYwZDRmNTFhMyIsICJuYW1lIjogIkFub255bW91cyBMeXNpdGhlYSIsICJkaXNwbGF5X25hbWUiOiAiQW5vbnltb3VzIEx5c2l0aGVhIiwgImluaXRpYWxzIjogIkFMIiwgImNvbG9yIjogbnVsbH0|587e609fe7fc575e84763d39c6fd8b9feb8cdd0da05046d03dec57ba58307aef3.2.2 具体解析
我们需要获得method、url、protocl来创建Request对象
访问http://localhost:8080/abc
//处理socket
try {InputStream inputStream socket.getInputStream();//解析请求头获得method、url、protocolbyte[] bytes new byte[1024];int read inputStream.read(bytes);String request new String(bytes, 0, read);String[] split request.split(\r\n);String[] split1 split[0].split( );String method split1[0];String url split1[1];String protocol split1[2];System.out.println(method: method);System.out.println(url: url);System.out.println(protocol: protocol);
} catch (IOException e) {throw new RuntimeException(e);
}输出
method: GET
url: /abc
protocol: HTTP/1.1请求头和请求体同理
3.2.3 Request
创建com.sjb.Request
public class Request {private String method;private String url;private String protocol;public Request(String method, String url, String protocol) {this.method method;this.url url;this.protocol protocol;}public String getMethod() {return method;}public String getUrl() {return url;}public String getProtocol() {return protocol;}
}
在com.sjb.SocketProcessor#processSocket中创建Request对象再进行之后的匹配servlet之后执行对应的doGet方法、doPost方法等等
//处理socket
try {InputStream inputStream socket.getInputStream();//解析请求头获得method、url、protocolbyte[] bytes new byte[1024];int read inputStream.read(bytes);String request new String(bytes, 0, read);String[] split request.split(\r\n);String[] split1 split[0].split( );String method split1[0];String url split1[1];String protocol split1[2];Request tomcatrequest new Request(method, url, protocol);//根据url找到对应的servlet} catch (IOException e) {throw new RuntimeException(e);
}3.3 请求类Request
Request类需要实现接口HttpServletResponse里面有很多方法我们避免麻烦创建一个com.sjb.AbstractHttpServletResponse类实现接口我们的Request类只需要继承AbstractHttpServletResponse即可
创建com.sjb.AbstractHttpServletResponse
public class AbstractHttpServletRequest implements HttpServletRequest {Overridepublic String getAuthType() {return null;}Overridepublic Cookie[] getCookies() {return new Cookie[0];}.................创建com.sjb.Request
public class Request extends AbstractHttpServletRequest {private String method;private String url;private String protocol;private Socket socket;public Socket getSocket() {return socket;}public Request(String method, String url, String protocol, Socket socket) {this.method method;this.url url;this.protocol protocol;this.socket socket;}public String getMethod() {return method;}public StringBuffer getRequestURL() {return new StringBuffer(url);}public String getProtocol() {return protocol;}
}public Request(String method, String url, String protocol, Socket socket)一个socket对应一个Request对应一个response
3.4 响应类Response
与3.3类似
创建com.sjb.AbstractHttpServletResponse
public class AbstractHttpServletResponse implements HttpServletResponse {Overridepublic void addCookie(Cookie cookie) {}Overridepublic boolean containsHeader(String s) {return false;}...................创建com.sjb.Response
public class Response extends AbstractHttpServletResponse {private String messageOK;private int status200;private MapString,String headers new HashMap();private Request request;private OutputStream socketOutputStream;private ResponseOutputStream responseOutputStreamnew ResponseOutputStream();public Response(Request request) {this.request request;try {this.socketOutputStream request.getSocket().getOutputStream();} catch (IOException e) {throw new RuntimeException(e);}}Overridepublic void setStatus(int status, String message) {this.status status;this.message message;}Overridepublic int getStatus() {return status;}Overridepublic void addHeader(String s, String s1) {headers.put(s, s1);}Overridepublic ResponseOutputStream getOutputStream() throws IOException {return responseOutputStream;}public void complete() {//发送响应sendResponseLine();sendResponseHeader();sendResponseBody();}private void sendResponseBody() {try {byte[] body getOutputStream().getBody();int pos getOutputStream().getPos();socketOutputStream.write(body, 0, pos);} catch (IOException e) {throw new RuntimeException(e);}}private void sendResponseHeader() {try {for (Map.EntryString, String entry : headers.entrySet()) {String key entry.getKey();String value entry.getValue();socketOutputStream.write(key.getBytes());socketOutputStream.write(: .getBytes());socketOutputStream.write(value.getBytes());socketOutputStream.write(\r\n.getBytes());}socketOutputStream.write(\r\n.getBytes());} catch (IOException e) {throw new RuntimeException(e);}}private void sendResponseLine() {try {socketOutputStream.write(request.getProtocol().getBytes());socketOutputStream.write( .getBytes());socketOutputStream.write(String.valueOf(status).getBytes());socketOutputStream.write( .getBytes());socketOutputStream.write(message.getBytes());socketOutputStream.write(\r\n.getBytes());} catch (IOException e) {throw new RuntimeException(e);} }
}属性
int status状态码String message说明MapString,String header存储响应头例如一些长度、编码信息等Request request一个response对应一个requestOutputStream socketOutputStreamsocket的整个输出流ResponseOutputStream responseOutputStream响应体的输出流因为响应体可能有很多所以需要用一个输出流进行缓存并且如果响应失败的话也只有响应行和响应头返回响应体不返回。
private ResponseOutputStream responseOutputStreamnew ResponseOutputStream();一开始就创建好响应响应体的输出流保证public ResponseOutputStream getOutputStream()拿到的都是同一个响应体的输出流
3.4.1 请求体输出流ResponseOutputStream
因为响应体可能有很多所以需要用一个输出流进行缓存并且如果响应失败的话也只有响应行和响应头返回响应体不返回。
创建com.sjb.ResponseOutputStream
public class ResponseOutputStream extends ServletOutputStream {public byte[] getBody() {return body;}public int getPos() {return pos;}private byte[] bodynew byte[1024];private int pos0;Overridepublic void write(int b) throws IOException {//响应体body[pos](byte)b;}
}3.5 servlet SJBServlet
根据Request创建对应的Response并且创建servlet调用servlet的service方法,匹配doGet或者doPost
在com.sjb.SocketProcessor#processSocket中
try {InputStream inputStream socket.getInputStream();//解析请求头获得method、url、protocolbyte[] bytes new byte[1024];int read inputStream.read(bytes);String request new String(bytes, 0, read);String[] split request.split(\r\n);String[] split1 split[0].split( );String method split1[0];String url split1[1];String protocol split1[2];Request tomcatrequest new Request(method, url, protocol, socket);//根据url找到对应的servletResponse response new Response(tomcatrequest);SJBServlet servlet new SJBServlet();//调用servlet的service方法,匹配doGet或者doPostservlet.service(tomcatrequest, response);//输出响应response.complete();
}创建com.sjb.SJBServletservice方法自动给匹配请求方法我们只需要重写对应的doGet、doPost方法即可
public class SJBServlet extends HttpServlet {Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println(req.getMethod());resp.addHeader(Content-Type, text/html;charsetutf-8);resp.addHeader(Content-Length, 15);resp.getOutputStream().write(Hello World sjb.getBytes());}
}在doGet方法调用response.addHeader()中设置请求头编码方式、请求体长度等。
也不一定在doGet方法设置请求头亦可以在com.sjb.Response#sendResponseHeader中设置请求头
private void sendResponseHeader() {try {if(!headers.containsKey(Content-Type)){headers.put(Content-Type, text/html;charsetutf-8);}if(!headers.containsKey(Content-Length)){headers.put(Content-Length, String.valueOf(responseOutputStream.getPos()));}resp.getOutputStream().write(Hello World sjb.getBytes());设置请求体。
最后调用response.complete();写请求行、请求头、请求体。完成输出流的输出。
3.6 测试
访问 3.7 Tomcat的部署应用
在tomcat中有一个专门的目录webapps用来存放tomcat的项目一个tomcat项目中的classes中可以有多个servlet
如果要部署tomcat需要在对应的servlet上加上WebServlet 注解
在SJBServlet上添加注解
WebServlet(urlPatterns {/sjb}
)
public class SJBServlet extends HttpServlet {public SJBServlet() {}WebServlet 属于类级别的注解标注在继承了 HttpServlet 的类之上。常用的写法是将 Servlet 的相对请求路径即 value直接写在注解内WebServlet(urlPatterns {“/sjb”})。WebServlet(urlPatterns {“/sjb”})省略了 urlPatterns 属性名 如果 WebServlet 中需要设置多个属性则属性之间必须使用逗号隔开。 通过实现 Serlvet 接口或继承 GenericServlet 创建的 Servlet 类无法使用 WebServlet 注解。 使用 WebServlet 注解配置的 Servlet 类不要在 web.xml 文件中再次配置该 Servlet 相关属性。若同时使用 web.xml 与 WebServlet 配置同一 Servlet 类则 web.xml 中 的值与注解中 name 取值不能相同否则容器会忽略注解中的配置。
3.7.1 WebServlet 注解 和 web.xml 的区别
使用 web.xml 或 WebServlet 注解都可以配置 Servlet WebServlet 注解配置 Servlet 优点WebServlet 直接在 Servlet 类中使用代码量少配置简单。每个类只关注自身业务逻辑与其他 Servlet 类互不干扰适合多人同时开发。缺点Servlet 较多时每个 Servlet 的配置分布在各自的类中不便于查找和修改。 web.xml 配置文件配置 Servlet 优点集中管理 Servlet 的配置便于查找和修改。缺点代码较繁琐可读性不强不易于理解。
3.7.2 目录存放
要将重新编译好的SJBServlet.class放入Tomcat/webapps/hello/classes/com/sjb目录下并且删除之前的SJBServlet.class 所以在com.sjb.SocketProcessor#processSocket中就不能直接调用SJBServlet需要对环境进行解析找到对应的Servlet
3.7.3 部署
在com.sjb.Tomcat#main中需要部署apps
public static void main(String[] args) {Tomcat tomcatApplication new Tomcat();tomcatApplication.deployApps();tomcatApplication.start();
}deployApps() 获取当前父项目目录下所有的Tomcat/webapps下的所有目录
private void deployApps() {//拿到当前模块的目录的子目录//webapps D:\Code\JavaCode\handwith-Spring\handwith-Spring\Tomcat\webappsFile webapps new File(System.getProperty(user.dir),Tomcat/webapps);for(String app: webapps.list()){//app: hellodeployApp(webapps,app);}
}针对app: hello进行部署deployApp(webapps,app)
webapps父目录app子目录context负责存放当前项目。一个context项目下可能有多个appservlet
private void deployApp(File webapps,String app) {Context context new Context(app);//appDir D:\Code\JavaCode\handwith-Spring\handwith-Spring\Tomcat\webapps\helloFile appDir new File(webapps, app);//classesDir D:\Code\JavaCode\handwith-Spring\handwith-Spring\Tomcat\webapps\hello\classesFile classesDir new File(appDir, classes);ListFile files getAllFilePath(classesDir);for(File file:files){//name: D:\Code\JavaCode\handwith-Spring\handwith-Spring\Tomcat\webapps\hello\classes\com\sjb\SJBServlet.classString namefile.getPath();//name: com\sjb\SJBServlet.classnamename.replace(classesDir.getPath()\\,);//name: com\sjb\SJBServletnamename.replace(.class,);//name: com.sjb.SJBServletnamename.replace(\\,.);System.out.println(name);//加载类加载器try {WebappClassLoader webappClassLoader new WebappClassLoader(new URL[]{classesDir.toURL()});Class? aClass webappClassLoader.loadClass(name);if(HttpServlet.class.isAssignableFrom(aClass)){if(aClass.isAnnotationPresent(WebServlet.class)){WebServlet webServlet aClass.getAnnotation(WebServlet.class);//urlPatterns[/sjb]String[] urlPatterns webServlet.urlPatterns();for(String urlPattern:urlPatterns){context.addServlet(urlPattern, (Servlet) aClass.newInstance()); }}}} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (MalformedURLException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}}contextMap.put(app,context);}ListFile files getAllFilePath(classesDir);获取hello\classes下所有的文件递归搜索
private ListFile getAllFilePath(File classesDir) {ListFile result new ArrayList();File[] files classesDir.listFiles();if(files!null){for(File file:files){if(file.isDirectory()){result.addAll(getAllFilePath(file));}else{result.add(file);}}}return result;}获取到类名namecom.sjb.SJBServlet后通过类加载器进行加载。
这里不能使用Class? aClass1 Thread.currentThread().getContextClassLoader().loadClass(name);因为这里name是webapps目录下的com.sjb.SJBServlet而用Thread.currentThread().getContextClassLoader().loadClass()只能加载当前项目下的target目录下的类。aClass.newInstance()通过类加载器new一个对象和url一起组成Entry放入context下的map里最后将context放入context的map里
因此需要创建一个新的类加载器com.sjb.WebappClassLoader继承URLClassLoader通过url来进行加载
public class WebappClassLoader extends URLClassLoader {public WebappClassLoader(URL[] urls) {super(urls);}
}HttpServlet.class.isAssignableFrom(aClass)然后再判断这个类是不是HttpServletaClass.isAnnotationPresent(WebServlet.class)判断有没有WebServlet然后获取注解的内容并且获取url,然后把他存入string,context的map中context用来存放每个项目/应用hello每个项目中可能有多个servlet
创建com.sjb.Context
public class Context {private String appName;private MapString, Servlet servletMapnew HashMap();public Context(String appName) {this.appName appName;}public void addServlet(String url, Servlet servlet){servletMap.put(url,servlet);}public Servlet getServletByUrl(String url){return servletMap.get(url);}
}在com.sjb.Tomcat中创建一个MapString, Context的map
public class Tomcat {public MapString, Context getContextMap() {return contextMap;}private MapString,Context contextMapnew HashMap();这样就在解析阶段就已经找到了对应的servlet
在com.sjb.SocketProcessor#processSocket中
Request tomcatrequest new Request(method, url, protocol, socket);
Response response new Response(tomcatrequest);
//根据url找到对应的servlet
// url: /hello/sjb
urlurl.substring(1);
// parts: [abc]
String[] parts url.split(/);
String appNameparts[0];
Context context tomcat.getContextMap().get(appName);
Servlet servlet context.getServletByUrl(/parts[1]);
servlet.service(tomcatrequest, response);3.7.4 部署测试
访问localhost:8080/hello/sjb成功访问到