[安卓/Android]通过自定义注解和Java反射自动实例化对象

以前就玩过注解和反射,但是最近在完善自己的小MVP框架,所以对这俩情有独钟,也算是以前玩SpringBoot那会对IOC的情有独钟吧,也算是能把自己想要的都实现了。开始吧!

 概要

这里就不细讲了,我相信你也不会细看的,就说说功能和感受吧。

注解

注解想必都很常见,诸如自带的@Override、@Deprecated、@SuppressWarnings、@Nullable等等,如果玩过Spring的话那就更多了。注解的出现能够让我们在编码的时候省去很多时间,这也是我很喜欢它的原因,真的是很爽,一时用一时爽那样。就好比黄油刀框架里边的BindVIew,不知道省去了多少个findViewById。

反射

如果说注解是接口的话,那反射就是实现类了,因为想要实现注解的功能,就必须要知道你当前注解下的类、方法或者是变量,如果是私有的那就跟别提获取它的类型。所以很明显,反射的功能基本上能够对一个类的访问,即使它是私有的,所以说很强大,当然,强大也是有所付出的,这里咱就不讨论了。

实现方案

想要通过注解和反射来实现对变量的实例比较简单,就是获取当前类后,查找所有类中的成员变量,得到的变量在查找是否拥有自己想要的注解,当然你也可以查它的继承类。找到后实例化就行了。

注:不要在意谓词,比如变量、成员变量、字段这类。

正题

创建一个注解

//注解的范围。当前只允许成员变量,如果你在方法或者类中注解,那么他会爆红
@Target(ElementType.FIELD)
//生命周期。因为我们是在运行的时候进行反射,所以设置运行时
@Retention(RetentionPolicy.RUNTIME)
//注解实际上是在接口前边加一个@
public @interface Model {}
Target注解:允许被注解的范围。
ElementType.FIELD(成员变量)、METHOD(方法)、TYPE(类)。

也可以通过逗号隔开一起用,它的作用就是限制它的作用域,比如你只写了对成员变量的实例,然后使用者将它设置在方法上,你岂不想挥起自己40米的大刀追他环游世界。

@Retention:生命周期。也算是限制,就不过是针对虚拟机。
RetentionPolicy.RUNTIME(运行时依旧存在)、CLASS(运行前将会被抛弃)、SOURCE(编译前保留)

比如你的自定义注解只是作为提示或者警告,例如:@Override 或者 @SuppressWarnings 它俩都只是用于提示或者警告,不会参与编译或者运行的时候需要被查找到,那他只需要声明为SOURCE就行了,没必要编译到Class中。同理,如果你想实例化某个对象,那就要找到指定注解,所以需要声明为RUNTIME。

一个反射类

反射所需要的类或方法:Field、Class下的 getDeclaredFields()。其他的待会说。

Field:看个人理解吧,比如你想对某个变量赋值,那么可能是 int a = 1;,但在运行时你不能这么玩,除非你自定义模板,它就可以实现这种操作,同时也能获取当前变量的类型。

getAnnotation():获取当前变量是否拥有指定的注解。

getDeclaredFields():和它长得差不多的一个方法叫 getFields() ,长,就有它自己的优势,比如能获取私有变量。不管怎么说他的功能还是获取当前类中的所有成员变量。

不介绍了,直接上代码吧,不会有人看的。

这里我用了静态方法实现反射,我做了一个基方法,主要用来查找和实例,实例通过一个接口返回的数据实现,这样拓展性比较好,因为你可能不只是实例。

    /**
     * 实例条件范围内的成员变量
     * @param t     当前类,this
     * @param call  回调条件。返回null时跳过当前变量
     * @param <T>   泛型
     */
    public static <T> void instanceValClass(@NonNull T t,
                                             @Nullable CallValue<T> call) {
        /* 遍历当前类中的所有变量,包括了私有变量 */
        for(Field f : t.getClass().getDeclaredFields() ) {
            Object val;
            //接口为空或者返回值为空,则跳过当前变量,保证不会影响到无关的变量
            if( call == null || ( val = call.value( t, f ) ) == null ) continue;
            //允许被访问
            f.setAccessible( true );
            try {
                //对其赋值,可以理解为:YBModel m = new YBModel();,此时,被Model所注解的成员变量无需实例。
                f.set(t, val);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 回调构建的值,返回 null 时跳过构建
     */
    public interface CallValue<T> { Object value(T t, @NonNull Field f); }

传入的类这里用泛型,实际上就是Object,就是看着高端。第二个参数会传当前循环到的Field,以及当前类,因为反射代码基本都一样,可以理解成修饰成一个工具类,调用者只需要返回它实例的类就行了。(通俗点讲就是懒)

接下来就是主代码了,代码量不是很多,就是基本的判断是否是我们想要的注解,然后实例它返回,我顺便贴上了实例化对象的方法。如果你的实例化对象必须传参,你可以用有参构造。(如果你没法拿捏实例的变量是否带构造参数时,在设计对象时可以采用init的方法(不传参,设置一个public void init() {} 方法进行传参 ))。

    /**
     * 实例当前类中所有成员变量
     * 成员变量必须拥有{@link Model}、{@link Autowired} 其中之一。
     * @param t     当前类,this
     * @param <T>   泛型
     */
    public static <T> void instanceVarFromAnn(@NonNull T t) {
        instanceValClass(t, (t1, f) -> {
            //获取之前定义的Model,查询它是否被当前变量拥有
            boolean isModel = f.getAnnotation(Model.class) != null;
            //如果有,我们实例化它,否则跳过
            return isModel ? newClass( f.getType() ) : null;
        });
    }

    /***** 以下代码用于实例化对象 *****/

    /**
     * 无参构造Class
     * @param cls   构造的Class
     * @param <T>   类的泛型
     * @return      返回创建好的泛型
     */
    @Nullable
    public static <T> T newClass(Class<T> cls) {
        return newClass(cls, null, (Object) null);
    }


    /**
     * 带参构造一个泛型的Class
     * @param cls               构造的Class
     * @param parameterTypes    参数类型    eg: String.class
     * @param initargs          参数       eg: "AaBb"
     * @param <T>               类的泛型
     * @return                  返回创建好的泛型
     */
    @Nullable
    public static <T> T newClass(@Nullable Class<T> cls,
                                 @Nullable Class<?>[] parameterTypes,
                                 @Nullable Object... initargs) {
        try {
            if( cls == null ) return null;
            //构造对象
            if( parameterTypes == null || initargs == null ||
                    parameterTypes.length != initargs.length ) {
                return cls.getConstructor().newInstance();
            }else {
                return cls.getConstructor( parameterTypes ).newInstance( initargs );
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

接下来我们需要在实例的构造方法中调用  instanceVarFromAnn()方法就行了

例如我在Activity的onCreate中调用了这个方法:

   
    @Model
    private TestClass tCls;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //实例被Model注解的成员变量
        YBAnn.instanceVarFromAnn( this );
        
        Log.d("TAG", tCls.getS());
    }


    public class TestClass {
        private String s = "Hello World";
        public String getS() { return s; }
    }

好了,基本就结束了。根据这个办法我完善了之前说的那个MVP,也就是M层需要被P层持有,或者说P层需要被V层持有,为了减少代码,可以采用这种办法,使用者只需要设置注解就行了,不必要每次都要实例一次对象,这样很麻烦,尤其是V层和P层。

自动实例可能日常很少用,但是对于框架来讲很重要,可能会丢失些性能吧,但是很爽啊。除此之外,我们还可以解决findVIewById(),让它和黄油刀一样简洁。

猜你喜欢

转载自blog.csdn.net/u013599928/article/details/95822203