1,类对象的获取,注解的实现
2,请求体,响应体和http协议
3,方法的实现和服务器的启动
一,类对象的获取与注解的实现
在web容器中我们不会像平常使用new来实现类的实例化,这里通过Java的反射来获取类对象,通过Class获取构造方法实时创建对象。
下一个问题就是我们怎么知道要创建的对象是什么,这就和注解有关了。注解写在类和方法的前面,作为一个标识,这个标识就作用于识别是否利用反射调用了。下面就是一个注解的实现:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebAPI {
String value();
}
可以看见注解中也可以包含参数,除了注解名注解中的参数也是一个重要的识别标准。通过扫描特定目录下的文件,识别注解是否需要进行调用。下面就来展示如何将反射和注解结合在一起:
Class<?> clazz=Class.forName(webPath+"."+className);
if(clazz.isAnnotationPresent(WebAPI.class)){
Object obj = clazz.newInstance();
Annotation[] annotations = clazz.getAnnotations();
for (int j = 0; j < annotations.length; j++) {
Annotation a = annotations[j];
if (a instanceof WebAPI webAPI) {
String apiPath = webAPI.value();
apiList.put(apiPath, obj);//将方法名称和对象存入容器
System.out.println("加载成功:" + apiPath);
}
}
}
先获取对象存入clazz中,利用识别类是否含有特定的注解(也就是识别注解名)再实例化,一个类中会有很多个标签所以先用数组将类的标签存入,接着遍历找到我们需要注解,再获取注解里的属性存入容器中,这个属性通常就是需要调用的方法的路径。
这里的存入容器,这一步也和tomcat中servlet容器的管理类似,实现了路径到实现的映射。
二,请求体,响应体和http协议
Web 容器之所以能和浏览器通信,是因为双方都遵守HTTP/1.1协议。想要获取浏览器发送的请求就需要解析http协议,也就是要使用请求体来作为解析装置:
class Request {
private String method;
private String path;
private HashMap<String,String> map=new HashMap<>();
public Request(BufferedReader br) throws IOException {
String data = br.readLine();
String[] datas = data.split(" ");
this.method=datas[0];
String[] paths = datas[1].split("\\?");
this.path = paths[0];
if(paths.length>1){
String[] parms = paths[1].split("&");
for (int i = 0; i < parms.length; i++) {
String[] parm = parms[i].split("=");
this.map.put(parm[0],parm[1]);
}
}
String header;
while ((header = br.readLine()) != null && !header.isEmpty()) {
System.out.println(header);
}
}
public String get(String key){
return this.map.get(key);
}
public String getMethod(){
return this.method;
}
public String getPath(){
return this.path;
}
}
通过拆分字符知道发来的请求,这里所使用的map是用于存储http协议中的参数。响应体则是将对应的字节流写作HTTP/1.1的格式让浏览器能够识别:
class Response {
private BufferedWriter bw;
public Response(BufferedWriter bw){
this.bw=bw;
}
public void write(String data) throws IOException {
String head = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain;charset=utf-8\r\n" +
"Content-Length: " + data.getBytes().length + "\r\n" +
"\r\n" + data;
bw.write(head);
bw.flush();
}
}
3,方法的实现和服务器的启动
定义了一个抽象基类。它的核心作用是分发。实现如下:
public abstract class Dispatcher {
public void service(Request req,Response res) throws Exception {
String method=req.getMethod();//查看网页发送的请求类型
if(method.equals("GET")) {
get(req, res);
}
if(method.equals("POST")){
post(req,res);
}
}
public abstract void get(Request req,Response res);
public abstract void post(Request req,Response res);
}
首先通过服务器获取请求类型(如GET和POST),从而决定调用哪个方法。继承这个抽象类的子类,只需要重写get或post即可处理具体的业务。下面是其中的一个子类:
@WebAPI(value = "/login")
public class Login extends Dispatcher {
@Override
public void get(Request req, Response res) {
String name=req.get("name");
String psw=req.get("password");
try {
if (name.equals("admin") && psw.equals("123")) {
res.write("登录成功");
System.out.println("登录成功");
} else {
res.write("登录失败");
System.out.println("登录失败");
}
System.out.println(name+" "+psw);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void post(Request req, Response res) {
}
}
最后则是服务器的启动:
public class Webserver {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=new ServerSocket(8080);
Servlet servlet=new Servlet();
while(true){
Socket socket=serverSocket.accept();
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
Request req=new Request(reader);
Response res=new Response(bw);
String path=req.getPath();
if(servlet.getServlet(path)!=null){
Class<?> clazz=servlet.getServlet(path).getClass();
try{
Method method = clazz.getMethod("service", Request.class, Response.class);
method.invoke(servlet.getServlet(path), req, res);
}catch (Exception e){
throw new RuntimeException(e);
}
}
socket.close();
}
}
}
先和浏览器建立连接:通过new ServerSocket(8080)开启服务器,不断通过accept() 阻塞等待浏览器的访问。当请求进来时,服务器实时创建Request 和 Response 实例。根据解析出的 path,从 Servlet 容器(即我们第一步存入的 apiList)中取出对应的对象。服务器并不提前知道你会调用哪个方法,它通过clazz.getMethod("service", ...) 地找到 service 方法,并使用 method.invoke() 实现方法的调用。