springmvc注解版本--初识--12(代码片段)

大忽悠爱忽悠 大忽悠爱忽悠     2022-10-22     198

关键词:

Spring MVC注解版本--初识--12


初识基于注解的Controller

在Spring MVC框架中,传统的Handler类型,比如Controller或者ThrowawayController,都需 要具体实现类继承某个基类或者实现某个接口。而使用基于注解的Controller的话,则没有这样的限制。实际上,基于注解的controller就是一个普通的POJO,只是使用某些注解附加了一些相关的元数据信息而已。下面是某个基于注解的Controller实现:

@Controller
public class HelloController 
    @RequestMapping("/hello")
    public Stu sayHello(@RequestParam("name") String name, @RequestParam("age") Integer age)
        return new Stu(name,age);
    

可以看到,我们的controller实现类HelloController 无拘无束,不需要实现任 何强制接口类型,或者去继承哪个父类。最主要的是也不需要去依赖Servlet API,甚至Spring MVC相关的API。HelloController 只作为一个普通的POJO而存在。不过,应用程序中类似 的POJO到处可见,如果不能通过某种方式加以区分,Spring MVC显然无法知道,到底哪个POJO才是用于Web请求处理的Controller实现类。纵使要“大海捞针”,我们也得知道“针”到底是个什么样子不是?

传统的Controller或者ThrowawayController通过接口类型作为标志的方式,基于注解的Controller则采用标注于具体实现类上的某种类型的注解作为标志。在HelloController类的定义中,我们使用@Controller和@RequestMapping两种类型的注解来标注该类,以告知Spring MVC框架HelloController 可以作为处理某一Web请求的Controller实现类。

原则上来说, 只需要在webApplicationContext中添加如下配置:

<context:component-scan base-package="your.controller.package"/>

然后将各种基于注解的controller实现类定义在your.controller.package下即可。剩下的事情,像如何获取并调用这些基于注解的Controller等,就由Spring MVC框架帮我们全部揽下来了。

对于/hello形式的请求,Spring MVC将使用HelloController作为处理请求的Handler,并调用其标注了@RequestMapping的sayHello方法进行当前 Web请求的处理。

怎么样?

较之传统的Handler类型,使用基于注解的Controller是不是看起来要简洁得多?

基本上,只要在指定的包下面定义用于处理Web请求的Handler对象(任何你我喜欢的对象类型),然后使用指定的注解类型标注它们就行,完全省却了在Java文件与XML配置文件之间切换的烦恼。

最主要的是,这些基于注解的Controller甚至不依赖于Servlet API。而且,你或许还能找出更多喜爱它的理由。不得不承认,对于日常开发来说,事情确实简单了不少。但是,难道你就不想一探基于注解的Controller之下到底隐藏着什么样的秘密吗?

如果没有Spring MVC框架幕后的某种支持,你想啊,孤伶伶的一个使用某种注解类型标注的POJO,又能够发挥什么样的作用呢?


基于注解的Controller原型分析

在我们完全明白Spring MVC框架的整个结构之后,添加个基于注解的Controller已经不再是什么高难度动作了。对于Spring MVC框架来说,基于注解的Controller和传统的Controller或者 ThrowawayController在本质上并没什么区别,它们全都是框架内用于处理Web请求的Handler。如果 我们可以达成这样的共识,那么理解基于注解的Controller的运作机制就并非难事了。

基于注解的controller是Spring 2.5之后才出现的Handler形式。我们不妨先让自己回到“史前文明”,看一下要在Spring 2.5之前版本的Spring MVC中使用基于注解的Controller需要做哪些工作,问题实际上就简化为,如何实现自定义的Handler类型,对吗?

显然,基于注解的Controller这种类型的Handler定义我们已经有了,只是一些添加了某种类型注解标注的POJO而已,下面要做的是要搞清楚如下两个问题。

  • 如何让Spring MVC框架类(其实就是DispatcherServlet)知道当前Web请求应该由哪个基于注解的Controller处理?
  • 如何让Spring MVC框架类知道调用基于注解的Controller的哪个方法来处理具体的Web请求?

用我们的行话来说就是,我们需要为基于注解的Controller提供相应的HandlerMapping以处理Web请求到Handler的映射关系,需要提供相应的HandlerAdaptor以调用并执行自定义handler的 处理逻辑。


自定义用于基于注解的Controller的HandlerMapping

在基于注解的Controller中,当前实现类将用于哪个Web请求的处理是由相应的注解标注的。

比如,@RequestMapping(“/hello”)标注的HelloController将 用于URL为"helloController"的Web请求处理。这些注解中的信息,我们需要通过Java 的反射机制(Java Reflection)来读取。

无论是现有的BeanNameUrlHandlerMapping还是SimpleUrlHandlerMapping,显然都没有提供通过反射读取注解中映射信息的功能。

所以,我们不得不为基于注解的Controller提供特定的HandlerMapping实现。

要实现一个专门用于基于注解的Controller的HandlerMapping实现,从原理上来说并不困难。我们所要作的只是遍历所有可用的基于注解的Controller实现类,然后根据请求的路径信息,与实现类中的注解所标注的请求处理映射信息进行比对。

如果当前基于注解的Controller实现类的注解所包含的信息与请求的路径信息相匹配,那么就返回当前这一基于注解的Controller实现类即可。

根据注解新提供Web请求映射功能的HandlerMapping原型代码示例:

public class AnnotationBaseHandlerMapping implements HandlerMapping 
    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception 
        HandlerExecutionChain chain=null;

        Object[] annoControllers=getAvailableAnnotationControllers();
        for (Object annoController : annoControllers) 
            Class<?> annoClass = annoController.getClass();
            if(annoClass.isAnnotationPresent(RequestMapping.class))
                RequestMapping mapping = annoClass.getAnnotation(RequestMapping.class);
                if(match(mapping,request))
                    chain=new HandlerExecutionChain(annoController);
                    if(!CollectionUtils.isEmpty(getHandlerInterceptors()))
                        chain.addInterceptors(getHandlerInterceptors().toArray());
                        break;
                    
                
            ;
        
        return chain;
    

在HandlerMapping接口必须实现的getHandler(…)方法中,我们首先遍历了所有可用的基于注解的Controller,然后通过反射获取@RequestMapping的相应信息与请求信息进行匹配,并最终返回匹配后的结果。这一总体逻辑很好理解,我们可能存在疑问的地方是后面的两个辅助方法,即getAvailableAnnotaitionControllers () 和matches (…)

getAvailableAnnotaitionControllers ()方法用于获取所有基于注解的controller实现类。如果你愿意,当然可以把所有的这些标注了注解的Controller实现类添加到webApplicationContext,然后注入给我们的AnnotationBasedHandlerMapping,并在getAvailableAnnotationControllers ()方法中直接返回。

不过,使用基于注解的Controller的一个优势就在于,不需要在webApplicationcontext中添加任何配置。所以,即使能这么做,我们也不应如此。不妨将这个问题暂且,搁下,稍后我们再来揭示Spring 2.5中是如何处理它的,目前我们只要了解这个方法是做什么的就可以。

至于matches(…)方法,其实现逻辑完全就是一个获取@RequestMapping所包含的信息,然后与当前请求进行对比的过程,这可以借助于某些框架类进行匹配,比如org.springframework.util.PathMatcher和org.springframework.web.util.UrlPathHelper等。当然,如果你不辞辛苦,完全凭一己之力去写所有匹配代码来完成这一工作也是可以的。

现在,只要将我们的AnnotationBasedHandlerMapping添加到DispatcherServlet的字距WebApplicationContext,理论上它就能正确处理Web请求到具体基于注解的Controller之间的映射关系。但原型终归是原型,它只是帮助我们简化问题的难度,从而了解一件事情本质上的东西。要应用于实际环境还有许多路要走,好在,这段路已经有人走过了。


官方提供用于处理注解的HandlerMapping

实际上,正如我们所预料的那样,Spring 2.5中基于注解的Controller确实依赖于某一个官方的HandlerMapping实现来处理映射关系,那就是DefaultAnnotationHandlerMapping。

DefaultAnnotationHandlerMapping在实现原理上与 我们的AnnotationBasedHandlerMapping原型相似,它会首先扫描应用程序的Classpath,通过反射 获取所有标注了@Controller的对象,之后就可以像AnnotationBasedHandlerMapping所展示的 那样来完成一个HandlerMapping的职责了。

至于扫描Classpath然后获取标注了@Controller的对象 这样的工作,是由<context:component-scan/>来完成的。这也就是为什么需要我们的基于注解的Controller实现类必须标注@Controller类型的这一注解的原因。

因为<context:component- scan/>内置了对@Controller的支持,但并不一定内置了其他你所需要的注解类型的支持。如果你不想使用@Controller,那么就得自己实现扫描classpath并获取相应controller对象的逻辑。

不过 那又何苦呢?当然,不管你最终如何选择,我想,看到这里,如何实现原型中的getAvailableAnnotationControllers()方法你已经心中有数了吧?


注意

DefaultAnnotationHandlerMapping实现逻辑要比AnnotationBasedHandlerMapping原型复杂得多。随着后面对基于注解的Controller特性的深入,我们就会认识到这一点。

在2.5版本的Spring MVC中,DefaultAnnotationHandlerMapping将在DispatcherServlet初始化的时候就被默认启用(随同一起的还有BeanNameUr1HandlerMapping)。除非想要定制它的某些行为,大多情况下,并不需要在DispatcherServlet的webApplicationContext中明确地声明它。

在Spring 5.+版本中,DispathcerServlet默认启用的HandlerMapping有如下几个:


自定义用于基于注解的Controller的HandlerAdaptor

有了针对于基于注解的Controller的HandlerMapping实现,只是完成了一半的工作,要让基于注解的Controller真正工作起来,我们还需要完成剩下的那一半,即实现针对基于注解的Controller的自定义HandlerAdaptor。

HandlerMapping返回了处理Web请求的某个基于注解的controller实例,而DispatcherServlet并不知道应该去调用该实例的哪个方法来处理Web请求。为了能够以统一的方式调用各种类型的Handler,DispatcherServlet需要一个针对基于注解的Controller的HandlerAdaptor实现。 这跟“传统的Controller需要一个SimpleControllerHandlerAdapter,ThrowawayController需 要一个ThrowawayControllerHandlerAdapter”是一个道理。

为了构建一个针对基于注解的Controller的HandlerAdaptor原型实现,我们得回头看一下基于注解的Controller所定义的Web请求处理方法有何特征。我们知道,对于传统的Controller来说,SimpleControllerHandlerAdapter只需要调用其接口中所定义的handleRequest(…)方法即 可。对于ThrowawayController情况也是相似的,ThrowawayControllerHandlerAdapter知道 execute()方法就是ThrowawayController所定义的Web请求处理方法,而基于注解的Controller 显然无法找到这样的契约关系。不过,我们依然可以找到特定于它的Web请求处理方法,那就是使用@RequestMapping标注的方法定义。(@RequestMapping可以有两种用途,我们稍后详述。)

与DefaultAnnotationHandlerMapping(或者我们的AnnotationBasedHandlerMapping)能够 使用反射获取相应注解的信息这一做法类似,针对基于注解的Controller的HandlerAdaptor原型实现,同样可以如法炮制,只要通过反射查找标注了@RequestMapping的方法定义,然后通过反射调用该方法,并返回DispatcherServlet所需要的ModelAndView即可。有了这样的思路,如下所示的一个HandlerAdaptor原型也就不难构建了。

public class AnnotationControllerHandlerAdaptor implements HandlerAdapter 
    @Override
    public boolean supports(Object handler) 
        Class<?> clazz = handler.getClass();
        return clazz.isAnnotationPresent(Controller.class) ||
                clazz.isAnnotationPresent(RequestMapping.class);
    

    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        Method[] methods = handler.getClass().getDeclaredMethods();
        for (Method method : methods) 
            if(method.isAnnotationPresent(RequestMapping.class))
                ModelAndView mav = invokeAndReturn(method, handler, request);
                return mav;
            
        
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return null;
    

    private ModelAndView invokeAndReturn(Method method, Object handler, HttpServletRequest request) 
        //1.使用DataBinder或者其他装备将request参数绑定到方法参数
        Object[] parameterValues=bind(request,method);
        //2.使用绑定后获得的相应参数调用方法
        Object ret=method.invoke(handler,parameterValues);
        ModelAndView mav = new ModelAndView();
        if(ret instanceof String)
            mav.setViewName((String) ret);
        else  if(ret instanceof ModelMap)
            mav.addAllObjects((Map<String, ?>) ret);
        else
        ...;
        return mav;
    

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


实际上,一个真正用于生产环境下,对应基于注解的Controller的HandlerAdaptor实现类要考虑的事情很多。而我们的原型实现因为要将问题简化,所以并没涉及,如下所述。

如何根据@RequestMapping提供的各种信息来决定是否调用当前方法?

  • 我们的原型只要发现当前方法标注有@RequestMapping,即认为它就是将被调用的Web请求处理方法,那如果
    @RequestMapping指定了metethod=ReequestMethod.POST),我们同样可能在Web请求以GET形式发送的时候调用当前方法,这显然是违反@RequestMapping语义的。

如何在数据绑定期间决定将哪个请求参数绑定到方法的哪个参数上?

  • 通过现有的反射API可以获取当前方法的参数类型,但无法获取方法参数的名称,单靠反射API显然无法识别请求参数到方法参数一对一的映射关系,也就无法实现正确的数据绑定。为了解决这个问题,Spring 2.5在实现类似的功能的时候,使用了ObjectWeb的ASM类库帮助解决方法参数名称的获取问题。
  • 方法参数同样可能持有相应的注解,显然需要为这些标注有相应注解的方法参数的处理提供更多的分支逻辑,以囊括所有可能的情况,比如,如果指定Web请求参数中的author需要绑定到处理方法的name方法参数上去,我们可能需要定义处理方法如下:
public String processMethod(@RequestParam("author")String name,...)
...

这种情况下,我们不能依赖默认的请求参数与方法参数名称匹配的原则进行数据绑定,而应该以@RequestParam所指定的绑定原则为准。这就需要我们添加更多的代码逻辑支持。

  • 如果基于注解的Controller中某些数据需要通过HttpSession进行管理,而基于注解的Controller又不依赖于任何ServletAPI,该如何在HandlerAdaptor中提供代码逻辑支持呢?
  • 基于注解的Controller的请求处理方法返回值有没有限制?如果有,可以定义哪些类型?如果没有,HandlerAdaptor要如何枚举所有可能的返回值类型呢?我们的原型中只是考虑了两种类型。如果有其他的类型,难道要继续枚举未知的类型?在Spring 2.5的基于注解的Controller中,处理方法的返回值类型只能有规定的几种,所以,这个问题选择了比较简单的解决方案。
  • 如果基于注解的Controller需要访问模型数据或者返回某些模型数据,我们的HandlerAdaptor可以通过哪些方式提供支持?是让具体的处理方法通过返回值将模型参数返回,然后由HandlerAdaptor添加到要返回给DispatcherServlet使用的ModelAndView中,还是由 HandlerAdaptor实现为基于注解的Controller传递一个能够进行模型数据访问的对象引用?这显然也是需要考虑的。

说了这么多,实际上就一个目的,实现一个针对基于注解的Controller的HandlerAdaptor的目的是明确的,那就是将请求信息绑定到具体的controller实例,然后调用相应的处理方法,并将返回结果以ModelAndView的形式返回给DispatcherServlet使用。但是,要实现这样一个 HandlerAdaptor的工作量和要考虑的关注点是很多的。所以,理解原理就好。如果要使用这样一个HandlerAdaptor实现类,还是使用Spring 2.5提供的官方支持吧!

Spring 2.5中为基于注解的Controller提供的HandlerAdaptor实现类是AnnotationMethodHandlerAdapter。该类为我们考虑并 实现了以上提到的几乎所有问题,甚至还要更全面。默认情况下,2.5版本的DispatcherServlet将在初始化的时候就实例化了一个AnnotationMethodHandlerAdapter,用于支持基于注解的 Controller。我想,如果没有对AnnotationMethodHandlerAdapter的定制需求的话,通常就不用在DispatcherServlet的webApplicationContext中明确地声明一个该类的bean定义了。


在Spring 5.+版本中,DispathcerServlet默认启用的HandlerAdapter有如下几个:


springmvc原理剖析(代码片段)

初识SpringMVC概述SpringMVC原理剖析回顾Servlet原生态SpringMVC使用注解实现SpringMVC结尾概述笔者这段时间在学习SpringMVC,于是突发奇想想写一篇博客来记录学习的过程,希望自己巩固知识的同时,也能帮助到别人。SpringMVC... 查看详情

java-初识注解annotation(代码片段)

直接通过代码简单了解一下: 1packagecom.etc;23importjava.lang.annotation.ElementType;4importjava.lang.annotation.Retention;5importjava.lang.annotation.RetentionPolicy;6importjava.lang.annotation.Target;7import 查看详情

springmvc:springmvc的requestmapping注解(代码片段)

文章目录SpringMVC的RequestMapping注解一、@RequestMapping控制请求方式二、@RequestMapping控制请求参数params和请求头headers三、@PathVariable注解和RESTful风格的支持1、配置hiddenHttpMethodFilter2、准备Controller层代码3、准备页面代码SpringMV... 查看详情

springmvc:springmvc的常见注解(代码片段)

文章目录SpringMVC的常见注解一、@RequestMapping二、@RequestParam三、@PathVariable四、@RequestHeader(了解)五、@CookieValue(了解)SpringMVC的常见注解一、@RequestMapping作用:用于建立 查看详情

springmvc常用注解超详解(代码片段)

文章目录SpringMVC常用注解超详解✨✨✨1.@RequestMapping注解1.1@RequestMapping注解的功能1.2@RequestMapping注解的位置1.3@RequestMapping注解的value属性1.4@RequestMapping注解的method属性1.5@RequestMapping注解的params属性&# 查看详情

免费下载全套最新006springmvc视频教程+教学资料+学习课件+源代码+软件开发工具

006SpringMVC视频教程网盘地址:链接:https://pan.baidu.com/s/1LbKIui6W2sRZ3Kt-2RsZPw提取码:nvre加公众号获取更多新教程教程目录大纲./006SpringMVC├──10.SpringMVC_RequestHeader注解.zip├──11.SpringMVC_CookieValue注解.zip├──12.SpringMVC_使用POJO作为... 查看详情

springmvc的常用注解(代码片段)

SpringMVC的常用注解前言SpringMVC框架为开发者提供了功能强大的注解机制,可以帮助我们简化代码的开发,提高开发效率,同时使得程序具备更好的扩展性,这一讲就来详细讲解SpringMVC框架中常用注解的具体使用。&... 查看详情

springmvc学习笔记-01初识(代码片段)

文章目录1什么叫MVC2SpringMVC2.1初始2.2核心组件2.3DisPatcherServlet源码分析2.4工作流程1什么叫MVC​mvc是一种架构模式,视图层只去管理页面,model层只去管理业务逻辑,控制层就像是一个媒婆,来关联view层和model,... 查看详情

springmvc@requestmapping注解属性(代码片段)

...用及用法示例代码依赖版本如下SpringBoot:3.0.0-SNAPSHOTSpringMVC:6.0.0-SNAPSHOTserver.port=81811.源代码@Target(ElementType.TYPE,ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documented@Mapping@Reflective(ControllerMappingReflectiveProcess... 查看详情

springmvc—“@requestmapping注解及其属性”(代码片段)

SpringMVC—"@RequestMapping注解"一、@RequestMapping注解的功能二、@RequestMapping注解的位置三、@RequestMapping注解的相关属性(1)value属性(2)method属性(3)params属性ÿ 查看详情

springmvc注解方式与文件上传(代码片段)

目录:springmvc的注解方式文件上传(上传图片,并显示)一、注解在类前面加上@Controller表示该类是一个控制器在方法handleRequest前面加上@RequestMapping("/index")表示路径/index会映射到该方法上将上一篇的博客改为注解方式:SpringMVC... 查看详情

分享知识-快乐自己:初识hibernate概念片

1):什么是Hibernate?  Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序... 查看详情

springmvc基于注解的controller(代码片段)

SpringMVC基于注解的Controller基于注解的控制器与传统风格的控制器的区别主要有两点。1-传统风格的控制器需要在SpringMVC配置文件中配置请求与控制器类的映射关系,而基于注解的控制器不需要在SpringMVC配置文件中部署映射... 查看详情

使用注解方式搭建springmvc(代码片段)

1.以前搭建SpringMVC框架一般都使用配置文件的方式进行,相对比较繁琐。spring提供了使用注解方式搭建SpringMVC框架的方式,方便简洁。使用SpringIOC作为根容器管理service、dao、datasource,使用springMVC容器作为子容器管理controller、视... 查看详情

springmvc中的注解(代码片段)

SpringMVC中的注解文章目录SpringMVC中的注解@RequestMapping注解@RequestMapping中的value属性@RequestMapping中的method属性派生类@PathVariable注解@RequestParam注解@RequestMapping注解@RequestMapping中的value属性@RequestMapping:既... 查看详情

springmvc@requestmapping注解属性(代码片段)

...用及用法示例代码依赖版本如下SpringBoot:3.0.0-SNAPSHOTSpringMVC:6.0.0-SNAPSHOT1.源码@Target(ElementType.TYPE,ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documented@Mapping@Reflective(ControllerMappingReflectiveProcessor.class)public@int... 查看详情

注解版springmvc(代码片段)

注解版SpringMVC<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</incl 查看详情

springmvc注解详解(代码片段)

1、@Controller在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。在SpringMVC 中... 查看详情