インタビュアー:Spring MVCの実行プロセスについて教えてください。なぜこのように設計されているのですか?

ここに画像の説明を挿入

SpringMVCの手書き

Spring MVCの全体的な実装の基本を理解できるように、最初にSpringMVCを作成しましょう。

githubコード:https://github.com/erlieStar/servlet-learningv3
ブランチ

サーブレット3.0が使用されるため、web.xmlは不要であり、プロセス全体に注釈が付けられます。

定義アノテーション

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    
    
    String value() default "";
}
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    
    
    String value() default "";
}

中央コントローラーを定義する

基本的にすべてのロジックはこのクラスにあり、メインフローは次のとおりです

  1. DispatcherServletが作成されると、tomcatはinit()メソッドを呼び出して、URLと対応する処理メソッドの間のマッピング関係を初期化します。
  2. リクエストが来たら、uriInvokeInfoMapから対応するメソッドを取得します。対応するメソッドがある場合は、リフレクションによってメソッドを呼び出し、ページ名を取得し、ページアドレスをスプライスして、対応するページに転送します。それ以外の場合は、404を返します。
@WebServlet(urlPatterns="/", loadOnStartup = 1)
public class DispatcherServlet extends HttpServlet {
    
    

    // 保存所有的handler
    private List<Object> beanList = new ArrayList<>();
    // 保存 uri 和 handler 的映射关系
    private Map<String, HandlerMethod> uriHandlerMethodMap = new HashMap<>();

    private static final String SLASH = "/";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        // 去掉项目路径
        uri = uri.replace(contextPath, "");
        System.out.println(uri);
        if (uri == null) {
    
    
            return;
        }
        HandlerMethod handlerMethod = uriHandlerMethodMap.get(uri);
        if (handlerMethod == null) {
    
    
            resp.getWriter().write("404");
            return;
        }
        String pageName = (String)methodInvoke(handlerMethod.getBean(), handlerMethod.getMethod());
        viewResolver(pageName, req, resp);
    }

    // 视图解析器
    public void viewResolver(String pageName, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        String prefix = "/";
        String suffix = ".jsp";
        req.getRequestDispatcher(prefix + pageName + suffix).forward(req, resp);
    }

    // 反射执行方法
    private Object methodInvoke(Object object, Method method) {
    
    
        try {
    
    
            return method.invoke(object);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void init() throws ServletException {
    
    
        // 获取指定包下的Class对象
        List<Class<?>> classList = ClassUtil.getAllClassByPackageName("com.javashitang.controller");
        // 找到所有标注了@Controller的类
        findAllConrollerClass(classList);
        // 初始化 uri 和 handler 的映射关系
        handlerMapping();
    }


    public void findAllConrollerClass(List<Class<?>> list) {
    
    
        list.forEach(bean -> {
    
    
            // 将被@Controller注解修饰的类放到beanList
            if (bean.isAnnotationPresent(Controller.class)) {
    
    
                try {
    
    
                    beanList.add(bean.newInstance());
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
        });
    }

    // 根据url找到相应的处理类
    public void handlerMapping() {
    
    
        for (Object bean : beanList) {
    
    
            Class<? extends Object> classInfo = bean.getClass();
            // 获取类上的@RequestMapping信息
            RequestMapping beanRequestMapping = classInfo.getDeclaredAnnotation(RequestMapping.class);
            String baseUrl = beanRequestMapping != null ? beanRequestMapping.value() : "";
            Method[] methods = classInfo.getDeclaredMethods();
            for (Method method : methods) {
    
    
                System.out.println(method.getName());
                // 获取方法上的@RequestMapping信息
                RequestMapping methodRequestMapping = method.getDeclaredAnnotation(RequestMapping.class);
                if (methodRequestMapping != null) {
    
    
                    String requestUrl = SLASH + baseUrl + SLASH + methodRequestMapping.value();
                    // 为了处理@Controller和@RequestMapping value 中加了 / 前缀的情况
                    requestUrl = requestUrl.replaceAll("/+", "/");
                    HandlerMethod handlerMethod = new HandlerMethod(bean, method);
                    uriHandlerMethodMap.put(requestUrl, handlerMethod);
                }
            }
        }
    }
}

メソッドとそれに対応するクラスはカプセル化されています

public class HandlerMethod {
    
    

    private Object bean;
    private Method method;

    public HandlerMethod(Object bean, Method method) {
    
    
        this.bean = bean;
        this.method = method;
    }
}

テストを開始します

@Controller
@RequestMapping("index")
public class IndexController {
    
    

    @RequestMapping("user")
    public String user() {
    
    
        return "user";
    }
}

次の接続にアクセスすると、ページは正常に表示されます

http://localhost:8080/show/index/user

Spring MVCのソースコードを読んだ場合、それは本質的にマップにアクセスするプロセスです

  1. 開始時に、URLとそれに対応するメソッドをマップに保存します
  2. リクエストがあった場合は、URLに従ってマップから対応するメソッドを見つけ、そのメソッドを実行して結果を返します。

SpringMVC実行プロセス

ここに画像の説明を挿入
上の図は、SpringMVCの実行プロセスを示しています。

  1. ユーザーがDispatcherServletにリクエストを送信します
  2. DispatcherServletは、HandlerMappingから対応するハンドラーを見つけます
  3. DispatcherServletは、ハンドラーを実行できるHandlerAdapterを見つけます
  4. HandlerAdapterはHandlerを実行し、Handlerから返されたModelAndViewをDispatcherServletに返します。
  5. DispatcherServletはModelAndViewをViewResolverに転送します
  6. ViewResolverは、対応するViewオブジェクトを返します
  7. DispatcherServletは、Viewオブジェクトに基づいてビューをレンダリングします(モデルデータをビューに入力します)
  8. DispatcherServletはユーザーにビューを応答します

DispatcherServlet

DispatcherServletの継承関係を次の図に示します。HttpServletクラスとその親クラスはjavaxパッケージで定義され、残りはSpringパッケージで定義されています
ここに画像の説明を挿入
。DispatcherServletは本質的にHttpServletであることがわかります。

Spring MVCなしでWebプログラムを作成する方法を考えてみてください。

  1. HttpServletを継承し、doGetまたはdoPostメソッドをオーバーライドするクラスを記述します
  2. @WebServletアノテーションを使用して、要求されたパスを定義します

以前は、リクエストに対してHttpServletが作成されていたことがわかります。現在、すべてのリクエストがDispatcherServletに転送され、DispatcherServletリフレクションによってコントローラーのメソッドが呼び出され、結果がユーザーに返されます。

ここに画像の説明を挿入
これの目的は、Webリクエストの処理フローを統一された方法で管理することです。StrutsとSpringMVCはどちらもこのように設計されています。

Springコンテナが開始または更新されると、DispatcherServletは、HandlerMapping、HandlerAdapter、ViewResolverなどの後続の一般的に使用されるコンポーネントを初期化します。

HandlerMapping

HandlerMappingは、主に要求されたURLに従って対応するハンドラーを検索します。これを見ると、ControllerMappingと呼ぶ方がよいのではないかと思うかもしれません。いいえ、ハンドラーが言われる理由は、Spring MVCではハンドラーを実装する一般的な方法が3つあるためですが、通常は@RequestMappingアノテーションのみを使用します

コントローラインターフェイスを実装する

@Component("/index")
public class IndexController implements Controller {
    
    

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
		response.getWriter().write("IndexController");
		return null;
	}
}

http:// localhost:8080 / indexにアクセスし、ページはIndexControllerを出力します。ここで説明する2つのポイントがあります

  1. ハンドラーによって返されるModelAndViewがnullの場合、後続のViewResolverはビューを検索し、ビューのレンダリングプロセスは省略されます。
  2. @Componentアノテーションの値は/で始まる必要があります。理由については後で説明します。

HttpRequestHandlerインターフェースを実装する

@Component("/address")
public class AddressController implements HttpRequestHandler {
    
    
	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
		response.getWriter().write("AddressController");
	}
}

http:// localhost:8080 / addressにアクセスすると、ページにAddressControllerが出力されます。

@RequestMappingアノテーションを使用する

この方法は誰もが知っているはずなので、紹介しません

Handlerの実装は多数あるため、対応する検索メソッドも多数あるはずです。SpringMVCには、さまざまなマッピング戦略に対応する3つのHandlerMapping実装クラスがあります。

マッピング戦略 ハンドラーの実装 実装クラスを見つける
シンプルなURLマッピング HttpRequestHandlerインターフェイスを実装します。Controllerインターフェイスを実装し、パスを指定します SimpleUrlHandlerMapping
BeanNameマッピング パスを指定せずにコントローラーインターフェイスを実装する BeanNameUrlHandlerMapping
@RequestMappingマッピング @RequestMappingアノテーションを使用する RequestMappingHandlerMapping

詳細については、私の記事を参照してください。この記事の焦点はさまざまなコンポーネントの分析ではないため、ここでは説明しません
https://blog.csdn.net/zzti_erlie/article/details/ 106168046

HandlerAdapter

実際、HandlerAdapterコンポーネントが必要な理由を最初は理解していませんでした。ハンドラーが見つかったため、ハンドラーメソッドを直接呼び出すだけでは不十分です。

ハンドラーを実装する方法はたくさんあることを知っていたので、ハンドラーの種類ごとに呼び出しロジックが異なるため、HandlerAdapterが必要であることに気付きました。HandlerAdapterを使用すると、分離することができます。それ以外の場合は、それ以外の場合は多数です。

たとえば、Controllerインターフェイスを実装するハンドラーの場合、呼び出しロジックはhandleRequestメソッドを実行し、@ RequestMappingハンドラーはリフレクションを介してメソッドを実行します。

一般的に使用されるHandlerAdapterは次のとおりです

クラス名 効果
HttpRequestHandlerAdapter HttpRequestHandlerインターフェースを実装するハンドラーを実行します
SimpleControllerHandlerAdapter コントローラインターフェイスを実装するハンドラを実行します
RequestMappingHandlerAdapter 実行ハンドラタイプはHandlerMethodとそのサブクラスのハンドラであり、RequestMappingHandlerMappingによって返されるハンドラはHandlerMethodタイプです。

Spring MVCにHandlerMappingとHandlerAdapterが非常に多いのはなぜですか?

主にさまざまなシナリオに適応するために、静的リソース要求、明確なロジック、およびデバッグが容易なSimpleUrlHandlerMappingを使用すると特に便利です。また、RequestMappingHandlerMappingは、複雑で変更可能なシナリオに適応できるため、ビジネスの作成に適しています。

ViewResolver和View

ViewResolverは、ビューを解決するために使用されます。ビューは、最終的にユーザーに返されるビューです。

現在、エンタープライズプロジェクトでは、フロントエンドとバックエンドが分離されているためです。したがって、コンテンツのこの部分については詳しく説明しません。

@RequestMappingを使用してハンドラーを実装する場合、@ ReponseBodyアノテーションをクラスに追加すると、応答は応答に直接書き込まれ、ハンドラーによって返されるModelAndViewはnullになるため、ViewResolverプロセスとViewプロセスはnullになります。到達した

フォローへようこそ

ここに画像の説明を挿入

リファレンスブログ

[1] https://my.oschina.net/liughDevelop/blog/1622646
[2] https://www.malaoshi.top/show_1EFutw6imbI.html

リファレンスブログ

おすすめ

転載: blog.csdn.net/zzti_erlie/article/details/109013525