Java自定义注解和动态加载
我正在尝试开发用于数据库同步的 ORM,并决定尝试一下 Java 反射。我有一个定义同步注释的库,如下所示
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Synchronised {
String tableName();
SynchronisationType synchronisationType();
}
在android项目中,我使用此注释来标记模型类
@Synchronised(tableName="UserAccounts", synchronisationType=SynchronisationType.DownloadOnly)
public class UserAccount {
@SynchronisedField(fieldName="CompanyFk")
private int companyId;
@SynchronisedField(fieldName="Id")
private int userId;
@SynchronisedField(fieldName="Username")
private String username;
@SynchronisedField(fieldName="Password")
private String password;
@SynchronisedField(fieldName="Salt")
private String salt;
@SynchronisedField(fieldName="IsLocked")
private boolean isLocked;
@SynchronisedField(fieldName="HasMobileAccess")
private boolean hasMobileAccess;
}
经过一番调查,最终编写了一个“加载器”方法,允许在当前apk中发现模型类。它应该检索所有标记为“Synchronized”的类,但这里的问题是 getAttribute(Synchronized.class) 不起作用。手动迭代注释并搜索属性(instanceof等)也不起作用。调试时,我注意到来自反射的注释实际上是一个代理(getClass() 给出“class Proxy2”,而annotation.AnnotationType() 返回正确的名称) - 这解释了为什么我之前的尝试都没有成功。当尝试直接转换时 - 抛出转换异常(可以理解)。基本上我在这里不知所措,所以欢迎任何想法。
模型加载方法(在库项目中):
public static List<Class> getModels(Context context, String packageName)
throws IOException, URISyntaxException, ClassNotFoundException,
NameNotFoundException {
String apkName = context.getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
DexFile dexFile = new DexFile(apkName);
//PathClassLoader classLoader = new PathClassLoader(apkName, ClassLoader.getSystemClassLoader());
PathClassLoader classLoader = new PathClassLoader(apkName, Thread.currentThread().getContextClassLoader());
List<Class> classes = new ArrayList<Class>();
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String entry = entries.nextElement();
// only check items that exist in source package and not in libraries, etc.
if (entry.startsWith(packageName)) {
Log.d(TAG, "Entry: " + entry);
Class<?> entryClass = classLoader.loadClass(entry);//dexFile.loadClass(entry, classLoader);
if (entryClass != null) {
Annotation[] annotations = entryClass.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Synchronised) {
classes.add(entryClass);
}
}
}
}
}
return classes;
}
[解决方案] 类加载器需要像这样链接:
PathClassLoader classLoader2 = new PathClassLoader(apkName, Thread.currentThread().getContextClassLoader());
DexClassLoader classLoader = new DexClassLoader(apkName, new ContextWrapper(context).getCacheDir().getAbsolutePath(), null, classLoader2);
如果您仍然不知道我在说什么,感谢您阅读这篇冗长的文章(-.
I'm trying to develop ORM for database synchronisation and decided to give Java reflection a go. I have a library that defines Synchronised annotation like this
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Synchronised {
String tableName();
SynchronisationType synchronisationType();
}
In android project I use this annotation to mark a model class
@Synchronised(tableName="UserAccounts", synchronisationType=SynchronisationType.DownloadOnly)
public class UserAccount {
@SynchronisedField(fieldName="CompanyFk")
private int companyId;
@SynchronisedField(fieldName="Id")
private int userId;
@SynchronisedField(fieldName="Username")
private String username;
@SynchronisedField(fieldName="Password")
private String password;
@SynchronisedField(fieldName="Salt")
private String salt;
@SynchronisedField(fieldName="IsLocked")
private boolean isLocked;
@SynchronisedField(fieldName="HasMobileAccess")
private boolean hasMobileAccess;
}
After some investigation a "loader" method was finally written that allows discovering model classes in current apk. It should retrieve all classes that are marked as "Synchronised", but the issue here is that getAttribute(Synchronised.class) doesn't work. Manually iterating annotations and searching for the attribute (instanceof, etc.) doesn't work either. When debugging I noticed that annotation coming from reflection is actually a proxy (getClass() gives "class Proxy2", while annotation.AnnotationType() returns correct name) - this explains why non of my previous attempts succeeded. When trying to cast directly - a cast exception is thrown (understandably). Basically I'm at a loss here so any idea is welcomed.
Model loading method (in library project):
public static List<Class> getModels(Context context, String packageName)
throws IOException, URISyntaxException, ClassNotFoundException,
NameNotFoundException {
String apkName = context.getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
DexFile dexFile = new DexFile(apkName);
//PathClassLoader classLoader = new PathClassLoader(apkName, ClassLoader.getSystemClassLoader());
PathClassLoader classLoader = new PathClassLoader(apkName, Thread.currentThread().getContextClassLoader());
List<Class> classes = new ArrayList<Class>();
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String entry = entries.nextElement();
// only check items that exist in source package and not in libraries, etc.
if (entry.startsWith(packageName)) {
Log.d(TAG, "Entry: " + entry);
Class<?> entryClass = classLoader.loadClass(entry);//dexFile.loadClass(entry, classLoader);
if (entryClass != null) {
Annotation[] annotations = entryClass.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Synchronised) {
classes.add(entryClass);
}
}
}
}
}
return classes;
}
[SOLUTION] The class loader needs to be chained like this:
PathClassLoader classLoader2 = new PathClassLoader(apkName, Thread.currentThread().getContextClassLoader());
DexClassLoader classLoader = new DexClassLoader(apkName, new ContextWrapper(context).getCacheDir().getAbsolutePath(), null, classLoader2);
If you still have no idea what I'm talking about, thanks for reading this lengthy post (-.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
仅获取您感兴趣的注释:
更新:
问题是 DexFile.getClass() 显然返回一个代理。 OP已找到答案并用解决方案更新了问题。
Get just annotation that you are interested in:
Update:
The problem is that DexFile.getClass() apparently returns a proxy. OP has found the anser and updated the question with a solution.