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.
Related
I am not able to share the actual code because of corporate policies but below is an example of method structures.
So in the example I want to the cache on the method in Class B to be cleared when the exception is thrown in class A.
NB: I can not move the cache to Class A so that is not a feasible solution.
I have tried reading all answers and posts online to get this working but not able to figure it out.
Please help with suggestions. A
I have set the following properties in application.properties
spring.cache.enabled=true
spring.cache.jcache.config=classpath:cache/ehcache.xml
#EnableCaching
#EnableTransactionManagement
Main Class{
#Autowired
CacheManager cacheManager
#PostConstruct
void postConstruct(){
(JCacheCacheManager)cachemanager).setTransactionAware(true);
}
}
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
//TestCode to test cache clears if exception thrown here
throw new RuntimeException("test");
}
}
#Service
Class B{
#Cacheable("cacheName")
public List<Data> getDataFromSystem(String key){
client call code here
return dataList;
}
}
There should be other ways, but the following could be a valid solution.
The first step will be to define a custom exception in order to be able to handle it later as appropriate. This exception will receive, among others, the name of the cache and the key you want to evict. For example:
public class CauseOfEvictionException extends RuntimeException {
public CauseOfEvictionException(String message, String cacheName, String cacheKey) {
super(message);
this.cacheName = cacheName;
this.cacheKey = cacheKey;
}
// getters and setters omitted for brevity
}
This exception will be raised by your B class, in your example:
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
// Sorry, because in a certain sense you need to be aware of the cache
// name here. Probably it could be improved
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
Now, we need a way to handle this kind of exception.
Independently of that way, the idea is that the code responsible for handling the exception will access the configured CacheManager and trigger the cache eviction.
Because you are using Spring Boot, an easy way to deal with it is by extending ResponseEntityExceptionHandler to provide an appropriate #ExceptionHandler. Please, consider read for more information the answer I provided in this related SO question or this great article.
In summary, please, consider for example:
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired
private CacheManager cacheManager;
#ExceptionHandler(CauseOfEvictionException.class)
public ResponseEntity<Object> handleCauseOfEvictionException(
CauseOfEvictionException e) {
this.cacheManager.getCache(e.getCacheName()).evict(e.getCacheKey());
// handle the exception and provide the necessary response as you wish
return ...;
}
}
It is important to realize that when dealing with keys composed by several arguments by default (please, consider read this as well) the actual cache key will be wrapped as an instance of the SimpleKey class that contains all this parameters.
Please, be aware that this default behavior can be customized to a certain extend with SpEL or providing your own cache KeyGenerator. For reference, here is the current implementation of the default one provided by the framework, SimpleKeyGenerator.
Thinking about the problem, a possible solution could be the use of some kind of AOP as well. The idea will be the following.
First, define some kind of helper annotation. This annotation will be of help in determining which methods should be advised. For example:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
}
The next step will be defining the aspect that will handle the actual cache eviction process. Assuming you only need to advice Spring managed beans, for simplicity we can use Spring AOP for that. You can use either an #Around or an #AfterThrowing aspect. Consider the following example:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Around("#annotation(your.pkg.EvictCacheOnError)")
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (CauseOfEvictionException e) {
this.cacheManager.getCache(
e.getCacheName()).evict(e.getCacheKey()
);
// rethrow
throw e;
}
}
}
The final step would be annotate the methods in which the behavior should be applied:
#Service
Class A{
#Autowired
B b;
#Transactional
#EvictCacheOnError
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
You may even try generalizing the idea, by providing in the EvictCacheOnError annotation all the necessary information you need to perform the cache eviction:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
String cacheName();
int[] cacheKeyArgsIndexes();
}
With the following aspect:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Autowired
private KeyGenerator keyGenerator;
#Around("#annotation(your.pkg.EvictCacheOnError)")
// You can inject the annotation right here if you want to
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (Throwable t) {
// Assuming only is applied on methods
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
// Obtain a reference to the EvictCacheOnError annotation
EvictCacheOnError evictCacheOnError = method.getAnnotation(EvictCacheOnError.class);
// Compute cache key: some safety checks are imperative here,
// please, excuse the simplicity of the implementation
int[] cacheKeyArgsIndexes = evictCacheOnError.cacheKeyArgsIndexes();
Object[] args = pjp.getArgs();
List<Object> cacheKeyArgsList = new ArrayList<>(cacheKeyArgsIndexes.length);
for (int i=0; i < cacheKeyArgsIndexes.length; i++) {
cacheKeyArgsList.add(args[cacheKeyArgsIndexes[i]]);
}
Object[] cacheKeyArgs = new Object[cacheKeyArgsList.size()];
cacheKeyArgsList.toArray(cacheKeyArgs);
Object target = pjp.getTarget();
Object cacheKey = this.keyGenerator.generate(target, method, cacheKeyArgs);
// Perform actual eviction
String cacheName = evictCacheOnError.cacheName();
this.cacheManager.getCache(cacheName).evict(cacheKey);
// rethrow: be careful here if using in it with transactions
// Spring will per default only rollback unchecked exceptions
throw new RuntimeException(t);
}
}
}
This last solution depends on the actual method arguments, which may not be appropriate if the cache key is based on intermediate results obtained within your method body.
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;
}
}
I have a Spring Boot project ( 2.3.3 ) where I want to validate the service layer methods input parameters. So in my pom.xml I added
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
as it is no more part of the parent. Next I have my service method interface and the implementing service method. My implemening service is annotated with #Validated and my method looks like
public void deleteGreetingById(#NotNull(message = "greetingId must not be null.")Integer greetingId) {
I've also read that the validation is bound per default only to the controller layer. So to enable it also for the servie layer I added a PostValidationProcesser.
#Configuration
public class MethodValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
When I now execute my test with null as input param, nothing happens and no exception is thrown. When I do
Assert.notNull(greetingId,"greetingId must not be null");
inside the method, an InvalidParameterException is thrown like expected. But I would prefere the annotation based validation because of the #Valid validation of whole class Objects as input parameter.
Can one explain why the validation is not triggered?
EDIT:
#RestController
public class GreetingsConsumerController {
private final GreetingsService greetingsService;
public GreetingsConsumerController(GreetingsService greetingsService) {
this.greetingsService = greetingsService;
}
#PostMapping(value = "/greetings", consumes = MediaType.APPLICATION_JSON_VALUE)
public Greeting createGreeting( #RequestBody #Valid GreetingDto greetingDto){
return greetingsService.addGreeting(greetingDto);
}
#GetMapping(value = "/greetings/{id}")
public Greeting getGreetingById(#PathVariable Integer id){
return greetingsService.findGreetingById(id);
}
#GetMapping(value = "/greetings")
public List<Greeting> getAllGreetings(){
return greetingsService.findAllGreetings();
}
#DeleteMapping(value = "/greetings/{id}")
#ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteGreetingById(#PathVariable Integer id){
greetingsService.deleteGreetingById(id);
}
}
Interface:
public interface GreetingsService {
Greeting findGreetingById(Integer greetingId);
List<Greeting> findAllGreetings();
Greeting addGreeting( GreetingDto greetingDto);
void deleteGreetingById( Integer greetingId);
}
IterfaceImpl:
#Service
#Validated
public class GreetingsServiceImpl implements GreetingsService {
.
.
.
#Override
public void deleteGreetingById(#NotNull(message = "greetingId must not be null. ") Integer greetingId) {
...
}
}
I also added the Bean to my SpringBootApplication but still no exception is thrown.
#SpringBootApplication
public class GreetingsConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingsConsumerApplication.class, args
);
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
Below is the sample example to validate a model at service layer.
class TestModel{
#NotNull
private String name;
}
TestModel model= new TestModel();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<TestModel>> violations = validator.validate(model);
I "solved" the problem. My error was that I configured my Tests wrong. I configured the test with
#Extendwith(SpringExtension.class)
as I've only written unit tests without using the context in this class before. Obviously using the parameter validation this way you have to use the Context which makes the whole scenario an integration test. I'm glad it works now and I'm sorry for the needless discussions. I should have posted my test also in the code.
Although I am glad it works now I'm also a bit confused. In genereal I don't want to start the Spring context just for constraint validation. But this is another question.
When you have services implementing interfaces and you reference the interface you need the validation annotations on the interface, not the implementing class. Add the validation annotations to the GreetingsService interface.
I have a custom validator class that implements Validator, like this:
public class MyCustomValidator implements Validator
I want to be able to call its validate() method from a Service.
This is how this method looks:
#Override
public void validate(Object target, Errors errors) {
// validation goes here
MyClass request = (MyClass) target;
if (request.getId() == null) {
errors.reject("content.id", "Id is missing";
}
}
I don't want to have this validator in my endpoint, because I need to fetch the object to be validated from the database and then call the validation on it, so I need to do it from my service.
Can you please guide me on how to achieve this?
Use validation annotations in class but don't use #Valid on request body, then spring won't validate your class.
public class MyClass{
#NotNull
private Integer id;
#NotBlank
private String data;
}
Autowired Validator first
#Autowired
private final Validator validator;
Then for class validate using the validator conditionally when needed.
if(isValidate) {
Set<ConstraintViolation<MyClass>> violations = validator.validate(myClassObj);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations));
}
}
The Validator interface is, as far as i understand it, called as soon as a matching object (determined by the public boolean Validator.supports(Class clazz) method).
However, your goal seems to be to validate an object of MyClass only at a specific time, coming from your persistence layer to your service layer.
There are multiple ways to achieve this.
The first and most obvious one is to not extend any classes, but to use a custom component with some notion of a validation function:
#Component
public class CustomValidator{
public void validate(MyClass target) throws ValidationException {
// validation goes here
if (target.getId() == null) {
throw new ValidationException("Id is missing");
}
}
}
And inject/autowire it into your service object:
#Component
public class MyClassService{
// will be injected in first instance of this component
#Autowired
private CustomValidator validator
public MyClass get(MyClass target) {
try {
validator.validate(target);
return dao.retrieve(target);
} catch (ValidationException) {
// handle validation error
} catch (DataAccessException) {
// handle dao exception
}
}
}
This has the benefit that you yourself can control the validation, and error handling.
The negative side is the relatively high boilerplate.
However, if you want different Validators for different CRUD-Operations (or Service Methods), you may be interested in the Spring Validation Groups Feature.
First, you create a simple marker interface for each Operation you want to differ:
interface OnCreate {};
interface OnUpdate {};
Then, all you need to do is use the marker interfaces in the fields of your entity class,
using the Bean Validation Annotations:
public class MyClass{
#Null(groups = OnCreate.class)
#NotNull(groups = OnUpdate.class)
String id;
}
In order to use those groups in your Service Class, you will have to use the #Validated annotation.
#Validated
#Service
public class MyService {
#Validated(OnCreate.class)
void validateForCreate(#Valid InputWithGroups input){
// do something
}
#Validated(OnUpdate.class)
void validateForUpdate(#Valid InputWithGroups input){
// do something
}
}
Note that #Validated is applied to the service class as well as the methods. You can also set the group for the whole service, if you plan on using multiple services.
I for once mostly use the built-in Jakarta Bean Validation annotations in combination with marker interfaces, because of their ease of use and almost no boilerplate, while staying somewhat flexible and adjustable.
You could inject Validator and call validate
#Autowired
Validator validator;
And then call validate:
Set<ConstraintViolation<Driver>> violations = validator.validate(yourObjectToValidate);
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