注解
注解本身没有意义,单独的注解是一种注释,他需要结合其他如反射、插桩等技术才有意义
Java 注解(Annotation)又称 Java 标注,是JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
注解声明
声明一个注解类型
Java中所有的注解,默认实现 Annotation 接口:
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:
public @interface Lance{
}
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :
另外还有@Documented 与 @Inherited 元注解,前者用于被javadoc工具提取成文档,后者表示允许子类继承父类中定义的注解。
@Target
注解标记另一个注解(可以传多个,数组类型),以限制可以应用注解的 Java 元素类型。目标注解指定以下元素类型之一作为其值:
- ElementType.ANNOTATION_TYPE 可以应用于注解类型。
- ElementType.CONSTRUCTOR 可以应用于构造函数。
- ElementType.FIELD 可以应用于字段或属性。
- ElementType.LOCAL_VARIABLE 可以应用于局部变量。
- ElementType.METHOD 可以应用于方法级注解。
- ElementType.PACKAGE 可以应用于包声明。
- ElementType.PARAMETER 可以应用于方法的参数。
- ElementType.TYPE 可以应用于类的任何元素。
@Retention
注解指定标记注解的存储方式(只能传一个) 默认级别为Class:
- RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
- RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。Android在打包dex的时候会把所有class级别的注解抛弃掉。
- RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。、
@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
注解应用场景
根据注解的保留级别的不同,对注解的使用自然存在不同的场景,根据注解的三个不同保留级可知,注解作用于源码、字节码、运行时:
按照@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:
SOURCE
RetentionPolicy.SOURCE ,作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。
在Android开发中, support-annotations 与 androidx.annotation 中均有提供 @IntDef、@DrawableResId 等注解,此注解的定义如下:
@Retention(SOURCE)//源码级别注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
int[] value() default {};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
/**
* Whether any other values are allowed. Normally this is
* not the case, but this allows you to specify a set of
* expected constants, which helps code completion in the IDE
* and documentation generation and so on, but without
* flagging compilation warnings if other values are specified.
*/
boolean open() default false;
}
Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。比常量多5到10倍的内存占用。
此注解的意义在于能够取代枚举,实现如方法入参限制。
如:我们定义方法 test ,此方法接收参数 teacher 需要在:Lance、Alvin中选择一个。如果使用枚举能够实现为:
public enum Teacher{
LANCE,ALVIN
}
public void test(Teacher teacher) {
}
而现在为了进行内存优化,我们现在不再使用枚举,则方法定义为:
public static final int LANCE = 1;
public static final int ALVIN = 2;
public void test(int teacher) {
}
然而此时,调用 test 方法由于采用基本数据类型int,将无法进行类型限定。此时使用@IntDef增加自定义注解:
public static final int LANCE = 1;
public static final int ALVIN = 2;
@IntDef(value = {LANCE, ALVIN}) //限定为LANCE,ALVIN
@Target(ElementType.PARAMETER) //作用于参数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface Teacher {
}
public void test(@Teacher int teacher) {
}
此时,我们再去调用 test 方法,如果传递的参数不是 LANCE 或者 ALVIN 则会显示 Inspection 警告(编译不会报错)。
可以修改此类语法检查级别:
以上注解均为 SOURCE 级别,本身IDEA/AS 就是由Java开发的,Lint工具实现了对Java语法的检查,借助注解能对被注解的特定语法进行额外检查。
APT注解处理器
可以看做Javac在编译过程中调起的插件(小程序)
APT全称为:“Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
创建单独的java模块annotation和compiler,compiler引用annotation和compiler,compiler引用annotation
app包:
compiler包:
annotation包下:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface MyClass {
}
compiler包下:
processor要继承AbstractProcessor,同时要对processor进行注册,javac才会来读取配置文件,如图:
或者用AutoService注解:就这么一标记,annotationProcessor project()应用一下,编译时就能自动执行该类了,生成相应的配置文件avax.annotation.processing.Processor。
另外要重写方法getSupportedAnnotationTypes(),设置支持的注解类型,设置允许此注解器可以处理的注解,还有一种方式可以直接用注解@SupportedAnnotationTypes({“com.enjoy.annotation.MyClass”})来进行设置,要写全类名。
@SupportedSourceVersion注解:声明我们所支持的jdk版本,@SupportedSourceVersion(SourceVersion.RELEASE_8)
/**
* activity: A extends Activity
*/
//@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.enjoy.annotation.MyClass"})
public class TestProcessor extends AbstractProcessor {
/**
* javac调用此方法
*
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// roundEnvironment.processingOver()
// set.isEmpty()
// 写什么代码就做什么事情
//process: javac编译时的一个回调
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "============>");
String code = "\n" +
" public class A{\n" +
" \n" +
" }";
if (!set.isEmpty()){
Filer filer = processingEnv.getFiler();
OutputStream outputStream = null;
try {
JavaFileObject sourceFile = filer.createSourceFile("A");
outputStream = sourceFile.openOutputStream();
outputStream.write(code.getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
return false;
}
/**
* 允许此注解处理器处理的注解
* @return
*/
// @Override
// public Set<String> getSupportedAnnotationTypes() {
// return super.getSupportedAnnotationTypes();
// }
}
注解处理器中process方法为什么会执行多次?
这是正常的,java编译过程:词法分析、语法分析、填充符号表、注解处理器处理注解、语义分析、解语法糖、生成字节码。注解处理器可以增删改抽象语法树的任意元素。执行到注解处理器,都会重新执行词法分析、语法分析、填充符号表步,直到注解处理器不再对语法树进行修改为止,每一次的循环过程都称为一次Round。process第一个参数set集合是要处理的注解集合,如果这个集合为null了,就不需要处理了。写代码就这样去做。
if(!set.isEmpty()){
//…执行处理
}
Class
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断。
如果我们使用普通的编程方式,需要在代码中进行 if-else 的判断,也许存在十个判断点,则需要在每个判断点加入此项判断。此时,我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录两种类型,即两个切面。对于切面的区分即可采用注解。
//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
}
@Login
public void jumpA(){
startActivity(new Intent(this,AActivity.class));
}
public void jumpB(){
startActivity(new Intent(this,BActivity.class));
}
在上述代码中, jumpA 方法需要具备登录身份。而 Login 注解的定义被设置为 CLASS 。因此我们能够在该类所编译的字节码中获得到方法注解 Login 。在操作字节码时,就能够根据方法是否具备该注解来修改class中该方法的内容加入 if-else 的代码段:
//Class字节码
@Login
public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}
public void jumpB() {
startActivity(new Intent(this,BActivity.class));
}
注解能够设置类型元素(参数),结合参数能实现更为丰富的场景,如:运行期权限判定等。
字节码增强
字节码增强技术相当于是一把打开运行时JVM的钥匙,利用它可以动态地对运行中的程序做修改,也可以跟踪JVM运行中程序的状态。此外,我们平时使用的动态代理、AOP也与字节码增强密切相关,它们实质上还是利用各种手段生成或修改符合规范的字节码文件。综上所述,掌握字节码增强后可以高效地定位并快速修复一些棘手的问题(如线上性能问题、方法出现不可控的出入参需要紧急加日志等问题),也可以在开发中减少冗余代码,大大提高开发效率。
RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
反射
般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。
面试官:反射是否可以修改final类型变量?
例如在java文件中:
public class ReflectTest {
//编译期间final类型的数据自动被优化
public final int i = 1 ;
public ReflectTest() {
// i = 1;
}
public int getI() {
return i;
}
public static void main(String[] args) throws Exception {
ReflectTest reflectTest = new ReflectTest();
Field i = ReflectTest.class.getDeclaredField("i");
i.setAccessible(true);
i.set(reflectTest, 2);
// 动态拿
System.out.println(i.get(reflectTest));
// 编译时写死
System.out.println(reflectTest.i);
}
}
生成的class:
public class ReflectTest {
public final int i = 1;
public ReflectTest() {
}
public int getI() {
return 1;
}
public static void main(String[] args) throws Exception {
ReflectTest reflectTest = new ReflectTest();
Field i = ReflectTest.class.getDeclaredField("i");
i.setAccessible(true);
i.set(reflectTest, 2);
System.out.println(i.get(reflectTest));
PrintStream var10000 = System.out;
reflectTest.getClass();
var10000.println(1);
}
}
Java反射机制主要提供了以下功能:
- 在运行时构造任意一个类的对象
- 在运行时获取或者修改任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法(属性)
Class
反射始于Class,Class是一个类,封装了当前对象所对应的类的信息。一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。
Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。 对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例。
获得 Class 对象
获取Class对象的三种方式
- 通过类名获取 类名.class
- 通过对象获取 对象名.getClass()
- 通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)
- 使用 Class 类的 forName 静态方法
public static Class<?> forName(String className)
- 直接获取某一个对象的 class
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
- 调用某个对象的 getClass() 方法
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
判断是否为某个类的实例
一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法:
public native boolean isInstance(Object obj);
判断是否为某个类的类型
public boolean isAssignableFrom(Class<?> cls)
创建实例
通过反射来生成对象主要有两种方式。
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class;
Object str = c.newInstance();
- 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这
种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
获取构造器信息
得到构造器的方法
Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
获取类的成员变量(字段)信息
获得字段信息的方法
Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段
调用方法
获得方法信息的方法
Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法
当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。 invoke 方法的原型为:
public Object invoke(Object obj, Object... args)
public static Object newInstance(Class<?> componentType, int length);
利用反射创建数组
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:
public static Object newInstance(Class<?> componentType, int length);
反射获取泛型真实类型
当我们对一个泛型类进行反射时,需要的到泛型中的真实数据类型,来完成如json反序列化的操作。此时需要通过 Type 体系来完成。 Type 接口包含了一个实现类(Class)和四个实现接口,他们分别是:
- TypeVariable
泛型类型变量。可以泛型上下限等信息;
- ParameterizedType
具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
- GenericArrayType
当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现。
- WildcardType
通配符泛型,获得上下限信息;
TypeVariable
ParameterizedType
GenericArrayType
WildcardType
Gson反序列化
在进行GSON反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成泛型的反序列化。但是为什么TypeToken 要被定义为抽象类呢?
因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型signature信息记录到Class元数据中。
反射为什么这么慢
1、Method#invoke 需要进行自动拆装箱
invoke 方法的参数是 Object[] 类型,如果是基本数据类型会转化为Integer装箱,同时再包装成Object数组。在执行时候又会把数组拆解开,并拆箱为基本数据类型。
2、反射需要按名检索类和方法
http://androidxref.com/9.0.0_r3/xref/art/runtime/mirror/class.cc#1265
3、需要检查方法
反射时需要检查方法可见性以及每个实际参数与形式参数的类型匹配性
4、编译器无法对动态调用的代码做优化,比如内联
反射涉及到动态解析的类型,影响内联判断并且无法进行JIT
动态代理原理
类的完整生命周期:
动态代理会在运行时再创建代理类和其实例,因此显然效率较低。要完成这个场景,需要在运行期动态创建一个Class。JDK提供了 Proxy 来完成这件事情。基本使用如下:
实际上, Proxy.newProxyInstance 会创建一个Class,与静态代理不同,这个Class不是由具体的.java源文件编译而来,即没有真正的文件,只是在内存中按照Class格式生成了一个Class:
在此Class的clinit(静态代码块与静态属性组成的方法)中,获得 method 备用。而这个代理类中所有方法的实现变为:
这里的 h 其实就是 InvocationHandler 接口,所以我们在使用动态代理时,传递的 InvocationHandler 就是一个监听,在代理对象上执行方法,都会由这个监听回调出来。
/**
* 代理抽象角色: 定义了服务的接口
*/
public interface NDK {
void ndk();
}
/**
* 足浴
*/
public interface UI {
void ui();
}
/**
* 真实实现类: 提供马杀鸡服务的露西
*/
public class Lance implements NDK {
@Override
public void ndk() {
System.out.println("绿色健康小清新");
}
}
public class Alvin implements UI {
@Override
public void ui() {
}
}
public class ProxyInvokeHandler implements InvocationHandler {
private Object realObject;
public ProxyInvokeHandler(Object realObject) {
this.realObject = realObject;
}
/**
*
* @param o 代理对象M
* @param method 调用的方法
* @param objects 方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return method.invoke(realObject, objects);
}
}
public class Student {
public static void main(String[] args) {
NDK lance = new Lance();
// JAVA动态代理 NDK
/**
* 1、类加载器
* 2、要代理的接口
* 3、回调
*/
NDK ndk = (NDK) Proxy.newProxyInstance(lance.getClass().getClassLoader(),
new Class[]{NDK.class}, new ProxyInvokeHandler(lance));
//Proxy$0
ndk.ndk();
//现在要能否解决UI的问题
// UI alvin = new Alvin();
// UI ui = (UI) Proxy.newProxyInstance(alvin.getClass().getClassLoader(),
// alvin.getClass().getInterfaces(), new ProxyInvokeHandler(alvin));
// ui.ui();
}
}
Retrofit中的注解反射和动态代理
Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责网络请求接口的封装。
public class EnjoyRetrofit {
final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
final Call.Factory callFactory;
final HttpUrl baseUrl;
EnjoyRetrofit(Call.Factory callFactory, HttpUrl baseUrl) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
}
public <T> T create(final Class<T> service) {
//代理(一个可以进行http请求的对象)原本一个普通的java接口。
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//解析这个method 上所有的注解信息
ServiceMethod serviceMethod = loadServiceMethod(method);
//args:
return serviceMethod.invoke(args);
}
});
}
private ServiceMethod loadServiceMethod(Method method) {
//先不上锁,避免synchronized的性能损失
ServiceMethod result = serviceMethodCache.get(method);
if (result != null) return result;
//多线程下,避免重复解析
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
/**
* 构建者模式,将一个复杂对象的构建和它的表示分离,可以使使用者不必知道内部组成的细节。
*/
public static final class Builder {
private HttpUrl baseUrl;
//Okhttp->OkhttClient
private Call.Factory callFactory; //null
public Builder callFactory(Call.Factory factory) {
this.callFactory = factory;
return this;
}
public Builder baseUrl(String baseUrl) {
this.baseUrl = HttpUrl.get(baseUrl);
return this;
}
public EnjoyRetrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
return new EnjoyRetrofit(callFactory, baseUrl);
}
}
}
/**
* 记录请求类型 参数 完整地址
*/
public class ServiceMethod {
private final Call.Factory callFactory;
private final String relativeUrl;
private final boolean hasBody;
private final ParameterHandler[] parameterHandlers;
private FormBody.Builder formBuild;
HttpUrl baseUrl;
String httpMethod;
HttpUrl.Builder urlBuilder;
public ServiceMethod(Builder builder) {
baseUrl = builder.enjoyRetrofit.baseUrl;
callFactory = builder.enjoyRetrofit.callFactory;
httpMethod = builder.httpMethod;
relativeUrl = builder.relativeUrl;
hasBody = builder.hasBody;
parameterHandlers = builder.parameterHandlers;
//如果是有请求体,创建一个okhttp的请求体对象
if (hasBody) {
formBuild = new FormBody.Builder();
}
}
public Object invoke(Object[] args) {
//1 获取最终请求地址
HttpUrl url;
if (urlBuilder == null) {
urlBuilder = baseUrl.newBuilder(relativeUrl);
}
/**
* 2 处理请求的地址与参数
*/
for (int i = 0; i < parameterHandlers.length; i++) {
ParameterHandler handlers = parameterHandlers[i];
//handler内本来就记录了key,现在给到对应的value
handlers.apply(this, args[i].toString());
}
url = urlBuilder.build();
//3 请求体
FormBody formBody = null;
if (formBuild != null) {
formBody = formBuild.build();
}
Request request = new Request.Builder().url(url).method(httpMethod, formBody).build();
return callFactory.newCall(request);
}
// get请求, 把 k-v 拼到url里面
public void addQueryParameter(String key, String value) {
if (urlBuilder == null) {
urlBuilder = baseUrl.newBuilder(relativeUrl);
}
urlBuilder.addQueryParameter(key, value);
}
//Post 把k-v 放到 请求体中
public void addFiledParameter(String key, String value) {
formBuild.add(key, value);
}
public static class Builder {
private final EnjoyRetrofit enjoyRetrofit;
private final Annotation[] methodAnnotations;
private final Annotation[][] parameterAnnotations;
private String httpMethod;
private String relativeUrl;
private boolean hasBody;
ParameterHandler[] parameterHandlers;
public Builder(EnjoyRetrofit enjoyRetrofit, Method method) {
this.enjoyRetrofit = enjoyRetrofit;
//获取方法上的所有的注解
methodAnnotations = method.getAnnotations();
//获得方法参数的所有的注解 (一个参数可以有多个注解,一个方法又会有多个参数)
parameterAnnotations = method.getParameterAnnotations();
}
public ServiceMethod build() {
/**
* 1 解析方法上的注解, 只处理POST与GET
*/
for (Annotation methodAnnotation : methodAnnotations) {
if (methodAnnotation instanceof POST) {
//记录当前请求方式
this.httpMethod = "POST";
//记录请求url的path
this.relativeUrl = ((POST) methodAnnotation).value();
// 是否有请求体
this.hasBody = true;
} else if (methodAnnotation instanceof GET) {
this.httpMethod = "GET";
this.relativeUrl = ((GET) methodAnnotation).value();
this.hasBody = false;
}
}
/**
* 2 解析方法参数的注解
*/
int length = parameterAnnotations.length;
parameterHandlers = new ParameterHandler[length];
for (int i = 0; i < length; i++) {
// 一个参数上的所有的注解
Annotation[] annotations = parameterAnnotations[i];
// 处理参数上的每一个注解
for (Annotation annotation : annotations) {
//todo 可以加一个判断:如果httpMethod是get请求,现在又解析到Filed注解,可以提示使用者使用Query注解
if (annotation instanceof Field) {
//得到注解上 请求参数的key
String key = ((Field) annotation).value();
parameterHandlers[i] = new ParameterHandler.FiledParameterHandler(key);
} else if (annotation instanceof Query) {
String key = ((Query) annotation).value();
parameterHandlers[i] = new ParameterHandler.QueryParameterHandler(key);
}
}
}
return new ServiceMethod(this);
}
}
}
public abstract class ParameterHandler {
abstract void apply(ServiceMethod serviceMethod, String value);
static class QueryParameterHandler extends ParameterHandler {
String key;
public QueryParameterHandler(String key) {
this.key = key;
}
//serviceMethod: 回调
@Override
void apply(ServiceMethod serviceMethod, String value) {
serviceMethod.addQueryParameter(key,value);
}
}
static class FiledParameterHandler extends ParameterHandler {
String key;
public FiledParameterHandler(String key) {
this.key = key;
}
@Override
void apply(ServiceMethod serviceMethod, String value) {
serviceMethod.addFiledParameter(key,value);
}
}
}
public interface WeatherApi {
@POST("/v3/weather/weatherInfo")
Call postWeather(@Field("city") String city, @Field("key") String key);
@GET("/v3/weather/weatherInfo")
Call getWeather(@Query("city") @Nullable String city, @Query("key") String key);
}
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {
String value();
}
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
@Target(METHOD)
@Retention(RUNTIME)
public @interface POST {
String value() default "";
}
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Query {
String value();
}
@MyClass
public class MainActivity extends AppCompatActivity {
private WeatherApi weatherApi;
private static final String TAG = "MainActivity";
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EnjoyRetrofit retrofit = new EnjoyRetrofit.Builder().baseUrl("https://restapi.amap.com")
.build();
weatherApi = retrofit.create(WeatherApi.class);
}
public void get(View view) {
okhttp3.Call call = weatherApi.getWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull okhttp3.Call call, @NotNull IOException e) {
}
@Override
public void onResponse(@NotNull okhttp3.Call call, @NotNull Response response) throws IOException {
if (response.isSuccessful()) {
ResponseBody body = response.body();
try {
String string = body.string();
Log.i(TAG, "onResponse get: " + string);
} catch (IOException e) {
e.printStackTrace();
} finally {
body.close();
}
}
}
});
}
public void post(View view) {
Call call = weatherApi.postWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull okhttp3.Call call, @NotNull IOException e) {
}
@Override
public void onResponse(@NotNull okhttp3.Call call, @NotNull Response response) throws IOException {
if (response.isSuccessful()) {
ResponseBody body = response.body();
try {
String string = body.string();
Log.i(TAG, "onResponse get: " + string);
} catch (IOException e) {
e.printStackTrace();
} finally {
body.close();
}
}
}
});
}
}
利用反射、注解、动态代理实现OnClick与OnLongClick事件的自动注入
实现:
@Click({R.id.bottom,R.id.end})
public void abc(View view){
}
代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener")
public @interface OnClick {
int[] value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnLongClickListener.class, listenerSetter = "setOnLongClickListener")
public @interface OnLongClick {
int[] value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnTouchListener.class, listenerSetter = "setOnTouchListener")
public @interface OnTouch {
int[] value();
}
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
Class listenerType();
String listenerSetter();
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectUtils.injectEvent(this);
}
@OnClick({R.id.btn1, R.id.btn2})
public void click(View view) {
switch (view.getId()) {
case R.id.btn1:
Log.i(TAG, "click: 按钮1");
break;
case R.id.btn2:
Log.i(TAG, "click: 按钮2");
break;
}
}
@OnLongClick({R.id.btn1, R.id.btn2})
public boolean longClick(View view) {
switch (view.getId()) {
case R.id.btn1:
Log.i(TAG, "longClick: 按钮1");
break;
case R.id.btn2:
Log.i(TAG, "longClick: 按钮2");
break;
}
return false;
}
@OnTouch({R.id.btn1, R.id.btn2})
public boolean touch(View view,MotionEvent event) {
switch (view.getId()) {
case R.id.btn1:
Log.i(TAG, "touch: 按钮1");
break;
case R.id.btn2:
Log.i(TAG, "touch: 按钮2");
break;
}
return false;
}
}
public class InjectUtils {
public static void injectEvent(Activity activity) {
Class<? extends Activity> activityClass = activity.getClass();
Method[] declaredMethods = activityClass.getDeclaredMethods();
for (Method method : declaredMethods) {
//获得方法上所有注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
//注解类型
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType.isAnnotationPresent(EventType.class)) {
EventType eventType = annotationType.getAnnotation(EventType.class);
// OnClickListener.class
Class listenerType = eventType.listenerType();
//setOnClickListener
String listenerSetter = eventType.listenerSetter();
try {
// 不需要关心到底是OnClick 还是 OnLongClick
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] viewIds = (int[]) valueMethod.invoke(annotation);
method.setAccessible(true);
ListenerInvocationHandler<Activity> handler = new ListenerInvocationHandler(activity, method);
Object listenerProxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType}, handler);
// 遍历注解的值
for (int viewId : viewIds) {
// 获得当前activity的view(赋值)
View view = activity.findViewById(viewId);
// 获取指定的方法(不需要判断是Click还是LongClick)
// 如获得:setOnClickLisnter方法,参数为OnClickListener
// 获得 setOnLongClickLisnter,则参数为OnLongClickLisnter
Method setter = view.getClass().getMethod(listenerSetter, listenerType);
// 执行方法
setter.invoke(view, listenerProxy); //执行setOnclickListener里面的回调 onclick方法
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
/**
* 还可能在自定义view注入,所以是泛型: T = Activity/View
*
* @param <T>
*/
static class ListenerInvocationHandler<T> implements InvocationHandler {
private Method method;
private T target;
public ListenerInvocationHandler(T target, Method method) {
this.target = target;
this.method = method;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return this.method.invoke(target, args);
}
}
}