目前确实已经有很多十分完善的框架,但是也一定会有想自己搭建框架的想法,学会反射、注解是最基础的。注解本身并不是非常困难,难的是利用注解设计自己的框架,以下是对注解知识的总结和归纳,重点放在参数注解上,希望有一定的Java反射知识之后再来看这些内容。
我觉得数据库Dao层的设计会相对简单一些,大家可以尝试一波,可以参考我另一篇文章,然后再利用字段注解完善,思路大概是通过注解找到数据库中对应的表名、列名:利用反射机制设计Dao
AnnotatedElement属于java.lang.reflect.AnnotatedElement),这个包提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见(正常情况,编译后删除,它是占额外资源的,所以说避免滥用注解),当class文件被装载时被保存在class文件中。
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。
该接口主要有如下几个实现类:
程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
1. getAnnotation(Class annotationClass):
返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
2. getAnnotations():返回该程序元素上存在的所有注解。
3. isAnnotationPresent(Class annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
4. getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
java API提供了四个元注解,是专门用来定义注解的注解,分别是:@Target,@Retention,@Documented,@Inherited ,其作用分别如下:
@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:
@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括:
@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。
@Inherited 允许子类继承父类中的注解。
/** * 创建一个空的注解 * * @author ChenSS * @version 1.2 */
public @interface Test {
}
/** * 引用类 * * @author ChenSS */
public class AnnotationTest {
@Test // 使用了类成员注解
private Integer age;
@Test // 使用了构造方法注解
public AnnotationTest() {
}
@Test // 使用了类方法注解
public void a() {
@Test // 使用了局部变量注解
Map m = new HashMap(0);
}
@Test
public void b(@Test Integer a) { // 使用了方法参数注解
}
}
方法:如果是方法注解,就通过反射获取方法,再获取方法的注解,就是这么简单,前提是要会反射
看了下面的代码,大家可能有一个疑问。怎么没有使用value,而直接就写”abc”了。那么”abc”到底传给谁了,System.out.println(test.value())能正常执行么?其实这里有一个约定。如果没有写属性名的值,而这个注释又有value属性,就将这个值赋给value属性,如果没有,就出现编译错误。
//我们希望注解用在方法上,所以ElementType设置为METHOD
@Target(ElementType.METHOD)
//我们希望运行时也可以使用注解,所以RetentionPolicy设置为RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
//不设置默认值
public String value();
//默认为3
public int id() default 3;
}
class UsingTest{
@Test(value="id",id=1)
private String id;
//这里只是写"abc",不明确指出给谁赋值,测试运行效果
@Test("abc")
public void getId(){
}
}
public class AnnotationTest {
public static void main(String[] args) {
//获取注解的值
for(Method method:UsingTest.class.getDeclaredMethods()){
Test test=method.getAnnotation(Test.class);
if(test==null)
break;
System.out.println(test.value());
System.out.println(test.id());
}
for(Field field:UsingTest.class.getDeclaredFields()){
Test test=field.getAnnotation(Test.class);
System.out.println(test.value());
System.out.println(test.id());
}
}
}
/** * 设计某种查询方式的接口,比如:数据库查询列名为id,值为3,我们希望用户指定这两个参数,然后列名通过注解获取 * * @author ChenSS * */
@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
//值
String value();
boolean encoded() default false;
}
/** * 一个使用该接口的类,如果是数据库查询就叫某某dao,这里取名RetrofitService,因为是从安卓代码拷贝的 * * @author ChenSS * */
class RetrofitService {
/** * 意思就是说:我们要根据id、name对数据库做一个查询 * * @param id * @param name */
public void query(@Query("id") String id, @Query("name") String name) {
// 方法体
};
}
/** * 测试类,先遍历所有的方法,再获取方法中的参数注解,之后再考虑 * 获取一下参数值,就达到目的了,但是代码似乎还有冗余 * * @author ChenSS * */
public class AnotationTest {
public static void main(String[] args) {
Class<RetrofitService> clazz = RetrofitService.class;
//获取所有方法
for (Method method : clazz.getDeclaredMethods()) {
//获取方法中参数的注解
for (Parameter parameter : method.getParameters()) {
Query query = parameter.getAnnotation(Query.class);
System.out.println(query.value()+"="+"再获取一下参数值");
}
}
}
}
使用getStackTrace()方法从堆栈中快速查找到我们调用的是哪一个方法
class RetrofitService {
/** * 大概意思就是说:我们要根据id、name对数据库做一个查询 * * @param id * @param name */
public void query(@Query("id") String id, @Query("name") String name) {
try {
test();
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
};
public void test() throws NoSuchMethodException, SecurityException {
//直接从堆栈中获取所需的方法(API方法介绍:供编程访问由 printStackTrace 输出的堆栈跟踪信息)
StackTraceElement[] stack = new Throwable().getStackTrace();
for (StackTraceElement stackTraceElement : stack) {
//这里输出堆栈中所有的方法名
System.out.println(stackTraceElement);
}
}
}
public class AnotationTest {
public static void main(String[] args) {
RetrofitService retrofitService = new RetrofitService();
retrofitService.query("1", "小明");
}
}
待解决的问题:通过反射获取参数注解,与方法的参数值,难以通过一个简单的、通用算法进行匹配。
问题可能出自我的设计思路,网络上说的较多的解决方式是动态代理机制,这等我学完动态代理再尝试解决。
如果是字段注解会轻松很多,但是参数注解无可避免。
如果对这个问题有什么好的思路,又或者有什么好的意见和建议,欢迎留言。
以下程序输出结果:
id=1
name=小明
class RetrofitService {
/** * 大概意思就是说:我们要根据id、name对数据库做一个查询 * * @param id * @param name */
public void query(@Query("id") String id, @Query("name") String name) {
try {
Parameter[] params = utils();
System.out.println(params[0].getAnnotation(Query.class).value()+"="+id);
System.out.println(params[1].getAnnotation(Query.class).value()+"="+name);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
};
/** * 直接从堆栈中获取调用此方法,的方法,的参数(不一定都提高效率,但是便于方法的统一) * * @return * * @throws NoSuchMethodException * @throws SecurityException */
public Parameter[] utils() throws NoSuchMethodException, SecurityException {
String methodName = new Throwable().getStackTrace()[1].getMethodName();
for (Method method : this.getClass().getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
return method.getParameters();
}
}
return null;
}
}
public class AnotationTest {
public static void main(String[] args) {
RetrofitService retrofitService = new RetrofitService();
retrofitService.query("1", "小明");
}
}