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

我恰芙蓉王發表於2020-08-05

前言

在上一篇《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 : https://github.com/wqfrw/HandWritingSpringV2.0

相關文章