2015-02-05 20 views
5

związanej zatwierdzenia w ramach wiosennej https://github.com/spring-projects/spring-framework/commit/5aefcc802ef05abc51bbfbeb4a78b3032ff9eee3Wiosna cache używając @Cacheable podczas @PostConstruct nie działa

inicjalizacji jest ustawiony na późniejszym etapie z afterPropertiesSet() do afterSingletonsInstantiated()

W skrócie: Zapobiega to pracy bufora podczas używania w przypadku użycia @PostConstruct.

Dłuższa wersja: Zapobiega to przypadek użycia gdzie byś

  1. tworzenia serviceB z @Cacheable na MethodB

  2. tworzenia serviceA z @PostConstruct wywołującego serviceB.methodB

    @Component 
    public class ServiceA{ 
    
    @Autowired 
    private ServiceB serviceB; 
    
    @PostConstruct 
    public void init() { 
        List<String> list = serviceB.loadSomething(); 
    } 
    

Powoduje to, że org.springframework.cache.interceptor.CacheAspectSupport nie jest teraz inicjowany i nie buforuje wyniku.

protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { 
    // check whether aspect is enabled 
    // to cope with cases where the AJ is pulled in automatically 
    if (this.initialized) { 
//>>>>>>>>>>>> NOT Being called 
     Class<?> targetClass = getTargetClass(target); 
     Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass); 
     if (!CollectionUtils.isEmpty(operations)) { 
     return execute(invoker, new CacheOperationContexts(operations, method, args, target, targetClass)); 
     } 
    } 
//>>>>>>>>>>>> Being called 
    return invoker.invoke(); 
} 

Moje obejście jest ręcznie wywołać metodę inicjalizacji:

@Configuration 
public class SomeConfigClass{ 

    @Inject 
    private CacheInterceptor cacheInterceptor; 

    @PostConstruct 
    public void init() { 
    cacheInterceptor.afterSingletonsInstantiated(); 
    } 

To oczywiście rozwiązuje mój problem, ale to ma skutki uboczne innych, które po prostu są nazywane 2 razy (1 instrukcja i 1 przez ramy zgodnie z przeznaczeniem)

Moje pytanie brzmi: „Czy to jest bezpieczne obejście zrobić jak początkowa commiter wydawało się, że problem z tylko przy użyciu afterPropertiesSet()”

+0

Opcja '@ PostConstruct' daje żadnych gwarancji, że serwery proxy zostały już utworzone (czyli z tego samego powodu, dlaczego' @ Transactional' nie działa dla 'Metody @ PostConstruct'.Metodę '@ PostConstruct' wywołuje się zaraz po skonstruowaniu i po wstrzyknięciu zależności, ale prawie zawsze przed punktem, w którym zostały utworzone proxy. Dlaczego potrzebujesz go w metodzie '@ PostConstruct'? Ogólnie rzecz biorąc, 'ApplicationListener ' lepiej implementuje interfejs 'SmartInitializingSingleton' zamiast' @ PostConstruct'. –

+0

Dziękuję za odpowiedź. Używamy postkonstruktu do inicjowania komponentu bean z wartościami pobranymi z innej usługi (która ma @Cacheable na swoich metodach) Oczekiwaliśmy, że wartości te zostaną zbuforowane nawet przy użyciu postkonstrukcji. Jeśli tak nie jest, to dokument java byłby korzystny dla środowiska, ponieważ inni deweloperzy mogą nie być tego świadomi. Zamiast tego spróbuję SmartInitializingSingleton. Dzięki! – user2966436

+0

Zostało to wyjaśnione w [podręczniku] (http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-lifecycle-default-init-destroy-methods) przeczytaj ostatni akapit tej sekcji. –

Odpowiedz

3

Jak już powiedział Marten, nie powinieneś używać żadnej z tych usług w fazie PostConstruct, ponieważ nie masz żadnej gwarancji, że przechwytywacz proxy w pełni się uruchomił w tym momencie.

Najlepszym ujęciem przy wstępnym ładowaniu pamięci podręcznej jest słuchanie ContextRefreshedEvent (więcej wsparcia przychodzi w 4.2) i wykonywanie tam pracy. Biorąc to pod uwagę, rozumiem, że może nie być jasne, że takie użycie jest zabronione, dlatego stworzyłem SPR-12700 w celu udoskonalenia dokumentacji. Nie jestem pewien, do czego odnosi się javadoc.

Aby odpowiedzieć na pytanie: nie, nie jest to bezpieczne rozwiązanie. To, czego używałeś wcześniej, działało przez "efekt uboczny" (tzn. Nie powinno działać, jeśli Twój komponent bean został zainicjowany przed CacheInterceptor, masz taki sam problem ze starszą wersją tego frameworka). Nie wywołuj takiej infrastruktury niskiego poziomu w swoim własnym kodzie.

2

Po prostu miałem dokładnie ten sam problem co OP i słuchanie ContextRefreshedEvent powodowało dwukrotne wywołanie mojej metody inicjalizacji. Najlepiej dla mnie działało słuchanie ApplicationReadyEvent. Oto kod użyłem

@Component 
public class MyInitializer implements ApplicationListener<ApplicationReadyEvent> { 
    @Override 
    public void onApplicationEvent(ApplicationReadyEvent event) { 
     //doing things 
    } 
}