引言
IOC,全称 Inversion of Control,控制反转。也算是老生常谈了。
老生常谈:原指老书生的平凡议论;今指常讲的没有新意的老话。
同时另一个话题,依赖注入,用什么就声明什么,直接就声明,或者构造函数或者加注解,控制反转是实现依赖注入的一种方式。
通过依赖注入:我们无需管理对象的创建,通过控制反转:我们可以一键修改注入的对象。
最近在做 Android 实验与小程序相关的开发,发现用惯了 IOC 的我们再去手动 new 对象的时候总感觉心里不舒服,以后改起来怎么办呢?既然没有 IOC,我们就自己写一个吧。
实现
就像我在标题中描述的一样,我先给大家讲解一下标配 IOC 的原理,使大家更清晰明了,但是受 Android 与小程序相关的限制,我具体的实现,是低配版 IOC。
个人扯淡
不管是 Spring 还是 Angular,要么是开源大家,要么是商业巨头,具体的框架实现都是相当优秀。我还没水平也没精力去研读源码,只希望分享自己对 IOC 的理解,帮到更多的人。
毕竟现在小学生都开始写 Python,以后的框架设计会越来越优秀,学习成本越来越低,开发效率越来越高。可能是个人报个培训班学个俩月也会设计微服务,也能成为全栈工程师。所以我们应该想的是如何设计框架,而不是仅停留在使用的层面,渐渐地被只会写增删改查天天搬砖的人取代。
996 加班的工程师都开始挤时间写框架扩大影响力,让社会听到程序员的呐喊,我们还有什么不努力的理由?
“标配”IOC
不管是 Spring 还是 Angular,它们的核心是什么呢?打上 mvn spring-boot:run 后台就 Started Application in xxx seconds 了,它到底干什么了呢?
容器
Spring 与 Angular 就是一个大的 IOC 容器,所以应用启动的过程,其实就是构造容器的过程。
容器,肯定是装东西的啊?IOC 容器里装的是什么?
装的是对象。控制器 Controller,服务 Service,自定义的组件 Component,所有被 Spring 管理的对象都将被放进 IOC 容器里。
所以,大家应该能明白,为什么 IOC 是依赖注入的一种实现方式?
因为这个对象不是你自己 new 的,是从容器中拿的,容器初始化的时候,就已经把这个对象构造好了,该注的都注进来了。
思考
从上面大家可以看到,依赖注入的前提是什么?是要求这个对象必须是从容器中拿的,所以才能依赖注入成功。
Spring Boot 中没问题,Tomcat 转发的路由直接交给容器中相应的对象去处理,同理,Angular 也一样。
Android 呢?
手机调用的并不是我们构造的 Activity,而是它自己实例化的,小程序也与之类似,Page 的实例化不归我们管。
所以“标配”IOC 在这里不适用,所以我设计了“低配”IOC 容器。
“低配”IOC
找点自己能管得了的对象放在 IOC 容器里,这样再需要对象就不用去 new 了,Service 有变更直接修改注入就行了。
受我们管理的只有 Service,计划设计一个管理所有 Service 的 IOC 容器,然后 Activity 或 Page 里用的时候,直接从容器中拿。(低配在这里,不能依赖注入了,得自己拿)。
Android 端
一个单例的 Configuration 负责注册 Bean 和获取 Bean,存储着一个 context 上下文。看着挺高级的,其实就是一个 HashMap 存储着接口类型到容器对象的映射。
/**
* 全局配置类
*/
public class Configuration {
private static Map<Class<?>, Object> context = new HashMap<>();
private static final class Holder {
private static final Configuration INSTANCE = new Configuration();
}
public static Configuration getInstance() {
return Holder.INSTANCE;
}
public Configuration registerBean(Class<?> clazz, Object bean) {
context.put(clazz, bean);
return this;
}
public <T> T getBean(Class<?> clazz) {
return (T) context.get(clazz);
}
}
写一个静态方法,更加方便配置。
/**
* 云智,全局配置辅助类
*/
public class Yunzhi {
…
public static <T> T getBean(Class<?> clazz) {
return Configuration.getInstance().getBean(clazz);
}
}
一个 Application 负责容器中所有对象的创建。
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Yunzhi.init()
.setApi(“http://192.168.2.110:8888”)
.setTimeout(1L)
.registerBean(AuthService.class, new AuthServiceImpl())
.registerBean(LetterService.class, new LetterServiceImpl());
}
}
使用方法和原来就一样了,一个接口,一个实现类。这里用到了 RxJava,看上去倒是类似我们的 Angular 了。
public interface AuthService {
Observable<Auth> login(String username, String password);
}
public class AuthServiceImpl implements AuthService {
private static final String TAG = “AuthServiceImpl”;
@Override
public Observable<Auth> login(String username, String password) {
Log.d(TAG, “BASIC 认证 ”);
String credentials = username + “:” + password;
String basicAuth = “Basic ” + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
Log.d(TAG, “ 请求登录 ”);
return HttpClient.request(AuthRequest.class)
.login(basicAuth)
.subscribeOn(Schedulers.io()) // 在 IO 线程发起网络请求
.observeOn(AndroidSchedulers.mainThread()); // 在主线程处理
}
}
因为 Activity 我们管不着,所以在 Activity 里用不了依赖注入,需要手动从容器里拿。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.authService = Yunzhi.getBean(AuthService.class);
}
这里有一处没考虑到的问题,就是手机端的性能问题,手机和服务器的处理能力肯定是比不了的,服务器在初始化的时候把所有对象都创建处理无可厚非,但是感觉手机端这样做还是会对性能产生一定影响的。
应该是某些对象用到的时候再创建,实验要求时间很紧,这个就先这样吧,不改了。
小程序端
小程序是在 Android 之后写的,想到了之前设计的部分缺陷,对容器使用了另一种思想进行实现。
export class YunzhiService {
private static context = new Map<string, object>();
public static getBean(beanName: string): object {
// 从 context 里拿对象
let bean = this.context.get(beanName);
// 如果没有,构造一个,并放进 context
if (!bean) {
bean = this.createBeanByName(beanName);
this.context.set(beanName, bean);
}
// 返回
return bean;
}
public static createBeanByName(beanName: string): object {
// 根据不同名称构造不同的 Bean
switch (beanName) {
case Bean.AUTH_SERVICE:
return new AuthServiceImpl();
case Bean.SCORE_SERVICE:
return new ScoreServiceImpl();
case Bean.SEMESTER_SERVICE:
return new SemesterServiceImpl();
default:
throw ‘ 错误,未注册的 bean’;
}
}
}
总结
Spring Boot 真厉害,什么时候我们也能写出如此优秀的框架?