转载

java – 反射与注解小练手

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个方法:

  • getUserInfo方法:指定path匹配/user/{username}/info这样的URI,其中{username}的花括号表示捕获URI中该位置的值,作为String username参数传给方法。
  • getProductInfo方法:一样的道理,不过捕获的productID要作为int类型传参给getProductInfo方法。

接下来,我要实现路由逻辑,根据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负责具体路由逻辑:

  • 拆分URI:将请求的URI按/拆分成数组,这是为了后续与@Uri注解中的path高效匹配。
  • 遍历所有方法:反射App类得到所有method,并进一步获取每个method上的Uri类的注解对象,
    • 如果method应用了Uri注解,那么得到其path值,按/拆分成数组,并且与Uri进行匹配判定。
    • 如果path某个部分是{xxx}的捕获语法,那么将其对应URI中的值捕获下来,作为后续调用method的传参,注意捕获的都是String。

调用方法

	// 路由匹配
			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

全文结束。

如果文章帮到了你,请您乐于扫码捐赠1元钱,以便支持服务器运转。

java – 反射与注解小练手
原文  https://yuerblog.cc/2019/08/10/java-反射与注解小练手/
正文到此结束
Loading...