Javax validation constraints not recognized by hibernate [duplicate] - java

I am trying to add #NotNull constraint into my Person object but I still can #POST a new Person with a null email. I am using Spring boot rest with MongoDB.
Entity class:
import javax.validation.constraints.NotNull;
public class Person {
#Id
private String id;
private String username;
private String password;
#NotNull // <-- Not working
private String email;
// getters & setters
}
Repository class:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends MongoRepository<Person, String> {
}
Application class:
#SpringBootApplication
public class TalentPoolApplication {
public static void main(String[] args) {
SpringApplication.run(TalentPoolApplication.class, args);
}
}
pom.xml
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
...
When I #POST a new object via Postman like:
{
"username": "deadpool",
"email": null
}
I still get STATUS 201 created with this payload:
{
"username": "deadpool",
"password": null,
"email": null
....
....
}

I had the same problem, but just enabling validation didn't work for me, this did work with both JPA and MongoDb to save anyone else spending ages on this. Not only does this get validation working but I get a nice restful 400 error rather than the default 500.
Had to add this to my build.gradle dependencies
compile('org.hibernate:hibernate-validator:4.2.0.Final')
and this config class
#Configuration
public class CustomRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("afterCreate", validator());
validatingListener.addValidator("beforeCreate", validator());
validatingListener.addValidator("afterSave", validator());
validatingListener.addValidator("beforeSave", validator());
}
}

i found it better to make my own version of #NotNull annotation which validates empty string as well.
#Documented
#Constraint(validatedBy = NotEmptyValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotEmpty {
String message() default "{validator.notEmpty}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class NotEmptyValidator implements ConstraintValidator<NotEmpty, Object> {
#Override
public void initialize(NotEmpty notEmpty) { }
#Override
public boolean isValid(Object obj, ConstraintValidatorContext cxt) {
return obj != null && !obj.toString().trim().equals("");
}
}

You can either use the following code for validating
#Configuration
#Import(value = MongoAutoConfiguration.class)
public class DatabaseConfiguration extends AbstractMongoConfiguration
{
#Resource
private Mongo mongo;
#Resource
private MongoProperties mongoProperties;
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Override
protected String getDatabaseName() {
return mongoProperties.getDatabase();
}
#Override
public Mongo mongo() throws Exception {
return mongo;
}
}

Normally, the #RestRepository will resolve into a controller than handles validation by itself, except if you Override the default behavior or it by including some #HandleBeforeSave, #HandleBeforeCreate, ... into your code.
A solution is to remove the #HandleBeforeSave, #HandleBeforeCreate, ...
and then spring will handle the validation again.
Or if you want to keep them, you can provide a handler for any object validation like this:
#Component
#RepositoryEventHandler
public class EntityRepositoryEventHandler {
#Autowired
private Validator validator;
#HandleBeforeSave
#HandleBeforeCreate
public void validate(Object o) {
Set<ConstraintViolation<Object>> violations = this.validator.validate(o);
if (!violations.isEmpty()) {
ConstraintViolation<Object> violation = violations.iterator().next();
// do whatever your want here as you got a constraint violation !
throw new RuntimeException();
}
}
}

Related

Spring Boot: repository does not autowire in the custom validator

I have a custom validator that validates data against DB using repository:
#Constraint(validatedBy = DataValidator.class)
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomValidator {
String message() default "some message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
#Component
public class DataValidator implements ConstraintValidator<CustomValidator, String> {
#Autowired
private DataRepository repository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
var data = repository.findDataByValue(value);
//validation logic with result in 'isValid' variable
return isValid;
}
}
I have entity with a field that is annotated with DataValidator:
#Entity
#Table(name = "custom_data")
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#DataValidator
#NotBlank(message = "Value is mandatory")
#Column
private String value;
Spring Boot dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
When I call repository.save(data) from the rest controller, my validator is called, but its repository field is null.
What configuration did I miss that DataRepository bean was injected to RestController correctly, but wasn't injected into DataValidator?
Try it this way.
#Configuration
public class DataValidator implements ConstraintValidator<CustomValidator, String> {
private static final DataValidator holder = new DataValidator();
#Bean
public static DataValidator bean(DataRepository repository) {
holder.repository = repository;
return holder;
}
private DataRepository repository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
var data = holder.repository.findDataByValue(value);
//validation logic with result in 'isValid' variable
return isValid;
}
}
I found a solution that will autowire the bean, but you need to call a validator manually.
First of all, add the following to application.properties to disable automatic validation trigger on data persistence:
spring.jpa.properties.javax.persistence.validation.mode=none
Create #Configuration class and describe Validator bean, configure validator factory for it:
#Bean
public Validator validator(AutowireCapableBeanFactory beanFactory) {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()
.constraintValidatorFactory(new SpringConstraintValidatorFactory(beanFactory))
.buildValidatorFactory();
return validatorFactory.getValidator();
}
Add validator to the class where you want to use it and and call its validate method directly:
#RestController
public class DataController {
#Autowired
private DataRepository repository;
#Autowired
private Validator validator;
#PostMapping("/doSomething")
public Data doSomething(#RequestBody Data data) {
var validationResult = validator.validate(data);
//validation result processing
return repository.save(data);
}
}
or if you use validator within the REST endpoint as in this example, usage of the #Valid annotation is more correct, to my mind. Then you don't need to declare Validator bean:
#RestController
public class DataController {
#Autowired
private DataRepository repository;
#PostMapping("/doSomething")
public Data doSomething(#Valid #RequestBody Data data) {
return repository.save(data);
}
}

Why am I not able to get my Bean definition available in my Spring context with Spring Boot?

I'm feeling stupid to ask this, but I can't understand where I'm wrong with my code.
The context is :
a Spring Boot application (1.5.7) with an embedded Jetty server and a
controller to expose some endpoints
a unique #Configuration class, where some of my beans are defined (Singleton and Prototype scopes)
a #Service that uses some beans defined in my #Configuration class
The problem is:
a NoSuchBeanDefinitionException for one of my #Configuration bean.
Now the details:
My SpringBootApplication :
#SpringBootApplication
public class HbbTVApplication {
public static void main(String[] args) {
SpringApplication.run(HbbTVApplication.class, args);
}
}
My #Configuration class:
#Configuration
#Profile(value = { "dev", "int", "pre", "pro" })
public class StandaloneFrontalConfig extends WebMvcConfigurerAdapter {
#Value("${kafka.bootstrap-servers}")
private String bootstrapServers;
#Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return props;
}
#Bean
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
#Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/standalone/");
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedHeaders("*");
}
};
}
#Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
#Bean
public Security securityManager() {
return new Security();
}
#Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KngAflow getTechnicalCookie() {
return new KngAflow();
}
#Bean
public EmbeddedServletContainerCustomizer customizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof JettyEmbeddedServletContainerFactory) {
customizeJetty((JettyEmbeddedServletContainerFactory) container);
}
}
private void customizeJetty(JettyEmbeddedServletContainerFactory jetty) {
jetty.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(Server server) {
for (Connector connector : server.getConnectors()) {
if (connector instanceof ServerConnector) {
HttpConnectionFactory connectionFactory = ((ServerConnector) connector)
.getConnectionFactory(HttpConnectionFactory.class);
connectionFactory.getHttpConfiguration().setCookieCompliance(CookieCompliance.RFC2965);
}
}
}
});
}
};
}
}
My #Service:
#Service
public class CookieService implements services.CookieService, InitializingBean {
/**
* Serializable
*/
private static final long serialVersionUID = -1997257884335775587L;
#Autowired
ApplicationContext app;
#Override
public Cookie createTechnicalCookie() {
return new Cookie(app.getBean(KngAflow.class), null);
}
#Override
public void afterPropertiesSet() throws Exception {
if (app != null) {
for (String bean : app.getBeanDefinitionNames()) {
System.out.println("Bean: " + bean);
}
}
}
}
And the "non defined" bean:
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties({ "security", "maxAge", "domain", "updated" })
public class KngAflow implements Serializable, InitializingBean {
#JsonProperty(value = "did")
private String did;
#JsonProperty(value = "checksum")
private String checksum;
#Autowired
private Security security;
private Integer maxAge;
private String domain;
private boolean updated = false;
public KngAflow() {
domain = ".mydomain.com";
}
#Override
public void afterPropertiesSet() throws Exception {
did = UUID.randomUUID().toString();
maxAge = 365 * 24 * 60 * 60;
checksum = security.encrypt(did + security.md5(did));
}
}
NB: Classes are not complete, and there are more classes in my project. I only put what I saw as relevant information.
If something else is needed, just ask me please.
By the way, all the endpoints are defined into a unique #Controller class, and all the endpoints are working except those needing the getTechCookie #Bean.
So, my problem occurs in runtime execution. When I start my Spring Boot app, Jetty is started and listening on the configured port.
Nevertheless, if you look at the CookieService #Service, I'm listing all the bean names defined in the autowired context and my getTechnicalCookie (a.k.a KngAflow) #Bean is missing. I can't understand why.
Of course, when I invoke my #controller to execute my #Service code, the NoSuchBeanDefinitionException is thrown executing the line app.getBean(KngAflow.class).
I tried to use a bean name instead of bean type, no change.
For testing purpose (as it doesn't make sense from a logical point of view), I defined my bean getTechCookie #Bean as a Singleton scoped bean, and the name is still missing from the ApplicationContext.
And the last but not least thing is: Everything works fine with Eclipse!
I mean, all my devs are done using Eclipse IDE. My Spring Boot app is built with Maven and executing it inside Eclipse works correctly (and my getTechCookie Bean is defined and listed).
When I package my app using the Maven Spring Boot plugin and execute it using java -jar, my getTechCookie (KngAflow.class) bean is missing. Nevertheless, this class is present inside the jar.
Spring parameters to launch the spring boot app are spring default values (port 8080, no SSL, ...) and the active.profiles are always between dev, int, pre or pro (those defined in my #Configuration class)
What am I doing wrong?
Thanks!
If it helps, I add my POM definition:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>com.mydomain.bigdata</groupId>
<artifactId>mybigapp</artifactId>
<version>1.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
<include>application.yml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
EDIT: I changed my #Service class to "force" spring to accept my class as a prototype bean, and it works. It's very ugly but it works. But if someone could help me to find what's wrong, I don't like this workaround:
#Override
public void afterPropertiesSet() throws Exception {
if (!context.containsBeanDefinition(KngAflow.class.getName()))
context.registerBeanDefinition(KngAflow.class.getName(),
BeanDefinitionBuilder.genericBeanDefinition(KngAflow.class).setScope("prototype").getBeanDefinition());
}
I made a following simple application to reproduce issue.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
run(Application.class, args);
}
}
#Configuration
#Profile("dev")
public class BeanConfiguration {
#Bean
#Scope(scopeName = SCOPE_PROTOTYPE)
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
}
public class PrototypeBean {}
#Service
#Slf4j
public class SingletonBean implements InitializingBean {
#Autowired
private ApplicationContext context;
public PrototypeBean getPrototypeBean() {
return context.getBean(PrototypeBean.class);
}
#Override
public void afterPropertiesSet() throws Exception {
for (String name : context.getBeanDefinitionNames()) {
Class<?> c = context.getBean(name).getClass();
log.debug("===> Name: {}, Type = {}", name, c.getTypeName());
}
}
}
#RestController
#RequestMapping("/bean")
public class BeanRestController {
#Autowired
private SingletonBean singletonBean;
#GetMapping("/name")
public String getName() {
return singletonBean.getPrototypeBean().getClass().getName();
}
}
When I execute application with -Dspring.profiles.active=dev setting
Then I see in the log without no issue and REST endpoint gives back response properly:
===> Name: prototypeBean, Type = PrototypeBean
But if I execute application without profile setting
Then I see error in the log and REST endpoint raise exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'PrototypeBean' available

How to create the bean of FindByIndexNameSessionRepository

I am trying to create the Bean of FindByIndexNameSessionRepository. I need to get all user sessions using it but I am getting the bean error even I already defined it. I am using the Spring Boot Starter 1.5.7
Error: Field sessionRepository required a bean of type 'org.springframework.session.FindByIndexNameSessionRepository' that could not be found.
Consider defining a bean of type
'org.springframework.session.FindByIndexNameSessionRepository' in your
configuration.
I am trying to create bean and using it in my configuration, something like that:
import com.x.security.SpringSessionBackedSessionRegistry;
#Bean
SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry<ExpiringSession>(
this.sessionRepository);
}
#Autowired
private FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;
My configuration is below
http<...>
.maximumSessions(2)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(false)
.<other settings>
My SpringSessionBackedSessionRegistry class is as follow:
public class SpringSessionBackedSessionRegistry<S extends ExpiringSession>
implements SessionRegistry {
private final FindByIndexNameSessionRepository<S> sessionRepository;
public SpringSessionBackedSessionRegistry(
FindByIndexNameSessionRepository<S> sessionRepository) {
Assert.notNull(sessionRepository, "sessionRepository cannot be null");
this.sessionRepository = sessionRepository;
}
#Override
public List<Object> getAllPrincipals() {
throw new UnsupportedOperationException("SpringSessionBackedSessionRegistry does "
+ "not support retrieving all principals, since Spring Session provides "
+ "no way to obtain that information");
}
#Override
public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
Collection<S> sessions = this.sessionRepository.findByIndexNameAndIndexValue(
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
name(principal)).values();
List<SessionInformation> infos = new ArrayList<>();
for (S session : sessions) {
if (includeExpiredSessions || !Boolean.TRUE.equals(session
.getAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR))) {
infos.add(new SpringSessionBackedSessionInformation<S>(session,
this.sessionRepository));
}
}
return infos;
}
#Override
public SessionInformation getSessionInformation(String sessionId) {
S session = this.sessionRepository.getSession(sessionId);
if (session != null) {
return new SpringSessionBackedSessionInformation<S>(session,
this.sessionRepository);
}
return null;
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
#Override
public void refreshLastRequest(String sessionId) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
#Override
public void registerNewSession(String sessionId, Object principal) {
}
/*
* This is a no-op, as we don't administer sessions ourselves.
*/
#Override
public void removeSessionInformation(String sessionId) {
}
/**
* Derives a String name for the given principal.
*
* #param principal as provided by Spring Security
* #return name of the principal, or its {#code toString()} representation if no name
* could be derived
*/
protected String name(Object principal) {
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
}
if (principal instanceof Principal) {
return ((Principal) principal).getName();
}
return principal.toString();
}
}
My pom snippet is as follows
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
</dependencies>
Any help is much appreciated.
Assuming you've got Spring Session configured properly (with Spring Boot 1.5.x that would be by setting spring.session.store-type configuration property to redis, or explicitly by using #EnableRedisHttpSession), you should be able to use FindByIndexNameSessionRepository<? extends ExpiringSession>. For example:
#Autowired
FindByIndexNameSessionRepository<? extends ExpiringSession> sessionRepository;

Feign Hystrix fallback not working

I have the below FeignClient:
#FeignClient(name="FooMS",fallback=CustomerFeign.CustomerFeignImpl.class)
public interface CustomerFeign {
#RequestMapping(value="/bar/{phoneNo}")
List<Long> getFriends(#PathVariable("phoneNo") Long phoneNo);
class CustomerFeignImpl implements CustomerFeign{
#Override
public List<Long> getFriends(Long phoneNo) {
return new ArrayList<Long>(108);
}
}
}
When the FooMS instance is down, I get a 500 error instead of the fallback being executed. Why is this happening?
adding #Component and feign.hystrix.enabled=true works fine
Tag your CustomerFeignImpl as a #Component or create a #Bean out of it.
This works for me with 2020.0.3:
In application.properties
feign.circuitbreaker.enabled=true
In pom.xml
<spring-cloud.version>2020.0.3</spring-cloud.version>
and
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
Thank you, rostlvan!
I am outlining my implementation below:
I am using Spring Cloud version 2020.0.4 and the following configuration worked for me:
in pom.xml, I have these dependencies:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
Though I'm not sure if we need to have both openfeign and hystrix dependencies. Someone can validate that!
In my application.properties I have feign.circuitbreaker.enabled=true
In my Main Application class, I have
#SpringBootApplication
#EnableFeignClients
public class MySpringBootApplication{
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
And finally, my Feign Client, fallback and fallback factory:
UserServiceFeignClient.java
#FeignClient(name = "USER-SERVICE", fallbackFactory = UserServiceFallbackFactory.class)
public interface UserServiceFeignClient {
#GetMapping("/api/users/{userId}")
public ResponseEntity<User> getUser(#PathVariable String userId);
}
UserServiceFeignClientFallback.java
public class UserServiceFeignClientFallback implements UserServiceFeignClient{
#Override
public ResponseEntity<User> getUser(String userId) {
return ResponseEntity.ok().body(new User());
}
}
And, UserServiceFeignClientFallbackFactory.java:
#Component
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceFeignClientFallback>{
#Override
public UserServiceFeignClientFallback create(Throwable cause) {
return new UserServiceFeignClientFallback();
}
}
Was facing the problem myself, until I stumbled upon the answer from #rostlvan

How to handle custom objects for #AfterReturning Aspect for Spring Boot Application

So, I have the next class:
public class MyCustomClass {
private String someField;
public String getSomeField() {
return someField;
}
public void setSomeField(String someField) {
this.someField = someField;
}
}
I would like to handle methods that return an instance of this class:
#Aspect
public class CustomAspect {
#AfterReturning(
pointcut = "execution(* com.*.*(..))",
returning = "val")
public void handleCustom(JoinPoint joinPoint, MyCustomClass val) {
System.out.println(val.getSomeField());
}
}
But it's working only if the type of val is Object:
public void handleCustom(JoinPoint joinPoint, Object val) {//...}
I tried to make something like this:
if (val instanceof MyCustomClass) {
System.out.println(((MyCustomClass) val).getSomeField());
}
But it doesn't work, in debug mode I saw that the type is ArrayList and it's empty.
I know, I'm a beginner in AOP, but Could you please advise some workarounds that could help me?
updated:
Maven dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>1.4.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
AspectConfig (imported to the Application class):
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy
#ComponentScan(basePackages="com.aspect")
public class AspectConfig {
#Bean
public CustomAspect customAspect () {
return new CustomAspect ();
}
}
I tried to use CustomAspect as a #Component as well.
The custom object is just a DTO that is taken from maven artifact and is obtained via rest request.
So, I DO NOT have problems if the type of val is Object, everything works fine. But I do want to have MyCustomClass there instead of Object. Is it possible?
update 2:
#RestController
#RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController{
#Autowired
private MyService myService;
#RequestMapping(value = "dosmth")
public ResponseEntity<?> doSmth() {
SomeObject res = myService.doSmthAndGet();
return new ResponseEntity<>(res, HttpStatus.OK);
}
}
#Service
public class MyService{
public SomeObject doSmthAndGet() {
return SomeObject.of(getMyCustomClass());
}
private MyCustomClass getMyCustomClass() {
MyCustomClass result = new MyCustomClass();
result.setSomeField("Hello, Stack!");
return result;
}
}
It seems that #AfterReturning doesn't work for my submethod:
private MyCustomClass getMyCustomClass()
It's only triggered by the method that was called from Controller:
public SomeObject doSmthAndGet()
I tried to use different executions for #AfterReturning...
What am I doing wrong?

Categories

Resources