`
dingchd
  • 浏览: 15078 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

超轻量级的REST框架实现

 
阅读更多

RESTful webservice相比SOAP webservice复杂度低很多,REST鼓励无状态设计,完全由http协议,且返回值为json

 

本文设计基于Servlet请求转发的一个超轻量级的REST框架(某种程度也可视为MVC框架)

 

类UML如下图:



ClassParser扩展自ClassVisitor用于扫描指定路径下的class文件,并建立url同处理器的对应关系

ProcessDesc描述了一个处理器,包含请求的url、调用的单例fqcn、方法名、参数映射列表

ProcessorManager实现了具体的创建请求url同处理器的对应关系,并提供运行时执行引擎

RequestHandlerServlet为前端执行分发器,需要在web.xml中进行配置

 

设计三种注解:

Resource:用于标注类为处理器

Path:用于标注方法对应的url路径

RequestVariable:用于标注方法参数映射的Http请求的参数名字

 

实现一个简单的处理器类:

@Resource
public class ActionResource {
	@Path("/a/b")
	public String getId(@RequestVariable("tid") String tid,
			@RequestVariable("bid") String bid) {
		return "hello "+tid+":"+bid;
	}
}

 

在浏览器中输入 localhost:8080/WebHandler/a/b?tid=100&bid=haha

则返回:



 

至此,实现了简单的REST框架,原理即是将所有HTTP请求都映射到RequestHandlerSevlet,ProcessorManager在Servlet.init阶段扫描工程路径下的所有class文件,并建立对应关系,Servlet处理http请求时根据不同的url分发到指定的处理器(即上图的ActionResource),处理器应当是POJO的。

 

核心源码如下:

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.objectweb.asm.ClassReader;

/**
 * 处理器管理器类,提供: 指定路径的类文件并建立url同处理器描述的对应关系 运行时根据url获得指定处理器描述
 * 
 * @author dingchunda
 * 
 */
public class ProcessorManager {
	private static ProcessorManager single;

	// key-fqcn value-object
	private Map<String, Object> singleObject = new HashMap<String, Object>();
	// key-desc value-ProcessDesc
	private Map<String, ProcessDesc> mapping = new HashMap<String, ProcessDesc>();

	/**
	 * 将path目录下的所有class文件扫描,建立url到处理器的初始映射
	 * 
	 * @param path
	 * @throws DOFException
	 */
	public void scan(String path) throws DOFException {
		// 获取当前目录列表
		File base = new File(path);
		File[] childrenFiles = base.listFiles();

		for (File childFile : childrenFiles) {
			if (childFile.isDirectory()) {
				scan(childFile.getAbsolutePath());
			} else if (childFile.isFile()
					&& childFile.getName().endsWith(".class")) {
				scanFile(childFile);
			}
		}
	}

	/**
	 * 扫描当前文件
	 * 
	 * @param file
	 * @throws DOFException
	 */
	private void scanFile(File file) throws DOFException {
		ClassParser parser = null;
		try {
			FileInputStream fin = new FileInputStream(file);

			// 使用asm工具访问类文件
			parser = new ClassParser();
			ClassReader reader = new ClassReader(fin);
			reader.accept(parser, 0);
		} catch (IOException e) {
			String str = "parse class file failed:" + e.toString();
			throw new DOFException(str, e, DOFException.DEFAULT);
		}

		// 如果class文件存在类注解Resource则进行解析
		if (parser.isResouce()) {
			String fqcn = parser.getFQCN();

			// 如果已经检测过,则不进行解析
			if (!singleObject.containsKey(fqcn)) {
				// 创建单例对象
				try {
					Class<?> c = Class.forName(fqcn);
					Object o = c.newInstance();

					singleObject.put(fqcn, o);
				} catch (Exception e) {
					String str = "create singleton object failed:"
							+ e.toString();
					throw new DOFException(str, e, DOFException.DEFAULT);
				}

				List<ProcessDesc> descs = parser.getMethodProcessDesc();
				for (ProcessDesc desc : descs) {
					mapping.put(desc.getURL(), desc);
				}
			}
		}
	}

	public boolean isRoutable(String url) {
		return mapping.containsKey(url);
	}

	/**
	 * 方法调用
	 * 
	 * @param url
	 *            请求的url
	 * @param params
	 *            运行时方法参数
	 * @return json
	 * 
	 * @throws DOFException
	 */
	public String invoke(String url, Map<String, String[]> params)
			throws DOFException {
		// 根据url获取处理器描述
		ProcessDesc desc = mapping.get(url);

		// 获取处理器参数列表描述
		String[] paramVariables = desc.getParamNames();
		int paramLen = paramVariables.length;

		// 组装方法参数类型,均为String类型
		Class<?>[] paramTypes = new Class<?>[paramVariables.length];
		for (int i = 0; i < paramLen; i++) {
			paramTypes[i] = String.class;
		}

		// 获取方法句柄
		Object o = singleObject.get(desc.getFqcn());
		Method m = null;
		try {
			m = o.getClass().getMethod(desc.getMethod(), paramTypes);
		} catch (Exception e) {
			String str = "get method handler failed:" + e.toString();
			throw new DOFException(str, e, DOFException.DEFAULT);
		}

		// 组装运行时参数
		Object[] runtimeParams = new String[paramLen];
		for (int i = 0; i < paramLen; i++) {
			runtimeParams[i] = params.get(paramVariables[i])[0];
		}

		// 方法调用
		String json = null;
		try {
			json = (String) m.invoke(o, runtimeParams);
		} catch (Exception e) {
			String str = "invoke handler failed:" + e.toString();
			throw new DOFException(str, e, DOFException.DEFAULT);
		}

		return json;
	}

	/**
	 * 获取单例
	 * 
	 * @return
	 */
	public synchronized static ProcessorManager getSingleInstance() {
		if (single == null) {
			single = new ProcessorManager();
		}

		return single;
	}
}

---

package test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.MethodNode;

/**
 * 类字节码访问器,从字节码中解析出处理器描述
 * 一个处理器描述包含:
 * <li>访问的url</li>
 * <li>方法名字</li>
 * <li>类全路径</li>
 * 请求参数映射表
 * @see ProcessDesc
 * @author dingchunda
 *
 */
public class ClassParser implements ClassVisitor {
	private static final String RESOURCE = "Resource";
	private static final String PATH = "Path";
	private static final String REQUEST = "RequestVariable";

	private List<AnnotationNode> visibleAnnotations = new ArrayList<AnnotationNode>();

	private String fqcn; // 类全路径 a/b/c

	// key 方法名, value 方法描述
	private Map<String, MethodNode> methods = new HashMap<String, MethodNode>();

	public String getFQCN() {
		return fqcn.replace("/", ".");
	}

	private final String getPrefix() {
		int pos = fqcn.lastIndexOf("/");
		return "L" + fqcn.substring(0, pos);
	}

	@Override
	public void visit(int arg0, int arg1, String arg2, String arg3,
			String arg4, String[] arg5) {
		fqcn = arg2;
	}

	@Override
	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		AnnotationNode an = new AnnotationNode(desc);
		if (visible) {
			visibleAnnotations.add(an);
		}

		return an;
	}

	@Override
	public void visitAttribute(Attribute arg0) {
	}

	@Override
	public void visitEnd() {
	}

	@Override
	public FieldVisitor visitField(int arg0, String arg1, String arg2,
			String arg3, Object arg4) {
		return null;
	}

	@Override
	public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
	}

	@Override
	public MethodVisitor visitMethod(int accessFlag, String methodName,
			String desc, String signature, String[] exceptions) {
		MethodNode node = new MethodNode(accessFlag, methodName, desc,
				signature, exceptions);

		methods.put(methodName, node);
		return node;
	}

	@Override
	public void visitOuterClass(String arg0, String arg1, String arg2) {
	}

	@Override
	public void visitSource(String arg0, String arg1) {
	}

	/**
	 * 检查当前类是否使用Resouce注解标记
	 * 
	 * @return
	 */
	public boolean isResouce() {
		for (AnnotationNode annotation : visibleAnnotations) {
			String checkString = getPrefix() + "/" + RESOURCE + ";";
			if (checkString.equals(annotation.desc)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 获取当前类的映射方法集合
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<ProcessDesc> getMethodProcessDesc() {
		List<ProcessDesc> list = new ArrayList<ProcessDesc>();

		for (Entry<String, MethodNode> entry : methods.entrySet()) {
			MethodNode methodNode = entry.getValue();
			String methodName = entry.getKey();

			List<AnnotationNode> methodAnnotations = methodNode.visibleAnnotations;
			if (methodAnnotations != null) {
				for (AnnotationNode methodAnnotation : methodAnnotations) {
					String checkString = getPrefix() + "/" + PATH + ";";
					if (checkString.equals(methodAnnotation.desc)) {
						String url = (String) methodAnnotation.values.get(1);
						list.add(getSingleMethodProcessDesc(methodNode, url,
								methodName));
						break;
					}
				}
			}
		}

		return list;
	}

	/**
	 * 解析单个方法
	 * 
	 * @param methodNode
	 * @param url
	 * @param methodName
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private ProcessDesc getSingleMethodProcessDesc(MethodNode methodNode,
			String url, String methodName) {

		List<AnnotationNode>[] pNodes = methodNode.visibleParameterAnnotations;

		ArrayList<String> requestVariables = new ArrayList<String>();

		if (pNodes != null) {
			for (List<AnnotationNode> pNode : pNodes) {
				for (AnnotationNode ano : pNode) {
					String checkString = getPrefix() + "/" + REQUEST + ";";
					if (checkString.equals(ano.desc)) {
						requestVariables.add((String) ano.values.get(1));
					}
				}
			}
		}

		String[] requests = new String[requestVariables.size()];
		requestVariables.toArray(requests);

		// 使用"/"分割的fqcn,以便类加载器加载
		String fqcn = this.fqcn.replace("/", ".");

		return new ProcessDesc(url, methodName, fqcn, requests);
	}
}

 ---

package test;

import java.io.IOException;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RequestHandlerServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private static ProcessorManager manager;

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		String path = config.getServletContext().getRealPath("/");

		try {
			manager = ProcessorManager.getSingleInstance();
			manager.scan(path);
		} catch (DOFException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void destroy() {

	}

	@Override
	public void service(ServletRequest request, ServletResponse response)
			throws ServletException, IOException {
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		// 获取访问的具体url
		String uri = httpRequest.getRequestURI();
		uri = uri.substring(uri.indexOf("/", 1));
		int index = uri.indexOf("?");
		if (index != -1) {
			uri = uri.substring(0, index);
		}

		if (!manager.isRoutable(uri)) {
			super.service(httpRequest, httpResponse);
		}

		// 请求路由并处理
		Map<String, String[]> params = request.getParameterMap();
		try {
			String json = manager.invoke(uri, params);

			response.getOutputStream().write(json.getBytes("utf-8"));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 

 详细工程见附件

 


 

 

  • 大小: 19.3 KB
  • 大小: 9.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics