I'm new to Spring. I'm trying to use ComponentScan.I have a simple bean with a string variable annotated with #Component. Trying to use the #Configuration with a java class instead of xml file. When I try to access the bean from my main class, it says 'No bean found'
project directory structure
StudentTest.java
package com.spring.Annotations.tests;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
#Component
public class StudentTest {
#Value("${name}")
private String name;
public String getName() {
return name;
}
public void setName(
String name) {
this.name = name;
}
public StudentTest()
{
System.out.println("obj created");
}
}
Config.java
package com.spring.Annotations.tests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#PropertySource("classpath=com.spring.Annoations.tests.project.properties")
#ComponentScan(basePackages={"com.spring.Annotations","com.spring.Annotations.tests"})
public class Config {
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
return configurer;
}
}
App.java
package com.spring.Annotations;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.spring.Annotations.tests.StudentTest;
public class App
{
public static void main( String[] args )
{
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext("com.spring.Annotations.tests.Config.class");
StudentTest s=(StudentTest)ctx.getBean("studentTest");
System.out.println( s.getName() );
}
}
pom.xml
<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.spring</groupId>
<artifactId>Annotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Annotations</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.0.6.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Spring 3 dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
project.properties
name=Madhu
When I run App.java class it gives me following error.
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'studentTest' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:641)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1157)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:280)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:973)
at com.spring.Annotations.App.main(App.java:17)
use context:component-scan and context:annotation-configinto ApplicationContaxt.xml file. you can find the sample code :
<context:component-scan annotation-config="true" base-package="com.demo.test" />
<context:annotation-config />
component-scan is use for scanning all packages to scan.
Why do yo add "class" to the path? Try
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.spring");
You can only use this type of configuration if your spring version is greater than Spring 3.1.
Can you please check and comment your spring version.
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(com.spring.Annotations.tests.Config.class);
Remove double quotes inside AnnotationConfigApplicationContext and try
Related
I'm trying to test that my beans have correct validation annotations. I'm using spring-boot. Here is an example test case:
package com.example.sandbox;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.validation.annotation.Validated;
#SpringBootTest
class ValidationTest {
#Test
void testConstructor() {
TestedBean bean = new TestedBean(null);
assertThatThrownBy(() -> checkIfValidated(bean)).isInstanceOf(ConstraintViolationException.class);
}
#Test
void testSetter() {
TestedBean bean = new TestedBean(null);
assertThatThrownBy(() -> bean.setSomeProperty(null)).isInstanceOf(ConstraintViolationException.class);
}
private void checkIfValidated(#Valid TestedBean bean) {
}
#Validated
class TestedBean {
#NotNull
private String someProperty;
public TestedBean(String someProperty) {
super();
this.someProperty = someProperty;
}
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(#NotNull String someProperty) {
this.someProperty = someProperty;
}
}
}
I expect the call to checkIfvalidated() and to setSomeProperty(null) to raise a ConstraintViolationException, and the tests to pass, but they both fail with:
java.lang.AssertionError:
Expecting code to raise a throwable.
at com.example.sandbox.ValidationTest.test(ValidationTest.java:20)
...
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>com.example.springbootsandbox</artifactId>
<version>0.0</version>
<name>SpringBootSandbox</name>
<description>Sandbox for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Why is there no ConstraintViolationException raised here? The bean property has a #NotNull annotation, the bean itself is #Validated and the method signature requires a #Valid bean.
Is there a simple way to have that exception raised in the context of my test class?
When I use validation annotations on method signatures for a service interface, everything works as expected. I don't understand where is the difference.
Service interface:
package com.example.sandbox;
import javax.validation.constraints.NotNull;
import org.springframework.validation.annotation.Validated;
#Validated
public interface IService {
public void setValue(#NotNull String value);
}
Service implementation:
package com.example.sandbox;
import org.springframework.stereotype.Service;
#Service
public class SomeService implements IService {
#Override
public void setValue(String value) {
// Do nothing
}
}
Test case:
package com.example.sandbox;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import javax.validation.ConstraintViolationException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
class SomeServiceTests {
#Autowired
IService service;
#Test
void testSetValue() {
assertThatThrownBy(() -> service.setValue(null)).isInstanceOf(ConstraintViolationException.class);
}
}
==> The test passes.
Working code according to the given answer:
The test class:
#SpringBootTest
class ValidationTest {
#Autowired
private Validator validator; // Using the default validator to test property annotations
#Autowired
private TestedBeanService service; // Using a service to test method annotations
#Test
void testPropertyAnnotations() {
TestedBean bean = new TestedBean(null);
Set<ConstraintViolation<TestedBean>> violations = validator.validate(bean);
assertThat(violations).isNotEmpty();
}
#Test
void testMethodAnnotations() {
TestedBean bean = new TestedBean(null);
assertThatThrownBy(() -> service.setBeanProperty(bean, null)).isInstanceOf(ConstraintViolationException.class);
}
}
The tested bean:
#Validated
class TestedBean {
#NotNull
private String someProperty;
public TestedBean(String someProperty) {
super();
this.someProperty = someProperty;
}
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(String someProperty) { // No more annotation on setter
this.someProperty = someProperty;
}
}
The service interface:
#Validated
public interface TestedBeanService {
// method annotation on the interface method
void setBeanProperty(TestedBean bean, #NotNull String someProperty);
}
The service implementation:
#Service
public class TestedBeanServiceImpl implements TestedBeanService {
#Override
public void setBeanProperty(TestedBean bean, String someProperty) {
bean.setSomeProperty(someProperty);
}
}
Why is there no ConstraintViolationException raised here? The bean property has a #NotNull annotation, the bean itself is #Validated and the method signature requires a #Valid bean.
Annotations by themselves do not mean anything, they should be processed in some way. In this case the #Validated annotation is processed by Spring for its beans. The test is not a Spring bean, so the framework does not look at the annotations related to valdidation, hence no exception.
Even if the test were a Spring Bean, the approach may not work out of the box. See this question for details.
Is there a simple way to have that exception raised in the context of my test class?
Take a look at this question
When I use validation annotations on method signatures for a service interface, everything works as expected. I don't understand where is the difference.
This happens because the service is a Spring bean, but test is not. When a method on the service is invoked, it gets intercepted by MethodValidationInterceptor, which is not the case for test
I have the following problem. I trying to start a Spring Boot application with the DB2 database with Hybernate. So i created a repository and used the #Autowired annotation to get some data from the DB. The problem is that when I run the application i receive the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field studenteRepository in com.ibm.snam.ai4legal.controller.HelloWorldController required a bean of type 'com.ibm.snam.ai4legal.repositories.StudenteRepository' that could not be found.
Action:
Consider defining a bean of type 'com.ibm.snam.ai4legal.repositories.StudenteRepository' in your configuration.
Here are the classes of the application
Application class:
package com.ibm.snam.ai4legal.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication(scanBasePackages = {"com.ibm"})
public class SBApplication {
public static void main(String[] args) {
SpringApplication.run(SBApplication.class, args);
}
}
Repository class:
package com.ibm.snam.ai4legal.repositories;
import org.springframework.data.repository.CrudRepository;
import com.ibm.snam.ai4legal.model.Studente;
public interface StudenteRepository extends CrudRepository<Studente, Integer>{
}
Model class:
package com.ibm.snam.ai4legal.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
public class Studente{
#Id
#GeneratedValue
private int id;
private String nome;
private String cognome;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getnome() {
return nome;
}
public void setnome(String nome) {
this.nome = nome;
}
public String getcognome() {
return cognome;
}
public void setcognome(String cognome) {
this.cognome = cognome;
}
}
Controller class:
package com.ibm.snam.ai4legal.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.ibm.snam.ai4legal.repositories.StudenteRepository;
#RestController
public class HelloWorldController {
#Autowired
StudenteRepository studenteRepository;
#GetMapping(value = "/home")
public ModelAndView helloworld() {
ModelAndView hello = new ModelAndView("helloworld");
return hello;
}
}
and here the pom.xml file
<?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>projects</groupId>
<artifactId>springwebapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</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-actuator</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.ibm.db2.jcc</groupId>
<artifactId>db2jcc4</artifactId>
<version>4.26.14</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
</dependencies>
<!-- <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>-->
<repositories>
<repository>
<id>repo</id>
<url>file://${project.basedir}/lib</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<packaging>war</packaging>
</project>
On the internet I found that I should insert <context:annotation-config/> in some configuration file but I have no idea in which file I have to put it. Someone can help?
You have to use #ComponentScan annotation. Try the below code.
#ComponentScan({"com.ibm.*"})
#SpringBootApplication
public class SBApplication {
public static void main(String[] args) {
SpringApplication.run(SBApplication.class, args);
}
}
Also mention #Repository annotation in StudenteRepository class.
Either move SBApplication to com.ibm.snam.ai4legal package so it can benefit from default component scanning or add the following annotations to specify packages to be scanned for entities and repositories.
#SpringBootApplication(scanBasePackages = {"com.ibm"})
#EnableJpaRepositories(basePackages = {"com.ibm"})
#EntityScan(basePackages = {"com.ibm"})
public class SBApplication {
public static void main(String[] args) {
SpringApplication.run(SBApplication.class, args);
}
}
Since you are using spring-boot-starter-data-jpa you need to provide the annotation #EnableJpaRepositories to tell springboot to autoconfigure everything.So you might want to use the auto configuration feature of springboot.The #EnableJpaRepositories annotation is not mandatory for auto configuring the spring-data-jpa but in some cases if spring component scan didn't recognize spring-data-jpa in classpath you will have to use this annotation to tell spring to autoconfigure it.
#EnableJpaRepositories will enabling auto configuration support for Spring Data JPA required to know the path of the JPA the repositories. By default, it will scan only the main application package and its sub packages for detecting the JPA repositories.So take care to put the main application class at the root package of your application.
#EnableJpaRepositories(basePackages ="com.ibm")
#SpringBootApplication(scanBasePackages = {"com.ibm"})
public class SBApplication {
public static void main(String[] args) {
SpringApplication.run(SBApplication.class, args);
}
}
Also, if your entity classes are not in the same package then you can use the #EntityScan annotation to specify the base packages. In your case you have not specifies the #Repository annotation on your interface which will tell spring-boot to create default implementations for your interface.If that annotation is not provided then spring will just ignore the interface and the bean creation will not happen.You won't be able to autowire it .So provide that and have methods declared in your interface and spring-bot will take care of the rest.
#Repository
public interface StudenteRepository extends CrudRepository<Studente, Integer>{
//If to find a student record by the id attribute
public Studente findById();
}
I have the following class that I would like to test. Having to test an anonymous inner class is proving to be very difficult. Any help would be appreciated.
#Configuration
public class CustomErrorConfiguration {
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
errorAttributes.remove("timestamp");
errorAttributes.remove("status");
errorAttributes.remove("exception");
errorAttributes.remove("path");
if (errorAttributes.containsKey("error") && errorAttributes.containsKey("message")) {
Map<String, Object> attr = new HashMap<>();
attr.put("message", errorAttributes.get("message"));
return attr;
}
return errorAttributes;
}
};
}
}
This should be the minimum needed to test your inner class:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#ContextConfiguration
public class BeanTest {
#Autowired
ErrorAttributes errorAttributes;
#Test
public void testMyBean() {
RequestAttributes requestAttributes = new RequestAttributes();
System.out.println(errorAttributes.getErrorAttributes(requestAttributes, true));
}
#Configuration
#ComponentScan(basePackages = "package.where.your.configuration.is")
public static class SpringTestConfig {
}
}
This test does a few things:
It leverages SpringRunner.class to create an ApplicationContext for you test.
The #ContextConfiguration annotation picks up the the static nested class SpringTestConfig. This class does the heavy lifting and actually scans base packages looking for other classes marked with Configuration, Bean, Component, etc. This will discover your Configuration which will in turn cause instantiation of your Bean.
Since the application context is now setup we can inject the bean with #Autowired as you would in normal application code.
I needed the following maven dependencies to accomplish this (junit >= 4.12 required). All are available in maven central:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.0.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
I am trying to implement AOP concept using Spring Boot. But before annotation is not working.
This is my code,
POM.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</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-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Application.properties
server.port=6500
spring.aop.proxy-target-class=true
Main:
package com.techno.theater;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.techno.theater.services.SampleService;
#SpringBootApplication
public class DigitalTheaterApplication {
private static Logger logger=LoggerFactory.getLogger(DigitalTheaterApplication.class);
public static void main(String[] args) {
SpringApplication.run(DigitalTheaterApplication.class, args);
new SampleService().sample();
}
}
Sample Service:
package com.techno.theater.services;
import org.springframework.stereotype.Service;
#Service
public class SampleService {
public void sample(){
System.out.println("Sample method inovking");
}
}
Aspect class
package com.techno.theater.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class AspectService {
#Before("execution(* com.techno.theater.services.SampleService.sample())")
public void beforeSampleMethod() {
System.out.println("Sample method aspect");
}
}
Here I am invoking sample method from DigitalTheaterApplication class but before execute this method my aspect method should be executed but it's not working am not sure do I need to add some configuration.
public static void main(String[] args) {
SpringApplication.run(DigitalTheaterApplication.class, args);
new SampleService().sample();
}
The code above is the problem, to be precise the new SampleService().sample(); is what is flawed in your code. You are creating a new instance outside the scope of Spring and as such it will not be exposed to AOP.
Instead what you should do, is retrieve the SampleService from the ApplicationContext.
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DigitalTheaterApplication.class, args);
ctx.getBean(SampleService.class).sample();
}
This will get the Spring created, and proxied, instance, with AOP applied.
Another way, without messing around with the ApplicationContext is to create a CommandLineRunner which will be executed during startup.
public static void main(String[] args) {
SpringApplication.run(DigitalTheaterApplication.class, args);
}
#Bean
public CommandLineRunner tester(SampleService service) {
return args -> service.sample();
}
Something like that will also invoke the sample method on the Spring managed instance without having to get it yourself.
I have an application running under Spring Boot 1.2.3 that uses methods annotated with #Async. To date it's been working properly.
After upgrading to Spring Boot 1.3.3, methods marked as #Async are not being called in a separate thread.
Here's a sample program that illustrates the issue:
App.java:
package test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
#Configuration
#EnableAutoConfiguration
#ComponentScan(basePackages = { "test" })
#EnableAsync
public class App implements CommandLineRunner {
private static final Logger log = LoggerFactory.getLogger(App.class);
#Autowired
AsyncClass async;
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
public void run(String... arg0) throws Exception {
log.info("in run");
async.start();
log.info("done run");
}
}
AsyncClass.java:
package test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
#Component
public class AsyncClass {
private static final Logger log = LoggerFactory.getLogger(AsyncClass.class);
#Async("myTaskExecutor")
public void start() {
log.info("in async task");
try {
Thread.sleep(2000);
} catch (InterruptedException e) { }
log.info("done async task");
}
#Bean
public ThreadPoolTaskExecutor myTaskExecutor() {
ThreadPoolTaskExecutor bean = new ThreadPoolTaskExecutor();
bean.setCorePoolSize(1);
bean.setMaxPoolSize(1);
bean.setQueueCapacity(10);
bean.setThreadPriority(1);
bean.setWaitForTasksToCompleteOnShutdown(true);
return bean;
}
}
pom.xml:
<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>dbush</groupId>
<artifactId>async-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>async-test</name>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- this is the only line that differs -->
<version>1.3.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
Under 1.2.3, the log statements in the start method show them as running in thread myTaskExecutor-1. Under 1.3.3, the same logs show that they run in thread main.
Any idea what might be wrong here?
You need place your bean factory method in a other class annotated as #Configuration. Executor will be used for #Async method execution in this way.
#Configuration
#EnableAsync
public class AsyncConfig {
#Bean(name = "myTaskExecutor")
public ThreadPoolTaskExecutor myTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
Injecting into configuration classes might be a challenge, I wouldn't recommend it especially if that class is also an actual bean. IMHO your class does too much. Next to that move the configuration of the ThreadPoolTaskExecutor where it belongs.
Instead of autowiring create a #Bean method which returns a CommandLineRunner instead of you implementing it.
#SpringBootApplication
#EnableAsync
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Bean
public CommandLineRunner runner(AsyncClass async) {
return new CommandLineRunner() {
public void run(String... arg0) throws Exception {
log.info("in run");
async.start();
log.info("done run");
}
};
}
#Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor bean = new ThreadPoolTaskExecutor();
bean.setCorePoolSize(1);
bean.setMaxPoolSize(1);
bean.setQueueCapacity(10);
bean.setThreadPriority(1);
bean.setWaitForTasksToCompleteOnShutdown(true);
return bean;
}
}
And of course cleanup your AsyncClass.