java – 反射与注解小练手
JAVA生态的开源项目大量使用反射,并且随处可见注解,如果不懂基础就照葫芦画瓢的确让人很不舒服。
下面我将利用注解和反射,实现一个简单的Web路由(类似spring mvc的感觉)示例:即在处理函数上利用注解配置URI映射,并自动的根据请求的URI调用对应的处理函数。
完整代码:https://github.com/owenliang/java-somewhat/tree/master/src/cc/yuerblog/annotation
定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package cc.yuerblog.annotation; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Retention; /** * 用于配置路由的注解,用于类方法 * @author liangdong * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Uri { String path() default "/"; } |
通过@interface可以定义注解类Uri。
该注解使用的时候可以接收1个参数叫做path,用于声明哪个方法用于处理哪个URI的请求,稍等下面我们会看到使用示例。
Target元注解声明Uri注解只能用在类的方法上,ElementType也支持指定为类的注解、用于参数的注解、用于私有变量的注解等等。
Retention元注解声明Uri注解是运行时的,也就是说JVM运行我们程序的时候,我们可以通过反射机制拿到Uri注解的详细信息,比如获取其中的path()参数。
还是有点抽象,所以下面先说怎么用Uri。
使用注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package cc.yuerblog.annotation; import cc.yuerblog.annotation.Uri; public class App { @Uri(path="/user/{username}/info") public void getUserInfo(String username) { System.out.println("查找用户:" + username); } @Uri(path="/product/{productID}/info") public void getProductInfo(int productID) { System.out.println("查找商品:" + productID); } } |
App类相当于Controller,用于处理HTTP请求。
有2个方法:
- getUserInfo方法:指定path匹配/user/{username}/info这样的URI,其中{username}的花括号表示捕获URI中该位置的值,作为String username参数传给方法。
- getProductInfo方法:一样的道理,不过捕获的productID要作为int类型传参给getProductInfo方法。
接下来,我要实现路由逻辑,根据URI找到对应的方法并调用。
匹配路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package cc.yuerblog.annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List; import cc.yuerblog.annotation.Uri; public class Main { public static void main(String[] args) { App app = new App(); Main.handleRoute(app, "/user/owenliang/info"); Main.handleRoute(app, "/product/110/info"); } public static void handleRoute(App app, String uri) { // 拆分uri String[] uriArr = uri.split("/"); // 遍历所有方法 Method[] methods = app.getClass().getMethods(); for (Method method : methods) { // 获取Uri注解 Uri anno = method.getAnnotation(Uri.class); if (anno == null) { continue; } // 得到path String path = anno.path(); // 拆分path String[] pathArr = path.split("/"); // 匹配 if (pathArr.length != uriArr.length) { continue; } boolean isMatch = true; ArrayList<String> catchParams = new ArrayList<>(); for (int i = 0; i < pathArr.length; ++i) { if (pathArr[i].startsWith("{")) { // 参数捕获 String paramValue = uriArr[i]; // 捕获参数值 catchParams.add(paramValue); } else if (!pathArr[i].equals(uriArr[i])) { isMatch = false; break; } } |
首先创建App对象,测试2个URI的路由结果。
handleRoute负责具体路由逻辑:
- 拆分URI:将请求的URI按/拆分成数组,这是为了后续与@Uri注解中的path高效匹配。
- 遍历所有方法:反射App类得到所有method,并进一步获取每个method上的Uri类的注解对象,
- 如果method应用了Uri注解,那么得到其path值,按/拆分成数组,并且与Uri进行匹配判定。
- 如果path某个部分是{xxx}的捕获语法,那么将其对应URI中的值捕获下来,作为后续调用method的传参,注意捕获的都是String。
调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// 路由匹配 if (isMatch) { // 进行参数校验 boolean canCall = true; // 反射方法参数 Parameter[] params = method.getParameters(); if (params.length == catchParams.size()) { // 参数数量相等 // 处理每个参数 List prepareParams = new ArrayList(); for (int i = 0; i < params.length; ++i) { // 根据类型进行转换 if (params[i].getType() == int.class) { // 数字参数 prepareParams.add(Integer.parseInt(catchParams.get(i))); } else if (params[i].getType() == String.class) { // 字符串参数 prepareParams.add(catchParams.get(i)); } else { // 不支持的类型 canCall = false; break; } } if (canCall) { // 执行对应的处理函数 try { method.invoke(app, prepareParams.toArray()); } catch (Exception e) { System.out.println(e); } } } break; } |
一旦确认path和URI完全匹配,那么就要最终调用该method了。
所以接下来做的事情是:
- 反射参数:getParameters()获取待调用method的所有参数信息,
- 确认参数个数和之前捕获的参数值个数相同。
- 遍历每个参数信息,根据其类型决定如何对捕获的值字符串进行类型转换:
- 参数是int,那么通过parseInt将捕获字符串变成Integer。
- 参数是String,那么原样传递。
- 最后,将准备好的参数值列表作为method的参数,通过invoke传入app对象与参数列表,就可以完成调用了。
程序输出:
查找用户:owenliang
查找商品:110
全文结束。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~
