webapi获取当前http上下文_webapi 从请求上下文获取header - CSDN
精华内容
参与话题
  • ASP.NET WEBAPI 的身份验证和授权

    千次阅读 2018-07-06 10:35:35
    定义身份验证(Authentication):确定用户是谁。授权(Authorization):确定用户能做什么...验证时,宿主会创建一个表示安全上下文的主体对象(实现 IPrincipal),将它附加到当前线程。主体对象包含一个存储用户信...

    定义

    身份验证(Authentication):确定用户是谁。

    授权(Authorization):确定用户能做什么,不能做什么。

    身份验证

    WebApi 假定身份验证发生在宿主程序称中。对于 web-hosting,宿主是 IIS。这种情况下使用 HTTP Module 进行验证。

    验证时,宿主会创建一个表示安全上下文的主体对象(实现 IPrincipal),将它附加到当前线程。主体对象包含一个存储用户信息的 Identity 对象。若验证成功,Identity.IsAuthenticated 属性将返回 true。

    HTTP 消息处理程序(HTTP Message Handler)

    可以用 HTTP 消息处理程序代替宿主进行身份验证。这种情况下,由 HTTP 消息处理程序检查请求并设置主体对象。

    请考虑以下事项决定是否使用消息处理程序进行身份验证:

    • HTTP 模块检查所有经过 asp.net 管道的请求,消息处理程序只检查路由到 WebAPI的请求。
    • 可以为每个路由单独设置消息处理程序。
    • HTTP 模块仅在 IIS 中可用。消息处理程序则与宿主无关,在 web-hosting 和 self-hosting 中均可用。
    • HTTP 模块参与IIS 日志和审计等功能。
    • HTTP模块在管道之前运行,主体在消息处理程序运行之前不会设置,当响应离开 消息处理程序时,主体会恢复成原来的那个。

    一般来说,不需要自承载时,HTTP 模块较好。

    设置主体

    进行自定义身份验证时,应在两个地方设置主体对象:

    • Thread.CurrentPrincipal,这是 .net 中设置线程主体的标准方式。
    • HttpContext.Current.User 这是特定于 ASP.NET 的属性。
    复制代码
    private void SetPrincipal(IPrincipal principal)
    {
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = principal;
        }
    }
    复制代码

    采用 web-hosting 时,必须同时设置两处,避免安全上下文不一致。对于 self-hosting,HttpContext.Current 为 null,所以设置之前应进行检查。

    授权

    授权发生在管道中更接近 controller 的位置。

    • 授权筛选器(Authorization filter)在 action 之前运行。若请求未授权,返回错误,action 不运行。
    • 在 action 内部,可以用 ApiController.User 属性获取主体对象,做进一步的控制。

    [Authorize] 属性

    AuthorizeAttribute 是内置的授权筛选器。用户未通过身份验证时,它返回 HTTP 401 状态码。可以在全局,控制和 action 三个级别应用它。

    在全局级别应用

    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new AuthorizeAttribute());
    }

    在控制器级别应用

    复制代码
    [Authorize]
    public class ValuesController : ApiController
    {
        public HttpResponseMessage Get(int id) { ... }
        public HttpResponseMessage Post() { ... }
    }
    复制代码

    在 Action 级别应用

    复制代码
    public class ValuesController : ApiController
    {
        public HttpResponseMessage Get() { ... }
            [Authorize]
        public HttpResponseMessage Post() { ... }
    }
    复制代码

    在控制器上应用 [Authorize] 时,可以在 Action 上应用 [AllowAnonymous] 取消对某个 Action 的授权要求。上面的代码可以改成下面的形式:

    复制代码
    [Authorize]
    public class ValuesController : ApiController
    {
        [AllowAnonymous]
        public HttpResponseMessage Get() { ... }
    
        public HttpResponseMessage Post() { ... }
    }
    复制代码

    指定用户和角色进行限制:

    复制代码
    // 按用户限制访问
    [Authorize(Users="Alice,Bob")]
    public class ValuesController : ApiController
    {
    }
       
    // 按角色限制访问
    [Authorize(Roles="Administrators")]
    public class ValuesController : ApiController
    {
    }
    复制代码

    用于 WebAPI 的 AuthorizeAttribute 位于 System.Web.Http 命名空间。在 System.Web.Mvc 命名空间中有一个同名属性,不可用于 WebAPI。

    自定义授权筛选器

    可从以下类型派生自定义授权筛选器

    • AuthorizeAttribute,基于用户和角色进行授权。
    • AuthorizationFilterAttribute,不基于用户和角色的同步授权。
    • IAuthorizationFilter,实现此接口执行异步授权逻辑。例如,授权逻辑中有对 IO 或网络的异步调用。(CPU-bound的授权逻辑更适合从 AuthorizationFilterAttribute 派生,这样不必写异步方法)。

    下图是 AuthorizeAttribute 类层次

    在 Action 中执行验证

    可在控制器中检查 ApiController.User 属性,根据用户和角色使用不同的逻辑。

    复制代码
    public HttpResponseMessage Get()
    {
        if (User.IsInRole("Administrators"))
        {
            // ...    }
    }
    复制代码

     

    展开全文
  • 前言无论是ASP.NET MVC还是Web API框架,在从请求到响应这一过程中对于请求信息的认证以及认证成功过后对于访问页面的授权是极其重要的,用两节来重点来讲述这二者,这一节首先讲述一下关于这二者的一些基本信息,下...

    前言

    无论是ASP.NET MVC还是Web API框架,在从请求到响应这一过程中对于请求信息的认证以及认证成功过后对于访问页面的授权是极其重要的,用两节来重点来讲述这二者,这一节首先讲述一下关于这二者的一些基本信息,下一节将通过实战以及不同的实现方式来加深对这二者深刻的认识,希望此文对你有所收获。

    Identity

    Identity代表认证用户的身份,下面我们来看看此接口的定义

    public interface IIdentity
    {
        // Properties
        string AuthenticationType { get; }
    
        bool IsAuthenticated { get; }
    
        string Name {get; }
    }

    该接口定义了三个只读属性, AuthenticationType 代表认证身份所使用的类型, IsAuthenticated 代表是否已经通过认证, Name 代表身份的名称。对于AuthenticationType认证身份类型,不同的认证身份类型对应不同的Identity,若采用Windows集成认证,则其IdentityWindowsIdentity,反之对于Form表单认证,则其IdentityFormsIdentity,除却这二者之外,我们还能利用GenericIdentity对象来表示一般意义的Identity

    WindowsIdentity

    WindowIdentity对象中的属性Groups返回Windows账号所在的用户组,而属性IsGuest则用于判断此账号是否位于Guest用户组中,最后还有一个IsSystem属性很显然表示该账号是否是一个系统账号。在对于匿名登录中,该对象有一个IsAnonymous来表示该账号是否是一个匿名账号。并且其方法中有一个GetAnonymous方法来返回一个匿名对象的WindowsIdentity对象,但是此WindowsIdentity仅仅只是一个空对象,无法确定对应的Windows账号。

    FormsIdentity

    我们来看看此对象的定义

    public class FormsIdentity : ClaimsIdentity
    {
    
        public FormsIdentity(FormsAuthenticationTicket ticket);
    
        protected FormsIdentity(FormsIdentity identity);
    
        public override string AuthenticationType { get; }
    
        public override IEnumerable<Claim> Claims { get; }
    
        public override bool IsAuthenticated { get; }
    
        public override string Name { get; }
    
        public FormsAuthenticationTicket Ticket { get; }
    
        public override ClaimsIdentity Clone();
    }

    一个FormsIdentity对象是通过加密过的认证票据(Authentication Ticket)或者是安全令牌(Security Token)来创建,被加密的内容或者是Cookie或者是请求的URl,下述就是通过FormsIdentity来对Cookie进行加密。

    var ticket = new FormsAuthenticationTicket(1, "cookie", DateTime.Now, DateTime.Now.AddMinutes(20), true, "userData", FormsAuthentication.FormsCookiePath);
    
    var encriptData = FormsAuthentication.Encrypt(ticket);

    GenericIdentity

    以上两者都有其对应的Identity类型,如果想自定义认证方式只需继承该类即可,它表示一般性的安全身份。该类继承于IIdentity接口。至于如何判断一个匿名身份只需通过用户名即可,若用户名为空则对象的属性IsAuthenticatedtrue,否则为false

    Principal  

    这个对象包含两个基本的要素即基于用户的安全身份以及用户所具有的权限,而授权即所谓的权限都是基于角色而绑定,所以可以将此对象描述为:【身份】+【角色】。

    首先我们来看看此接口

    public interface IPrincipal
    {
    
        bool IsInRole(string role);
    
        IIdentity Identity { get; }
    }

    上述基于IIdentity接口的实现即WindowsIdentityGenericIdentity,当然也就对应着Principal类型即WindowsPrincipalGenericPrincipal,除此之外还有RolePrincipal,关于这三者就不再叙述,我们重点来看看下APiController中的IPrincipal属性。

    APiController中User

    我们看看此User属性

    public IPrincipal User { get; }

    继续看看此属性的获取

    public IPrincipal User
    {
        get
        {
            return Thread.CurrentPrincipal;
        }
    }

    到这里还是不能看出什么,即使你用VS编译器查看也不能查看出什么,此时就得看官方的源码了。如下:

    public HttpRequestContext RequestContext
    {
        get
        {
            return ControllerContext.RequestContext;
        }
        set
        {......}
    }
    
    public IPrincipal User
    {
        get { return RequestContext.Principal; }
        set { RequestContext.Principal = value; }
    }

    到这里我们看出一点眉目了
    IPrincipal的属性User显然为当前请求的用户并且与HttpRequestContext中的属性Principal具有相同的引用。 

    那么问题来了,HttpRequestContext又是来源于哪里呢?  

    我们知道寄宿模式有两种,一者是Web Host,另一者是Self Host,所以根据寄宿模式的不同则请求上下文就不同,我们来看看Web Host中的请求上下文。

    Web Host

    internal class WebHostHttpRequestContext : HttpRequestContext
     {
         private readonly HttpContextBase _contextBase;
         private readonly HttpRequestBase _requestBase;
         private readonly HttpRequestMessage _request;
    
    
         public override IPrincipal Principal
         {
             get
             {
                 return _contextBase.User;
             }
             set
             {
                 _contextBase.User = value;
                 Thread.CurrentPrincipal = value;
             }
         }
    }

    从这里我们可以得出一个结论:
    Web Host模式下的Principal与当前请求上下文中的User具有相同的引用,与此同时,当我们将属性Principal进行修改时,则当前线程的Principal也会一同进行修改。

    Self Host

    我们看看在此寄宿模式下的对于Principal的实现

    internal class SelfHostHttpRequestContext : HttpRequestContext
    {
        private readonly RequestContext _requestContext;
        private readonly HttpRequestMessage _request;
    
         public override IPrincipal Principal
        {
            get
            {
                return Thread.CurrentPrincipal;
            }
            set
            {
                Thread.CurrentPrincipal = value;
            }
        }
    
    }

    在此模式我们可以得出结论:

    Self Host模式下的Principal默认是返回当前线程使用的Principal。  

    接下来我们来看看认证(Authentication)以及授权(Authorization)。

    AuthenticationFilter 

    AuthenticationFilter是第一个执行过滤器Filter,因为任何发送到服务器请求Action方法首先得认证其身份,而认证成功后的授权即Authorization当然也就在此过滤器之后了,它被MVC5Web API 2.0所支持。下面用一张图片来说明这二者在管道中的位置及关系
    这里写图片描述

    接下来我们首先来看看第一个过滤器AuthenticationFilter的接口IAuthenticationFilter的定义:

    public interface IAuthenticationFilter : IFilter
    {
    
        Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken);
    
    
        Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken);
    }

    该接口定义了两个方法,一个是 AuthenticateAsync ,它主要认证用户的凭证。另一个则是 ChallengeAsync ,它主要针对于在认证失败的情况下,向客户端发送一个质询(Chanllenge)。

    以上两者关于认证的方法都分别对应定义在Http协议的RFC2612以及RFC2617中,并且两者都是基于以下两点:

    • 如果客户端没有发送任何凭证到服务器,那么将返回一个401(unauthorized)响应到客户端,在返回到客户端的响应中包括一个WWW-Authenticate头,在这个头中包含一个或多个质询,并且每个质询将指定被服务器识别的认证组合。
    • 在从服务器响应返回一个401到客户端后,客户端将在认证头里发送所有的凭证。

    下面我们详细查看每个方法的内容:

    AuthenticateAsync

    此方法中的参数类型为HttpAuthenticationContext,表示为认证上下文,我们看看此类的实现

    public class HttpAuthenticationContext
    {
    
        public HttpAuthenticationContext(HttpActionContext actionContext, IPrincipal principal)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException("actionContext");
            }
    
            ActionContext = actionContext;
            Principal = principal;
        }
    
    
        public HttpActionContext ActionContext { get; private set; }
    
        public IPrincipal Principal { get; set; }
    
        public IHttpActionResult ErrorResult { get; set; }
    
        public HttpRequestMessage Request
        {
            get
            {
                Contract.Assert(ActionContext != null);
                return ActionContext.Request;
            }
        }
    }

    在构造函数中通过Action上下文和认证的用户的Principal属性进行初始化,而属性ErrorResult则返回一个HttpActionResult对象,它是在认证失败的情况直接将错误消息返回给客户端。我们应该能想到请求到Action方法上的AuthenticationFilter可能不止一个,此时Web API会通过FilterScope进行排序而形成一个AuthenticationFilter管道,紧接着认证上下文会通过当前的Action请求上下文以及通过APiControllerUser属性返回的Principal而被创建,最终认证上下文会作为AuthenticationFilter中的AuthenticateAsync方法的参数并进行调用。

    当执行为AuthenticateAsync方法被成功执行并返回一个具体的HttpActionResult,此时后续操作将终止,接下来进入第二个方法即【发送认证质询】阶段。

    ChallengeAsync

    public class HttpAuthenticationChallengeContext
    {
        private IHttpActionResult _result;
    
        public HttpAuthenticationChallengeContext(HttpActionContext actionContext, IHttpActionResult result)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException("actionContext");
            }
    
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }
    
            ActionContext = actionContext;
            Result = result;
        }
    
        public HttpActionContext ActionContext { get; private set; }
    
        public IHttpActionResult Result
        {
            get
            {
                return _result;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
    
                _result = value;
            }
        }
    
        public HttpRequestMessage Request
        {
            get
            {
                Contract.Assert(ActionContext != null);
                return ActionContext.Request;
            }
        }
    }

    很显然,当调用AuthenticateAsync方法完成认证工作后,此时ErrorResult将返回一个具体的HttpActionResult,然会将Action上下文以及具体的HttpActionResult传递到构造函数中进行初始化HttpAuthenticationChallgeContext,最后依次调用ChallengeAsync方法,当此方法都被执行后,此类中的Result属性也就返回一个具体的HttpActionResult对象,此对象将创建相应的HttpResponseMessage对象,并回传到消息处理管道中并作出响应从而发出认证质询。  

    总结

    (1)在Web API中使用AuthenticationFilter进行认证主要是以下三步

    • Web API会为每个需要被调用Action方法创建所有可能的AuthenticationFilter列表,若有多个则通过FilterScope来进行排序,最终形成AuthenticationFilter管道。
    • Web API将为AuthenticationFilter管道中的每一个过滤器依次调用AuthenticateAsync方法,在此方法中每个AuthenticationFilter将验证来自客户端的Http请求凭证,即使在认证过程中触发到了错误,此时进程也不会终止。
    • 若认证成功,Web API将调用每个AuthenticationFilterChallengeAsync方法,接下来每一个AuthenticationFilter将通过此方法做出质询响应。

    (2)通过上述描述我们用三张示意图来对照着看
    这里写图片描述

    认证方案

    我们知道Http协议中的认证方案有两种,一种是Basic基础认证,一种是Digest摘要认证

    Basic基础认证

    此认证是在客户端将用户名和密码以冒号的形式并用Base64明文编码的方式进行发送,但是不太安全,因为未被加密,在此基础上采用Https信息通道加密则是不错的认证方案。

    Digest摘要认证

    此认证可谓是Basic基础认证的升级版,默认是采用MD5加密的方式,在一定程度上算是比较安全的,其执行流程和Basic基础认证一样,只是生成的算法不同而已。
    未完待续:接下来将通过认证方案手动通过不同的方式来实现认证。。。。。。

    展开全文
  • swagger简介 ... api,这个框架可以自动为你的业务代码生成restfut风格的api,而且还提供相应的测试界面,自动显示json格式的响应。大大方便了后台开发人员与前端的沟通与联调成本。 springfox-swagger

    swagger简介

           swagger确实是个好东西,可以跟据业务代码自动生成相关的api接口文档,尤其用于restful风格中的项目,开发人员几乎可以不用专门去维护rest api,这个框架可以自动为你的业务代码生成restfut风格的api,而且还提供相应的测试界面,自动显示json格式的响应。大大方便了后台开发人员与前端的沟通与联调成本。

    springfox-swagger简介

           签于swagger的强大功能,java开源界大牛spring框架迅速跟上,它充分利用自已的优势,把swagger集成到自己的项目里,整了一个spring-swagger,后来便演变成springfoxspringfox本身只是利用自身的aop的特点,通过plug的方式把swagger集成了进来,它本身对业务api的生成,还是依靠swagger来实现。

           关于这个框架的文档,网上的资料比较少,大部分是入门级的简单使用。本人在集成这个框架到自己项目的过程中,遇到了不少坑,为了解决这些坑,我不得不扒开它的源码来看个究竟。此文,就是记述本人在使用springfox过程中对springfox的一些理解以及需要注意的地方。

    springfox大致原理

           springfox的大致原理就是,在项目启动的过种中,spring上下文在初始化的过程,框架自动跟据配置加载一些swagger相关的bean到当前的上下文中,并自动扫描系统中可能需要生成api文档那些类,并生成相应的信息缓存起来。如果项目MVC控制层用的是springMvc那么会自动扫描所有Controller类,跟据这些Controller类中的方法生成相应的api文档。

           因本人的项目就是SpringMvc,所以此文就以Srping mvc集成springfox为例来讨论springfox的使用与原理。

    SpringMvc集成springfox的步骤

    首先,项目需要加入以下三个依赖:

    <!-- sring mvc依赖 -->

          <dependency>

             <groupId>org.springframework</groupId>

             <artifactId>spring-webmvc</artifactId>

             <version>4.2.8.RELEASE</version>

          </dependency>

    <!-- swagger2核心依赖 -->

          <dependency>

             <groupId>io.springfox</groupId>

             <artifactId>springfox-swagger2</artifactId>

             <version>2.6.1</version>

          </dependency>

          <!-- swagger-ui为项目提供api展示及测试的界面 -->

          <dependency>

             <groupId>io.springfox</groupId>

             <artifactId>springfox-swagger-ui</artifactId>

             <version>2.6.1</version>

          </dependency>

    上面三个依赖是项目集成springmvcspringfox最基本的依赖,其它的依赖这里省略。其中第一个是springmvc的基本依赖,第二个是swagger依赖,第三个是界面相关的依赖,这个不是必须的,如果你不想用springfox自带的api界面的话,也可以不用这个,而另外自己写一套适合自己项目的界面。加入这几个依赖后,系统后会自动加入一些跟springfoxswagger相关jar包,我粗略看了一下,主要有以下这么几个:

    springfox-swagger2-2.6.1.jar

    swagger-annotations-1.5.10.jar

    swagger-models-1.5.10.jar

    springfox-spi-2.6.1.jar

    springfox-core-2.6.1.jar

    springfox-schema-2.6.1.jar

    springfox-swagger-common-2.6.1.jar

    springfox-spring-web-2.6.1.jar

    guava-17.0.jar

    spring-plugin-core-1.2.0.RELEASE.jar

    spring-plug-metadata-1.2.0.RELEASE.jar

    spring-swagger-ui-2.6.1.jar

    jackson-databind-2.2.3.jar

    jackson-annotations-2.2.3.jar

    上面是我通过目测觉得springfox可能需要的jar,可能没有完全例出springfox所需的所有jar。从上面 jar可以看出pringfox除了依赖swagger之外,它还需要guavaspring-plugjackson等依赖包(注意jackson是用于生成json必须的jar包,如果项目里本身没有加入这个依赖,为了集成swagger的话必须额外再加入这个依赖)。

    springfox的简单使用

           如果只用springfox的默认的配置的话,与springmvc集成起来非常简单,只要写一个类似于以下代码的类放到你的项目里就行了,代码如下:

    @Configuration

    @EnableWebMvc

    @EnableSwagger2

    publicclass ApiConfig {

    }

    注意到,上面是一个空的java类文件,类名可以随意指定,但必须加入上述类中标出的@Configuration@EnableWebMvc@EnableSwagger2三个注解,这样就完成了springmvcspringfox的基本集成,有了三个注解,项目启动后就可以直接用类似于以下的地址来查看api列表了:

    http://127.0.0.1:8080/jadDemo/swagger-ui.html

    这确实是一个很神奇的效果,简单的三个注解,系统就自动显示出项目里所有Controller类的所有api了。现在,我们就这个配置类入手,简单分析它的原理。这个类中没有任何代码,很显然,三个注解起了至关重要的作用。其中@Configuration注解是spring框架中本身就有的,它是一个被@Component元注解标识的注解,所以有了这个注解后,spring会自动把这个类实例化成一个bean注册到spring上下文中。第二个注解@EnableWebMvc故名思义,就是启用srpingmvc了,在Eclipse中点到这个注解里面简单看一下,它就是通过元注解@Import(DelegatingWebMvcConfiguration.class)spring context中塞入了一个DelegatingWebMvcConfiguration类型的bean。我想,这个类的目的应该就是为swagger提供了一些springmvc方面的配置吧。第三个注解:@EnableSwagger2,看名字应该可以想到,是用来集成swagger 2的,他通过元注解:@Import({Swagger2DocumentationConfiguration.class}),又引入了一个Swagger2DocumentationConfiguration类型的配置bean,而这个就是Swagger的核心配置了。它里面的代码如下:

    @Configuration

    @Import({ SpringfoxWebMvcConfiguration.class, SwaggerCommonConfiguration.class })

    @ComponentScan(basePackages = {

     "springfox.documentation.swagger2.readers.parameter",

        "springfox.documentation.swagger2.web",

        "springfox.documentation.swagger2.mappers"

    })

    publicclassSwagger2DocumentationConfiguration {

      @Bean

      public JacksonModuleRegistrar swagger2Module() {

        returnnewSwagger2JacksonModule();

      }

    }

    这个类头部通过一些注解,再引入SpringfoxWebMvcConfiguration类和SwaggerCommonConfiguration类,并通过ComponentScan注解,自动扫描springfox .swagger2相关的的beanspring context中。这里,我最感兴趣的是SpringfoxWebMvcConfiguration这个类,这个类我猜应该就是springfox集成mvc比较核心的配置了,点进去,看到以下代码:

    @Configuration

    @Import({ModelsConfiguration.class })

    @ComponentScan(basePackages = {

        "springfox.documentation.spring.web.scanners",

    "springfox.documentation.spring.web.readers.operation","springfox.documentation.spring.web.readers.parameter","springfox.documentation.spring.web.plugins","springfox.documentation.spring.web.paths"

    })

    @EnablePluginRegistries({ DocumentationPlugin.class,

        ApiListingBuilderPlugin.class,

        OperationBuilderPlugin.class,

        ParameterBuilderPlugin.class,

        ExpandedParameterBuilderPlugin.class,

        ResourceGroupingStrategy.class,

        OperationModelsProviderPlugin.class,

        DefaultsProviderPlugin.class,

        PathDecorator.class

    })

    publicclassSpringfoxWebMvcConfiguration {

    }

    这个类中下面的代码,无非就是通过@Bean注解再加入一些新的Bean,我对它的兴趣不是很大,我最感兴趣的是头部通过@EnablePluginRegistries加入的那些东西。springfox是基于spring-plug的机制整合swagger的,spring-plug具体是怎么实现的,我暂时还没有时间去研究spring-plug的原理。但在下文会提到自己写一个plug插件来扩展swagger的功能。上面通过@EnablePluginRegistries加入的plug中,还没有时间去看它全部的代码,目前我看过的代码主要有ApiListingBuilderPlugin.class, OperationBuilderPlugin.class,ParameterBuilderPlugin.class, ExpandedParameterBuilderPlugin.class,

    第一个ApiListingBuilderPlugin,它有两个实现类,分别是ApiListingReaderSwaggerApiListingReader。其中ApiListingReader会自动跟据Controller类型生成api列表,而SwaggerApiListingReader会跟据有@Api注解标识的类生成api列表。OperationBuilderPlugin插件就是用来生成具体api文档的,这个类型的插件,有很多很多实现类,他们各自分工,各做各的事情,具体我没有仔细去看,只关注了其中一个实现类:OperationParameterReader,这个类是用于读取api参数的Plugin。它依赖于ModelAttributeParameterExpander工具类,可以将Controller中接口方法参数中非简单类型的命令对像自动解析它内部的属性得出包含所有属性的参数列表(这里存在一个可能会出现无限递归的坑,下文有介绍)。而ExpandedParameterBuilderPlugin插件,主要是用于扩展接口参数的一些功能,比如判断这个参数的数据类型以及是否为这个接口的必须参数等等。总体上说,整个springfox-swagger内部其实是由这一系列的plug转运起来的。他们在系统启动时,就被调起来,有些用来扫描出接口列表,有些用来读取接口参数等等。他们共同的目地就是把系统中所有api接口都扫描出来,并缓存起来供用户查看。那么,这一系列表plug到底是如何被调起来的,它们的执行入口倒底在哪?

       我们把注意点放到上文SpringfoxWebMvcConfiguration这个类代码头部的ComponentScan注解内容上来,这一段注解中扫描了一个叫springfox.documentation.spring.web.pluginspackage,这个packagespringfox-spring-web-2.6.1.jar中可以找到。这个package下,我们发现有两个非常核心的类,那就是DocumentationPluginsManagerDocumentationPluginsBootstrapper。对于第一个DocumentationPluginsManager,它是一个没有实现任何接口的bean,但它内部有诸多PluginRegistry类型的属性,而且都是通过@Autowired注解把属性值注入进来的。接合它的类名来看,很容易想到,这个就是管理所有plug的一个管理器了。很好理解,因为ComponentScan注解的配置,所有的plug实例都会被spring实例化成一个bean,然后被注入到这个DocumentationPluginsManager实例中被统一管理起来。在这个package中的另一个重要的类DocumentationPluginsBootstrapper,看名字就可以猜到,他可能就是plug的启动类了。点进去看具体时就可以发现,他果然是一个被@Component标识了的组件,而且它的构造方法中注入了刚刚描述的DocumentationPluginsManager实例,而且最关键的,它还实现了SmartLifecycle接口。对spring bean生命周期有所了解的人的都知道,这个组件在被实例化为一个bean纳入srping context中被管理起来的时候,会自动调用它的start()方法。点到start()中看代码时就会发现,它有一行代码scanDocumentation(buildContext(each));就是用来扫描api文档的。进一步跟踪这个方法的代码,就可以发现,这个方法最终会通过它的DocumentationPluginsManager属性把所有plug调起一起扫描整个系统并生成api文档。扫描的结果,缓存在DocumentationCache这个类的一个map属性中。

       以上就是,srpingMvc整合springfox的大致原理。它主要是通过EnableSwagger2注解,向srping context注入了一系列bean,并在系统启动的时候自动扫描系统的Controller类,生成相应的api信息并缓存起来。此外,它还注入了一些被@Controller注解标识的Controller类,作为ui模块访问api列表的入口。比如springfox-swagger2-2.6.1.jar包中的Swagger2Controller类。这个Controller就是ui模块中用来访问api列表的界面地址。在访问http://127.0.0.1:8080/jadDemo/swagger-ui.html这个地址查看api列表时,通过浏览器抓包就可以看到,它是通过类似于http://127.0.0.1:8080/jadDemo/v2/api-docs?group=sysGroup这样的地址异步获得api信息(Json格式)并显示到界面上,这个地址后台对应的Controller入口就是上文的Swagger2Controller类,这个类收到请求后,直接从事先初始化好的缓存中的取出api信息生成json字符串返回。

       了解了springfox的原理,下面来看看springfox使用过程中,我遇到的哪些坑。

    springfox第一大坑:配置类生成的bean必须与spring mvc共用同一个上下文。

       前文描述了,在springmvc项目中,集成springfox是只要在项目写一个如下的没有任何业务代码的简单配置类就可以了。

    @Configuration

    @EnableWebMvc

    @EnableSwagger2

    publicclass ApiConfig {

    }

    因为@Configuration注解的作用,spring会自动把它实例化成一个bean注入到上下文。但切记要注意的一个坑就是:这个bean所在的上下文必须跟spring mvc为同一个上下文。怎么解理呢?因为在实际的spring mvc项目中,通常有两个上下文,一个是跟上下文,另一个是spring mvc(它是跟上下文的子上下文)。其中跟上下文是就是web.xml文件中跟spring相关的那个org.springframework.web.context.request.RequestContextListener监听器,加载起来的上下文,通常我们会写一个叫spring-contet.xml的配置文件,这里面的bean最终会初始化到跟上下文中,它主要包括系统里面的service,daobean,也包括数据源、事物等等。而另一个上下文是就是spring mvc了,它通过web.xml中跟spring mvc相关的那个org.springframework.web.servlet.DispatcherServlet加载起来,他通常有一个配置文件叫spring-mvc.xml。我们在写ApiConfig这个类时,如果决定用@Configuration注解来加载,那么就必须保证这个类所在的路径刚好在springmvccomponent-scan的配置的base-package范围内。因为在ApiConfig在被spring加载时,会注入一列系列的bean,而这些bean中,为了能自动扫描出所有Controller类,有些bean需要依赖于SpringMvc中的一些bean,如果项目把Srpingmvc的上下文与跟上下文分开来,作为跟上下文的子上下文的话。如果不小心让这个ApiConfig类型的bean被跟上文加载到,因为root context中没有spring mvccontext中的那些配置类时就会报错。

           实事上,我并不赞成通过@Configuration注解来配置Swagger,因为我认为,Swaggerapi功能对于生产项目来说是可有可无的。我们Swagger往往是用于测试环境供项目前端团队开发或供别的系统作接口集成使上。系统上线后,很可能在生产系统上隐藏这些api列表。 但如果配置是通过@Configuration注解写死在java代码里的话,那么上线的时候想去掉这个功能的时候,那就尴尬了,不得不修改java代码重新编译。基于此,我推荐的一个方法,通过spring最传统的xml文件配置方式。具体做法就是去掉@Configuration注解,然后它写一个类似于<bean class="com.jad.web.mvc.swagger.conf.ApiConfig"/>这样的bean配置到springxml配置文件中。在root contextmvccontext分开的项目中,直接配置到spring-mvc.xml中,这样就保证了它跟springmvc context一定处于同一个context中。

    springfox第二大坑:Controller类的参数,注意防止出现无限递归的情况。

    Spring mvc有强大的参数绑定机制,可以自动把请求参数绑定为一个自定义的命令对像。所以,很多开发人员在写Controller时,为了偷懒,直接把一个实体对像作为Controller方法的一个参数。比如下面这个示例代码:

    @RequestMapping(value = "update")

    public String update(MenuVomenuVo, Model model){

    }

    这是大部分程序员喜欢在Controller中写的修改某个实体的代码。在跟swagger集成的时候,这里有一个大坑。如果MenuVo这个类中所有的属性都是基本类型,那还好,不会出什么问题。但如果这个类里面有一些其它的自定义类型的属性,而且这个属性又直接或间接的存在它自身类型的属性,那就会出问题。例如:假如MenuVo这个类是菜单类,在这个类时又含有MenuVo类型的一个属性parent代表它的父级菜单。这样的话,系统启动时swagger模块就因无法加载这个api而直接报错。报错的原因就是,在加载这个方法的过程中会解析这个update方法的参数,发现参数MenuVo不是简单类型,则会自动以递归的方式解释它所有的类属性。这样就很容易陷入无限递归的死循环。

           为了解决这个问题,我目前只是自己写了一个OperationParameterReader插件实现类以及它依赖的ModelAttributeParameterExpander工具类,通过配置的方式替换掉到srpingfox原来的那两个类,偷梁换柱般的把参数解析这个逻辑替换掉,并避开无限递归。当然,这相当于是一种修改源码级别的方式。我目前还没有找到解决这个问题的更完美的方法,所以,只能建议大家在用spring-fox Swagger的时候尽量避免这种无限递归的情况。毕竟,这不符合springmvc命令对像的规范,springmvc参数的命令对像中最好只含有简单的基本类型属性。

    springfox第三大坑:api分组相关,Docket实例不能延迟加载

           springfox默认会把所有api分成一组,这样通过类似于http://127.0.0.1:8080/jadDemo/swagger-ui.html这样的地址访问时,会在同一个页面里加载所有api列表。这样,如果系统稍大一点,api稍微多一点,页面就会出现假死的情况,所以很有必要对api进行分组。api分组,是通过在ApiConf这个配置文件中,通过@Bean注解定义一些Docket实例,网上常见的配置如下:

     @EnableWebMvc

    @EnableSwagger2

    publicclass ApiConfig {

    @Bean

     public Docket customDocket() {

           return newDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());

        }

    }

    上述代码中通过@Bean注入一个Docket,这个配置并不是必须的,如果没有这个配置,框架会自己生成一个默认的Docket实例。这个Docket实例的作用就是指定所有它能管理的api的公共信息,比如api版本、作者等等基本信息,以及指定只列出哪些api(通过api地址或注解过滤)。

                   Docket实例可以有多个,比如如下代码:

    @EnableWebMvc

    @EnableSwagger2

    publicclass ApiConfig {

    @Bean

     public Docket customDocket1() {

           return newDocket(DocumentationType.SWAGGER_2)

    .groupName("apiGroup1").apiInfo(apiInfo()).select()

    .paths(PathSelectors.ant("/sys/**"));

        }

    @Bean

     public Docket customDocket2() {

           return newDocket(DocumentationType.SWAGGER_2)

    .groupName("apiGroup2").apiInfo(apiInfo())

    .select()

    .paths(PathSelectors.ant("/shop/**"));

        }

    }

           当在项目中配置了多个Docket实例时,也就可以对api进行分组了,比如上面代码将api分为了两组。在这种情况下,必须给每一组指定一个不同的名称,比如上面代码中的"apiGroup1""apiGroup2",每一组可以用paths通过ant风格的地址表达式来指定哪一组管理哪些api。比如上面配置中,第一组管理地址为/sys/开头的api第二组管理/shop/开头的api。当然,还有很多其它的过滤方式,比如跟据类注解、方法注解、地址正则表达式等等。分组后,在api 列表界面右上角的下拉选项中就可以选择不同的api组。这样就把项目的api列表分散到不同的页面了。这样,即方便管理,又不致于页面因需要加载太多api而假死。

                   然而,同使用@Configuration一样,我并不赞成使用@Bean来配置Docket实例给api分组。因为这样,同样会把代码写死。所以,我推荐在xml文件中自己配置Docket实例实现这些类似的功能。当然,考虑到Docket中的众多属性,直接配置bean比较麻烦,可以自己为Docket写一个FactoryBean,然后在xml文件中配置FactoryBean就行了。然而将Docket配置到xml中时。又会遇到一个大坑,就那是,springbean的加载方式默认是延迟加载的,在xml中直接配置这些Docket实例Bean后。你会发现,没有一点效果,页面左上角的下拉列表中跟本没有你的分组项。

                   这个问题曾困扰过我好几个小时,后来凭经验推测出可能是因为sping bean默认延迟加载,这个Docket实例还没加载到spring context中。实事证明,我的猜测是对的。我不知道这算是springfox的一个bug,还是因为我跟本不该把对Docket的配置从原来的java代码中搬到xml配置文件中来。

    springfox其它的坑:springfox还有些其它的坑,比如@ApiOperation注解中,如果不指定httpMethod属性具体为某个getpost方法时,api列表中,会它get,post,delete,put等所有方法都列出来,搞到api列表重复的太多,很难看。另外,还有在测试时,遇到登录权限问题,等等。这一堆堆的比较容易解决的小坑,因为篇幅有限,我就不多说了。还有比如@Api@ApiOperation@ApiParam等等注解的用法,网上很多这方面的文档,我就不重复了。

    更多信息,大家可以关注我的原创公众号


    展开全文
  • Swagger原理解析

    万次阅读 2018-03-13 17:38:46
    swagger确实是个好东西,可以跟据业务代码自动生成相关的api接口文档,尤其用于restful风格中的项目,开发人员几乎可以不用专门去维护rest api,这个框架可以自动为你的业务代码生成restfut风格的api,而且还提供...

    swagger简介

    swagger确实是个好东西,可以跟据业务代码自动生成相关的api接口文档,尤其用于restful风格中的项目,开发人员几乎可以不用专门去维护rest api,这个框架可以自动为你的业务代码生成restfut风格的api,而且还提供相应的测试界面,自动显示json格式的响应。大大方便了后台开发人员与前端的沟通与联调成本。

    springfox-swagger简介

    签于swagger的强大功能,Java开源界大牛spring框架迅速跟上,它充分利用自已的优势,把swagger集成到自己的项目里,整了一个spring-swagger,后来便演变成springfoxspringfox本身只是利用自身的aop的特点,通过plug的方式把swagger集成了进来,它本身对业务api的生成,还是依靠swagger来实现。

    关于这个框架的文档,网上的资料比较少,大部分是入门级的简单使用。本人在集成这个框架到自己项目的过程中,遇到了不少坑,为了解决这些坑,我不得不扒开它的源码来看个究竟。此文,就是记述本人在使用springfox过程中对springfox的一些理解以及需要注意的地方。

    springfox大致原理

    springfox的大致原理就是,在项目启动的过种中,spring上下文在初始化的过程,框架自动跟据配置加载一些swagger相关的bean到当前的上下文中,并自动扫描系统中可能需要生成api文档那些类,并生成相应的信息缓存起来。如果项目MVC控制层用的是springMvc那么会自动扫描所有Controller类,跟据这些Controller类中的方法生成相应的api文档。

    因本人的项目就是SpringMvc,所以此文就以SpringMvc集成springfox为例来讨论springfox的使用与原理。

    SpringMvc集成springfox的步骤

    首先,项目需要加入以下三个依赖:

    <!– sring mvc依赖 –>
    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-webmvc</artifactId>
       <version>4.2.8.RELEASE</version>
    </dependency>
    
    <!– swagger2核心依赖 –>
    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger2</artifactId>
       <version>2.6.1</version>
    </dependency>
    
    <!– swagger-ui为项目提供api展示及测试的界面 –>
    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger-ui</artifactId>
       <version>2.6.1</version>
    </dependency>

    上面三个依赖是项目集成springmvcspringfox最基本的依赖,其它的依赖这里省略。其中第一个是springmvc的基本依赖,第二个是swagger依赖,第三个是界面相关的依赖,这个不是必须的,如果你不想用springfox自带的api界面的话,也可以不用这个,而另外自己写一套适合自己项目的界面。加入这几个依赖后,系统后会自动加入一些跟springfoxswagger相关jar包,我粗略看了一下,主要有以下这么几个:

    springfox-swagger2-2.6.1.jar
    swagger-annotations-1.5.10.jar
    swagger-models-1.5.10.jar
    springfox-spi-2.6.1.jar
    springfox-core-2.6.1.jar
    springfox-schema-2.6.1.jar
    springfox-swagger-common-2.6.1.jar
    springfox-spring-web-2.6.1.jar
    guava-17.0.jar
    spring-plugin-core-1.2.0.RELEASE.jar
    spring-plug-metadata-1.2.0.RELEASE.jar
    spring-swagger-ui-2.6.1.jar
    jackson-databind-2.2.3.jar
    jackson-annotations-2.2.3.jar

    上面是我通过目测觉得springfox可能需要的jar,可能没有完全例出springfox所需的所有jar。从上面jar可以看出springfox除了依赖swagger之外,它还需要guavaspring-plugjackson等依赖包(注意jackson是用于生成json必须的jar包,如果项目里本身没有加入这个依赖,为了集成swagger的话必须额外再加入这个依赖)。

    springfox的简单使用

    如果只用springfox的默认的配置的话,与springmvc集成起来非常简单,只要写一个类似于以下代码的类放到你的项目里就行了,代码如下:

    @Configuration
    @EnableWebMvc
    @EnableSwagger2
    publicclass ApiConfig {
    
    }

    注意到,上面是一个空的java类文件,类名可以随意指定,但必须加入上述类中标出的@Configuration@EnableWebMvc@EnableSwagger2三个注解,这样就完成了springmvcspringfox的基本集成,有了三个注解,项目启动后就可以直接用类似于以下的地址来查看api列表了:

    http://127.0.0.1:8080/jadDemo/swagger-ui.html

    这确实是一个很神奇的效果,简单的三个注解,系统就自动显示出项目里所有Controller类的所有api了。现在,我们就这个配置类入手,简单分析它的原理。这个类中没有任何代码,很显然,三个注解起了至关重要的作用。其中@Configuration注解是spring框架中本身就有的,它是一个被@Component元注解标识的注解,所以有了这个注解后,spring会自动把这个类实例化成一个bean注册到spring上下文中。第二个注解@EnableWebMvc故名思义,就是启用springmvc了,在Eclipse中点到这个注解里面简单看一下,它就是通过元注解@Import(DelegatingWebMvcConfiguration.class)spring context中塞入了一个DelegatingWebMvcConfiguration类型的bean。我想,这个类的目的应该就是为swagger提供了一些springmvc方面的配置吧。第三个注解:@EnableSwagger2,看名字应该可以想到,是用来集成swagger2的,他通过元注解:@Import({Swagger2DocumentationConfiguration.class}),又引入了一个Swagger2DocumentationConfiguration类型的配置bean,而这个就是Swagger的核心配置了。它里面的代码如下:

    @Configuration
    @Import({ SpringfoxWebMvcConfiguration.class, SwaggerCommonConfiguration.class })
    @ComponentScan(basePackages = {
        "springfox.documentation.swagger2.readers.parameter",
        "springfox.documentation.swagger2.web",
        "springfox.documentation.swagger2.mappers"
    
    })
    
    publicclassSwagger2DocumentationConfiguration {
      @Bean
      public JacksonModuleRegistrar swagger2Module() {
    
        returnnewSwagger2JacksonModule();
      }
    }

    这个类头部通过一些注解,再引入SpringfoxWebMvcConfiguration类和SwaggerCommonConfiguration类,并通过ComponentScan注解,自动扫描springfox .swagger2相关的的beanspring context中。这里,我最感兴趣的是SpringfoxWebMvcConfiguration这个类,这个类我猜应该就是springfox集成mvc比较核心的配置了,点进去,看到以下代`码:

    @Configuration
    @Import({ModelsConfiguration.class })
    @ComponentScan(basePackages = {
     "springfox.documentation.spring.web.scanners",
     "springfox.documentation.spring.web.readers.operation",
     "springfox.documentation.spring.web.plugins",
     "springfox.documentation.spring.web.paths"
    })
    
    @EnablePluginRegistries({ 
    
        DocumentationPlugin.class,
        ApiListingBuilderPlugin.class,
        OperationBuilderPlugin.class,
        ParameterBuilderPlugin.class,
        ExpandedParameterBuilderPlugin.class,
        ResourceGroupingStrategy.class,
        OperationModelsProviderPlugin.class,
        DefaultsProviderPlugin.class,
        PathDecorator.class
    })
    
    publicclassSpringfoxWebMvcConfiguration {
    
    }

    这个类中下面的代码,无非就是通过@Bean注解再加入一些新的Bean,我对它的兴趣不是很大,我最感兴趣的是头部通过@EnablePluginRegistries加入的那些东西。springfox是基于spring-plug的机制整合swagger的,spring-plug具体是怎么实现的,我暂时还没有时间去研究spring-plug的原理。但在下文会提到自己写一个plug插件来扩展swagger的功能。上面通过@EnablePluginRegistries加入的plug中,还没有时间去看它全部的代码,目前我看过的代码主要有ApiListingBuilderPlugin.class,OperationBuilderPlugin.class,ParameterBuilderPlugin.class, ExpandedParameterBuilderPlugin.class,

      第一个ApiListingBuilderPlugin,它有两个实现类,分别是ApiListingReaderSwaggerApiListingReader。其中ApiListingReader会自动跟据Controller类型生成api列表,而SwaggerApiListingReader会跟据有@Api注解标识的类生成api列表。OperationBuilderPlugin插件就是用来生成具体api文档的,这个类型的插件,有很多很多实现类,他们各自分工,各做各的事情,具体我没有仔细去看,只关注了其中一个实现类:OperationParameterReader,这个类是用于读取api参数的Plugin。它依赖于ModelAttributeParameterExpander工具类,可以将Controller中接口方法参数中非简单类型的命令对像自动解析它内部的属性得出包含所有属性的参数列表(这里存在一个可能会出现无限递归的坑,下文有介绍)。而ExpandedParameterBuilderPlugin插件,主要是用于扩展接口参数的一些功能,比如判断这个参数的数据类型以及是否为这个接口的必须参数等等。总体上说,整个springfox-swagger内部其实是由这一系列的plug转运起来的。他们在系统启动时,就被调起来,有些用来扫描出接口列表,有些用来读取接口参数等等。他们共同的目地就是把系统中所有api接口都扫描出来,并缓存起来供用户查看。那么,这一系列表plug到底是如何被调起来的,它们的执行入口倒底在哪?
      我们把注意点放到上文SpringfoxWebMvcConfiguration这个类代码头部的ComponentScan注解内容上来,这一段注解中扫描了一个叫springfox.documentation.spring.web.pluginspackage,这个packagespringfox-spring-web-2.6.1.jar中可以找到。这个package下,我们发现有两个非常核心的类,那就是DocumentationPluginsManagerDocumentationPluginsBootstrapper。对于第一个DocumentationPluginsManager,它是一个没有实现任何接口的bean,但它内部有诸多PluginRegistry类型的属性,而且都是通过@Autowired注解把属性值注入进来的。接合它的类名来看,很容易想到,这个就是管理所有plug的一个管理器了。很好理解,因为ComponentScan注解的配置,所有的plug实例都会被spring实例化成一个bean,然后被注入到这个DocumentationPluginsManager实例中被统一管理起来。在这个package中的另一个重要的类DocumentationPluginsBootstrapper,看名字就可以猜到,他可能就是plug的启动类了。点进去看具体时就可以发现,他果然是一个被@Component标识了的组件,而且它的构造方法中注入了刚刚描述的DocumentationPluginsManager实例,而且最关键的,它还实现了SmartLifecycle接口。对spring bean生命周期有所了解的人的都知道,这个组件在被实例化为一个bean纳入srping context中被管理起来的时候,会自动调用它的start()方法。点到start()中看代码时就会发现,它有一行代码scanDocumentation(buildContext(each));就是用来扫描api文档的。进一步跟踪这个方法的代码,就可以发现,这个方法最终会通过它的DocumentationPluginsManager属性把所有plug调起一起扫描整个系统并生成api文档。扫描的结果,缓存在DocumentationCache这个类的一个map属性中。

      以上就是,srpingMvc整合springfox的大致原理。它主要是通过EnableSwagger2注解,向spring context注入了一系列bean,并在系统启动的时候自动扫描系统的Controller类,生成相应的api信息并缓存起来。此外,它还注入了一些被@Controller注解标识的Controller类,作为ui模块访问api列表的入口。比如springfox-swagger2-2.6.1.jar包中的Swagger2Controller类。这个Controller就是ui模块中用来访问api列表的界面地址。在访问http://127.0.0.1:8080/jadDemo/swagger-ui.html这个地址查看api列表时,通过浏览器抓包就可以看到,它是通过类似于http://127.0.0.1:8080/jadDemo/v2/api-docs?group=sysGroup这样的地址异步获得api信息(Json格式)并显示到界面上,这个地址后台对应的Controller入口就是上文的Swagger2Controller类,这个类收到请求后,直接从事先初始化好的缓存中的取出api信息生成json字符串返回。


    了解了springfox的原理,下面来看看springfox使用过程中,我遇到的哪些坑。

    springfox第一大坑:配置类生成的bean必须与spring mvc共用同一个上下文。

    前文描述了,在springmvc项目中,集成springfox是只要在项目写一个如下的没有任何业务代码的简单配置类就可以了。

    @Configuration
    @EnableWebMvc
    @EnableSwagger2
    publicclass ApiConfig {
    
    }

    因为@Configuration注解的作用,spring会自动把它实例化成一个bean注入到上下文。但切记要注意的一个坑就是:这个bean所在的上下文必须跟spring mvc为同一个上下文。怎么解理呢?因为在实际的spring mvc项目中,通常有两个上下文,一个是跟上下文,另一个是spring mvc(它是跟上下文的子上下文)。其中跟上下文是就是web.xml文件中跟spring相关的那个org.springframework.web.context.request.RequestContextListener监听器,加载起来的上下文,通常我们会写一个叫spring-contet.xml的配置文件,这里面的bean最终会初始化到跟上下文中,它主要包括系统里面的service,daobean,也包括数据源、事物等等。而另一个上下文是就是spring mvc了,它通过web.xml中跟spring mvc相关的那个org.springframework.web.servlet.DispatcherServlet加载起来,他通常有一个配置文件叫spring-mvc.xml。我们在写ApiConfig这个类时,如果决定用@Configuration注解来加载,那么就必须保证这个类所在的路径刚好在springmvccomponent-scan的配置的base-package范围内。因为在ApiConfig在被spring加载时,会注入一列系列的bean,而这些bean中,为了能自动扫描出所有Controller类,有些bean需要依赖于SpringMvc中的一些bean,如果项目把Srpingmvc的上下文与跟上下文分开来,作为跟上下文的子上下文的话。如果不小心让这个ApiConfig类型的bean被跟上文加载到,因为root context中没有spring mvccontext中的那些配置类时就会报错。
      实事上,我并不赞成通过@Configuration注解来配置Swagger,因为我认为,Swaggerapi功能对于生产项目来说是可有可无的。我们Swagger往往是用于测试环境供项目前端团队开发或供别的系统作接口集成使上。系统上线后,很可能在生产系统上隐藏这些api列表。 但如果配置是通过@Configuration注解写死在java代码里的话,那么上线的时候想去掉这个功能的时候,那就尴尬了,不得不修改java代码重新编译。基于此,我推荐的一个方法,通过spring最传统的xml文件配置方式。具体做法就是去掉@Configuration注解,然后它写一个类似于<bean class=”com.jad.web.mvc.swagger.conf.ApiConfig"/>这样的bean配置到springxml配置文件中。在root contextmvccontext分开的项目中,直接配置到spring-mvc.xml中,这样就保证了它跟springmvccontext一定处于同一个context中。

    springfox第二大坑:Controller类的参数,注意防止出现无限递归的情况。

    Spring mvc有强大的参数绑定机制,可以自动把请求参数绑定为一个自定义的命令对像。所以,很多开发人员在写Controller时,为了偷懒,直接把一个实体对像作为Controller方法的一个参数。比如下面这个示例代码:

    @RequestMapping(value = “update”)
    public String update(MenuVo menuVo, Model model){
    
    }

      这是大部分程序员喜欢在Controller中写的修改某个实体的代码。在跟swagger集成的时候,这里有一个大坑。如果MenuVo这个类中所有的属性都是基本类型,那还好,不会出什么问题。但如果这个类里面有一些其它的自定义类型的属性,而且这个属性又直接或间接的存在它自身类型的属性,那就会出问题。例如:假如MenuVo这个类是菜单类,在这个类时又含有MenuVo类型的一个属性parent代表它的父级菜单。这样的话,系统启动时swagger模块就因无法加载这个api而直接报错。报错的原因就是,在加载这个方法的过程中会解析这个update方法的参数,发现参数MenuVo不是简单类型,则会自动以递归的方式解释它所有的类属性。这样就很容易陷入无限递归的死循环。
      为了解决这个问题,我目前只是自己写了一个OperationParameterReader插件实现类以及它依赖的ModelAttributeParameterExpander工具类,通过配置的方式替换掉到springfox原来的那两个类,偷梁换柱般的把参数解析这个逻辑替换掉,并避开无限递归。当然,这相当于是一种修改源码级别的方式。我目前还没有找到解决这个问题的更完美的方法,所以,只能建议大家在用spring-fox Swagger的时候尽量避免这种无限递归的情况。毕竟,这不符合springmvc命令对像的规范,springmvc参数的命令对像中最好只含有简单的基本类型属性。

    springfox第三大坑:api分组相关,Docket实例不能延迟加载

      springfox默认会把所有api分成一组,这样通过类似于http://127.0.0.1:8080/jadDemo/swagger-ui.html这样的地址访问时,会在同一个页面里加载所有api列表。这样,如果系统稍大一点,api稍微多一点,页面就会出现假死的情况,所以很有必要对api进行分组。api分组,是通过在ApiConf这个配置文件中,通过@Bean注解定义一些Docket实例,网上常见的配置如下:

    @EnableWebMvc
    @EnableSwagger2
    publicclass ApiConfig {
    @Bean
     public Docket customDocket() {
           return newDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
      }
    }

    上述代码中通过@Bean注入一个Docket,这个配置并不是必须的,如果没有这个配置,框架会自己生成一个默认的Docket实例。这个Docket实例的作用就是指定所有它能管理的api的公共信息,比如api版本、作者等等基本信息,以及指定只列出哪些api(通过api地址或注解过滤)。

    Docket实例可以有多个,比如如下代码:

    @EnableWebMvc
    @EnableSwagger2
    publicclass ApiConfig {
    @Bean
     public Docket customDocket1() {
    
           return newDocket(DocumentationType.SWAGGER_2)
               .groupName(“apiGroup1”).apiInfo(apiInfo()).select()
               .paths(PathSelectors.ant(“/sys/**”));
        }
    
    @Bean
     public Docket customDocket2() {
           return newDocket(DocumentationType.SWAGGER_2)
            .groupName(“apiGroup2”).apiInfo(apiInfo())
            .select()
            .paths(PathSelectors.ant(“/shop/**”));
        }
    }

      当在项目中配置了多个Docket实例时,也就可以对api进行分组了,比如上面代码将api分为了两组。在这种情况下,必须给每一组指定一个不同的名称,比如上面代码中的apiGroup1apiGroup2,每一组可以用paths通过ant风格的地址表达式来指定哪一组管理哪些api。比如上面配置中,第一组管理地址为/sys/开头的api第二组管理/shop/开头的api。当然,还有很多其它的过滤方式,比如跟据类注解、方法注解、地址正则表达式等等。分组后,在api列表界面右上角的下拉选项中就可以选择不同的api组。这样就把项目的api列表分散到不同的页面了。这样,即方便管理,又不致于页面因需要加载太多api而假死。
      然而,同使用@Configuration一样,我并不赞成使用@Bean来配置Docket实例给api分组。因为这样,同样会把代码写死。所以,我推荐在xml文件中自己配置Docket实例实现这些类似的功能。当然,考虑到Docket中的众多属性,直接配置bean比较麻烦,可以自己为Docket写一个FactoryBean,然后在xml文件中配置FactoryBean就行了。然而将Docket配置到xml中时。又会遇到一个大坑,就那是,springbean的加载方式默认是延迟加载的,在xml中直接配置这些Docket实例Bean后。你会发现,没有一点效果,页面左上角的下拉列表中跟本没有你的分组项。

      这个问题曾困扰过我好几个小时,后来凭经验推测出可能是因为sping bean默认延迟加载,这个Docket实例还没加载到spring context中。实事证明,我的猜测是对的。我不知道这算是springfox的一个bug,还是因为我跟本不该把对Docket的配置从原来的java代码中搬到xml配置文件中来。
      springfox其它的坑:springfox还有些其它的坑,比如@ApiOperation注解中,如果不指定httpMethod属性具体为某个getpost方法时,api列表中,会它get,post,delete,put等所有方法都列出来,搞到api列表重复的太多,很难看。另外,还有在测试时,遇到登录权限问题,等等。这一堆堆的比较容易解决的小坑,因为篇幅有限,我就不多说了。还有比如@Api@ApiOperation@ApiParam等等注解的用法,网上很多这方面的文档,我就不重复了。

    展开全文
  • Jetty架构

    千次阅读 2014-08-08 16:11:46
    概览 Jetty Server是一个Connector集合和一个Handler集合间的管道,Connector用于接收HTTP连接...当Jetty请求/响应源自Servlet API时,Servelet API的完整特征只有你配置适当的handler才是可用的。例如,在请求中的ses
  • 这篇我们主要来介绍我们如何在API项目中完成API的登录及身份认证. 所以这篇会分为两部分, 登录APIAPI身份验证. 这一篇的主要原理是: API会提供一个单独的登录API, 通过用户名,密码来产生一个SessionKey, ...
  • 到目前为止,“使用C#开发HTTP服务器”这个系列系列文章目前已经接近尾声了,虽然我们在服务器功能的完整性(如支持并发、缓存、异步、Htts等)没有再继续深入下去,可是我们现在已经具备了一个基本的服务器框架啦,...
  • JSP中application的用法

    万次阅读 2014-07-09 21:53:26
    application对象 1 什么是application对象 ? (1) 当Web服务器启动时,Web... 一个Web服务器通常有多个Web服务目录 (网站),当Web服务器启动时,它自动为每个Web服务目录都创建一个application对象,这些applicati
  • 对于application对象的认识

    千次阅读 2018-03-22 21:29:50
    本文转载于...(1) 当Web服务器启动时,Web服务器会自动创建一个application对象。application对象一旦创建,它将一直存在,直到Web服务器关闭。 一个Web服务器通常有多个Web服务目录 (网站),...
  • System.Web.HttpApiController类

    千次阅读 2019-05-27 12:47:29
    Properties ActionContext Gets the ...Gets the http context. ControllerContext Gets or sets the ControllerContext. MetadataProvider Gets the IModelMetadataProvider. ModelState Gets ...
  • 深入理解Java类加载器(2):线程上下文类加载器

    万次阅读 多人点赞 2014-07-06 19:11:32
    线程上下文类加载器 ...类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLo
  • 最新版本 ...当前版本 http://www.w3.org/2012/08/mobile-web-app-state/ 个版本 http://www.w3.org/2012/05/mobile-web-app-state/ 随着网页技术的成熟,我们已经可以用它来构建全功能
  • 一、Servlet初始化 ①Servlet在初始化的时候,是通过init(ServletConfig config) 或 init() 来执行的。 ServletConfig 是一个接口,它怎样传递给他一格对象来进行初始化呢?其实,是这个对象是由 servlet 容 ...
  • 在ASP.NET Core中获取客户端IP地址

    千次阅读 2018-12-25 00:59:34
    WebForms和MVC Web应用程序只是访问当前HTTP上下文的请求。   var ip = HttpContext.Current.Request.UserHostAddress;   或者只是直接引用当前的Request  var ip = Request.UserHostAddress; ...
  • Web adi 导入笔记 详细图解

    千次阅读 2015-01-28 16:43:33
    Web ADI 全称 web application desktop integrator,是用来实现Excel与ebs数据通信的,可以进行各种个性化开发,实现数据的导入。 开发时,需拥有web adi的相应职责。 在EBS 11.5.10.2环境中,WEBADI的职责名...
  • ASP.NET Web APIWeb宿主

    千次阅读 2014-03-16 13:54:05
    一节描述了API的整个运行框架,即分为三层hosting、message handler pipeline 和 controller handling。此节讲其中一个宿主,WebHost 寄宿在asp.net 传统管道。 Routing(路由) 在asp.net平台,路由是一般...
  • 在Android做服务端开发/Web开发/SpringMVC开发

    千次阅读 热门讨论 2020-05-09 11:01:34
    像SpringMVC一样在AndroidWeb开发 一部分Android开发者看到这个标题时可能有点疑惑,SpringMVC不是用来做JavaWeb开发的吗?难道被移植到Android上来了?答案是否定的,因为SpringMVC是基于Servlet的,在Android...
  • Servlet规范

    千次阅读 2011-06-02 22:11:00
    原文地址:http://ajava.org/readbook/J2EE/srqcj2eejg/14338.html6.2  Servlet规范6.2.1 Servlet的发展史 Servlet是运行在Web服务器的Java程序,用于响应客户请求。也可以说,Servlet是Java组件,...
1 2 3 4 5 ... 20
收藏数 41,956
精华内容 16,782
关键字:

webapi获取当前http上下文