java spring 缓存会破坏反射吗? [英] Does java spring caching break reflection?

查看:64
本文介绍了java spring 缓存会破坏反射吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近在使用 Spring Boot 和集成缓存.在我的测试中,我使用了一些反射.

I'm using spring boot and integrated caching recently. Within my tests I use reflection a bit.

这是一个例子:

@Service
public class MyService {

    private boolean fieldOfMyService = false;

    public void printFieldOfMyService() {
        System.out.println("fieldOfMyService:" + fieldOfMyService);
    }

    @Cacheable("noOpMethod")
    public void noOpMethod() {
    }

}

这是测试:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { MyApplication.class })
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @Test
    public void test() throws Exception {

        myService.printFieldOfMyService();

        boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);

        System.out.println("fieldOfMyService via reflection before change:" + fieldOfMyService);

        FieldUtils.writeField(myService, "fieldOfMyService", true, true);

        boolean fieldOfMyServiceAfter = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);

        System.out.println("fieldOfMyService via reflection after change:" + fieldOfMyServiceAfter);

        myService.printFieldOfMyService();

    }

}

如您所见,这很简单:

  • MyService 有一个私有字段 fieldOfMyService
  • 测试通过反射将其从 false 更改为 true
  • MyService has a private field fieldOfMyService
  • the test changes this from false to true via reflection

问题

  • 一切正常,无需缓存.这是输出:
fieldOfMyService:false
fieldOfMyService via reflection before change:false
fieldOfMyService via reflection after change:true
fieldOfMyService:true

现在我通过以下方式激活缓存:

Now I activate caching via:

  • @EnableCaching 春天
  • 然后你会得到这个:
fieldOfMyService:false
fieldOfMyService via reflection before change:false
fieldOfMyService via reflection after change:true
fieldOfMyService:false                      <<<<< !!!

长话短说:

  • 当缓存被激活时,服务似乎对通过反射所做的更改免疫

有趣的是,这只发生在相应的服务通过至少一个 @Caching 注释方法实际使用缓存时.如果服务没有这样的:

The funny thing is, this only happens when the according service actually uses caching via at least one @Caching annotated method. In case the service does not have this like:

@Service
public class MyService {

    private boolean fieldOfMyService = false;

    public void printFieldOfMyService() {
        System.out.println("fieldOfMyService:" + fieldOfMyService);
    }

}

...然后它仍然有效.

.. then it still works.

我猜这与激活缓存时添加的层有关.但为什么?有什么解决办法吗?

I guess this has something to do with the layers which get added when caching is activated. But... why? And is there a solution to it?

预先感谢您的帮助:-)

Thanks in advance for your help :-)

推荐答案

行为上的差异是由于 Spring 框架完成的代理.当 bean 需要任何特殊处理时,在本例中为缓存,在运行时创建 bean 的代理.Spring 中有两种代理技术 - 基于 JDK 和 CGLIB 的代理.请阅读共享的文档链接以了解更多详细信息.

The difference in behaviour is due to the proxying done by the Spring framework. When any special processing is required for a bean , in this case Caching , a proxy for the bean is created at runtime . There are two types of proxying techniques in Spring - JDK- and CGLIB-based proxies . Please read through the documentation link shared for more details.

在共享的示例代码中,发挥作用的是 CGLIB 代理(线索:MyService 没有实现接口).由 CGLIB 创建的运行时子类将拥有原始类的所有字段,但将根据数据类型(此处为 false)保持为空/默认.对代理的方法调用也委托给原始实例方法.

In the example code shared , it is the CGLIB proxying that comes into play ( clue : MyService does not implement an interface) . The runtime subclass created by the CGLIB will have all the fields of the original class , but will remain null/default based on the data type ( here false). Also the method calls to the proxy is delegated to the original instance method.

对代码的后续更改将提供有关其工作原理的更多详细信息.

Following changes to the code will give more details on how this works.

Change 1 : 打印 object.getClass() 以及

@Service
public class MyService {

    private Boolean fieldOfMyService = false;

    public void printFieldOfMyService() {
        System.out.println("fieldOfMyService:" + this.getClass()+" : "+fieldOfMyService);
    }

    @Cacheable("noOpMethod")
    public void noOpMethod() {
    }

}

测试类

@SpringBootTest(classes = { MyApplication.class })
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @Test
    public void test() throws Exception {

        myService.printFieldOfMyService();

        boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);

        System.out.println(
                "fieldOfMyService via reflection before change:" + myService.getClass() + " " + fieldOfMyService);

        FieldUtils.writeField(myService, "fieldOfMyService", true, true);

        boolean fieldOfMyServiceAfter = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);

        System.out.println(
                "fieldOfMyService via reflection after change:" + myService.getClass() + " " + fieldOfMyServiceAfter);

        myService.printFieldOfMyService();

    }

}

使用 @EnableCaching 将打印此代码

fieldOfMyService:class rg.so.qn.MyService : false
fieldOfMyService via reflection before change:class rg.so.qn.MyService$$EnhancerBySpringCGLIB$$dfa75fca false
fieldOfMyService via reflection after change:class rg.so.qn.MyService$$EnhancerBySpringCGLIB$$dfa75fca true
fieldOfMyService:class rg.so.qn.MyService : false

这里通过反射更新的值是针对运行时子类实例

Here the value updated through reflection is for the runtime subclass instance

如果没有 @EnableCaching 这个代码会打印

Without @EnableCaching this code would print

fieldOfMyService:class rg.so.qn.MyService : false
fieldOfMyService via reflection before change:class rg.so.qn.MyService false
fieldOfMyService via reflection after change:class rg.so.qn.MyService true
fieldOfMyService:class rg.so.qn.MyService : true

这里通过反射更新的值是针对实际实例的.

Here the value updated through reflection is for the actual instance.

Change 2:将MyService.fieldOfMyService的数据类型修改为Boolean

@Service
public class MyService {

    private Boolean fieldOfMyService = false;

    public void printFieldOfMyService() {
        System.out.println("fieldOfMyService:" + this.getClass()+" : "+fieldOfMyService);
    }

    @Cacheable("noOpMethod")
    public void noOpMethod() {
    }

}

使用 @EnableCaching 此代码将导致 MyServiceTest.test() 处的 NPE,boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true); 因为运行时代理将该字段初始化为 null.

With @EnableCaching this code would result in an NPE at MyServiceTest.test() , boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true); as the runtime proxy has the field initialized to null.

如果没有 @EnableCaching,这段代码会按预期运行.

Without @EnableCaching this code would run as expect.

希望这会有所帮助.

注意:@SpringBootTest@ExtendWith 元注解,@RunWith 可以避免.

Note : @SpringBootTest is meta annotated with @ExtendWith and @RunWith can be avoided.

------------------------------- 更新---------------------------------------

------------------------------- Update---------------------------------------

想分享这个问题的答案以及有解决方案吗?"

Thought of sharing the answer to this question as well "And is there a solution to it?"

要么使用setter方法来更新字段的状态

Either use setter methods to update the state of the field

或者

如果反射是唯一的选择,获取实际实例并使用反射来更新字段.

If reflection is the only option , get the actual instance and use reflection to update the field.

以下代码假定通过 @EnableCaching 启用缓存,并且转换可以在没有任何检查的情况下工作.请进行必要的修改以使其万无一失.

Following code assumes that caching is enabled through @EnableCaching and the casting can work without any checks. Please make necessary modifications to make it foolproof.

@Test
public void test() throws Exception {

    myService.printFieldOfMyService();

    MyService actualInstance = (MyService)((Advised) myService).getTargetSource().getTarget();

    boolean fieldOfMyService = (boolean) FieldUtils.readField(actualInstance, "fieldOfMyService", true);

    System.out.println(
            "fieldOfMyService via reflection before change:" + myService.getClass() + " " + fieldOfMyService);

    FieldUtils.writeField(actualInstance, "fieldOfMyService", true, true);

    boolean fieldOfMyServiceAfter = (boolean) FieldUtils.readField(actualInstance, "fieldOfMyService", true);

    System.out.println(
            "fieldOfMyService via reflection after change:" + myService.getClass() + " " + fieldOfMyServiceAfter);

    myService.printFieldOfMyService();

}

这篇关于java spring 缓存会破坏反射吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆