JAVA生态的开源项目大量使用反射,并且随处可见注解,如果不懂基础就照葫芦画瓢的确让人很不舒服。
下面我将利用注解和反射,实现一个简单的Web路由(类似spring mvc的感觉)示例:即在处理函数上利用注解配置URI映射,并自动的根据请求的URI调用对应的处理函数。
完整代码: https://github.com/owenliang/java-somewhat/tree/master/src/cc/yuerblog/annotation
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。
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个方法:
接下来,我要实现路由逻辑,根据URI找到对应的方法并调用。
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负责具体路由逻辑:
// 路由匹配
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了。
所以接下来做的事情是:
程序输出:
查找用户:owenliang
查找商品:110
全文结束。
如果文章帮到了你,请您乐于扫码捐赠1元钱,以便支持服务器运转。