I am trying to add json serialization to my SpringBoot app using MapStruct. #Mapper class uses #Service to add some "aftermapping" logic. The problem is, that this #Service class is not autowired.
This is my Mapper class:
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public abstract class InstrumentMapper {
protected MarketDataService marketDataService; // is #Service
#Mapping(target = "marketCode",
expression = "java(instrument.getMarket().getCode())")
public abstract InstrumentDto fromInstrument(Instrument instrument);
public abstract List<InstrumentDto> fromInstruments(List<Instrument> instruments);
#Mapping(target = "market",
expression = "java(marketDataService.findMarketByCode(instrumentDto.getMarketCode()))")
public abstract Instrument toInstrument(InstrumentDto instrumentDto);
public abstract List<Instrument> toInstruments(List<InstrumentDto> instrumentDtos);
#Autowired
public void setMarketDataService(MarketDataService marketDataService) {
this.marketDataService = marketDataService;
}
}
When toInstrument method is called, application fails with NPE, trying to marketDataService.findMarketByCode(instrumentDto.getMarketCode()).
Hopefully, this information will be enough. Let me know if anything else is needed.
Thanks in advance!
Update:
MarketDataService class. It is added to the context through #Service annotation.
#Service
public class MarketDataService {
#Autowired
private InstrumentRepository instrumentRepository;
public Instrument findInstrumentByCode(String code) {
return instrumentRepository.findFirstByCode(code);
}
public List<InstrumentDto> getAllInstrumentDtos() {
List<Instrument> instruments = getAllInstruments();
List<InstrumentDto> dtos = Mappers.getMapper(InstrumentMapper.class).fromInstruments(instruments);
return dtos;
}
public void updateInstrument(InstrumentDto instrumentDto) {
Instrument instrument = findInstrumentByCode(instrumentDto.getCode());
if (instrument == null) {
throw new IllegalArgumentException("Market with given code not found!");
}
instrumentRepository.delete(instrument);
instrument = Mappers.getMapper(InstrumentMapper.class).toInstrument(instrumentDto);
instrumentRepository.save(instrument);
}
}
The algorithm is the following: #Controller class gets PUT request and calls MarketDataService.updateInstrument method with the body of the request (instrumentDto parameter). The latter one calls toInstrument method with the same parameter.
The reason why you have an NPE is because you are using the MapStruct Mappers factory for a non default component model.
The Mappers factory does not perform any dependency injections.
You have to inject your mapper in your MarketDataService. Be careful when doing that because you have a cyclic dependency.
In addition to that the patterns you are using in your Mapper are not really the right ones. You are using an expression when a simple source will do.
e.g.
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public abstract class InstrumentMapper {
protected InstrumentRepository instrumentRepository;
#Mapping(target = "marketCode", source = "market.code")
public abstract InstrumentDto fromInstrument(Instrument instrument);
public abstract List<InstrumentDto> fromInstruments(List<Instrument> instruments);
#Mapping(target = "market", source = "marketCode")
public abstract Instrument toInstrument(InstrumentDto instrumentDto);
public abstract List<Instrument> toInstruments(List<InstrumentDto> instrumentDtos);
protected Instrument findInstrumentByCode(String code) {
return instrumentRepository.findFirstByCode(code);
}
#Autowired
public void setMarketDataService(MarketDataService marketDataService) {
this.marketDataService = marketDataService;
}
}
Related
My code goes like this,
#Mapper
public interface DtoMapper {
DtoMapper MAPPER = Mappers.getMapper(DtoMapper.class);
ExampleModel dtoToDdModel(ExampleDto exampleDto);
ExampleDto someOtherEntityToDto(OtherEntity otherEntity);
}
public class DtoMapperImpl implements DtoMapper {
#Override
public ExampleDto someOtherEntityToDto(OtherEntity otherEntity){
if ( OtherEntity == null ) {
return null;
}
// Conversion
}
// I don't want to define dtoToDdModel
}
Is it possible to only provide implementation of someOtherEntityToDto in DtoMapperImpl? While dtoToDdModel follows the default mapping provided by MapStruct?
Thank you in Advance!
Apply below annotation in DtoMapper interface
#InheritInverseConfiguration
ExampleModel dtoToDdModel(ExampleDto exampleDto)
MapStruct will only implement the abstract methods. Which means that if you provide a custom default methods then MapStruct will not implement it.
e.g.
#Mapper
public interface DtoMapper {
DtoMapper MAPPER = Mappers.getMapper(DtoMapper.class);
default ExampleModel dtoToDdModel(ExampleDto exampleDto) {
//TODO write implementation
}
ExampleDto someOtherEntityToDto(OtherEntity otherEntity);
}
Note: the #Mapper does not have to be an interface it can also be an abstract class.
I am trying to make my advice more dynamic based on the class/method it is providing advice for. Looking for something like this pseudoish code:
class Activity
private TheAdviceStrategyInterface activityAdviceStrategy = new ActivityAdviceStrategy();
#Entity(adviceStrategy = activityAdviceStrategy)
public void doSomething(ActivityInput ai) { ... }
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Entity {
public TheAdviceStrategyInterface adviceStrategy();
}
#Aspect
public class TheAdvice {
#Around("#annotation(entity)")
public Object doStuff (ProceedingJoinPoint pjp, Entity entity) {
...
TheAdviceStrategyInterface theStrat = entity.adviceStrategy();
....
}
}
But of course we can not have Objects or Interfaces as annotation parameters.
Any "advice" on how I can implement this? I basically want one Aspect annotation to handle very similar situations, with a slight difference depending on which class is using the annotation.
But of course we can not have Objects or Interfaces as Annotation
parameters. Any "advice" on how I can implement this?
1- Create a String parameter in the Entity interface to represent the possible strategies:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Entity {
public String adviceStrategy();
}
2- Create a class that implements the factory pattern, for instance:
public class TheAdviceStrategyFactory {
//use getShape method to get object of type shape
public TheAdviceStrategyInterface getStrategy(String strategy){
if(strategy == null){
return null;
}
if(strategy.equalsIgnoreCase("Strategy1")){
return new TheAdviceStrategy1();
} else if(strategy.equalsIgnoreCase("Strategy2")){
return new TheAdviceStrategy2();
return null;
}
}
with the Classes TheAdviceStrategy1 and TheAdviceStrategy2 implementing the interface TheAdviceStrategyInterface.
Take advantage of both in the advice:
#Aspect
public class TheAdvice {
#Around("#annotation(entity)")
public Object doStuff (ProceedingJoinPoint pjp, Entity entity) {
...
TheAdviceStrategyFactory factory = new TheAdviceStrategyFactory();
TheAdviceStrategyInterface theStrat = factory.getStrategy(entity.adviceStrategy());
....
}
}
I have an interface(QBuilder) and there are two classes(MBuilder, TBuilder) implementing this interface. The interface contains a test method. This method receives parameter type of MCubeInfo in MBuilder and TCubeInfo in TBuilder.
public interface QBuilder<T> {
public String test(T cubeInfo);
}
public class MBuilder implements QBuilder<MCubeInfo> {
#Override
public String test(MCubeInfo cubeInfo) {
System.out.println("MCube Info");
return "MCube";
}
}
public class TBuilder implements QBuilder<TCubeInfo> {
#Override
public String test(TCubeInfo cubeInfo) {
System.out.println("TCube Info");
return "TCube";
}
}
I am expecting that when I call test method in QuerySvc, qBuilder redirect to me according to the parameter type. However in autowired QBuilder set automatically with MBuilder. Therefore when I sent TCubeInfo object to the test function, occurs an error that it can not be convert MCubeInfo.
#RestController
public class QuerySvc {
private QBuilder qBuilder;
#Autowired
public void setQBuilder(QBuilder q){
qBuilder = q)
}
#RequestMapping(value = "/boot", method = RequestMethod.GET)
public ResponseEntity<String> getTest(){
.
.
.
TCubeInfo cube = .....
qBuilder.test(cube);
}
}
When I search the problem, I encountered with #Qualifier annotation but I cannot adapt it to my problem.
I think you should make two different beans of these two Service/Component Class that you defined.
public class MBuilder //two different beans in configuration Class.
public class Tuilder
Spring-boot Configuration Class.
#Bean(name="mBuilder") //define bean name
public MBuilder mBuilder(){ //mBuilder bean for MBuilder Class.
return new MBuilder();
}
#Bean(name="tBuilder") //Define bean name
public TBuilder tBuilder(){ //tBuilder bean for TBuilder Class.
return new TBuilder();
}
Now, In Your RestController try to inject two beans with different #Qualifier statement. As shown below.
RestController Class.
#RestController
public class QuerySvc {
#Qualifier("tBuilder") //Now use tBuilder Object as per Your Need.
#Autowired
private QBuilder tBuilder;
#Qualifier("mBuilder") // You can use mBuilder Object as per need.
#Autowired
private QBuilder mBuilder;
#Autowired
public void setQBuilder(QBuilder q){
qBuilder = q)
}
#RequestMapping(value = "/boot", method = RequestMethod.GET)
public ResponseEntity<String> getTest(){
.
.
.
TCubeInfo cube = .....
qBuilder.test(cube);
}
}
Note :- Here You Used generics Typed parameters which resolve at Compile Time Only. Here TCubeInfo and MCubeInfo both are different classes (they are not in relationship heirarchy). So, It is impossible to cast the object which not comes under heirarchy. It will raise ClassCastException.
I am trying to build a REST Controller using Spring. To format the data for readability and more integration, I have used Mapstruct. Here's how I wrote Mapper.
#Mapper
public abstract class DeviceDataMapper {
#Autowired
DeviceService deviceService;
public static DeviceDataMapper INSTANCE = Mappers.getMapper(DeviceDataMapper.class);
#Mappings({
#Mapping(source = "deviceId", target = "iddevice"),
#Mapping(source = "deviceName", target = "name")
})
public abstract TODevice deviceToTODevice(DeviceData device);
public DeviceData toDeviceToDeviceData(TODevice toDevice){
DeviceData deviceData = new DeviceData();
deviceData.setDeviceId(toDevice.getIddevice());
deviceData.setDeviceName(toDevice.getName());
deviceData.setDeviceTemplateId(toDevice.getDeviceTemplateId());
try {
deviceData.setDeviceTemplateName(deviceService.findDeviceTemplateById(toDevice.getDeviceTemplateId()).getName());
} catch (Exception e) {
e.printStackTrace();
}
return deviceData;
}}
The API Controller function looks like this
#RequestMapping(value = "/{deviceId}",method = RequestMethod.GET)
public #ResponseBody DeviceData get(#PathVariable int deviceId) {
DeviceData deviceData=new DeviceData();
try {
deviceData = DeviceDataMapper.INSTANCE.toDeviceToDevice(deviceService.findOne(deviceId));
} catch (Exception e) {
e.printStackTrace();
}
return deviceData;
}
The output deviceData returns fine except for one detail. I couldn't get to this function deviceService.findDeviceTemplateById(toDevice.getDeviceTemplateId() (where deviceService is autowired). The error stack trace shows me NullPointerException. So I am wondering whether is there any general rule about the accessibility of the autowired resources in abstract class? Or is the way I am instantiating that makes this function inaccessible? What should I change to make it work? I have also tried with #Inject from javax.inject with same result.
You could use Spring as the component model for the mapper:
#Mapper(componentModel="spring")
public abstract class DeviceDataMapper {
...
}
That way you can inject dependencies into it (e.g. other hand-written it uses) as well as inject the mapper into other classes instead of resorting to the INSTANCE pattern.
In order for #Autowired to work, the DeviceDataMapper class needs to be a Spring bean. It will not work if you instantiate it yourself.
Either make it a Spring bean and use it like one, or pass a reference to deviceService into it from your controller.
I created one factory to decide what best implementation should be returned, based in some conditional check.
// Factory
#Component
public class StoreServiceFactory {
#Autowired
private List<StoreService> storeServices;
public StoreService getService(){
if(isActiveSale){
return storeServices.get("PublicStoreService")
}
return storeServices.get("PrivateStoreService")
}
}
//Service Implementations
#Service
#Qualifier("PublicStoreService")
public class PublicStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
#Service
#Qualifier("PrivateStoreService")
public class PrivateStoreService implements StoreService {
public getStoreBalanceScore(){
Do Stuff....
}
}
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService();
return simulationService.simulate(sellerId, simulation);
}
Is this approach good? If yes, how can i get my service from an elegant way?
I would like to use only annotations, without configurations.
You should use a map instead of a List and pass a string parameter to the getService method.
public class StoreServiceFactory {
#Autowired
private Map<String,StoreService> storeServices = new HashMap<>();
public StoreService getService(String serviceName){
if(some condition...){
// want to return specific implementation on storeServices map, but using #Qualifier os something else
storeServices.get(serviceName)
}
}
}
You can prepopulate the map with supported implementations. You can then get an appropriate service instance as follows :
// Controller
#Autowired
StoreServiceFactory storeServiceFactory;
#Override
public StoreData getStoreBalance(String storeId) {
StoreService storeService = storeServiceFactory.getService("private");//not sure but you could pass storeId as a parameter to getService
return simulationService.simulate(sellerId, simulation);
}
If you don't like using Strings, you can define an enum for the supported implementations and use that as the key for your map.
You don't need to create a list or map on your code. You can retrieve it directly from Spring context using GenericBeanFactoryAccessor. This has various method to retrieve a specific bean like based on name, annotation etc. You can take a look at javadoc here. This avoids unnecessary complexity.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/beans/factory/generic/GenericBeanFactoryAccessor.html