以前就玩过注解和反射,但是最近在完善自己的小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(),让它和黄油刀一样简洁。