Spring學習之——手寫Spring源碼V2.0(實現IOC、DI、MVC、AOP)

前言

在上一篇《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

 

 

IOC與DI實現

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();
            }
        }
    }

 

 

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 : https://github.com/wqfrw/HandWritingSpringV2.0

相關文章
相關標籤/搜索