spring之mvc原理分析及簡單模擬實現
在之前的一篇博客中已經簡單的實現了spring的IOC和DI功能,本文將在之前的基礎上實現mvc功能。
一 什麽是MVC
MVC簡單的說就是一種軟件實現的設計模式,將整個系統進行分層,M(model 數據模型,業務邏輯層) 、V(view 視圖層)、C(controller 控制器調度),實現應用程序的分層開發。實現原理如下圖:
主要執行步驟:
1 用戶在發起request請求給前端控制器;
2 控制器接收到請求後,經過一系統的過濾器,找到對應的請求處理映射;
3 根據請求映射獲得請求處理適配器;
4 適配器將對請求進行處理並將處理結果(ModelAndView)返回給前端控制器;
5 前端處理器將處理結果交給視圖解析器解析;
6 視圖解析器將解析的結果返回給控制器;
7 控制器將結果返回給用戶。
二 簡單模擬實現
創建一個核心控制器(DispatcherServlet)繼承HttpServlet,配置在web.xml中,並指定要初始化的參數
1 <!-- 核心servlet --> 2 <servlet> 3 <servlet-name>dispatcherServlet</servlet-name> 4 <servlet-class>org.wl.test.spring.mvc.DispatcherServlet</servlet-class> 5 <!-- 初始化參數 --> 6 <init-param> 7 <param-name>contextConfigLocation</param-name> 8 <param-value>classpath:application.properties</param-value> 9 </init-param> 10 <!-- 啟動時加載 --> 11 <load-on-startup>0</load-on-startup> 12</servlet> 13 <servlet-mapping> 14 <servlet-name>dispatcherServlet</servlet-name> 15 <url-pattern>/</url-pattern> 16 </servlet-mapping>
核心控制器,在web容器啟動時執行init方法進行文件的初始化
1 public class DispatcherServlet extends HttpServlet { 2 3 private List<HandlerMapping> handlerMappingList = new ArrayList<HandlerMapping>(); 4 5 private Map<HandlerMapping, HandlerAdapter> adapterMap = new HashMap<>(); 6 7 @Override 8 public void init(ServletConfig config) throws ServletException { 9 // web.xml 配置核心servlet 獲取配置的信息 10 String configFile = config.getInitParameter("contextConfigLocation"); 11 //定義一個當前上下文對象,實現基礎包的掃描、IOC、DI 12 ApplicationContext context = new ApplicationContext(configFile.replace("classpath:", "")); 13 //獲取掃描到的有Controller註解的類 14 List<Object> controllerList = context.getControllerList(); 15 //初始化HandlerMapping 16 initHandlerMapping(controllerList); 17 //初始化HandlerAdapter 18 initHandlerAdapter(); 19 } 20 21 22 private void initHandlerAdapter() { 23 if (handlerMappingList.size() == 0) { 24 return; 25 } 26 27 handlerMappingList.forEach(handlerMapping -> { 28 Method method = handlerMapping.getMethod(); 29 //方法的參數 <參數索引,參數名字> 30 Map<Integer, String> paramMap = new HashMap<>(); 31 32 //使用了註解參數 33 Annotation[][] annos = method.getParameterAnnotations(); 34 if(annos.length > 0){ 35 for(int i=0; i<annos.length; i++){ 36 for(Annotation anno : annos[i]){ 37 if(anno instanceof RequestParam){ 38 RequestParam requestParam = (RequestParam) anno; 39 String paramName = requestParam.value(); 40 41 paramMap.put(i, paramName); 42 } 43 } 44 } 45 } 46 //直接用的servlet參數,如HttpServletRequest 47 Class<?>[] paramTypes = method.getParameterTypes(); 48 if(paramTypes.length > 0){ 49 for(int i=0; i<paramTypes.length; i++){ 50 Class<?> typeClass = paramTypes[i]; 51 if (typeClass == HttpServletRequest.class || typeClass == HttpServletResponse.class) { 52 String paramName = typeClass.getName(); 53 54 paramMap.put(i, paramName); 55 } 56 } 57 } 58 59 HandlerAdapter handlerAdapter = new HandlerAdapter(paramMap); 60 adapterMap.put(handlerMapping, handlerAdapter); 61 }); 62 } 63 64 /** 65 * 完成請求方法與請求處理實例的映射關系 66 * @param controllerList 67 */ 68 private void initHandlerMapping(List<Object> controllerList) { 69 if(controllerList.size() == 0){ 70 return; 71 } 72 73 controllerList.forEach(controllerObj -> { 74 //類上的請求路徑 75 String classRequestUrl = ""; 76 if (controllerObj.getClass().isAnnotationPresent(RequestMapping.class)) { 77 RequestMapping classRequestMapping = controllerObj.getClass().getAnnotation(RequestMapping.class); 78 if(classRequestMapping != null){ 79 classRequestUrl += urlHandler(classRequestMapping.value()); 80 } 81 } 82 //方法上的請求路徑 83 Method[] methods = controllerObj.getClass().getDeclaredMethods(); 84 if(methods.length > 0){ 85 for(int i=0; i<methods.length; i++){ 86 String methodRequestUrl = ""; 87 Method method = methods[i]; 88 //必須是public修飾的方法 89 if(method.getModifiers() == Modifier.PUBLIC){ 90 if (method.isAnnotationPresent(RequestMapping.class)) { 91 RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class); 92 if(methodRequestMapping != null){ 93 methodRequestUrl += urlHandler(methodRequestMapping.value()); 94 } 95 96 String requestUrl = classRequestUrl + methodRequestUrl; 97 98 HandlerMapping handlerMapping = new HandlerMapping(); 99 handlerMapping.setMethod(method); 100 handlerMapping.setUrl(requestUrl); 101 102 handlerMapping.setControllerInstance(controllerObj); 103 handlerMappingList.add(handlerMapping); 104 } 105 } 106 107 } 108 } 109 110 }); 111 112 } 113 114 /** 115 * url處理 116 * @param url 117 * @return 118 */ 119 public String urlHandler( String url){ 120 if(!url.startsWith("/")){ 121 url = "/" + url; 122 } 123 if(url.endsWith("/")){ 124 url = url.substring(0, url.length() - 1); 125 } 126 return url; 127 } 128 129 @Override 130 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 131 this.doPost(req, resp); 132 } 133 134 @Override 135 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 136 doDispatcher(req, resp); 137 } 138 139 /** 140 * 請求處理 141 * @param req 142 * @param resp 143 */ 144 private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 145 req.setCharacterEncoding("utf-8"); 146 resp.setContentType("text/html;charset=UTF-8"); 147 148 String contextUrl = req.getContextPath(); 149 String requestUrl = req.getRequestURI(); 150 151 String url = requestUrl.replace(contextUrl, ""); 152 HandlerMapping handlerMapping = null; 153 for(int i=0; i<handlerMappingList.size(); i++){ 154 if(url.equals(handlerMappingList.get(i).getUrl())){ 155 handlerMapping = handlerMappingList.get(i); 156 break; 157 } 158 } 159 if(handlerMapping == null){ 160 resp.getWriter().write("404, 未知的請求!"); 161 }else{ 162 HandlerAdapter adapter = adapterMap.get(handlerMapping); 163 try { 164 Object result = adapter.handler(req, resp, handlerMapping); 165 166 viewResolve(req, resp, result); 167 } catch (Exception e) { 168 e.printStackTrace(); 169 resp.getWriter().write("500, 服務器發生異常!"); 170 } 171 } 172 173 } 174 175 /** 176 * 視圖解析 返回 177 * @param result 178 */ 179 private void viewResolve(HttpServletRequest request, HttpServletResponse response, Object result) throws Exception{ 180 if (result.getClass() == ModelAndView.class) { 181 ModelAndView mv = (ModelAndView) result; 182 String view = mv.getViewName(); 183 Map<String, Object> dataMap = mv.getData(); 184 if(dataMap.size() > 0){ 185 for(String key : dataMap.keySet()){ 186 request.setAttribute(key, dataMap.get(key)); 187 } 188 } 189 request.getRequestDispatcher(view).forward(request, response); 190 } 191 } 192 193 }
ApplicationContext的具體實現如下,主要實現對執行資源文件的掃描,並完成IOC和DI
public class ApplicationContext { /** * 配置文件 */ private static String PROPERTIES_FILE = ""; /** * 初始化一個集合,存放掃描到的class對象 */ private List<Class<?>> classList = Collections.synchronizedList(new ArrayList<>()); /** * 初始化map 存放別名與對象實例 */ private Map<String, Object> aliasInstanceMap = new HashMap<>(); public ApplicationContext(String fileName) { PROPERTIES_FILE = fileName; try { String basePackage = getBasePackage(PROPERTIES_FILE); buildAliasInstanceMap(basePackage); doAutowired(); } catch (Exception e) { e.printStackTrace(); } } /** * 完成別名與實例的映射 */ public void buildAliasInstanceMap(String basePackage) throws Exception { scanClasses(basePackage); if(classList.size() == 0){return;} for(Class<?> clazz : classList){ if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class) || clazz.isAnnotationPresent(Autowired.class)) { String alias = getAlias(clazz); Object obj = aliasInstanceMap.get(alias); //如果別名實例映射關系已經存在,則給出提示 if(obj != null){ throw new Exception("alias is exist!"); }else{ aliasInstanceMap.put(alias, clazz.newInstance()); } } } System.out.println(aliasInstanceMap); } /** * 屬性對象的註入 */ public void doAutowired(){ if (aliasInstanceMap.size() == 0) { return; } aliasInstanceMap.forEach((k, v)->{ Field[] fields = v.getClass().getDeclaredFields(); for(Field field : fields){ if (field.isAnnotationPresent(Autowired.class)) { String alias = ""; Autowired autowired = field.getAnnotation(Autowired.class); if(autowired != null){ //註入的對象是接口時,由於不知道接口有幾個實現類,所以就必須在Autowired或者Qualifier上指定要註解的具體的實現類 if(!"".equals(autowired.value())){ alias = autowired.value(); }else{ Qualifier qualifier = field.getAnnotation(Qualifier.class); if(qualifier != null){ alias = qualifier.value(); } } } if ("".equals(alias)) { alias = getAlias(field.getType()); } Object instance = null; if(!"".equals(alias)){ instance = aliasInstanceMap.get(alias); } field.setAccessible(true); try { field.set(v, instance); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }); } /** * 獲取對象的別名,如果註解中配置了別名,別使用配置的別名,否則默認使用類名首字母小寫 * @param clazz * @return */ public String getAlias(Class<?> clazz){ String alias = ""; Controller controller = clazz.getAnnotation(Controller.class); if(controller != null){ alias = controller.value(); } Service service = clazz.getAnnotation(Service.class); if (service != null) { alias = service.value(); } Autowired autowired = clazz.getAnnotation(Autowired.class); if(autowired != null){ alias = autowired.value(); } //註解中沒有配置別名 if("".equals(alias)){ String simpleName = clazz.getSimpleName(); alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1); } return alias; } /** * 跟據基礎包名讀取包及子包中的類對象 * @param basePackage */ public void scanClasses(String basePackage){ if(basePackage == null || "".equals(basePackage)){return;} doScan(basePackage); System.out.println(classList); } private void doScan(String basePackage) { String path = basePackage.replaceAll("\\.","/"); URL url = this.getClass().getClassLoader().getResource(path); File file = new File(url.getFile()); file.listFiles(new FileFilter() { @Override public boolean accept(File childFile) { String fileName = childFile.getName(); if(childFile.isDirectory()){ //當前文件是目錄,遞歸 掃描下級子目錄下的class文件 doScan(basePackage + "." + fileName); }else{ if(fileName.endsWith(".class")){ String className = basePackage + "." + fileName.replace(".class", ""); try { Class<?> clazz = this.getClass().getClassLoader().loadClass(className); classList.add(clazz); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } return false; } }); } /** * 從配置的屬性文件中讀取要掃描的包 * @return */ public String getBasePackage(String fileName) throws IOException { String basePackage; Properties prop = new Properties(); InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName); prop.load(in); basePackage = prop.getProperty("basePackage"); return basePackage; } /** * 根據beanName 獲取 * @param beanName * @return */ public Object getBean(String beanName){ return aliasInstanceMap.get(beanName); } /** * 獲取所有標註了controller的註解 * @return */ public List<Object> getControllerList(){ List<Object> controllerList = new ArrayList<>(); if(aliasInstanceMap.size() > 0) { aliasInstanceMap.values().forEach(obj -> { if(obj.getClass().isAnnotationPresent(Controller.class)){ controllerList.add(obj); } }); } return controllerList; } public static void main(String[] args) throws Exception { String fileName = "application.properties"; ApplicationContext context = new ApplicationContext(fileName); String basePackage = context.getBasePackage(PROPERTIES_FILE); context.buildAliasInstanceMap(basePackage); context.doAutowired(); //測試 UserController controller = (UserController) context.getBean("userController"); controller.save(); } }
請求映射HandlerMapping,用來存放請求url和要執行的方法和方法所在對象實例,構建請求與請求處理的映射關系
1 public class HandlerMapping { 2 3 private String url; 4 private Method method; 5 private Object controllerInstance; 6 7 //此處省去getter和setter 8 }
處理適配器HandlerAdapter,每一個請求映射都有一個請求處理適配器來完成請求的處理(handler)
1 public class HandlerAdapter { 2 3 private Map<Integer, String> paramMap; 4 5 public HandlerAdapter(Map<Integer, String> paramMap){ 6 this.paramMap = paramMap; 7 } 8 9 public Object handler(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping) throws Exception { 10 Method method = handlerMapping.getMethod(); 11 Object classInstance = handlerMapping.getControllerInstance(); 12 13 int paramNum = method.getParameterCount(); 14 Object[] paramObj = new Object[paramNum]; 15 for(int i=0; i<paramNum; i++){ 16 String paramName = paramMap.get(i); 17 if(paramName.equals(HttpServletRequest.class.getName())){ 18 paramObj[i] = request; 19 }else if(paramName.equals(HttpServletResponse.class.getName())){ 20 paramObj[i] = response; 21 } else { 22 paramObj[i] = request.getParameter(paramName); 23 } 24 } 25 Object result = method.invoke(classInstance, paramObj); 26 return result; 27 } 28 29 30 public Map<Integer, String> getParamMap() { 31 return paramMap; 32 } 33 34 public void setParamMap(Map<Integer, String> paramMap) { 35 this.paramMap = paramMap; 36 } 37 }
處理結果ModelAndView,用來存放當前請求要返回的視圖和數據
1 public class ModelAndView { 2 3 private String viewName; 4 private Map<String, Object> data = new HashMap<>(); 5 6 public ModelAndView(String viewName) { 7 this.viewName = viewName; 8 } 9 10 public void addAttribute(String name, Object value){ 11 data.put(name, value); 12 } 13 14 //此處省略getter和setter 15 }
視圖展示返回的結果
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>hello mvc...</h3>
<hr/>
用 戶 信 息:<%=request.getAttribute("user") %>
</body>
</html>
瀏覽器端顯示信息
以上是模擬實現springmvc的主要代碼,實現的源代碼我已經上傳在github,感興趣的朋友可以訪問
https://github.com/wlzq/spring
spring之mvc原理分析及簡單模擬實現