Spring LoadTimeWeaver 的那些事儿

 http://www.iteye.com/topic/481813

 DDD 现在越来越流行了, 不管正确与否, new User().save() 这样的写法相对千篇一律的 service dao transaction script 总是显得更酷也更具吸引力, save 方法一般来说是这个样子 

Java代码   收藏代码
  1. public void save() {  
  2.     userRepository.save(this);  
  3. }  



看起来很自然, 但如何取得 userRepositry 却一直是个难题, 现在 jdk5 新增的 Instrumentation 机制使得这个问题有了一个标准解决方案 : 通过 instrumentation 的动态字节码增强在装载期向 domain object 中注入依赖,  也就是本文的主题 LoadTimeWeaver, aspectj 很早就开始支持这个功能, 今天我们主要探讨一下 spring 基于 aspectj 的 LoadTimeWeaver 支持和一些常见问题. 

   spring load time weaver 主要通过以下步骤完成 : 

   1. 在启动程序的 jvm argument 中增加 spring-agent.jar 以获得 jvm 导出的 instrumentation 
   2. aspectj 拦截 domain object 的创建 
   3. 在 AnnotationBeanConfigurerAspect 中完成对 domain object 的注入 

下面详细说明 

1. Add spring-agent.jar to jvm argument 
   
   如果是命令行启动, 使用 java -javaagent:#{your path}/spring-agent.jar MyProgram 命令启动程序, 如果是 ide, 在 jvm argument 中增加 -javaagent:#{your path}/spring-agent.jar 即可. 

   增加这个参数的目的就是获取 jvm 导出的 instrumentation 引用以便后续操作的进行, 打开 spring-agent.jar 的 META-INF/MENIFEST.MF 会发现其中一句 : Premain-Class: org.springframework.instrument.InstrumentationSavingAgent, 没错, 根据 instrumentation 规范, Premain-Class 就是用于处理 instrumentation 的入口, 事实上 spring-agent.jar 里也只有这一个 class, 打开代码会发现非常简单 : 

Java代码   收藏代码
  1. public class InstrumentationSavingAgent {  
  2.   
  3.     private static volatile Instrumentation instrumentation;  
  4.   
  5.   
  6.     /** 
  7.      * Save the {@link Instrumentation} interface exposed by the JVM. 
  8.      */  
  9.     public static void premain(String agentArgs, Instrumentation inst) {  
  10.         instrumentation = inst;  
  11.     }  
  12.   
  13. }  



在 premain 方法里将 instrumentation 保存到 static 引用中以便后续访问. 

2. 配置 spring 支持 load time weaver 

Java代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.              xmlns:context="http://www.springframework.org/schema/context"  
  5.              xsi:schemaLocation="http://www.springframework.org/schema/beans  
  6.                                                 http://www.springframework.org/schema/beans/spring-beans.xsd  
  7.                                                 http://www.springframework.org/schema/context  
  8.                                                 http://www.springframework.org/schema/context/spring-context.xsd">  
  9.     <context:annotation-config />  
  10.     <context:load-time-weaver aspectj-weaving="on" />  
  11.       
  12.     <bean class="example.ltw.DefaultUserRepository" />  
  13.       
  14. </beans>  



通过 <context:load-time-weaver aspectj-weaving="on" /> 使 spring 开启 loadtimeweaver, 注意 aspectj-weaving 有三个选项 : on, off, auto-detect, 
建议设置为 on 以强制使用 aspectj, 如果设置为 auto-detect, spring 将会在 classpath 中查找 aspejct 需要的 META-INF/aop.xml, 如果找到则开启 aspectj weaving, 这个逻辑在 LoadTimeWeaverBeanDefinitionParser#isAspectJWeavingEnabled 方法中 

Java代码   收藏代码
  1. protected boolean isAspectJWeavingEnabled(String value, ParserContext parserContext) {  
  2.     if ("on".equals(value)) {  
  3.         return true;  
  4.     }  
  5.     else if ("off".equals(value)) {  
  6.         return false;  
  7.     }  
  8.     else {  
  9.         // Determine default...  
  10.         ClassLoader cl = parserContext.getReaderContext().getResourceLoader().getClassLoader();  
  11.         return (cl.getResource(ASPECTJ_AOP_XML_RESOURCE) != null);  
  12.     }  
  13. }  



3. Code of User 

Java代码   收藏代码
  1. @Configurable(autowire = Autowire.BY_TYPE)  
  2. public class User {  
  3.       
  4.     @Resource  
  5.     // 或使用 @Autowired  
  6.     private UserRepository userRepository;  
  7.       
  8.     public void save() {  
  9.         userRepository.save(this);  
  10.     }  
  11.   
  12. }  




4. 将 spring-agent.jar, spring-aspects.jar, aspectj-weaver.jar, aspectj-rt.jar 加入到 classpath 中, 运行期主要发生以下调用 : 

  • LoadTimeWeaverBeanDefinitionParser (spring.jar) // 解析配置
  • ->  AspectJWeavingEnabler (spring.jar) // 开启 aspectj weaving
  • ->  InstrumentationSavingAgent (spring-agent.jar)  // 获取 instrumentation
  • ->  InstrumentationLoadTimeWeaver#addTransformer (spring.jar) // 增加 aspectj class transformer 到 instrumentation
  • ->  ClassPreProcessorAgentAdapter#transform (aspectj-weaver.jar) // aspectj 拦截 domain object 装载
  • ->  AnnotationBeanConfigurerAspect#configureBean (spring-aspects.jar) // spring 注入依赖到标注了 @Configurable 的对象中



至此完成整个 load time weave 过程. 

注意前文中的 <context:annotation-config /> 并不是必须的, 如果不配置, userRepository 就不能用 annotation(@Resource 或 @Autowired) 注入而必须使用 set 方法. 

5. What's in spring-aspects.jar 

   spring-aspects.jat 是一个独立的 jar, 它并不被包含于常用的 spring.jar 中, 其中的 META-INF/aop.xml 定义了 aspectj 需要的配置, AnnotationBeanConfigurerAspect 负责注入依赖到标注了 @Configurable  domain object 中 : 

Java代码   收藏代码
  1. public pointcut inConfigurableBean() : @this(Configurable);  
  2.   
  3. public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*);   
  4.   
  5. declare parents: @Configurable * implements ConfigurableObject;  
  6.   
  7. public void configureBean(Object bean) {  
  8.  // 这里执行了 inject  
  9.     beanConfigurerSupport.configureBean(bean);  
  10. }  



附件是文中的示例项目, 运行 LoadTimeWeaverTest 即可. 

PS : Spring 也可以使用一些特定应用服务器的 ClassLoader 实现 LoadTimeWeaver, 如有兴趣请参考相应文档, 本文不再赘述. 

猜你喜欢

转载自gcc2ge.iteye.com/blog/2242662