Spring MVC - how to load/inject service dynamically - spring

For my Spring MVC application, I will have a controller which will handle all requests from paths say service/*.
urls can be like
/service/item/getitems,
/service/property/getproperties
The controller will have to load the service class at runtime based on the url specified. For example, if url is /service/item/getitems, the controller should load itemService and should be able to call itemService.getItems();
if the url is /service/property/getproperties, it should load propertyService and call propertyService.getProperties()
How can I implement the run time (dynamic) loading of services from controller?

application.yml should look like this
server:
port: 8090
servlet:
context-path: /service
ApiController.java should look like this
public abstract class ApiController<Id extends Serializable, E> {
/**
* Collection for service classes.
*
* #see AbstractService
*/
private final AbstractService<Id, E> service;
#Autowired
private ApplicationContext appContext;
public ApiController(AbstractService<Id, E> service) {
this.service = service;
}
}
AbstractService.java should look like this
public interface AbstractService<Id extends Serializable, E> {
/**
*
* #return
*/
public default Class<E> getEntityClass() {
return (Class<E>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
ItemController.java should look like this
#RestController
#RequestMapping(value = "/item")
public class ItemController extends ApiController<Integer, Item> {
private final ItemService itemService;
#Autowired
public ItemController(ItemService service) {
super(service);
this.service = service;
}
}
PropertyController.java should look like this
#RestController
#RequestMapping(value = "/property")
public class PropertyController extends ApiController<Integer, Property> {
private final PropertyService propertyService;
#Autowired
public PropertyController(propertyService service) {
super(service);
this.service = service;
}
}
That's what you need to do, in order to get desired results.

Related

How refactor 2 restcontrollers almost identical?

I have 2 rest controllers which look the same:
File 1:
#RestController
#RequestMapping("/api/v1/foo")
#RequiredArgsConstructor
public class FooAPI {
private final ConfigService<Foo> service;
#GetMapping
(...)
File 2:
#RestController
#RequestMapping("/api/v1/bar")
#RequiredArgsConstructor
public class BarAPI {
private final ConfigService<Bar> service;
#GetMapping
(...)
Everything is the same, except the private final ConfigService<(Foo|Bar)> service;.
Is it possible to generalize a restcontroller class?
You can create abstract class that you can inherit from.
So you get something like:
Abstract class:
public abstract class AbstractAPI {
private final ConfigService<?> service;
public AbstractAPI(ConfigService<?> service){
this.service = service;
}
#GetMapping
(...)
File 1:
#RestController
#RequestMapping("/api/v1/foo")
public class FooAPI extends AbstractAPI {
public FooAPI (ConfigService<?> service) {
super(service);
}
}
File 2:
#RestController
#RequestMapping("/api/v1/bar")
public class BarAPI extends AbstractAPI {
public BarAPI (ConfigService<?> service) {
super(service);
}
}

Spring - How to test Controller with ApplicationEventPublisher dependency?

I have a Controller which is publishing an event
#RestController
public class Controller
{
#Autowired
private ApplicationEventPublisher publisher;
#GetMapping("/event")
public void get()
{
publisher.publishEvent(new Event());
}
}
Now I want to test that the event is published. First I tried to #MockBean the ApplicationEventPublisher and verify the method call. But this does not work according to https://jira.spring.io/browse/SPR-14335
So I am doing it like this:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = Controller.class)
public class ControllerTest
{
#Autowired
private MockMvc mockMvc;
#Test
public void getTest() throws Exception
{
this.mockMvc.perform(get("/").contentType(MediaType.APPLICATION_JSON)
.andExpect(status().isOk());
assertNotNull(Listener.event);
}
#TestConfiguration
static class Listener
{
public static Event event;
#EventListener
void listen ( Event incoming )
{
event = incoming;
}
}
}
Is there an easier way for this common use case?
You can do it like this
#RunWith(SpringRunner.class)
public class ControllerTest {
private MockMvc mockMvc;
#MockBean
private ApplicationEventPublisher publisher;
#Before
public void setup() {
Controller someController= new Controller(publisher);
mockMvc = MockMvcBuilders.standaloneSetup(someController).build();
}
#Test
public void getTest() throws Exception
{
ArgumentCaptor<Event> argumentCaptor = ArgumentCaptor.forClass(Event.class);
doAnswer(invocation -> {
Event value = argumentCaptor.getValue();
//assert if event is correct
return null;
}).when(publisher).publishEvent(argumentCaptor.capture());
this.mockMvc.perform(get("/").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
verify(publisher, times(1)).publishEvent(any(Event.class));
}
}
And also change Field Injection to Constructor Injection in your controller class(It is a good practice).
#RestController
public class Controller
{
private ApplicationEventPublisher publisher;
#Autowired
public Controller(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
....
}
im facing the same problem, and for now i solve the problem using TestConfiguration:
#SpringBootTest
class MyUseCaseIT {
#Autowired private ApplicationEventPublisher publisher;
#Autowired private MyService service;
#Test
void callUseCase() {
var event = mock(MyEvent.class);
doNothing().when(publisher).publishEvent(event);
service.useCase();
verify(publisher).publishEvent(event);
}
#TestConfiguration
static class MockitoPublisherConfiguration {
#Bean
#Primary
ApplicationEventPublisher publisher() {
return mock(ApplicationEventPublisher.class);
}
}
Another possiblity would be that you replace the ApplicationEventPublisher instance on your controller with the mock instance using reflection in your test:
public class ControllerTest {
...
// Collect your controller
#Autowired
private Controller controller;
// Use the mock publisher
#MockBean
private ApplicationEventPublisher publisherMock;
// E.g. in setup set the mock publisher on your controller
#Before
public void setup() {
ReflectionTestUtils.setField(controller, "publisher", publisherMock);
}
...

Spring boot autowiring an interface with multiple implementations

In normal Spring, when we want to autowire an interface, we define it's implementation in Spring context file.
What about Spring boot?
how can we achieve this?
currently we only autowire classes that are not interfaces.
Another part of this question is about using a class in a Junit class inside a Spring boot project.
If we want to use a CalendarUtil for example, if we autowire CalendarUtil, it will throw a null pointer exception. What can we do in this case? I just initialized using "new" for now...
Use #Qualifier annotation is used to differentiate beans of the same interface
Take look at Spring Boot documentation
Also, to inject all beans of the same interface, just autowire List of interface
(The same way in Spring / Spring Boot / SpringBootTest)
Example below:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
public interface MyService {
void doWork();
}
#Service
#Qualifier("firstService")
public static class FirstServiceImpl implements MyService {
#Override
public void doWork() {
System.out.println("firstService work");
}
}
#Service
#Qualifier("secondService")
public static class SecondServiceImpl implements MyService {
#Override
public void doWork() {
System.out.println("secondService work");
}
}
#Component
public static class FirstManager {
private final MyService myService;
#Autowired // inject FirstServiceImpl
public FirstManager(#Qualifier("firstService") MyService myService) {
this.myService = myService;
}
#PostConstruct
public void startWork() {
System.out.println("firstManager start work");
myService.doWork();
}
}
#Component
public static class SecondManager {
private final List<MyService> myServices;
#Autowired // inject MyService all implementations
public SecondManager(List<MyService> myServices) {
this.myServices = myServices;
}
#PostConstruct
public void startWork() {
System.out.println("secondManager start work");
myServices.forEach(MyService::doWork);
}
}
}
For the second part of your question, take look at this useful answers first / second
You can also make it work by giving it the name of the implementation.
Eg:
#Autowired
MyService firstService;
#Autowired
MyService secondService;
Assume that you have a GreetingService
public interface GreetingService {
void doGreetings();
}
And you have 2 implementations HelloService
#Service
#Slf4j
public class HelloService implements GreetingService{
#Override
public void doGreetings() {
log.info("Hello world!");
}
}
and HiService
#Slf4j
#Service
public class HiService implements GreetingService{
#Override
public void doGreetings() {
log.info("Hi world!");
}
}
Then you have another interface, which is BusinessService to call some business
public interface BusinessService {
void doGreetings();
}
There are some ways to do that
#1. Use #Autowired
#Component
public class BusinessServiceImpl implements BusinessService{
#Autowired
private GreetingService hiService; // Spring automatically maps the name for you, if you don't want to change it.
#Autowired
private GreetingService helloService;
#Override
public void doGreetings() {
hiService.doGreetings();
helloService.doGreetings();
}
}
In case you need to change your implementation bean name, refer to other answers, by setting the name to your bean, for example #Service("myCustomName") and applying #Qualifier("myCustomName")
#2. You can also use constructor injection
#Component
public class BusinessServiceImpl implements BusinessService {
private final GreetingService hiService;
private final GreetingService helloService;
public BusinessServiceImpl(GreetingService hiService, GreetingService helloService) {
this.hiService = hiService;
this.helloService = helloService;
}
#Override
public void doGreetings() {
hiService.doGreetings();
helloService.doGreetings();
}
}
This can be
public BusinessServiceImpl(#Qualifier("hiService") GreetingService hiService, #Qualifier("helloService") GreetingService helloService)
But I am using Spring Boot 2.6.5 and
public BusinessServiceImpl(GreetingService hiService, GreetingService helloService)
is working fine, since Spring automatically get the names for us.
#3. You can also use Map for this
#Component
#RequiredArgsConstructor
public class BusinessServiceImpl implements BusinessService {
private final Map<String, GreetingService> servicesMap; // Spring automatically get the bean name as key
#Override
public void doGreetings() {
servicesMap.get("hiService").doGreetings();
servicesMap.get("helloService").doGreetings();
}
}
List also works fine if you run all the services. But there is a case that you want to get some specific implementation, you need to define a name for it or something like that. My reference is here
For this one, I use #RequiredArgsConstructor from Lombok.
As mentioned in the comments, by using the #Qualifier annotation, you can distinguish different implementations as described in the docs.
For testing, you can use also do the same. For example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyClassTests {
#Autowired
private MyClass testClass;
#MockBean
#Qualifier("default")
private MyImplementation defaultImpl;
#Test
public void givenMultipleImpl_whenAutowiring_thenReturnDefaultImpl() {
// your test here....
}
}
There are 2 approaches when we have autowiring of an interface with multiple implementations:
Spring #Primary annotation
In short it tells to our Spring application whenever we try to autowire our interface to use that specific implementation which is marked with the #Primary annotation. It is like a default autowiring setting. It can be used only once per cluster of implementations of an interface. → #Primary Docs
Spring #Qualifier annotation
This Spring annotation is giving us more control to select the exact implementation wherever we define a reference to our interface choosing among its options. → #Qualifier Docs
For more details follow the links to their documentation.
public interface SomeInterfaces {
void send(String message);
String getType();
}
kafka-service
#Component
public class SomeInterfacesKafkaImpl implements SomeInterfaces {
private final String type = "kafka";
#Override
public void send(String message) {
System.out.println(message + "through Kafka");
}
#Override
public String getType() {
return this.type;
}
}
redis-service
#Component
public class SomeInterfacesRedisImpl implements SomeInterfaces {
private final String type = "redis";
#Override
public void send(String message) {
System.out.println(message + "through Redis");
}
#Override
public String getType() {
return this.type;
}
}
master
#Component
public class SomeInterfacesMaster {
private final Set<SomeInterfaces> someInterfaces;
public SomeInterfacesMaster(Set<SomeInterfaces> someInterfaces) {
this.someInterfaces = someInterfaces;
}
public void sendMaster(String type){
Optional<SomeInterfaces> service =
someInterfaces
.stream()
.filter(service ->
service.getType().equals(type)
)
.findFirst();
SomeInterfaces someService =
service
.orElseThrow(() -> new RuntimeException("There is not such way for sending messages."));
someService .send(" Hello. It is a letter to ....");
}
}
test
#SpringBootTest
public class MultiImplementation {
}
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTest extends MultiImplementation {
#Autowired
private SomeInterfacesMaster someInterfacesMaster;
#Test
void sendMaster() {
someInterfacesMaster.sendMaster("kafka");
}
}
Thus, according to the Open/Closed principle, we only need to add an implementation without breaking existing code.
#Component
public class SomeInterfacesRabbitImpl implements SomeInterfaces {
private final String type = "rabbit";
#Override
public void send(String message) {
System.out.println(message + "through Rabbit");
}
#Override
public String getType() {
return this.type;
}
}
test-v2
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTestV2 extends MultiImplementation {
#Autowired
private SomeInterfacesMaster someInterfacesMaster;
#Test
void sendMasterV2() {
someInterfacesMaster.sendMaster("rabbit");
}
}
If we have multiple implementations of the same interface, Spring needs to know which one it should be autowired into a class. Here is a simple example of validator for mobile number and email address of Employee:-
Employee Class:
public class Employee {
private String mobileNumber;
private String emailAddress;
...
/** Getters & Setters omitted **/
}
Interface EmployeeValidator:
public interface EmployeeValidator {
public Employee validate(Employee employee);
}
First implementation class for Mobile Number Validator:
#Component(value="EmployeeMobileValidator")
public class EmployeeMobileValidator implements EmployeeValidator {
#Override
public Employee validate(Employee employee) {
//Mobile number Validation logic goes here.
}
}
Second implementation class for Email address Validator:
#Component(value="EmployeeEmailValidator")
public class EmployeeEmailValidator implements EmployeeValidator {
#Override
public Employee validate(Employee employee) {
//Email address validation logic goes here.
}
}
We can now autowired these above validators individually into a class.
Employee Service Interface:
public interface EmployeeService {
public void handleEmployee(Employee employee);
}
Employee Service Implementation Class
#Service
public class EmployeeServiceImpl implements EmployeeService {
/** Autowire validators individually **/
#Autowired
#Qualifier("EmployeeMobileValidator") // Autowired using qualifier for mobile validator
private EmployeeValidator mobileValidator;
#Autowired
#Qualifier("EmployeeEmailValidator") // Autowired using qualifier for email valodator
private EmployeeValidator emailValidator;
#Override
public void handleEmployee(Employee employee) {
/**You can use just one instance if you need**/
employee = mobileValidator.validate(employee);
}
}

Adding web sockets support to spring data rest events

I have spring data rest repositories with the default post and update and delete.
I have also implemented an Event handler, now how can I add Web sockets to the event handlers?
#RepositoryEventHandler(User.class)
#Service
public class UserEventHandler {
private static final Logger LOG = LoggerFactory.getLogger(UserEventHandler.class);
#Autowired
private SmtpMailSender smtpMailSender;
#HandleAfterCreate
public void handleBeforeCreate(User u) {
}
}
// Repository class
#Transactional
#RepositoryRestResource(path = "users", collectionResourceRel = "users")
public interface UserRepository extends PagingAndSortingRepository<User, String> {
}

Spring autowire trouble with generic parameter

I try to use generic parameter in autowire but it doesn't work. My goal is to create a generic JSON controller with Spring 4.1.3 and Hibernate
I have a generic abstract class , and I use it to create service via a model objec(the same as my DAO) as generic parameter.
The code of my AbstractService
public interface IGenericService<T extends Serializable> extends IOperations <T>{}
public interface IOperations<T extends Serializable> {
T findOne(final long id);
List<T> findAll();
void create(final T entity);
T update(final T entity);
void delete(final T entity);
void deleteById(final long entityId);
List<T> findByField(String field, String value);
T save(final T entity);
}
//MyAbstractService (generic service)
public abstract class AbstractService<T extends Serializable> implements
IGenericService<T> {
public static final Logger logger = LoggerFactory
.getLogger(AbstractService.class);
public AbstractService(){}
...
#Override
#Transactional
public T update( T entity) {
logger.debug("public T update( T entity)");
return getDao().update(entity);
}
...
}
Now I create a SecuredUserService with this abstract service
#Transactional
#Component //(value = "userService")
#Qualifier("userService")
public class UserService extends AbstractService<SecuredUser> implements
IUserService {
// I override the method upate of the abstract service
#Override
#Transactional
public SecuredUser update(SecuredUser user){
... // password encoding for example
}
}
public interface IUserService extends IGenericService<SecuredUser> {
T findOne(final long id);
...
}
In my JUnit test I made autowire with this code :
#Autowire
IGenericService<SecuredUser> userGenericService;
Or
#Autowire
IUserService userService;
At this point every thing is ok, I use the overrided method of userService and not those of abstractService. I pass my Junit Test. An I create a package.
Now I want to make generic spring mvc controller to handle common Json request GET/PUT/DELETE/POST :
//Generic Controller
public abstract class GenericSecuredController <MODEL extends Serializable> extends CommonSecuredController {
/**
* spring generic service retrieve by MODEL class type
*/
#Autowired
private IGenericService <MODEL> genericService;
/**
* Spring generic URI retrieve by MODEL class type
*/
#Autowired
private IGenericURI<MODEL> genericURI ;
...
}
// interface to manage URI in a generic way
public interface IGenericURI<MODEL extends Serializable> {
// root for the controller
public static String CONTROLLER_MAPPING="" ;
// path to the file system
public static String PATH_MAPPING = "";
// key to retrieve data in path
public static String PATH="{id}";
// Json REST SERVICE MappedUri
public static String JSON_DUMMY = "/dummy";
public static String JSON_GET = "/" + PATH;
public static String JSON_GET_ALL = "";
public static String JSON_CREATE = "";
public static String JSON_DELETE = "/" + PATH;
public static String JSON_UPDATE = "/" + PATH;
public static String HTML_VIEW = "/{view}.view.html";
public String getControllerMapping() ;
public String getPathMapping() ;
}
// The specific URI for the SecuredUser model object
#Component
public class SecuredUserURI implements Serializable, IGenericURI<SecuredUser> {
public static final String CONTROLLER_MAPPING = "/user";
public static final String PATH_MAPPING = "user";
public String getControllerMapping() {
return CONTROLLER_MAPPING;
}
public String getPathMapping() {
return PATH_MAPPING;
}
}
Now I could create a specific controller for SecuredUser like this :
public class UserController extends GenericSecuredController<SecuredUser> {
/**
* creator to set Class type for the GenericSecuredController<MODEL>
*/
public UserController() {
super(SecuredUser.class);
}
}
The problem appear at this point. The autowire of the
IGenericURI<MODEL>
work fine, but the autowiring with
IGenericService <MODEL> genericService;
doesn't use the overrided specific method of the userService but the abstract method with common behaviour!!!
So my question is :
Is it possible to autowire bean with generic parameter like in my example.
Maybe there is to many level for Spring autowiring .
Other information :
As workaround, I try to pass the userService as parameter of the contoller but, the same behaviour: the generic service use the abstract method.
UPDATE : If I autowire IGenericService genericService in the UserController and create a new handler, the specific service is call.
Thanks

Resources