`
Feiing
  • 浏览: 239254 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Spring LoadTimeWeaver 的那些事儿

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

public void save() {
	userRepository.save(this);
}


看起来很自然, 但如何取得 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, 打开代码会发现非常简单 :

public class InstrumentationSavingAgent {

	private static volatile Instrumentation instrumentation;


	/**
	 * Save the {@link Instrumentation} interface exposed by the JVM.
	 */
	public static void premain(String agentArgs, Instrumentation inst) {
		instrumentation = inst;
	}

}



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

2. 配置 spring 支持 load time weaver

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                                http://www.springframework.org/schema/beans/spring-beans.xsd
                                                http://www.springframework.org/schema/context
                                                http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config />
    <context:load-time-weaver aspectj-weaving="on" />
    
    <bean class="example.ltw.DefaultUserRepository" />
    
</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 方法中

	protected boolean isAspectJWeavingEnabled(String value, ParserContext parserContext) {
		if ("on".equals(value)) {
			return true;
		}
		else if ("off".equals(value)) {
			return false;
		}
		else {
			// Determine default...
			ClassLoader cl = parserContext.getReaderContext().getResourceLoader().getClassLoader();
			return (cl.getResource(ASPECTJ_AOP_XML_RESOURCE) != null);
		}
	}


3. Code of User

@Configurable(autowire = Autowire.BY_TYPE)
public class User {
	
	@Resource
	// 或使用 @Autowired
	private UserRepository userRepository;
	
	public void save() {
		userRepository.save(this);
	}

}



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 中 :

	public pointcut inConfigurableBean() : @this(Configurable);

	public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*); 

	declare parents: @Configurable * implements ConfigurableObject;

	public void configureBean(Object bean) {
  // 这里执行了 inject
		beanConfigurerSupport.configureBean(bean);
	}



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

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

王政 于 2009, 10, 5


 
分享到:
评论
8 楼 Feiing 2009-10-09  
mikab 写道
看起来非常酷,确实是一种解决domain对象的依赖注入的思路。

但是我看在你的代码里所有的Repositry都是private的,会不会导致你的domain对象脱离了你用spring构建起来的环境就基本不能用了?我觉得这个违背了对pojo的定义。


手动注入可以增加 setter method, 示例代码是为了简洁以及演示 annotation injection 的用法
7 楼 mikab 2009-10-09  
看起来非常酷,确实是一种解决domain对象的依赖注入的思路。

但是我看在你的代码里所有的Repositry都是private的,会不会导致你的domain对象脱离了你用spring构建起来的环境就基本不能用了?我觉得这个违背了对pojo的定义。
6 楼 Feiing 2009-10-08  
by5739 写道
需要在一个事物当中更新2个实体, 不知道该如何写代码?
例如出库动作,需要更新库存的同时产生一张最终出库单,这2个对象并没有直接的联系


将不自然属于任何一个 Entity 或 Value Object 的逻辑封装到无状态的 Service 中, eg


public class WarehourseService {

     @Transactional
     public void withdrawal(Cargo cargo, double amount) {
          cargo.updateInventory(amount);
          new WarehouseOrder(cargo).save();
     }

}

5 楼 by5739 2009-10-08  
需要在一个事物当中更新2个实体, 不知道该如何写代码?
例如出库动作,需要更新库存的同时产生一张最终出库单,这2个对象并没有直接的联系
4 楼 Feiing 2009-10-07  
didiluck 写道
我经常碰到的XXXRepository都是跨越多个实体的,要保持一个事务,像楼主这样注入到实体中,适用面不广吧,我觉得把Repository通过实体的构造函数或者是函数参数的方法引入到实体中也挺不错的。


保持一个事务跟跨越多个实体没什么关系吧, 只要把事务控制放在最外层就好了, 比如这样


new User().addRole(role).save();



例子中的 user 没有跟其他实体关联是想让代码更简洁, 毕竟只是为了演示 loadtimeweaver

至于你说的通过构造函数或方法注入当然是可以的, 但是 loadtimeweaver 就是为了省去这个步骤才出现的, 否则你代码里只要有构造实体的地方就会有大驼大驼的 inject 方法调用, 那就不是 dependency injection 而是 manual injection 了,  像这个样子


public class User {
     
    private Date birth;

     @Resource
     private UserRepositry userRepositry;
     @Resource
     private TImeService timeService;   

     public int getAge() {
          return timeService.currentTime().substract(birth).years();
     }

}



如果要调用 getAge 方法就必须需要手动注入 TimeService, 然后你的 repository 就变成了这样



public class UserRepository {

    public User load(id) {
         User user = hibernateTemplate.load(id);
         return inject(user);
    }

    public List<User> find() {
         return hibernateTemplate.find().transform(
              new Tranformer() {
                  public User tranform(User each) {
                     return inject(each);
                  }
              }
         );
     }

    private User inject(User user) {
       user.setUserRepository(userRepository);
       user.setTimeService(timeService);
       return user;
    }
}



对 inject 的调用在每一个构造实体的地方都要发生, 不但乏味而且容易遗漏, loadtimeweaver 要解决的正是这个问题 : 一次配置 到处使用
3 楼 didiluck 2009-10-07  
我经常碰到的XXXRepository都是跨越多个实体的,要保持一个事务,像楼主这样注入到实体中,适用面不广吧,我觉得把Repository通过实体的构造函数或者是函数参数的方法引入到实体中也挺不错的。
2 楼 Feiing 2009-10-06  
加入 xercesImpl 正常运行的话可能是 jdk 版本问题, 你是 1.5 or 1.6 ?  1.6 已经包含了 xml parser 应该不需要加
1 楼 ftj20003 2009-10-05  
试验的过程中,报错:
Caused by: java.lang.RuntimeException: Installation Problem??? Couldn't load messages: Can't find bundle for base name org.apache.xerces.impl.xpath.regex.message, locale zh_CN
后来跟踪并且上网查了相关的问题,加入xercesImpl.jar即可正常运行了,但是不知道具体的原因,请问lz碰到过吗?能不能解释一下产生这个错误的原因。。。

相关推荐

    spring3.0+hibernate3.5整合那些事儿

    本文将深入探讨"Spring 3.0 + Hibernate 3.5整合那些事儿",结合给出的标签"源码"和"工具",我们将讨论如何将这两个强大的框架结合在一起,以及在整合过程中可能遇到的问题和解决方案。 首先,Spring是一个全面的...

    java那些事儿chm

    在这个压缩包中,包含的文件名为“java那些事儿.chm”。 Java,作为世界上最流行的编程语言之一,拥有广泛的应用领域,从企业级应用到移动开发,无处不在。这个CHM文档很可能是对Java基础知识、进阶概念、实战技巧...

    Spring Boot整合Spring Batch,实现批处理

    在Java开发领域,Spring Boot和Spring Batch的整合是构建高效批处理系统的一种常见方式。Spring Boot以其简洁的配置和快速的启动能力深受开发者喜爱,而Spring Batch作为Spring框架的一部分,专注于批量处理任务,...

    spring2.0升级到spring3.0.5的开发包

    Spring框架是Java应用程序开发中的一个核心组件,它提供了一个丰富的IOC(Inversion of Control,控制反转)和AOP(Aspect-Oriented Programming,面向切面编程)功能,使得开发者能够更方便地管理对象和实现模块化...

    spring-agent.jar

    For environments where class instrumentation is required but are not supported by the existing LoadTimeWeaver implementations, a JDK agent can be the only solution. For such cases, Spring provides ...

    spring-instrument-tomcat源码

    这个模块主要实现了`org.springframework.instrument.classloading`包下的接口,例如`LoadTimeWeaver`,它的作用是在类加载时进行代码修改或增强,这在AOP(面向切面编程)和代理类生成等场景下尤其有用。...

    Spring Cloud Gateway 整合 Spring Security 统一登录认证鉴权

    在构建分布式系统时,Spring Cloud Gateway 作为微服务架构中的边缘服务或 API 网关,扮演着至关重要的角色。它负责路由请求到相应的微服务,并可以提供过滤器功能,如限流、熔断等。而Spring Security 则是 Java ...

    spring-instrument源码

    在Spring Instrument源码中,我们可以看到`org.springframework.instrument.classloading.LoadTimeWeaver`接口扮演着关键角色。它定义了如何在类加载时进行织入(weaving),即在类加载到内存之前或之后插入额外的...

    SpringBatch+Spring+Mybatis+MySql (spring batch 使用jar)

    Spring Batch是一个轻量级的,完全面向Spring的批处理框架,可以应用于企业级大量的数据处理系统。Spring Batch以POJO和大家熟知的Spring框架为基础,使开发者更容易的访问和利用企业级服务。Spring Batch可以提供...

    Getting started with Spring Framework: covers Spring 5(epub)

    Getting started with Spring Framework (4th Edition) is a hands-on guide to begin developing applications using Spring Framework 5. The examples (consisting of 88 sample projects) that accompany this ...

    Spring Framework 6 中文文档

    Spring Framework 是Java开发中的核心框架,它以其强大的功能和易用性成为了许多...对于那些寻找高质量Spring中文资料的人来说,这是一个值得信赖的在线资源,无需注册或关注,直接在https://springdoc.cn/ 就能访问。

    spring 4.3.14(全)最新的spring4正式版

    这个版本在Spring 5.0发布之前提供了一个稳定可靠的平台,尤其对于那些尚未准备好升级到Java 8或Spring 5的项目来说,它是理想的选择。 Spring框架的核心特性包括依赖注入(Dependency Injection,DI),面向切面...

    spring3.1 官方全部jar包

    spring3.1官方所有的jar包 org.springframework.aop-3.1.RELEASE.jar org.springframework.asm-3.1.RELEASE.jar org.springframework.aspects-3.1.RELEASE.jar org.springframework.beans-3.1.RELEASE.jar org....

    spring_in_action-sixth-edition.pdf

    "Spring 实战第六版" Spring Framework 是一个广泛使用的 Java 应用程序框架,它提供了一个通用的编程模型和配置机制,帮助开发者快速构建企业级应用程序。下面是对 Spring Framework 的详细知识点总结: 1. 什么...

    Spring cloud与Spring boot 集成完整案例

    Spring Cloud和Spring Boot是两个非常重要的Java开发框架,它们在微服务架构中扮演着核心角色。Spring Boot简化了创建独立的、生产级别的基于Spring的应用程序的过程,而Spring Cloud则为开发者提供了快速构建分布式...

    spring-mock.jar

    Classes contained in spring-mock.jar: org.springframework.mock.jndi.ExpectedLookupTemplate.class org.springframework.mock.jndi.SimpleNamingContext.class org.springframework.mock.jndi....

    spring4.3.1官方全套jar包下载

    spring4.3.1全套jar下载。 4.3.1/spring-aop-4.3.1.RELEASE.jar 4.3.1/spring-aspects-4.3.1.RELEASE.jar 4.3.1/spring-beans-4.3.1.RELEASE.jar 4.3.1/spring-context-4.3.1.RELEASE.jar 4.3.1/spring-core-4.3.1....

    spring整合rabbitmq需要的jar包(spring版本4.2.0)

    在IT行业中,Spring框架是Java应用开发中的一个关键组件,它提供了一整套服务来简化企业级应用的构建。RabbitMQ则是一个流行的开源消息队列系统,它基于AMQP(Advanced Message Queuing Protocol)协议,用于高效地...

    spring3.0.5 所有jar文件

    包含spring 3.0.5的所有jar文件: org.springframework.aop-3.0.5.RELEASE.jar org.springframework.asm-3.0.5.RELEASE.jar org.springframework.aspects-3.0.5.RELEASE.jar org.springframework.beans-3.0.5.RELEASE...

Global site tag (gtag.js) - Google Analytics