美文网首页
IOC(一)-反射注入

IOC(一)-反射注入

作者: 烧伤的火柴 | 来源:发表于2020-04-02 17:12 被阅读0次

IOC介绍

IOC(inversion of control)的中文解释是“控制反转”,控制反转是一种软件设计的思想,指导我们设计出一种弱耦合的程序。
依赖注入(Dependency Injection,简称 DI)是IOC的一种实现方式:在类A的实例创建过程中就创建了实例B对象,通过类型或者名称来判断将不同的对象注入到不同的属性中。

简单应用

用过butterKnife的都知道butterKnife的便利性,butterKnife早期就是利用注解配置文件加上IOC控制 反射注解内容实现对象的注入。

我们可以参考butterKnife写一个简单的demo。

注入布局文件

我们传统的编码方式是:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
    }

我们可以通过注解把布局文件定义到MainActivity类上。
1.定义注解

@Retention(RetentionPolicy.RUNTIME)//运行期
@Target(ElementType.TYPE)//类上
public @interface ContentView {

    @LayoutRes int value();//布局文件id
}

2.定义控制单元即调用注解得到资源id,注入到程序中
为了通用性,这里控制单元作为一个工具类独立出来,供其他地方使用,并且可以处理其他的注解进行注入。

public class InjectUtils {

    /**
     * 反射设置资源
     *
     * @param context activity等
     */
    public static void inject(Object context) {
        injectContentView(context);
    }

    private static void injectContentView(Object context) {
        ContentView contentView = context.getClass().getAnnotation(ContentView.class);
        if (contentView != null) {
            int contentViewId = contentView.value();
            if (contentViewId == 0) {
                return;
            }
            try {
                Method setContentViewMethod = context.getClass().getMethod("setContentView", int.class);
                setContentViewMethod.invoke(context, contentViewId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

这里要注意使用getMethod方法,这个方法可以获得父类中定义的方法。
injectContentView方法内部主要逻辑是从ContentView注解对象中得到布局资源id contentViewId,然后调用反射方法讲contentViewId对象注入到方法setContentView中。
3.使用
为了复用性,这里讲调用方法写到基类BaseActivity中

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);
    }
}

MainActivity.java

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

这样就通过ioc处理注解配置布局文件,反射将布局注入到activity中了。

进阶应用

我们也可以像butterKnife那样注解成员变量,注入对象。
1.定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {

    @IdRes int value();
}

2.处理注解中的资源

public class InjectUtils {

    private static SparseArray<View> viewSparseArray = new SparseArray<>();

    /**
     * 反射设置资源
     *
     * @param context activity等
     */
    public static void inject(Object context) {
        injectContentView(context);
        injectViews(context);
    }

   ......

    private static void injectViews(Object context) {
        Method findViewByIdMethod = null;
        try {
            Field[] fields = context.getClass().getDeclaredFields();
            for (Field field : fields) {
                BindView bindViewAnnotation = field.getAnnotation(BindView.class);
                if (bindViewAnnotation == null) {
                    break;
                }
                int viewId = bindViewAnnotation.value();
                if (findViewByIdMethod == null) {
                    findViewByIdMethod = context.getClass().getMethod("findViewById", int.class);
                }
                Object view = findViewByIdMethod.invoke(context, viewId);
                field.set(context, view);
                //缓存起来
                viewSparseArray.put(viewId, (View) view);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里通过反射findViewById方法通过资源id得到View对象,并且把对象设置给定义了注解的属性。
3.使用

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @BindView(R.id.button1)
    Button button1;
    @BindView(R.id.button2)
    Button button2;
    ......
}

注册点击事件

事件的类型有很多种,比如onClic,onLongClick,onTouch,onCheck等等,为了能够动态的定义事件,这里在引入一个注解定义事件类型,事件方法,事件赋值方法。

1.定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
    //订阅关系
    String listenerSetter();
    //事件类型 OnClickLi
    Class<?> listenerType();
    //事件处理
    String actionMethod();
}

应用EventBase

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",
        listenerType = View.OnClickListener.class,
        actionMethod = "onClick")
public @interface OnClick {
    @IdRes int[] value();
}

2.处理注解

public class InjectUtils {

    private static SparseArray<View> viewSparseArray = new SparseArray<>();

    /**
     * 反射设置资源
     *
     * @param context activity等
     */
    public static void inject(Object context) {
        injectContentView(context);
        injectViews(context);
        injectActionEvent(context);
    }

    ......

    private static void injectActionEvent(Object context) {
        try {
            Method[] declaredMethods = context.getClass().getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                Annotation[] annotations = declaredMethod.getAnnotations();//1
                if (annotations.length > 0) {
                    for (Annotation annotation : annotations) {
                        //为了扩展性 这里不指定具体的注解了
//                        if (annotation.annotationType()== OnClick.class) {
                        //过滤出有EventBase注解修饰的 注解
                        EventBase eventBaseAnnotation = annotation.annotationType().getAnnotation(EventBase.class);
                        if (eventBaseAnnotation == null) {
                            continue;
                        }

                        Method valueMethod = annotation.getClass().getDeclaredMethod("value");
                        int[] value = (int[]) valueMethod.invoke(annotation);
                        if (value != null && value.length > 0) {
                            for (int id : value) {
                                View view = viewSparseArray.get(id);
                                if (view == null) {
                                    //这里使用getMethod 得到父类的方法
                                    //1.得到注册事件的view
                                    Method findViewByIdMethod = context.getClass().getMethod("findViewById", int.class);
                                    view = (View) findViewByIdMethod.invoke(context, id);
                                    viewSparseArray.put(id, view);
                                }
                                //调用事件
                                //          button1.setOnClickListener(new View.OnClickListener() {
                                //               @Override
                                //               public void onClick(View v) {
                                //
                                //            }
                                //        });
                                if (view == null) {
                                    continue;
                                }

                                String listenerSetter = eventBaseAnnotation.listenerSetter();//setOnClickListener
                                String actionMethod = eventBaseAnnotation.actionMethod();//onClick
                                Class<?> listenerType = eventBaseAnnotation.listenerType();//View.OnClickListener.class
                                Method setListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                                //使用动态代理
                                setListenerMethod.invoke(view, Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{listenerType}, new ListenerInvocationHandler(context, declaredMethod)));

                            }
                        }


//                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

因为方法会有很多种注解修饰,比如@TargetApi(), @Deprecated等等,所以注释1出获取所有注解,然后过滤含有EventBase注解修饰的注解,通过id获取view的时候,可以利用前边@BindView注解缓存的view对象,避免创建多余的对象。在反射调用事件Listener的时候,为了通用性,使用动态代理的方式,将接口的onXXX方法委托给代理对象处理。动态代理的定义如下:

public class ListenerInvocationHandler implements InvocationHandler {

    private Object object;
    private Method eventMethod;

    public ListenerInvocationHandler(Object object, Method eventMethod) {
        this.object = object;
        this.eventMethod = eventMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return eventMethod.invoke(object, args);
    }
}

注意在调用代理的时候是:eventMethod.invoke(object, args); 即将onXXX的参数也设置进去了。所以我们在定义方法的时候方法的参数要和Listner接口中事件方法的一样。另外这里恰好接口就一个事件的方法,如果有多个方法也会有问题了。

3.使用

 @OnClick({R.id.button1, R.id.button2})
    void onClick(View v){
        if (v.getId()==R.id.button1) {
            Toast.makeText(this, "点击了第一个button",Toast.LENGTH_LONG).show();
        }else if (v.getId()==R.id.button2){
            Toast.makeText(this, "点击了第二个button",Toast.LENGTH_LONG).show();
        }
    }

总结

这里只是通过这个例子学习一下IOC的原理,不推荐用到项目中,因为反射是在运行期调用的,所以效率低下,而且还有很多地方没有考虑到。

相关文章

  • 每日java--Day02:依赖注入和控制反转

    昨天学的反射,今天学习依赖注入。控制反转 IOC:利用了反射机制依赖注入 DI:是 IOC 的一种形式,使类与类之...

  • IOC(一)-反射注入

    IOC介绍 IOC(inversion of control)的中文解释是“控制反转”,控制反转是一种软件设计的思...

  • Spring AOP IOC 实现原理,面试问到如何回答

    IOC:控制反转,通过依赖注入方式实现,IOC利用java反射机制,AOP利用代理模式。所谓控制反转是指,本来被调...

  • 反射动态代理注解-依赖注入

    ioc 依赖注入 + 反射动态代理 基本思想 = 配置文件 + factory实例https://www.cnbl...

  • IOC(二)-注解注入

    介绍 上一节中介绍了IOC的概念,以及使用反射注入对象,但是反射的效率比较低效,所以butterKnife换成了注...

  • 手写源码(二):自己实现SpringIOC

    手写IOC IOC控制反转就是通过反射机制帮我们托管了所有的类。我想要自己实现的就是使用XML注入Bean和使用注...

  • spring记错本

    1.IOC:控制反转(DI:依赖注入)对控制反转和依赖注入的理解:image.png 其中原理是利用反射机制调用s...

  • Spring:简单实现IOC

    使用反射实现简单的IOC,目标:1.仅支持单例、set方法注入的IOC2.解决循环依赖 解决循环依赖:个人认为有两...

  • Spring AOP IOC

    IOC 反射在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。 总而言...

  • Dagger2常用注解诠释

    依赖注入 控制反转(IoC)与依赖注入(DI)浅谈依赖注入理解依赖注入(IOC)和学习Unity Gradle配置...

网友评论

      本文标题:IOC(一)-反射注入

      本文链接:https://www.haomeiwen.com/subject/iomxphtx.html