一、之前遗留的问题
在上一篇文章中,提到了服务定位模式是如何一步一步演变最后实现跨包注册及动态匹配的,但也留了三个问题:
1、有没有可能再进一步优化此方案,让初始化也变成动态的呢?
2、如果实现类现在还要SDK中的其他依赖又该如何处理呢?
3、每次我们在调用方法的时候都需要去初始化一下这个类对象,在有些需求里面,这个LoginService的对象其实完全可以是单例或者是在某个作用域下是单例的,有没有可能在SDK中实现呢?
二、解决思路及方案
问题一:有没有可能再进一步优化此方案,让初始化也变成动态的呢?
解决思路:将初始化用到的参数对象也通过一些方式初始化好,然后当IController实现类初始化时直接使用初始化好的对象进行初始化。这就需要:
(1)业务端在注册时(如果有多个Constructor)告诉SDK真正要用的是哪个构造方法,
(2)业务线主动提供构造方法的参数构建方式。
问题二:如果实现类现在还要SDK中的其他依赖又该如何处理
解决思路:将依赖通过内部sdk的provider,共享给外部使用。
如何实现:
从UML图上可以发现,拥有控制权的DILoginActivity的依赖有DILoginRegister、ILoginController以及LoginModule,分别代表着DILoginActivity需要关心的内容注册器、向外提供的实现类接口以及自己能够提供什么到注册器中这三项。
DILoginRegister:核心注册方法
private static final String TAG = DILoginRegister.class.getSimpleName();
private static Class targetController;
private static Map<String, SoftReference<Method>> sCachedAction = new HashMap<>();
private static Map<Class, SoftReference<Method>> mModuleMethedMap = new HashMap<>();
private static Object module;
public static void bind(@LoginController Class clazz) {
targetController = clazz;
}
public static void initProvider(@Module Object module){
DILoginRegister.module = module;
Class clazz = module.getClass();
for (Method method: clazz.getDeclaredMethods()){
Provider provider = method.getAnnotation(Provider.class);
if (provider != null){
Class returnType = method.getReturnType();
mModuleMethedMap.put(returnType, new SoftReference<>(method));
}
}
}
/**
* 执行目标Action的Method
*
* @param mContext
* @param action
* @return
*/
@Nullable
public static ILoginController executeTargetAction(Context mContext, String action) {
try {
//根据action找到对应的方法
Class clazz = targetController;
if (clazz == null) {
return null;
}
List<Constructor> constructors = getConstructorByAnnotation();
Object obj = null;
for (Constructor constructor: constructors){
obj = setupConstructor(constructor);
if (obj != null){
break;
}
}
if (obj == null){
Log.e(TAG, "constructor can not be initialized");
return null;
}
Method actionMethod = getCachedMethod(action, clazz);
if (actionMethod != null) {
actionMethod.setAccessible(true);
ILoginController iLoginController = (ILoginController) actionMethod.invoke(obj);
return iLoginController;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
/**
* 构造对应Object
*
* @param constructor
* @return
*/
private static Object setupConstructor(Constructor constructor) {
Class[] paramsClasses = constructor.getParameterTypes();
Object[] paramsObj = new Object[paramsClasses.length];
Object result = null;
int index = 0;
for (Class clazz: paramsClasses){
if (mModuleMethedMap.get(clazz) != null){
paramsObj[index] = getParamsInstance(mModuleMethedMap.get(clazz).get());
}
try {
result = constructor.newInstance(paramsObj);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 获取参数的实例
* @param method
* @param args
* @return
*/
private static Object getParamsInstance(Method method, Object... args) {
Object object = null;
if (method != null){
try {
object = method.invoke(module, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return object;
}
/**
* 根据注解获取构造函数
* @return
*/
private static List<Constructor> getConstructorByAnnotation() {
Constructor[] constructors =
targetController.getDeclaredConstructors();
List<Constructor> targetConstructors = new ArrayList<>();
for (Constructor constructor: constructors){
Annotation annotation = constructor.getAnnotation(Inject.class);
if (annotation == null){
continue;
}
if (annotation instanceof Inject){
//当前constructor的注解就是Inject的
targetConstructors.add(constructor);
}
}
return targetConstructors;
}
/**
* 在缓存中获取action和class对应的method
*
* @param action
* @param clz
* @return
*/
private static Method getCachedMethod(String action, Class clz) {
Method actionMethod = null;
if (sCachedAction != null) {
SoftReference<Method> reference = sCachedAction.get(action);
actionMethod = reference != null ? reference.get() : null;
}
if (actionMethod == null) {
for (Method method : clz.getDeclaredMethods()) {
Action annotation = method.getAnnotation(Action.class);
if (annotation == null) {
continue;
}
String methodAction = annotation.action();
if (action.equalsIgnoreCase(methodAction)
&& method.getReturnType().isAssignableFrom(ILoginController.class)) {
if (sCachedAction == null) {
sCachedAction = new HashMap<>();
}
sCachedAction.put(action, new SoftReference<Method>(method));
actionMethod = method;
break;
}
}
}
return actionMethod;
}
ILoginController:对外的实现类接口
public interface ILoginController {
boolean login(String account, String password);
}
@Inject:注入注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface Inject {
}
@Module:模块注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface Inject {
}
@Provider: 提供者注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface Provider {
}
DILoginActivity:Activity执行需要的内容
//注册可以提供的module内容
DILoginRegister.initProvider(new LoginModule(this));
//执行对应的方法
ILoginController iLoginController = DILoginRegister.executeTargetAction(this, "doLogin");
LoginModule:主要的内容提供者
@Module
public class LoginModule {
private Context context;
public LoginModule(Context context) {
this.context = context;
}
@Provider
public Context getContext(){
return context;
}
}
DiLoginPresenter:在非引用module内的真正逻辑实现类
public class DiLoginPresenter {
private Context mContext;
@Inject
public DiLoginPresenter(Context mContext) {
this.mContext = mContext;
}
@Action(action = "doLogin")
public ILoginController outterDoLogin(){
return new SLLoginController(mContext);
}
}
至此,就可以将构造方法也动态的进行匹配,构造方法所需要的参数会在构造时自行创建,所有的外部逻辑类现在更加灵活,LoginPresenter只需要在构造方法前添加Inject注解,就可以在SDK内部动态匹配创建。如果业务Presenter现在不仅仅需要context参数而且还需要很多其他参数,这时只需要在业务线新建一个Module并且将Module注册到Register中即可。这就是构造方法的依赖注入原理和好处。
问题三:已经创建过的资源是否可以在某些范围内保证单例,减少资源消耗?
解决思路:
1、保证单例:在使用对应资源前,先进行为空判断。
2、某些范围:添加作用域概念,即对应资源的生命周期,随着被注入类的生命周期变化而变化。
如何实现:
DILoginRegister:
添加map存储class与对应的数据,再有需要用到该类型时,进行匹配。
private static Object getParamsInstance(Class clazz, Method method, Object... args) {
if (mInjectedObjs.get(clazz) != null && mInjectedObjs.get(clazz).get() !=null){
Log.d(TAG, "Existing instance objects");
return mInjectedObjs.get(clazz).get();
}
Object object = null;
if (method != null){
try {
object = method.invoke(module, args);
mInjectedObjs.put(clazz, new SoftReference<>(object));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return object;
}
添加destory方法,在退出当前类时,进行主动销毁。(之后文章中会优化为自动销毁)
/**
* DILoginRegister 清空所有对象
*/
public static void onDestory(){
mInjectedObjs.clear();
mInjectedObjs = null;
}
至此,遗留的三个问题已经解决,但明显仍然存在优化的空间,主要还需要解决:
1、如果不是构造函数需要依赖,而是属性需要依赖如何处理
2、如果不仅是一个模块,而需要多个模块都需要依赖注入需要怎么处理?
将静态Register注册改为动态注册,并且把注入的对象进行生命周期与属性的管理。
3、有没有可能不使用反射,实现跨module的依赖注入
JIT时进行代码添加,使用工厂模式进行功能实现,类似Dagger2
这些问题都会在之后的文章中实现。