手写Tomcat实现基本功能
手写Tomcat:实现基本功能
首先,Tomcat是一个软件,所有的项目都能在Tomcat上加载运行,Tomcat最核心的就是Servlet集合,本身就是HashMap。Tomcat需要支持Servlet,所以有servlet底层的资源:HttpServlet抽象类、HttpRequest和HttpResponse,否则我们无法新建Servlet。
这样我们就可以在webapps写项目了,一个项目有两大资源:servlet资源和静态资源,servlet本身是java类,我们让它调用doGet和doPost方法,必须继承HttpServlet,同时也需要@WebServlet注解,那么在Tomcat中就必须要有@WebServlet注解的实现,如果没有@WebServlet,我们就无法拿到相应的注解。这样我们就能成功搭建起Servlet资源。
如果servlet集合在Tomcat启动之前实现,也就是main()方法之前,需要使用static代码块去实现;如果servlet集合在Tomcat启动之后实现,也就是main()方法之后,再去加载servlet容器。
当Http请求打过来之后,先打到socket上,处理Http请求,其本身就是连接器,从请求上能获取到请求路径和请求方法。先获取到请求路径,判断servlet容器中是否含有相同路径,如果有,再获取请求方法,判断是doGet还是doPost。
一、Servlet资源准备
1、Servlet接口:
- Servlet接口的作用: 实现Servlet生命周期 ,定义init()、service(request,response)和destroy()方法。
- 接口中的方法全都是抽象方法,只定义不实现,方法头默认为public abstract。
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;
public interface Servlet {
void init();
void service(HttpServletRequest request, HttpServletResponse response) throws Exception;
void destroy();
}
2、GenericServlet抽象类
- GenericServlet抽象类实现Servlet接口中的 init()方法 和 destroy()方法 。
为什么GenericServlet类是抽象类?
我们知道当一个普通类继承一个接口时,需要对接口中的所有方法进行实现,但这里我们不实现Servlet接口中的service(request,response)方法,因此我们需要把GenericServlet类变成抽象类,因为抽象类不必实现接口中的全部方法。
public abstract class GenericServlet implements Servlet {
public void init(){
System.out.println("初始化成功");
}
public void destroy(){
System.out.println("销毁成功");
}
}
3、HttpServlet抽象类
- HttpServlet抽象类实现Servlet接口中的 service(request,response) 方法
为什么HttpServlet类也是抽象类?
之所以HttpServlet类也是抽象类,是因为普通类中必须对方法进行实现,而在HttpServlet类中,实现service(request,response)方法的逻辑为:如果接收到GET请求,那么执行doGet方法;如果接收到POST请求,那么执行doPost方法。在此我们只需要对doGet和doPost方法进行定义不需要实现,所以HttpServlet只有成为抽象类,才不必实现doGet和doPost方法。
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;
public abstract class HttpServlet extends GenericServlet {
//实现Servlet生命周期的service方法
public void service(HttpServletRequest request, HttpServletResponse response) throws Exception {
//如果接收到GET请求,那么执行doGet方法
if(request.getMethod().equals("GET")){
doGet(request,response);
}
//如果接收到POST请求,那么执行doPost方法
else if(request.getMethod().equals("POST")){
doPost(request,response);
}
}
protected abstract void doGet(HttpServletRequest request,HttpServletResponse response) throws Exception;
protected abstract void doPost(HttpServletRequest request,HttpServletResponse response)throws Exception;
}
4、HttpRequest类
- HttpRequest类实现 请求路径 和 请求方法 的获取与输出。
public class HttpServletRequest {
private String path;
private String method;
public String getPath(){
return path;
};
public void setPath(String path){
this.path = path;
}
public String getMethod(){
return method;
}
public void setMethod(String method){
this.method = method;
}
}
5、HttpResponse类
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class HttpServletResponse {
private OutputStream outputStream;
public HttpServletResponse(OutputStream outputStream){
this.outputStream = outputStream;
}
public void writeServlet(String context) throws IOException{
outputStream.write(context.getBytes());
}
}
二、注解
- @Retention():表示该注解的作用阶段。阶段分三种:源代码阶段、类对象阶段和运行时阶段。
- @Target():表示该注解的作用范围。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//作用阶段
@Retention(RetentionPolicy.RUNTIME)//作用在运行阶段
//作用范围:类、方法......
@Target(ElementType.TYPE)//作用在类上
public @interface WebServlet {
String urlMapping() default "";//自定义的servlet路径
}
三、工具类
1、SearchClassUtil类
SearchUtil类的功能:扫描com.qcby.webapps包下的文件,获取全路径名
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/*
* 扫描com.qcby.webapps包下的文件,获取全路径名
* */
public class SearchClassUtil {
public static List<String> classPaths = new ArrayList<String>();
public static List<String> searchClass(){
//需要扫描的包名
String basePack = "com.qcby.webapps";
//将获取到的包名转换为路径
String classPath = SearchClassUtil.class.getResource("/").getPath();
basePack = basePack.replace(".", File.separator);
String searchPath = classPath + basePack;
doPath(new File(searchPath),classPath);
//这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
return classPaths;
}
/**
* 该方法会得到所有的类,将类的绝对路径写入到classPaths中
* @param file
*/
private static void doPath(File file,String classpath) {
if (file.isDirectory()) {//文件夹
//文件夹我们就递归
File[] files = file.listFiles();
for (File f1 : files) {
doPath(f1,classpath);
}
} else {//标准文件
//标准文件我们就判断是否是class文件
if (file.getName().endsWith(".class")) {
String path = file.getPath().replace(classpath.replace("/","\\").
replaceFirst("\\\\",""),"").replace("\\",".").
replace(".class","");
//如果是class文件我们就放入我们的集合中。
classPaths.add(path);
}
}
}
public static void main(String[] args) {
List<String> classes = SearchClassUtil.searchClass();
for (String s: classes) {
System.out.println(s);
}
}
}
2、ResponseUtil类
public class ResponseUtil {
public static final String responseHeader200 = "HTTP/1.1 200 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n";
public static String getResponseHeader404(){
return "HTTP/1.1 404 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + "404";
}
public static String getResponseHeader200(String context){
return "HTTP/1.1 200 \r\n"+
"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + context;
}
}
四、Servlet类
1、LoginServlet类
import com.qcby.Servlet.HttpServlet;
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;
import com.qcby.Util.ResponseUtil;
import com.qcby.Util.WebServlet;
import java.io.IOException;
@WebServlet(urlMapping = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("我是LoginServlet下的doGet方法");
response.writeServlet(ResponseUtil.getResponseHeader200("hello"));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
System.out.println("我是LoginServlet下的doPost方法");
}
}
五、ServletConfigMapping类
ServletConfigMapping类是Servlet容器,是Tomcat中最核心的部分,其本身是一个HashMap,其功能为:将路径和对象写入Servlet容器中。
import com.qcby.Servlet.HttpServlet;
import com.qcby.Util.SearchClassUtil;
import com.qcby.Util.WebServlet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ServletConfigMapping {
//创建servlet容器
public static Map<String, HttpServlet> servletMap = new HashMap<>();
//将Path和对象写入servlet容器当中
//采用static代码块 在启动tomcat之前实现
static{
List<String> classesPath = SearchClassUtil.searchClass();
for(String path:classesPath){
try{
//加载类
Class clazz = Class.forName(path);
//获取注解
WebServlet webServlet = (WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
HttpServlet servlet = (HttpServlet) clazz.newInstance();
servletMap.put(webServlet.urlMapping(),servlet);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
六、MyTomcat
import com.qcby.Servlet.HttpServlet;
import com.qcby.Servlet.req.HttpServletRequest;
import com.qcby.Servlet.req.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyTomcat {
static HttpServletRequest request = new HttpServletRequest();
public static void main(String[] args) throws Exception {
//ServletConfigMapping.init(); // tomcat启动,但是还没有监听之前,将信息加载入servlet容器当中
//1.创建serversocket对象,持续监听8080端口
ServerSocket serverSocket = new ServerSocket(8888);
while (true){
//accept():阻塞监听 ,当代码执行到这一样,如果没有数据到来,那么循环阻塞在这里。如果有数据到来,就继续向下执行
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
HttpServletResponse response = new HttpServletResponse(outputStream);
int count = 0;
while (count == 0){
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String Context = new String(bytes);
System.out.println(Context);
//解析数据
if(Context.equals("")){
System.out.println("你输入了一个空请求");
}else {
String firstLine = Context.split("\\n")[0];
request.setMethod(firstLine.split("\\s")[0]);
request.setPath(firstLine.split("\\s")[1]);
}
//如果我们访问的路径在servlet容器当中存在
if(ServletConfigMapping.servletMap.containsKey(request.getPath())){
HttpServlet servlet = ServletConfigMapping.servletMap.get(request.getPath());
servlet.service(request,response);
}
}
}
}