有没有办法自动将代码插入方法?
我有以下带有getter和setter的典型字段,我想将指示的代码插入到setter方法中,该方法记录字段是否也被修改以插入指示的“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;
}
}
我也不确定我是否可以将方法名称的子集作为方法本身内部的字符串,如我将字段名作为字符串添加到更新字段集中的行所示:{{1} }。而且我不确定如何将字段插入到我添加布尔字段的类中,该字段用于跟踪字段是否已被修改(为了防止必须操作集合的效率):updatedFields.add("firstName");
似乎最明显的答案是在eclipse中使用代码模板,但我担心以后必须返回并更改代码。
我应该使用这个更简单的代码而不是上面的例子。它只是将字段的名称作为字符串添加到集合中。
boolean isFirstNameChanged = false;
}
答案 0 :(得分:6)
是的,你可以,一种方法是使用某种形式的字节代码操作(例如javassist,ASM,BCEL)或更高级别的AOP库位于其中一个工具之上,例如AspectJ,JBoss 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);
}
答案 1 :(得分:3)
使用AspectJ,您可以使用建议修改方法和字段。
我的示例是用@AspectJ
语法编写的,它在编译时或加载时修改代码。如果要在运行时进行修改,可以使用Spring AOP,它也支持这种@AspectJ
语法。
使用简单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);
}
}
使用AspectJ在Person类中执行main()
方法的输出:
更新了Person上的以下字段 在存储库中:[lastName, 名字]
这里需要注意的是main()方法调用DtoRepository.update(T t)
但DtoRepository.update(T t, Set<String> updatedFields)
由于存储库方面的around建议而被执行。
监控演示包中所有写入私有字段的方面:
@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");
}
}
}
最后,方面使用的两个辅助类:
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>
的接口,那么您可以避免使用最后一个方面。
我希望这能回答你的问题!
答案 2 :(得分:1)
您可以使用Dynamic Proxy Classes并在调用setFirstName
和其他方法set...
之前获取活动,确定字段名称为method.substring(3)
=&gt; “FirstName”,并将其放入setFirstName
。