IOC技术-编译时注入(Butterknife,dagger2的实现原理)

不同于运行时注入的思想,编译时注入主要是借助APT工具,在编译时生成辅助类,运行时通过反射加载辅助类,执行其构造方法,来达到解耦,方便编程的目的.

编译时注入比较流行的框架有Butterknife,Dagger2,针对android平台有daggerAndroid,实际daggerAndroid是对dagger2的优化,因为dagger2使用过程中有点违反了注入的核心思想(在注入的类里面,能够看到提供注入的类的对象),而daggerAndroid解决了这个问题.

Butterknife,dagger2通常会搭配MVC,MVP去使用,如果你使用了MVVM,那就不需要使用了,原因是MVVM中的对象,在后台使用DataBinding去生成了,这个原理跟Butterknife,dagger2实现是类似的.

至于DataBinding,Butterknife,dagger2的具体使用,我们这里不讨论,网上有很多的介绍文档,这里主要是看它的实现原理,就是你按照框架的用法加了那几行代码,怎么实现的相应的功能.

其实,还是要先沾几行代码,不然上面说"那几行代码",你一定会迷糊,到底是那几行呢?

下面的代码Butterknife使用的demo.

添加依赖库,module的build.gradle

dependencies {
    implementation 'com.jakewharton:butterknife:10.2.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
}
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.button)
    public Button mBtn;

    @BindView(R.id.imageView)
    protected ImageView mIv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.button)
    public void onClick(View view) {

    }
}

上面就是Butterknife的简单实用,除了控件对象上的@BindView的注解,事件监听上的@OnClickd的注解,还有OnCreate中的BuffterKnife.bind(this)的调用,

这里要讨论的是,这几行代码是怎么实现的控件对象的赋值,及监听事件的调用的.

在使用了Butterknife的项目编译后,在app/build/intermediates目录会多一个MainActivity_ViewBinding.class文件.

这个文件时借助APT工具生成的,APT(Annotation Processing Tools)是一个处理注解的工具,在编译使用这个工具扫描和处理注解,来生成.class文件,

到这里,应该就明白了,这些框架的原理就是通过注解,借助APT工具,生成了一些辅助类.

然后在运行时,把生成辅助类加载到内存,利用反射调用辅助类的构造函数等方法.

 

APT工具,更确切的说时一个javac的工具,只是去处理注解,不会去影响源文件.它作用的过程,在如下图的位置:

Javac对Java文件的编译过程:,

  1. 解析和填充符号表过程。
  2. 插入式注解处理器的注解处理过程。
  3. 分析和字节码生成过程。

图中的Round意思:如果注解处理器在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每次循环称为一个Round

看完了辅助文件的生成过程,再来看Butterknife是怎么实现绑定功能的,可以想到,绑定的实现就是辅助文件的被调用执行的过程。

启动是从MainActivity的onCreate方法中的

ButterKnife.bind(this); 这句调用开始的。

Butterknife.java,这里的DecorView是View树的根view,包括了应用自定义的contentView及装饰条。

  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }

 下面是加载MainActivity_ViewBinding.class类,并调用其构造方法的过程:

 private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);

    String clsName = cls.getName();
//如果是android framework中的类,或者java公共的api,就不做处理
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
//代码执行到这里,apt生成的那个辅助类,就被加载进内存了。然后调用构造方法,就把类实例构建出来了。
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

接着看辅助类的构造函数,

MainActivity_ViewBinding.class
  @UiThread
    public MainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
        View view = Utils.findRequiredView(source, 2131165251, "field 'mBtn' and method 'onClick'");
        target.mBtn = (Button)Utils.castView(view, 2131165251, "field 'mBtn'", Button.class);
        this.view7f070043 = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
            public void doClick(View p0) {
                target.onClick(p0);
            }
        });
        target.mIv = (ImageView)Utils.findRequiredViewAsType(source, 2131165290, "field 'mIv'", ImageView.class);
    }

跟进去看下构造函数中调用的Utils中两个方法的实现:

  public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
    View view = findRequiredView(source, id, who);
    return castView(view, id, who, cls);
  }

  public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
    }
  }

一个是通过findViewById来获取到View,另一个把view转成具体类型,赋值给MainActivity中的控件对象,然后如果有事件绑定,在生成事件绑定的代码.这样这个框架就完成了对象绑定,和事件绑定.

前面这么多内容都是铺垫,接下来讨论的才是这篇文章的重点, 就是这个辅助文件是怎么生成的,当然下面的实现不会写的像Buterknife那么复杂,重点是弄明白这个框架的实现原理.

下面就是新建一个app,然后,自定义注解,自定义注解处理器,实现对象,事件的绑定.这里分三个模块:

1),app本身,

2),annotations,提供注解的定义,供整个框架使用,

3),annotation_compiler,注解处理模块,来处理注解.

这三个模块间的关系,

app依赖annotations, annotation_compiler,

Annotation_compiler依赖annotations, 这两个模块都是运行在电脑端的,是在编译期起作用,所以选择的module类型是java library.

在app的build.gradle中修改下annotation_compiler的依赖配置,因为他是处理注解的,所以改为annotationProcessor,以方便使用annotation相关的语法.

在annotations模块中,定义一个用在控件Button上的注解:


//这个注解应用在按钮,
//保留到源码阶段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value();
}

在App模块定义一个接口类,ButterKnifeIBinder.java,提供一个bind方法,功能跟ButterKnife的bind方法类似,用于绑定Activity使用,这里定义成一个泛型类,可以绑定多种Activity.

public interface ButterKnifeIBinder<T> {
    void bind(T activity);
}

在annotations_compiler模块中的build.gradle中,注册下使用APT的功能,

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(path: ':annotations')
    //注册APT的功能
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
}

到这里,准备工作已就绪,下面就开核心代码的编写,我把注释直接写在代码中,

 

//注解处理器的功能类,扫描注解,生成辅助类
//使用前,要用注解@AutoService(Process.class)注册下,这个注册就类似与在manifest里面注册四大组件,

说明下代码处理的逻辑:

1,拿到项目中所有应用了属性(@BindView)注解的VariableElements,这是个map集合,键值是Activity的全类名,

2,拿到项目中所有应用了事件(@OnClick)注解的ExecutableElements.这是个map集合,键值是Activity的全类名

对于上面这两类elements集合,是包含了项目中所有Activity中的应用了这两个注解的属性,事件,但是我们生成辅助类时,是要每个Activity生成单独的文件,所以会对这两个集合重新分类,把属于同一个Activity的放入同一个集合,

3,拿到所有需要处理的activity的集合,在每个需要处理的activity元素中,分别拼接属性代码,事件代码.

4,属性赋值,方法代码的生成,就是字符串的拼接.

@AutoService(Processor.class)
public class AnnotationnCompiler extends AbstractProcessor {
    Logger logger = Logger.getLogger(AnnotationnCompiler.class.toString());
    Filer mFiler;
    final String mIndentSpace1 = "    ";
    final String mIndentSpace2 = "        ";
    final String mIndentSpace3 = "            ";

    //使用APT处理那些注解,
    // 注意这里导入BindView是注解模块中我们自己定义的注解类:
//import com.test.annotations.BindView;
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

//支持的java的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

//获取一个用来生成辅助类文件的对象,
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
    }

//在这个方法中,根据需要,完成APT的功能,
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<String> activitySets = new HashSet<>();

        //参数annotations,拿到我们关注的那个文件中的所有注解,这里指的是MainActivity中的所有注解,
        //参数roundEnv,上下文的环境变量,

        //获取使用BindView注解的控件对象,这里获取的是一个集合,就是在MainActivity中,
        // 所有用BindView注解的控件对象都会返回过来,Element是对控件(如Button,ImageView)的一个封装。
        // Element有几个具体的实现类,TypeElement,MethodElement,VarialbeElement.

        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindView.class);


        //需要注意的一点,elementsAnnotatedWith这集合获取到的是整个项目中,所有类中,使用了BindView注解的属性,
        // 比如项目中有3个Activity,每个Activity都有被BindView注解的属性Button,
        // 那么这个elementsAnnotatedWith集合拿到的是3个activity中的所有被BindView注解的属性Button,
        // 但是我们在生成辅助类时,每个activity是要单独生成一个辅助类的,
        // 所以这里要分离出每个activity中的属性,然后用map分别保存下来,
        // 其中map 的key就是activity对象,value是属性集合。

        Map<String, List<VariableElement>> mapVariable = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement)element;
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            activitySets.add(activityName);
            List<VariableElement> variableElements = mapVariable.get(activityName);
            if (null == variableElements) {
                variableElements = new ArrayList<>();
                mapVariable.put(activityName, variableElements);
            }
            variableElements.add(variableElement);
        }

//事件集合的处理
        Set<? extends Element> elementsAnnotatedMethods = roundEnv.getElementsAnnotatedWith(OnClick.class);
        Map<String, List<ExecutableElement>> mapExecutable = new HashMap<>();
        for (Element elementsAnnotatedMethod : elementsAnnotatedMethods) {
            ExecutableElement executableElement = (ExecutableElement) elementsAnnotatedMethod;
            String activityName = executableElement.getEnclosingElement().getSimpleName().toString();
            activitySets.add(activityName);
            List<ExecutableElement> executableElements = mapExecutable.get(activityName);
            if (null == executableElements) {
                executableElements = new ArrayList<>();
                mapExecutable.put(activityName, executableElements);
            }
            executableElements.add(executableElement);
        }

//开始生成文件
             if (activitySets.size() > 0) {
            for (String activityName : activitySets) {
                String packageName = null;
                Writer writer = null;
                try {
                    if (mapVariable.size() > 0) {
                        List<VariableElement> variableElementList = mapVariable.get(activityName);
                        //packageName
                        TypeElement enclosingElement = (TypeElement) variableElementList.get(0).getEnclosingElement();
                        packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                        //生成文件,就是字符串拼接
                        JavaFileObject sourceFile = mFiler.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                        writer = sourceFile.openWriter();
                        fileBegin(writer, packageName, activityName);

                        variableBuild(writer, variableElementList);
                    }

                    if (mapExecutable.size() >0) {
                        List<ExecutableElement> executableElementList = mapExecutable.get(activityName);
                        if (null == packageName || null == writer) {
                            //packageName
                            TypeElement enclosingElement = (TypeElement)executableElementList.get(0).getEnclosingElement();
                            packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                            JavaFileObject sourceFile = mFiler.createSourceFile(packageName+"."+activityName+"_ViewBinding");
                            writer = sourceFile.openWriter();
                            fileBegin(writer, packageName, activityName);
                        }
                        executableBuild(writer, executableElementList);
                    }
                    fileEnd(writer);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (null != writer) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return false;
    }

//生成类文件中,公共的部分
    private void fileBegin(Writer writer, String packageName, String activityName) throws IOException {
        writer.write("package " + packageName + ";\n\n");
        writer.write("import " + "android.view.View;\n");
        writer.write("import " + packageName + ".ButterKnifeIBinder;\n\n");

        writer.write("public class "+activityName+"_ViewBinding implements ButterKnifeIBinder<"+packageName+"."+activityName+"> {\n");
        writer.write(mIndentSpace1+"private "+activityName +" mTarget;\n");
        writer.write(mIndentSpace1+"@Override\n");

        writer.write(mIndentSpace1+"public void bind("+packageName+"."+activityName+" target) {\n");
        writer.write(mIndentSpace2+"mTarget = "+ "target;\n");
    }
    private void fileEnd(Writer writer) throws IOException{
        writer.write("\n"+mIndentSpace1+"}");
        writer.write("\n}");
    }

//生成属性赋值的代码
    private void variableBuild(Writer writer, List<VariableElement> variableElementList) throws IOException{
        for (VariableElement variableElement : variableElementList) {
            String variableName = variableElement.getSimpleName().toString();
            int id = variableElement.getAnnotation(BindView.class).value();
            TypeMirror typeMirror = variableElement.asType();
            writer.write(mIndentSpace2+"mTarget."+variableName+"=("+typeMirror+")mTarget.findViewById("+id+");\n");
        }
    }

//生成方法代码

    private void executableBuild(Writer writer, List<ExecutableElement> methodsElementList) throws IOException{
        writer.write("\n");
        for (ExecutableElement executableElement : methodsElementList) {
            String methodName = executableElement.getSimpleName().toString();
            List<? extends VariableElement> parameters = executableElement.getParameters();
            writer.write("//"+parameters.get(0)+"\n");
            OnClick annotationOnClick = executableElement.getAnnotation(OnClick.class);
            Class<? extends Annotation> annotationClass = annotationOnClick.annotationType();
            ListenerClass annotationListenerClass = annotationClass.getAnnotation(ListenerClass.class);
            String listenerSetter = annotationListenerClass.listenerSetter();
            String listenerType = annotationListenerClass.listenerType();
            String callbackMethod = annotationListenerClass.callbackMethod();
            String parameters1 = annotationListenerClass.parameters();
            int fieldId = annotationOnClick.value();
            writer.write(mIndentSpace2+"View view"+fieldId +"=(View)mTarget.findViewById("+fieldId+");\n");
            writer.write(mIndentSpace2+"view"+fieldId+"."+listenerSetter+"(new "+listenerType+"() {\n"+
                    mIndentSpace3+"@Override\n"+
                    mIndentSpace3+"public void "+callbackMethod+"("+parameters1+" parameter) {\n"+
                    mIndentSpace3+mIndentSpace1+"mTarget."+methodName+"(parameter);\n"+
                    mIndentSpace3+"}\n"+
                    mIndentSpace2+"});\n");
        }
    }
}

事件注解类,因为要获取事件的处理要素,用到了注解的多态。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@ListenerClass(
        listenerSetter = "setOnClickListener",
        listenerType = "android.view.View.OnClickListener",
        callbackMethod = "onClick",
        parameters = "android.view.View")
public @interface OnClick {
    int value();
}
@Target(ElementType.ANNOTATION_TYPE)//注解应用在什么地方,
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期
public @interface ListenerClass {
    String listenerSetter();//表示订阅关系
    String listenerType();//表示事件本身
    String callbackMethod();//表示事件处理的方法名
    String parameters();//方法参数,
}

属性上的注解类:

package com.test.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value();
}

辅助类生成出来后,怎么使用,类似ButterKnife的用法,利用反射去生成辅助类的对象,然后调用其bind方法,把Activity传进去。

 

package com.test.butterknifeframe;

import android.app.Activity;

public class InjectButterKnife {
    public static void bind(Activity activity) {
        String name = activity.getClass().getName() + "_ViewBinding";
        try {
            Class<?> aClass = Class.forName(name);
            ButterKnifeIBinder binder = (ButterKnifeIBinder)aClass.newInstance();
            binder.bind(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

如下是MainActivity的完整代码,应用了两个事件注入,一个属性注入。

import com.test.annotations.BindView;
import com.test.annotations.OnClick;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn1)
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectButterKnife.bind(this);
        btn.setText("IOCButton");
    }

    @OnClick(R.id.btn2)
    public void btn2Click(View view) {
        Log.d("MainActivity","btn2,IOC button click.");
    }

    @OnClick(R.id.btn3)
    public void btn3Click(View view) {
        Log.d("MainActivity","btn3,IOC button click.");
    }
}

最后看下,生成的辅助类的代码,存放路径:D:\ButterKnifeFrame\app\build\generated\source\apt\debug\com\test\butterknifeframe

package com.test.butterknifeframe;

import android.view.View;
import com.test.butterknifeframe.ButterKnifeIBinder;

public class MainActivity_ViewBinding implements ButterKnifeIBinder<com.test.butterknifeframe.MainActivity> {
    private MainActivity mTarget;
    @Override
    public void bind(com.test.butterknifeframe.MainActivity target) {
        mTarget = target;
        mTarget.btn=(android.widget.Button)mTarget.findViewById(2131165218);

//view
        View view2131165219=(View)mTarget.findViewById(2131165219);
        view2131165219.setOnClickListener(new android.view.View.OnClickListener() {
            @Override
            public void onClick(android.view.View parameter) {
                mTarget.btn2Click(parameter);
            }
        });
//view
        View view2131165220=(View)mTarget.findViewById(2131165220);
        view2131165220.setOnClickListener(new android.view.View.OnClickListener() {
            @Override
            public void onClick(android.view.View parameter) {
                mTarget.btn3Click(parameter);
            }
        });

    }
}

这里是以Button控件赋值,OnClick事件绑定为例,其他属性,事件的处理都是一个套路.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring的IOC(Inverse of Control)实现原理是通过IOC容器来实现的。IOC容器负责实例化、定位、配置应用程序中的对象,并建立这些对象间的依赖关系,从而实现对象之间的松耦合。 在Spring中,通过配置文件或注解的方式告诉Spring哪些Bean需要进行管理,Spring会根据配置文件或注解来实例化这些Bean,并将它们放入IOC容器中。当我们需要使用这些Bean,只需从IOC容器中获取即可,而不需要手动创建对象。这样就实现了将控制对象创建的过程反转给Spring容器来管理的效果。 Spring的IOC容器充当了一个类似于餐馆的角色,我们只需要告诉Spring哪些Bean需要进行管理,然后通过指定的方式从IOC容器中获取相应的Bean。Spring提供了多种类型的IOC容器,例如基于XML配置的ApplicationContext,基于注解的AnnotationConfigApplicationContext等等。无论使用哪种类型的IOC容器,Spring都会负责创建和管理Bean的生命周期,并根据依赖关系进行自动注入。 总结来说,Spring的IOC实现原理是通过IOC容器管理Bean的实例化、定位和配置,实现对象之间的解耦,并提供便利的方式来获取和使用这些Bean。通过IOC容器,我们可以更加灵活地组织和管理应用程序的对象。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [springIoc实现原理](https://download.csdn.net/download/zhangcongyi420/11131211)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [一文带你深入剖析Spring IOC 实现原理](https://blog.csdn.net/SQY0809/article/details/118678588)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值