如何使 Lombok + Gson 与 Spring AOP 代理一起工作

2023-11-30

假设有一个简单的类Student

@Data @NoArgsConstructor @AllArgsConstructor
public class Student {
    private Integer age;
    private String name;
}

在 aop.xml 中使用 Spring AOP 添加日志记录方面

<aop:config>
    <aop:aspect id="log" ref="logging">
        <aop:pointcut id="selectAll" expression="execution(* com.tutorial.Student.getName(..))"/>
        <aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
        <aop:after pointcut-ref="selectAll" method="afterAdvice"/>
    </aop:aspect>
</aop:config>
<bean id="student" class="com.tutorial.Student">
    <property name="name"  value="Zara" />
    <property name="age"  value="11"/>
</bean>

排除方面字段

public class ExcludeAspects implements ExclusionStrategy {
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        if(f.getName().startsWith("CGLIB$"))
            return true;
        return false;
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}

main,注意第一个 bean 的输出为空(“{}”):

public static void main(String[] args) {
   Gson gson = new GsonBuilder().setPrettyPrinting().addSerializationExclusionStrategy(new ExcludeAspects()).create();
   ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");

   //return "{}"
   Student student = (Student) context.getBean("student");
   gson.toJson(student);       

   //works fine
   Student student2 = new Student(11,"Zara");
   gson.toJson(student2);       
}

Update根据已接受的答案,unProxy对我有用。


您的代码似乎暗示您的方面正在工作,即您的配置的建议执行之前/之后。如果不这样做,其他地方就会出现问题。我进一步假设

  • 你的方面按设计工作,并且你已经检查过,
  • 您正在使用 Spring AOP,而不是带有加载时编织的 AspectJ,
  • 不知何故,GSON 看到的是 CGLIB 代理,而不是底层的原始对象。

那么问题可能是 GSON - 我对它的经验为零,以前从未使用过它 - 使用反射来搜索代理类中的字段。但它不会找到任何代理,因为代理仅重写方法,但没有字段,因为后者位于原始类(代理的父类)中。如果是这样,则需要将 GSON 配置为在原始类中搜索,而不是在代理类中搜索。那么你也不必排除任何东西。


Update:

我上面的有根据的猜测是正确的。

只是因为我很好奇如何从 CGLIB 代理获取原始对象,所以我在调试器中查看了它。似乎每个代理都有一个公共的最终方法getTargetSource您可以通过反射调用它:

package com.tutorial;

import org.springframework.aop.TargetSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Application {
  public static void main(String[] args) throws Exception {
    Gson gson = new GsonBuilder().setPrettyPrinting().create();
    ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");

    Student student = (Student) context.getBean("student");
    TargetSource targetSource = (TargetSource)
      student
        .getClass()
        .getMethod("getTargetSource", null)
        .invoke(student, null);
    System.out.println(gson.toJson(targetSource.getTarget()));
 }
}

这对我来说适用于你的代码,但我没有在混合中使用 Lombok (你根本没有提到它,我只是在尝试编译你的代码时发现的!),而是手动创建的构造函数、getter 和 setter 只是为了起床和跑步。

此外,您不需要ExclusionStrategy不再了。

控制台日志:

{
  "age": 11,
  "name": "Zara"
}

顺便说一句,由于类命名冲突,Lombok 已知会在与 AspectJ 相关时造成麻烦,请参阅我的回答在这里。这也可能会影响 Spring AOP。

我认为你在这里使用了不健康的(因为不兼容的)技术组合,如果你找到了一个解决方案并且不想最终为每个 bean 类编写自定义类型适配器,那么这将是相当糟糕的。如果你删除了 Lombok,至少你可以从 Spring AOP 切换到AspectJ 与 LTW以摆脱代理问题。 AspectJ 不使用代理,因此 GSON 可能会更好地配合它。


更新2:

我的第一次更新只是在茶歇期间进行的快速修改。由于不是 Spring 用户,我也必须先查找 API 文档才能找到接口Advised。它包含方法getTargetSource(), i.e.:

  • 我们可以将 Spring bean(AOP 代理)转换为Advised从而避免丑陋的反射。
  • 更进一步,我们可以动态确定给定对象是否是(建议的)代理,即如果您更改或停用方面,相同的代码仍然可以工作。
package com.tutorial;

import org.springframework.aop.framework.Advised;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Application {
  public static void main(String[] args) throws Exception {
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aop.xml")) {
      Gson gson = new GsonBuilder().setPrettyPrinting().create();
      Student student = (Student) context.getBean("student");
      System.out.println(gson.toJson(unProxy(student)));
    }
 }

  public static Object unProxy(Object object) throws Exception {
    return object instanceof Advised
      ? ((Advised) object).getTargetSource().getTarget()
      : object;
  }
}

更新3:我很好奇,也为我的 IDE 安装了 Lombok。实际上上面的示例确实可以与 Gson 和我的小unProxy(Object)方法。所以你可以走了。:-)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使 Lombok + Gson 与 Spring AOP 代理一起工作 的相关文章