java注解的详细使⽤(APT、插桩、反射)
注解的详细使⽤
⼀、APT,编译时注解处理器
1、概述:
什么是apt:
APT,就是Annotation Processing Tool的简称,就是可以在代码编译期间对注解进⾏处理,并且⽣成Java⽂件,减少⼿动的代码输⼊。注解我们平时⽤到的⽐较多的可能会是运⾏时注解,⽐如⼤名⿍⿍的retrofit就是⽤运⾏时注解,通过动态代理来⽣成⽹络请求。编译时注解平时开发中可能会涉及的⽐较少,但并不是说不常⽤,⽐如我们经常⽤的轮⼦Dagger2, ButterKnife, EventBus3 都在⽤,所以要紧跟潮流来看看APT技术的来龙去脉。
编译时注解:
也有⼈叫它代码⽣成,其实他们还是有些区别的,在编译时对注解做处理,通过注解,获取必要信息,在项⽬中⽣成代码,运⾏时调⽤,和直接运⾏⼿写代码没有任何区别。⽽更准确的叫法:APT - Annotation Processing Tool
⼤概原理:
Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现⾃⼰的注解解析逻辑。APT 的原理就是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的⼦类,并且⾃动调⽤其 process()⽅法,然后将添加了指定注解的所有代码元素作为参数传递给该⽅法,开发者再根据注解元素在编译期输出对应的 Java 代码
APT在代码编译期解析注解,并且⽣成新的 Java ⽂件,减少⼿动的代码输⼊。
APT是⼀个命令⾏⼯具,它对源代码⽂件进⾏检测出其中的annotation后,使⽤AbstractProcessor来处理annotation。
JavaPoet + Auto Service + java APT完成对编译时注解的处理。
2、Android studio中使⽤APT过程:
(1) 创建android过程SelfAnnotationProcessor
(2) 给⼯程添加java module:名称 apt:主要⽤于处理运⾏时注解的解析⼯作;给⼯程添加java module:名称 anno:主要⽤于定义运⾏时注解;
(3) 在app module的adle添加依赖
annotationProcessorproject(":apt")
implementationproject(’:anno’)
在apt module的adle添加依赖
Implementation project(‘:anno’)
implementation’com.squareup:javapoet:1.11.1’
implementation’le.auto.service:auto-service:1.0-rc2’
(4) 在 anno module下定义注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD,ElementType.TYPE)
public @interface BindView {
int value();
}
(5) 在app module的MainActivity中使⽤⾃定义注解 BindSelfView
@BindView(1)
public class MainActivity extends Activity{
@BindView(View1)
TextView textView1;
@BindView(View2)
TextView textView1;
protected void onCreate(Bundle savedInstanceState){
setContentView(R.layout.activity_main);
}
@BindView(2)
class ViewHolder{
}
}
(6) 在apt module下定义⾃定义注解处理器(代码过长)
@AutoService(Processor.class)
@SupportedAnnotationTypes("ample.sjh.anno.BindSelfView")
public class MyAnnoProcessor extends AbstractProcessor{
添加了 @AutoService(Processor.class)
那么就会在当前⼯程apt的META-INF下⽣成配置⽂件, 内容为注册的注解处理器
在init⽅法中进⾏⼯具类对象的获取:
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment){
System.out.printlin("MyAnnoProcessor init is invoke.processingEnvironment"+processingEnvironment);
super.init(processingEnvironment);
Filer();
ElementUtils();
}
在核⼼⽅法process⾥⾯对 被注解元素进⾏分析,并⽣成⽬标⽂件;
整个过程中⽤到TypeSpec,MethodSpec,JavaFile等对象
TypeSpec . Builder tb = TypeSpec .classBuilder( name:"MainActivity. _BindView").
addModifiers (Modifier . PUBLIC, Modifier .FINAL)
.addField(String.class, name:"textView", Modifier . PRIVATE)
.string());
MethodSpec .Builder mb = MethodSpec .methodBuilder( name:"onCreate").returns(TypeName. VOID).
addModifiers(Modifier.PUBLIC)
.addParameter(String.class,"savedInstanceState");
java接口有没有构造方法Set<?extends Element> eleSet = roundEnvi ronment. getElementsAnnot at edWith(BindSeIfView.class);
Iterstorc?extends Element) t . eleSet.iterator();
while(it.haslext())(
Element ();
if(ele instanceof TypeElement){
Annotation annotation(BindSelfView.class);
BindSelfView view=(BindSelfView)anno;
String desc="eleName:"+SimpleName().toString()+",annoNmae:"+anno.annotationType().getClass().getSimpleName()+",annoValue:"+view.val ue()+",kind:"+Kind();
CodeBlock cb=CodeBlock.builder()
.addStatement(""+desc);
.build();
mb.addCode(cb);
}
}
tb.addMethod(mb.build());
JavaFile jf=JavaFile.builder( packageName:"com. example.sjh.selfannotationprocessor",tb.build())
.build();
try{
jf .writeTo(mFller);
}catch(IOException e){
e.printStackTrace();
}
return true;
其中⽤到了javapoet的提供的API
(7) 然后执⾏build(rebuild)操作,就可以在 app module下看到⽣成的⽬标⽂件
//这是⼀个单纯编译时注解的demo
⼆、插桩,编译后处理筛选
什么是插桩?
插桩就是将⼀段代码插⼊或者替换原本的代码。字节码插桩顾名思义就是在我们编写的源码编译成字节码(Class)后,在Android下⽣成dex之前修改Class⽂件,修改或者增强原有代码逻辑的操作。
字节码操作框架
QQ空间使⽤了 Javaassist 来进⾏字节码插桩,除了 Javaassist 之外还有⼀个应⽤更为⼴泛的 ASM 框架同样也是字节码操作框
架,Instant Run包括 AspectJ 就是借助 ASM来实现各⾃的功能。
字节码操作框架的作⽤在于⽣成或者修改Class⽂件,因此在Android中字节码框架本⾝是不需要打包
进⼊APK的,只有其⽣成/修改之后的Class才需要打包进⼊APK中。它的⼯作时机为Android打包流程中的⽣成Class之后,打包dex之前。
Android打包流程图:
通过上图可知,只要在图中红⾊箭头处拦截(⽣成class⽂件之后,dex⽂件之前),就可以拿到当前应⽤程序中所有的.class⽂件,再去借助ASM之类的库,就可以遍历这些.class⽂件中所有⽅法,再根据⼀定的条件到需要的⽬标⽅法,最后进⾏修改并保存,就可以插⼊指定代码。
ASM
ASM是⼀个字节码操作库,它可以直接修改已经存在的class⽂件或者⽣成class⽂件。ASM提供了⼀些便捷的功能来操作字节码内容。与其它字节码操作框架(⽐如:AspectJ等)相⽐,ASM更偏向于底层,它是直接操作字节码的,在设计上相对更⼩、更快,所以在性能上更好,⽽且⼏乎可以任意修改字节码。
三、反射,运动时动态获取注解信息
1.概述:
JAVA反射机制是在运⾏状态中,对于任意⼀个类,都能够知道这个类的所有属性和⽅法;对于任意⼀个对象,都能够调⽤它的任意⼀个⽅法和属性;这种动态获取的信息以及动态调⽤对象的⽅法的功能称为java语⾔的反射机制。
要想解剖⼀个类,必须先要获取到该类的字节码⽂件对象。⽽解剖使⽤的就是Class类中的⽅法.所以先要获取到每⼀个字节码⽂件对应的Class类型的对象.
反射就是把java类中的各种成分映射成⼀个个的Java对象
例如:⼀个类有:成员变量、⽅法、构造⽅法、包等等信息,利⽤反射技术可以对⼀个类进⾏解剖,把个个组成部分映射成⼀个个对象。
Class对象的由来是将class⽂件读⼊内存,并为之创建⼀个Class对象。
2、查看Class类在java中的api详解
Class 类的实例表⽰正在运⾏的 Java 应⽤程序中的类和接⼝。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)Class 没有公共构造⽅法。Class 对象是在加载类时由 Java 虚拟机以及通过调⽤类加载器中的defineClass ⽅法⾃动构造的。也就是这不需要我们⾃⼰去处理创建,JVM已经帮我们创建好了。
3、反射的使⽤(这⾥使⽤Student类做演⽰)
先写⼀个Student类。
1、获取Class对象的三种⽅式
1.1 Object ——> getClass();
1.2 任何数据类型(包括基本数据类型)都有⼀个“静态”的class属性
1.3 通过Class类的静态⽅法:forName(String className)(常⽤)
package fanshe;
/**
* 获取Class对象的三种⽅式
* 1 Object ——> getClass();
* 2 任何数据类型(包括基本数据类型)都有⼀个“静态”的class属性
* 3 通过Class类的静态⽅法:forName(String className)(常⽤)
*
*/
public class Fanshe {
public static void main(String[] args){
//第⼀种⽅式获取Class对象
Student stu1 =new Student();//这⼀new 产⽣⼀个Student对象,⼀个Class对象。
Class stuClass = Class();//获取Class对象
System.out.Name());
//第⼆种⽅式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第⼀种⽅式获取的Class对象和第⼆种⽅式获取的是否是同⼀个
//第三种⽅式获取Class对象
try{
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种⽅式是否获取的是同⼀个Class对象
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
注意:在运⾏期间,⼀个类,只有⼀个Class对象产⽣。
三种⽅式常⽤第三种,第⼀种对象都有了还要反射⼲什么。第⼆种需要导⼊类的包,依赖太强,不导包就抛编译错误。⼀般都第三种,⼀个字符串可以传⼊也可写在配置⽂件中等多种⽅法。
2、通过反射获取构造⽅法并使⽤
package fanshe;
public class Student {
//---------------构造⽅法-------------------
//(默认的构造⽅法)
Student(String str){
System.out.println("(默认)的构造⽅法 s = "+ str);
}
//⽆参构造⽅法
public Student(){
System.out.println("调⽤了公有、⽆参构造⽅法执⾏了。。。");
}
//有⼀个参数的构造⽅法
public Student(char name){
System.out.println("姓名:"+ name);
}
//有多个参数的构造⽅法
public Student(String name ,int age){
System.out.println("姓名:"+name+"年龄:"+ age);//这的执⾏效率有问题,以后解决。}
//受保护的构造⽅法
protected Student(boolean n){
System.out.println("受保护的构造⽅法 n = "+ n);
}
//私有构造⽅法
private Student(int age){
System.out.println("私有的构造⽅法年龄:"+ age);
}
}
共有6个构造⽅法;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论