超轻量级的REST框架实现

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

 

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

 

类UML如下图:


超轻量级的REST框架实现_第1张图片
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 singleObject = new HashMap();
	// key-desc value-ProcessDesc
	private Map mapping = new HashMap();

	/**
	 * 将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 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 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;

/**
 * 类字节码访问器,从字节码中解析出处理器描述
 * 一个处理器描述包含:
 * 
  • 访问的url
  • *
  • 方法名字
  • *
  • 类全路径
  • * 请求参数映射表 * @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 visibleAnnotations = new ArrayList(); private String fqcn; // 类全路径 a/b/c // key 方法名, value 方法描述 private Map methods = new HashMap(); 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 getMethodProcessDesc() { List list = new ArrayList(); for (Entry entry : methods.entrySet()) { MethodNode methodNode = entry.getValue(); String methodName = entry.getKey(); List 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[] pNodes = methodNode.visibleParameterAnnotations; ArrayList requestVariables = new ArrayList(); if (pNodes != null) { for (List 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 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();
    		}
    	}
    }
    

     

     详细工程见附件

     


     

     

    你可能感兴趣的:(java,rest,设计模式)