自分でjavaのmvcフレームワークを書きましょう(四)

17500 ワード

自分でmvcフレームを書きましょう(四)
リクエストのエントリを書き、フレームワークを初期化します.
前章では、取得方法のパラメータを記述し、パラメータタイプに基づいてデータ変換を行います.このとき,反射呼び出し法によるすべての必要条件を備えている.httpリクエストのエントリが欠けています.servletです.今から書きましょう~
この章で私たちがしなければならないことは
  • は、どのようなリクエストがどのclassにマッピングされるかを記述するためのプロファイルを定義する.
  • servletが初期化されると、上記で定義したプロファイルに基づいてmvcフレームワークがロードされます.
  • httpリクエストが入った後、そのリクエストパスに基づいて、対応するメソッドを見つけ、パラメータを取得し、反射を使用してこのメソッドを実行する.
  • メソッドの実行結果が得られた後、まずjsonとしてブラウザに表示される.このステップは、ビューレイヤの機能で、まずこのように書き、その後、さまざまなビューコントローラを書きます.

  • 今から書きましょう
    プロファイルの定義
    ここの構成は必ずしもxml,json,yaml...などのファイルでなければならないとは限らず,注釈の形式であってもよい.違いはフレームワークをロードするときに異なる形式で解析すればよいだけです.ここでは書きやすいように、まずjsonのプロファイルを定義します(jsonのファイルは使いやすいからです).
    このプロファイルでは、httpリクエストをメソッドにマッピングする必要があるパラメータを定義する必要があります.私はこのように定義しました.
    {
      "annotationSupport": false,
      "mapping": [
        {
          "url": "/index",
          "requestType": [
            "get"
          ],
          "method": "index",
          "objectClass": "com.hebaibai.demo.web.IndexController",
          "paramTypes": [
            "java.lang.String"
          ]
        }
      ]
    }

    次に、各プロパティが何に使われているかを説明します.
    1:annotationSupport:注釈を開くサポートがあるかどうかを説明するために、まだ書いていないのでfalseをあげました.
    2:mapping:マッピング関係を記述するデータで、配列のタイプです.オブジェクトはマッピング関係を表します.
    3:url:httpリクエストのアドレスで、このマッピング関係がどのリクエストアドレスに対応するかを示します.
    4:requestType:このマッピングでサポートされるリクエストタイプ、配列の形式.1つのメソッドが複数のリクエスト方式をサポートすることを説明します.
    5:objectClass:このマッピングは、どのjavaオブジェクトであるかに違いありません.
    6:method:このマッピング関係に対応するobjectClassのメソッド名.
    7:paramTypes:メソッドのパラメータタイプ.ここでは、定義されたメソッドのパラメータ順序と一致する配列です.このパラメータを定義するには、反射によって1つのMethodを見つけるには、メソッド名と入力タイプの2つのパラメータが必要です.だからこの二つは欠かせない.
    ここの配置は正直複雑に見えますが、使い勝手もよくありません.たとえば、メソッドのパラメータを変更する場合、パラメータタイプを変更すると、対応する構成を変更します.ここでは後で注釈の形式を使うなど、簡略化した処理をすることができ、便利になります.しかし、設計と実装の段階では、すべての構成を最も複雑な形式で行うことができ、機能を完了してから最適化することができ、グローバルなデフォルトの構成を追加することができ、構成ファイルの作成を減らすことができます.
    上のプロファイルが書き終わったら、このプロファイルをロードする方法を書き始め、このmvcフレームワークを初期化します.
    コンフィギュレーション・ファイル名の取得
    リクエストのエントリはservletを使用しているので、各servletにはservlet-nameを構成する必要があります.したがって、プロファイルの名前はservlet-nameの名前に「.json」を付けることを約束することができます.たとえば、servletを定義します.
    
        mvc
        com.hebaibai.amvc.MvcServlet
    
    
        mvc
        /*
    

    このとき、プロファイルの名前はmvc.jsonです.では、どうすればいいですか.私たちはこう書きます.
    //     servlet
    public class MvcServlet extends HttpServlet {
    
        //       
        @Override
        public void init(ServletConfig config) {
            //     init  
            super.init(config);
            //  servlet   
            String servletName = config.getServletName();
            //   ,         
        }
    }

    上記のコードでは、servlet-nameのみを取得し、プロファイルの読み取りを開始していません.構成の読み取りとフレームワークのロードは1つのservletに書くべきではないと思いますので、クラスApplication.javaを定義しました.このクラスでは、プロファイルの読み取り、各種構成のロード、httpマッピングのキャッシュ、その他の処理に使用します.思いがけないことです.このApplication.javaにはパラメータ付きのコンストラクション関数があります.パラメータはアプリケーション名でservlet-nameです.これで各クラスの機能が分離できます.次に、このクラスに何があるべきかを書きます.
    プロファイルを読み込み、フレームワークのロードを完了
    まずコードを貼り付けます.
    package com.hebaibai.amvc;
    
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import com.hebaibai.amvc.namegetter.AsmParamNameGetter;
    import com.hebaibai.amvc.objectfactory.AlwaysNewObjectFactory;
    import com.hebaibai.amvc.objectfactory.ObjectFactory;
    import com.hebaibai.amvc.utils.Assert;
    import com.hebaibai.amvc.utils.ClassUtils;
    import lombok.NonNull;
    import lombok.SneakyThrows;
    import lombok.extern.java.Log;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * aMac
     *
     * @author hjx
     */
    @Log
    public class Application {
    
        private static final String NOT_FIND = "    !";
        //urlMapping    
        private static final String MAPPING_NODE = "mapping";
        //      
        private static final String ANNOTATION_SUPPORT_NODE = "annotationSupport";
    
        /**
         *       
         */
        private UrlMethodMappingFactory urlMethodMappingFactory = new UrlMethodMappingFactory();
    
        /**
         *        
         */
        private ObjectFactory objectFactory;
    
        /**
         *      
         */
        private String applicationName;
    
        /**
         *       urlMapping
         */
        private Map applicationUrlMapping = new ConcurrentHashMap<>();
    
    
        /**
         *     ,  servletName    
         *
         * @param applicationName
         */
        public Application(String applicationName) {
            this.applicationName = applicationName;
            init();
        }
    
        /**
         *      
         */
        @SneakyThrows(IOException.class)
        protected void init() {
            String configFileName = applicationName + ".json";
            InputStream inputStream = ClassUtils.getClassLoader().getResourceAsStream(configFileName);
            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            String config = new String(bytes, "utf-8");
            //    
            JSONObject configJson = JSONObject.parseObject(config);
            boolean annotationSupport = configJson.getBoolean(ANNOTATION_SUPPORT_NODE);
    
            //TODO:      ,       
            Assert.isTrue(!annotationSupport, "        !");
            urlMethodMappingFactory.setParamNameGetter(new AsmParamNameGetter());
    
            //TODO:        (        new      )
            this.objectFactory = new AlwaysNewObjectFactory();
    
            JSONArray jsonArray = configJson.getJSONArray(MAPPING_NODE);
            Assert.notNull(jsonArray, MAPPING_NODE + NOT_FIND);
            for (int i = 0; i < jsonArray.size(); i++) {
                UrlMethodMapping mapping = urlMethodMappingFactory.getUrlMethodMappingByJson(jsonArray.getJSONObject(i));
                addApplicationUrlMapping(mapping);
            }
        }
    
        /**
         *           
         *
         * @param urlMethodMapping
         */
        protected void addApplicationUrlMapping(@NonNull UrlMethodMapping urlMethodMapping) {
            RequestType[] requestTypes = urlMethodMapping.getRequestTypes();
            String url = urlMethodMapping.getUrl();
            for (RequestType requestType : requestTypes) {
                String urlDescribe = getUrlDescribe(requestType, url);
                if (applicationUrlMapping.containsKey(urlDescribe)) {
                    throw new UnsupportedOperationException(urlDescribe + "    !");
                }
                Method method = urlMethodMapping.getMethod();
                Class aClass = urlMethodMapping.getClass();
                log.info("mapping url:" + urlDescribe + " to " + aClass.getName() + "." + method.getName());
                applicationUrlMapping.put(urlDescribe, urlMethodMapping);
            }
        }
    
        /**
         *   Url   
         *
         * @param requestType
         * @param url
         * @return
         */
        protected String getUrlDescribe(RequestType requestType, @NonNull String url) {
            return requestType.name() + ":" + url;
        }
    
        /**
         *   url     UrlMethodMapping
         *
         * @param urlDescribe
         * @return
         */
        protected UrlMethodMapping getUrlMethodMapping(@NonNull String urlDescribe) {
            UrlMethodMapping urlMethodMapping = applicationUrlMapping.get(urlDescribe);
            return urlMethodMapping;
        }
    
        /**
         *        
         *
         * @return
         */
        protected ObjectFactory getObjectFactory() {
            return this.objectFactory;
        }
    
    }
    

    このクラスではlombokの注釈をいくつか使っていますが、皆さんはまず気にしないでください.
    属性の説明:
    1:UrlMethodMappingFactory:urlとMethodのマッピング関係を作成するために使用される:UrlMethodMappingのファクトリクラスは、自分でmvcフレームワークを書きましょう(二)という編で述べています.
    2:アプリケーションName:適用される名前は、実はservletの名前(web.xmlのservlet-nameノードの値)です.
    3:アプリケーションUrlMapping:urlはUrlMethodMappingとの対応関係を記述します.url記述は私自身が定義したもので、基本的には要求タイプ+":"+要求アドレスです.例:「GET:/index」です.
    4:objectFactory:オブジェクトファクトリ、オブジェクトをインスタンス化するためのもので、自分でmvcフレームワークを書きましょう(二)という記事にあります.
    メソッドの説明:
    1:init():アプリケーション名に基づいて、プロファイルの名前をつなぎ、その内容を読み取り、いくつかの検証を行うために使用されます.
    2:getUrlDescribe():前述のurlの説明を取得します.
    3:addApplicationUrlMapping(UrlMethodMappingurlMethodMapping):アプリケーションUrlMappingを塗りつぶします.
    4:getUrlMethodMapping(String urlDescribe):urlの説明に従ってurlMethodMappingを取得します.
    5:getobjectFactory():servletでオブジェクトをインスタンス化するオブジェクトファクトリを取得します.
    これでフレームワークをロードするコードが書き終わり、サーブレットを書き始めます.
    ライトリクエストのエントリ:servlet
    これは簡単に書けますが、やるべきことは以下の通りです.
    1:servletが初期化されたときにservletの名前を取得し、mvcフレームワークをロードします.
    2:httpリクエストを1回取得した場合、リクエストアドレス、リクエスト方式に応じて対応するMethod、すなわちurlMethodMappingを取得する.
    3:urlMethodMappingに従って対応するパラメータを取得し、対応するタイプに変換し、反射によって方法を実行します.
    4:戻り結果をJsonに変換し、ブラウザに表示します.(このステップは一時的です)
    前の章では多くのコードを書いたので、ここでは前に書いたものをつなぎ合わせるだけでいいです.あまり多くのものを書く必要はありません.次のコードを貼ってください.
    
    import com.alibaba.fastjson.JSONObject;
    import com.hebaibai.amvc.objectfactory.ObjectFactory;
    import lombok.SneakyThrows;
    import lombok.extern.java.Log;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * mvc   
     *
     * @author hjx
     */
    @Log
    public class MvcServlet extends HttpServlet {
    
        /**
         *   
         */
        private Application application;
    
        /**
         *          
         */
        private MethodValueGetter methodValueGetter;
    
    
        /**
         *      
         * 1:  Servlet  ,           
         * 2:        urlMapping
         */
        @Override
        @SneakyThrows(ServletException.class)
        public void init(ServletConfig config) {
            super.init(config);
            String servletName = config.getServletName();
            log.info("aMvc init servletName:" + servletName);
            application = new Application(servletName);
            methodValueGetter = new MethodValueGetter();
        }
    
        /**
         *     
         *
         * @param request
         * @param response
         */
        @SneakyThrows({IOException.class})
        private void doInvoke(HttpServletRequest request, HttpServletResponse response) {
            RequestType requestType = getRequestType(request.getMethod());
            String urlDescribe = application.getUrlDescribe(requestType, request.getPathInfo());
            UrlMethodMapping urlMethodMapping = application.getUrlMethodMapping(urlDescribe);
            //       mapping
            if (urlMethodMapping == null) {
                unsupportedMethod(request, response);
                return;
            }
            //      
            Object result = invokeMethod(urlMethodMapping, request);
            //TODO:    ,  JSON    
            response.setHeader("content-type", "application/json;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            writer.write(JSONObject.toJSONString(result));
            writer.close();
        }
    
        /**
         *       
         *
         * @param urlMethodMapping
         * @param request
         * @return
         */
        @SneakyThrows({IllegalAccessException.class, InvocationTargetException.class})
        private Object invokeMethod(UrlMethodMapping urlMethodMapping, HttpServletRequest request) {
            Object[] methodValue = methodValueGetter.getMethodValue(urlMethodMapping.getParamClasses(), urlMethodMapping.getParamNames(), request);
            Method method = urlMethodMapping.getMethod();
            Class objectClass = urlMethodMapping.getObjectClass();
            //         objectClass
            ObjectFactory objectFactory = application.getObjectFactory();
            Object object = objectFactory.getObject(objectClass);
            return method.invoke(object, methodValue);
        }
    
        /**
         *   http      RequestType
         *
         * @param requestMethod
         * @return
         */
        private RequestType getRequestType(String requestMethod) {
            if (requestMethod.equalsIgnoreCase(RequestType.GET.name())) {
                return RequestType.GET;
            }
            if (requestMethod.equalsIgnoreCase(RequestType.POST.name())) {
                return RequestType.POST;
            }
            if (requestMethod.equalsIgnoreCase(RequestType.PUT.name())) {
                return RequestType.PUT;
            }
            if (requestMethod.equalsIgnoreCase(RequestType.DELETE.name())) {
                return RequestType.DELETE;
            }
            throw new UnsupportedOperationException("       :" + requestMethod);
        }
    
        /**
         *         
         *
         * @param request
         * @param response
         */
        @SneakyThrows(IOException.class)
        private void unsupportedMethod(HttpServletRequest request, HttpServletResponse response) {
            String protocol = request.getProtocol();
            String method = request.getMethod();
            String errorMsg = "        :" + method + "!";
            if (protocol.endsWith("1.1")) {
                response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, errorMsg);
            } else {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, errorMsg);
            }
        }
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
            doInvoke(request, response);
        }
    
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) {
            doInvoke(request, response);
        }
    
        @Override
        protected void doPut(HttpServletRequest request, HttpServletResponse response) {
            doInvoke(request, response);
        }
    
        @Override
        protected void doDelete(HttpServletRequest request, HttpServletResponse response) {
            doInvoke(request, response);
        }
    }
    

    ここでは主にdoInvoke(HttpServiceletRequest request,HttpServiceletResponse response)とinvokeMethod(UrlMethodMappingurlMethodMapping,HttpServiceletRequest request)の2つの方法について述べる.
    doInvoke:各リクエストを処理する主な方法で、リクエストされた情報に基づいて対応するMethodを取得し、このMethodを実行し、対応するMethodが見つからないときに対応するエラー情報を表示します.最後に、構成に基づいて対応するビュー(現在はJson)に処理します.
    invokeMethod:オブジェクトファクトリからインスタンス化されたオブジェクトを取得し、反射によりMethodを実行し、メソッドの戻り値を取得します.
    今入り口は書き終わったので、新しいWebプロジェクトを作ってテストしましょう.
    テストしてみる
    まず、Webプロジェクトを新規作成し、Web.xmlに追加します.
    
        mvc
        com.hebaibai.amvc.MvcServlet
    
    
        mvc
        /*
    

    次に、controllerとしてIndexController.javaを書きます.
    package com.hebaibai.demo.web;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author hjx
     */
    public class IndexController {
    
        /**
         * @param name
         * @return
         */
        public Map index(String name) {
            Map map = new HashMap<>();
            map.put("value", name);
            map.put("msg", "success");
            return map;
        }
    
    }
    

    servlet-nameの値はmvcなので、resourcesディレクトリの下にプロファイルとしてmvc.jsonを新規作成する必要があります.so~新規ファイル:
    {
      "annotationSupport": false,
      "mapping": [
        {
          "url": "/index",
          "requestType": [
            "get"
          ],
          "method": "index",
          "objectClass": "com.hebaibai.demo.web.IndexController",
          "paramTypes": [
            "java.lang.String"
          ]
        }
      ]
    }

    今ではすべての配合ができて、テストできます~~~
    but~~,今BUGがあって、惊いて惊きません!!!
    バグがある
    このバグは、自分でmvcフレームワークを書きましょう(二)この章のasm取得方法でパラメータ名を入力したときに現れます.前のコードはこうです.
    ClassReader classReader = null;
    try {
        classReader = new ClassReader(className);
    } catch (IOException e) {
        e.printStackTrace();
    }

    私たちが最終的に書いたmvcフレームワークはjarパッケージとして現れるので、jarでは、このjarに依存するプロジェクトのclassをこのような形式で解析することはできません.ここで異常が発生します.クラスローダがファイルパスを取得する際の質問だと思います.どうやって解決しますか?
    バグの解決
    classReader=new ClassReader(className)というメソッドの実装コードを見てみましょう.
    /**
     * Constructs a new {@link ClassReader} object.
     *
     * @param className the fully qualified name of the class to be read. The ClassFile structure is
     *     retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}.
     * @throws IOException if an exception occurs during reading.
     */
    public ClassReader(final String className) throws IOException {
      this(
          readStream(
              ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
    }

    彼はclassのパケット名でファイルパスに変換した後、相対パス(プロジェクトパスをルートとするべき)で読み取ったので、これで解決できます.絶対パスの形式(システム内のルート)でこのファイルストリームを取得すればいいです.このように書きます.
    ClassReader getClassReader(Class aClass) {
        Assert.notNull(aClass);
        String className = aClass.getName();
        String path = getClass().getClassLoader().getResource("/").getPath();
        File classFile = new File(path + className.replace('.', '/') + ".class");
        try (InputStream inputStream = new FileInputStream(classFile)) {
            ClassReader classReader = new ClassReader(inputStream);
            return classReader;
        } catch (IOException e) {
        }
        throw new RuntimeException(className + "    !");
    }

    プロジェクト内のルートディレクトリがシステム内の場所を取得し、パッケージ名をファイルパスに変換し、最後につなぎ合わせればいい~できます.
    今すぐテストできます.さっきのwebプロジェクトを起動して、構成されたアドレスにアクセスすればいいだけです.私は書きません~~
    最後に
    まだビューコントローラが残っていますが、今は簡単にJsonで返すだけです.これはよくありません.少なくともページを返すことができます.
    次の章では、ビューコントローラの書き込みを開始します
    バイバイ~
    完了