手写一个简单的 javaweb框架

整体结构
在这里插入图片描述

  • log 日志
  • http ⇒ Tomcat
  • config-parser 配置文件解析
  • container ⇒ Spring
  • mvc ⇒ SpringMVC
  • service ⇒ SpringBoot
  • logx 支持 Spring 自动注入的 log
  • sqlx ⇒ MyBatis
  • registry-client 服务注册客户端
  • remote-invoke 微服务通信

详细说明

log

在这里插入图片描述

只有一个 Log 类,可以存放 package.Class,方便打印,提供 info(), error(), warn() 方法

http

在这里插入图片描述

简单来说就是一个 ServerSocket 服务器,循环等待客户端 Socket 的到来。

  • 每有一个 Socket 进来,就为其创建一个线程进行处理。
  • 从 Socket 中读取数据,将其封装为一个 Request 类,进一步封装成 HttpServletRequest,创建 HttpServletResponse,然后从 Servlet 容器中取出对应的 Servlet,调用请求对应的方法,参数为 HttpServletRequest,HttpServletResponse。
  • 在处理完成后,将结果 HttpServletResponse 转换成字符串,从 Socket 写出。

config-parser

在这里插入图片描述

提供接口,制定通用方法,(简单)实现了 XML 和 YAML 文件的读取

container

在这里插入图片描述

AnnotationApplicationContext 是入口,在实例化的时候,会把标注了 @AnnotationContext 的类(就暂且叫 starter 吧)传过来,通过三个步骤完成整个流程

  • loadClass
    • 从 ClassLoader 获取到 starter 的路径,扫描其路径下所有 java 文件,存放到 List<Class<?>> classes。当然,还有 starter 注解里面 @Import 的文件也要一并扫描
  • defineBeans
    • 遍历 classes,判断其是否有 @component 注解,有这个注解才算是 bean,才会为其创建 BeanDefinition,放入 beanDefinitionMap 中。在这个过程中还有一件事,就是如果某个类继承了 interfaces 里面的某个接口,就要将这个类放入对应的接口 list 中,后面创建 bean 的时候会需要使用到。
  • initSingletonPool
    • 遍历 beanDefinitionMap,找出单例模式(默认单例)且 非懒加载(@Lazy(false) ,默认情况懒加载)的 beanDefinition,为其创建对象,放入单例池。
    • 在这个环节,创建对象是通过调用 getBean(String name) 来触发的。对于创建中的每一个 bean,都会经历很多的检查,比如检查 @Value,@Autowired。如果这个 bean 的类实现了 BeanNameAware 接口,要调用 setBeanName()。实现了 InitializingBean 要调用 afterPropertiesSet()。还要经历所有 BeanPostProcessor 实现类的处理。
    • 以上所有步骤只是 BaseBeanCreator 所做的事,用户可能还定义了自己的 BeanCreator,所以上面的步骤结束之后,创建出来的这个 bean 对象,还要继续经历所有自定义 BeanCreator 的处理,才能算的上圆满。
    • 哦,对了,还有在根据 class 创建实例的时候,如果这个 class 是个 interface,还得经过所有 BeanInterfaceFactory 的处理,调用 xxInterfaceFactory.newProxy(),为这个 class 创建动态代理对象,把代理对象作为 bean。

以上所有步骤中的 BeanPostProcessor,BeanInterfaceFactory,BeanCreator 都是在第二步定义的时候收集的,存放在对应的 list 里,当有对象需要时,就遍历某个 list 的所有对象,调用方法进行处理。虽然说一个 bean 要经历所有 BeanPostProcessor 或者 BeanInterfaceFactory 或者 BeanCreator 的处理,但并不是每个处理器都会处理这个 bean。有些处理器一看,这不是我负责的内容,可能就直接返回了。要想实现这个效果,反射和注解是功不可没的。

mvc

在这里插入图片描述

mvc 做的事情很简单

  • RequestHandler:Object bean; Method method
  • ControllerCreator:在 container 中我们见过 BeanCreator,这个 ControllerCreator 就实现了 BeanCreator。它在 BaseBeanCreator 处理完后,需要继续处理 bean,首先会检测 bean 是否有 @Controller。然后获取 @RequestMapping(该类的 url 路径)
    • 遍历 bean 的所有方法,找出标有 @GetMapping 的方法,取出 url 路径,和类的 url 路径拼接成完整的 url。把 method,bean 封装成 RequestHandler,将 url,requestHandler 存入 DispacherServlet(静态 Map<String, RequestHandler>)。最后返回 null,container 收到 null,就不会继续创建这个 bean 了。
  • DispacherServlet:继承 Servlet,实现 doGet 方法。在这个方法中,从 HttpServletRequest 获取 url 路径,从 map 中找到对应的 requestHandler,然后调用其方法,拿到结果后写入 HttpServletResponse。

service

在这里插入图片描述

  • @ServiceApplication 相当于 SpringBoot 的 @SpringBootApplication。在其中 @Import 了 mvc 的 ControllerCreator,这样在启动的时候,container 就能加载 ControllerCreator 了
  • Application 也很简单,创建 AnnotationApplicationContext(即 container)。实例化 DispacherSerlvet,其对应的路径是 *。
    最后实例化 Bootstrap(即 http),把 key: *, value: DispacherServlet 放入 bootstrap 并启动

registry-client

在这里插入图片描述

到这里就很简单了,@EnableRegistry 里 @import 了 InitailizingRegistryClient

  • 如果不加 @EnableRegistry,就无法使用成为微服务客户端,就无法连接服务注册中心。
  • InitailizingRegistryClient 实现了 InitailizingBean,被扫描到后会在字段属性设置完成之后进行一些操作,具体的就是指向服务注册中心发送一个请求(包含自己的服务名,域名,端口),而服务注册中心会接收到这个请求,把这些存在 map 里。

remote-invoke

在这里插入图片描述

  • @EnableRemoteInvoke 里面 @Import 了 RpcInterfaceFactory,如果不标注 @EnableRemoteInvoke 就无法进行服务之间的调用
  • @RemoteInvokeServer 是标注在远程调用接口上的,value 是要调用的服务名
  • RpcInterfaceFactory 从名字可以看出来这个是接口代理的工厂,因为在本微服务类,要想去进行调用远程,得有一个标注了 @RemoteInvokeServer 的接口,所以接口具体怎么调用,就是这个代理对象所要解决的了。
    在这里面
    1,首先会解析被调用的方法
    2,然后根据 @RemoteInvokeServer 可以知道要调用哪个服务
    3,向服务注册中心发送请求,查找目标服务的信息,就可以得到目标地址
    4,解析方法的参数
    5,正式请求

registry

在这里插入图片描述

服务注册中心,因为相对于自己的微服务是独立的,所以在最上面的依赖关系中没有看见

  • RegisterController 就是接收请求的,什么注册啊,查看所有微服务信息啊,查看指定微服务信息啊,都在这里面
  • Registry 只是个启动类,不用管
  • ServiceTag 嘛,在远程调用里面也有看见这个,这个其实就是存服务名称,域名,端口,这些信息的

整个应用的时序图

在这里插入图片描述

示例

UserService-2001

application.yaml

server:
    port: 2001
    name: User-Service

registry:
    host: localhost
    port: 2000

UserService2001.java

package sky.cin.ms.test1;

import sky.cin.logx.annotations.EnableLogger;
import sky.cin.ms.registryclient.annotations.EnableRegistry;
import sky.cin.service.Application;
import sky.cin.service.annotations.ServiceApplication;

@ServiceApplication
@EnableLogger
@EnableRegistry
public class UserService2001 {
    
    public static void main(String[] args) {
        Application.run(UserService2001.class);
    }
    
}

LoginController.java

package sky.cin.ms.test1.controller;

import sky.cin.container.annotations.beans.Controller;
import sky.cin.container.annotations.beans.Value;
import sky.cin.container.annotations.https.GetMapping;
import sky.cin.container.annotations.https.RequestMapping;
import sky.cin.container.annotations.https.RequestParam;
import sky.cin.mvc.annotations.ApiDescribe;
import sky.cin.test.commons.entity.User;

@Controller
@RequestMapping("/user")
public class LoginController {
    
    @Value("${server.port}")
    private Integer port;
    
    @Value("${server.name}")
    private String name;

    @ApiDescribe("用户登录")
    @GetMapping("/login")
    public User login(
        @RequestParam("username") String username,
        @RequestParam("password") String password
    ) {
        return new User(username, password, port, name);
    }
    
}

UserService-2002

application.yaml

server:
    port: 2002
    name: User-Service

registry:
    host: localhost
    port: 2000

UserService2002.java

package sky.cin.ms.test2;

import sky.cin.logx.annotations.EnableLogger;
import sky.cin.ms.registryclient.annotations.EnableRegistry;
import sky.cin.service.Application;
import sky.cin.service.annotations.ServiceApplication;

@ServiceApplication
@EnableLogger
@EnableRegistry
public class UserService2002 {
    
    public static void main(String[] args) {
        Application.run(UserService2002.class);
    }
    
}

LoginController.java

package sky.cin.ms.test2.controller;

import sky.cin.container.annotations.beans.Controller;
import sky.cin.container.annotations.beans.Value;
import sky.cin.container.annotations.https.GetMapping;
import sky.cin.container.annotations.https.RequestMapping;
import sky.cin.container.annotations.https.RequestParam;
import sky.cin.test.commons.entity.User;

@Controller
@RequestMapping("/user")
public class LoginController {
    
    @Value("${server.port}")
    private Integer port;
    
    @Value("${server.name}")
    private String name;
    
    @GetMapping("/login")
    public User login(
        @RequestParam("username") String username,
        @RequestParam("password") String password
    ) {
        return new User(username, password, port, name);
    }
    
}

Login-Service-3000

application.yaml

server:
    port: 3000
    name: login

registry:
    host: localhost
    port: 2000

LoginService3000.java

package sky.cin.ms.test3;

import sky.cin.logx.annotations.EnableLogger;
import sky.cin.ms.registryclient.annotations.EnableRegistry;
import sky.cin.ms.rpc.annotations.EnableRemoteInvoke;
import sky.cin.service.Application;
import sky.cin.service.annotations.ServiceApplication;

@ServiceApplication
@EnableLogger
@EnableRegistry
@EnableRemoteInvoke
public class LoginService3000 {
    
    public static void main(String[] args) {
        Application.run(LoginService3000.class);
    }
    
}

LoginInvokeUser.java

package sky.cin.ms.test3.invoke;

import sky.cin.container.annotations.beans.Component;
import sky.cin.container.annotations.https.GetMapping;
import sky.cin.container.annotations.https.RequestParam;
import sky.cin.ms.rpc.annotations.RemoteInvokeServer;
import sky.cin.test.commons.entity.User;

@Component
@RemoteInvokeServer("User-Service")
public interface LoginInvokeUser {
    
    @GetMapping("/user/login")
    User login(
        @RequestParam("username") String username,
        @RequestParam("password") String password
    );
    
}

LoginController.java

package sky.cin.ms.test3.controller;

import sky.cin.mvc.annotations.ApiDescribe;
import sky.cin.container.annotations.beans.Autowired;
import sky.cin.container.annotations.beans.Controller;
import sky.cin.container.annotations.https.GetMapping;
import sky.cin.container.annotations.https.RequestMapping;
import sky.cin.container.annotations.https.RequestParam;
import sky.cin.http.core.HttpServletResponse;
import sky.cin.ms.test3.invoke.LoginInvokeUser;
import sky.cin.test.commons.entity.User;

@Controller
@RequestMapping("/user")
public class LoginController {
    
    @Autowired
    private LoginInvokeUser invokeUser;

    @ApiDescribe("用户登录")
    @GetMapping("/login")
    public User login(
        HttpServletResponse response,
        @RequestParam("username") String username,
        @RequestParam("password") String password
    ) {
        response.setHeader("Content-Type", "application/json");
        return invokeUser.login(username, password);
    }
    
}
  • 第一步,启动服务注册中心
    Registry
    在这里插入图片描述
  • 第二步,启动其他三个微服务
    UserService2001在这里插入图片描述
    UserService2002在这里插入图片描述
    LoginService3000
    在这里插入图片描述
  • 此时可以回注册中心看看,发现三个服务确实都注册进来了
    在这里插入图片描述
  • 第三步,访问 /user/login(因为使用了远程调用,负载均衡这里可以多访问几次,看看不同的效果)
    在这里插入图片描述
    在这里插入图片描述
    LoginService3000
    在这里插入图片描述
    UserService2001
    在这里插入图片描述
    UserService2002
    在这里插入图片描述
    源码地址
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值