實現一個簡易版的SpringMvc框架

先來看咱們的程序入口DispatcherServlethtml

主要核心處理流程以下:java

1 掃描基礎包下的帶有controller 以及 service註解的class,並保存到listweb

2 對第一步掃描到的class進行實例化操做json

3 對第一步掃描到的class中帶有AutoWired註解的屬性值進入自動注入數組

4 創建url和方法,方法對應的controller的映射關係(使用map服務器

 

 

當以上準備工做完成之後,app

 

用戶提交請求,服務器獲取url 並在第4步創建的映射關係中查找ide

 

找到了,就執行相關方法,找不到 就報404錯誤測試

 

如下是核心代碼ui

 

幾個自定義註解

 

 

@Target({ElementType.FIELD})  

 

@Retention(RetentionPolicy.RUNTIME)  

 

@Documented  

 

public @interface Autowired {  

 

    String value() default "";  

 

}  

 

 

 

@Target({ElementType.TYPE})  

 

@Retention(RetentionPolicy.RUNTIME)  

 

@Documented  

 

public @interface Controller {  

 

  

 

    String value() default "";  

 

}

 

 

 

@Target({ElementType.TYPE, ElementType.METHOD})  

 

@Retention(RetentionPolicy.RUNTIME)  

 

@Documented  

 

public @interface RequestMapping {  

 

    String value() default "";  

 

}  

 

 

@Target({ElementType.PARAMETER})  

 

@Retention(RetentionPolicy.RUNTIME)  

 

@Documented  

 

public @interface RequestParam {  

 

    String value() default "";  

 

  

 

    boolean required() default true;  

 

}  

 

 

@Target({ElementType.TYPE})  

 

@Retention(RetentionPolicy.RUNTIME)  

 

@Documented  

 

public @interface Service {  

 

    String value() default "";  

 

}  

================================================================================

 

 

幫助類

 

須要自行引入

 

Pom位置

 

<dependency>


<groupId>org.objectweb.asm</groupId>


<artifactId>org.objectweb.asm</artifactId>


<version>3.3</version>


</dependency>

  

 

 

public class AsmUtil {
	/**
	 * 獲取指定類指定方法的參數名
	 * 
	 * @param clazz
	 *            要獲取參數名的class
	 * @param method
	 *            要獲取參數名的方法
	 * @return 按參數順序排列的參數名列表,若是沒有參數,則返回null
	 */
	public static String[] getMethodParameterNamesByAsm4(final Class clazz, final Method method) {
		final String methodName = method.getName();
		final Class<?>[] methodParameterTypes = method.getParameterTypes();
		final int methodParameterCount = methodParameterTypes.length;
		String className = method.getDeclaringClass().getName();
		final boolean isStatic = Modifier.isStatic(method.getModifiers());
		final String[] methodParametersNames = new String[methodParameterCount];
		int lastDotIndex = className.lastIndexOf(".");
		className = className.substring(lastDotIndex + 1) + ".class";
		InputStream is = clazz.getResourceAsStream(className);
		try {
			ClassReader cr = new ClassReader(is);
			ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
			cr.accept(new ClassAdapter(cw) {
				public MethodVisitor visitMethod(int access, String name, String desc, String signature,
						String[] exceptions) {

					MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

					final Type[] argTypes = Type.getArgumentTypes(desc);
					// 參數類型不一致
					if (!methodName.equals(name) || !matchTypes(argTypes, methodParameterTypes)) {
						return mv;
					}
					return new MethodAdapter(mv) {
						public void visitLocalVariable(String name, String desc, String signature, Label start,
								Label end, int index) {
							// 若是是靜態方法,第一個參數就是方法參數,非靜態方法,則第一個參數是 this ,而後纔是方法的參數
							int methodParameterIndex = isStatic ? index : index - 1;
							if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) {
								methodParametersNames[methodParameterIndex] = name;
							}
							super.visitLocalVariable(name, desc, signature, start, end, index);
						}
					};
				}
			}, 0);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return methodParametersNames;
	}

	/**
	 * 比較參數是否一致
	 */
	private static boolean matchTypes(Type[] types, Class<?>[] parameterTypes) {
		if (types.length != parameterTypes.length) {
			return false;
		}
		for (int i = 0; i < types.length; i++) {
			if (!Type.getType(parameterTypes[i]).equals(types[i])) {
				return false;
			}
		}
		return true;
	}
}

  

核心控制器

public class DispatcherServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = -8630585768835454263L;
	// 保存含有controller或者service註解的class名字
	private List<String> classNames = new ArrayList<>();
	// 保存含有實例化對象
	private Map<String, Object> instancesMap = new HashMap<>();
	// 保存url和url對應的方法以及controller的映射關係
	private Map<String, UrlMethodMappingModel> handlerMapping = new HashMap<>();

	public void init(ServletConfig config) throws ServletException {

		// 1 掃描基礎包下的全部controller 以及 service
		scanBasePackage(config.getInitParameter("basePackage"));

		try {
			// 2 實例化list中的對象 而且保存到map中
			instanceClass();
			// 3 注入使用了autowired的註解的屬性
			doAutoWired();
			// 4創建 url和方法的映射
			urlAndMethodMapping();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void urlAndMethodMapping() {
		for (Map.Entry<String, Object> entry : instancesMap.entrySet()) {
			Class<?> clazz = entry.getValue().getClass();
			// 只處理Controller的,只有Controller有RequestMapping
			if (!clazz.isAnnotationPresent(Controller.class)) {
				continue;
			}
			// 定義url
			StringBuffer urlBuffer = new StringBuffer();

			urlBuffer.append("/");
			if (clazz.isAnnotationPresent(RequestMapping.class)) {
				RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
				urlBuffer.append(requestMapping.value());

			}

			// 獲取方法上的RequestMapping
			Method[] methods = clazz.getMethods();
			// 只處理帶RequestMapping的方法
			for (Method method : methods) {
				if (!method.isAnnotationPresent(RequestMapping.class)) {
					break;
				}
				RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
				// requestMapping.value()便是在requestMapping上註解的請求地址,無論用戶寫不寫"/",咱們都給他補上
				String realUrl = urlBuffer.append("/" + methodMapping.value()).toString();
				// 替換掉多餘的"/",由於有的用戶在RequestMapping上寫"/xxx/xx",有的不寫,因此咱們處理掉多餘的"/"
				realUrl = realUrl.replaceAll("/+", "/");
				// -------獲取訪問url完整路徑end

				// ---獲取方法的請求參數開始
				// 獲取全部的參數的註解,有幾個參數就有幾個annotation[],空的話也會有一個數組
				// 是數組的緣由是,一個參數能夠有多個註解……
				Annotation[][] annotations = method.getParameterAnnotations();
				// 因爲後面的Method的invoke時,須要傳入全部參數的值的數組,因此須要保存各參數的位置
				Map<String/* 參數名 如 name */, Integer/* 參數下標位置 */> paramMap = new HashMap<>();

				// 獲取方法裏的全部參數的參數名(注意:此處使用了ASM.jar
				// 如TestController 的test(String name, HttpServletResponse
				// reponse),將獲得以下數組["name","reponse"]
				String[] paramNames = AsmUtil.getMethodParameterNamesByAsm4(clazz, method);
				// 獲取全部參數的類型
				Class<?>[] paramTypes = method.getParameterTypes();

				for (int i = 0; i < annotations.length; i++) {
					Annotation[] annotationArray = annotations[i];
					if (annotationArray.length == 0) // 就是當前參數沒有使用註解 (String
														// name )
					{
						Class<?> type = paramTypes[i];

						if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
							paramMap.put(type.getName(), i);
						} else { // 普通屬性
							paramMap.put(paramNames[i], i);
						}
					}
					// 有註解,就遍歷每一個參數上的全部註解
					for (Annotation annotation : annotationArray) {
						if (annotation.annotationType() == RequestParam.class) {
							String paramValue = ((RequestParam) annotation).value();
							if (!"".equals(paramValue)) {
								paramMap.put(paramValue, i);
								break;
							}

						}
					}
				}

				UrlMethodMappingModel model = new UrlMethodMappingModel(method, entry.getValue(), paramMap);
				handlerMapping.put(realUrl, model);
			}
		}
	}

	private void doAutoWired() {

		// 遍歷instancesMap中被託管的對象
		for (Map.Entry<String, Object> entry : instancesMap.entrySet()) {
			// 查找全部被Autowired註解的屬性
			Field[] fieldArray = entry.getValue().getClass().getDeclaredFields();
			for (Field field : fieldArray) {
				// 沒有使用Autowired註解
				if (!field.isAnnotationPresent(Autowired.class)) {
					continue;
				}
				Autowired autowired = field.getAnnotation(Autowired.class);
				if ("".equals(autowired.value())) {
					field.setAccessible(true);
					String beanName = lowerFirstChar(field.getType().getSimpleName());
					if (instancesMap.get(beanName) != null) {
						try {
							field.set(entry.getValue(), instancesMap.get(beanName));
						} catch (IllegalArgumentException e) {
							e.printStackTrace();
						} catch (IllegalAccessException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	}

	private void instanceClass() throws Exception {
		if (classNames.size() == 0) {
			return;
		}
		for (String className : classNames) {
			try {
				Class<?> clazz = Class.forName(className);
				// 若是是controller 或者 service 直接使用類名
				if (clazz.isAnnotationPresent(Controller.class)) {
					instancesMap.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance());

				} else if (clazz.isAnnotationPresent(Service.class)) {
					// 獲取註解上的名字 若是有的話
					// 獲取註解上的值
					Service service = clazz.getAnnotation(Service.class);
					String servicename = service.value();
					if (!"".equals(servicename)) {
						instancesMap.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance());
					} else {
						Class<?>[] interfaces = clazz.getInterfaces();
						// 這裏簡單處理 假定ServiceImpl只實現了一個接口
						for (Class<?> class1 : interfaces) {
							instancesMap.put(lowerFirstChar(class1.getSimpleName()), clazz.newInstance());
							break;
						}

					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}

	}

	private void scanBasePackage(String basePackage) {
		URL baseUrl = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));

		File baseFile = new File(baseUrl.getFile());
		File[] files = baseFile.listFiles();
		for (File file : files) {
			if (file.isDirectory()) { // 是目錄
				scanBasePackage(basePackage + "." + file.getName());
			} else { // 是文件

				if (!file.getName().endsWith(".class")) {
					continue;
				}
				String className = basePackage + "." + file.getName().replace(".class", "");
				// 獲取該名字對應的的字節碼對象 判斷是否是含有Controller註解以及Service註解
				try {
					Class<?> clazz = Class.forName(className);

					if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {
						classNames.add(className);
					}
				} catch (ClassNotFoundException e) {

					e.printStackTrace();
				}
			}
		}

	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		try {
			// 匹配用戶請求
			boolean isPattern = patternUrlRequest(request, response);
			if (!isPattern) {
				out(response, "404 not found");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private boolean patternUrlRequest(HttpServletRequest request, HttpServletResponse response) {
		String url = request.getRequestURI();
		String projectUrl = request.getContextPath();
		url = url.replace(projectUrl, "").replaceAll("/+", "/");
		UrlMethodMappingModel mappingModel = handlerMapping.get(url);
		if (mappingModel != null) // 匹配成功
		{
			Map<String, Integer> paramsIndex = mappingModel.getParamMap();
			Object[] values = new Object[paramsIndex.size()];
			// 獲取該方法的參數類型
			Class<?>[] methodTypes = mappingModel.getMethod().getParameterTypes();
			for (Map.Entry<String, Integer> params : paramsIndex.entrySet()) {
				String key = params.getKey(); // 變量名
				if (key.equals(HttpServletRequest.class.getName())) {
					values[params.getValue()] = request;
				} else if (key.equals(HttpServletResponse.class.getName())) {
					values[params.getValue()] = response;
				} else {
					String userPostValue = request.getParameter(key);
					Class<?> type = methodTypes[params.getValue()];
					// 轉換用戶提交上來的數據
					Object convertValue = convertValue(userPostValue, type);
					values[params.getValue()] = convertValue;
				}
			}
			// 激活該方法
			try {
				mappingModel.getMethod().invoke(mappingModel.getController(), values);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return true;
		} else {
			return false;
		}

	}

	private Object convertValue(String userPostValue, Class<?> type) {
		if (type == String.class) {
			return userPostValue;
		} else if (type == Integer.class || type == int.class) {
			return Integer.parseInt(userPostValue);
		} else if (type == Long.class || type == long.class) {
			return Long.valueOf(userPostValue);
		} else {
			try {
				throw new Exception("不支持的數據類型");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return null;
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}

	// 首字母小寫
	private String lowerFirstChar(String className) {
		char[] chars = className.toCharArray();
		chars[0] += 32;
		return String.valueOf(chars);
	}

	private void out(HttpServletResponse response, String str) {
		try {
			response.setContentType("application/json;charset=utf-8");
			response.getWriter().print(str);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private class UrlMethodMappingModel {
		private Method method;
		private Object controller;
		private Map<String/* 參數名 */, Integer/* 下標位置 */> paramMap;

		public Method getMethod() {
			return method;
		}

		public Object getController() {
			return controller;
		}

		public Map<String, Integer> getParamMap() {
			return paramMap;
		}

		public UrlMethodMappingModel(Method method, Object controller, Map<String, Integer> paramMap) {
			this.method = method;
			this.controller = controller;
			this.paramMap = paramMap;
		}
	}
}

  

測試Controller 

@Controller
public class TestController {
	@Autowired
	ITestService testServ;

	@RequestMapping(value = "/testAdd")
	public void testAdd(@RequestParam("name") String name, HttpServletResponse reponse) {
		String result = testServ.add(name);
		out(reponse, result);
	}
	@RequestMapping(value = "/test")
	public void test(String name, HttpServletResponse reponse) {
		String result = testServ.add(name+" *** ");
		out(reponse, result);
	}

	private void out(HttpServletResponse response, String str) {
		try {
			response.setContentType("application/json;charset=utf-8");
			response.getWriter().print(str);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

  

服務接口定義

public interface ITestService {
 String add(String name);
}

服務接口實現

@Service
public class TestServImp implements ITestService {
	@Override
	public String add(String name) {
		return "add " + name + " ";
	}

}

  運行結果以下

 

 總結:大致功能基本已經完成,若是想寫出更多功能,建議閱讀

相關文章
相關標籤/搜索