整体结构
- 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
源码地址