ASM在Spring中的使用

本文介绍了Spring框架中如何使用ASM进行bean定义的加载与扫描,包括ClassPathXmlApplicationContext及FileSystemXmlApplicationContext的使用,并深入探讨了AbstractRefreshableApplicationContext的refreshBeanFactory方法及ClassPathBeanDefinitionScanner的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上次我介绍了一下ASM大概情况请参见ASM详解
接着上次说得的我会介绍ASM在Spring中的一些使用,接触过spring的朋友都应该接触过ClassPathXmlApplicationContext或FileSystemXmlApplicationContext,他们都是加载spring配置文件形成spring应用上下文的类。
下面是ClassPathXmlApplicationContext与FileSystemXmlApplicationContext类关系。
AbstractApplicationContext
/|\
|
AbstractRefreshableApplicationContext
/|\
|
AbstractRefreshableConfigApplicationContext
/|\
|
AbstractXmlApplicationContext
/|\ /|\
| |
ClassPathXmlApplicationContext FileSystemXmlApplicationContext


在spring中设置扫描annotation的语句是:<context:component-scan base-package="com.appspot.coder9527.joinhack">,这样容器在启动的时候就会去扫描配置包对应的类,查看是否有annotation,如果有annotation的配置,spring容器就会根据annotation的描述完成bean的配置。在上述过程中使用到ASM的地方就是AbstractRefreshableApplicationContext的refreshBeanFactory方法,refreshBeanFactory方法调用loadBeanDefinitions方法完成bean定义loadBeanDefinitions中会调用XmlBeanDefinitionReader类的loadBeanDefinitions加载bean的定义.在这个过程中会调用到NamespaceHandlerSupport的parse方法,该方法通过配置寻找到对应的BeanDefinitionParser,上述的配置中spring会使用ComponentScanBeanDefinitionParser来完成bean定义,下面是ComponentScanBeanDefinitionParser的实现代码.
public BeanDefinition parse(Element element, ParserContext parserContext) {
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

return null;
}
在上述实现中值得注意的是ClassPathBeanDefinitionScanner类就是来扫描ClassPath中指定包对应的bean,在doScan方法中可以看到找到findCandidateComponents方法通过MetadataReaderFactory创建类的MetadataReader使用,MetadataReader来读取annotation,下面是MetadataReader的类结构。

MetadataReader
/|\
|
SimpleMetadataReader

ClassPathBeanDefinitionScanner中扫描所有的Class文件,使用SimpleMetadataReader获得ClassMetadata信息,下面是SimpleMetadataReader的代码.
final class SimpleMetadataReader implements MetadataReader {

private final Resource resource;
private final ClassMetadata classMetadata;
private final AnnotationMetadata annotationMetadata;

SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException {
InputStream is = resource.getInputStream();
ClassReader classReader = null;
try {
classReader = new ClassReader(is);
} finally {
is.close();
}

AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
classReader.accept(visitor, true);

this.annotationMetadata = visitor;
// (since AnnotationMetadataReader extends ClassMetadataReadingVisitor)
this.classMetadata = visitor;
this.resource = resource;
}

public Resource getResource() {
return this.resource;
}

public ClassMetadata getClassMetadata() {
return this.classMetadata;
}

public AnnotationMetadata getAnnotationMetadata() {
return this.annotationMetadata;
}
}
到这里为止我们可以看到ASM相关代码ClassReader和Vistor。有兴趣的朋友可以继续读相关的源码。
### 如何在Spring使用ASM字节码操作库 #### 利用ASM增强Spring应用功能 为了利用ASM来增强Spring应用程序的功能,可以采用一种方式是在控制器层面上插入额外的日志记录逻辑。具体来说,在定义了一个标准的`@RestController`之后,可以通过修改其编译后的字节码文件来实现在每次调用特定接口之前自动打印日志信息。 ```java @RestController public class MyController { @GetMapping("/hello") public String hello() { return "Hello, Spring Cloud!"; } } ``` 上述代码展示了创建一个简单的RESTful Web Service端点[^1]。要在此基础上加入日志记录,则需借助于ASM提供的API读取并调整该类的方法体,从而引入新的指令序列用于执行日志输出动作。 #### ASM作为高性能字节码处理工具的作用 ASM是一个专为Java设计的强大而灵活的库,它允许开发者直接访问和操控.class文件的内容。由于其高效的解析机制和支持多种版本JVM特性,因此被广泛应用于各种场景之中,特别是在实现面向切面编程(AOP)方面有着不可替代的地位。例如,在Spring框架内部就集成了基于ASM构建的服务组件,如CGLIB代理工厂等[^2]。 #### 实现细节和技术要点 当涉及到具体的实施策略时,通常会先编写一段能够正确反映预期行为变化的新方法模板——即所谓的“织入”(weaving),接着再运用ASM所提供的ClassVisitor及其子类(比如MethodVisitor)遍历目标类型的结构,并按照预设规则替换原有部分或添加新片段。对于更复杂的案例而言,可能还需要考虑如何妥善处理异常传播路径、确保线程安全等问题。 此外,值得注意的是,尽管可以直接操作底层字节流来进行定制化改造,但在实际项目开发过程中往往推荐优先选用更高层次抽象层面的技术方案,除非确实有必要深入到底层优化性能或者解决某些特殊需求。这是因为低级别的改动容易引发难以预料的风险,并且维护成本较高[^3]。 #### 示例:通过ASM自动生成对应代码 下面给出了一段简化版的例子,说明怎样利用ASMified插件来自动生成针对某个测试函数(`test`)的操作脚本: ```java package sample.method; public class GoodChild { public void test() { new GoodChild("lcc", 8); } } ``` 这段原始源码经过转换后将会变成一系列ASM API调用语句,这些命令共同作用下实现了相同的效果,但形式上更加贴近机器可理解的形式[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值