- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我需要记录对实体中字段的任何更改 - 无论是字符串更改,还是对集合/映射的添加/删除。
给定一个带有一堆原始字段的 JPA 实体,编写一个切入点来拦截字段上的任何 set(..) 方法是相当简单的。
但是,我遇到的问题是如何编写切入点来处理集合/集合/嵌入/等。
给定以下实体:
@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
}
其中 Contact 是一个带有一堆原始字段的简单实体,而 Validity 是一个带有一些原始字段的非实体对象。
以下切入点将拦截类中的所有 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 方法周围,我如何知道哪个是实际被修改/保留的父对象?
对于集合/集合/ map /等,我如何建议不要使用添加/删除方法?我最终需要做的是建议 getCollection().add() 方法以及 getCollection.remove() 方法。但我似乎想不出什么好办法。
最佳答案
这不能直接完成,只能通过手动簿记来完成,因为集合或映射不会更改其身份,只会更改其调用方法时的内部状态,即没有 set()
joinpoint要被拦截,只有方法调用。因此,您需要维护分配给您感兴趣的对象成员的集合/映射之间的映射并跟踪它们的更改,这是相当乏味的。以下是一些示例代码,其中包含适用于 Collection.add()
的概念证明。和Map.put()
。您必须为所有更改内部状态的方法扩展它,例如remove()
, clear()
基本上它的工作原理是这样的:
驱动程序类别:
这是一个示例 Person
具有两个原始成员、两个集合和一个映射的类。它
Person
分配默认值成员(member),Person
成员,将它们保存在局部变量中,Person
成员),Person
成员(member),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()
切入点定位 Person
对象),Collection+.add()
,Map+.put()
.该方面还在其members
中保留了相当复杂的数据结构。属性:a Map<Object, Set<Entry<Person, String>>>
使用集合/映射作为键和Person
对和String
(字段名称)元素作为值。为什么要这么复杂的数据结构呢?因为相同的集合/映射可以分配给多个 Person
成员,甚至同一成员的多个成员 Person
对象,具体取决于您使用的集合/映射的类型。所以数据结构非常通用。请随意扩展驱动程序类以玩多个Person
对象和/或 Person
中具有多个相同类型的成员类(class)。我还没有测试过,但它应该可以工作。
更新:
getOldFieldValue()
辅助方法是必要的,因为 AspectJ 不会公开 set()
中的旧值。切入点,只有新值。因此需要通过反射来确定。AbstractMap.SimpleEntry
。此外,其equals()
方法保证将具有相同键和值的对视为相等,因此我可以创建一个新实例并在我的 Map.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[...”。基本上这就是您想要实现的目标,但我个人认为,除了是一个很好的练习之外,它可能有点慢并且是维护噩梦,但可行。
关于java - 如何使用 AOP 在 JPA 实体中建议/切入点 setter ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24786391/
我是一名优秀的程序员,十分优秀!