一. 综述
-
注解其实是一个高级版的注释,如果没有解析它,那么它的代码并不比注释高级多少。
-
解析注解的方法:分为编译期扫码和运行期反射;我们可以利用apt技术在编译期去动态生成代码,利用反射在运行期去调用代码。(apt 全称annotation processing tool 注解处理工具,)
-
注解的代价:侵入式编程,增加了耦合性。反射破坏了封装。更难以debug
-
元注解:在注解类上面配置一些参数。元注解是利用apt tools 技术来实现的。其中有一个@Retention 表示注解的生命周期三种, source表示源文件中有效,例如Overrite 。class 会保存在class中,不会加载进JVM。runtime 在运行时一直都有效,可以通过反射来获取其注解。比如 在使用Retrofit 你传给它一个apiService里面定义了接口地址,它通过反射来取数据,例如是@GET or @POST 。
二. 如何使用
当使用注解在运行时获取注解的属性。
第一个例子
2.1 [-> DeprecatedTracker.java]
public static void trackDeprecated(Class<?> cl) {
Field[] fields = cl.getDeclaredFields();
for (Field field : fields) {
//读取到带注解的字段,如果使用注解就返回true【见小节3.1】
if (field.getAnnotation(Deprecated.class) != null) {
System.out.println("field: " + field);
}
}
Method[] methods = cl.getDeclaredMethods();
for (Method method : methods) {
//读取到带注解的方法
if (method.getAnnotation(Deprecated.class) != null) {
System.out.println("method: " + method);
}
}
Constructor<?>[] constructors = cl.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
//读取到带注解的构造方法
if (constructor.getAnnotation(Deprecated.class)!=null){
System.out.println("constructor: "+constructor);
}
}
}
public static void main(String[] args) {
DeprecatedTracker.trackDeprecated(VideoItem.class);//类【见小节2.2】
}
打印结果:
field: private java.lang.String com.company.annotation.VideoItem.id
method: public void com.company.annotation.VideoItem.setId(java.lang.String)
constructor: public com.company.annotation.VideoItem(java.lang.String)
2.2 [-> VideoItem.java]
public class VideoItem {
@Deprecated
private String id;
private String videoName;
private String videoUrl;
@Deprecated
public VideoItem(String id) {
this.id = id;
}
public VideoItem() {
}
@Deprecated
public void setId(String id) {
this.id = id;
}
}
第二个例子
2.3 [-> RegexValidTest.java]
public class RegexValidTest {
public static void main(String[] args) throws Exception {
//告诉系统把注解代理类编译后显示在项目目录中【见小节4.1】
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
RegexValidTest.User user = new RegexValidTest.User("小明", "2021-01-23", "327047419@qq.com", "1531348294");
if (RegexValidUtil.check(user)) {
System.out.println(user + "正则校验通过");
}
}
public static class User {
private String name;
@RegexValid(policy = Policy.DATE)//定义注解,一定时机有人去解析它
private String date;
@RegexValid(policy = Policy.MAIL)
private String email;
@RegexValid("0?(13|14|15|18|17)[0-9]{9}")
private String phone;
@RegexValid("0?(13|14|15|18|17)[0-9]{9}")
private int phone2;
public User(String name, String date, String email, String phone) {
this.name = name;
this.date = date;
this.email = email;
this.phone = phone;
}
public String toString() {
return "User{name='" + this.name + "', date='" + this.date + "', email='" + this.email + "', phone='" + this.phone + "'}";
}
}
}
2.4 [-> RegexValid.java]
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {
RegexValid.Policy policy() default RegexValid.Policy.EMPTY;
String value() default "";
public static enum Policy {
EMPTY((String)null),
DATE("\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}"),
MAIL("\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}");
private String policy;
private Policy(String policy) {
this.policy = policy;
}
public String getPolicy() {
return this.policy;
}
}
}
注解中的数据类型是有限制的。支持如下
- 基本类型
- String类型
- Class类型
- enum
- Annotation
- 以上类型的数组
注解的属性必须有确认的值。建议指定默认值
2.5 [-> RegexValidUtil.java]
public class RegexValidUtil {
public static boolean check(Object obj) throws Exception {
boolean result = true;
StringBuilder sb = new StringBuilder();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
//判断obj类当前字段是否使用了RegexValid的注解【见小节3.1】
if (field.isAnnotationPresent(RegexValid.class)) {
RegexValid regexValid = field.getAnnotation(RegexValid.class);【见小节3.2】
String value = regexValid.value();
if ("".equals(value)) {
value = regexValid.policy().getPolicy();
}
field.setAccessible(true);
Object fieldObj = null;
try {
fieldObj = field.get(obj);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
if (fieldObj == null) {
sb.append("\n").append(String.format(" %s 类中的 %s 字段不能为空!", obj.getClass().getName(), field.getName()));
result = false;
} else {
if (fieldObj instanceof String) {
String text = (String) fieldObj;
result = Pattern.compile(value).matcher(text).matches();
if (!result) sb.append("\n").append(String.format("%s 不是合法的 %s!", text, field.getName()));
} else {
sb.append("\n").append(String.format("%s 不是String 无法校验!", fieldObj));
}
}
}
}
if (sb.length() > 0) throw new Exception(sb.toString());
return result;
}
}
当使用注解去动态生成代码。我们来去仿butterknife(在Android使用)。
第三个例子
2.3 [-> BindView.java]
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
2.4 [-> ButterKnife.java]
public class ButterKnife {
//利用反射来调用注解生成的类,来绑定view的id(赋值其成员view对象)
public static void bind(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
try {
Class<?> bindViewClass = Class.forName(clazz.getName() + "ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (Exception e) {
e.printStackTrace();
Log.d("liuyuzhe", "ButterKnife.bind: "+e.getMessage());
}
}
}
还需要这2个module
- Java library: apt_annotation 定义注解
- Java library: apt_processor 定义注解处理器来生成Java类
RegexValid regexValid = field.getAnnotation(RegexValid.class) //getAnnotation创建了AnnotationInvocationHandler对象并且把接口的数据内容存到了map中
三. 源码解析
运行期注解使用@interface来实现,编译后会变成一个接口(@会被去掉),并且实现了Annotation接口。而我们通过反射返回获取到的对象其实是一个代理类。这代理类是包装类,本质是通过AnnotationInvocationHandler来调用方法的,它内部维护了一个map,map的key是方法名 value是方法对应的值。
3.1 [-> AccessibleObject.java]
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
//这里转发给父类调用
return AnnotatedElement.super.isAnnotationPresent(annotationClass);
}
3.1.1 [-> AnnotatedElement.java]
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;【小节3.2】
}
3.2 [-> Field.java]
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
return annotationClass.cast(declaredAnnotations().get(annotationClass));
}
Field 它重写了getAnnotation方法,从map尝试取字段上是否有annotationClass这个注解对象。
3.3 [-> Field.java]
private Map<Class<? extends Annotation>, Annotation> declaredAnnotations() {
Map<Class<? extends Annotation>, Annotation> declAnnos;
if ((declAnnos = declaredAnnotations) == null) {
synchronized (this) {
if ((declAnnos = declaredAnnotations) == null) {
Executable root = (Executable)getRoot();
if (root != null) {
declAnnos = root.declaredAnnotations();
} else {
//如果没有缓存数据,则会解析注解的数据,把对象存到declAnnos的map中【见小节3.3.1】
declAnnos = AnnotationParser.parseAnnotations(
getAnnotationBytes(),
SharedSecrets.getJavaLangAccess().
getConstantPool(getDeclaringClass()),
getDeclaringClass()
);
}
declaredAnnotations = declAnnos;
}
}
}
return declAnnos;
}
3.3.1 [-> AnnotationParser.java]
parseAnnotations ->会转发调用parseAnnotation2
@SuppressWarnings("unchecked")
private static Annotation parseAnnotation2(ByteBuffer buf,
ConstantPool constPool,
Class<?> container,
boolean exceptionOnMissingAnnotationClass,
Class<? extends Annotation>[] selectAnnotationClasses) {
Map<String, Object> memberValues =
new LinkedHashMap<String, Object>(type.memberDefaults());//创建缓存map
int numMembers = buf.getShort() & 0xFFFF;
for (int i = 0; i < numMembers; i++) {
int memberNameIndex = buf.getShort() & 0xFFFF;
String memberName = constPool.getUTF8At(memberNameIndex);
Class<?> memberType = memberTypes.get(memberName);
if (memberType == null) {
// Member is no longer present in annotation type; ignore it
skipMemberValue(buf);
} else {
//去解析获取注解里的值
Object value = parseMemberValue(memberType, buf, constPool, container);
if (value instanceof AnnotationTypeMismatchExceptionProxy)
((AnnotationTypeMismatchExceptionProxy) value).
setMember(type.members().get(memberName));
memberValues.put(memberName, value);//方法名和其值存起来,待调用时返回给客户端
}
}
return annotationForMap(annotationClass, memberValues);【见小节3.4】
}
3.4 [-> AnnotationParser.java]
我们直接从parseAnnotations方法 一直跟进到annotationForMap方法
/**
* Returns an annotation of the given type backed by the given
* member {@literal ->} value map.
*/
public static Annotation annotationForMap(final Class<? extends Annotation> type,
final Map<String, Object> memberValues)
{
//这里利用JDK动态代理,type是注解class,memberValues是缓存map key是方法(接口里属性)名 value是;当使用注解调用方法时,会走AnnotationInvocationHandler的invoke方法
return AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
return (Annotation) Proxy.newProxyInstance(
type.getClassLoader(), new Class<?>[] { type },
new AnnotationInvocationHandler(type, memberValues));
}});
}
3.5 [-> AnnotationInvocationHandler.java]
public Object invoke(Object proxy, Method method, Object[] args) {
//取出方法名字
String member = method.getName();
int parameterCount = method.getParameterCount();
// Handle Object and Annotation methods
if (parameterCount == 1 && member == "equals" &&
method.getParameterTypes()[0] == Object.class) {
return equalsImpl(proxy, args[0]);
}
if (parameterCount != 0) {
throw new AssertionError("Too many parameters for an annotation method");
}
//如果是系统自带方法直接返回默认实现
if (member == "toString") {
return toStringImpl();
} else if (member == "hashCode") {
return hashCodeImpl();
} else if (member == "annotationType") {
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);//如果是客户端自定义方法从缓存的Map中取出。如何存的【见小节3.4】
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
4.1 [-> $Proxy1.java]
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.sun.proxy;
import com.company.annotation.RegexValid;
import com.company.annotation.RegexValid.Policy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1 extends Proxy implements RegexValid {
private static Method m0;
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m5;
public $Proxy1(InvocationHandler param1) {
super(var1);
}
public final int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//这些hashcode 和 toString 等都是默认生成的
public final boolean equals(Object var1) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//这value是我们自定义的方法,h是AnnotationInvocationHandler调用了invoke走到了【见小节3.5】
public final String value() {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Policy policy() {
try {
return (Policy)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() {
try {
return (Class)super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.company.annotation.RegexValid").getMethod("value");
m4 = Class.forName("com.company.annotation.RegexValid").getMethod("policy");
m5 = Class.forName("com.company.annotation.RegexValid").getMethod("annotationType");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
四. 小结
经过源码分析发现注解的动态代理的处理逻辑和Retrofit的动态代理一样,难道Retrofit作者仿注解思路来写的🤣。
开发中编译后可以在build->source->apt里找到编译生成的代码。