Hystrix Javanica fallback not working in Spring Cloud 1.0 - spring-boot

I built a super simple Hystrix short circuit example based on #spencergibb feign-eureka spring cloud starter example. At first I thought I couldn't get the hystrix javanica default fallbackMethod to trigger due to feign.. now, removing feign, the hystrix default fallbackMethod still doesn't catch exceptions.
pom.xml
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>1.0.0.RELEASE</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
:
</dependencies>
Main file:
#SpringBootApplication
#EnableDiscoveryClient
#EnableHystrix
#RestController
public class HelloClientApplication {
#Autowired
HelloClientComponent helloClientComponent;
#RequestMapping("/")
public String hello() {
return helloClientComponent.makeMultipleCalls();
}
public static void main(String[] args) {
SpringApplication.run(HelloClientApplication.class, args);
}
}
HelloClientComponent.java (created because I know javanica expects to be inside a spring managed component or service):
#Component
public class HelloClientComponent {
#Autowired
RestTemplate restTemplate;
public String makeMultipleCalls() {
int cnt=20;
StringBuilder sb = new StringBuilder();
while (cnt-- > 0) {
String response = theServerRequestRoot();
sb.append(response).append(" ");
}
return sb.toString();
}
public String theServersRequestRootFallback() {
System.out.println("BOMB!!!!!!");
return "BOMB!!!!!!";
}
#HystrixCommand(fallbackMethod = "theServersRequestRootFallback", commandKey = "callToServers")
public String theServerRequestRoot() {
ResponseEntity<String> result = restTemplate.getForEntity("http://HelloServer", String.class);
System.out.println(result.getBody());
return result.getBody();
}
}
I start 2 servers, one which always succeeds and responds, and the other will fail 30% of the time with a 500 error. When I curl this client (to '/') things go normally for the non-forced failure calls. Round robining works fine as well. When the second server does return the 500 error, the fallbackMethod does not get called and the curl to '/' ends and returns with an error.
Update with solution per Spencer and Dave's suggestions. Change to the following:
Main Application file:
#SpringBootApplication
#EnableDiscoveryClient
#EnableHystrix
#RestController
public class HelloClientApplication {
#Autowired
HelloClientComponent helloClientComponent;
#RequestMapping("/")
public String hello() {
int cnt=20;
StringBuilder sb = new StringBuilder();
while (cnt-- > 0) {
String response = helloClientComponent.theServerRequestRoot(); // call directly to #Component in order for #HystrixCommand to intercept via AOP
sb.append(response).append(" ");
}
return sb.toString();
}
public static void main(String[] args) {
SpringApplication.run(HelloClientApplication.class, args);
}
}
HelloClientComponent.java:
#Component
public class HelloClientComponent {
#Autowired
RestTemplate restTemplate;
public String theServersRequestRootFallback() {
System.out.println("BOMB!!!!!!");
return "BOMB!!!!!!";
}
#HystrixCommand(fallbackMethod = "theServersRequestRootFallback", commandKey = "callToServers")
public String theServerRequestRoot() {
ResponseEntity<String> result = restTemplate.getForEntity("http://HelloServer", String.class);
System.out.println(result.getBody());
return result.getBody();
}
}

#HystrixCommand only works because Spring makes a proxy to call that method on. If you call the method from within the proxy it doesn't go through the interceptor. You need to call the #HystrixCommand from another #Component (or use AspectJ).

Related

How to make springboot register a RestController?

I havent worked too much with Spring API tools before, have more experience with Jersey. I have looked at many different articles explaining how to get REST endpoints wired up with Springboot and I just dont see why this is not working.
Here are some relevant code snippets:
In application.properties
server.port=5000
The package is com.myproj
#SpringBootApplication(scanBasePackages={"com.myproj"})
public class ClaimsIntakeServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ClaimsIntakeServiceApplication.class, args);
}
}
In com.myproj.controllers
#Controller
#RequestMapping(value = "/hello")
public class ClaimController {
#GetMapping
public ResponseEntity<Item> read() {
System.out.println("hi");
return new ResponseEntity<>("", HttpStatus.OK);
}
If i hit this URL in postman: https://localhost:5000/hello ..
It says
connection refused
How do I wire up a rest endpoint with springboot so I can hit it with postman as shown above?
Something like this should work:
Main class:
#SpringBootApplication
#ComponentScan(basePackages = {"com.myproj"})
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
Controller:
#RestController
public class HelloWorldController {
#RequestMapping(path = "/hello-world", method = RequestMethod.GET, produces =
MediaType.APPLICATION_JSON_VALUE)
public HelloWorldResponse getHelloWorld() {
return new HelloWorldResponse("hello!!");
}
}
Docs here
For some reason while i get all the stuff for API with this:
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-cosmosdb-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
I had to include this as well in order for the API to work:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot 5 multiple JUnit JPA test files wiring

I have two tests files in project.
One is testing directly my persistence layer :
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserTests {
#Autowired
private TestEntityManager entityManager;
#Autowired
private UserRepository repository;
#Test
public void whenFindByEmail_thenReturnUser() {
// given
User user = new User("email#email.com", "12345678", "Some Name");
entityManager.persist(user);
entityManager.flush();
// when
User found = repository.findByEmail(user.getEmail());
// then
assertThat(found.getName()).isEqualTo(user.getName());
assertThat(found.getEmail()).isEqualTo(user.getEmail());
assertThat(found.getPasswordHash()).isEqualTo(user.getPasswordHash());
}
}
The other one is testing a service using the persistence layer :
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserServiceTests {
#Autowired
private UserService service;
#Test
public void testSuccessfullUserCreation() {
UserCreationResult res = service.createUser("anything#anything.com", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testWrongEmailUserCreation() {
UserCreationResult res = service.createUser("anything#anything", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.INVALID_EMAIL);
}
#Test
public void testTooShortPasswordUserCreation() {
String shortPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength - 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", shortPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testTooLongPasswordUserCreation() {
String longPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength + 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", longPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testMaxLengthPasswordUserCreation() {
String maxPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", maxPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testMinLengthPasswordUserCreation() {
String minPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", minPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testReservedEmailUserCreation() {
String email = "email#email.com";
UserCreationResult res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.RESERVED_EMAIL);
}
}
First, my service autowiring wasn't working (UnsatisfiedDependencyException) so I had to add :
#ComponentScan("my.service.package") annotation to the test class.
This made the tests of UserServiceTests work when they were run independently (using eclipse to run only this class).
But when running all the tests of my app (in eclipse or with a good old mvn clean test), I had the same error on the same test class.
I tried adding the same component scan annotation to the other test class (UserTests) then everything worked.
I removed the component scan annotation from UserServiceTests and it still works.
I obviously deduced that the order the tests are executed matters.
Here are my 3 questions, the real one being the last one :
In the first place why do I have to put this component scan annotation even if my class is properly annotated #Service (so should be detected as a bean) ?
How is it that the order of tests matters ?
How can I have multiple JPA tests files that would run with a proper dependency injection independently ?
Here is my service class :
#Service
public class UserService {
#Autowired
private UserRepository repository;
public static final int minPasswordLength = 8;
public static final int maxPasswordLength = 50;
public static enum UserCreationResult {
OK, INVALID_EMAIL, RESERVED_EMAIL, WRONG_PASSWORD_LENGTH, UNKNOWN_ERROR
}
#Transactional
public UserCreationResult createUser(String email, String password, String name) {
if (password.length() < minPasswordLength || password.length() > maxPasswordLength) {
return UserCreationResult.WRONG_PASSWORD_LENGTH;
}
if (!EmailValidator.getInstance().isValid(email)) {
return UserCreationResult.INVALID_EMAIL;
}
final User existingUser = repository.findByEmail(email);
if (existingUser != null) {
return UserCreationResult.RESERVED_EMAIL;
}
final User user = repository.save(new User(email, password, name));
return user == null ? UserCreationResult.UNKNOWN_ERROR : UserCreationResult.OK;
}
}
And there is my pom.xml :
<?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>
<groupId>com.somedomain</groupId>
<artifactId>ws-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>My App</name>
<description>My app's description</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</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-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Thanks to this question and answer I was able to make my tests work with proper configuration.
In my UserServiceTests, the service was basically not autowired because that's the expected behavior of #DataJpaTest : it doesn't scan for regular beans.
So I used #SpringBootTest for this class and removed the component scanning in both test classes.
After that, some of my service tests were failing because with #SpringBootTest, the database is not reset after each test.
I added a simple repository cleaning after each service test and everything works fine.
This question still remains :
How is it that the order of tests matters ?
Here are my new test classes :
Service tests :
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTests {
#Autowired
private UserService service;
#Autowired
private UserRepository repository;
#After
public void cleanUsers() {
repository.deleteAll();
}
#Test
public void testSuccessfullUserCreation() {
UserCreationResult res = service.createUser("anything#anything.com", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testWrongEmailUserCreation() {
UserCreationResult res = service.createUser("anything#anything", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.INVALID_EMAIL);
}
#Test
public void testTooShortPasswordUserCreation() {
String shortPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength - 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", shortPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testTooLongPasswordUserCreation() {
String longPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength + 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", longPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testMaxLengthPasswordUserCreation() {
String maxPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", maxPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testMinLengthPasswordUserCreation() {
String minPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", minPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testReservedEmailUserCreation() {
String email = "email#email.com";
UserCreationResult res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.RESERVED_EMAIL);
}
}
JPA tests :
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserTests {
#Autowired
private TestEntityManager entityManager;
#Autowired
private UserRepository repository;
#Test
public void whenFindByEmail_thenReturnUser() {
// given
User user = new User("user#user.com", "12345678", "Some Name");
entityManager.persist(user);
entityManager.flush();
// when
User found = repository.findByEmail(user.getEmail());
// then
assertThat(found.getName()).isEqualTo(user.getName());
assertThat(found.getEmail()).isEqualTo(user.getEmail());
assertThat(found.getPasswordHash()).isEqualTo(user.getPasswordHash());
}
}

Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type:

I am trying to expose REST service, but while hitting it from POSTMAN i am getting below :
WARNING: Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.util.ArrayList
Where as i have also included below jar files which are required :
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.1</version>
</dependency>
Here is my REST controller Code :
#RestController
#RequestMapping("/MayankAPI")
public class TestRestAPI {
#RequestMapping(value="/sayHello" , method = RequestMethod.POST)
public TestPojo postData(#RequestBody String payload) {
System.out.println("Hello post"+payload);
TestPojo payload1=new TestPojo();
payload1.setStudentName("Jack");
return payload1;
}
#RequestMapping(value="/sayHello" , method = RequestMethod.GET)
public List<TestPojo> getData(String payload) {
System.out.println("Hello get"+payload);
List<TestPojo> payload1=new ArrayList<TestPojo>();
TestPojo tp = new TestPojo();
tp.setStudentName("Jack");
payload1.add(tp);
return payload1;
}
Here is my bean which i am trying to return :
public class TestPojo {
private String studentName;
private String studentId;
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getStudentId() {
return studentId;
}
public void setStudentId(String studentId) {
this.studentId = studentId;
}
}
Please help me where i am doing wrong.
I know it too late but still
Alternate Solution:
you need to enable Spring project as Web MVC as follow:
#Configuration
#EnableWebMvc
#ComponentScan("basePackages = com.test")
public class MiBenefitConfiguration
{
}
This is happening because MappingJackson2HttpMessageConverter is not registered in my App config file as below.
#Configuration
#ComponentScan("basePackages = com.test")
public class MiBenefitConfiguration extends WebMvcConfigurationSupport{
#Bean
public ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
#Bean
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(getObjectMapper());
return converter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(messageConverter());
addDefaultHttpMessageConverters(converters);
}
}

Mocked Spring #Service that has #Retryable annotations on methods fails with UnfinishedVerificationException

I'm using Spring Boot 1.4.0.RELEASE with spring-boot-starter-batch, spring-boot-starter-aop and spring-retry
I have a Spring Integration test that has a #Service which is mocked at runtime. I've noticed that if the #Service class contains any #Retryable annotations on its methods, then it appears to interfere with Mockito.verify(), I get a UnfinishedVerificationException. I presume this must be something to do with spring-aop? If I comment out all #Retryable annotations in the #Service then verify works ok again.
I have created a github project that demonstrates this issue.
It fails in sample.batch.MockBatchTestWithRetryVerificationFailures.batchTest() at validateMockitoUsage();
With something like:
12:05:36.554 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext#5ec0a365 testClass = MockBatchTestWithRetryVerificationFailures, testInstance = sample.batch.MockBatchTestWithRetryVerificationFailures#5abca1e0, testMethod = batchTest#MockBatchTestWithRetryVerificationFailures, testException = org.mockito.exceptions.misusing.UnfinishedVerificationException:
Missing method call for verify(mock) here:
-> at sample.batch.service.MyRetryService$$FastClassBySpringCGLIB$$7573ce2a.invoke(<generated>)
Example of correct verification:
verify(mock).doSomething()
However I have another class (sample.batch.MockBatchTestWithNoRetryWorking.batchTest()) with a mocked #Service that doesn't have any #Retryable annotation and verify works fine.
What am I doing wrong?
In my pom.xml I have the following:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
...
Then all the related Java Classes
#SpringBootApplication
#EnableBatchProcessing
#Configuration
#EnableRetry
public class SampleBatchApplication {
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
#Autowired
private MyRetryService myRetryService;
#Autowired
private MyServiceNoRetry myServiceNoRetry;
#Bean
protected Tasklet tasklet() {
return new Tasklet() {
#Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext context) {
myServiceNoRetry.process();
myRetryService.process();
return RepeatStatus.FINISHED;
}
};
}
#Bean
public Job job() throws Exception {
return this.jobs.get("job").start(step1()).build();
}
#Bean
protected Step step1() throws Exception {
return this.steps.get("step1").tasklet(tasklet()).build();
}
public static void main(String[] args) throws Exception {
// System.exit is common for Batch applications since the exit code can be used to
// drive a workflow
System.exit(SpringApplication
.exit(SpringApplication.run(SampleBatchApplication.class, args)));
}
#Bean
ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
#Bean
public JobRepository getJobRepo() throws Exception {
return new MapJobRepositoryFactoryBean(transactionManager()).getObject();
}
}
#Service
public class MyRetryService {
public static final Logger LOG = LoggerFactory.getLogger(MyRetryService.class);
#Retryable(maxAttempts = 5, include = RuntimeException.class, backoff = #Backoff(delay = 100, multiplier = 2))
public boolean process() {
double random = Math.random();
LOG.info("Running process, random value {}", random);
if (random > 0.2d) {
throw new RuntimeException("Random fail time!");
}
return true;
}
}
#Service
public class MyServiceNoRetry {
public static final Logger LOG = LoggerFactory.getLogger(MyServiceNoRetry.class);
public boolean process() {
LOG.info("Running process that doesn't do retry");
return true;
}
}
#ActiveProfiles("Test")
#ContextConfiguration(classes = {SampleBatchApplication.class, MockBatchTestWithNoRetryWorking.MockedRetryService.class}, loader = AnnotationConfigContextLoader.class)
#RunWith(SpringRunner.class)
public class MockBatchTestWithNoRetryWorking {
#Autowired
MyServiceNoRetry service;
#Test
public void batchTest() {
service.process();
verify(service).process();
validateMockitoUsage();
}
public static class MockedRetryService {
#Bean
#Primary
public MyServiceNoRetry myService() {
return mock(MyServiceNoRetry.class);
}
}
}
#ActiveProfiles("Test")
#ContextConfiguration(classes = { SampleBatchApplication.class,
MockBatchTestWithRetryVerificationFailures.MockedRetryService.class },
loader = AnnotationConfigContextLoader.class)
#RunWith(SpringRunner.class)
public class MockBatchTestWithRetryVerificationFailures {
#Autowired
MyRetryService service;
#Test
public void batchTest() {
service.process();
verify(service).process();
validateMockitoUsage();
}
public static class MockedRetryService {
#Bean
#Primary
public MyRetryService myRetryService() {
return mock(MyRetryService.class);
}
}
}
EDIT: Updated question and code based on a sample project I put together to show the problem.
So after looking at a similar github issue for spring-boot
I found that there is an extra proxy getting in the way. I found a nasty hack by unwrapping the aop class by hand, makes verification work, ie:
#Test
public void batchTest() throws Exception {
service.process();
if (service instanceof Advised) {
service = (MyRetryService) ((Advised) service).getTargetSource().getTarget();
}
verify(service).process();
validateMockitoUsage();
}
Hopefully, this can be fixed similar to the above github issue. I'll raise an issue and see how far I get.
EDIT: Raised the github issue
After I've seen #Joel Pearsons answer, and especially the linked GitHub issue, I worked around this by temporarily using a static helper method that unwraps and verifies:
public static <T> T unwrapAndVerify(T mock, VerificationMode mode) {
return ((T) Mockito.verify(AopTestUtils.getTargetObject(mock), mode));
}
With this method the only difference in the test cases is the verification call. There is no overhead other than this:
unwrapAndVerify(service, times(2)).process();
instead of
verify(service, times(2)).process();
Actually, it was even possible to name the helper method like the actual Mockito method, so that you only need to replace the import, but I didn't like the subsequent confusion.
However, unwrapping shouldn't be required if #MockBean is used instead of mock() to create the mocked bean. Spring Boot 1.4 supports this annotation.

Dependency Injection in JSR-303 Constraint Validator with Spring fails

I have the same problem as here and here but couldn't find a solution yet.
So my sample test project will show the whole relevant configuration and code:
Constraint annotation:
#Target({ ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = FooValidator.class)
public #interface FooValid {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Annotated PoJo:
public class Foo {
#FooValid(message = "Test failed")
private Integer test;
[...]
}
Annotated Service with #Validated:
#Service
#Validated
public class FooService {
private final Test test;
#Autowired
public FooService(final Test test) {
this.test = test;
}
public void foo(#Valid final Foo foo) {
this.test.test(foo);
}
}
JSR-303 ConstraintValidator:
public class FooValidator implements ConstraintValidator<FooValid, Integer> {
#Autowired
private ValidationService validationService;
#Override
public void initialize(final FooValid constraintAnnotation) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(final Integer value, final ConstraintValidatorContext context) {
// this.validationService is always NULL!
Assert.notNull(this.validationService, "the validationService must not be null");
return false;
}
}
Injected ValidationService:
#Service
public class ValidationService {
public void test(final Foo foo) {
System.out.println(foo);
}
}
Spring boot application and configuration:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(final String[] args) {
final ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
final FooService service = context.getBean(FooService.class);
service.foo(new Foo());
}
#Bean
public static LocalValidatorFactoryBean validatorFactory() {
return new LocalValidatorFactoryBean();
}
#Bean
public static MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
relevant maven pom:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.Application</start-class>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
I'm using the LocalValidatorFactoryBean with the default SpringConstraintValidatorFactory.
But why the dependency injection is not working in the ConstraintValidator and the ValidationService could not be autowired?
By the way if I don't use #Validated at the service, inject in opposite the spring or javax Validator interface and call manually "validator.validate" the dependency injection will work. But I don't want to call the validate method in every service manually.
Many thanks for help :)
I have fought the same problem in Spring Boot environment and I found out that Hibernate internal implementation got in instead of the configured Spring's one. When the application started, debugger caught a line with the Spring's factory but later in runtime there was Hibernate's one. After some debugging, I came to the conclusion that MethodValidationPostProcessor got the internal one. Therefore I configured it as follows:
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator);
return methodValidationPostProcessor;
}
Note the setter for validator - it did the job.
I had the same issue. The problem is arises because Hibernate is finding and applying the validators instead of Spring. So you need to set validation mode to NONE when configuring Hibernate:
#Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
localContainerEntityManagerFactoryBean(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean lcemfb =
new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(dataSource);
lcemfb.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
lcemfb.setValidationMode(ValidationMode.NONE);
// Continue configuration...
If you have confgured a LocalValidationFactoryBean Spring will pick up any validators annotated with #Component and autowire them.
This is what worked for me. I had to used the #Inject tag.
public class FooValidator implements ConstraintValidator<FooValid, Integer> {
private ValidationService validationService;
#Inject
public FooValidator(ValidationService validationService){
this.validationService = validationService;
}
#Override
public void initialize(final FooValid constraintAnnotation) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(final Integer value, final ConstraintValidatorContext context) {
// this.validationService is always NULL!
Assert.notNull(this.validationService, "the validationService must not be null");
return false;
}
}

Resources