spring AbstractHandlerMethodMapping 源码
spring AbstractHandlerMethodMapping 代码
文件路径:/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import reactor.core.publisher.Mono;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.MethodIntrospector;
import org.springframework.http.server.RequestPath;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
/**
* Abstract base class for {@link HandlerMapping} implementations that define
* a mapping between a request and a {@link HandlerMethod}.
*
* <p>For each registered handler method, a unique mapping is maintained with
* subclasses defining the details of the mapping type {@code <T>}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sam Brannen
* @since 5.0
* @param <T> the mapping for a {@link HandlerMethod} containing the conditions
* needed to match the handler method to an incoming request.
*/
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
/**
* Bean name prefix for target beans behind scoped proxies. Used to exclude those
* targets from handler method detection, in favor of the corresponding proxies.
* <p>We're not checking the autowire-candidate status here, which is how the
* proxy target filtering problem is being handled at the autowiring level,
* since autowire-candidate may have been turned to {@code false} for other
* reasons, while still expecting the bean to be eligible for handler methods.
* <p>Originally defined in {@link org.springframework.aop.scope.ScopedProxyUtils}
* but duplicated here to avoid a hard dependency on the spring-aop module.
*/
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
/**
* HandlerMethod to return on a pre-flight request match when the request
* mappings are more nuanced than the access control headers.
*/
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
new HandlerMethod(new PreFlightAmbiguousMatchHandler(),
ClassUtils.getMethod(PreFlightAmbiguousMatchHandler.class, "handle"));
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
static {
ALLOW_CORS_CONFIG.addAllowedOriginPattern("*");
ALLOW_CORS_CONFIG.addAllowedMethod("*");
ALLOW_CORS_CONFIG.addAllowedHeader("*");
ALLOW_CORS_CONFIG.setAllowCredentials(true);
}
private final MappingRegistry mappingRegistry = new MappingRegistry();
// TODO: handlerMethodMappingNamingStrategy
/**
* Return a (read-only) map with all mappings and HandlerMethod's.
*/
public Map<T, HandlerMethod> getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
try {
return Collections.unmodifiableMap(
this.mappingRegistry.getRegistrations().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().handlerMethod)));
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
/**
* Return the internal mapping registry. Provided for testing purposes.
*/
MappingRegistry getMappingRegistry() {
return this.mappingRegistry;
}
/**
* Register the given mapping.
* <p>This method may be invoked at runtime after initialization has completed.
* @param mapping the mapping for the handler method
* @param handler the handler
* @param method the method
*/
public void registerMapping(T mapping, Object handler, Method method) {
if (logger.isTraceEnabled()) {
logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
}
this.mappingRegistry.register(mapping, handler, method);
}
/**
* Un-register the given mapping.
* <p>This method may be invoked at runtime after initialization has completed.
* @param mapping the mapping to unregister
*/
public void unregisterMapping(T mapping) {
if (logger.isTraceEnabled()) {
logger.trace("Unregister mapping \"" + mapping);
}
this.mappingRegistry.unregister(mapping);
}
// Handler method detection
/**
* Detects handler methods at initialization.
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
// Total includes detected mappings + explicit registrations via registerMapping
int total = getHandlerMethods().size();
if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {
logger.debug(total + " mappings in " + formatMappingName());
}
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #isHandler(Class)
* @see #getMappingForMethod(Method, Class)
* @see #handlerMethodsInitialized(Map)
*/
protected void initHandlerMethods() {
String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
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);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
/**
* Look for handler methods in a handler.
* @param handler the bean name of a handler or a handler instance
*/
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String beanName ?
obtainApplicationContext().getType(beanName) : handler.getClass());
if (handlerType != null) {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
else if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
private String formatMappings(Class<?> userType, Map<Method, T> methods) {
String packageName = ClassUtils.getPackageName(userType);
String formattedType = (StringUtils.hasText(packageName) ?
Arrays.stream(packageName.split("\\."))
.map(packageSegment -> packageSegment.substring(0, 1))
.collect(Collectors.joining(".", "", "." + userType.getSimpleName())) :
userType.getSimpleName());
Function<Method, String> methodFormatter = method -> Arrays.stream(method.getParameterTypes())
.map(Class::getSimpleName)
.collect(Collectors.joining(",", "(", ")"));
return methods.entrySet().stream()
.map(e -> {
Method method = e.getKey();
return e.getValue() + ": " + method.getName() + methodFormatter.apply(method);
})
.collect(Collectors.joining("\n\t", "\n\t" + formattedType + ":" + "\n\t", ""));
}
/**
* Register a handler method and its unique mapping. Invoked at startup for
* each detected handler method.
* @param handler the bean name of the handler or the handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already registered
* under the same mapping
*/
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
/**
* Create the HandlerMethod instance.
* @param handler either a bean name or an actual handler instance
* @param method the target method
* @return the created HandlerMethod
*/
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
if (handler instanceof String beanName) {
return new HandlerMethod(beanName,
obtainApplicationContext().getAutowireCapableBeanFactory(),
obtainApplicationContext(),
method);
}
return new HandlerMethod(handler, method);
}
/**
* Extract and return the CORS configuration for the mapping.
*/
@Nullable
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, T mapping) {
return null;
}
/**
* Invoked after all handler methods have been detected.
* @param handlerMethods a read-only map with handler methods and mappings.
*/
protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
}
// Handler method lookup
/**
* Look up a handler method for the given request.
* @param exchange the current exchange
*/
@Override
public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod;
try {
handlerMethod = lookupHandlerMethod(exchange);
}
catch (Exception ex) {
return Mono.error(ex);
}
if (handlerMethod != null) {
handlerMethod = handlerMethod.createWithResolvedBean();
}
return Mono.justOrEmpty(handlerMethod);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
/**
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
* @param exchange the current exchange
* @return the best-matching handler method, or {@code null} if no match
* @see #handleMatch
* @see #handleNoMatch
*/
@Nullable
protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(exchange);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, exchange);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, exchange);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(exchange.getLogPrefix() + matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
for (Match match : matches) {
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
}
else {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
RequestPath path = exchange.getRequest().getPath();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + path + "': {" + m1 + ", " + m2 + "}");
}
}
}
handleMatch(bestMatch.mapping, bestMatch.getHandlerMethod(), exchange);
return bestMatch.getHandlerMethod();
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), exchange);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, ServerWebExchange exchange) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, exchange);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
}
}
}
/**
* Invoked when a matching mapping is found.
* @param mapping the matching mapping
* @param handlerMethod the matching method
* @param exchange the current exchange
*/
protected void handleMatch(T mapping, HandlerMethod handlerMethod, ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
/**
* Invoked when no matching mapping is not found.
* @param mappings all registered mappings
* @param exchange the current exchange
* @return an alternative HandlerMethod or {@code null}
* @throws Exception provides details that can be translated into an error status code
*/
@Nullable
protected HandlerMethod handleNoMatch(Set<T> mappings, ServerWebExchange exchange) throws Exception {
return null;
}
@Override
protected boolean hasCorsConfigurationSource(Object handler) {
return super.hasCorsConfigurationSource(handler) ||
(handler instanceof HandlerMethod handlerMethod && this.mappingRegistry.getCorsConfiguration(handlerMethod) != null);
}
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
CorsConfiguration corsConfig = super.getCorsConfiguration(handler, exchange);
if (handler instanceof HandlerMethod handlerMethod) {
if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
return ALLOW_CORS_CONFIG;
}
CorsConfiguration methodConfig = this.mappingRegistry.getCorsConfiguration(handlerMethod);
corsConfig = (corsConfig != null ? corsConfig.combine(methodConfig) : methodConfig);
}
return corsConfig;
}
// Abstract template methods
/**
* Whether the given type is a handler with handler methods.
* @param beanType the type of the bean being checked
* @return "true" if this a handler type, "false" otherwise.
*/
protected abstract boolean isHandler(Class<?> beanType);
/**
* Provide the mapping for a handler method. A method for which no
* mapping can be provided is not a handler method.
* @param method the method to provide a mapping for
* @param handlerType the handler type, possibly a subtype of the method's
* declaring class
* @return the mapping, or {@code null} if the method is not mapped
*/
@Nullable
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
/**
* Return the request mapping paths that are not patterns.
* @since 5.3
*/
protected Set<String> getDirectPaths(T mapping) {
return Collections.emptySet();
}
/**
* Check if a mapping matches the current request and return a (potentially
* new) mapping with conditions relevant to the current request.
* @param mapping the mapping to get a match for
* @param exchange the current exchange
* @return the match, or {@code null} if the mapping doesn't match
*/
@Nullable
protected abstract T getMatchingMapping(T mapping, ServerWebExchange exchange);
/**
* Return a comparator for sorting matching mappings.
* The returned comparator should sort 'better' matches higher.
* @param exchange the current exchange
* @return the comparator (never {@code null})
*/
protected abstract Comparator<T> getMappingComparator(ServerWebExchange exchange);
/**
* A registry that maintains all mappings to handler methods, exposing methods
* to perform lookups and providing concurrent access.
*
* <p>Package-private for testing purposes.
*/
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* Return all registrations.
* @since 5.3
*/
public Map<T, MappingRegistration<T>> getRegistrations() {
return this.registry;
}
/**
* Return matches for the given URL path. Not thread-safe.
* @since 5.3
* @see #acquireReadLock()
*/
@Nullable
public List<T> getMappingsByDirectPath(ServerWebExchange exchange) {
String path = exchange.getRequest().getPath().pathWithinApplication().value();
return this.pathLookup.get(path);
}
/**
* Return CORS configuration. Thread-safe for concurrent use.
*/
@Nullable
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
return this.corsLookup.get(original != null ? original : handlerMethod);
}
/**
* Acquire the read lock when using getMappings and getMappingsByUrl.
*/
public void acquireReadLock() {
this.readWriteLock.readLock().lock();
}
/**
* Release the read lock after using getMappings and getMappingsByUrl.
*/
public void releaseReadLock() {
this.readWriteLock.readLock().unlock();
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) {
MappingRegistration<T> registration = this.registry.get(mapping);
HandlerMethod existingHandlerMethod = (registration != null ? registration.getHandlerMethod() : null);
if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException(
"Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" +
handlerMethod + "\nto " + mapping + ": There is already '" +
existingHandlerMethod.getBean() + "' bean method\n" + existingHandlerMethod + " mapped.");
}
}
public void unregister(T mapping) {
this.readWriteLock.writeLock().lock();
try {
MappingRegistration<T> registration = this.registry.remove(mapping);
if (registration == null) {
return;
}
for (String path : registration.getDirectPaths()) {
List<T> mappings = this.pathLookup.get(path);
if (mappings != null) {
mappings.remove(registration.getMapping());
if (mappings.isEmpty()) {
this.pathLookup.remove(path);
}
}
}
this.corsLookup.remove(registration.getHandlerMethod());
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
}
static class MappingRegistration<T> {
private final T mapping;
private final HandlerMethod handlerMethod;
private final Set<String> directPaths;
private final boolean corsConfig;
public MappingRegistration(
T mapping, HandlerMethod handlerMethod, @Nullable Set<String> directPaths, boolean corsConfig) {
Assert.notNull(mapping, "Mapping must not be null");
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
this.mapping = mapping;
this.handlerMethod = handlerMethod;
this.directPaths = (directPaths != null ? directPaths : Collections.emptySet());
this.corsConfig = corsConfig;
}
public T getMapping() {
return this.mapping;
}
public HandlerMethod getHandlerMethod() {
return this.handlerMethod;
}
public Set<String> getDirectPaths() {
return this.directPaths;
}
public boolean hasCorsConfig() {
return this.corsConfig;
}
}
/**
* A thin wrapper around a matched HandlerMethod and its mapping, for the purpose of
* comparing the best match with a comparator in the context of the current request.
*/
private class Match {
private final T mapping;
private final MappingRegistration<T> registration;
public Match(T mapping, MappingRegistration<T> registration) {
this.mapping = mapping;
this.registration = registration;
}
public HandlerMethod getHandlerMethod() {
return this.registration.getHandlerMethod();
}
public boolean hasCorsConfig() {
return this.registration.hasCorsConfig();
}
@Override
public String toString() {
return this.mapping.toString();
}
}
private class MatchComparator implements Comparator<Match> {
private final Comparator<T> comparator;
public MatchComparator(Comparator<T> comparator) {
this.comparator = comparator;
}
@Override
public int compare(Match match1, Match match2) {
return this.comparator.compare(match1.mapping, match2.mapping);
}
}
private static class PreFlightAmbiguousMatchHandler {
@SuppressWarnings("unused")
public void handle() {
throw new UnsupportedOperationException("Not implemented");
}
}
}
相关信息
相关文章
spring HandlerMethodArgumentResolver 源码
spring HandlerMethodArgumentResolverComposite 源码
spring HandlerMethodArgumentResolverSupport 源码
spring InvocableHandlerMethod 源码
spring RequestMappingInfoHandlerMapping 源码
spring SyncHandlerMethodArgumentResolver 源码
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦