SpringMVC自定義相容性Handler

是馮吉榮呀發表於2021-03-12

寫在前面

看到這篇部落格時,預設你知道Spring MVC中handler的作用,及前臺請求到響應的的流轉。
感謝網上其他大佬部落格給我的借鑑,部落格地址這裡忘記了。
大家可以直接點選右上角進入我的SpringBoot專案檢視原始碼,有用的話幫我點亮下唄。

自定義Handler

我有時候會考慮是否可以自定義handler,可以參考RequestMappingHandlerMapping繼承的父類,並且重寫部分方法,以下為我的實現。

首先,需要新建一個註釋,這個註釋的作用同@RequestMapping.

package com.example.feng.annotation;


import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author fengjirong
 * @date 2021/3/11 14:20
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FengRequestMapping {

    String value() default "";

    RequestMethod[] method() default {};
}


接下來是自定義handler的程式碼,需要實現多個方法,用於指定自定義註釋修飾的方法使用當前handler,設定handler物件的url等引數。需要注意的是,需將自定義handler的優先順序設定為order(0),否則會出現異常。

package com.example.feng.handler;

import com.example.feng.annotation.FengRequestMapping;
import com.example.feng.utils.ApplicaitonFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringValueResolver;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
import org.springframework.web.servlet.handler.RequestMatchResult;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * @author fengjirong
 * @date 2021/3/11 15:51
 */
@Component
class FengRequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {
    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

    @Nullable
    private StringValueResolver embeddedValueResolver;

    /**
     * handler是否含有@FengRequestMapping註解
     *
     * @param beanType
     * @return boolean
     * @Author fengjirong
     * @Date 2021/3/11 14:35
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        Method[] methods = beanType.getDeclaredMethods();
        for (Method method : methods) {
            if (AnnotationUtils.findAnnotation(method, FengRequestMapping.class) != null) {
                return true;
            }
        }
        return false;

    }

    /**
     * description: 使用方法級別的@ {@FengRequestMapping}註釋建立RequestMappingInfo。
     *
     * @param method  handlerType
     * @param handlerType  handlerType
     * @return RequestMappingInfo
     * @Author fengjirong
     * @Date   2021/3/12 11:24
     */
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = null;
        FengRequestMapping mapping = method.getAnnotation(FengRequestMapping.class);
        if (mapping != null){
            RequestCondition<?> condition = getCustomMethodCondition(method);
            info = createRequestMappingInfo(mapping, condition);
        }
        return info;
    }

    /**
     * description: 匹配操作
     *
     * @param info
     * @return
     * @Author fengjirong
     * @Date   2021/3/12 11:26
     */
    @Override
    protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
        request.setAttribute("isMongo", true);
        request.setAttribute("handledTime", System.nanoTime());
    }

    /**
     * description: 不匹配url處理
     * 
     * @param infos 
     * @param lookupPath 
     * @param request 
     * @return HandlerMethod
     * @Author fengjirong
     * @Date   2021/3/12 11:37
     */
    @Override
    protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {
        return null;
    }

    /**
     * description: 從註解中獲得請求路徑、請求型別等建立RequestMappingInfo物件方法
     *
     * @param requestMapping
     * @param customCondition
     * @return RequestMappingInfo
     * @Author fengjirong
     * @Date   2021/3/12 11:28
     */
    private RequestMappingInfo createRequestMappingInfo(
            FengRequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
        ConfigurableApplicationContext context = ApplicaitonFactory.getContext();
        RequestMappingInfo.Builder builder = RequestMappingInfo
                .paths(resolveEmbeddedValuesInPatterns(new String[]{requestMapping.value()}))
                .methods(requestMapping.method())
                .params(new String[]{})
                .headers(new String[]{})
                .consumes(new String[]{})
                .produces(new String[]{})
                .mappingName("");
        if (customCondition != null) {
            builder.customCondition(customCondition);
        }
        return builder.options(this.config).build();
    }

    /**
     * 屬性設定
     */
    @Override
    public void afterPropertiesSet() {
        // 提升當前 HandlerMapping 的在對映處理器列表中的順序
        super.setOrder(0);
        super.afterPropertiesSet();
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    @Override
    public RequestMatchResult match(HttpServletRequest request, String pattern) {
        Assert.isNull(getPatternParser(), "This HandlerMapping requires a PathPattern");
        RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
        RequestMappingInfo match = info.getMatchingCondition(request);
        return (match != null && match.getPatternsCondition() != null ?
                new RequestMatchResult(
                        match.getPatternsCondition().getPatterns().iterator().next(),
                        UrlPathHelper.getResolvedLookupPath(request),
                        getPathMatcher()) : null);
    }

    /**
     * Resolve placeholder values in the given array of patterns.
     * @return a new array with updated patterns
     */
    protected String[] resolveEmbeddedValuesInPatterns(String[] patterns) {
        if (this.embeddedValueResolver == null) {
            return patterns;
        }
        else {
            String[] resolvedPatterns = new String[patterns.length];
            for (int i = 0; i < patterns.length; i++) {
                resolvedPatterns[i] = this.embeddedValueResolver.resolveStringValue(patterns[i]);
            }
            return resolvedPatterns;
        }
    }

    @Nullable
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        return null;
    }
}

接下來是測試controller,測試@FengRequestMapping與@RequestMapping的相容性。

package com.example.feng.student;

import com.example.feng.annotation.FengRequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author fengjirong
 * @date 2021/3/10 11:11
 */
@Controller
public class StudentController {

    @ResponseBody
    @FengRequestMapping(value = "/student", method = RequestMethod.GET)
    public String get(){
        return "get submit";
    }

    @ResponseBody
    @FengRequestMapping(value = "/student", method = RequestMethod.POST)
    public String post(){
        return "post submit";
    }

    @ResponseBody
    @FengRequestMapping(value = "/student", method = RequestMethod.PUT)
    public String put(){
        return "put submit";
    }

    @ResponseBody
    //@FengRequestMapping(value = "/student", method = RequestMethod.DELETE)
    @DeleteMapping(value = "/student")
    public String delete(){
        return "delete submit";
    }
}

前臺頁面使用rest風格表單提交的index.html。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>rest風格controller測試</title>
    <script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
    <script type="text/javascript">
        function doButton() {
            $.ajax({
                type: "DELETE",
                url: "/student",
                async:false,
                success:function (data) {
                    alert(data)
                }
            });
        }
    </script>
</head>
<body>
<form action="/student" method="GET">
    <input type="submit" value="get">
</form>
<form action="/student" method="POST">
    <input type="submit" value="post">
</form>
<form action="/student" method="POST">
    <input name="_method" value="put" type="hidden">
    <input name="_m" value="put" type="hidden">
    <input type="submit" value="put">
</form>
<form action="/student" method="POST">
    <input name="_method" value="delete" type="hidden">
    <input name="_m" value="delete" type="hidden">
    <input type="submit" value="delete">
</form>

<button name="button1" onclick="doButton()">
    確認
</button>


</body>
</html>

看效果

檢視自定義handler中handler註冊詳情。

而使用@RequestMapping標註的handler註冊在RequestMappingHandlerMapping元件中。

由上兩張圖,我們可以看到即使多個註解在一個conroller中,也能夠得到很好的對映,這樣提高了自定義handler的相容性。
由於我測試的時候構建的是Spring Boot專案,訪問http://localhost:8004/跳轉到index.html,點選按鈕,前臺提交表單,可以得到對應的響應。

相關文章