最近在寫一個權限框架,想着怎麼獲取項目裏面全部的url映射? 先說結論是怎麼獲取的,後面詳細介紹。java
Map<RequestMappingInfo, HandlerMethod> map = RequestMappingHandlerMapping.getHandlerMethods();
// 獲取全部的key
Set<RequestMappingInfo> requestMappingInfos = handlerMethods.keySet();
// 獲取全部的mapping
Set<String> mappings =new LinkedHashSet<>();
for (RequestMappingInfo info : requestMappingInfos) {
PatternsRequestCondition patternsCondition = info.getPatternsCondition();
for (String mapping : patternsCondition.getPatterns()){
mappings.add(mapping);
}
}
複製代碼
而後就想起來MVC運行的流程,它爲何能夠經過一個uri就能夠執行到對應的方法呢? 這確定是初始化的時候,將Controller裏面的帶有@RequestMapping
註解的方法與uri保存在集合中,而後執行的時候,根據請求的uri到集合裏面去找到對應的方法, 而後經過反射執行方法,完成調用。web
RequestMappingHandlerMapping
:首先從該類的的afterPropertiesSet
入手:spring
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());
//調用了父類的afterPropertiesSet
super.afterPropertiesSet();
}
//判斷bean是否帶有@Controller或者@RequestMapping註解
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
複製代碼
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet
父類的初始化方法:bash
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
//getCandidateBeanNames()獲取全部註冊的bean
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 處理候選bean
processCandidateBean(beanName);
}
}
// 打印日誌,輸出日誌handlerMethods的數量
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
// 獲取bean的類型
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// 從候選bean中選出知足isHandler(beanType)的bean,判斷bean是否帶有@Controller或者@RequestMapping註解
// isHandler(Class<?> beanType)在該類中未實現,具體實現是在RequestMappingHandlerMapping中,上面貼出了代碼
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
// 在指定的handler中尋找被@RequestMapping標記的methods
// 這裏的handler能夠理解成咱們常說的Controller
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 返回自己的類,若是該Class是CGLB代理的,那麼就返回它的父類
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 基於關聯元數據的查找,選擇給定目標相似上的方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 選擇被@RequestMapping標記的方法,這裏返回的是RequestMappingInfo對象,mapping也被存放在該對象中
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
// 返回給定一個Class上的可調用的方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 註冊請求映射
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
複製代碼
RequestMappingInfo
,簡單介紹一下,這個也是下面註冊方法裏面傳遞的mapping,也就是說咱們所須要的mapping值存在這個對象中。app
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 獲取帶有@RequestMapping註解方法上面的path值
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//獲取方法對於類上@RequestMapping註解方法上面的path值
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 拼接起來
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
RequestMappingInfo.Builder builder = RequestMappingInfo
// 能夠看到咱們path被賦值到paths中
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
// 看一下這個builder,將path屬性轉移到哪裏?
return builder.options(this.config).build();
}
public RequestMappingInfo build() {
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
// 能夠看到path值變成PatternsRequestCondition的一個屬性patterns;,這裏代碼比較簡單,我就知道說結論了
PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions());
// 而PatternsRequestCondition又是RequestMappingInfo的一個屬性,因此獲取mapping的方法找到了
// 對應文章開頭的方法獲取全部的mapping
return new RequestMappingInfo(this.mappingName, patternsCondition,
new RequestMethodsRequestCondition(this.methods),
new ParamsRequestCondition(this.params),
new HeadersRequestCondition(this.headers),
new ConsumesRequestCondition(this.consumes, this.headers),
new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition);
}
複製代碼
介紹這個registerHandlerMethod(Object,Method,Mapping)
方法以前,先介紹幾個類HandlerMethod
,看名字就知道是handle與method的結合體,這個結構保存了handle與對應的method。cors
public class HandlerMethod {
// handle
private final Object bean;
@Nullable
private final BeanFactory beanFactory;
// handle Class
private final Class<?> beanType;
// 方法
private final Method method;
private final Method bridgedMethod;
private final MethodParameter[] parameters;
...其餘幾個不重要的忽略
複製代碼
MappingRegistry
是一個內部類,裏面有幾個關鍵成員變量,來存放全部的mapping與方法框架
class MappingRegistry {
// 保存的是mapping與MappingRegistration關係。MappingRegistration
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
// 保存的是mapping與HandlerMethod的映射關係
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
// 保存的是url與mapping的映射關係,原本這個很關鍵,可是沒有提供對外的方法,因此想要獲取mapping,仍是得從RequestMappingInfo獲取
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
// 保存的是名字與List<HandlerMethod>的關係
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
// // 保存的是HandlerMethod與CorsConfiguration的關係
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
複製代碼
咱們繼續來介紹核心方法`registerHandlerMethod(Object,Method,Mapping)ide
// mappingRegistry是AbstractHandlerMethodMapping的一個抽象類,下面會介紹
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] parameterTypes = method.getParameterTypes();
if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
// 上讀寫鎖
this.readWriteLock.writeLock().lock();
try {
// 建立根據handle與method建立HandlerMethod,也就是創建起了Controller與對應執行方法的關係
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//校驗
validateMethodMapping(handlerMethod, mapping);
// 將mapping與handlerMethod存入map
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
將url與mapping存入urlLookup中
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
複製代碼