We have a scenario where the dependency should be determinded at runtime. An example shows the detail below:
public class OrderProcessor {
// The Validator should be determinded based on the version of the service.
private Validator orderProcessValidator;
public Confirmation process(Order order) {
if(orderProcessValidator.validate(order)) {
// Business logic
}
}
}
Is possible to inject Validator dynamically with Spring IOC, or can only be solved through the factory pattern?
It is still a bit unclear for me your scenario, but I am assuming you have two actual Order classes in your project. Maybe one is in com.foo.api1 package and the other is in com.foo.api2 package. Or one Order is called Order1 and the other is called Order2. The idea is that I'm assuming you have two different classes for the two 'api versions' of Order.
You can achieve what you need by using Spring AOP:
#Aspect
#Component
public class MyAspect {
#Autowired
private Validator1 validator1;
#Autowired
private Validator2 validator2;
#Pointcut("execution(* com.foo.bar.OrderProcessor.process(..))")
private void myPointcut() {}
#Around("myPointcut() && args(order)")
public Object myAroundAdvice(ProceedingJoinPoint pjp, Object order) throws Throwable {
if (order instanceof Order1) {
validator1.validate((Order1) order);
} else
if (order instanceof Order2) {
validator2.validate((Order2) order);
}
Object retVal = pjp.proceed();
return retVal;
}
}
#Component
public class OrderProcessor {
public void process(Object order) {
System.out.println("processing order");
}
}
#Component
public class Validator1 {
public void validate(Order1 order) {
System.out.println("validating inside validator 1");
}
}
#Component
public class Validator2 {
public void validate(Order2 order) {
System.out.println("validating inside validator 2");
}
}
So, basically, you are defining an aspect that should intercept calls to your OrderProcessor class and, depending on what parameter it receives, it calls one validator or the other.
Related
I have a legacy application which uses Spring for bean management (AnnotationConfigWebApplicationContext) and CDI (inject, brought by jersey-spring dependency) for DI.
I have the following situation:
#Service
#RequestScoped
#Scope(value = "request")
public class InjectedClass {
private SomeEnum someAttribute;
public void getSomeAttribute() {
return someAttribute;
}
}
#Service
public class MiddleLayer {
#Inject
public MiddleLayer(InjectedClass injectedClass) {
}
private void middleLayerMethod() {
if (someAttribute == SomeEnum.Y) {
// do something specific
}
}
}
// controller
public class SomeController {
#Inject
// In this flow injectedClass instance is initialised with SomeAttribute = Y by ContainerRequestFilter
public SomeController(MiddleLayer middleLayer) {
}
public void someMethod () {
MethodResult result = middleLayer.middleLayerMethod();
// do some additional things with result
}
}
#Component
public class PeriodicActivity {
#Inject
// I need this MiddleLayer to be injected with injectedClass instance where SomeAttribute = X, since it doesn't go via request filter
public PeriodicActivity(MiddleLayer middleLayer) {
}
public void method () {
MethodResult result = middleLayer.middleLayerMethod();
// do some other things with result
}
}
Without DI what I need to happen would look like this:
public class PeriodicActivity() {
InjectedClass injectedClassObjA = new InjectedClass();
injectedClassObjA.setSomeAttribute(X);
MiddleLayer middleLayer = new MiddleLayer(injectedClassObjA);
middleLayer.middleLayerMethod();
}
I am looking to do something similar with dependencies.
After some reading I am starting to wonder whether it is possible.
Assume I am creating a PrinterService class that has a AbstractPrinter object. AbstractPrinter is subclassed by classes such as HPPrinter, FilePrinter etc.
The exact kind of printer object to be used is mentioned in the RequestParam object passed to my Controller (it is a request attribute).
Is there any way I can inject the right kind of concrete printer class using Spring?
All the other dependencies are injected using #Autowired annotation. How to inject this one?
You can create and load a factory of AbstractPrinter objects during container startup as shown below and dynamically call the respective the AbstractPrinter's print() (or your own method) based on the input parameter (comes from controller) to the service.
In the below code for PrinterServiceImpl class, the main point is that all of the List<AbstractPrinter> will be injected by Spring container (depends upon how many implementation classes you provide like HPPrinter, etc..). Then you will load those beans into a Map during container startup with printerType as key.
#Controller
public class YourController {
#Autowired
private PrinterService printerService;
public X myMethod(#RequestParam("input") String input) {
printerService.myServiceMethod(input);
//return X
}
}
PrinterServiceImpl class:
public class PrinterServiceImpl implements PrinterService {
#Autowired
private List<AbstractPrinter> abstractPrinters;
private static final Map<String,AbstractPrinter> myPrinters = new HashMap<>();
#PostConstruct
public void loadPrinters() {
for(AbstractPrinter printer : abstractPrinters) {
myPrinters.put(printer.getPrinterType(), printer);
}
}
//Add your other Autowired dependencies here
#Override
public void myServiceMethod(String input){//get input from controller
AbstractPrinter abstractPrinter= myPrinters.get(input);
abstractPrinter.print();//dynamically calls print() depending on input
}
}
HPPrinter class:
#Component
public class HPPrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "HP";
}
#Override
public void print() {
// Your print code
}
}
FilePrinter class:
#Component
public class FilePrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "FILE";
}
#Override
public void print() {
// Your print code
}
}
You could create a dedicated PrinterService instance per AbstractPrinter concrete class. For example you could achieve this using Spring configuration which follow the factory pattern:
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private HPPrinter hpPrinter;
#Autowired
private FilePrinter filePrinter;
#Bean
public PrinterService hpPrinterService() {
return new PrinterService(hpPrinter);
}
#Bean
public PrinterService filePrinterService() {
return new PrinterService(filePrinter);
}
public PrinterService findPrinterService(PrinterType type){
if (type == HP)
return hpPrinterService();
....
}
}
Then in your controller, inject PrinterServiceConfiguration then call findPrinterService with the right printer type.
Don't forget to add PrinterServiceConfiguration at your configuration #Import.
If the list of printer is dynamic you could switch to prototype bean :
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private List<AbstractPrinter> printers;
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrinterService createPrinterService(PrinterType type){
return new PrinterService(findPrinterByType(type));
}
private Printer findPrinterByType(PrinterType type) {
// iterate over printers then return the printer that match type
// throw exception if no printer found
}
}
// My Factory class
#Component
public class UserRewardAccountValidatorFactory {
#Autowired
private VirginAmericaValidator virginAmericaValidator;
private static class SingletonHolder {
static UserRewardAccountValidatorFactory instance = new UserRewardAccountValidatorFactory();
}
public static UserRewardAccountValidatorFactory getInstance() {
return SingletonHolder.instance;
}
private UserRewardAccountValidatorFactory() {}
public PartnerValidator getPartnerValidator(Partner partner){
return virginAmericaValidator;
}
}
// My Validator class
#Service
public class VirginAmericaValidator implements PartnerValidator {
#Override
public void validate(String code) throws InvalidCodeException{
//do some processing if processing fails throw exception
if (code.equals("bad".toString())){
throw new InvalidCodeException();
}
}
}
//Usage:
PartnerValidator pv = UserRewardAccountValidatorFactory.getInstance().getPartnerValidator(partner);
if (pv != null){
try {
pv.validate(userRewardAccount);
} catch (InvalidCodeException e){
return buildResponse(ResponseStatus.INVALID_USER_REWARD_ACCOUNT, e.getMessage());
}
}
My package scan level is at much higher level. Whats happening is my virginAmericaValidator is always empty. Why is #Autowired annotation not working.
Your current approach will not work with Spring as you are ultimately using new UserRewardAccountValidatorFactory to create the instance which essentially bypasses Spring context altogether. Two approaches that should possibly work are these:
a. Using a factory-method and using xml to define your bean:
<bean class="package.UserRewardAccountValidatorFactory" name="myfactory" factory-method="getInstance"/>
This will essentially return the instance that you are creating back as a Spring bean and should get autowired cleanly.
b. Using Java #Configuration based mechanism:
#Configuration
public class MyBeanConfiguration {
#Bean
public UserRewardAccountValidatorFactory myFactory() {
return UserRewardAccountValidatorFactory.getInstance();
}
}
public abstract class AService<T> {
public T needsToBeAdvised(T param) {
T result = doSomething(param);
return result;
}
}
#Service
public class BService extends AService<B> {
#Override
public T needsToBeAdvised(T param) {
return super.needsToBeAdvised(param);
}
}
#Service
public class CService extends AService<C> {}
// (B & C implement an interface AType)
#Component
#Aspect
public class MyAspect {
#Pointcut("execution(* package.AService+.needsToBeAdvised(*))")
private void aNeedToBeAdvised() {}
#AfterReturning(pointcut="aNeedToBeAdvised()", returning="param")
public void interceptNeedsToBeAdvised(JoinPoint joinPoint, AType param) {
// some action
}
}
Given this setup:
bService.needsToBeAdvised(bParam) //is intercepted
but,
cService.needsToBeAdvised(cParam) //is NOT.
How do I achieve this without overriding needsToBeAdvised() in CService?
EDIT:
I should add that BService and CService are both in the same package.
If I change my point-cut to the following:
#Pointcut("execution(* package.CService.needsToBeAdvised(*))")
cService.needsToBeAdvised(cParam) //is still not intercepted
The only way it works is if I override needsTobeAdvised() in CService
Are all services in the same package? Given from your example code, I suspect AService and BService to be in the package package, but CService to be in another package. If services indeed are in different packages, you have some options:
Move so that they are in the same package
Change you pointcut to be more generic, e.g. "execution(* *.A+.needsToBeAdvised(*))
Add more pointcuts
I was wondering if this recipe does not use Open Session in View or Dozer works (the scenario is a spring, jsf, hibernate application):
I have this in back-end module:
public abstract class AbstractMap<E extends Serializable> implements Mapper<E> {
public abstract E map(E e) ;
public List<E> map(List<E> l) {
List<E> map = new ArrayList<E>();
for (E t : l) {
map.add(this.map(t));
}
return map;
}
}
#Component
public class PersonService extends AbstractService<Person>{
#Autowired
PersonDAO personDao;
#Transacted
public List<Person> findPeople(Mapper<Person> m, PersonFilter filter) {
return m.map(personDao.findByFilter(filter));
}
}
In front-end I have this jsb bean:
#Component
#Scope("session")
public class PersonBean {
#Autowired
PersonService personService;
PersonFilter personFilter;
List<Person> getPeople() {
return personService.findPeople(new AbstractMap<People>() {
public Person map(Person person) {
Person dto = new Person();
dto.setName(person.getName());
dto.setAddresses(new AbstractMap<Address>() {
public Address map(Address a) {
Address dto = new Address();
dto.setStreet(a.getStreet());
return dto;
}
}.map(person.getAddresses()));
return dto;
}
}, personFilter);
}
}
From my point of view has some advantages:
front-end must to indicate that it want to retrieve every invocation to back-end.
if need use a mapper in more than a place, I can move map code to a external class.
back-end open and close transaction.
don't need duplicate code to define DTOs for Person and Address.
What problems that could cause this proposed solution?