Spring學習之——手寫Spring源碼V2.0(實現IOC、D、MVC、AOP)
前言
在上一篇《Spring學習之——手寫Spring源碼(V1.0)》中,我實現了一個Mini版本的Spring框架,在這幾天,部落客又看了不少關於Spring源碼解析的影片,受益匪淺,也對Spring的各組件有了自己的理解和認識,於是乎,在空閑時間把之前手寫Spring的程式碼重構了一遍,遵循了單一職責的原則,使結構更清晰,並且實現了AOP,這次還是只引用一個servlet包,其他全部手寫實現。
全部源碼照舊放在文章末尾~
開發工具
環境:jdk8 + IDEA + maven
jar包:javax.servlet-2.5
項目結構
具體實現
配置文件
web.xml 與之前一樣 並無改變
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
IOC與DI實現
1.在DispatcherServlet的init方法中初始化ApplicationContent;
2.ApplicationContent是Spring容器的主入口,通過創建BeanDefintionReader對象載入配置文件;
3.在BeanDefintionReader中將掃描到的類解析成BeanDefintion返回;
4.ApplicationContent中通過BeanDefintionMap這個快取來關聯BeanName與BeanDefintion對象之間的關係;
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(); } } }
MVC實現
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 正常返回頁面
AOP實現
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 : //github.com/wqfrw/HandWritingSpringV2.0