当项目很大,又需要给URL做权限控制的时候,一个一个去Controller找,一条一条手动输入是有多麻烦可想而知,本篇文章旨在介绍如何用代码的形式获取项目中所有的url。
直接上代码
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Controller
public class GetAllUrl {
@RequestMapping("/getAllUrl")
@ResponseBody
public Set<String> getAllUrl(HttpServletRequest request) {
Set<String> result = new HashSet<String>();
WebApplicationContext wc = (WebApplicationContext) request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
RequestMappingHandlerMapping bean = wc.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethods = bean.getHandlerMethods();
for (RequestMappingInfo rmi : handlerMethods.keySet()) {
PatternsRequestCondition pc = rmi.getPatternsCondition();
Set<String> pSet = pc.getPatterns();
result.addAll(pSet);
}
return result;
}
}
在浏览器中输入请求的url:http://localhost:8080/getAllUrl 可以看见结果,这里直接返回的是String,可以用JSON在线格式转换工具转换,可以看见下图结果(这里只有一个url)
小雷我获取项目的全部URL是因为小雷的好奇心!
下面讲解一下原理。
代码的核心:
WebApplicationContext wc = (WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
RequestMappingHandlerMapping bean = wc.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethods = bean.getHandlerMethods();
1、WebApplicationContext
WebApplicationContext是专门为web应用准备的,他允许从相对于web根目录的路径中装载配置文件完成初始化工作,从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置在ServletContext中,以便web应用可以访问spring上下文。
这里通过request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)获取的WebApplicationContext。
DispatcherServlet
Spring中的DispatcherServlet除了作为处理request的前端控制器,还负责与Spring IOC容器整合并提供Spring的功能。
DispatcherServlet的工作流程
在Spring MVC中,每个DispatcherServlet拥有其独立的WebApplicationContext,继承了root/global WebApplicationContext中所定义的所有的bean。这些继承的bean可以在DispatcherServlet中被重写;DispatcherServlet也可以定义自己特有的benas。
可以在web.xml中为DispatcherServlet指定一个对应的配置文件,当然也可以选择不指定,那么Servlet的WebApplicationContext将没有特定的benas。
2、RequestMappingHandlerMapping
从WebApplicationContext中获取RequestMappingHandlerMapping.class类型的所有bean。
源码分析:
RequestMappingHandlerMapping主要有两个功能注册和查找
注册指的是注册Interceptor和controller
查找是根据request查找与之对应的controller和Interceptor
我们知道初始化DispatcherServlet的时候首先要初始化HandlerMapping,这个时候就会初始化spring容器中注册的HandlerMapping就是RequestMappingHandlerMapping
RequestMappingHandlerMapping的初始化主要分成两个部分
初始化Interceptor和controller
初始化的工作在AbstractHandlerMapping类中完成代码如下
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}
完成了Interceptor的初始化后,就是controller的初始化controller的初始化在AbstractHandlerMethodMapping中进行代码如下
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
上面的代码中首先是找到spring容器中注册了的所有类的beamName然后遍历这些类,检查它们是否是controller如果是则调用detectHandlerMethods方法代码如下
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String) ?
getApplicationContext().getType((String) handler) : handler.getClass();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
public boolean matches(Method method) {
return getMappingForMethod(method, userType) != null;
}
});
for (Method method : methods) {
T mapping = getMappingForMethod(method, userType);
registerHandlerMethod(handler, method, mapping);
}
}
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
info = createRequestMappingInfo(methodAnnotation, methodCondition);
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
在getMappingForMethod方法里面,找到方法里面的RequestMapping注解,然后根据注解生成RequestMappingInfo完成上面的步骤之后,就会将找到的controller注册 代码如下
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean()
+ "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '"
+ oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
}
this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
}
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
}
这里面有两个map一个是urlMap,一个是handlerMethods通过url我们可以找到与之对应的mapping,而通过mapping找到url对应的HandlerMethod 也就是要执行的controller和方法。