当前位置: 首页 > 知识库问答 >
问题:

如何使用AOP为JPA实体中的切入点设置者提供建议?

殷永嘉
2023-03-14

我需要记录对实体中字段的任何更改—无论是字符串更改,还是对集合/映射的添加/删除。

给定一个带有一组基本字段的JPA实体,编写一个切入点来截取任何集合(..)是相当简单的字段上的方法。

然而,我陷入困境的是如何编写切入点来处理集合/集合/嵌入等。

鉴于以下实体:

@Entity
public class Provider implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;


    private String name;

    @Column(name="type", nullable=false)
    @Enumerated(EnumType.STRING)
    private ProviderType providerType;


    @ManyToMany
    private List<Contact> contacts;

    @Embedded
    private Validity validity;

   // setters and getters omitted for brevity

}

其中,联系人是一个简单的实体,有一堆原始字段,有效性是一个非实体对象,有一些原始字段。

以下切入点将截取类中的所有set()方法:

pointcut fieldSetter() : set(!static !final !transient * *.Provider) && args(val) && target(o);

对此我可以写一个之前/之后/周围的建议。

before( Object val, Object o) : fieldSetter{
  String fieldName = thisJoinPointStaticPart.getSignature().getName();
  System.out.println( "Object being changed: " + o.toString() );
  System.out.println( "New Value for: " + fieldname + " is: " + v.toString() );
}

但是我如何处理嵌入式对象或集合的情况?对于嵌入式对象,如果我只是把我的建议放在对象中的setter方法周围,我如何知道哪个是实际被修改/持久化的父对象?

在集合/集合/映射/等的情况下,我如何建议不要使用添加/删除方法?我最终需要做的是建议getCollection(). add()方法以及getCollection.remove()方法。但是我似乎想不出一个好办法。

共有1个答案

纪畅
2023-03-14

这不能直接完成,只能通过手动簿记,因为集合或映射在调用方法时不会更改其标识,只更改其内部状态,即没有要拦截的set()joinpoint,只有方法调用。因此,您需要维护分配给您感兴趣的对象成员的集合/映射之间的映射,并跟踪它们的更改,这相当繁琐。下面是一些用于集合的示例代码,其中包含概念验证。添加()映射。put()。您必须将其扩展到所有更改内部状态的方法,例如remove()clear()等。基本上,它是这样工作的:

驱动类:

这是一个示例Person类,包含两个基本成员、两个集合和一个映射。信息技术

  • 为所有Person成员指定默认值,
package de.scrum_master.app;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Person {
    int id;
    String name;
    List<Object> objects = new ArrayList<>();
    Set<Integer> numbers = new HashSet<>();
    Map<String, Object> properties = new HashMap<>();

    public Person(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person[id=" + id + ", name=" + name + "]";
    }

    public static void main(String[] args) {
        System.out.println("Creating Person object");
        Person person = new Person(2, "Werner Heisenberg");

        System.out.println("\nChanging member object states");
        person.id = 1;
        person.name = "Albert Einstein";
        person.objects.add("foo");
        person.objects.add(11);
        person.objects.add(new Object());
        person.numbers.add(11);
        person.numbers.add(22);
        person.numbers.add(33);
        person.properties.put("year of birth", 1879);
        person.properties.put("year of death", 1955);
        person.properties.put("well known for", new String[] { "Photoelectric Effect", "Special Relativity", "General Relativity" });

        System.out.println("\nUnassigning member objects");
        List<Object> objects = person.objects;
        person.objects = null;
        Set<Integer> numbers = person.numbers;
        person.numbers = null;
        Map<String, Object> properties = person.properties;
        person.properties = null;

        System.out.println("\nChanging non-member object states");
        objects.add("bar");
        objects.add(22);
        objects.add(new Object());
        numbers.add(44);
        numbers.add(55);
        numbers.add(66);
        properties.put("Nobel Prize year", 1921);

        System.out.println("\nReassigning member objects");
        person.objects = objects;
        person.numbers = numbers;
        person.properties = properties;

        System.out.println("\nChanging member object states again");
        person.objects.add("zot");
        person.objects.add(33);
        person.objects.add(new Object());
        person.numbers.add(77);
        person.numbers.add(88);
        person.numbers.add(99);
        person.properties.put("Time Person of the Century year", 1999);
    }
}

直接/间接成员更改的日志方面:

这方面截获

  • 直接成员更改(set()pointcut targetingPersonobjects),

aspect在其成员属性映射中还保留了相当复杂的数据结构

更新:

  • 丑陋的getOldFieldValue()helper方法是必需的,因为AspectJ不会在set()切入点中公开旧值,而只公开新值。因此,它需要通过反射来确定
  • 因为JDK没有泛型pair/tuple类,并且我不想使用list/vector来保存值对,所以我使用AbstractMap。SimpleEntry。此外,它的equals()方法保证将具有相等键和值的对视为相等,因此我可以创建一个新实例并在映射中使用它。remove()call-无需通过迭代搜索现有值。以防万一你想知道
package de.scrum_master.aspect;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;

import org.aspectj.lang.Signature;
import org.aspectj.lang.SoftException;

import de.scrum_master.app.Person;

public aspect MemberChangeLogger {
    private Map<Object, Set<Entry<Person, String>>> members =
        Collections.synchronizedMap(
            new IdentityHashMap<Object, Set<Entry<Person, String>>>()
        );

    private Object getOldFieldValue(Signature signature, Person person) {
        Field field;
        try {
            field = signature.getDeclaringType().getDeclaredField(signature.getName());
        }
        catch (Exception e) { throw new SoftException(e); }
        field.setAccessible(true);
        try {
            return field.get(person); 
        }
        catch (Exception e) { throw new SoftException(e); }
    }

    pointcut directMemberChange(Person person, Object newValue) :
        set(* Person.*) &&
        args(newValue) &&
        target(person);

    pointcut collectionChange(Collection collection, Object newElement) :
        !cflow(adviceexecution()) &&
        call(* Collection+.add(*)) &&
        args(newElement) &&
        target(collection);

    pointcut mapChange(Map map, Object key, Object value) :
        !cflow(adviceexecution()) &&
        call(* Map+.put(*, *)) &&
        args(key, value) &&
        target(map);

    before(Person person, Object newValue) : directMemberChange(person, newValue) {
        String fieldName = thisJoinPointStaticPart.getSignature().getName();
        System.out.println(
            "Direct field change: " +
            person + " -> " + fieldName + " = " + newValue
        );
        Object oldValue = getOldFieldValue(thisJoinPoint.getSignature(), person);
        if (!(
            newValue instanceof Collection || newValue instanceof Map ||
            oldValue instanceof Collection || oldValue instanceof Map
        ))
            return;
        if (oldValue != null && members.get(oldValue) != null) {
            members.get(oldValue).remove(new SimpleEntry<Person, String>(person, fieldName));
            if (members.get(oldValue).size() == 0)
                members.remove(oldValue);
        }
        if (newValue == null)
            return;
        if (members.get(newValue) == null)
            members.put(newValue, new HashSet<Map.Entry<Person, String>>());
        members.get(newValue).add(new SimpleEntry<Person, String>(person, fieldName));
    }

    before(Collection collection, Object newElement) : collectionChange(collection, newElement) {
        if (members.get(collection) == null)
            return;
        for (Entry<Person, String> entry : members.get(collection)) {
            System.out.println(
                "Indirect field change: " +
                entry.getKey() + " -> " + entry.getValue() +
                " -> adding element " + newElement + " to " + collection
            );
        }
    }

    before(Map map, Object key, Object value) : mapChange(map, key, value) {
        if (members.get(map) == null)
            return;
        for (Entry<Person, String> entry : members.get(map)) {
            System.out.println(
                "Indirect field change: " +
                entry.getKey() + " -> " + entry.getValue() +
                " -> putting entry (" + key + "=" + value + ") into " + map
            );
        }
    }
}

控制台输出:

如果你运行的Person.main()与方面编织,输出应该如下:

Creating Person object
Direct field change: Person[id=0, name=null] -> objects = []
Direct field change: Person[id=0, name=null] -> numbers = []
Direct field change: Person[id=0, name=null] -> properties = {}
Direct field change: Person[id=0, name=null] -> id = 2
Direct field change: Person[id=2, name=null] -> name = Werner Heisenberg

Changing member object states
Direct field change: Person[id=2, name=Werner Heisenberg] -> id = 1
Direct field change: Person[id=1, name=Werner Heisenberg] -> name = Albert Einstein
Indirect field change: Person[id=1, name=Albert Einstein] -> objects -> adding element foo to []
Indirect field change: Person[id=1, name=Albert Einstein] -> objects -> adding element 11 to [foo]
Indirect field change: Person[id=1, name=Albert Einstein] -> objects -> adding element java.lang.Object@69d30fe7 to [foo, 11]
Indirect field change: Person[id=1, name=Albert Einstein] -> numbers -> adding element 11 to []
Indirect field change: Person[id=1, name=Albert Einstein] -> numbers -> adding element 22 to [11]
Indirect field change: Person[id=1, name=Albert Einstein] -> numbers -> adding element 33 to [22, 11]
Indirect field change: Person[id=1, name=Albert Einstein] -> properties -> putting entry (year of birth=1879) into {}
Indirect field change: Person[id=1, name=Albert Einstein] -> properties -> putting entry (year of death=1955) into {year of birth=1879}
Indirect field change: Person[id=1, name=Albert Einstein] -> properties -> putting entry (well known for=[Ljava.lang.String;@1fb93cf8) into {year of birth=1879, year of death=1955}

Unassigning member objects
Direct field change: Person[id=1, name=Albert Einstein] -> objects = null
Direct field change: Person[id=1, name=Albert Einstein] -> numbers = null
Direct field change: Person[id=1, name=Albert Einstein] -> properties = null

Changing non-member object states

Reassigning member objects
Direct field change: Person[id=1, name=Albert Einstein] -> objects = [foo, 11, java.lang.Object@69d30fe7, bar, 22, java.lang.Object@3a51ce0d]
Direct field change: Person[id=1, name=Albert Einstein] -> numbers = [33, 55, 66, 22, 11, 44]
Direct field change: Person[id=1, name=Albert Einstein] -> properties = {year of birth=1879, Nobel Prize year=1921, year of death=1955, well known for=[Ljava.lang.String;@1fb93cf8}

Changing member object states again
Indirect field change: Person[id=1, name=Albert Einstein] -> objects -> adding element zot to [foo, 11, java.lang.Object@69d30fe7, bar, 22, java.lang.Object@3a51ce0d]
Indirect field change: Person[id=1, name=Albert Einstein] -> objects -> adding element 33 to [foo, 11, java.lang.Object@69d30fe7, bar, 22, java.lang.Object@3a51ce0d, zot]
Indirect field change: Person[id=1, name=Albert Einstein] -> objects -> adding element java.lang.Object@50aed564 to [foo, 11, java.lang.Object@69d30fe7, bar, 22, java.lang.Object@3a51ce0d, zot, 33]
Indirect field change: Person[id=1, name=Albert Einstein] -> numbers -> adding element 77 to [33, 55, 66, 22, 11, 44]
Indirect field change: Person[id=1, name=Albert Einstein] -> numbers -> adding element 88 to [33, 55, 66, 22, 77, 11, 44]
Indirect field change: Person[id=1, name=Albert Einstein] -> numbers -> adding element 99 to [33, 55, 66, 22, 77, 11, 88, 44]
Indirect field change: Person[id=1, name=Albert Einstein] -> properties -> putting entry (Time Person of the Century year=1999) into {year of birth=1879, Nobel Prize year=1921, year of death=1955, well known for=[Ljava.lang.String;@1fb93cf8}

正如您所看到的,“更改非成员对象状态”一节中没有任何输出,正如预期的那样。但是“更改成员对象状态”和“再次更改成员对象状态”部分中的add()/put()调用被记录为“间接字段更改:Person[…”。基本上这就是你想要实现的,但我个人认为,除了是一个很好的练习,它可能有点慢,是一个维护噩梦,但可行。

 类似资料:
  • 是否有一种方法可以使用spring来指向cut hibernate实体。 这是我发现的,但没有解决办法

  • 我在一个类中有一个方法,它被其他各种方法调用。我已经为这个方法创建了一个切入点,around建议记录了这个方法的所有执行细节,比如当前执行时间、最大执行时间等,或者任何异常。 这是拦截器: 我想知道哪个方法正在调用切入点方法,以便我可以记录它或根据调用者对执行统计信息进行分组。 如何使用Spring AOP获取呼叫者的姓名?

  • 我定义了这个类方面,它对服务很好(除非用户有角色管理器,否则不允许访问,但对控制器(?)没有限制 和ImportController: 当我作为userrole.manager登录时,将调用before(joinpointjp)方法,否则不是 我明白了,当我只是在浏览器中粘贴URL时,前面的方法(joinpointjp)没有被调用....

  • 我有3个实体- > 课程 组件 时间线课程是一个独立的实体,具有以下属性:课程-(id Integer主键,Course_name) @ Id @ Column(name = " Id ")Integer courseId;@Column(name = "course_name ")字符串course _ name; 接下来是另一个实体模块,模块中的每一行都与一门课程相关,因此模块和课程之间存在一

  • 这会产生一个错误,即:- 原因:组织。冬眠hql。内部的ast。QuerySyntaxException:意外的令牌::靠近第1行第15列[select c from:entity c]at org。冬眠hql。内部的ast。QuerySyntaxException。convert(QuerySyntaxException.java:91)~[hibernate-core-4.3.11.Final

  • 我希望在每次加载或持久化实体时包装/展开它。我知道我不能使用JPA监听器来完成这件事,因为它们只能对对象执行一个操作,而不能与其他对象交换它。自然的解决方案是使用方面。但是有没有特别的方法我可以切入呢?问题是要包装/解包装的实体可以是另一个实体的字段...