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

时间:2018-02-24 10:00:12

标签: java gson spring-aop lombok cglib

假设有一个简单的类Student

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

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

<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适用于我。

3 个答案:

答案 0 :(得分: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;

    ...
}

答案 1 :(得分:2)

您的代码似乎暗示您的方面正在运行,即在您的配置建议执行之前/之后。如果他们不在,你在其他地方遇到问题。我进一步假设

  • 您的方面按设计工作,并且您已检查过,
  • 您正在使用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"
}
BTW,由于类命名冲突,Lombok因与AspectJ有关而导致麻烦,请参阅my answer here。这也可能影响Spring AOP。

我认为你在这里使用了一种不健康的(因为不兼容的)技术组合,如果你找到一个解决方案并且不希望最终为每个bean类编写自定义类型适配器,那将非常hacky。如果删除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)方法有关。所以你很高兴。 :-)

答案 2 :(得分:1)

实施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(..)