【问题标题】:How to make Lombok + Gson work with Spring AOP proxies如何使 Lombok + Gson 与 Spring AOP 代理一起工作
【发布时间】:2018-02-24 10:00:12
【问题描述】:

假设有一个简单的类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);       
}

更新根据接受的答案,unProxy 为我工作。

【问题讨论】:

  • 我认为您不应该对原来的问题进行太多更改,而是评论答案。现在阿伦的回答似乎有点无关紧要。调用context.getBean("student"); 是否使用Gson 反序列化?你有没有记录那个学生,否则它有什么价值?对于调试,您可以将 nameage 设置为某个默认调试值。
  • 2 个 bean 的 nameage 的值在运行时相同。
  • 不确定这是否不再是 AOP 的事情,但可能与您获得的 student bean 可能是代理有关。另请参阅this queation

标签: java gson spring-aop lombok cglib


【解决方案1】:

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

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

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


更新:

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

只是因为我对如何从 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 相关的麻烦,请参阅my answer here。这也可能会影响 Spring AOP。

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


更新 2:

我的第一次更新只是在茶歇期间快速破解。不是 Spring 用户,我也必须先查找 API 文档才能找到接口Advised。它包含方法getTargetSource(),即:

  • 我们可以将 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) 方法有关。所以你很高兴。 :-)

【讨论】:

  • 是的,你是对的。在这种情况下破解并不难(如果 obj 是代理,只需调用 getTargetSource)。
  • 下班后,我再次查看了它,并在我的第二次更新中添加了一个改进的示例,没有反映。随意看看。
  • 更新 3:添加了有关龙目岛的信息。享受吧!
  • 我真的很喜欢龙目岛并在任何可能的地方使用它。
【解决方案2】:

您可以使用@Expose 注解忽略 aop 字段。 例如:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); String json = gson.toJson(new Book());

public class book {

    @Expose
    public String name;

    @Expose
    public int some;

    ...
}

【讨论】:

    【解决方案3】:

    实现ExclusionStrategy 喜欢:

    @RequiredArgsConstructor
    public class ExcludeListedClasses implements ExclusionStrategy {
        @NonNull
        private Set<Class<?>> classesToExclude;
        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            return false;
        }
        @Override
        public boolean shouldSkipClass(Class<?> clazz) {
            return classesToExclude.contains(clazz);
        }
    }
    

    像这样使用:

    ExclusionStrategy es = new ExcludeListedClasses( new HashSet<Class<?>>() {{
                    add(Logging.class);
        }} );
    
    Gson gson = new GsonBuilder().setPrettyPrinting()
                    .addSerializationExclusionStrategy(es).create();
    

    可能还会出现其他不可序列化的类,由方面添加。只需将它们也添加到构造 ExcludeListedClasses 时提供的集合中。

    与 Arun 回答的不同之处在于,这样您就不需要在每个类的每个可能是不可序列化字段的字段中添加 @Expose 注释。

    例如,如果您想跳过按字段名称进行序列化,也可以类似的方式使用方法shouldSkipField(..)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多