Spring 3アノテーションを使用して単純なファクトリパターンを実装する方法を知りたいと思いました。ドキュメントでは、ファクトリクラスを呼び出してファクトリメソッドを実行するBeanを作成できることを見ました。注釈のみを使用してこれが可能かどうか疑問に思っていました。
現在呼び出しているコントローラーがあります
MyService myService = myServiceFactory.getMyService(test);
result = myService.checkStatus();
MyServiceは、checkStatus()という1つのメソッドを持つインターフェースです。
私のファクトリクラスは次のようになります。
@Component
public class MyServiceFactory {
public static MyService getMyService(String service) {
MyService myService;
service = service.toLowerCase();
if (service.equals("one")) {
myService = new MyServiceOne();
} else if (service.equals("two")) {
myService = new MyServiceTwo();
} else if (service.equals("three")) {
myService = new MyServiceThree();
} else {
myService = new MyServiceDefault();
}
return myService;
}
}
MyServiceOneクラスは次のようになります。
@Autowired
private LocationService locationService;
public boolean checkStatus() {
//do stuff
}
このコードを実行すると、locationService変数は常にヌルになります。これは、工場内でオブジェクトを自分で作成しており、自動配線が行われていないためです。これを正しく機能させるために注釈を追加する方法はありますか?
ありがとう
オブジェクトを手動で作成することにより、Springに自動配線を実行させません。 Springによるサービスの管理も検討してください。
_@Component
public class MyServiceFactory {
@Autowired
private MyServiceOne myServiceOne;
@Autowired
private MyServiceTwo myServiceTwo;
@Autowired
private MyServiceThree myServiceThree;
@Autowired
private MyServiceDefault myServiceDefault;
public static MyService getMyService(String service) {
service = service.toLowerCase();
if (service.equals("one")) {
return myServiceOne;
} else if (service.equals("two")) {
return myServiceTwo;
} else if (service.equals("three")) {
return myServiceThree;
} else {
return myServiceDefault;
}
}
}
_
しかし、全体的な設計はかなり貧弱だと思います。一般的なMyService
実装を1つ用意し、one
/two
/three
文字列をcheckStatus()
の追加パラメータとして渡す方が良いとは思いませんか?何を達成したいですか?
_@Component
public class MyServiceAdapter implements MyService {
@Autowired
private MyServiceOne myServiceOne;
@Autowired
private MyServiceTwo myServiceTwo;
@Autowired
private MyServiceThree myServiceThree;
@Autowired
private MyServiceDefault myServiceDefault;
public boolean checkStatus(String service) {
service = service.toLowerCase();
if (service.equals("one")) {
return myServiceOne.checkStatus();
} else if (service.equals("two")) {
return myServiceTwo.checkStatus();
} else if (service.equals("three")) {
return myServiceThree.checkStatus();
} else {
return myServiceDefault.checkStatus();
}
}
}
_
新しいMyService
実装を追加するにはMyServiceAdapter
の変更も必要になるため(SRP違反)、これはstill設計が不十分です。しかし、これは実際には良い出発点です(ヒント:マップと戦略パターン)。
次は私のために働いた:
インターフェイスは、ロジックメソッドと追加のIDメソッドで構成されます。
public interface MyService {
String getType();
void checkStatus();
}
いくつかの実装:
@Component
public class MyServiceOne implements MyService {
@Override
public String getType() {
return "one";
}
@Override
public void checkStatus() {
// Your code
}
}
@Component
public class MyServiceTwo implements MyService {
@Override
public String getType() {
return "two";
}
@Override
public void checkStatus() {
// Your code
}
}
@Component
public class MyServiceThree implements MyService {
@Override
public String getType() {
return "three";
}
@Override
public void checkStatus() {
// Your code
}
}
また、工場自体は次のとおりです。
@Service
public class MyServiceFactory {
@Autowired
private List<MyService> services;
private static final Map<String, MyService> myServiceCache = new HashMap<>();
@PostConstruct
public void initMyServiceCache() {
for(MyService service : services) {
myServiceCache.put(service.getType(), service);
}
}
public static MyService getService(String type) {
MyService service = myServiceCache.get(type);
if(service == null) throw new RuntimeException("Unknown service type: " + type);
return service;
}
}
そのような実装は、より簡単で、よりクリーンで、はるかに拡張性が高いことがわかりました。新しいMyServiceの追加は、他の場所で変更を加えることなく、同じインターフェースを実装する別のSpring Beanを作成するのと同じくらい簡単です。
ファクトリーBeanをMyServiceFactoryに追加して(Springにファクトリーであることを伝えるために)、register(String service、MyService instance)を追加してから、各サービスを呼び出してください。
@Autowired
MyServiceFactory serviceFactory;
@PostConstruct
public void postConstruct() {
serviceFactory.register(myName, this);
}
このようにして、必要に応じて各サービスプロバイダーをモジュールに分離できます。Springは、デプロイ済みで利用可能なサービスプロバイダーを自動的に選択します。
Springに手動で自動配線するように依頼できます。
ファクトリーにApplicationContextAwareを実装してください。次に、工場で次の実装を提供します。
@Override
public void setApplicationContext(final ApplicationContext applicationContext {
this.applicationContext = applicationContext;
}
そして、Beanを作成した後、以下を実行します。
YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.
これにより、LocationServiceが完全に自動配線されます。
また、Factoryクラスとして機能するタイプ ServiceLocatorFactoryBean のBeanを宣言的に定義することもできます。 Spring 3でサポートされています。
署名付きの1つ以上のメソッド(通常はMyService getService()またはMyService getService(String id))が必要なインターフェースを取り、そのインターフェースを実装する動的プロキシを作成するFactoryBean実装
DruidKuma の回答に従います
リッテは自動配線コンストラクターを使用して工場をリファクタリングします。
@Service
public class MyServiceFactory {
private static final Map<String, MyService> myServiceCache = new HashMap<>();
@Autowired
public MyServiceFactory(List<MyService> services) {
for(MyService service : services) {
myServiceCache.put(service.getType(), service);
}
}
public static MyService getService(String type) {
MyService service = myServiceCache.get(type);
if(service == null) throw new RuntimeException("Unknown service type: " + type);
return service;
}
}
Org.springframework.beans.factory.config.ServiceLocatorFactoryBeanを使用すると仮定します。コードが大幅に簡素化されます。 MyServiceAdapterを除くuは、メソッドMyService getMyServiceと、クラスを登録するためのアリーを使用して、インターフェースMyServiceAdapterのみを作成できます。
コード
bean id="printStrategyFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="YourInterface" value="factory.MyServiceAdapter" />
</bean>
<alias name="myServiceOne" alias="one" />
<alias name="myServiceTwo" alias="two" />
すべてのサービスクラスをパラメーターとして渡すことにより、「AnnotationConfigApplicationContext」をインスタンス化できます。
@Component
public class MyServiceFactory {
private ApplicationContext applicationContext;
public MyServiceFactory() {
applicationContext = new AnnotationConfigApplicationContext(
MyServiceOne.class,
MyServiceTwo.class,
MyServiceThree.class,
MyServiceDefault.class,
LocationService.class
);
/* I have added LocationService.class because this component is also autowired */
}
public MyService getMyService(String service) {
if ("one".equalsIgnoreCase(service)) {
return applicationContext.getBean(MyServiceOne.class);
}
if ("two".equalsIgnoreCase(service)) {
return applicationContext.getBean(MyServiceTwo.class);
}
if ("three".equalsIgnoreCase(service)) {
return applicationContext.getBean(MyServiceThree.class);
}
return applicationContext.getBean(MyServiceDefault.class);
}
}
これを試して:
public interface MyService {
//Code
}
@Component("One")
public class MyServiceOne implements MyService {
//Code
}
@Component("Two")
public class MyServiceTwo implements MyService {
//Code
}
PavelČerný here のソリューションに基づいて、このパターンの汎用型付き実装を作成できます。それには、NamedServiceインターフェースを導入する必要があります。
public interface NamedService {
String name();
}
抽象クラスを追加します。
public abstract class AbstractFactory<T extends NamedService> {
private final Map<String, T> map;
protected AbstractFactory(List<T> list) {
this.map = list
.stream()
.collect(Collectors.toMap(NamedService::name, Function.identity()));
}
/**
* Factory method for getting an appropriate implementation of a service
* @param name name of service impl.
* @return concrete service impl.
*/
public T getInstance(@NonNull final String name) {
T t = map.get(name);
if(t == null)
throw new RuntimeException("Unknown service name: " + name);
return t;
}
}
次に、MyServiceのような特定のオブジェクトの具体的なファクトリを作成します。
public interface MyService extends NamedService {
String name();
void doJob();
}
@Component
public class MyServiceFactory extends AbstractFactory<MyService> {
@Autowired
protected MyServiceFactory(List<MyService> list) {
super(list);
}
}
whereコンパイル時にMyServiceインターフェースの実装のリストをリストします。
名前によってオブジェクトを生成するアプリ全体に複数の同様のファクトリがある場合、このアプローチは正常に機能します(名前によってオブジェクトを生成する場合はもちろんビジネスロジックで十分です)。ここで、mapはStringをキーとして適切に機能し、サービスの既存の実装をすべて保持します。
オブジェクトを生成するための異なるロジックがある場合、この追加のロジックを別の場所に移動し、これらのファクトリー(名前でオブジェクトを取得する)と組み合わせて動作できます。