【小家Spring】Spring MVC容器的web九大组件之---HandlerMapping源码详解(二)---RequestMappingHandlerMapping系列(下)

简介: 【小家Spring】Spring MVC容器的web九大组件之---HandlerMapping源码详解(二)---RequestMappingHandlerMapping系列(下)

下面就介绍Spring MVC目前的唯一构造方案:通过@RequestMapping来构造一个RequestMappingInfo


RequestMappingHandlerMapping 唯一实现类


根据@RequestMapping注解生成RequestMappingInfo,同时提供isHandler实现。


直到这个具体实现类,才与具体的实现方式@RequestMapping做了强绑定了


有了三层抽象的实现,其实留给本类需要实现的功能已经不是非常的多了~

// @since 3.1  Spring3.1才提供的这种注解扫描的方式的支持~~~  它也实现了MatchableHandlerMapping分支的接口
// EmbeddedValueResolverAware接口:说明要支持解析Spring的表达式~
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
    implements MatchableHandlerMapping, EmbeddedValueResolverAware {
  ...
  private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
  // 配置要应用于控制器方法的路径前缀
  // @since 5.1:Spring5.1才出来的新特性,其实有时候还是很好的使的  下面给出使用的Demo
  // 前缀用于enrich每个@RequestMapping方法的映射,至于匹不匹配由Predicate来决定  有种前缀分类的效果~~~~
  // 推荐使用Spring5.1提供的类:org.springframework.web.method.HandlerTypePredicate
  public void setPathPrefixes(Map<String, Predicate<Class<?>>> prefixes) {
    this.pathPrefixes = Collections.unmodifiableMap(new LinkedHashMap<>(prefixes));
  }
  // @since 5.1   注意pathPrefixes是只读的~~~因为上面Collections.unmodifiableMap了  有可能只是个空Map
  public Map<String, Predicate<Class<?>>> getPathPrefixes() {
    return this.pathPrefixes;
  }
  public void setUseRegisteredSuffixPatternMatch(boolean useRegisteredSuffixPatternMatch) {
    this.useRegisteredSuffixPatternMatch = useRegisteredSuffixPatternMatch;
    this.useSuffixPatternMatch = (useRegisteredSuffixPatternMatch || this.useSuffixPatternMatch);
  }
  // If enabled a method mapped to "/users" also matches to "/users/".
  public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
    this.useTrailingSlashMatch = useTrailingSlashMatch;
  }
  @Override
  public void afterPropertiesSet() {
    // 对RequestMappingInfo的配置进行初始化  赋值
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper()); // 设置urlPathHelper默认为UrlPathHelper.class
    this.config.setPathMatcher(getPathMatcher()); //默认为AntPathMatcher,路径匹配校验器
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); // 是否支持后缀补充,默认为true
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); // 是否添加"/"后缀,默认为true
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); // 是否采用mediaType匹配模式,比如.json/.xml模式的匹配,默认为false      
    this.config.setContentNegotiationManager(getContentNegotiationManager()); //mediaType处理类:ContentNegotiationManager
    // 此处 必须还是要调用父类的方法的
    super.afterPropertiesSet();
  }
  ...
  // 判断该类,是否是一个handler(此处就体现出@Controller注解的特殊性了)
  // 这也是为何我们的XXXController用@Bean申明是无效的原因(前提是类上木有@RequestMapping注解,否则也是阔仪的哦~~~)
  // 因此我个人建议:为了普适性,类上的@RequestMapping也统一要求加上,即使你不写@Value也木关系,这样是最好的
  @Override
  protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
        AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
  }
  // 还记得父类:AbstractHandlerMethodMapping#detectHandlerMethods的时候,回去该类里面找所有的指定的方法
  // 而什么叫指定的呢?就是靠这个来判定方法是否符合条件的~~~~~
  @Override
  @Nullable
  protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // 第一步:先拿到方法上的info
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
      // 方法上有。在第二步:拿到类上的info
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if (typeInfo != null) {
        // 倘若类上面也有,那就combine把两者结合
        // combile的逻辑基如下:
        // names:name1+#+name2
        // path:路径拼接起来作为全路径(容错了方法里没有/的情况)
        // method、params、headers:取并集
        // consumes、produces:以方法的为准,没有指定再取类上的
        // custom:谁有取谁的。若都有:那就看custom具体实现的.combine方法去决定把  简单的说就是交给调用者了~~~
        info = typeInfo.combine(info);
      }
      // 在Spring5.1之后还要处理这个前缀匹配~~~
      // 根据这个类,去找看有没有前缀  getPathPrefix():entry.getValue().test(handlerType) = true算是hi匹配上了
      // 备注:也支持${os.name}这样的语法拿值,可以把前缀也写在专门的配置文件里面~~~~
      String prefix = getPathPrefix(handlerType);
      if (prefix != null) {
        // RequestMappingInfo.paths(prefix)  相当于统一在前面加上这个前缀~
        info = RequestMappingInfo.paths(prefix).build().combine(info);
      }
    }
    return info;
  }
  // 根据此方法/类,创建一个RequestMappingInfo
  @Nullable
  private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    // 注意:此处使用的是findMergedAnnotation  这也就是为什么虽然@RequestMapping它并不具有继承的特性,但是你子类仍然有继承的效果的原因~~~~
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    // 请注意:这里进行了区分处理  如果是Class的话  如果是Method的话
    // 这里返回的是一个condition 也就是看看要不要处理这个请求的条件~~~~
    RequestCondition<?> condition = (element instanceof Class ?
        getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    // 这个createRequestMappingInfo就是根据一个@RequestMapping以及一个condition创建一个
    // 显然如果没有找到此注解,这里就返回null了,表面这个方法啥的就不是一个info~~~~
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
  }
  // 他俩都是返回的null。protected方法留给子类复写,子类可以据此自己定义一套自己的规则来限制匹配
  // Provide a custom method-level request condition.
  // 它相当于在Spring MVC默认的规则的基础上,用户还可以自定义条件进行处理~~~~
  @Nullable
  protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
    return null;
  }
  @Nullable
  protected RequestCondition<?> getCustomMethodCondition(Method method) {
    return null;
  }
  // 根据@RequestMapping 创建一个RequestMappingInfo 
  protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
    RequestMappingInfo.Builder builder = RequestMappingInfo
        // 强大的地方在此处:path里竟然还支持/api/v1/${os.name}/hello 这样形式动态的获取值
        // 也就是说URL还可以从配置文件里面读取  Spring考虑很周到啊~~~
        // @GetMapping("/${os.name}/hello") // 支持从配置文件里读取此值  Windows 10
        .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
        .methods(requestMapping.method())
        .params(requestMapping.params())
        .headers(requestMapping.headers())
        .consumes(requestMapping.consumes())
        .produces(requestMapping.produces())
        .mappingName(requestMapping.name());
    // 调用者自定义的条件~~~
    if (customCondition != null) {
      builder.customCondition(customCondition);
    }
    // 注意此处:把当前的config设置进去了~~~~
    return builder.options(this.config).build();
  }
  @Override
  public RequestMatchResult match(HttpServletRequest request, String pattern) { ... }
  // 支持了@CrossOrigin注解  Spring4.2提供的注解
  @Override
  protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { ... }
}


至此RequestMappingHandlerMapping的初始化完成了。pathPrefixes这种配置,可以全局统一配置来控制每个Controller,如常用的/api/v1前缀~

如何配置呢?我给出个示例供给你参考:


@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        //configurer.setUseSuffixPatternMatch(false); //关闭后缀名匹配,关闭最后一个/匹配
        //configurer.setUseTrailingSlashMatch(false);
        // 这样HelloController上的方法自动就会有此前缀了,而别的controller上是不会有的
        // 注意:这是Spring5.1后才支持的新特性
        configurer.addPathPrefix("/api/v1", clazz -> clazz.isAssignableFrom(HelloController.class));
        // 使用Spring提供的HandlerTypePredicate,更加的强大
        HandlerTypePredicate predicate = HandlerTypePredicate.forBasePackage("com.fsx");
        //HandlerTypePredicate predicate = HandlerTypePredicate.forBasePackageClass(HelloController.class);
        //HandlerTypePredicate predicate = HandlerTypePredicate.forAssignableType(...);
        //HandlerTypePredicate predicate = HandlerTypePredicate.forAnnotation(...);
        //HandlerTypePredicate predicate = HandlerTypePredicate.builder()
        //        .basePackage()
        //        .basePackageClass()
        //        .build();
        configurer.addPathPrefix("/api/v2", predicate);
    }
}


细节注意:若添加了两prefix都可以作用在某个Controller上,那么会按照放入的顺序(因为它是LinkedHashMap)以先匹配上的为准,可参考RequestMappingHandlerMapping#getPathPrefix方法~


RequestMappingHandlerMapping 向容器中注册的时候,检测到实现了 InitializingBean接口,容器去执行afterPropertiesSet(),在afterPropertiesSet中完成Controller中完成方法的映射


以上就是Spring MVC在容器启动过程中,完成URL到Handler映射的所有内容~


@RequestMapping属性详解


使用@RequestMapping 来映射URL 到控制器类,或者是到Controller 控制器的处理方法上。

当@RequestMapping 标记在Controller 类上的时候,里面使用@RequestMapping 标记的方法的请求地址都是相对于类上的@RequestMapping 而言的;当Controller 类上没有标记@RequestMapping 注解时,方法上的@RequestMapping 都是绝对路径。


这种绝对路径和相对路径所组合成的最终路径都是相对于根路径“/ ”而言的。


这个注解的属性众多,下面逐个解释一下:


// @since 2.5 用于将Web请求映射到具有灵活方法签名的请求处理类中的方法的注释  Both Spring MVC and `Spring WebFlux` support this annotation
// @Mapping这个注解是@since 3.0  但它目前还只有这个地方使用到了~~~ 我感觉是多余的
@Target({ElementType.METHOD, ElementType.TYPE}) // 能够用到类上和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
  //给这个Mapping取一个名字。若不填写,就用HandlerMethodMappingNamingStrategy去按规则生成
  String name() default "";
  // 路径  数组形式  可以写多个。  一般都是按照Ant风格进行书写~
  @AliasFor("path")
  String[] value() default {};
  @AliasFor("value")
  String[] path() default {};
  // 请求方法:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
  // 显然可以指定多个方法。如果不指定,表示适配所有方法类型~~
  // 同时还有类似的枚举类:org.springframework.http.HttpMethod
  RequestMethod[] method() default {};
  // 指定request中必须包含某些参数值时,才让该方法处理
  // 使用 params 元素,你可以让多个处理方法处理到同一个URL 的请求, 而这些请求的参数是不一样的
  // 如:@RequestMapping(value = "/fetch", params = {"personId=10"} 和 @RequestMapping(value = "/fetch", params = {"personId=20"}
  // 这两个方法都处理请求`/fetch`,但是参数不一样,进入的方法也不一样~~~~
  // 支持!myParam和myParam!=myValue这种~~~
  String[] params() default {};
  // 指定request中必须包含某些指定的header值,才能让该方法处理请求
  // @RequestMapping(value = "/head", headers = {"content-type=text/plain"}
  String[] headers() default {};
  // 指定处理请求request的**提交内容类型**(Content-Type),例如application/json、text/html等
  // 相当于只有指定的这些Content-Type的才处理 
  // @RequestMapping(value = "/cons", consumes = {"application/json", "application/XML"}
  // 不指定表示处理所有~~  取值参见枚举类:org.springframework.http.MediaType
  // 它可以使用!text/plain形如这样非的表达方式
  String[] consumes() default {};
  // 指定返回的内容类型,返回的内容类型必须是request请求头(Accept)中所包含的类型
  // 仅当request请求头中的(Accept)类型中包含该指定类型才返回;
  // 参见枚举类:org.springframework.http.MediaType
  // 它可以使用!text/plain形如这样非的表达方式
  String[] produces() default {};
}


Spring4.3之后提供了组合注解5枚:


@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping


consumes 与 headers 区别


consumes produces params headers四个属性都是用来缩小请求范围。

consumes只能指定 content-Type 的内容类型,但是headers可以指定所有。

所以可以认为:headers是更为强大的(所有需要指定key和value嘛),而consumes和produces是专用的,头的key是固定的,所以只需要写value值即可,使用起来也更加的方便~。


推荐一个类:org.springframework.http.HttpHeaders,它里面有常量:几乎所有的请求头的key,以及我们可以很方便的构建一个HttpHeader,平时可以作为参考使用


Spring MVC默认使用的HandlerMapping是什么?


Spring对这块的设计也是很灵活的,允许你自己配置,也允许你啥都不做使用Spring默认的配置。处理代码在:DispatcherServlet#initHandlerMappings


public class DispatcherServlet extends FrameworkServlet {
  // 为此DispatcherServlet 初始化HandlerMappings
  // 备注:DispatcherServlet是允许你有多个的~~~~
  private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    //detectAllHandlerMappings该属性默认为true,表示会去容器内找所有的HandlerMapping类型的定义信息
    // 若想改为false,请调用它的setDetectAllHandlerMappings() 自行设置值(绝大部分情况下没啥必要)
    if (this.detectAllHandlerMappings) {
      // 这里注意:若你没有标注注解`@EnableWebMvc`,那么这里找的结果是空的
      // 若你标注了此注解,这个注解就会默认向容器内注入两个HandlerMapping:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping
      Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {
        this.handlerMappings = new ArrayList<>(matchingBeans.values());
        // 多个的话 还需要进行一次排序~~~
        AnnotationAwareOrderComparator.sort(this.handlerMappings);
      }
    }
    // 不全部查找,那就只找一个名字为`handlerMapping`的HandlerMapping 实现精准控制
    // 绝大多数情况下  我们并不需要这么做~
    else {
      try {
        HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
        this.handlerMappings = Collections.singletonList(hm);
      } catch (NoSuchBeanDefinitionException ex) {
        // Ignore, we'll add a default HandlerMapping later.
      }
    }
    // 若一个都没找到自定义的,回滚到Spring的兜底策略,它会想容器注册两个:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping
    if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
      // 输出trace日志:表示使用了兜底策略~
      // 兜底策略配置文件:DispatcherServlet.properties
      if (logger.isTraceEnabled()) {
        logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
            "': using default strategies from DispatcherServlet.properties");
      }
    }
  }
}


通过这段代码,我们能够很清晰的看到。绝大部分情况下,我们容器内会有这两个HandlerMapping Bean:

RequestMappingHandlerMapping和BeanNameUrlHandlerMapping

换句话说,默认情况下@RequestMapping和BeanNameUrl的方式都是被支持的~


请注意:使用@EnableWebMvc和不使用它有一个非常非常重要的区别:

使用@EnableWebMvc原来是依托于这个WebMvcConfigurationSupport config类向容器中注入了对应的Bean,所以他们都是交给了Spring管理的(所以你可以@Autowired他们)

但是,但是,但是(重说三),若是走了Spring它自己去读取配置文件走默认值,它的Bean是没有交给Spring管理的,没有交给Spring管理的。它是这样创建的:context.getAutowireCapableBeanFactory().createBean(clazz) 它创建出来的Bean都不会交给Spring管理。

参考博文:【小家Spring】为脱离Spring IOC容器管理的Bean赋能【依赖注入】的能力,并分析原理(借助AutowireCapableBeanFactory赋能)


小插曲:在Spring5以下,DispatcherServlet.properties这个配置文件里写的是这样的:


image.png


相当于最底层默认使用的是DefaultAnnotationHandlerMapping,而在Spring5之后,改成了RequestMappingHandlerMapping。DefaultAnnotationHandlerMapping是Spring2.5用来处理@RequestMapping注解的,自从Spring3.2后已被标记为:@Deprecated


需要注意的是:纯Spring MVC环境下我们都会开启@EnableWebMvc,所有我们实际使用的还是RequestMappingHandlerMapping的。

而在SpringBoot环境下,虽然我们一般不建议标注@EnableWebMvc,但是Boot它默认也会注册RequestMappingHandlerMapping它的。现在Spring5/Boot2以后一切都爽了~~~~


DefaultAnnotationHandlerMapping的一个小坑


在功能上DefaultAnnotationHandlerMapping和RequestMappingHandlerMapping绝大多数是等价的。但是因为DefaultAnnotationHandlerMapping过于古老了,它并不支持像@GetMapping(Spring4.3后提供)这样的组合注解的。 从源码角度理由如下:


比如Handler这么写的:

 
         


DefaultAnnotationHandlerMapping处理代码为:


 
         


值如下:


image.png


发现我们的URL并没有获取到。但是RequestMappingHandlerMapping的获取代码为:


 
         


可以发现使用AnnotatedElementUtils.findMergedAnnotation是支持这个组合注解的。但是AnnotatedElementUtils整个工具类才Spring4.0后才有,而DefaultAnnotationHandlerMapping早在Spring3.2后就被标记为废弃了,因为就无需Spring也就无需继续维护了~~~~


所以若你是纯Spring MVC环境,为确保万无一失,请开启SpringMVC:@EnableWebMvc


备注:若使用非组合注解如@RequestMapping,两者大体一样。但既然人家都废弃了,所以非常不建议再继续使用~~~

其实在Spring5.以后,就直接把这个两个类拿掉了,所以也就没有后顾之忧了。(DispatcherServlet.properties这个配置文件也做了对应的修改)


总结


Spring MVC在启动时会扫描所有的@RequestMapping并封装成对应的RequestMapingInfo。

一个请求过来会与RequestMapingInfo进行逐个比较,找到最适合的那个RequestMapingInfo。


Spring MVC通过HandlerMapping建立起了Url Pattern和Handler的对应关系,这样任何一个URL请求过来时,就可以快速定位一个唯一的Handler,然后交给其进行处理了~

当然这里面还有很多实现细节,其中还有一个非常重要的一块:HandlerAdapter,会在下文继续源码分析,请保持持续关注~


关于定制RequestMappingHandlerMapping,以及自定义RequestCondition来灵活的使用映射器,我推荐可参阅这篇文章:Spring Mvc之定制RequestMappingHandlerMapping

相关文章
|
2月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
2月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
3月前
|
存储 安全 Java
如何在 Spring Web 应用程序中使用 @SessionScope 和 @RequestScope
Spring框架中的`@SessionScope`和`@RequestScope`注解用于管理Web应用中的状态。`@SessionScope`绑定HTTP会话生命周期,适用于用户特定数据,如购物车;`@RequestScope`限定于单个请求,适合无状态、线程安全的操作,如日志记录。合理选择作用域能提升应用性能与可维护性。
179 1
|
9月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
838 0
|
4月前
|
存储 NoSQL Java
探索Spring Boot的函数式Web应用开发
通过这种方式,开发者能以声明式和函数式的编程习惯,构建高效、易测试、并发友好的Web应用,同时也能以较小的学习曲线迅速上手,因为这些概念与Spring Framework其他部分保持一致性。在设计和编码过程中,保持代码的简洁性和高内聚性,有助于维持项目的可管理性,也便于其他开发者阅读和理解。
160 0
|
5月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
377 0
|
5月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
466 0
|
5月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
170 0
|
5月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
282 0
|
前端开发 Java BI
Spring3 Web MVC 集成Jasper Report生成PDF例子
Spring3 Web MVC 集成Jasper Report生成PDF例子
223 7