私はSpring Beanを注釈付きで使用しており、実行時に異なる実装を選択する必要があります。
@Service
public class MyService {
public void test(){...}
}
たとえば、Windowsのプラットフォームの場合、MyServiceWin extending MyService
、Linuxプラットフォームの場合はMyServiceLnx extending MyService
。
今のところ、恐ろしい解決策を1つだけ知っています。
@Service
public class MyService {
private MyService impl;
@PostInit
public void init(){
if(windows) impl=new MyServiceWin();
else impl=new MyServiceLnx();
}
public void test(){
impl.test();
}
}
私は注釈のみを使用し、XML configは使用していないことを考慮してください。
次のように、Beanインジェクションを構成に移動できます。
@Configuration
public class AppConfig {
@Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
または、プロファイルwindows
およびlinux
を使用し、@Profile("linux")
または@Profile("windows")
のような@Profile
注釈でサービス実装に注釈を付けることもできます。このプロファイルの1つをアプリケーションに提供します。
Condition
を実装しますpublic class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Windows
についても同じです。
Configuration
クラスで @Conditional
を使用します@Configuration
public class MyConfiguration {
@Bean
@Conditional(LinuxCondition.class)
public MyService getMyLinuxService() {
return new LinuxService();
}
@Bean
@Conditional(WindowsCondition.class)
public MyService getMyWindowsService() {
return new WindowsService();
}
}
@Autowired
を使用します@Service
public class SomeOtherServiceUsingMyService {
@Autowired
private MyService impl;
// ...
}
美しい設定を作成しましょう。
Animalインターフェイスがあり、DogおよびCat実装があると想像してください。書き込みを書きたい:
@Autowired
Animal animal;
しかし、どの実装を返す必要がありますか?
それでは解決策は何ですか?問題を解決する方法はたくさんあります。 @ Qualifierとカスタム条件を一緒に使用する方法を書きます。
それではまず、カスタムアノテーションを作成しましょう。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public @interface AnimalType {
String value() default "";
}
および構成:
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class AnimalFactoryConfig {
@Bean(name = "AnimalBean")
@AnimalType("Dog")
@Conditional(AnimalCondition.class)
public Animal getDog() {
return new Dog();
}
@Bean(name = "AnimalBean")
@AnimalType("Cat")
@Conditional(AnimalCondition.class)
public Animal getCat() {
return new Cat();
}
}
注 Bean名はAnimalBeanです。 なぜこのBeanが必要なのですか? Animalインターフェイスをインジェクトするときは、単に@ Qualifier( "AnimalBean")と書くからです。
また、カスタムアノテーションを作成して、値をカスタム条件に渡しました。
ここで、条件は次のようになります(「犬」の名前は、構成ファイルまたはJVMパラメーターまたは...
public class AnimalCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
.entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
}
return false;
}
}
そして最後に注入:
@Qualifier("AnimalBean")
@Autowired
Animal animal;
すべての実装を@Qualifier
アノテーションを使用してファクトリに自動配線し、必要なサービスクラスをファクトリから返します。
public class MyService {
private void doStuff();
}
私のWindowsサービス:
@Service("myWindowsService")
public class MyWindowsService implements MyService {
@Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
私のMacサービス:
@Service("myMacService")
public class MyMacService implements MyService {
@Override
private void doStuff() {
//Mac specific stuff happens here
}
}
私の工場:
@Component
public class MyFactory {
@Autowired
@Qualifier("myWindowsService")
private MyService windowsService;
@Autowired
@Qualifier("myMacService")
private MyService macService;
public MyService getService(String serviceNeeded){
//This logic is ugly
if(serviceNeeded == "Windows"){
return windowsService;
} else {
return macService;
}
}
}
本当にトリッキーにしたい場合は、enumを使用して実装クラスのタイプを保存し、enum値を使用してどの実装を返すかを選択できます。
public enum ServiceStore {
MAC("myMacService", MyMacService.class),
WINDOWS("myWindowsService", MyWindowsService.class);
private String serviceName;
private Class<?> clazz;
private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
static {
//This little bit of black magic, basically sets up your
//static map and allows you to get an enum value based on a classtype
ServiceStore[] namesArray = ServiceStore.values();
for(ServiceStore name : namesArray){
mapOfClassTypes.put(name.getClassType, name);
}
}
private ServiceStore(String serviceName, Class<?> clazz){
this.serviceName = serviceName;
this.clazz = clazz;
}
public String getServiceBeanName() {
return serviceName;
}
public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
return mapOfClassTypes.get(clazz);
}
}
その後、ファクトリはアプリケーションコンテキストをタップして、インスタンスを独自のマップにプルできます。新しいサービスクラスを追加する場合は、enumに別のエントリを追加するだけで、あとはそれだけです。
public class ServiceFactory implements ApplicationContextAware {
private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
public MyService getInstance(Class<?> clazz) {
return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myServices.putAll(applicationContext.getBeansofType(MyService.class));
}
}
これで、必要なクラスタイプをファクトリーに渡すだけで、必要なインスタンスが返されます。特にサービスを汎用的にしたい場合に非常に役立ちます。