Grails探索系列之domain class的编译期属性注入

Grails会在编译期往domain class中注入id和version两个属性(在编译后的class文件中可以发现它们)。它是怎么做到的呢?

 

搜索Grails的源代码,有如下发现:

 

1. 找到一个叫做DefaultGrailsDomainClassInjector的类,其中有

 

    public void performInjection(SourceUnit source, GeneratorContext context,
                                 ClassNode classNode) {
        if (isDomainClass(classNode, source) && shouldInjectClass(classNode)) {
            performInjectionOnAnnotatedEntity(classNode);
        }
    }

    public void performInjectionOnAnnotatedEntity(ClassNode classNode) {
            injectIdProperty(classNode);

            injectVersionProperty(classNode);
            
            injectToStringMethod(classNode);

            injectAssociations(classNode);
    }

    // ...

 

2. 找到java类GlobalEntityASTTransformation,其中有这么两句:

GrailsDomainClassInjector domainInjector = new DefaultGrailsDomainClassInjector();
domainInjector.performInjectionOnAnnotatedEntity(cNode);
 

3. 文件META-INF/services/org.codehaus.groovy.transform.ASTTransformation中有

org.codehaus.groovy.grails.compiler.injection.GlobalEntityASTTransformation

 

4. 进一步搜索Groovy的源代码,类ASTTransformationVisitor中有

Enumeration<URL> globalServices = transformLoader.getResources("META-INF/services/org.codehaus.groovy.transform.ASTTransformation");
while (globalServices.hasMoreElements()) {
    // ...
}
 

貌似找到了答案:Grails在执行groovyc的时候,从 META-INF/services/org.codehaus.groovy.transform.ASTTransformation文件中读取到org.codehaus.groovy.grails.compiler.injection.GlobalEntityASTTransformation -> 调用它的visit方法 -> 调用DefaultGrailsDomainClassInjector的performInjection方法,实现domain class的id和version属性注入。如果这个假设成立,那么我们只要修改DefaultGrailsDomainClassInjector类的performInjectionOnAnnotatedEntity方法,就可以注入更多的属性,比如

    public void performInjectionOnAnnotatedEntity(ClassNode classNode) {
            // ...
            
            // added by S.C. on 26May2010
            injectDeletedProperty(classNode);
            injectDateCreatedProperty(classNode);
            injectHistoryProperty(classNode);
            
            // ...
    }

应该可以注入deleted, dateCreated, history这三个属性。

 

权且试试,把编译后得到的类拷贝进%GRAILS_HOME%/dist/grails-core-1.2.2.jar,覆盖之。执行grails clean, grails run-app...结果很令人沮丧,domain class中deleted, dateCreated, history这三个属性一个都没有。(期间还碰到一个问题,让我现在依然摸不着头脑:执行grails war,得到的war中WEB-INF/lib下的grails-core-1.2.2.jar竟然还是未被修改过的!它是从哪里来的呢?dist下的确定已经修改成功了,源代码也改了,甚至网线也断掉了,不可能从dist拷贝过来,也不是从src编译过来然后打的jar包,也不可能从grails网站下载 -- 它到底是从哪来的呢?????? 这个问题权且放一放。)

 

结论:假设不成立。

 

怎么才能让domain class编译得到的字节码中含有我需要的deleted, dateCreated, history属性呢?(我不想在写代码的时候往每个domain class中都写上这三个属性,这样会让我感觉很不爽。我只想利用groovy的AST编译期注入)

 

纠结了几天,今天终于另辟蹊径,实现了这个需求。上代码(注:以下部分代码由grails的源代码片段修改而成):

 

package com.grs.ast;

import org.codehaus.groovy.transform.GroovyASTTransformationClass;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@GroovyASTTransformationClass("com.grs.ast.GrsDomainTransformation")
public @interface Auditable {
}
 
package com.grs.ast;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
import org.codehaus.groovy.grails.compiler.injection.GrailsASTUtils;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;

import java.lang.reflect.Modifier;
import java.util.ArrayList;

@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class GrsDomainTransformation implements ASTTransformation {

    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
        for (ASTNode node : nodes) {
            if (!(node instanceof ClassNode)) {
                continue;
            }
            try {
                transform((ClassNode) node);
            }
            catch(Exception exception){

            }
            break;
        }
    }

    private void transform(ClassNode classNode) {
        injectDeletedProperty(classNode);
        injectDateCreatedProperty(classNode);
        injectHistoryProperty(classNode);
    }

    private void injectDeletedProperty(ClassNode classNode) {
        final boolean hasDeleted = GrailsASTUtils.hasOrInheritsProperty(classNode, GrsDomainClassProperty.DELETED);

        if (!hasDeleted) {
            classNode.addProperty(GrsDomainClassProperty.DELETED, Modifier.PUBLIC, new ClassNode(Boolean.class), null, null, null);
        }
    }
    private void injectDateCreatedProperty(ClassNode classNode) {
        final boolean hasDateCreated = GrailsASTUtils.hasOrInheritsProperty(classNode, GrailsDomainClassProperty.DATE_CREATED);

        if (!hasDateCreated) {
            classNode.addProperty(GrailsDomainClassProperty.DATE_CREATED, Modifier.PUBLIC, new ClassNode(java.util.Date.class), null, null, null);
        }
    }
    private void injectHistoryProperty(ClassNode classNode) {
        final boolean hasHistory = GrailsASTUtils.hasOrInheritsProperty(classNode, GrsDomainClassProperty.HISTORY);

        if (!hasHistory) {
            classNode.addProperty(GrsDomainClassProperty.HISTORY, Modifier.PUBLIC, new ClassNode(ArrayList.class), null, null, null);
        }
    }

}
 
package com.grs.timesheet

import com.grs.ast.Auditable

@Auditable
class Project {
    String name
    String description
    static constraints = {
    }
}

 

在IntelliJ中查看target/classes/com/grs/timesheet/Project.class,看到

// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package com.grs.timesheet;

@com.grs.ast.Auditable
public static class Project  implements groovy.lang.GroovyObject{
    private java.lang.String name;
    private java.lang.String description;
    private static java.lang.Object constraints;
    java.lang.Long id;
    java.lang.Long version;
    java.lang.Boolean deleted;
    java.util.Date dateCreated;
    java.util.ArrayList history;
    
    public Project() { /* compiled code */ }
    
    public java.lang.String toString() { /* compiled code */ }
    
    public java.lang.String getName() { /* compiled code */ }
    
    public void setName(java.lang.String s) { /* compiled code */ }
    
    public java.lang.String getDescription() { /* compiled code */ }
    
    public void setDescription(java.lang.String s) { /* compiled code */ }
    
    public static java.lang.Object getConstraints() { /* compiled code */ }
    
    public static void setConstraints(java.lang.Object o) { /* compiled code */ }
    
    public java.lang.Long getId() { /* compiled code */ }
    
    public void setId(java.lang.Long aLong) { /* compiled code */ }
    
    public java.lang.Long getVersion() { /* compiled code */ }
    
    public void setVersion(java.lang.Long aLong) { /* compiled code */ }
    
    public java.lang.Boolean getDeleted() { /* compiled code */ }
    
    public void setDeleted(java.lang.Boolean aBoolean) { /* compiled code */ }
    
    public java.util.Date getDateCreated() { /* compiled code */ }
    
    public void setDateCreated(java.util.Date date) { /* compiled code */ }
    
    public java.util.ArrayList getHistory() { /* compiled code */ }
    
    public void setHistory(java.util.ArrayList arrayList) { /* compiled code */ }
}

 

deleted, dateCreated,history这三个属性已经成功注入,同时对应的getter/setter(属于boilerplate code)也有了。

 

Cheers!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值