Core API之Class接口和组件

本文深入探讨了ASM框架中ClassVisitor接口及其子接口的使用方法,包括如何生成和转换类文件,解析现有类文件,以及如何添加和删除类成员。

Presentation

    ASM生成和转换编译后class的API是基于ClassVisitor接口。简单部分通过简单方法调用(参数描述它们的内容,且返回类型为void)来访问。哪些内容长度不定且复杂的部分可以通过初始方法调用来访问,它返回附加的访问接口。visitAnnotation,visitField和visitMethod方法各自返回AnnotationVisitor、FieldVisitor和MethodVisitor。
public interface ClassVisitor {
    void visit(int version, int access, String name, String signature,
               String superName, String[] interfaces);
    void visitSource(String source, String debug);
    void visitOuterClass(String owner, String name, String desc);
               AnnotationVisitor visitAnnotation(String desc, boolean visible);
    void visitAttribute(Attribute attr);
    void visitInnerClass(String name, String outerName, String innerName,int access);
    FieldVisitor visitField(int access, String name, String desc,
         String signature, Object value);
    MethodVisitor visitMethod(int access, String name, String desc,
         String signature, String[] exceptions);
    void visitEnd();
}

public interface FieldVisitor {
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    void visitAttribute(Attribute attr);
    void visitEnd();
}

    ClassVisitor接口必须按以下顺序调用:visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd

    意味着,visit必须第一个被调用,紧接着最多调用一次visitSource,接着最多调用一次visitOuterClass,接着顺序调用多次visitAnnotation和visitAttribute,接着多次顺序调用visitInnerClass,visitFieldvisitMethod,调用最后visitEnd结束。ASM提供了三个基于ClassVisitor接口的核心组件来生成和转换class。

    ASM提供了三个基于ClassVisitor接口的核心组件来生成和转换class。

    CLassReader:将编译过的class转换为字节数组,且调用ClassVisitor实例的visitXxx方法,ClassVisitor作为参数传递给accept方法。可以看作是事件生产者。

    ClassWriter:ClassVisitor接口的一个实现,它以二进制格式直接构建编译后的class。它产生包含编译后的class的二进制字节数组作为输出。可以看作是事件消费者。

    ClassAdapter:ClassVisitor接口的一个实现,它委托所有来自另外一个ClassVisitor实例的方法调用。它可以看作是过滤器。

Parsing classes

    转换一个已存在的class需要唯一组件是ClassReader。假设我们要打印class的内容,类似反编译命令javap。第一步实现ClassVisitor接口来打印class信息。
public class ClassPrinter implements ClassVisitor {
    public void visit(int version, int access, String name,
                      String signature, String superName, String[] interfaces) {
       System.out.println(name + " extends " + superName + " {");
    }
    public void visitSource(String source, String debug) {
    }
    public void visitOuterClass(String owner, String name, String desc) {
    }
    public AnnotationVisitor visitAnnotation(String desc,boolean visible) {
       return null;
    }
    public void visitAttribute(Attribute attr) {
    }
    public void visitInnerClass(String name, String outerName,String innerName, int access) {
    }
    public FieldVisitor visitField(int access, String name, String desc,String signature, Object value) {
       System.out.println("" + desc + " " + name);
       return null;
    }
    public MethodVisitor visitMethod(int access, String name,
                         String desc, String signature, String[] exceptions) {
       System.out.println("" + name + desc);
       return null;
    }
    public void visitEnd() {
       System.out.println("}");
    }
}
    第二步Classprinter与ClassReader一起使用,以便ClassReader产生的事件能被ClassPrinter来消费。    

ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
   accept方法在Runnable类字节码最后一行被转换后被调用,并且会调用cp上的ClassVisitor方法。  

Generating classes

生成Class的唯一组件是ClassWriter。
package pkg;
public interface Comparable extends Mesurable {
   int LESS = -1;
   int EQUAL = 0;
   int GREATER = 1;
   int compareTo(Object o);
}
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
     "pkg/Comparable", null, "java/lang/Object",
     new String[] { "pkg/Mesurable" });
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I",
     null, new Integer(-1)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I",
     null, new Integer(0)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",
     null, new Integer(1)).visitEnd();
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo",
     "(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd();
byte[] b = cw.toByteArray();
    visit方法定义了class的头信息。V1_5是在ASM Opcodes接口中定义的常量,它指定了class的版本。ACC_XXX常量对应Java修饰符。这里我们指定它是public和abstract。接下来参数指明了类名、接口和父类。最后一个参数它扩增的接口数组。
    连续的调用visitField方法,用来定义三个接口域,第一个参数是修饰符,第二个参数是field的名称,第三个参数是field类型。第四个参数泛型,在这里没有使用泛型,故为null。最后一个参数是field的常量值,这个参数仅仅被常量域使用。这里没有annotation,调用visitEnd方法。 visitMethod调用被用来定义compareTo方法。第一个参数是修饰符,第二个是方法名称。第三个参数是方法的描述。第四个为泛型,最后一个参数为方法能抛出的异常数组。visitMethod方法返回MethodVisitor,用来定义方法的annotation、属性和最为重要的方法的代码。接着调用visitEnd方法返回MethodVisitor。
最后一次调用visitEnd用来通知cw类已经结束,调用toByteArray获取字节数组。
可以使用自定义ClassLoader来加载生成的class。

class MyClassLoader extends ClassLoader {
    public Class defineClass(String name, byte[] b) {
        return defineClass(name, b, 0, b.length);
    }
}

Transforming classes 

 目前为止,ClassReader和ClassWriter组件被单独使用。联合使用这两个组件:

    1)ClassReader生产的event直接传递给ClassWriter。

byte[] b1 = ...;
ClassWriter cw = new ClassWriter();
ClassReader cr = new ClassReader(b1);
cr.accept(cw, 0);
byte[] b2 = cw.toByteArray();//b2代表了与b1Class一样的类.

    2)在ClassReader和ClassWriter引入ClassAdapter
byte[] b1 = ...;
ClasssWriter cw = new ClassWriter();
ClassAdapter ca = new ClassAdapter(cw); // ca forwards all events to cw
ClassReader cr = new ClassReader(b1);
cr.accept(ca, 0);
byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1
转换链:ClassReader->ClassAdapter->ClassWriter
public class ChangeVersionAdapter extends ClassAdapter {
    public ChangeVersionAdapter(ClassVisitor cv) {
      super(cv);
    }
    @Override
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {
      cv.visit(V1_5, access, name, signature, superName, interfaces);
    }
}
通过修改visit方法的其他参数,你可以完成其他转换。

Using transformed classes

    转换过的class可以存储到磁盘上或使用ClassLoader加载,但是在一个ClassLoader内的class转换只能转换被该类的classLoader加载进来的类。如果你向转换所有class,你必须将转换置于ClassFileTransformer(定义在java.lang.instrument)内。

public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new ClassFileTransformer() {
       public byte[] transform(ClassLoader l, String name, Class c,
                  ProtectionDomain d, byte[] b)
       throws IllegalClassFormatException {
                  ClassReader cr = new ClassReader(b);
                  ClassWriter cw = new ClassWriter(cr, 0);
                  ClassVisitor cv = new ChangeVersionAdapter(cw);
                  cr.accept(cv, 0);
                 return cw.toByteArray();
       }
   });
}

Removing class members

    通过改变visitField和visitMethod方法中的access和name参数,就可以改变数据域和方法的访问修饰符或名字。这样作的效果就是数据域或方法被移除了。
本例子就是移除外部类、内部类和源文件名称。
public class RemoveDebugAdapter extends ClassAdapter {
    public RemoveDebugAdapter(ClassVisitor cv) {
       super(cv);
    }
    @Override
    public void visitSource(String source, String debug) {
    }
    @Override
    public void visitOuterClass(String owner, String name, String desc) {
    }
    @Override
    public void visitInnerClass(String name, String outerName,
           String innerName, int access) {
    }
}
下例移除了指定方法
public class RemoveMethodAdapter extends ClassAdapter {
    private String mName;
    private String mDesc;
    public RemoveMethodAdapter(
       ClassVisitor cv, String mName, String mDesc) {
          super(cv);
          this.mName = mName;
          this.mDesc = mDesc;
    }
    @Override
    public MethodVisitor visitMethod(int access, String name,
           String desc, String signature, String[] exceptions) {
       if (name.equals(mName) && desc.equals(mDesc)) {
       // do not delegate to next visitor -> this removes the method
           return null;
       }
       return cv.visitMethod(access, name, desc, signature, exceptions);
    }
}

Adding class members

public class AddFieldAdapter extends ClassAdapter {
    private int fAcc;
    private String fName;
    private String fDesc;
    private boolean isFieldPresent;
    public AddFieldAdapter(ClassVisitor cv, int fAcc, String fName,
           String fDesc) {
        super(cv);
        this.fAcc = fAcc;
        this.fName = fName;
        this.fDesc = fDesc;
    }
    @Override
    public FieldVisitor visitField(int access, String name, String desc,
           String signature, Object value) {
        if (name.equals(fName)) {
           isFieldPresent = true;
        }
        return cv.visitField(access, name, desc, signature, value);
    }
    @Override
    public void visitEnd() {
        if (!isFieldPresent) {
           FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
           if (fv != null) {
             fv.visitEnd();
           }
        }
        cv.visitEnd();
    }
}



<think>嗯,用户现在想了解如何在ASP.NET Core框架下开发RESTful API接口,并且添加Swagger组件。之前他问过关于AppDbContext获取连接字符串的问题,现在转向API开发Swagger集成,可能需要一个结构化的步骤指南。 首先,我需要确定用户的基本需求。他们可能正在开始一个新的ASP.NET Core项目,需要构建RESTful API,并且希望使用Swagger来生成API文档测试界面。可能需要从项目创建开始,逐步引导他们添加必要的组件配置。 接下来,我应该回顾ASP.NET Core创建API的基本流程。使用控制器Action方法,可能需要涉及路由、HTTP动词属性等。然后,集成Swagger通常需要安装Swashbuckle.AspNetCore包,并在Program.cs中进行配置。 用户可能不熟悉中间件的配置步骤,比如如何添加Swagger服务启用中间件。需要详细说明每个步骤,包括代码示例,确保他们能够正确复制粘贴。此外,可能需要解释Swagger的不同版本支持,比如Swagger(OpenAPI 2.0)Swagger/OpenAPI 3.0的区别,但通常默认使用最新支持的版本。 另外,用户可能希望了解如何测试API,所以需要提到运行应用程序后访问Swagger UI的路径,比如/swagger/index.html。同时,可能需要提示如何配置API的元数据,比如项目名称、版本等信息,这样生成的文档更清晰。 还要注意常见的错误或遗漏,例如忘记调用UseSwaggerUI,或者没有正确配置端点。可能需要强调这些步骤的重要性,避免用户遇到问题。此外,对于控制器Action方法,可能需要说明如何应用HttpPost、HttpGet等属性,以及路由属性,确保Swagger能够正确识别显示API端点。 最后,用户可能想知道进一步的定制选项,比如添加JWT认证支持或者XML注释,但考虑到当前问题,可能需要简要提及这些扩展点作为可选的高级配置,而不深入细节,除非用户后续提问。</think>--- 在ASP.NET Core中开发RESTful API并集成Swagger(OpenAPI文档工具)的步骤如下: --- ### **1. 创建ASP.NET Core Web API项目** ```bash dotnet new webapi -n MyApiProject ``` --- ### **2. 添加Swagger组件** 通过NuGet安装`Swashbuckle.AspNetCore`包: ```bash dotnet add package Swashbuckle.AspNetCore ``` --- ### **3. 配置Swagger服务** 在`Program.cs`中注册Swagger生成器: ```csharp var builder = WebApplication.CreateBuilder(args); // 添加Swagger服务 builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1", Description = "示例RESTful API" }); }); var app = builder.Build(); ``` --- ### **4. 启用Swagger中间件** 在开发环境中启用Swagger UIJSON端点: ```csharp if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); } ``` --- ### **5. 创建RESTful API控制器** 示例控制器`WeatherForecastController.cs`: ```csharp [ApiController] [Route("api/[controller]")] public class WeatherForecastController : ControllerBase { [HttpGet] public IEnumerable<WeatherForecast> Get() { // 返回示例数据 } [HttpPost] public ActionResult<WeatherForecast> Create(WeatherForecast forecast) { // 创建操作逻辑 return CreatedAtAction(nameof(Get), new { id = forecast.Id }, forecast); } } ``` --- ### **6. 运行并测试** 启动应用后访问: - Swagger UI界面:`https://localhost:PORT/swagger/index.html` - 原始OpenAPI文档:`https://localhost:PORT/swagger/v1/swagger.json` --- ### **关键配置项说明** | 配置项 | 作用 | |-------------------------|----------------------------------------------------------------------| | `AddSwaggerGen()` | 生成API文档元数据 | | `SwaggerEndpoint()` | 指定JSON文档路径UI标题 | | `[ApiController]`特性 | 标记控制器为API专用(自动验证模型、推断参数来源等) | | `[Route]``[Http*]`特性| 定义API端点的路由HTTP方法 | --- ### **扩展配置** #### **添加XML注释文档** 1. 在项目文件中启用XML文档生成: ```xml <PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> ``` 2. 配置Swagger加载注释: ```csharp builder.Services.AddSwaggerGen(c => { var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); }); ``` #### **添加JWT认证支持** ```csharp builder.Services.AddSwaggerGen(c => { // ...其他配置 c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT授权头: Bearer {token}", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey }); }); ``` --- ### **验证是否成功** - 访问Swagger UI应显示所有API端点 - 每个端点可展开查看请求/响应模型 - 支持直接在UI中测试API --- 通过这种方式,您可以在20分钟内快速构建一个具有完整文档测试能力的RESTful API服务。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值