SpringMVC原始碼分析(二)-URL對映的註冊
首先我們來看下AbstractHandlerMethodMapping這個類,它實現了InitializingBean介面,裡面有個afterPropertiesSet()方法。這個介面是spring-beans這個元件的內容,想一想,平時使用搭建SpringMVC的時候,是不是把這個jar包也扔到專案裡頭了?對於spring-beans這個元件在這裡就 不拓展了,我們只要知道實現了InitializingBean這個介面後,spring容器在物件例項化完後進行呼叫afterPropertiesSet()方法即可(注意這裡是spring容器,而不是tomcat容器)。部分原始碼如下
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
*/
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
//取得容器的所有bean
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
//isHandler()方法用於提取有@Controller或者@RequestMapping的類
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());//此處SpringMVC並未做任何實現
}
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}
afterPropertiesSet()其實是呼叫了initHandlerMethods()方法,這方法上面註釋上面也寫得相當明白:掃描應用上下文,檢測並註冊處理方法。也就是說就是掃描所有的bean,並使用isHandler()提取出所有@Controller或者@RequestMapping的類。isHandler()是由子類去實現的,如下:
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
}
繼續分析第一段原始碼,將上面提取出來的類使用detectHandlerMethods()方法進行處理,看著這個方法的最後五行,也就是迴圈那部分。這裡面有兩個關鍵的變數,一是mapping,二是invocableMethod。前者是@RequestMapping對應的URL地址,包括這個URL是GET還是POST方法等;後者是URL對應的方法,包括入參返回值等等。舉個列子:
@RequestMapping(value = "stu/{id}",method = RequestMethod.GET)
@ResponseBody
public Object student(@PathVariable("id") Integer id){
return new Student(id,"小王");
}
上面例子,我們除錯可知道這兩個變數裡面的值為:
mapping:{[/stu/{id}],methods=[GET]}
invocableMethod:public java.lang.Object com.eejron.controller.HelloController.student(java.lang.Integer)
對於獲取到的這兩個變數,SpringMVC將其註冊起來便於後續的訪問。繼續看registerHandlerMethod()這個註冊方法,下面的程式碼還是前文中第一段程式碼同一個類的內容,但是排版上選擇將其拆開。會發現在內部類MappingRegistry中,有5個Map,它們都有各自的自責。
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
private final MappingRegistry mappingRegistry = new MappingRegistry();
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
private static class MappingRegistration<T> {//內部靜態類
private final T mapping;
private final HandlerMethod handlerMethod;
private final List<String> directUrls;
private final String mappingName;
}
class MappingRegistry {//內部類
private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();
private final Map<String, List<HandlerMethod>> nameLookup =
new ConcurrentHashMap<String, List<HandlerMethod>>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup =
new ConcurrentHashMap<HandlerMethod, CorsConfiguration>();
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
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<T>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
private List<String> getDirectUrls(T mapping) {
List<String> urls = new ArrayList<String>(1);
for (String path : getMappingPathPatterns(mapping)) {
if (!getPathMatcher().isPattern(path)) {
urls.add(path);
}
}
return urls;
}
private void addMappingName(String name, HandlerMethod handlerMethod) {
List<HandlerMethod> oldList = this.nameLookup.get(name);
if (oldList == null) {
oldList = Collections.<HandlerMethod>emptyList();
}
for (HandlerMethod current : oldList) {
if (handlerMethod.equals(current)) {
return;
}
}
if (logger.isTraceEnabled()) {
logger.trace("Mapping name '" + name + "'");
}
List<HandlerMethod> newList = new ArrayList<HandlerMethod>(oldList.size() + 1);
newList.addAll(oldList);
newList.add(handlerMethod);
this.nameLookup.put(name, newList);
if (newList.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace("Mapping name clash for handlerMethods " + newList +
". Consider assigning explicit names.");
}
}
}
}
}
我們主要來看下register()方法裡面涉及到Map的操作:
this.mappingLookup.put(mapping, handlerMethod);
這個很明瞭,就是前文分析獲取到的mapping跟invocableMethod的關聯,只不過是handlerMethod又把invocableMethod封裝一次而已
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
上面這個實現了同一個@RequestMapping對映多個請求,也就說說stu/{id}和student/{id}都能對映到下面同一個方法上,使用程式碼示例如下:
@RequestMapping(value = {"stu/{id}","student/{id}"},method = RequestMethod.GET)
@ResponseBody
public Object student(@PathVariable("id") Integer id){
return new Student(id,"小王");
}
還有個this.nameLookup.put(name, newList),但是這個被我吃掉了,因為沒看懂啥作用-,-
this.corsLookup.put(handlerMethod, corsConfig);
上面是跨域處理,使用@CrossOrigin註解才會進行處理
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
上面這個就是對上面分析的的一個封裝。
整個SpringMVC啟動時註冊相應的URL至此就完成了,後面的就是請求進來後提取註冊的資訊並呼叫相應方法執行