本篇文章将带你真正的搞定SpringMVC工作原理
创始人
2025-07-14 04:11:00
0

环境:Spring5.3.23

1. 简介

在Spring中要定义一个接口是非常的简单,如下示例:

@RestController
@RequestMapping("/demos")
public class DemoController {
  @GetMapping("/index")
  public Object index() {
    return "index" ;
  }
}

通过上面的@RestController, @RequestMapping就完成了一个简单的接口定义。

实际Spring Web底层是做了很多的工作,其核心组件有HandlerMapping, HandlerAdapter, ViewResolver等组件。

  • HandlerMapping
    根据当前请求的URI,查找对应的Handler,如:HandlerExecutionChain,包装的HandlerMethod
  • HandlerAdapter
    根据上面的确定的HandlerMethod, 找到能够处理该Handler的Adapter,进行调用
  • ViewResolver
    如果返回的ModelAndView对象那么会通过相应的ViewResolver进行渲染输出

了解了上面的几个核心组件之后,接下来就是自定义实现上面的核心类,来完成接口的请求处理。

2. 实战案例

2.1 自定义Endpoint

自定义@PackEndpoint注解,该注解的功能就类似@Controller标记这个类是一个处理器类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PackEndpoint {}

参数注解,该注解的作用就类似@RequestParam

@Target(ElementType.PARAMETER)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface PackParam {
 }

2.2 参数封装对象

该类的作用用来保存方法的参数相关的信息进行封装,如:参数名称,参数的类型及对应的方法Method。

public class PackMethodParameter {
  // 用来解析接口参数的名称
  private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer() ;
  private String name ;
  private Executable executable ;
  private int parameterIndex ;
  private Class type ;


  public PackMethodParameter(String name, int parameterIndex, Executable executable) {
    this.name = name;
    this.parameterIndex = parameterIndex ;
    this.executable = executable ;
  }


  public PackMethodParameter(int parameterIndex, Executable executable, Class type) {
    this.parameterIndex = parameterIndex ;
    this.executable = executable ;
    this.type = type ;
  }


  public boolean hasParameterAnnotation(Class clazz) {
    Method method = (Method) this.executable ;
    Parameter[] parameters = method.getParameters() ;
    return parameters[this.parameterIndex].isAnnotationPresent(clazz) ;
  }


  public String getParameterName() {
    String[] parameterNames = parameterNameDiscoverer.getParameterNames((Method) this.executable) ;
    return parameterNames[this.parameterIndex] ;
  }


}

2.3 自定义HandlerMapping

自定义实现了SpringMVC标准的HandlerMapping,这样在DispatcherServlet中才能够识别。HandlerMapping的作用就是用来匹配一个请求的URI与那个处理器(Controller)进行对应。

public class PackHandlerMapping implements HandlerMapping, InitializingBean, ApplicationContextAware {


  private ApplicationContext context;
  private Map mapping = new HashMap<>();


  @Override
  public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    String requestPath = request.getRequestURI();
    Optional opt = mapping.entrySet().stream().filter(entry -> entry.getKey().equals(requestPath)).findFirst()
        .map(Map.Entry::getValue);
    if (opt.isPresent()) {
      HandlerExecutionChain executionChain = new HandlerExecutionChain(opt.get()) ;
      return executionChain ;
    }
    return null;
  }


  // Bean初始化时,从容器中查找所有符合条件的Bean对象,即Bean对象上有@PackEndpoint注解
  @Override
  public void afterPropertiesSet() throws Exception {
    String[] beanNames = context.getBeanNamesForType(Object.class) ;
    for (String beanName : beanNames) {
      Object bean = this.context.getBean(beanName) ;
      Class clazz = bean.getClass() ;
      // 判断当前的Bean上是否有PackEndpoint注解,只对有该注解的类进行处理
      if (clazz.getAnnotation(PackEndpoint.class) != null) {
        RequestMapping clazzMapping = clazz.getAnnotation(RequestMapping.class) ;
        String rootPath = clazzMapping.value()[0] ;
        if (clazzMapping != null) {
          // 遍历当前类中的所有方法,查找使用了@RequestMapping注解的方法
          ReflectionUtils.doWithMethods(clazz, method -> {
            RequestMapping nestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class) ;
            if (nestMapping != null) {
              String nestPath = nestMapping.value()[0] ;
              String path = rootPath + nestPath ;
              // 所有信息都封装到该对象
              PackMethodHandler handler = new PackMethodHandler(method, bean) ;
              // 将请求的URI及对应的Handler进行对应,这样就可以通过请求的URI找到对应的处理器方法了
              mapping.put(path, handler) ;
            }
          }) ;
        }
      }
    }
  }
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context = applicationContext;
  }
  // 该类的作用:用来记录接口对应的信息,方法,对应的实例,参数信息
  public static class PackMethodHandler {
    private Method method;
    private Object instance;
    private PackMethodParameter[] parameters ;
    public Method getMethod() {
      return method;
    }
    public void setMethod(Method method) {
      this.method = method;
    }
    public Object getInstance() {
      return instance;
    }
    public void setInstance(Object instance) {
      this.instance = instance;
    }
    public PackMethodHandler(Method method, Object instance) {
      super();
      this.method = method;
      this.instance = instance;
      Parameter[] params = method.getParameters() ;
      this.parameters = new PackMethodParameter[params.length] ;
      for (int i = 0; i < params.length; i++) {
        this.parameters[i] = new PackMethodParameter(i, method, params[i].getType()) ;
      }
    }
    public PackMethodParameter[] getParameter() {
      return this.parameters ;
    }
  }
}

2.4 自定义参数解析器

参数解析器的作用就是用来解析通过请求URI找到对应的处理器方法(PackMethodHandler)。也就是从上面PackHandlerMapping类中保存到Map集合中的通过请求的URI找到对应的PackMethodHandler对象。

public interface PackHandlerMethodArgumentResolver {
  boolean supportsParameter(PackMethodParameter methodParameter) ;
  Object resolveArgument(PackMethodParameter methodParameter, HttpServletRequest request);
}
public class PackParamHandlerMethodArgumentResolver implements PackHandlerMethodArgumentResolver {


  @Override
  public boolean supportsParameter(PackMethodParameter methodParameter) {
    return methodParameter.hasParameterAnnotation(PackParam.class) ;
  }


  @Override
  public Object resolveArgument(PackMethodParameter methodParameter, HttpServletRequest request) {
    String name = methodParameter.getParameterName() ;
    Object arg = null;
    String[] parameterValues = request.getParameterValues(name) ;
    if (parameterValues != null) {
      arg = parameterValues.length == 1 ? parameterValues[0] : parameterValues ;
    }
    return arg ;
  }


}

2.5 自定义HandlerAdapter

自定义实现了SpringMVC标准的HandlerAdatper,这样在DispatcherServlet中才能够识别。该类的核心就是用来真正的调用目标方法的(PackMethodHandler)。

public class PackHandlerAdapter implements HandlerAdapter{


  @Resource
  private ConversionService conversionService ;


  private PackParamHandlerMethodArgumentResolver argumentResolver = new PackParamHandlerMethodArgumentResolver() ;


  @Override
  public boolean supports(Object handler) {
    return handler instanceof PackMethodHandler;
  }


  @Override
  public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    PackMethodHandler methodHandler = (PackMethodHandler) handler ;


    PackMethodParameter[] parameters = methodHandler.getParameter() ;
    Object[] args = new Object[parameters.length] ;
    for (int i = 0; i < args.length; i++) {
      if (this.argumentResolver.supportsParameter(parameters[i])) {
        // 解析对应的方法参数
        args[i] = this.argumentResolver.resolveArgument(parameters[i], request) ;
        // 类型转换
        args[i] = this.conversionService.convert(args[i], parameters[i].getType()) ;
      }
    }
    // 调用目标方法
    Object result = methodHandler.getMethod().invoke(methodHandler.getInstance(), args) ;
    // 设置响应header,输出内容
    response.setHeader("Content-Type", "text/plain;charset=utf8") ;
    PrintWriter out = response.getWriter() ;
    out.write((String) result) ;
    out.flush() ;
    out.close() ; 
    return null ;
  }


  @Override
  public long getLastModified(HttpServletRequest request, Object handler) {
    return -1 ;
  }


}

2.6 测试

@PackEndpoint
@RequestMapping("/users")
public class UserController {
  
  @GetMapping("/index")
  public Object index(@PackParam Long id, @PackParam String name) {
    return "id = " + id + ", name = " + name ;
  }
}

通过以上的步骤就完成了一个完全自定义SpringMVC核心组件的实现。而这就是底层SpringMVC的核心工作原理。

以上是本篇文章的全部内容,希望对你有帮助。

相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...
德国电信门户网站可实时显示全球... 德国电信周三推出一个门户网站,直观地实时提供其安装在全球各地的传感器网络检测到的网络攻击状况。该网站...
着眼MAC地址,解救无法享受D... 在安装了DHCP服务器的局域网环境中,每一台工作站在上网之前,都要先从DHCP服务器那里享受到地址动...
为啥国人偏爱 Mybatis,... 关于 SQL 和 ORM 的争论,永远都不会终止,我也一直在思考这个问题。昨天又跟群里的小伙伴进行...