Why does springboot refresh the bean only once when I remove bean and add another bean? - spring-boot

I have a requirement to remove the corresponding Bean and then add another Bean, but I find that springboot only refreshes the injected Bean once
the springboot version is 2.1.5.RELEASE,JDK1.8
public class Users {
String name;
int age;
String gender;
public Users() {
}
public Users(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
#Component
public class UsersController implements ApplicationRunner {
#Autowired(required = false)
// #Resource
List<Users> users= Collections.emptyList();
#Override
public void run(ApplicationArguments args) {
System.out.println(users);
}
}
#RestController
public class HelloController implements
BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
ApplicationContext applicationContext;
DefaultListableBeanFactory defaultListableBeanFactory;
Users users;
int i=0;
#RequestMapping("/hello1")
public String index1() {
return "hello";
}
#RequestMapping("/hello3")
public Users index3() {
UsersController controller =
applicationContext.getBean(UsersController.class);
controller.run(null);
return null;
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry
registry) throws BeansException {
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException {
defaultListableBeanFactory= (DefaultListableBeanFactory)
beanFactory;
Users users=new Users("hello"+(i++),23,"fe");
this.users=users;
defaultListableBeanFactory.registerBeanDefinition(users.getName(),
BeanDefinitionBuilder.genericBeanDefinition(Users.class, () ->
users).getRawBeanDefinition());
}
#EventListener
public void onApplicationEvent(#NonNull EnvironmentChangeEvent
environmentChangeEvent) {
System.out.println("before
remove:"+applicationContext.getBean(Users.class));
defaultListableBeanFactory.removeBeanDefinition(users.getName());
Users users=new Users("hello"+(i++),23,"fe");
this.users=users;
defaultListableBeanFactory.registerBeanDefinition(users.getName(),
BeanDefinitionBuilder.genericBeanDefinition(Users.class,
() -> users).getRawBeanDefinition());
System.out.println("after:"+applicationContext.getBean(Users.class));
}
#Override
public void setApplicationContext(ApplicationContext
applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
only bootstrap.yml:
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
when start the application it will output [Users(name=hello0, age=23, gender=fe)],
and then visit the url by POST,
http://localhost:8080/actuator/refresh
it will output
before remove:Users(name=hello0, age=23, gender=fe)
after:Users(name=hello1, age=23, gender=fe)
then visit the url
http://localhost:8080/hello3
output
[Users(name=hello1, age=23, gender=fe)]
This is to be expected,if visit the url again
http://localhost:8080/actuator/refresh
output
before remove:Users(name=hello1, age=23, gender=fe)
after:Users(name=hello2, age=23, gender=fe)
This is also to be expected,but visit the url
http://localhost:8080/hello3
we hope it output
Users(name=hello2, age=23, gender=fe)
but it output
Users(name=hello1, age=23, gender=fe)
The output message is not what we expected.
If change #Autowired to #Resource in class UsersController, it should be normal.But it maybe null in real environment,the application will run fail if use #Resource
How can I solve this problem?
Thanks for your time.

#Resource
#Lazy
can star application properly.But it may cause error like
ERROR [main] com.Users - No qualifying bean of type 'java.util.List<com.Users>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=), #org.springframework.context.annotation.Lazy(value=true)}

Please take a look at this part of the documentation in order to refresh your beans at runtime: https://cloud.spring.io/spring-cloud-static/Greenwich.SR1/single/spring-cloud.html#refresh-scope

Related

The AOuth2 Authorization server does not insert token in the given database

There is some configuration class in Authorization server:
#Configuration
public class AppConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;
#Value("${spring.database.driverClassName}")
private String dbDriverClassName;
#Value("${spring.datasource.username}")
private String dbUsername;
#Value("${spring.datasource.password}")
private String dbPassword;
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(dbDriverClassName);
dataSource.setUrl(datasourceUrl);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
return dataSource;
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource());
}
}
and the another class:
#EnableWebSecurity
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AccountUserDetailsService accountUserDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new HashingClass();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return accountUserDetailsService;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/webjars/**", "/resources/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean())
.passwordEncoder(passwordEncoder());
}
}
and the third class is:
#EnableAuthorizationServer
#Configuration
public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final AppConfig appConfig;
#Autowired
public AuthServerOAuth2Config(AuthenticationManager authenticationManager, AppConfig appConfig) {
this.authenticationManager = authenticationManager;
this.appConfig = appConfig;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(appConfig.dataSource());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/*
* Allow our tokens to be delivered from our token access point as well as for tokens
* to be validated from this point
*/
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(appConfig.tokenStore()); // Persist the tokens in the database
}
}
and the client application is like this:
#SpringBootApplication
#EnableOAuth2Sso
#RestController
#Configuration
public class SocialApplication extends WebSecurityConfigurerAdapter {
#Value("${security.oauth2.client.clientId}")
private String clientId;
#Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
#Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
#RequestMapping("/user")
public Principal user(HttpServletResponse response, Principal principal) {
return principal;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**", "/index.html", "/getEmployees.jsp").permitAll()
.anyRequest().authenticated()
.and().logout().logoutSuccessUrl("/").permitAll()
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
public OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
return details;
}
public static void main(String[] args) {
SpringApplication.run(SocialApplication.class, args);
}
}
and its application.yml file is:
server:
port: 8090
security:
basic:
enabled: false
oauth2:
client:
clientId: web
clientSecret: secret
accessTokenUri: http://localhost:8081/auth/oauth/token
userAuthorizationUri: http://localhost:8081/auth/oauth/authorize
authenticationScheme: query
clientAuthenticationScheme: form
resource:
userInfoUri: http://localhost:8081/auth/user
logging:
level:
org.springframework.security: DEBUG
When I request http://localhost:8090/user (this rest is owned client) I redirected to Authorization server and I can login successfully after login again I am redirected to the client with a code, but client is not able to exchange access token with the given code. and the following exception is raised on the browser:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing
this as a fallback. Mon Dec 17 09:47:27 IRST 2018 There was an
unexpected error (type=Unauthorized, status=401). Authentication
Failed: Could not obtain access token
And I query from DB:
select * from oauth_access_token
or
select * from oauth_client_token
The two above tables are empty.
Where is wrong? I am really confused.
EDIT
The application.properties file of AOuthrization server:
server.port=8081
server.context-path=/auth
security.basic.enabled=false
# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
# Connection url for the database w/createDatabaseIfNotExist=true
spring.datasource.url = jdbc:oracle:thin:#192.168.192.129:1521:hamed
spring.database.driverClassName = oracle.jdbc.OracleDriver
#spring.jpa.database = MySQL
#spring.datasource.platform = mysql
# Database - data initialization
spring.jpa.generate-ddl = true
# Username and password
spring.datasource.username = test
spring.datasource.password = test
# ===============================
# = JPA / HIBERNATE
# ===============================
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager).
# Show or not log for each sql query
spring.jpa.show-sql = true
# Transactions
spring.jpa.open-in-view = false
# Hibernate ddl auto (create, create-drop, update): with "update" the database
# schema will be automatically updated accordingly to java entities found in
# the project
spring.jpa.hibernate.ddl-auto = none
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle12cDialect
and the its pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</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-web</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId>
<version>12.1.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>r05</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>11.0</version>
</dependency>
</dependencies>
and the version of spring boot is:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/>
</parent>
I noticed you are using spring-boot and to me seems like you can clean up your code a little, for instance, all this DataSource config may not be necessary.
As for your problem, it could be related to the class AuthServerOAuth2Config where you are injecting AppConfig instead of DataSource and TokenStore, making this change can solve your problem.
Here you can find a sample auth service I did a while ago https://github.com/marcosbarbero/spring-security-authserver-sample
I could solve my problem by removing two lines of application.yml of client application.
These two lines are:
authenticationScheme: query
clientAuthenticationScheme: form
Why these two lines cause problem, I do not know.

How to create filter in Spring RESTful for Prevent XSS?

i use from Spring 4.2.6.RELEASE and backend is rest services.
and now I can not have a filter for Prevent XSS
my filter is:
#Component
#Order(1)
public class XSSFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
}
}
and XSSRequestWrapper is :
public class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
#Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = stripXSS(values[i]);
}
return encodedValues;
}
#Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return stripXSS(value);
}
#Override
public String getHeader(String name) {
String value = super.getHeader(name);
return stripXSS(value);
}
private String stripXSS(String value) {
return StringEscapeUtils.escapeHtml(value);
}
}
and in WebConfig extends WebMvcConfigurerAdapter Class:
// -----------------------------------------------------
// Prevent XSS
// -----------------------------------------------------
#Bean
public FilterRegistrationBean xssPreventFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new XSSFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
My Rest Class is:
#RestController
#RequestMapping("/personService")
public class PersonController extends BaseController<PersonDto, PersonCriteria> {
#RequestMapping( value= "/test" )
private void getTest2(#RequestParam String name) {
System.out.println(name);
System.out.println( StringEscapeUtils.escapeHtml(name) );
}
}
But it does not work, without any Error or Exception.
How can I do this and create my own filter? I use only Java Config and no XML.
in my Controller I was forced again use StringEscapeUtils.escapeHtml(name) and this is bad.
I’ve created a fully executable sample project based on your codes.
Everything goes fine, you can download full source from my github https://github.com/mehditahmasebi/spring/tree/master/spring-xss-filter and for running command “mvnw spring-boot:run” and in browser type: http://localhost:8080/personService/test?name=foobar, so you can see result in XSSRequestWrapper.stripXSS.
I hope this source code will help you.
Some explanation :
Project structure :
POM.xml
WebConfig.java for Spring web config
SpringBootApplication.java for starting up application
your classes (PersonController.java, XSSFilter.java and XSSRequestWrapper.java)
dive into them, but I was only copy important lines :
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
WebConfig.java (at the bottom lines you can see your bean) :
#Configuration
#EnableWebMvc
#EnableWebSecurity
public class WebConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedMethods("GET, POST, PATCH, PUT, DELETE, OPTIONS")
.allowedOrigins("*");
}
#Bean
public FilterRegistrationBean xssPreventFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new XSSFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
SpringBootApplication.java (for starting up project) :
#SpringBootApplication
public class SpringbootApplication extends SpringBootServletInitializer {
/**
* tomcat deployment needed
*/
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
System.out.println("Spring boot application started!");
}
}
The other java source files is exactly as you are , but with 2 changes :
first, I've added a sysout line to see trace of your code without debugging:
private String stripXSS(String value) {
if(value != null)
System.out.println("escapeHTML work successfully and escapeHTML value is : " + StringEscapeUtils.escapeHtml(value));
return StringEscapeUtils.escapeHtml(value);
}
and the second change is, I commented escapeHtml from PersonController as you said is not a good idea:
#RequestMapping( value= "/test" )
private void getTest2(#RequestParam String name) {
System.out.println(name);
// System.out.println( StringEscapeUtils.escapeHtml(name) );
}
You can find all the source at my github https://github.com/mehditahmasebi/spring/tree/master/spring-xss-filter

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