java весеннее кэширование нарушает отражение? - PullRequest
3 голосов
/ 22 апреля 2020

Я недавно использую весеннюю загрузку и встроенное кэширование. В своих тестах я немного использую отражение.

Вот пример:

@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 через отражение

проблема

  • все работает без кеширования .. это вывод:
fieldOfMyService:false
fieldOfMyService via reflection before change:false
fieldOfMyService via reflection after change:true
fieldOfMyService:true

Теперь я активирую кеширование с помощью:

  • @EnableCaching весной
  • и тогда вы получите:
fieldOfMyService:false
fieldOfMyService via reflection before change:false
fieldOfMyService via reflection after change:true
fieldOfMyService:false                      <<<<< !!!

Long Короче говоря:

  • , когда кеширование активируется, служба, кажется, неуязвима к изменениям, сделанным с помощью рефлексии

Самое смешное, что это происходит только тогда, когда Сервис фактически использует кеширование хотя бы одним аннотированным методом @Caching. В случае, если сервис не имеет такой как:

@Service
public class MyService {

    private boolean fieldOfMyService = false;

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

}

.. тогда он все еще работает.

Я думаю, это как-то связано со слоями, которые добавляются при активации кэширования. Но почему? И есть ли решение для этого?

Заранее спасибо за помощь: -)

1 Ответ

1 голос
/ 23 апреля 2020

Различие в поведении связано с проксированием, выполняемым средой Spring. Когда для bean-компонента требуется какая-либо специальная обработка, в данном случае Caching, во время выполнения создается прокси для bean-компонента. В Spring есть два типа методов прокси - JDK- и CGLIB-прокси . Пожалуйста, прочитайте ссылку на документацию для получения более подробной информации.

В приведенном примере кода используется прокси-сервер CGLIB (подсказка: MyService не реализует интерфейс). Подкласс среды выполнения, созданный CGLIB, будет иметь все поля исходного класса, но останется нулевым / по умолчанию в зависимости от типа данных (здесь false). Также вызовы метода для прокси делегируются исходному методу экземпляра.

Следующие изменения в коде дадут больше подробностей о том, как это работает.

Изменение 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

Здесь значение, обновленное с помощью отражения, предназначено для экземпляра подкласса времени выполнения

Без @EnableCaching, этот код будет печатать

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

Здесь значение, обновленное с помощью отражения, предназначено для фактического экземпляра.

Изменить 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 этот код приведет к NPE в MyServiceTest.test(), boolean fieldOfMyService = (boolean) FieldUtils.readField(myService, "fieldOfMyService", true);, поскольку прокси времени выполнения имеет поле, инициализированное нулевым значением.

Без @EnableCaching этот код будет работать как ожидалось.

Надеюсь, это поможет.

Примечание: @ SpringBootTest мета-аннотируется с помощью @ExtendWith и @RunWith можно избежать.

------------ ------------------- Обновление ------------------------ ---------------

Мысль о том, чтобы поделиться ответом и на этот вопрос: «И есть ли решение для него?»

Либо использовать сеттер методы для обновления состояния поля

Или

Если отражение - единственный вариант, получите фактический экземпляр и используйте отражение для обновления поля.

В следующем коде предполагается, что кэширование включено через @EnableCaching, и приведение может работать без каких-либо проверок. Пожалуйста, внесите необходимые изменения, чтобы сделать его надежным.

@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();

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...