自己撸一个基于运行时注解的简单IOC框架
来源:互联网 发布:知乎网络加载错误 编辑:程序博客网 时间:2024/06/11 03:05
概述
日常开发中的各种注解还是比较常见的,比如代码里面的各种@Override,@Nullable,后端的话Spring里面的各种注入以及依赖注入框架Dagger.对于更关注于界面的客户端,大名鼎鼎的ButterKnife以及XUtils中的ViewInject也是基于注解实现和使用的。先不去纠结运行效率,自己来写一段简单的注解代码,了解背后的原理。
基本原理
基于注解的开发,有一个基本概念IOC(Inverse Of Control):控制反转,简单说就是new对象的事情交给框架完成。但实际上类似ButterKnife等框架要做的事情是不让开发人员写一堆的findViewById、成员变量和类型转换(Android API26以后findViewById的返回值有变化,不用再强转),要达到此效果还需要配合ButterKnife在Android Studio中的插件使用。而实现注入的基础就是反射+自定义注解。
注入实现的基础步骤
先定义一个注解,取名Inject.通常自定义注解里面要声明两个关键字@Rentention(该注解何时生效)、@Target(该注解的适用范围)。注解内部可以定义其可以接受的类型,名称名可以随意,但如果是value的话,可以省略。这里我定义为name
@Retention(RetentionPolicy.RUNTIME) //该注解会被加载到JVM中@Target(ElementType.FIELD) //该注解只能用在字段上面(成员变量)public @interface Inject { //代表该自定义注解类可以接受的类型 String name();}
新建一个JavaBean,取名Person,创建一个成员变量player,并且使用@Inject,其值为欧文。注意因为名称是name,所以不可以忽略。要用’名称=值’的形式,如果名称为value才是可以缺省的。
public class Person { @Inject(name="欧文") private String player; @Override public String toString() { return "Person{" + "player='" + player + '\'' + '}'; }}
在主方法中,通过暴力反射获取Person里的player字段,再获取该字段上的自定义注解对象(要传入具体的注解类,因为一个字段是可以跟多个注解的),进一步获得该注解对象的值,至此就可以将值赋给player字段,也就完成了对player的注入。
Person person = new Person();Class clazz = Person.class;Field field = clazz.getDeclaredField("name");/* * 获取该字段上的自定义注解类对象 */Inject annotation = field.getAnnotation(Inject.class);/** 获取Inject上的值 */String name1 = annotation.name();field.setAccessible(true);field.set(person, name1);System.out.println(person);
类似的,如果要注入View到成员变量,也是这个路数,只不过相应的自定义注解的值类型是int(控件ID),当然反射获取的就是整个Activity中定义的成员变量了,因为我们对控件的声明都会写在这里
注入View到成员变量
1.自定义一个注解类ViewInject(随意命名),运行时获取控件的ID,声明在字段上:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface ViewInject { int value();}
2.以Activity组件示例,成员变量声明一个布局中的控件,并为其添加注解:
@ViewInject(R.id.text)private TextView mTextView;
3.setContentView后,布局组件生效,将Activity作为参数,开始注入: ViewUtils.inject(this);
4.开始完成ViewUtils类,也就是真正执行注入操作的地方:
private static void bindView(Activity activity) { //1.获取声明组件的字节码 Class<? extends Activity> clazz = activity.getClass(); //2.获取字节码中所有字段 Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { //3.遍历,筛选出只添加了@ViewInject的字段 ViewInject annotation = field.getAnnotation(ViewInject.class); if (annotation != null) { //4.获取自定义注解上的id,通过id获取到该控件 View view = activity.findViewById(annotation.value()); if (view != null) { try { //5.通过暴力反射将view赋值给该字段 field.setAccessible(true); field.set(activity, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }}
5.Activity里验证:mTextView.setText("成功注入");
事件的绑定
以最简单的点击事件为例,展示如何通过注解实现事件的绑定。同样也是为了简单,先看怎么实现单个控件的事件绑定。
1.自定义一个注解类OnClick(随意命名),运行时获取控件的ID,声明在方法上:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface OnClick { int value();}
2.以Activity组件示例,随便命名一个方法作为点击事件绑定的对应方法,并为其添加注解,这里的形参为空:
@OnClick(R.id.text)private void clickView() {}
3.完成ViewUtils类中进行事件绑定的代码部分
private static void bindOnClick(final Activity activity) { //1.获取声明组件的字节码 Class<? extends Activity> clazz = activity.getClass(); //2.获取所有声明的方法 Method[] declaredMethods = clazz.getDeclaredMethods(); for (final Method method : declaredMethods) { //3.遍历,筛选出只添加了@OnClick的字段 OnClick annotation = method.getAnnotation(OnClick.class); if (annotation != null) { final int value = annotation.value(); //4.获取自定义注解上的id,通过id获取到该控件 final View view = activity.findViewById(value); //5.在该控件的点击事件中,通过暴力反射调用对应在Activity中声明的方法 view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { method.setAccessible(true); method.invoke(activity); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }); } }}
和View的注入类似,就是把本来在Activity做的setOnClickListener拿到这里面来,而通过反射调用在Activity里声明的方法,也就相当于绑定了事件。注意因为声明的方法是无参的,因此反射调用方法时可变参数的长度为0
4.Activity里验证:
@OnClick(R.id.text)private void clickView() { Toast.makeText(this, "事件绑定成功", Toast.LENGTH_SHORT).show();}
总结
一个很简单的IOC框架完成了,但是距离实际使用差的还多,一个是基于运行时的注解通过反射完成,效率还是多少受了影响,虽然实际上是感受不出来的。(SUN已经做了大量优化)。更主要的是这样在开发中只是换了中形式,如果布局中有一堆需要操作的控件,写一堆@ViewInject和各种控件Id,而且还要自己声明变量,并没有提高多少效率。看看耳熟能详的ButterKnife,其实是基于编译时解析的技术。后续
- 自己撸一个基于运行时注解的简单IOC框架
- 打造自己的IOC注解框架------findViewById
- 使用注解打造自己的IOC框架
- 自己写一个IOC注解框架,里面有分析xutils3跟utterknife的IOC的源码分析--binbinyang
- 自己动手写一个简单的IOC框架,使用注解绑定资源和事件
- Android中使用注解打造自己的IOC框架
- Android 仿ButterKnife写自己的IOC注解框架
- 打造自己的框架之使用注解制作IOC组件
- 写一个自己的Spring框架——简单实现IoC容器功能
- 自己实现一个IOC框架
- Android 自己打造IOC注解框架
- 基于全注解的SSH简单框架
- 基于全注解的SSH简单框架
- 基于注解的IOC配置
- 一个简单的基于注解的controller
- 自定义一个Java运行时注解框架
- 来,咱们自己写一个Android的IOC框架!
- 自己实现的一个简易Spring框架(IoC+AOP)
- MFC CMFCPropertyGridCtrl控件使用问题:刷新后底部出现黑边
- AOP编程思想理解
- Automatic Inference of Search Patterns for Taint-Style Vulnerabilities
- Leetcode: sort-list
- shell编程
- 自己撸一个基于运行时注解的简单IOC框架
- 广义线性模型.多项式分布.softmax_????????????
- 激活函数
- RobotFramework学习笔记二:遇到Frame框架
- Maven与nexus关系
- [Python]
- 《数字技术》连载28:第4章 信息的寄存传输和转换 第1节 信息的编码,编码器
- 读写分离MySQL+MHA+MAXSCALE集群高可用
- TypeError: __init__() takes exactly 2 arguments (1 given)(已解决)