1. 程式人生 > >java 手寫程式碼簡單模擬SpringMVC

java 手寫程式碼簡單模擬SpringMVC

######1.在Spring MVC中,將一個普通的java類標註上Controller註解之後,再將類中的方法使用RequestMapping註解標註,那麼這個普通的java類就夠處理Web請求

######2.通過一個簡單的java專案來模擬Spring MVC,先說一下整體思路:

  • 1.定義@Controller、@RequestMapping註解
  • 2.模擬專案啟動時把這些類載入到統一容器中
  • 3.模擬web請求,執行請求並返回結果

######3.專案的整體結構

  • annotation:註解類
  • controller:業務控制器
  • test:測試類
  • util:把控制器載入到容器,並執行請求
    這裡寫圖片描述

######4.具體實現如下:
@Controller註解

package com.zypcy.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
	public String value() default "";
}

@RequestMapping註解

package com.zypcy.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
	public String value() default "";
}

專案啟動時,容器只加載@Controller註解類和包含@RequestMapping註解的方法
再定義一個檢視類ModelAndView,控制器可以返回檢視或rest資料

package com.zypcy.annotation;
//模擬SpringMvc的ModelAndView ,但渲染檢視需自行實現
public class ModelAndView {
	
	private String path;//檢視路徑
	private Object data;//資料
	
	private ModelAndView(){
		
	}
	
	public ModelAndView(String path){
		super();
		this.path = path;
	}
	
	public ModelAndView(Object data){
		super();
		this.data = data;
	}
	
	public ModelAndView(String path, Object data) {
		super();
		this.path = path;
		this.data = data;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}
}

######5.新增業務控制器

import com.zypcy.annotation.Controller;
import com.zypcy.annotation.ModelAndView;
import com.zypcy.annotation.RequestMapping;

@Controller
public class IndexController {
	
	//返回Rest json 資料
	@RequestMapping("/index")
	public String index(){
		System.out.println("歡迎進入IndexController的index方法");
		return "index";
	}
	
	//返回檢視,可以自行檢視渲染模版,把資料渲染到模版中,響應到客戶端
	@RequestMapping("/home")
	public ModelAndView home(){
		System.out.println("歡迎進入IndexController的index方法");
		return new ModelAndView("/home.html","home");
	}
	
	//未加註解的方法,不會被載入到容器中,也就是不會處理web請求
	public String getById(int id){
		return "zy"+id;
	}
}

######6.載入容器

package com.zypcy.util;

import java.lang.reflect.Method;
//容器中存放具體的Controller與Method物件
public class RequestExecuteModel {
	
	private Class<?> clazz;
	private Method method;
	
	public RequestExecuteModel(){
		
	}
	
	public RequestExecuteModel(Method method , Class<?> clazz){
		this.clazz = clazz;
		this.method = method;
	}

	public Class<?> getClazz() {
		return clazz;
	}

	public void setClazz(Class<?> clazz) {
		this.clazz = clazz;
	}

	public Method getMethod() {
		return method;
	}

	public void setMethod(Method method) {
		this.method = method;
	}
}

新增請求載入、處理類

package com.zypcy.util;

import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;

import com.zypcy.annotation.Controller;
import com.zypcy.annotation.ModelAndView;
import com.zypcy.annotation.RequestMapping;

public class RequestExecute {
	
	//容器,用來放class與method
	private static HashMap<String, RequestExecuteModel> maps = new HashMap<String, RequestExecuteModel>();
	
	/**
	 * 掃描含有@Controller註解的類,得到@RequestMapping註解的方法
	 * 1.先獲取包下面的所有類檔案
	 * 2.根據類檔案獲取有@Controller註解的class檔案
	 * 3.再獲取class中有@@RequestMapping註解的方法,並新增到HashMap中
	 * @param packagePath
	 */
	public static void scanRequestMapping(String packagePath) {
		try {
			//獲取包下面的所有類檔案
			String filePath = ClassLoader.getSystemResource("").getPath() + packagePath.replace(".", "/");  
			File file = new File(filePath);
			if(file.exists()){
				File[] files = file.listFiles();
				for(File f : files){
					//如果是目錄
					if(f.isDirectory()){
						//再加上目錄名,繼續掃描
						scanRequestMapping(packagePath + "." + f.getName());	
					}else{
						//如果是java類檔案
						String className = f.getName().substring(0,f.getName().length() - 6);
						//根據架包加類名載入class
						Class clazz = Class.forName(packagePath + '.' + className);
						//如果當前class使用了@Controller註解
						if(clazz != null && clazz.isAnnotationPresent(Controller.class)){
							//獲取類下面所有的方法
							Method[] methods = clazz.getDeclaredMethods();
							for(Method method : methods){
								//如果方法使用了@RequestMapping註解
								if(method.isAnnotationPresent(RequestMapping.class)){
									RequestMapping rm = method.getAnnotation(RequestMapping.class);
									RequestExecuteModel model = new RequestExecuteModel(method , clazz);
									maps.put(rm.value(), model);
								}
							}
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 執行請求,返回結果
	 * @param uri
	 */
	public static void executeRequest(String uri){
		try {
			RequestExecuteModel model = maps.get(uri);
			if(model != null){
				Method method = model.getMethod();
				Class clazz = model.getClazz();
				//執行請求,未傳入引數
				Object o = method.invoke(clazz.newInstance(), null);
				System.out.println("返回值:"+o);
				if(o instanceof ModelAndView){
					System.out.println("返回頁面檢視");
					ModelAndView mv = (ModelAndView)o;
					System.out.println("檢視的路徑: " + mv.getPath());
					System.out.println("檢視需渲染的資料:" + mv.getData());
				}else{
					System.out.println("返回json資料");
				}
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
}

######7.測試

package com.zypcy.test;

import com.zypcy.util.RequestExecute;

public class MvcTest {
	//測試
	public static void main(String[] args) {
		//專案啟動時,把控制器中的@RequestMapping路由請求載入進容器
		RequestExecute.scanRequestMapping("com.zypcy.controller");
		
		//模擬請求,獲取容器中的路由,並執行方法,獲取返回值
		//index方法返回 Json 資料
		RequestExecute.executeRequest("/index");
		
		System.out.println("--------------------------------");
		
		//home方法返回檢視,檢視可自行擴充套件對資料進行渲染
		RequestExecute.executeRequest("/home");
	}
}

下面是執行測試後結果:
這裡寫圖片描述

以上是一個簡單的MVC實現,未實現請求傳參,下面是請求加引數,不過需要新增2個jar包,spring-core-4.3.13.RELEASE.jar與commons-logging-1.1.2.jar

/**
* Spring利用類LocalVariableTableParameterNameDiscoverer得到方法引數名,並將相應的引數值注入到RequestMapping的方法中
* @param method
* @param formParams
* @return 
*/
public static Object[] getParamsToBePassed(Method method, HashMap<String, Object> formParams) {

    LocalVariableTableParameterNameDiscoverer discover = new LocalVariableTableParameterNameDiscoverer();

    String[] methodParams = discover.getParameterNames(method);

    ArrayList<Object> paramsToBePassed = new ArrayList<Object>();

    for (String methodParam : methodParams) {
        Set<String> formParamKeys = formParams.keySet();

        //和表單裡匹配的引數則相應設定值,不匹配的引數將預設設定為空值
        if(formParamKeys.contains(methodParam)) {
            paramsToBePassed.add(formParams.get(methodParam));
        }else {
            paramsToBePassed.add(null);
        }
    }
    return paramsToBePassed.toArray();
}
/**
 * 將Form表單的引數注入方法中。根據uri執行對應的處理方法
 * @param uri
 * @param formParams
*/
public static void executeRequest(String uri, HashMap<String, Object> formParams) {
    try {
        RequestExecuteModel model = maps.get(uri);
        if(model != null){
			Method method = model.getMethod();
			Class clazz = model.getClazz();
			//獲取引數
	        Object[] paramsToBePassed = getParamsToBePassed(method, formParams);
			Object o = method.invoke(clazz.newInstance(), paramsToBePassed);
		}
    } catch (Exception e) {
    }
}

這裡寫圖片描述
方法接收到了引數

原始碼下載