zoukankan      html  css  js  c++  java
  • 徒手用Java来写个Web服务器和框架吧<第三章:Service的实现和注册>

    徒手用Java来写个Web服务器和框架吧<第一章:NIO篇>

    徒手用Java来写个Web服务器和框架吧<第二章:Request和Response>

    这一章先把Web框架的功能说一些,有个雏形。

    先是制作一个Service,并绑定到一个正则地址。用到了注解和反射。

    项目地址: Telemarketer

    2017年08月02日更新 已改成注解模式注册 等有空更新


    Service的定义

    Telemarketer的Service是一个服务。请求了跟它关联的地址,那就由它来为你服务。

    它对外只需一个方法。并且对这个方法的要求大概只有输入一个Request,可以返回一个Response这么简单。

    那就这样

    public interface Service {
        Response execute(Request request);
    }

    问题来了,如何把它关联到uri上呢?第一想法是在创建一个注册中心。

    Service的绑定

    这部分代码在这里,可参考查看

    先来手动挡

    新建一个类ServiceRegistry, 在里面放这么一个静态的Map:

     private static Map<String, Service> services = new TreeMap<>(); 

    还提供了这么一个方法:

     public static void register(String pattern, Service service) {services.put(pattern, service);} 

    然后再提供查找服务的方法。

    public static Service findService(String pattern) {
        for (Map.Entry<String, Service> entry : services.entrySet()) {
            if (pattern.matches(entry.getKey())) {
                return entry.getValue();
            }
        }
        return null;
    }

    这样不就根据request确定了服务对象吗?让人们自己动手丰衣足食吧。

    然而转念一想,这么搞多麻烦,还得让人专门找个地方register一大堆?这一点都不方便..懒惰是第一大生产力!

    那再换个自动挡。

    再来自动挡

    稍微一想Spring那些框架,用注解搞依赖注入用的那是飞起,Telemarketer也可以!来,说干就干!

    先确定注解三个要点

      1. 确定目标:类
      2. 确定范围:运行时
      3. 确定内容:一个String可以放地址正则就好
     @Target(ElementType.TYPE)
     @Retention(RetentionPolicy.RUNTIME)
     public @interface InService {
         String urlPattern();
     }

    有了这个,赶紧写个服务先标上。地址正则就匹配根目录吧

    1 @InService(urlPattern = "^/$")
    2 public class IndexService implements Service {
    3     @Override
    4     public Response execute(Request request) {
    5         return new FileResponse(Status.SUCCESS_200, PropertiesHelper.getTemplateFile("index.html"));
    6     }
    7 }

     PropertiesHelper.getTemplateFile("index.html")  返回的是一个html的文件,暂时不用管。FileResponse是包含文件内容的Response。

    行了,现在想想怎么靠这个注解自动注册呢?

    假设我们知道了这个类名是 edu.telemarketer.services.servicesimpls.IndexService 。先反射一个:

     Class<?> aClass = Class.forName("edu.telemarketer.services.servicesimpls.IndexService"); 

    注意这里用Class.forName()和ClassLoader的load()方法区别:Class.forName()会初始化类中的静态域,有什么static都会准备好,而ClassLoader的load就不会有这样的效果。

    弄到这个Class对象后,获取InService标注

     InService annotation = aClass.getAnnotation(InService.class); 

    判断一下是不是null 再判断一下Service.class是不是这个类的接口。如果都满足那就是我们要找的类了。实例化它!注册它!

    if (annotation != null && Service.class.isAssignableFrom(aClass)) {
        ServiceRegistry.register(annotation.urlPattern(), aClass.asSubclass(Service.class).newInstance());
    }

    注意这里的 isAssignableFrom 和 instanceof 的差别, isAssignableFrom 左边是右边的父类或者接口。

    整个过程就是先获取根目录,然后递归扫描所有.class进行上述判断。而且反射的时候得用全名,那同时再记录一下包路径。具体可以查看源码。

    这个过程还遇到了一个问题: 在一开始我获取了edu.telemarketer的包名想转化为路径用的是name.replaceAll("\.", File.separator)。在windows环境下测试出现了一个异常。经过查证后发现,replaceAll是不准出现""的,会引起混淆。于是用Matcher.quoteReplacement(File.separator)进行替代单独的separator。具体点此处 StackOverflow--replaceAll “/” with File.separator

    到此为止自动挡就弄完了。

    Connector里的使用

    还记得第一章读取事件里面的Connector吗,它是一个实现了Runnable的类. 它的run里面大概是这么一个过程。

    1 Request request = Request.parseRequest(channel);
    2 Service service =ServiceRegistry.findService(request.getFilePath());
    3 Response response = service.execute(request);
    4 channel.register(selector, SelectionKey.OP_WRITE, response);

    启动一下,看看页面是不是出来了。


    在提交给service处理前和处理后应该由服务器先对头域中的一些信息进行处理,比如维护连接或者处理cookie等等。这些方面后续再来完善。

  • 相关阅读:
    【转】[C# 基础知识系列]专题七:泛型深入理解(一)
    【转】[C# 基础知识系列]专题六:泛型基础篇——为什么引入泛型
    【转】[C# 基础知识系列]专题五:当点击按钮时触发Click事件背后发生的事情
    【转】[C# 基础知识系列]专题四:事件揭秘
    【转】[C# 基础知识系列]专题三:如何用委托包装多个方法——委托链
    Day 47 Django
    Day 45 JavaScript Window
    Day 43,44 JavaScript
    Day 42 CSS Layout
    Day 41 CSS
  • 原文地址:https://www.cnblogs.com/imyijie/p/4825859.html
Copyright © 2011-2022 走看看