2011-10-11 6 views
8

Mam aplikację Java EE + Spring, która ułatwia adnotacje w stosunku do konfiguracji XML. Ziarna mają zawsze zakres prototypowy.Dynamiczne definiowanie komponentu bean do wypromowania w Spring (przy użyciu kwalifikatorów)

Mam teraz zasady dotyczące mojej aplikacji zależne od kraju, z którego pochodzi żądanie użytkownika. Więc chciałbym mieć coś takiego (należy pamiętać, przykład ten był mocno uproszczony):

@Component 
public class TransactionService { 
    @Autowired 
    private TransactionRules rules; 
    //.. 
} 


@Component 
@Qualifier("US") 
public class TransactionRulesForUS implements TransactionRules { 
    //.. 
} 

@Component 
@Qualifier("CANADA") 
public class TransactionRulesForCanada implements TransactionRules { 
    //.. 
} 

szukałem sposobu, aby mechanizm auto-wiring automatycznie wstrzyknąć odpowiednią Bean (albo USA lub Kanadzie w ten przykład) na podstawie kraju bieżącego wniosku. Kraj byłby przechowywany w zmiennej ThreadLocal i zmieniałby się w każdym żądaniu. Dla wszystkich krajów, które nie mają własnych, szczególnych zasad, istniałaby również klasa globalna.

Wyobrażam sobie, że musiałbym dostosować sposób, w jaki Spring decyduje o tworzeniu obiektów, które będzie wstrzykiwać. Jedynym sposobem, w jaki to zrobiłem, było użycie FactoryBean, ale nie było to tym, na co miałem nadzieję (nie generycznie). Miałem nadzieję, że zrobię coś takiego:

  1. Zanim Spring stworzy instancję, mój własny kod będzie musiał zostać wywołany.
  2. Jeśli wykryję, że żądany interfejs ma więcej niż jedną implementację, sprawdziłbym w mojej zmiennej ThreadLocal właściwy kraj i dynamicznie dodawał odpowiedni kwalifikator do żądania automatycznego połączenia.
  3. Po tym, Wiosna zrobiłaby całą swoją zwykłą magię. Jeśli dodano kwalifikator, należałoby go wziąć pod uwagę; jeśli nie, przepływ będzie kontynuowany jak zwykle.

Czy jestem na dobrej drodze? Jakieś pomysły dla mnie na ten temat?

Dzięki.

Odpowiedz

0

Można podać klasę konfiguracji, która zwróci prawidłowy komponent bean na podstawie wartości ThreadLocal. Zakłada to, że używasz Spring 3. Zrobiłem mały test, aby upewnić się, że metoda dostawcy została wywołana przy każdym żądaniu. Oto co zrobiłem.

@Configuration 
public class ApplicationConfiguration 
{ 
    private static int counter = 0; 

    @Bean(name="joel") 
    @Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS) 
    List<String> getJoel() 
    { 
     return Arrays.asList(new String[] { "Joel " + counter++ }); 
    } 
} 

I przywołał wartość w moim kontrolerze w następujący sposób.

@Resource(name="joel") 
private List<String> joel; 

w realizacji operatora można sprawdzić ThreadLocal dla danej lokalizacji, a następnie zwracają poprawne TransactionRules przedmiot lub coś podobnego. Rzeczy z ScopedProxy są spowodowane tym, że wstrzykiwałem się do kontrolera, który jest w zasięgu Singleton, podczas gdy wartość to zakres żądań.

4

Utwórz własną adnotację, która służy do dekorowania zmiennych instancji lub metod ustawiania, a następnie postprocesora, który przetwarza adnotację i wstrzykuje ogólny serwer proxy, który rozwiązuje poprawną implementację w czasie wykonywania i deleguje do niej wywołanie.

@Component 
public class TransactionService { 
    @LocalizedResource 
    private TransactionRules rules; 
    //.. 
} 

@Retention(RUNTIME) 
@Target({FIELD, METHOD}) 
public @interface LocalizedResource {} 

Oto algorytm metody w swojej fasoli post-procesor postProcessBeforeInitialization(bean, beanName):

  1. introspekcji klasa fasola, aby wybrać zmienne instancji lub metod dostępowych, które są oznaczone adnotacją z @LocalizedResource. Zapisz wynik w pamięci podręcznej (tylko na mapie) indeksowanej według nazwy klasy. Możesz użyć do tego celu Spring InjectionMetadata.Możesz szukać przykładów, jak to działa, wyszukując odnośniki do tej klasy w kodzie źródłowym.
  2. Jeśli takie pole lub metoda istnieje dla komponentu bean, utwórz proxy za pomocą metody InvocationHandler opisanej poniżej, przekazując ją bieżącemu komponentowi BeanFactory (postprocesor komponentu bean musi być aplikacją ApplicationContextAware). Wstaw ten serwer proxy do zmiennej instancji lub wywołaj metodę ustawiającą za pomocą instancji proxy.

Oto InvocationHandler dla serwera proxy, który będzie używany do tworzenia zlokalizowanych zasobów.

public class LocalizedResourceResolver implements InvocationHandler { 
    private final BeanFactory bf; 
    public LocalizedResourceResolver(BeanFactory bf) { 
    this.bf = bf; 
    } 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
    String locale = lookupCurrentLocale(); 
    Object target = lookupTarget(locale); 
    return method.invoke(target, args); 
    } 

    private String lookupCurrentLocale() { 
    // here comes your stuff to look up the current locale 
    // probably set in a thread-local variable 
    } 

    private Object lookupTarget(String locale) { 
    // use the locale to match a qualifier attached to a bean that you lookup using the BeanFactory. 
    // That bean is the target 
    } 
} 

Być może trzeba będzie wprowadzić więcej kontroli nad typem komponentu bean lub dodać żądany typ komponentu bean w polu InvocationHandler.

Następną sprawą jest automatyczne wykrycie implementacji danego interfejsu, zależnego od lokalnych zasobów, i zarejestrowanie ich za pomocą kwalifikatora odpowiadającego ustawieniom regionalnym. W tym celu można zaimplementować BeanDefinitionRegistryPostProcessor lub BeanFactoryPostProcessor, aby dodać nowy BeanDefinition s do rejestru z odpowiednim kwalifikatorem, po jednym dla każdej implementacji interfejsów obsługujących locale. Można odgadnąć ustawienia narodowe implementacji, stosując następujące konwencje nazewnictwa: jeśli interfejs rozpoznający ustawienia narodowe nazywa się TransactionRules, wówczas implementacje mogą mieć nazwę TransactionRules_ISOCODE w tym samym pakiecie.

Jeśli nie możesz pozwolić sobie na taką konwencję nazewnictwa, będziesz potrzebować jakiegoś rodzaju skanowania klasy clas + sposobu na odgadnięcie lokalizacji danej implementacji (może adnotacji na temat klas implementacji). Skanowanie metodą Classpath jest możliwe, ale dość złożone i powolne, więc staraj się tego unikać.

Oto podsumowanie tego, co się dzieje:

  1. Gdy aplikacja uruchamia się, implementacje TransactionRules zostaną odkryte i będą tworzone definicje fasoli dla każdego z nich, z kwalifikator odpowiadający lokalizacji każdego wdrożenia . Nazwa fasoli dla tych komponentów nie jest istotna, ponieważ wyszukiwanie odbywa się na podstawie typu i kwalifikatora.
  2. Podczas wykonywania ustaw bieżące ustawienia narodowe w zmiennej lokalnej dla wątku. Wyszukaj żądany komponent (np. TransactionService). Postprocesor wprowadzi proxy dla każdego pola instancji @LocalizedResource lub metody ustawiającej.
  3. Podczas wywoływania metody na TransactionService, która kończy się niektórymi metodami TransactionRules, program wywołujący powiązany z serwerem proxy przełącza się na poprawną implementację na podstawie wartości przechowywanej w zmiennej lokalnej wątku, a następnie deleguje wywołanie do tej implementacji.

Niezbyt trywialny, ale działa. W ten sposób przetwarzanie @PersistenceContext jest przetwarzane przez Spring, z wyjątkiem wyszukiwania implementacji, który jest dodatkową cechą twojego przypadku użycia.

+1

Czy w 2017 r. Nie ma prostszej metody osiągnięcia tego celu? – maxxyme

Powiązane problemy