7

メソッドにコードを自動的に挿入する方法はありますか?

ゲッターとセッターを備えた次の典型的なフィールドがあり、フィールドが変更されたかどうかを記録するセッターメソッドに示されたコードを挿入し、フィールドが変更されたかどうかを追跡するために示された「isFirstNameModified」フィールドを挿入したいと思います。いいえ。

 public class Person {

      Set<String> updatedFields = new LinkedHashSet<String>();

      String firstName;
      public String getFirstName(){
           return firstName;
      }

      boolean isFirstNameChanged = false;           // This code is inserted later
      public void setFirstName(String firstName){       
           if( !isFirstNameChanged ){               // This code is inserted later
                isFirstNameChanged = true;          // This code is inserted later
                updatedFields.add("firstName");     // This code is inserted later
           }                                        // This code is inserted later
           this.firstName = firstName;
      }
 }

また、更新されたフィールドのセットに文字列としてfieldNameを追加した行に示されているように、メソッド自体の内部からメソッド名のサブセットを文字列として使用できるかどうかもわかりませんupdatedFields.add("firstName");。また、フィールドが以前に変更されたかどうかを追跡するブールフィールドを追加するクラスにフィールドを挿入する方法がわかりません(セットを操作する必要をなくすために):boolean isFirstNameChanged = false;

これに対する最も明白な答えは、Eclipse内でコードテンプレートを使用することであるように思われますが、後で戻ってコードを変更する必要があるのではないかと心配しています。

編集:::::::::

上記の例の代わりに、この単純なコードを使用する必要がありました。フィールドの名前を文字列としてセットに追加するだけです。

 public class Person {

  Set<String> updatedFields = new LinkedHashSet<String>();

  String firstName;
  public String getFirstName(){
       return firstName;
  }
  public void setFirstName(String firstName){       
       updatedFields.add("firstName");        // This code is inserted later
       this.firstName = firstName;
  }

}

4

3 に答える 3

6

はい、できます。1つのアプローチは、何らかの形式のバイトコード操作(javassistASM、BCELなど)またはこれらのツールの1つ(AspectJ、JBoss AOPなど)の上にある高レベルのAOPライブラリを使用することです。

注:ほとんどのJDOライブラリは、永続性を処理するためにこれを行います。

javassistを使用した例を次に示します。

public class Person {

    private String firstName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

public static void rewritePersonClass() throws NotFoundException, CannotCompileException {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctPerson = pool.get("Person");
    CtClass ctSet = pool.get("java.util.LinkedHashSet");

    CtField setField = new CtField(ctSet, "updatedFields", ctPerson);
    ctPerson.addField(setField, "new java.util.LinkedHashSet();");

    CtMethod method = ctPerson.getDeclaredMethod("setFirstName");
    method.insertBefore("updatedFields.add(\"firstName\");");

    ctPerson.toClass();
}


public static void main(String[] args) throws Exception {
    rewritePersonClass();

    Person p = new Person();
    p.setFirstName("foo");

    Field field = Person.class.getDeclaredField("updatedFields");
    field.setAccessible(true);
    Set<?> s = (Set<?>) field.get(p);

    System.out.println(s);
}
于 2010-06-13T06:21:37.870 に答える
3

AspectJを使用すると、アドバイスを使用してメソッドとフィールドを変更できます。

私の例は@AspectJ、コンパイル時またはロード時にコードを変更する構文で書かれています。@AspectJ実行時に変更が必要な場合は、この構文もサポートするSpringAOPを使用できます。

単純なPersonクラスとスタブリポジトリの例。どのフィールドが更新されるかに関するすべての情報は、SetterAspectと呼ばれるアスペクトによって処理されます。フィールドが書き込まれるときに更新されるフィールドを監視します。

この例の他のアドバイスは、リポジトリの更新メソッドに関するものです。これは、最初の側面から収集されたデータを取得するためのものです。

Personクラス:

public class Person {

    private String firstName;

    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }   

    public static void main(String[] args) {
        Person person = new Person();
        person.setFirstName("James");
        person.lastName = "Jameson";

        DtoRepository<Person> personRepository = new DtoRepository<Person>();
        personRepository.update(person);
    }
}

スタブリポジトリ:

public class DtoRepository<T> {

    public void update(T t) {
        System.out.println(t.getClass().getSimpleName() + " updated..");
    }

    public void updatePerson(T t, Set<String> updatedFields) {
        System.out.print("Updated the following fields on " +
            t.getClass().getSimpleName() + " in the repository: "
            + updatedFields);       
    }
}

main()AspectJを使用してPersonクラスのメソッドを実行するための出力:

リポジトリ内のPersonの次のフィールドを更新しました:[lastName、firstName]

ここで重要なのは、main()メソッドが呼び出しますDtoRepository.update(T t)DtoRepository.update(T t, Set<String> updatedFields)、リポジトリの側面でのアドバイスのために実行されるということです。

デモパッケージのプライベートフィールドへのすべての書き込みを監視するアスペクト:

@Aspect
public class SetterAspect {

    private UpdatableDtoManager updatableDtoManager = 
        UpdatableDtoManager.INSTANCE;

    @Pointcut("set(private * demo.*.*)")
    public void setterMethod() {}

    @AfterReturning("setterMethod()")
    public void afterSetMethod(JoinPoint joinPoint) {
        String fieldName = joinPoint.getSignature().getName();
        updatableDtoManager.updateObjectWithUpdatedField(
                fieldName, joinPoint.getTarget());      
    }
}

リポジトリの側面:

@Aspect
public class UpdatableDtoRepositoryAspect {

    private UpdatableDtoManager updatableDtoManager = 
        UpdatableDtoManager.INSTANCE;

    @Pointcut("execution(void demo.DtoRepository.update(*)) " +
            "&& args(object)")
    public void updateMethodInRepository(Object object) {}

    @Around("updateMethodInRepository(object)")
    public void aroundUpdateMethodInRepository(
            ProceedingJoinPoint joinPoint, Object object) {

        Set<String> updatedFields = 
            updatableDtoManager.getUpdatedFieldsForObject(object);

        if (updatedFields.size() > 0) {
            ((DtoRepository<Object>)joinPoint.getTarget()).
                updatePerson(object, updatedFields);
        } else {

            // Returns without calling the repository.
            System.out.println("Nothing to update");
        }
    }   
}

最後に、アスペクトで使用される2つのヘルパークラス:

public enum UpdatableDtoManager {

    INSTANCE;

    private Map<Object, UpdatedObject> updatedObjects = 
        new HashMap<Object, UpdatedObject>();

    public void updateObjectWithUpdatedField(
            String fieldName, Object object) {
        if (!updatedObjects.containsKey(object)) {
            updatedObjects.put(object, new UpdatedObject());
        }

        UpdatedObject updatedObject = updatedObjects.get(object);
        if (!updatedObject.containsField(fieldName)) {
            updatedObject.add(fieldName);
        }
    }

    public Set<String> getUpdatedFieldsForObject(Object object) {
        UpdatedObject updatedObject = updatedObjects.get(object);

        final Set<String> updatedFields;
        if (updatedObject != null) {
            updatedFields = updatedObject.getUpdatedFields();
        } else {
            updatedFields = Collections.emptySet();
        }

        return updatedFields;
    }
}

public class UpdatedObject {

    private Map<String, Object> updatedFields = 
        new HashMap<String, Object>();

    public boolean containsField(String fieldName) {
        return updatedFields.containsKey(fieldName);
    }

    public void add(String fieldName) {
        updatedFields.put(fieldName, fieldName);        
    }

    public Set<String> getUpdatedFields() {
        return Collections.unmodifiableSet(
                updatedFields.keySet());
    }
}

私の例では、すべての更新ロジックをアスペクトで実行しています。すべてのDTOがを返すインターフェイスを実装している場合Set<String>は、最後の側面を回避できたはずです。

これがあなたの質問に答えたことを願っています!

于 2010-06-13T12:23:53.640 に答える
1

動的プロキシクラスを使用して、setFirstNameおよび他のメソッドを呼び出す前にイベントを取得し、=> "FirstName"でset...フィールド名を決定し、それをに入れることができます。method.substring(3)setFirstName

于 2010-06-13T06:17:52.093 に答える