java se用来写服务器_JavaSE 手写 Web 服务器(二)

一、背景

在上一篇文章 《JavaSE 手写 Web 服务器(一)》 中介绍了编写 web 服务器的初始模型,封装请求与响应和多线程处理的内容。但是,还是遗留一个问题:如何根据不同的请求 url 去触发不同的业务逻辑。

这个问题将在本篇解决。

二、涉及知识

XML:将配置信息写到 XML 文件,解决硬编码问题。

反射:读取 XML 文件配置并实例化对象。

三、封装控制器

目前手写的 web 容器只能处理一种业务请求,无论发送什么 url 的请求都是只返回一个内容相似的页面。

为了能很好的扩展 web 容器处理不同请求的功能,我们需要将 request 和 response 封装到控制器,让各个业务的控制器处理对应请求和响应。

3.1 抽离控制器

创建一个父类控制器:

public class Servlet {

public void service(Request request, Response response) {

doGet(request,response);

doPost(request,response);

}

protected void doGet(Request request, Response response) {

}

protected void doPost(Request request, Response response) {

}

}

父类中使用了模板方法的设计模式,将业务处理的行为交给子类去处理。

当我们需要一个控制器去处理登陆请求时,我们创建一个 LoginServlet 类去继承 Servlet 并重写 doGet 或 doPost 方法:

public class LoginServlet extends Servlet {

@Override

protected void doPost(Request request, Response response) {

response.println("")

.println("")

.println("

")

.println(" ")

.println("

测试")

.println(" ")

.println("

")

.println("

Hello " + request.getParameter("username") + "

")// 获取登陆名

.println(" ")

.println("");

}

}

如果需要处理注册请求,创建一个 RegisterServlet 类继承 Servlet 并重写 doGet 或 doPost 方法即可。

3.2 创建 web 容器上下文

为了能更好的管理多个控制器实例以及请求 url 与控制器的对应关系,我们需要一个类对其封装和管理。

/**

* web 容器上下文

* @author Light

*

*/

public class ServletContext {

// 存储处理不同请求的 servlet

// servletName -> servlet 子类

private Map servletMap;

// 存储请求 url 与 servlet 的对应关系

// 请求 url -> servletName

private Map mappingMap;

public ServletContext() {

this.servletMap = new HashMap();

this.mappingMap = new HashMap();

}

public Map getServletMap() {

return servletMap;

}

public void setServletMap(Map servletMap) {

this.servletMap = servletMap;

}

public Map getMappingMap() {

return mappingMap;

}

public void setMappingMap(Map mappingMap) {

this.mappingMap = mappingMap;

}

}

3.3 创建配置类

虽然有了 web 容器上下文,但是 web 容器上下文并不是一开始就存放配置信息的。配置信息在 web 容器启动时被注册或写入到上下文中,因此需要一个管理配置的类负责该操作:

/**

* 配置文件类

* @author Light

*

*/

public class WebApp {

private static ServletContext context;

static {

context = new ServletContext();

Map servletMap = context.getServletMap();

Map mappingMap = context.getMappingMap();

// 注册 登陆控制器

servletMap.put("login", new LoginServlet());

mappingMap.put("/login", "login");

// 如有更多请求需要处理,在此配置对应的控制器即可

}

/**

* 通过请求 url 获取对应的 Servlet 对象

* @param url

* @return

*/

public static Servlet getServlet(String url) {

if (url == null || "".equals(url.trim())) {

return null;

}

String servletName = context.getMappingMap().get(url);

Servlet servlet = context.getServletMap().get(servletName);

return servlet;

}

}

改造 Dispatcher 的 run 方法,从 WebApp 类中获取控制器实例:

@Override

public void run() {

// 获取控制器

Servlet servlet = WebApp.getServlet(this.request.getUrl());

// 处理请求

servlet.service(request, response);

try {

this.response.pushToClient(code);

this.socket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

四、解决硬编码问题

在 WebApp 类中,我们配置了 LoginServlet 类以及相关信息,这种写法属于硬编码,且这个两个类发生了耦合。

为了解决上述问题,我们可以将 LoginServlet 类的配置写到一个 xml 文件中,WebApp 类负责读取和解析 xml 文件中的信息并将信息写入到 web 容器上下文中,在 Dispatcher 类中通过反射实例化控制器对象处理请求。

创建 web.xml 配置文件:

login

com.light.controller.LoginServlet

login

/login

创建两个类用于封装 web.xml 配置文件中的数据,即 和 相关标签的内容:

/**

* 封装 web.xml 中 配置信息

* @author Light

*

*/

public class ServletXml {

private String servletName;

private String servletClazz;

public String getServletName() {

return servletName;

}

public void setServletName(String servletName) {

this.servletName = servletName;

}

public String getServletClazz() {

return servletClazz;

}

public void setServletClazz(String servletClazz) {

this.servletClazz = servletClazz;

}

}

/**

* 封装 web.xml 中 配置信息

* @author Light

*

*/

public class ServletMappingXml {

private String servletName;

private List urlPattern = new ArrayList<>();

public String getServletName() {

return servletName;

}

public void setServletName(String servletName) {

this.servletName = servletName;

}

public List getUrlPattern() {

return urlPattern;

}

public void setUrlPattern(List urlPattern) {

this.urlPattern = urlPattern;

}

}

创建 xml 文件解析器,用于解析 web.xml 配置文件:

/**

* xml文件解析器

* @author Light

*

*/

public class WebHandler extends DefaultHandler{

private List servletXmlList;

private List mappingXmlList;

private ServletXml servletXml;

private ServletMappingXml servletMappingXml;

private String beginTag;

private boolean isMapping;

@Override

public void startDocument() throws SAXException {

servletXmlList = new ArrayList<>();

mappingXmlList = new ArrayList<>();

}

@Override

public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

if (qName != null) {

beginTag = qName;

if ("servlet".equals(qName)) {

isMapping = false;

servletXml = new ServletXml();

} else if ("servlet-mapping".equals(qName)){

isMapping = true;

servletMappingXml = new ServletMappingXml();

}

}

}

@Override

public void characters(char[] ch, int start, int length) throws SAXException {

if (this.beginTag != null) {

String str = new String(ch,start,length);

if(isMapping) {

if("servlet-name".equals(beginTag)) {

servletMappingXml.setServletName(str);

} else if ("url-pattern".equals(beginTag)) {

servletMappingXml.getUrlPattern().add(str);

}

} else {

if("servlet-name".equals(beginTag)) {

servletXml.setServletName(str);

} else if ("servlet-class".equals(beginTag)) {

servletXml.setServletClazz(str);

}

}

}

}

@Override

public void endElement(String uri, String localName, String qName) throws SAXException {

if (qName != null) {

if ("servlet".equals(qName)) {

this.servletXmlList.add(this.servletXml);

} else if ("servlet-mapping".equals(qName)){

this.mappingXmlList.add(this.servletMappingXml);

}

}

this.beginTag = null;

}

public List getServletXmlList() {

return servletXmlList;

}

public List getMappingXmlList() {

return mappingXmlList;

}

}

改造 ServletContext 类:

// 存储处理不同请求的 servlet

// servletName -> servlet 子类的全名

private Map servletMap;

即 将 private Map servletMap 改成 private Map servletMap 。

注意,get 和 set 方法都需要修改。

改造 WebApp 类中注册控制器相关代码:

/**

* 配置文件类

* @author Light

*

*/

public class WebApp {

private static ServletContext context;

static {

context = new ServletContext();

Map servletMap = context.getServletMap();

Map mappingMap = context.getMappingMap();

try {

SAXParserFactory factory = SAXParserFactory.newInstance();

SAXParser sax = factory.newSAXParser();

WebHandler handler = new WebHandler();

// 读取和解析 xml 文件

sax.parse(Thread

.currentThread()

.getContextClassLoader()

.getResourceAsStream("com/light/server/web.xml"),

handler);

// 注册 servlet

List servletXmlList = handler.getServletXmlList();

for (ServletXml servletXml : servletXmlList) {

servletMap.put(servletXml.getServletName(), servletXml.getServletClazz());

}

// 注册 mapping

List mappingXmlList = handler.getMappingXmlList();

for (ServletMappingXml mapping : mappingXmlList) {

List urls = mapping.getUrlPattern();

for (String url : urls) {

mappingMap.put(url, mapping.getServletName());

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 通过请求 url 获取对应的 Servlet 对象

* @param url

* @return

*/

public static String getServletClazz(String url) {

if (url == null || "".equals(url.trim())) {

return null;

}

String servletName = context.getMappingMap().get(url);

String servletClazz = context.getServletMap().get(servletName);

return servletClazz;

}

}

改造 Dispatcher 类的 run 方法,通过反射实例化对象:

@Override

public void run() {

try {

// 获取控制器包名

String servletClazz = WebApp.getServletClazz(this.request.getUrl());

// 通过反射实例化控制器对象

Servlet servlet = (Servlet) Class.forName(servletClazz).newInstance();

// 处理请求

servlet.service(request, response);

this.response.pushToClient(code);

this.socket.close();

} catch (Exception e) {

e.printStackTrace();

}

}

五、总结

手写 web 容器的内容到此结束。

文章中主要介绍编写 web 容器的大致流程,代码中还有许多地方需要考虑(过滤器、监听器、日志打印等)和优化,仅作为笔者对 web 容器的理解与分享,并不是为了教读者重复造轮子。

学习东西要知其然,更要知其所以然。

六、源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值