都说依赖注入,我就从实现的角度来一发,以android作为引子..

用过诸多的view注入的框架,例如xutils,butterknife,KJLibraray,Guice等,你了解过如何实现吗?

从零来一发, 今天老司机为新来者带带路~其他老司机略过

从demo上,我只实现两个功能@InjectView,@OnClick。前者注入view,后者注入点击事件。其他的实现角度上是一样的,大家可以一起探讨一下!



还是老套路,先分析:

1.我需要两个注解类@InjectView和@OnClick

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectView {
    public @IdRes int value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    public @IdRes int[] value();
}

简单提一下:

@Target的ElementType中

public enum ElementType {?

    TYPE,  //加在类上的注解?
    FIELD,  //加在类中属性上的注解?
    METHOD, //加在类中方法上的注解?
    PARAMETER, //加在参数上的注解,可以是方法内的参数?
    CONSTRUCTOR, //加在构造函数上的注解?
    LOCAL_VARIABLE, //加在方法内变量上的注解?
    ANNOTATION_TYPE,? //加在注解上的注解
    PACKAGE?        //加在包名上的注解
    }

@Retention中:

public enum RetentionPolicy {

    SOURCE, //只保留在源码中

    CLASS, //保留在类字节码中

    RUNTIME //保留在运行时(我们自定义注解一般都是在运行时检测的)
}


ok,继续,

@InjectView中只接收一个int类型的值,用于表示view的id,

@OnClick中接收一个int[],表示可以接收多个view的id,绑定到同一个click执行方法上



既然有了注解,就少不了注解的解释者,

先分析一个view赋值的过程:

1. 首先要有一个rootView,用于findView
2. 还需要有目标view的id,这个就是@InjectView中的值
3. 需要目标对象
4. 把从rootView找到的view赋值给目标对象的目标变量

ok,看代码

public class InjectViewProcessor implements ProcessorIntf<Field>{
    @Override
    public boolean accept(AnnotatedElement e) {
        return e.isAnnotationPresent(InjectView.class);
    }

    @Override
    public void process(Object object, View view, Field field) {
        InjectView iv = field.getAnnotation(InjectView.class);
        final int viewId = iv.value();
        final View v = view.findViewById(viewId);
        field.setAccessible(true);
        try {
            field.set(object, v);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

怎么还实现了一个接口呢?

public interface ProcessorIntf<T extends AnnotatedElement> {
    public boolean accept(AnnotatedElement t);

    public void process(Object object, View view, T t);
}

这个泛型接口接收一个AnnotatedElement的子类,它是什么呢?

在java中,Field,Method,Constructor…一切可注解的对象都实现了AnnotatedElement接口。ProcessorIntf用于给解析器提供一系列通用行为:

/*
 * 每个不同的处理器都会通过这个方法来告诉最终的调度者,这个注解是否由我来
 * 处理
 */
public boolean accept(AnnotatedElement t); 

process方法换个角度一想,无论是@InjectView,@InjectString,@OnClick等等任何注入的操作,是不是应该都需要这几个条件呢?所以:

/*这样看来,可以把处理行为抽象成这几个参数?
 *第一个object是目标对象,
 *第二个view是根view
 *第三个是加上注解的那个东西
 */
public void process(Object object, View view, T e);

所以在InjectViewProcessor中是这样实现的:

@Override
public boolean accept(AnnotatedElement e) {
//如果当前这个AnnotatedElement实例加有InjectView注解,则返回true
   return e.isAnnotationPresent(InjectView.class);
}

如果是返回true,说明这个它可以处理,则走到

@Override
    public void process(Object object, View view, Field field) {
        InjectView iv = field.getAnnotation(InjectView.class);
        final int viewId = iv.value();
        final View v = view.findViewById(viewId);
        field.setAccessible(true);
        try {
            field.set(object, v);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

代码很简单,就简单说明下:

1.先拿到具体的注解类,并拿到里面的值比如R.id.txt等
2.从跟view中findViewById拿到指定的view
3.为防止目标属性是private的,将可访问设置为true,并赋值
-.ok,这样一个view就被注入到目标属性中了


接着再分析@OnClick的实现:

1.我需要知道要绑定点击事件的view的id,这个在@OnClick中的value指定
2.和之前的一样,也需要一个根view来拿到具体的view
3.要注入的对象,这个是必须的,
4.要把某个方法绑定为点击事件的回调,我还要知道是哪个方法
5.ok上述的条件都满足后,就可以拿到find来的view并设置setOnClickListener,在收到回调的时候,去调用指定方法,来实现间接的绑定

好了,分析就到这里面,看代码:

public class OnClickProcessor implements ProcessorIntf<Method> {
    @Override
    public boolean accept(AnnotatedElement e) {
        return e.isAnnotationPresent(OnClick.class);
    }

    @Override
    public void process(Object object,View view, Method method) {
        final OnClick oc = method.getAnnotation(OnClick.class);
        final int[] value = oc.value();
        for (int id : value) {
            view.findViewById(id).setOnClickListener(new InvokeOnClickListener(method,object));
        }
    }

    private static class InvokeOnClickListener implements View.OnClickListener {

        public Method method;
        public WeakReference<Object> obj;
        private boolean hasParam;

        InvokeOnClickListener(Method m, Object object) {
            this.method = m;
            this.obj = new WeakReference<Object>(object);
            final Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes == null || parameterTypes.length == 0) {
                hasParam = false;
            } else if (parameterTypes.length > 1 || !View.class.isAssignableFrom(parameterTypes[0])) {
                throw new IllegalArgumentException(String.format("%s方法只能拥有0个或一个参数,且只接收View", m.getName()));
            } else {
                hasParam = true;
            }
        }

        @Override
        public void onClick(View v) {
            //点击事件触发了
            Object o = obj.get();
            if (o != null) {
                try {
                    if (hasParam) {
                        method.invoke(o, v);
                    } else {
                        method.invoke(o);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

再来分析代码:

//这个很简单,就是告诉管理器我响应OnClick注解
 @Override
    public boolean accept(AnnotatedElement e) {
        return e.isAnnotationPresent(OnClick.class);
    }
/*
* 这个也还是一样,
* 1.先拿到具体的注解对象 ,并拿到里面的值
* 2.因为存在多个id绑定到一个方法上的情况,所以一个循环不可少
* 3.就是拿到view,设置监听事件
* 4.但是,这个InvokeOnClickListener是个什么东西呢?
*/
@Override
    public void process(Object object,View view, Method method) {
        final OnClick oc = method.getAnnotation(OnClick.class);
        final int[] value = oc.value();
        for (int id : value) {
            view.findViewById(id).setOnClickListener(new InvokeOnClickListener(method,object));
        }
    }
//先说下,这里面的InvokeOnClickListener是一个中间件,注册给系统,系统在得到点击事件后,通知给InvokeOnClickListener,在这个里面再调用你所指定的方法。

private static class InvokeOnClickListener implements View.OnClickListener {

        public Method method;
        public WeakReference<Object> obj;
        private boolean hasParam;

        InvokeOnClickListener(Method m, Object object) {
            this.method = m;
            this.obj = new WeakReference<Object>(object);
            final Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes == null || parameterTypes.length == 0) {
                hasParam = false;
            } else if (parameterTypes.length > 1 || !View.class.isAssignableFrom(parameterTypes[0])) {
                throw new IllegalArgumentException(String.format("%s方法只能拥有0个或一个参数,且只接收View", m.getName()));
            } else {
                hasParam = true;
            }
        }

        @Override
        public void onClick(View v) {
            //点击事件触发了
            Object o = obj.get();
            if (o != null) {
                try {
                    if (hasParam) {
                        method.invoke(o, v);
                    } else {
                        method.invoke(o);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

ok,构造函数中,拿到了要回调的方法,和目标对象。这是必须的。

然后就是几个简单的判断 :

1.先拿到方法的参数,看看有没有参数 , 没有就纪录下hasParam为false,

2.有参数的话,判断是几个参数,超过两个了直接就报错吧,那多的参数我从哪里给呢。

3.ok很听话的只接收一个View,hasParam为true


        @Override
        public void onClick(View v) {
            //点击事件触发了
            Object o = obj.get(); //为什么要用一个WeakReference,其实没有必要,因为activity消亡了,view也就消亡了,这个循环引用似乎不存在,但是我还是写下,假如有假如呢。
            if (o != null) {
                try {
                    if (hasParam) { //有参数,就把view传过去
                        method.invoke(o, v);
                    } else { //没有参数就直接调
                        method.invoke(o);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

ok,InjectView和OnClick到这里就已经完工了, 但是直接拿来用,却是有点不方便的,于是加一个管理者:

public class Injector {

    private static List<? extends ProcessorIntf<? extends AccessibleObject>> chain = Arrays.asList(new InjectViewProcessor(), new OnClickProcessor());

    public static void inject(Activity act) {
        inject(act,act.getWindow().getDecorView());
    }

    public static void inject(Object obj, View rootView) {
        final Class<?> aClass = obj.getClass();
        final Field[] declaredFields = aClass.getDeclaredFields();
        for (Field f : declaredFields) {
           doChain(obj,f,rootView);
        }
        final Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method m : declaredMethods) {
            doChain(obj, m, rootView);
        }
    }

    private static void doChain(Object obj,AccessibleObject ao, View rootView) {
            for (ProcessorIntf p : chain) {
                if(p.accept(ao)) p.process(obj,rootView,ao);
            }
    }
}

管理者很简单,提供了两个静态方法,一个给activity用, 一个可以给fragment,viewholder等任何对象用。其实最终用的也是同一个方法。

这里我用了一个处理器链的方式,假如后面我还要实现注入@string/xx,@color/xxx , @Service private WindowManager wm;等等等,把实现好的处理器加入到chain链中即可。

//这个就是前面已经说过的,把每个遍历到的方法或者属性,甚至是构造方法,类等等通过处理器链来询问这个注解你accept吗?接受则交给它来处理,
private static void doChain(Object obj,AccessibleObject ao, View rootView) {
            for (ProcessorIntf p : chain) {
                if(p.accept(ao)) p.process(obj,rootView,ao);
            }
    }
}

好了,长篇大论结束,最后贴下最终的调用:

public class TestActivity extends AppCompatActivity {

    @InjectView(R.id.txt)
    private TextView txt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_activity);
        Injector.inject(this);
    }

    @OnClick({R.id.btnTestOne,R.id.btnTestTwo})
    public void btnTestOne(Button view) {
        final int id = view.getId();
        if (id == R.id.btnTestOne) {
            txt.setText("按钮一被点击");
        }else{
            txt.setText("按钮二被点击");
        }

    }

}

ok,如果我要实现一个注入contentView怎么办呢?



留给读者。

时间: 2024-10-03 17:03:48

都说依赖注入,我就从实现的角度来一发,以android作为引子..的相关文章

深入理解ASP.NET 5的依赖注入

(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:ASP.NET 5整个底层都架构于依赖注入机制之下,今天介绍的文章详细介绍了内置依赖注入容器. 在ASP.NET之前的版本中,虽然各个框架(MVC.WEB API.SignalR)都支持依赖注入,但是由于框架是相互独立的,所以使用依赖注入的方式都有所差异.Katana曾经期望通过OWIN来统一这些差异,直到ASP.NET 5中才得以实现.这得利于整个ASP.NET 5都是构建于依赖注入机制之下

谈谈php依赖注入和控制反转

要想理解php依赖注入和控制反转两个概念,就必须搞清楚如下的问题: DI--Dependency Injection   依赖注入 IoC--Inversion of Control  控制反转 1.参与者都有谁? 答:一般有三方参与者,一个是某个对象:一个是IoC/DI的容器:另一个是某个对象的外部资源.又要名词解释一下,某个对象指的就是任意的.普通的Java对象; IoC/DI的容器简单点说就是指用来实现IoC/DI功能的一个框架程序:对象的外部资源指的就是对象需要的,但是是从对象外部获取的

【SSH系列】深入浅出spring IOC中三种依赖注入方式

spring的核心思想是IOC和AOP,IOC-控制反转,是一个重要的面向对象编程的法则来消减计算机程序的耦合问题,控制反转一般分为两种类型,依赖注入和依赖查找,依赖什么?为什么需要依赖?注入什么?控制什么?依赖注入和控制反转是一样的概念吗?接触新的知识,小编的脑袋中全是大大的问号,不过没有关系,今天这篇博文,小编主要来简单的介绍一下在spring IOC中依赖注入的方法. 依赖注入和控制反转,目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性.我们可以从以下几个方面理解: a.参与者都

工厂方法模式与IoC/DI控制反转和依赖注入

IoC——Inversion of Control  控制反转 DI——Dependency Injection   依赖注入 要想理解上面两个概念,就必须搞清楚如下的问题: 参与者都有谁? 依赖:谁依赖于谁?为什么需要依赖? 注入:谁注入于谁?到底注入什么? 控制反转:谁控制谁?控制什么?为何叫反转(有反转就应该有正转了)? 依赖注入和控制反转是同一概念吗? 下面就来简要的回答一下上述问题,把这些问题搞明白了,IoC/DI也就明白了.(1)参与者都有谁: 一般有三方参与者,一个是某个对象:一个

php之依赖注入和控制反转

  DI——Dependency Injection   依赖注入  IoC——Inversion of Control  控制反转  要想理解上面两个概念,就必须搞清楚如下的问题: 1.参与者都有谁? 答:一般有三方参与者,一个是某个对象:一个是IoC/DI的容器:另一个是某个对象的外部资源.又要名词解释一下,某个对象指的就是任意的.普通的Java对象; IoC/DI的容器简单点说就是指用来实现IoC/DI功能的一个框架程序:对象的外部资源指的就是对象需要的,但是是从对象外部获取的,都统称资源

Android中的依赖注入:Dagger函数库的使用(一)

--欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢-- 原文链接:http://antonioleiva.com/dependency-injection-android-dagger-part-1/ 本文Gitbooks链接:http://asce1885.gitbooks.io/android-rd-senior-advanced/content/androidzhong_de_yi_lai_zhu_ru_ff1a_da

Laravel 服务容器 IoC(控制反转) 和 DI(依赖注入)

Laravel 服务容器 IoC(控制反转) 和 DI(依赖注入) IoC 容器, laravel 的核心 Laravel 的核心就是一个 IoC 容器,根据文档,称其为“服务容器”,顾名思义,该容器提供了整个框架中需要的一系列服务.作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念. 本文一大半内容都是通过举例来让读者去理解什么是 IoC(控制反转) 和 DI(依赖注

(转)依赖注入和控制反转

发现一篇介绍依赖注入和控制反转的文章,特转载以备后用. 文章地址1:http://baitai.iteye.com/blog/792980 相关文章:http://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html: http://www.cnblogs.com/xingyukun/archive/2007/10/20/931331.html: http://www.cnblogs.com/zhenyulu/articles/6417

PHP 依赖注入和控制反转再谈(二)

今天有个朋友看到yii2中介绍的依赖注入一头雾水,之前我写过类似的文章发给他看了,可能还没深入理解吧,这里我再通俗点描述下依赖注入的原理吧,尽可能滴说通俗易懂一点吧:先还是扯下概念性滴问题(概念问题我个人的原则总是先简单瞟一眼概念,通过实例来对概念加深理解了) 要想理解 PHP 依赖注入 和 控制反转 两个概念,我们还是必须搞清楚下面的两个问题: DI -- Dependency Injection 依赖注入 IoC -- Inversion of Control 控制反转 什么是依赖注入 没有