寫在前面
看到這篇部落格時,預設你知道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,點選按鈕,前臺提交表單,可以得到對應的響應。