I have multiple beans with #Component implementing a generic interface.
I have a class where I use methods from these beans. Instead of autowiring each bean separately, I was figuring out of autowiring the interface as a list.
But how do I call the methods of different beans when I just autowire the interface.
public interface Generic {
}
#Component
public class A implements Generic{
.....
public void test{
}
}
#Component
public class B implements Generic{
.....
public void read{}
}
#component class C {
#Autowired
List <Generic> mylist; // type of list is Generic
Now I need to access read from B and test from A
With your new question you CAN do it like this way but I think it's better to use 2 seperate lists instead of the instanceOf.
public interface Generic { }
public interface GenericTestable extends Generic {
void test()
}
public interface GenericReader extends Generic {
void read()
}
#Component
public class A implements GenericTestable {
public void test() { }
}
#Component
public class B implements GenericReader {
public void read() { }
}
#Component
public class C {
private final List<Generic> genericItems;
#Autowired
public C(List<Generic> genericItems) {
this.genericItems = genericItems;
}
public void callThem() {
this.genericItems.forEach(this::callMethodOnSpecificType);
}
private void callMethodOnSpecificType(Generic genericItem) {
if (genericItem instanceof GenericTestable) {
((GenericTestable) genericItem).test();
} else if (genericItem instanceof GenericReader) {
((GenericReader) genericItem).read();
}
}
}
But I think that something like this is a better approach. Cleaner and faster code. Remove the Generic interface and use only the 2 separate interfaces.
public class BetterC {
private final List<GenericTestable> genericTestables;
private final List<GenericReader> genericReaders;
#Autowired
public BetterC(List<GenericTestable> genericTestables, List<GenericReader> genericReaders) {
this.genericTestables = genericTestables;
this.genericReaders = genericReaders;
}
public void callTestables() {
this.genericTestables.forEach(GenericTestable::test);
}
public void callReaders() {
this.genericReaders.forEach(GenericReader::read);
}
}
How about using the Generic interface to route the call ?
From the java-doc api : Ordered
Ordered is an interface that can be implemented by objects that should
be orderable, for example in a Collection. The actual order can be
interpreted as prioritization, with the first object (with the lowest
order value) having the highest priority.
import org.springframework.core.Ordered;
public interface Generic extends Ordered {
void call();
}
----
#Component
public class A implements Generic {
#Override
public int getOrder() {
return 2;
}
#Override
public void call() {
test();
}
public void test() {
System.out.println("Call test()");
}
}
----
#Component
public class B implements Generic {
#Override
public int getOrder() {
return 1;
}
#Override
public void call() {
read();
}
public void read() {
System.out.println("call read()");
}
}
----
#Component
public class C {
List<Generic> list;
public C(List<Generic> list) {
this.list = list;
}
public void testMethod() {
for(Generic g: list) {
g.call();
}
}
}
try this in your implementation
#Component
#Qualifer("a")
public class A implements Generic{ ..... public void test(){ }}
#Component
#Qualifer("b")
public class B implements Generic{ ..... public void read(){ }}
#component
public class C {
#Autowired
Generic a;
#Autowired
Generic b;
... call respective methods
}
Related
I am following this article to implement a database read/write separation feature by calling different methods. However, I got the error:
Missing method call for verify(mock) here: verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY);
when doing the testing.
My test case is trying to verify DatabaseEnvironment.READONLY has been set once when using TransactionReadonlyAspect AOP annotation:
// TransactionReadonlyAspectTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {LoadServiceImpl.class, TransactionReadonlyAspect.class})
public class TransactionReadonlyAspectTest {
#Autowired
private TransactionReadonlyAspect transactionReadonlyAspect;
#MockBean
private LoadServiceImpl loadService;
#Test
public void testReadOnlyTransaction() throws Throwable {
ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
Transactional mockTransactional = mock(Transactional.class);
DatabaseContextHolder spyDatabaseContextHolder = mock(DatabaseContextHolder.class);
when(mockTransactional.readOnly()).thenReturn(true);
when(loadService.findById(16)).thenReturn(null);
when(mockProceedingJoinPoint.proceed()).thenAnswer(invocation -> loadService.findById(16));
transactionReadonlyAspect.proceed(mockProceedingJoinPoint, mockTransactional);
verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY); // got the error: Missing method call for verify(mock)
verify(loadService, times(1)).findById(16);
assertEquals(DatabaseContextHolder.getEnvironment(), DatabaseEnvironment.UPDATABLE);
}
}
//TransactionReadonlyAspect.java
#Aspect
#Component
#Order(0)
#Slf4j
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,
org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
try {
if (transactional.readOnly()) {
log.info("Inside method " + proceedingJoinPoint.getSignature());
DatabaseContextHolder.set(DatabaseEnvironment.READONLY);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
// DatabaseContextHolder.java
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT = new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
DatabaseEnvironment context = CONTEXT.get();
System.out.println("context: " + context);
return CONTEXT.get();
}
public static void reset() {
CONTEXT.set(DatabaseEnvironment.UPDATABLE);
}
}
//DatabaseEnvironment.java
public enum DatabaseEnvironment {
UPDATABLE,READONLY
}
// LoadServiceImpl.java
#Service
public class LoadServiceImpl implements LoadService {
#Override
#Transactional(readOnly = true)
public LoadEntity findById(Integer Id) {
return this.loadDAO.findById(Id);
}
...
}
I just want to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) has been used once then in the TransactionReadonlyAspect finally block it will be reset to DatabaseEnvironment.UPDATABLE which make sense.
However, how to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) gets called once? Why does this error occur? Is there a better way to test TransactionReadonlyAspect?
public abstract class BaseController<E,DTO> {
protected final BaseService<E,DTO> service;
public BaseController(final BaseService<E,DTO> service) {
this.service = service;
}
// code ....
}
public class CarController extends BaseController<Car, CarDto> {
public CarController(final CarService service) {
super(service);
}
// code ...
}
#Service
public class CarService extends BaseService<Car, CarDto> {
// code ....
//custom method
public String getName(){
return "Car Sevice";
}
}
Spring-boot generic method develop. Service created in constructor. There is no problem working properly. Problem is custom method of service. Because service extends BaseService and see all base services methods but, it is custom services as CarService. Intellij give error
service.getname();
how can i solve this problem
your service is type of BaseService, not CarService.
When you write something like BaseService service = new CarService() you can't access methods of CarService because your variable is BaseService type.
you would need to change service to be type of generic, same as you did with entity and dto.
public abstract class BaseController<E,DTO, S extends BaseService> {
protected final S service;
public BaseController(S service) {
this.service = service;
}
// code ....
}
public class CarController extends BaseController<Car, CarDto, CarService> {
public CarController(final CarService service) {
super(service);
service.getName();
}
// code ...
}
The extends BaseService in BaseController is only required if you would like to use any methods of BaseService in BaseController, like for example:
public abstract class BaseController<E,DTO, S extends BaseService> {
protected final S service;
public BaseController(S service) {
this.service = service;
service.doSomething();
service.overrideMe();
}
// code ....
}
public class CarController extends BaseController<Car, CarDto, CarService> {
public CarController(final CarService service) {
super(service);
service.getName();
service.overrideMe();
service.doSomething();
}
// code ...
}
public abstract class BaseService<E, DTO> {
public abstract void overrideMe();
public void doSomething() {
System.out.println("hi");
}
}
#Service
public class CarService extends BaseService<Car, CarDto> {
// code ....
//custom method
public String getName(){
return "Car Sevice";
}
#Override
public void overrideMe() {
System.out.println("Overrided");
}
}
If your BaseController doesn't need to know that S is extending BaseService (you won't call any methods of BaseService in BaseController) you can delete extends BaseService part
public abstract class BaseController<E,DTO, S> {
protected final S service;
public BaseController(S service) {
this.service = service;
}
// code ....
}
public class CarController extends BaseController<Car, CarDto, CarService> {
public CarController(final CarService service) {
super(service);
service.getName();
service.overrideMe();
service.doSomething();
}
// code ...
}
public abstract class BaseService<E, DTO> {
public abstract void overrideMe();
public void doSomething() {
System.out.println("hi");
}
}
public class CarService extends BaseService<Car, CarDto> {
// code ....
//custom method
public String getName(){
return "Car Sevice";
}
#Override
public void overrideMe() {
System.out.println("Overrided");
}
}
Also think if you need E and DTO in BaseController
How can I choose a service implementation depending on a request parameter on SpringBoot? I can do this by manually instantiating the service, but that's not making use of the Spring Injection feature.
I can also Autowire the two services separately but I'm thinking that if I have more implementations that would pollute the class.
Here's a crude example:
#RestController
class RestControllerTest {
#Autowired
PizzaService pizzaService;
public void bakePizza(#RequestParam("type") String type,#RequestParam("extra") String extra) {
if (type.equals("cheese")) {
//set pizzaService Cheese Implementation
pizzaService = new CheezePizza();
} else {
//set PizzaService vegetable Impleentation;
pizzaService = new VegetablePizza();
}
pizzaService.prepareIngredients(extra);
pizzaService.bakePizza();
}
}
public abstract class PizzaService {
String ingredients;
public abstract void prepareIngredients(String exraIngredient);
public void bakePizza() {
System.out.println("baking pizza with " + ingredients);
}
}
class CheezePizza extends PizzaService {
#Override
public void prepareIngredients(String exraIngredient) {
ingredients = "Cheese " + exraIngredient;
}
}
class VegetablePizza extends PizzaService {
#Override
public void prepareIngredients(String exraIngredient) {
ingredients = "Vegetable " + exraIngredient;
}
}
You could autowire list of beans of same type. So let's say you add getType() to your PizzaService and register every type as spring bean.
public abstract class PizzaService {
abstract String getType();
}
#Component
class CheezePizza extends PizzaService {
#Override
public String getType() {
return "cheese";
}
}
#Component
class VegetablePizza extends PizzaService {
#Override
public String getType() {
return "vegetable";
}
}
#RestController
class RestControllerTest {
private final Map<String, PizzaService> pizzaServices;
public RestControllerTest(List<PizzaService> services) {
pizzaServices = services.stream().collect(Collectors.toMap(PizzaService::getType, Function.identity()));
}
public void bakePizza(#RequestParam("type") String type, #RequestParam("extra") String extra) {
PizzaService pizzaService = pizzaServices.get(type); // remember of handling missing type
pizzaService.prepareIngredients(extra);
pizzaService.bakePizza();
}
}
Another way is to use name your beans by convention i.e. cheesePizza, vegetablePizza and then use ApplicationContext#getBean(type + "Pizza") but I like first approach better, because it's less magical.
I'm trying to create a BeanFactory called TaskBeanFactory that I can Autowire into another prototype class that's running on a thread. I want a different instance of a bean returned by the Factory based on a taskName that i want to pass into it but when i start the application i get a null pointer exception because the taskName is null. I had a look at this article but i'm confused about how I should configure the Factory and then pass in the taskName.
The Factory:
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.stereotype.Component;
#Data
#Component
#NoArgsConstructor
public class TaskBeanFactory extends AbstractFactoryBean<GenericTask>{
private TaskNameEnum taskName;
public TaskBeanFactory(TaskNameEnum taskName) {
setSingleton(false);
}
#Override
public Class<?> getObjectType() {
return GenericTask.class;
}
#Override
protected GenericTask createInstance() throws Exception {
switch (taskName) {
case FILE_OPERATION:
return new FileTask();
case DATA_OPERATION:
return new DataTask();
default:
return new GenericTask();
}
}
}
The classes used by the Factory:
#Data
public class GenericTask {
private String idTask;
public void executeTask(Work work) {};
}
#Component
#Scope(value="prototype")
public class FileTask extends GenericTask {
#Override
public void executeTask(Work work) {
//some processing
}
}
#Component
#Scope(value="prototype")
public class DataTask extends GenericTask {
#Override
public void executeTask(Work work) {
//some processing
}
}
and the thread that's calling the Factory:
#Slf4j
#Data
#Scope("prototype")
#Component
public class WorkerThread implements Runnable {
#Autowired
private TaskBeanFactory taskBeanFactory;
#Autowired
private DataService dataService;
#Override
public void run() {
//iterate a Map of taskIds from the dataService
taskBeanFactory.setTaskName(TaskNameEnum.valueOf(taskEntry.getKey()));
GenericTask genericTask = taskBeanFactory.getObject();
//expecting genericTask to be of Type FileTask if called with one Key
//or of Type DataTask if called with another
}
}
}
I'm just new in the spring architecture and I am wondering if it is possible to use a generic T (o what you want) in an ItemStreamReader.
Something like this:
public class Reader implements ItemStreamReader<T extends SomeClass>{
public T read() {
.......
}
public void open() {
.......
}
public void update() {
.......
}
public void close() {
.......
}
}
So I pass to the reader various objects that extends SomeClass.
this should work:
public class Reader<T extends SomeClass> implements ItemStreamReader<T>{
public T read() {
.......
}
public void open() {
.......
}
public void update() {
.......
}
public void close() {
.......
}
}
use it like:
Reader<SomeClass> reader = new Reader<>();
Reader<ExtendedFromSomeClass> reader2 = new Reader<>();
I recommend you to read first about java generics.
http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html#What is a parameterized (or generic) type?
But if you define your class
public class Reader implements ItemStreamReader<SomeClass>{
public SomeClass read() {
.......
}
public void open() {
.......
}
public void update() {
.......
}
public void close() {
.......
}
}
Your method can return any object that is Subclass of SomeClass.
For example
Reader a = new Reader();
Subclass b = (Subclass)a.read();