java - que - spring mvc 4 tutorial español
Implementar un patrón de fábrica simple con anotaciones de Spring 3 (7)
¿Por qué no agregar la interfaz FactoryBean a MyServiceFactory (para decirle a Spring que es una fábrica), agregar un registro (servicio String, instancia MyService) y hacer que cada uno de los servicios llame a:
@Autowired
MyServiceFactory serviceFactory;
@PostConstruct
public void postConstruct() {
serviceFactory.register(myName, this);
}
De esta forma, puede separar a cada proveedor de servicios en módulos si es necesario, y Spring seleccionará automáticamente cualquier proveedor de servicios desplegado y disponible.
Me preguntaba cómo podría implementar el patrón simple de fábrica con las anotaciones de Spring 3. Vi en la documentación que puedes crear beans que llaman a la clase de fábrica y ejecutan un método de fábrica. Me preguntaba si esto era posible usando solo anotaciones.
Tengo un controlador que actualmente llama
MyService myService = myServiceFactory.getMyService(test);
result = myService.checkStatus();
MyService es una interfaz con un método llamado checkStatus ().
Mi clase de fábrica se ve así:
@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;
}
}
La clase MyServiceOne se ve así:
@Autowired
private LocationService locationService;
public boolean checkStatus() {
//do stuff
}
Cuando ejecuto este código, la variable locationService es siempre nula. Creo que esto se debe a que estoy creando los objetos yo mismo dentro de la fábrica y no está teniendo lugar el cableado automático. ¿Hay alguna forma de agregar anotaciones para que esto funcione correctamente?
Gracias
Lo siguiente funcionó para mí:
La interfaz consiste en usted métodos lógicos más un método de identidad adicional:
public interface MyService {
String getType();
void checkStatus();
}
Algunas implementaciones:
@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
}
}
Y la fábrica misma de la siguiente manera:
@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;
}
}
Encontré tal implementación más fácil, más limpia y mucho más extensible. Agregar un nuevo MyService es tan fácil como crear otro bean de primavera que implemente la misma interfaz sin realizar ningún cambio en otros lugares.
Puede instanciar "AnnotationConfigApplicationContext" pasando todas sus clases de servicio como parámetros.
@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);
}
}
Puede solicitar manualmente a Spring que lo autocance.
Haga que su fábrica implemente ApplicationContextAware. Luego proporcione la siguiente implementación en su fábrica:
@Override
public void setApplicationContext(final ApplicationContext applicationContext {
this.applicationContext = applicationContext;
}
y luego haz lo siguiente después de crear tu bean:
YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.
Esto conectará automáticamente su LocationService perfectamente.
Supongo que debes usar org.springframework.beans.factory.config.ServiceLocatorFactoryBean. Simplificará mucho su código. Excepto MyServiceAdapter, solo puede crear la interfaz MyServiceAdapter con el método MyService getMyService y con alies para registrar sus clases.
Código
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" />
También podría definir declarativamente un bean de tipo ServiceLocatorFactoryBean que actuará como una clase de fábrica. es compatible con Spring 3.
Una implementación de FactoryBean que toma una interfaz que debe tener uno o más métodos con las firmas (típicamente, MyService getService () o MyService getService (String id)) y crea un proxy dinámico que implementa esa interfaz
Aquí hay un ejemplo de implementación del patrón Factory usando Spring
Tienes razón, al crear objetos manualmente, no dejas que Spring realice el autoencendido. Considere administrar sus servicios por Spring también:
@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;
}
}
}
Pero consideraría que el diseño general es bastante pobre. ¿No sería mejor tener una implementación general de MyService
y pasar MyService
one
/ two
/ three
como parámetro extra para checkStatus()
? ¿Qué quieres lograr?
@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();
}
}
}
Esto todavía está mal diseñado porque agregar nueva implementación de MyServiceAdapter
requiere la modificación MyServiceAdapter
(violación de SRP). Pero este es en realidad un buen punto de partida (sugerencia: mapa y patrón de Estrategia).