首先当前主流的 web mvc 框架大部分使用的都是前端控制器模式(FrontController)
前端控制器模式(FrontController)
前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。
包含实体
● 前端控制器(Front Controller) - 处理应用程序所有类型请求的单个处理程序
● 调度器(Dispatcher) - 前端控制器可能使用一个调度器对象来调度请求到相应的具体处理程序。
● 视图(View) - 视图是为请求而创建的对象。
UML
● FrontController、Dispatcher 分别当作前端控制器和调度器。
● HomeView 和 StudentView 表示各种为前端控制器接收到的请求而创建的视图。
● 从FrontControllerPatternDemo发出一个请求到FrontController,然后通过Dispatcher进行调度,将请求转发到具体的HomeView或者StudentView,然后返回结果
对应图
Spring MVC
整体结构
SpringMVC流程
DispatcherServlet
● SpringMVC 使用一个 Servlet(DispatcherServlet) 代理所有的请求。
● 然后根据映射获取处理器后,将请求发送给对应的处理器处理,较好的分离了每一个处理器的职责。
HandlerMapping
● 由定义请求和处理程序对象之间的映射的对象实现的接口。
● 就是在这个对象里面,封装了我们的url和处理对象之间的关系,
● 有多种类型的封装,其中@Controller 只是其中一种,在RequestMappingHandlerMapping中,除此之外,还有很多类型的。
HandlerExecutionChain
● 封装了 Hadler对象 以及拦截器对象 以方便后续进行调用处理
HandlerAdapter
● 处理适配器, 因为在Spring中Handler的实现方式有很多,除了最基本的@Controller 以外,还有很多其他的实现方式例如HandlerFunction,Servlet等等,所以实现方式不同,进行处理的方式就不一样,自然需要不同的适配器来运行对应的Handler。
流程图
源码简介
核心doDispatch执行流程
HandlerMapping
● 从上图可以知道,真正处理流程的最主要的就是获取MappingHandler之后,然后获取对应映射的Handler,然后获取对应的拦截器。
● HandlerMapping 在这个 SpringMVC 体系结构中有着举足轻重的地位,充当着 url 和 Controller 之间映射关系配置的角色。
● 主要有三部分组成:HandlerMapping 映射注册、根据 url 获取对应的处理器、拦截器注册。
初始化initHandlerMethods
● AbstractHandlerMethodMapping是HandlerMapping的子类,并且实现了InitializingBean
● 在InitializingBean的afterPropertiesSet中对Handler进行了初始化
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
initHandlerMethods
● 判断 beanType 是否是满足要求的 handler 和检测 handlerMethod 是最为关键的两个过程。
isHandler
● isHandler方法判断是否满足要求的 handler
● 在RequestMappingHandlerMapping的实现中为判断带有Controller注解或者RequestMapping注解
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class)
||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
detectHandlerMethods简介
● 检测 HandlerMethods 是在 detectHandlerMethods 方法中实现的
● 通过 MethodIntrospector#selectMethods 来选择HandlerMethod
detectHandlerMethods源码简介
1. handler如果是String说是是BeanName,从Spring上下文获取Class,否则之间获取Class
2. 防止代理,获取原始Class
3. 重头戏 MethodIntrospector.selectMethods 从Class中获取的Handle方法
a. 其中getMappingForMethod是抽象方法,每种HandlerMapping的Handle的定义方式可以能不同
4. 注册 Handle和mapping 到 mappingRegistry ,以便后续使用
#1
C
lass<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
#2
Class<?> userType = ClassUtils.getUserClass(handlerType);
#3
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
return getMappingForMethod(method, userType);
});
...
#4
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
getHandler
方法入口
● 上面说了上HandlerMapping有很多类,所以在这里是将所有注册的HandlerMapping进行便利,找到一个执行链就退出
protected HandlerExecutionChain getHandler(HttpServletRequest request) {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;}
HandlerMapping#getHandler
- getHandlerInternal 根据request查找Handler,查不到可以看到返回的是默认Handler,这个方法是一个抽象方法.
- 有是一个判断是不是Bean的
- 这里是初始化一个请求路径,应该是防止前面没有初始化的
- getHandlerExecutionChain 第二个重点,获取执行链的方法,拦截器就在这里获取的
1
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
2
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
3
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}
4
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
…
return executionChain;
getHandlerExecutionChain
● 在这个方法可以看到 拦截器是在adaptedInterceptors中注册的,这个方法便利拦截器,匹配后加入到执行链当中去。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
HandlerAdapter
● 处理适配器, 因为在Spring中Handler的实现方式有很多,除了最基本的@Controller 以外,还有很多其他的实现方式例如HandlerFunction,Servlet等等,所以实现方式不同,进行处理的方式就不一样,自然需要不同的适配器来运行对应的Handler。
HandlerAdapter的定义
● 可以看到其是一个典型的适配器接口,基本从名字就可以判断功能
● supports来确定这个HandlerAdapter是否支持某个Handler的执行
● 而handle方法,就是真正去执行的方法
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
@Deprecated
long getLastModified(HttpServletRequest request, Object handler);
}
DispatcherServlet#getHandlerAdapter
● 这个方法就是我们去获取对应的Handler的HandlerAdapter的方法
● 可以看到这个获取的地方,就使用了supports来判断是否支持,获得支持的HandlerAdapter后直接返回
protected HandlerAdapter getHandlerAdapter(Object handler) {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException(...);
}
Handler的执行
● 从Handler的注册可以看出来,Spring已经获取了我们的处理方法的Class,映射等等信息,之后无论是直接实例化Class,还是根据Class 或者别的去Spring上下文获取到Bean之后使用代理进行处理,在Spring的上下文里面都是很容易可以做到。
● 但是在执行方法前,有一个很重要的问题,就是我们对于参数的匹配方式
反射方法的参数名的获取
直接反射获取方法参数名
@RequestMapping("/test")
public String ks(String name){}
● 我们上述代码肯定是写的很多了,一个普通的@RequestMapping请求,那么问题在哪里呢?
● 像我们在方法上面@RequestMapping注解中的映射,请求方式什么的,我们都知道可以很轻松的通过反射之间获取到,但是我们name,这个属性他是怎么做到的呢
● 一个反射我们去获取一个方法参数的名称的时候,如下面代码所示,我们只能获取到arg0,arg1 …的名称,也就是我们根本获取不到真正的参数名,那么这个就是一个比较有意思的东西了
public class HelloController2 {
public String hello(String hp1,Integer hp2,Boolean hp3){
return "hello";
}
}
Class<HelloController2> helloController2Class = HelloController2.class;
Method[] methods = helloController2Class.getMethods();
for (Method method : methods) {
if (!method.getName().equals("hello")){
continue;
}
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter.getName() + " "+parameter.getType());
}
}
输出:
arg0 class java.lang.String
arg1 class java.lang.Integer
arg2 class java.lang.Boolean
如果获取参数名称
1. -g
● 解释: 生成所有调试信息
● 将参数名存储在 方法局部变量描述 LocalVariableTable 中
public java.lang.String hello(java.lang.String, java.lang.Integer, java.lang.Boolean);
descriptor: (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Boolean;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=4, args_size=4
0: ldc #7 // String hello
2: areturn
LineNumberTable:
line 29: 0
LocalVariableTable:
Start Length Slot Name Signature
0 3 0 this LHelloController2;
0 3 1 hp1 Ljava/lang/String;
0 3 2 hp2 Ljava/lang/Integer;
0 3 3 hp3 Ljava/lang/Boolean;
● 生成方法局部变量描述之后,实际我们还是不能在反射中通过parameter.getName() 来获取属性
● 一般如果我们要获取这个参数名称的话,是需要通过 ASM 字节码操作技术来进行获取
2 . -parameters
● 解释: 生成元数据以用于方法参数的反射,为JDK8中新增的属性
● 在字节码中生成 MethodParameters ,来表示参数名
● JDK8之后可以通过-parameters方式获取,之前只能通过 -g 的方式获取
public java.lang.String hello(java.lang.String, java.lang.Integer, java.lang.Boolean);
descriptor: (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Boolean;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=4, args_size=4
0: ldc #7 // String hello
2: areturn
LineNumberTable:
line 29: 0
MethodParameters:
Name Flags
hp1
hp2
hp3
● 可以直接通过parameter.getName()的方式获取
● 这样编译的通过 parameter.isNamePresent() 返回的值为true
PrioritizedParameterNameDiscoverer#getParameterNames
● 这里从一堆参数解析器里面进行解析
public String[] getParameterNames(Method method) {
for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
String[] result = pnd.getParameterNames(method);
if (result != null) {
return result;
}
}
return null;
}
● 可以看到,有两种解析起,一种反射解析,一种通过LocalVariableTable进行解析
反射解析
StandardReflectionParameterNameDiscoverer#getParameterNames
@Nullable
private String[] getParameterNames(Parameter[] parameters) {
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
if (!param.isNamePresent()) {
return null;
}
parameterNames[i] = param.getName();
}
return parameterNames;
}
字节码解析
LocalVariableTableParameterNameDiscoverer#getParameterNames
public String[] getParameterNames(Method method) {
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
return doGetParameterNames(originalMethod);
}
private String[] doGetParameterNames(Executable executable) {
Class<?> declaringClass = executable.getDeclaringClass();
Map<Executable, String[]> map =
this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass);
return (map != NO_DEBUG_INFO_MAP ? map.get(executable) : null);
}
● 字节码获取核心代码
● 基本流程就是通过class获取到字节码流,然后通过这个流创建一个asm的ClassReader对象
● 在构建一个命令,在classReader中读取LocalVariableTable 属性数据
private Map<Executable, String[]> inspectClass(Class<?> clazz) {
InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
…
try {
ClassReader classReader = new ClassReader(is);
Map<Executable, String[]> map = new ConcurrentHashMap<>(32);
classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);
return map;
}
…
return NO_DEBUG_INFO_MAP;
}
参数解析都不行的话
```c
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
if (info.name.isEmpty()) {
name = parameter.getParameterName();
if (name == null) {
throw new IllegalArgumentException(
"Name for argument of type [" + parameter.getNestedParameterType().getName() +
"] not specified, and parameter name information not found in class file either.");
}
}
...
}
● 会抛出IllegalArgumentException 异常
自行实现一个MVC架构
看了上面那么多,我们大概就可以简单的实现一个基于前端控制器的MVC架构了
主要实现流程
1. 手动启动tomcat
2. 定义 @RequestMapping @RestController 注解
3. 扫描 @RequestMapping @RestController 注解类,解析映射后,注册到HandlerManger中
4. 定义拦截器 @Interceptor 注解
5. 扫描 @Interceptor 注解 ,注解到HandlerManger中
6. 然后定义统一处理servlet DispatcherServlet ,其处理流程主要是
a. 在HandlerManger中寻找对应映射的Handler
b. 寻找复合的拦截器
c. 返回Handler,解析请求参数,执行Handler,返回数据
一个简单的前端控制器实现的MVC模式就是这样了
虽然Spring的MVC非常复杂,但是其实本质原理是很简单的,
最主要复杂的内容,是,他支持了很多种实现方式,所以他使用了适配器,命令模式,等等来实现
还有映射的匹配等等
还有就是对于参数获取的方法,
自定义参数处理 加密,特殊字段