在上一篇《Spring學習之——手寫Spring源碼(V1.0)》中,我實現了一個Mini版本的Spring框架,在這幾天,博主又看了很多關於Spring源碼解析的視頻,受益不淺,也對Spring的各組件有了本身的理解和認識,因而乎,在空閒時間把以前手寫Spring的代碼重構了一遍,遵循了單一職責的原則,使結構更清晰,而且實現了AOP,此次仍是隻引用一個servlet包,其餘所有手寫實現。html
所有源碼照舊放在文章末尾~java
環境:jdk8 + IDEA + mavengit
jar包:javax.servlet-2.5github
web.xml 與以前同樣 並沒有改變web
application.properties 增長了html頁面路徑和AOP的相關配置數組
#掃描路徑# scanPackage=com.wqfrw #模板引擎路徑# templateRoot=template #切面表達式# pointCut=public .* com.wqfrw.service.impl..*ServiceImpl..*(.*) #切面類# aspectClass=com.wqfrw.aspect.LogAspect #切面前置通知# aspectBefore=before #切面後置通知# aspectAfter=after #切面異常通知# aspectAfterThrowing=afterThrowing #切面異常類型# aspectAfterThrowingName=java.lang.Exception
1.在DispatcherServlet的init方法中初始化ApplicationContent;緩存
2.ApplicationContent是Spring容器的主入口,經過建立BeanDefintionReader對象加載配置文件;服務器
3.在BeanDefintionReader中將掃描到的類解析成BeanDefintion返回;mvc
4.ApplicationContent中經過BeanDefintionMap這個緩存來關聯BeanName與BeanDefintion對象之間的關係;app
5.經過getBean方法,進行Bean的建立並封裝爲BeanWrapper對象,進行依賴注入,緩存到IoC容器中
/** * 功能描述: 初始化MyApplicationContext * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月03日 18:54:01 * @param configLocations * @return: **/ public MyApplicationContext(String... configLocations) { this.configLocations = configLocations; try { //1.讀取配置文件並解析BeanDefinition對象 beanDefinitionReader = new MyBeanDefinitionReader(configLocations); List<MyBeanDefinition> beanDefinitionList = beanDefinitionReader.loadBeanDefinitions(); //2.將解析後的BeanDefinition對象註冊到beanDefinitionMap中 doRegisterBeanDefinition(beanDefinitionList); //3.觸發建立對象的動做,調用getBean()方法(Spring默認是延時加載) doCreateBean(); } catch (Exception e) { e.printStackTrace(); } }
/** * 功能描述: 真正觸發IoC和DI的動做 1.建立Bean 2.依賴注入 * * @param beanName * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月03日 19:48:58 * @return: java.lang.Object **/ public Object getBean(String beanName) { //============ 建立實例 ============ //1.獲取配置信息,只要拿到beanDefinition對象便可 MyBeanDefinition beanDefinition = beanDefinitionMap.get(beanName); //用反射建立實例 這個實例有多是代理對象 也有多是原生對象 封裝成BeanWrapper統一處理 Object instance = instantiateBean(beanName, beanDefinition); MyBeanWrapper beanWrapper = new MyBeanWrapper(instance); factoryBeanInstanceCache.put(beanName, beanWrapper); //============ 依賴注入 ============ populateBean(beanName, beanDefinition, beanWrapper); return beanWrapper.getWrapperInstance(); }
/** * 功能描述: 依賴注入 * * @param beanName * @param beanDefinition * @param beanWrapper * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月03日 20:09:01 * @return: void **/ private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) { Object instance = beanWrapper.getWrapperInstance(); Class<?> clazz = beanWrapper.getWrapperClass(); //只有加了註解的類才須要依賴注入 if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) { return; } //拿到bean全部的字段 包括private、public、protected、default for (Field field : clazz.getDeclaredFields()) { //若是沒加MyAutowired註解的屬性則直接跳過 if (!field.isAnnotationPresent(MyAutowired.class)) { continue; } MyAutowired annotation = field.getAnnotation(MyAutowired.class); String autowiredBeanName = annotation.value().trim(); if ("".equals(autowiredBeanName)) { autowiredBeanName = field.getType().getName(); } //強制訪問 field.setAccessible(true); try { if (factoryBeanInstanceCache.get(autowiredBeanName) == null) { continue; } //賦值 field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance()); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
1.在DispatcherServlet的init方法中調用initStrategies方法初始化九大核心組件;
2.經過循環BeanDefintionMap拿到每一個接口的url、實例對象、對應方法封裝成一個HandlerMapping對象的集合,並創建HandlerMapping與HandlerAdapter(參數適配器)的關聯;
3.初始化ViewResolver(視圖解析器),解析配置文件中模板文件路徑(即html文件的路徑,其做用相似於BeanDefintionReader);
4.在運行階段,調用doDispatch方法,根據請求的url找到對應的HandlerMapping;
5.在HandlerMapping對應的HandlerAdapter中,調用handle方法,進行參數動態賦值,反射調用接口方法,拿到返回值與返回頁面封裝成一個MyModelAndView對象返回;
6.經過ViewResolver拿到View(模板頁面文件),在View中經過render方法,經過正則將返回值與頁面取值符號進行適配替換,渲染成html頁面返回
/** * 功能描述: 初始化核心組件 在Spring中有九大核心組件,這裏只實現三種 * * @param context * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月04日 11:51:55 * @return: void **/ protected void initStrategies(MyApplicationContext context) { //多文件上傳組件 //initMultipartResolver(context); //初始化本地語言環境 //initLocaleResolver(context); //初始化模板處理器 //initThemeResolver(context); //初始化請求分發處理器 initHandlerMappings(context); //初始化參數適配器 initHandlerAdapters(context); //初始化異常攔截器 //initHandlerExceptionResolvers(context); //初始化視圖預處理器 //initRequestToViewNameTranslator(context); //初始化視圖轉換器 initViewResolvers(context); //緩存管理器(值棧) //initFlashMapManager(context); }
/** * 功能描述: 進行參數適配 * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 19:41:38 * @param req * @param resp * @param mappedHandler * @return: com.framework.webmvc.servlet.MyModelAndView **/ public MyModelAndView handle(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping mappedHandler) throws Exception { //保存參數的名稱和位置 Map<String, Integer> paramIndexMapping = new HashMap<>(); //獲取這個方法全部形參的註解 因一個參數能夠添加多個註解 因此是一個二維數組 Annotation[][] pa = mappedHandler.getMethod().getParameterAnnotations(); /** * 獲取加了MyRequestParam註解的參數名和位置 放入到paramIndexMapping中 */ for (int i = 0; i < pa.length; i++) { for (Annotation annotation : pa[i]) { if (!(annotation instanceof MyRequestParam)) { continue; } String paramName = ((MyRequestParam) annotation).value(); if (!"".equals(paramName.trim())) { paramIndexMapping.put(paramName, i); } } } //方法的形參列表 Class<?>[] parameterTypes = mappedHandler.getMethod().getParameterTypes(); /** * 獲取request和response的位置(若是有的話) 放入到paramIndexMapping中 */ for (int i = 0; i < parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) { paramIndexMapping.put(parameterType.getName(), i); } } //拿到一個請求全部傳入的實際實參 由於一個url上能夠多個相同的name,因此此Map的結構爲一個name對應一個value[] //例如:request中的參數t1=1&t1=2&t2=3造成的map結構: //key=t1;value[0]=1,value[1]=2 //key=t2;value[0]=3 Map<String, String[]> paramsMap = req.getParameterMap(); //自定義初始實參列表(反射調用Controller方法時使用) Object[] paramValues = new Object[parameterTypes.length]; /** * 從paramIndexMapping中取出參數名與位置 動態賦值 */ for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) { //拿到請求傳入的實參 String value = entry.getValue()[0]; //若是包含url參數上的key 則動態轉型賦值 if (paramIndexMapping.containsKey(entry.getKey())) { //獲取這個實參的位置 int index = paramIndexMapping.get(entry.getKey()); //動態轉型並賦值 paramValues[index] = caseStringValue(value, parameterTypes[index]); } } /** * request和response單獨賦值 */ if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) { int index = paramIndexMapping.get(HttpServletRequest.class.getName()); paramValues[index] = req; } if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) { int index = paramIndexMapping.get(HttpServletResponse.class.getName()); paramValues[index] = resp; } //方法調用 拿到返回結果 Object result = mappedHandler.getMethod().invoke(mappedHandler.getController(), paramValues); if (result == null || result instanceof Void) { return null; } else if (mappedHandler.getMethod().getReturnType() == MyModelAndView.class) { return (MyModelAndView) result; } return null; } /** * 功能描述: 動態轉型 * * @param value String類型的value * @param clazz 實際對象的class * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月04日 16:34:40 * @return: java.lang.Object 實際對象的實例 **/ private Object caseStringValue(String value, Class<?> clazz) throws Exception { //經過class對象獲取一個入參爲String的構造方法 沒有此方法則拋出異常 Constructor constructor = clazz.getConstructor(new Class[]{String.class}); //經過構造方法new一個實例返回 return constructor.newInstance(value); }
/** * 功能描述: 對頁面內容進行渲染 * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月04日 17:54:40 * @param model * @param req * @param resp * @return: void **/ public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception { StringBuilder sb = new StringBuilder(); //只讀模式 讀取文件 RandomAccessFile ra = new RandomAccessFile(this.viewFile, "r"); String line = null; while ((line = ra.readLine()) != null) { line = new String(line.getBytes("ISO-8859-1"), "utf-8"); //%{name} Pattern pattern = Pattern.compile("%\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(line); while (matcher.find()) { String paramName = matcher.group(); paramName = paramName.replaceAll("%\\{|\\}", ""); Object paramValue = model.get(paramName); line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString())); matcher = pattern.matcher(line); } sb.append(line); } resp.setCharacterEncoding("utf-8"); resp.getWriter().write(sb.toString()); }
html頁面
404.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>頁面沒有找到</title> </head> <body> <font size="25" color="red">Exception Code : 404 Not Found</font> <br><br><br> @我恰芙蓉王 </body> </html>
500.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>服務器崩潰</title> </head> <body> <font size="25" color="red">Exception Code : 500 <br/> 服務器崩潰了~</font> <br/> <br/> <b>Message:%{message}</b> <br/> <b>StackTrace:%{stackTrace}</b> <br/> <br><br><br> @我恰芙蓉王 </body> </html>
index.html
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>自定義SpringMVC模板引擎Demo</title> </head> <center> <h1>你們好,我是%{name}</h1> <h2>我愛%{food}</h2> <font color="red"> <h2>時間:%{date}</h2> </font> <br><br><br> @我恰芙蓉王 </center> </html>
404.html 接口未找到
500.html 服務器錯誤
index.html 正常返回頁面
1.參照IOC與DI實現第五點,在對象實例化以後,依賴注入以前,將配置文件中AOP的配置解析至AopConfig中;
2.經過配置的pointCut參數,正則匹配此實例對象的類名與方法名,若是匹配上,將配置的三個通知方法(Advice)與此方法創建聯繫,生成一個 Map<Method, Map<String, MyAdvice>> methodCache 的緩存;
3.將原生對象、原生對象class、原生對象方法與通知方法的映射關係封裝成AdviceSupport對象;
4.若是須要代理,則使用JdkDynamicAopProxy中getProxy方法,得到一個此原生對象的代理對象,並將原生對象覆蓋;
5.JdkDynamicAopProxy實現了InvocationHandler接口(使用JDK的動態代理),重寫invoke方法,在此方法中執行切面方法與原生對象方法。
/** * 功能描述: 反射實例化對象 * * @param beanName * @param beanDefinition * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月03日 20:08:50 * @return: java.lang.Object **/ private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) { String className = beanDefinition.getBeanClassName(); Object instance = null; try { Class<?> clazz = Class.forName(className); instance = clazz.newInstance(); /** * ===========接入AOP begin=========== */ MyAdviceSupport support = instantiateAopConfig(beanDefinition); support.setTargetClass(clazz); support.setTarget(instance); //若是須要代理 則用代理對象覆蓋目標對象 if (support.pointCutMatch()) { instance = new MyJdkDynamicAopProxy(support).getProxy(); } /** * ===========接入AOP end=========== */ factoryBeanObjectCache.put(beanName, instance); } catch (Exception e) { e.printStackTrace(); } return instance; }
/** * 功能描述: 解析配置 pointCut * * @param * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 11:20:21 * @return: void **/ private void parse() { String pointCut = aopConfig.getPointCut() .replaceAll("\\.", "\\\\.") .replaceAll("\\\\.\\*", ".*") .replaceAll("\\(", "\\\\(") .replaceAll("\\)", "\\\\)"); //public .*.com.wqfrw.service..*impl..*(.*) String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4); this.pointCutClassPattern = Pattern.compile(pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1)); methodCache = new HashMap<>(); //匹配方法的正則 Pattern pointCutPattern = Pattern.compile(pointCut); //1.對回調通知進行緩存 Map<String, Method> aspectMethods = new HashMap<>(); try { //拿到切面類的class com.wqfrw.aspect.LogAspect Class<?> aspectClass = Class.forName(this.aopConfig.getAspectClass()); //將切面類的通知方法緩存到aspectMethods Stream.of(aspectClass.getMethods()).forEach(v -> aspectMethods.put(v.getName(), v)); //2.掃描目標類的方法,去循環匹配 for (Method method : targetClass.getMethods()) { String methodString = method.toString(); //若是目標方法有拋出異常 則截取 if (methodString.contains("throws")) { methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim(); } /** * 匹配目標類方法 若是匹配上,就將緩存好的通知與它創建聯繫 若是沒匹配上,則忽略 */ Matcher matcher = pointCutPattern.matcher(methodString); if (matcher.matches()) { Map<String, MyAdvice> adviceMap = new HashMap<>(); //前置通知 if (!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore()))) { adviceMap.put("before", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectBefore()))); } //後置通知 if (!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter()))) { adviceMap.put("after", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfter()))); } //異常通知 if (!(null == aopConfig.getAspectAfterThrowing() || "".equals(aopConfig.getAspectAfterThrowing()))) { MyAdvice advice = new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrowing())); advice.setThrowingName(aopConfig.getAspectAfterThrowingName()); adviceMap.put("afterThrowing", advice); } //創建關聯 methodCache.put(method, adviceMap); } } } catch (Exception e) { e.printStackTrace(); } }
/** * 功能描述: 返回一個代理對象 * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 14:17:22 * @param * @return: java.lang.Object **/ public Object getProxy() { return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.support.getTargetClass().getInterfaces(), this); } /** * 功能描述: 重寫invoke * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 20:29:19 * @param proxy * @param method * @param args * @return: java.lang.Object **/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Map<String, MyAdvice> advices = support.getAdvices(method, support.getTargetClass()); Object result = null; try { //調用前置通知 invokeAdvice(advices.get("before")); //執行原生目標方法 result = method.invoke(support.getTarget(), args); //調用後置通知 invokeAdvice(advices.get("after")); } catch (Exception e) { //調用異常通知 invokeAdvice(advices.get("afterThrowing")); throw e; } return result; } /** * 功能描述: 執行切面方法 * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 11:09:32 * @param advice * @return: void **/ private void invokeAdvice(MyAdvice advice) { try { advice.getAdviceMethod().invoke(advice.getAspect()); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
/** * @ClassName LogAspect * @Description TODO(切面類) * @Author 我恰芙蓉王 * @Date 2020年08月05日 10:03 * @Version 2.0.0 **/ public class LogAspect { /** * 功能描述: 前置通知 * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 17:24:30 * @param * @return: void **/ public void before(){ System.err.println("=======前置通知======="); } /** * 功能描述: 後置通知 * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 17:24:40 * @param * @return: void **/ public void after(){ System.err.println("=======後置通知=======\n"); } /** * 功能描述: 異常通知 * * @建立人: 我恰芙蓉王 * @建立時間: 2020年08月05日 17:24:47 * @param * @return: void **/ public void afterThrowing(){ System.err.println("=======出現異常======="); } }
以上只貼出了部分核心實現代碼,有興趣的童鞋能夠下載源碼調試,具體的註釋我都在代碼中寫得很清楚。
代碼已經提交至Git : https://github.com/wqfrw/HandWritingSpringV2.0