框架的API设计
1. 目的
设计的目的是服务代码编写的时候无论在何处,只需要简单的调用框架API即可获取资源,无需其他任何设置。
例如在数据转发服务的代码中使用Context.logger()
返回是的数据转发服务的日志,而在管控服务中调用Context.logger()
返回的是管控服务的日志。
2. 思考
- 不同的服务之间的资源不同,所以肯定有一个Map用来装着不同服务的资源,Map的索引即是服务的唯一索引。
- 同一个服务下,任何地方能直接获取到自己服务的资源,这也意味着在服务的任何地方都保存着当前服务的唯一索引。
我们首先想到的是ThreadLocal,但是同一个服务下运行着多个线程,意味着每当新开一个线程的时候需要手动添加ThreadLocal,因此此办法的效率太低。
解决办法:
不同的服务之间,除了服务的主键id不同以外,每个服务都有自己ClassLoader,因此ClassLoader可以作为服务的索引,每个线程都有自己的ContextClassLoader,当前线程新运行的子线程会直接继承父线程的ContextClassLoader,因此我们只需要在服务的启动线程设置好ContextClassLoader,服务在此线程上开启的新线程均会继承相同的ContextClassLoader,这样就实现了在一个服务的任何地方都可以通过
Thread.currentThread().getContextClassLoader()
来获取当前服务的索引。
public class Context{
private static Map<ClassLoader,IResource> map=new ConcurrentHashMap<>();
//将新的资源注册到上下文中
protected static void register(ClassLoader loader,IResource resource){
map.put(loader,resource);
}
//获取当前线程所在服务的资源
private static IResource getResource(){
return map.get(Thread.getCurrentThread().getContextCLassLoader();
}
//获取当前线程所在服务的日志
public static ILogger logger(){
return getResource().getILogger();
}
...//获取其他资源
}
3. 其他注意事项
- 因为服务的索引是Classloader,所以在服务的启动的时候必须单独开一个新的线程,将此线程的ContextClassLoader设置为服务的ClassLoader,并且此线程不会再被其他线程使用到。
- 因为只有子线程才会继承父类的ClassLoader。所以服务只能用自己的线程池,不同的服务之间不能共用一个线程池,否则会造成资源使用混乱。