简单的JAVAEE框架
注意:本次框架练习是为了了解tomcat的框架底层代码
一、解析web.xml文件
package cn.servlet; abstract class LoadConfig { //缺省 不允许外包访问,抽象 不允许实例化 不能被继承 private static Map<String,String > config; private LoadConfig(){ } static { config = new HashMap<String,String>(); load(); } /** * 读取配置文件的方法 */ public static void load(){ SAXBuilder buid = new SAXBuilder(); try { Document doc = buid.build(new FileReader(new File("WEB-INF/web.xml"))); Element root = doc.getRootElement(); XPath servletPath = XPath.newInstance("//servlet"); XPath servletMappingPath = XPath.newInstance("//servlet-mapping"); List<Element> servlets = servletPath.selectNodes(root); List<Element> servletMappings = servletMappingPath.selectNodes(root); for(Element map : servletMappings){ //获得各个键所对应的文本 String servletName = map.getChildText("servlet-name"); String servletMappingName = map.getChildText("url-pattern"); for(Element s : servlets){ if(servletName.equals(s.getChildText("servlet-name"))){ String servletClass = s.getChildText("servlet-class"); config.put(servletMappingName,servletClass); } } } } catch (JDOMException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /**取Map方法 * * @return 返回读取完的Map */ public static Map<String,String> getConfig(){ return config; } }
本过程是在cn.servlet包下进行的,该类为缺省的,抽象的不允许别人访问,与不允许被实例化,由于于配置文件只需读一次,所以生成Map和执行load()方法都写在静态块中,load()方法为读取web.xml文件方法。getConfig()是返回读取好的class和url键值对方法。
二、封装请求与响应
1.写出请求响应接口
先是写出请求与响应的接口,即HttpServletRequest和HttpServletResponse两个接口
package cn.servlet.http;
/**
* 对请求做封装
*/
public interface HttpServletRequest {
String getMethod(); //获取请求方式
String getRequestURI(); //获取URI
String getProtocol(); //获得协议
String[] getHeads(); //获取所有的请求头
String getHead(String key); //获取请求头的键
String getParameter(String name); //返回?aaa=bbb& 中的输入aaa返回bbb 获得前端数据
}
去看看请求报文就知道为什么要有这些方法
package cn.servlet.http;
import java.io.PrintWriter;
/**
* 对响应进行封装
*/
public interface HttpServletResponse {
PrintWriter getWriter();
}
2.写所有控制器的父类
当写到这里就发现了一个问题,当我们在浏览器搜索栏搜索时要访问login界面类时,需要通过输入下图的/login,但这也牵扯到了一些JAVA类的运行,所以我们的框架会自动解析web.xml,找到URL所对应的class,进行反射实例化对象进而执行逻辑。
但是我们又会遇到问题,在别人使用我们代码时,不同的人写的类大有不同,我们怎么能解决这个问题 ?
当然是进行写一个所有控制器的父类,当别人想用我框架时就要进行继承该类。
本次将Httpservlet作为最终父类。
package cn.servlet.http;
import cn.servlet.ServletException;
import java.io.IOException;
//这里进行了简化,真正框架本应该是这种结构
//Servlet
// –GenericServlet
// –HttpServlet
// –自己的servlet
//太过复杂,本次就当HttpServlet是Servlet 即所有控制器的父类
public class HttpServlet {
/**
* 当我的对象被实例化时第一个执行的方法(初始化方法)
*/
public void init(){
}
/**
* 你只需自己所写的业务逻辑,不需要客户进行处理异常
* 一般不要覆盖该方法
*/
public void service(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
if(request.getMethod().equalsIgnoreCase("get")){
doGet(request, response);
}else{
doPost(request, response);
}
}
public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
}
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
}
/**
*框架死亡前去调用
*/
public void destroy(){
}
}
对于这里发现service类处理异常时多了一个异常为ServletException,这是为什么呢?
因为我们框架在这里我们遇到的异常五花八门,我们不方便进行处理,所以要抛出到我们写的专门处理异常的类。
package cn.servlet;
/**
* 直接继承throwable可以处理所有java异常
*/
public class ServletException extends Throwable{
public ServletException(){
}
public ServletException(String msg){
super(msg);
}
}
3.实现请求响应接口
请求实现类
package cn.servlet.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
/**
* 请求的实现子类
*/
public class HttpServletRequestImpl implements HttpServletRequest {
private BufferedReader br;
private String line; //请求报文第一行
private String[] heads; //第一行按照“ ”做拆分
private String params; //参数
private Map<String, String> paramsMap; //请求头中的键值对
private String requestHead;
private Map<String, String> headMap; //请求体中的键值对
public HttpServletRequestImpl(BufferedReader br) throws IOException {
this.br = br;
this.line = br.readLine();
this.heads = line.split(" ");
this.requestHead = line;
headMap = new HashMap<String, String>();
paramsMap = new HashMap<String, String>();
while (true) {
line = br.readLine();
if (line.matches("^\\s*$")) {
break;
}
String[] temp = line.split(": ");
headMap.put(temp[0], temp[1]);
}
params = "";
if (heads[1].contains("?")) {
params = heads[1].substring(heads[1].indexOf("?") + 1);
params = URLDecoder.decode(params, "UTF-8");
params = params + "&";
setParams(params);
}
if (heads[0].equalsIgnoreCase("post")) {
//这里用字节流
char[] c = new char[1024 * 1024];
int len = br.read(c);
String s = new String(c, 0, len);
params = URLDecoder.decode(params, "UTF-8");
params = params + "&";
setParams(params);
}
}
public void setParams(String params) {
String[] pams = params.split("&");
for (String s : pams) {
String[] parts = s.split("=");
paramsMap.put(parts[0], parts[1]);
}
}
@Override
public String getMethod() {
return this.heads[0];
}
@Override
public String getRequestURI() {
return this.heads[1].contains("?") ? this.heads[1].substring(0, this.heads[1].indexOf("?")) : this.heads[1];
}
@Override
public String getProtocol() {
return this.heads[2];
}
@Override
public String[] getHeads() {
return heads;
}
@Override
public String getHead(String key) {
return this.headMap.get(key);
}
@Override
public String getParameter(String name) {
return this.paramsMap.get(name);
}
}
看了上面的代码,我们发现了好多的字符串拆分,我们思考一下是为什么?
我们是服务器,客户端想请求我们的文件是需要发送请求报文的,并且现在的报文一般都是要符合http协议的,所以我们知道请求报文长啥样,我们就需要根据报文模版对报文进行解析,来获得有用的信息。所以我们才需要进行如此多的字符串拆分。
响应实现类
package cn.servlet.http;
import java.io.PrintWriter;
public class HttpServletResponseImpl implements HttpServletResponse{
private PrintWriter out;
/**响应封装的实现类
*
* @param out 输出响应的打印流
* @param request 请求(我要知道请求是什么协议,我响应才知道是什么协议)
* @param stat 服务器的状态
*/
public HttpServletResponseImpl(PrintWriter out,HttpServletRequest request,String stat){
this.out = out;
out.println(request.getProtocol()+" "+stat+" OK\r\nContent-type: text/html; charset=UTF-8\r\n\r\n");
}
@Override
public PrintWriter getWriter() {
return out;
}
}
对于响应实现类来说,实现就会相对简单,我们只需获取当前的请求报文的协议是什么,用户所请求的资源是什么,我们响应后的状态是什么,再以相同的协议将这些数据返回给客户就行。
三、写核心调度类
之前写的类都是为这个类所服务的,他是框架的核心,本次训练核心调度类为WebService
1.核心调度类WebService
我们已经知道,其他类都是为我这个类做辅助的,即为我提供各种数据。在这个类中我们只需要作为服务器,使用套接字监听固定端口就行。在有人连接我时,只需要进行初始化,开一个线程进行接收数据。
package cn.servlet;
import cn.servlet.http.HttpServlet;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
class WebService {
private ServerSocket ss;
private Socket s;
private static Map<String, HttpServlet> servlets;
private boolean bool;
static {
servlets = new HashMap<String, HttpServlet>();
}
static Map<String, HttpServlet> getServlets() {
return servlets;
}
public WebService() {
try {
bool = true;
ss = new ServerSocket(9001);
System.out.println("服务器已启动,正在监视9001端口");
} catch (IOException e) {
e.printStackTrace();
}
init();
}
public void seBoolean(boolean bool) {
this.bool = bool;
}
private void init() {
while (bool) {
try {
s = ss.accept();
new SessionThread(s).start();
} catch (IOException e) {
e.printStackTrace();
}
}
Set<String> sets = servlets.keySet();
for (String key : sets) {
servlets.get(key).destroy();
sets.remove(key);
}
}
}
注意:在启动线程时While(bool)是当我服务器关闭时将bool赋值为false。之后就会执行:将servets 中所有的值放在sets 集合中,进行遍历销毁,再移出集合。
2.会话线程Sessionthread
这就是为核心调度类所调控的与客户进行交互的线程。
这个类也不难理解,思路如下:
- 需要获得套接字与用户之间进行交互
- 需要获得已解析所约定的web.xml文件
- 需要输入输出流,知道用户输入的是什么,我输出给用户是什么
- 要判断请求报文是否合法
- 通过请求报文获得uri,通过Config,按键取值获得获得类的路径
- 反射获得servlet对象
- 生成响应报文
- 执行servlet.init() servlet.service() servlet.distory() 方法
- 关闭流
package cn.servlet;
import cn.servlet.http.*;
import java.io.*;
import java.net.Socket;
import java.util.Map;
public class SessionThread extends Thread {
private Socket s;
private BufferedReader br;
private PrintWriter out;
private static Map<String, String> config;
private static Map<String, HttpServlet> servlets;
static {
config = LoadConfig.getConfig();
servlets = WebService.getServlets();
}
public SessionThread(Socket s) {
this.s = s;
}
public void run() {
HttpServletRequest request = null;
HttpServletResponse response = null;
HttpServlet servlet = null;
BufferedReader brFile = null;
try {
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()), true);
request = new HttpServletRequestImpl(br);
if (request.getHeads().length != 3) {
return;
}
if (!(request.getMethod()).equalsIgnoreCase("get") || (request.getMethod()).equalsIgnoreCase("post")) {
return;
}
if (!(request.getRequestURI().startsWith("/"))) {
return;
}
String uri = request.getRequestURI();
String classPath = config.get(uri);
//如果之前获得过该客户的对象,只需通过用户进行请求的uri进行按键取值,不需要再实例化了
//如果没实例化就进行反射实例化对象
if (classPath != null) {
if (!servlets.containsKey(uri)) {
classPath = config.get(uri);
if (classPath != null) {
Class c = null;
c = Class.forName(classPath);
servlet = (HttpServlet) c.newInstance();
servlet.init();
//这里至关重要,通过反射生成Servlet对象,并且用户已经继承Servlet,重写了doGet或doPost方法,但我们只需进行执行service方法就可以运行用户的业务逻辑
servlets.put(uri, servlet);
}
} else {
servlet = servlets.get(uri);
}
response = new HttpServletResponseImpl(out, request, "200");
servlet.service(request, response);
servlet.destroy();
} else if (uri.toLowerCase().endsWith("html")) {
File file = new File(uri.substring(1));
if (file.isFile()) {
response = new HttpServletResponseImpl(out, request, "200");
brFile = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line = null;
while ((line = brFile.readLine()) != null) {
out.println(line);
}
} else {
response = new HttpServletResponseImpl(out, request, "404");
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (brFile != null) {
try {
brFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
}
if (request.getProtocol().equalsIgnoreCase("http/1.0")) {
if (s != null) {
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
- 这里会发现多了一个Map<String , HttpServlst> servlets
- 这个容器是为了减轻我框架的压力,因为当一个用户反复请求我的页面时,每访问一次就会进行反射,产生新的对象,访问完成后调用distory方法进行销毁,这就会不停的产生销毁对象,效率低下。所以我就将每个用户的uri和产生的HttpServlet存入Map中,每次访问都会压入servlets集合中,在下一次访问中就会搜索servlets集合中是否有url这个键,如果有就找map将上次对象赋给他,如果没有就newInstence创建对象。
- 在最后关闭连接时候,可以看出http/1.0和http/1.1的差异。
到这里主要的就完成了
四、进行测试
1.写个方法启动服务器
package cn.servlet;
import cn.servlet.http.HttpServlet;
public class Start {
public static void main(String[] args) {
new WebService();
}
}
2.写一个类继承HttpServlet
package cn.com.servlets;
import cn.servlet.ServletException;
import cn.servlet.http.HttpServlet;
import cn.servlet.http.HttpServletRequest;
import cn.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class ShowServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
PrintWriter pw = response.getWriter();
pw.println("<html>");
pw.println("<head>");
pw.println("</head>");
pw.println("<body>");
pw.println("<h1>helloWord</h1>");
pw.println("</bode>");
pw.println("</html>");
}
}
3.用浏览器访问事先规定的url参数
完成了简单的框架,最后的结构如下:
标签:String,框架,request,练习,JAVAEE,import,new,servlet,public From: https://www.cnblogs.com/xxyuanxx/p/17851277.html