I'm trying to get my Aspect class to work but it gets completely ignored.
I have following files:
MyAnnotation.java
package annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
}
MyAspect.java
package annotations;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MyAspect {
#Before("#annotation(annotations.MyAnnotation)*")
public void interceptMethods(final JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
MyClass.java
package annotations;
import org.springframework.stereotype.Service;
#Service
public class MyClass {
#MyAnnotation
public int myMethod(final int i) {
System.out.println("method: " + i);
return i;
}
}
MyRestController.java
package annotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class MyRestController {
#Autowired
MyClass myClass;
#GetMapping("/myClass")
private int callMyMethod() {
return myClass.myMethod(1);
}
}
Application.java
package annotations;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>experiments</groupId>
<artifactId>annotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
</dependencies>
</project>
Does anyone see the problem and how to fix it?
I've tried multiple Before expressions ("execution(* annotations..(..))") but I just can't see to get it working.
I've tried Around instead of Before.
I've tried Pointcuts with Before.
I've been through articles:
AspectJ #Before annotation issue
Spring AspectJ, pointcut before method execution where method OR class is annotated
I finally did it. I'll leave the solution here for other people in need for a basic example.
These are the files:
MyAnnotation.java
package annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
}
MyAspect.java - it's tricky because:
annotation #Aspect is not sufficient. The annotation #Component also needs to be added to aspect class. If annotation #Component is omitted, the application will ignore the class.
in #Before annotation, there needs to be "and args(*)". If the args are omitted, there will be null pointer exception.
Code:
package annotations;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyAspect {
#Before("execution(* annotations.*.*(..)) and args(*)")
public void beforeExecution(final JoinPoint joinPoint) {
System.out.println("5");
}
}
MyClass.java
package annotations;
import org.springframework.stereotype.Service;
#Service
public class MyClass {
#MyAnnotation
public int myMethod(final int i) {
System.out.println("method: " + i);
return i;
}
}
MyRestController
package annotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class MyRestController {
#Autowired
MyClass myClass;
#GetMapping("/myClass")
private int callMyMethod() {
return myClass.myMethod(1);
}
}
Application.java
package annotations;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
}
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>experiments</groupId>
<artifactId>annotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
</dependencies>
Related
I'm trying to connect my Controller to Repository in one of the spring application but I'm getting an error saying "Field tweetRepository in TweetsController.TweetsController required a bean of type 'TweetsController.TweetRepository' that could not be found."
Can someone help me with this? Thanks in advance. I've attached code samples as well.
TwitterApplication.java
package SpringMVC.Twitter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#SpringBootApplication
#ComponentScan("AuthController")
#ComponentScan("TweetsController")
public class TwitterApplication {
public static void main(String[] args) {
SpringApplication.run(TwitterApplication.class, args);
}
}
TwitterController.java
package TweetsController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
#RestController
public class TweetsController {
#Autowired
private TweetRepository tweetRepository;
#RequestMapping("/tweets")
public Iterable<TweetsContent> getAllTweets() {
return tweetRepository.findAll();
}
#RequestMapping("tweet/{id}")
public Optional<TweetsContent> getTweet(#PathVariable int id) {
return tweetRepository.findById(id);
}
#RequestMapping(method = RequestMethod.POST, value = "/tweets")
public boolean addTweet(#RequestBody TweetsContent tweet) {
TweetsContent t = tweetRepository.save(new TweetsContent(tweet.getTitle(), tweet.getContent()));
if (t != null)
return true;
else
return false;
}
}
TwitterRepository.java
package TweetsController;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface TweetRepository extends CrudRepository<TweetsContent, Integer> { }
Add the spring data dependency on your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Add this too.
#ComponentScan("TweetRepository")
You don't need to use #ComponentScan("AuthController") and #ComponentScan("TweetsController") because if you use #SpringBootApplication, scanning will occur from the package of the class that declares this annotation. But you need to put TwitterApplication like this:
You don't have to specify #ComponentScan in your TwitterApplication, because spring boot application is scanning its own directory at the startup. and remove the #Repository
annotation from your repository class, it is also not needed.
I'm trying to mock a controller/resource including the jax-rs layer. The class has dependencies that need to be injected.
It however also has some String values that are injected using a qualifier interface.
Basically, I'm using JerseyTest to run a single controller and use HK2 for dependency injection. I created a ResourceConfig and registered a AbstractBinder to bind the injected classes.
This works fine for regular injected dependencies, but when the the additional #SomeQualifierInterface annotation is added, it crashes with the following error:
MultiException stack 1 of 3
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=String,parent=ThingsController,qualifiers={#com.company.SomeQualifierInterface()},position=-1,optional=false,self=false,unqualified=null,10035302)
...
MultiException stack 2 of 3
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.company.ThingsController errors were found
...
MultiException stack 3 of 3
java.lang.IllegalStateException: Unable to perform operation: resolve on com.company.ThingsController
...
See the simplified full code example below:
Controller / Resource
import org.slf4j.Logger;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
#Path("/things")
public class ThingsController {
#Inject
private Logger log;
#Inject
#SomeQualifierInterface
private String injectedQualifierValue;
#GET
public Response getThings() {
log.info("getting things");
System.out.println("Injected value: " + injectedQualifierValue);
return Response.status(200).entity("hello world!").build();
}
}
Qualifier interface
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
#Qualifier
#Retention(RUNTIME)
#Target({ TYPE, METHOD, FIELD, PARAMETER })
public #interface SomeQualifierInterface { }
Producing service
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
#ApplicationScoped
public class SomeProducerService {
#Produces
#Dependent
#SomeQualifierInterface
public String getQualifierValue() {
return "some value!";
}
}
Test
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.slf4j.Logger;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MockedThingsControllerTest extends JerseyTest {
private Logger logMock = mock(Logger.class);
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(ThingsController.class);
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure() {
bind(logMock).to(Logger.class);
bind("some mocked value").to(String.class); // Doesn't work
bind(new SomeProducerService()).to(SomeProducerService.class); // Doesn't work
}
});
return resourceConfig;
}
#Test
public void doSomething() {
Response response = target("/things").request().get();
assertEquals(200, response.getStatus());
verify(logMock).info("getting things");
}
}
POM
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.27.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
Solved!
First, use the AbstractBinder from org.glassfish.hk2.utilities.binding.AbstractBinder instead of org.glassfish.jersey.internal.inject.AbstractBinder.
Second, create a class that extends AnnotationLiteral and implements the interface.
Last, bind the value to a TypeLiteral and set the qualifiedBy to the AnnotationLiteral.
Full code:
import org.glassfish.hk2.api.AnnotationLiteral;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.slf4j.Logger;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MockedThingsControllerTest extends JerseyTest {
private Logger logMock = mock(Logger.class);
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(ThingsController.class);
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure() {
bind(logMock).to(Logger.class);
bind("some mocked value").to(new TypeLiteral<String>() {}).qualifiedBy(new SomeQualifierLiteral());
}
});
return resourceConfig;
}
#Test
public void doSomething() {
Response response = target("/things").request().get();
assertEquals(200, response.getStatus());
verify(logMock).info("getting things");
}
static class SomeQualifierLiteral extends AnnotationLiteral<SomeQualifierInterface> implements SomeQualifierInterface {}
}
i am already desperate, i cannot find out why this test is not evaluated as successful. I have checked it milion times:
package someptest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import java.sql.SQLException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import somep.Order;
import somepBO.BOException;
import somepdao.OrderDAO;
public class XXX {
#Mock
OrderDAO dao;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void captor_A() throws SQLException, BOException {
Order order = new Order();
ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
when(dao.read(any(Integer.class))).thenReturn(order);
dao.read(123);
dao.read(456);
verify(dao, times(2)).read(intCaptor.capture());
#SuppressWarnings("unused")
List<Integer> xs = intCaptor.getAllValues();
assertThat(intCaptor.getAllValues(), hasItems(456));
}
}
Here is a screen from my debugging, captor catches correct values, but assertThat does not accept it, why?
Phew, something seems to go quite wrong on your machine. Based on your test, I created the following self-contained test class:
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
public class ArgumentCaptorTest {
private static class Order {
}
public static class OrderDAO {
public Order read(Integer any) {
return null;
}
}
#Mock
OrderDAO dao;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void captor_A() {
Order order = new Order();
ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
when(dao.read(any(Integer.class))).thenReturn(order);
dao.read(123);
dao.read(456);
verify(dao, times(2)).read(intCaptor.capture());
assertThat(intCaptor.getAllValues(), hasItems(456));
}
}
Used dependencies (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>testing</groupId>
<artifactId>testing</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.18.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
When I start the test, it runs perfectly and gives me a green bar. For what I see, you're doing it right. Maybe you have version conflicts between JUnit, Hamcrest and Mockito that lead to your error? I used these JAR versions:
junit:junit:4.12
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:2.18.3
I also left the imports so you can compare them with yours (just in case that a "wrong" import causes the error). As you statically import Matchers.* from the Hamcrest package, I am quite sure that this and/or your used library versions cause your problem.
I want to be able to track methods that are anotated with #RequestMapping that are annotated with a certain annotation (for instance #LooseController).
I have two pointcuts: requestMappings() and looseController()
this works well if the method that is annotated with #RequestMapping is in a class that has #LooseController but not if the #LooseController is in a subclass
for instance, when update(id) is called on Controller1 it is not caught by this aspect
Update to include more information:
package de.scrum_master.app;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyAspect {
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
private static void requestMapping() {}
#Pointcut("#within(de.scrum_master.app.LooseController)")
private static void looseController() {}
// #Pointcut("#this(de.scrum_master.app.LooseController)")
// private static void looseController() {}
#Before("requestMapping() && looseController()")
public void myAdvice(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
package de.scrum_master.app;
import org.springframework.web.bind.annotation.RequestMapping;
//#LooseController
public abstract class PutController {
#RequestMapping("/{id}")
public void update(String id) {
}
}
package de.scrum_master.app;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Retention(RUNTIME)
public #interface LooseController {
}
package de.scrum_master.app;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
#Component
#LooseController
#RequestMapping("/something")
public class Controller1 extends PutController {
}
package de.scrum_master.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
#SpringBootApplication
public class AspectApplication implements CommandLineRunner {
#Autowired
private Controller1 controller1;
#Autowired
private ConfigurableApplicationContext context;
public static void main(String[] args) {
SpringApplication.run(AspectApplication.class, "--logging.level.root=WARN", "--spring.main.banner-mode=off");
}
#Override
public void run(String... strings) throws Exception {
controller1.update("test");
context.close();
}
}
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>aspects</groupId>
<artifactId>aspects</artifactId>
<version>1.0-SNAPSHOT</version>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>
As I said in my comment, I have to speculate:
It begins with your exact pointcut (which you do not show)
and goes on with the question whether you use Spring AOP or full AspectJ via LTW (load-time weaving).
In the former case you do not explain if your target classes are actual Spring beans because Spring AOP can only proxy Spring beans/components.
In the latter case you have more options but have to configure your application in a different way.
You also do not show your own annotation's implementation, especially not if it has the required runtime retention.
My hypothesis for now is that
you use proxy-based Spring AOP and
all classes and aspects are #Components or otherwise declared as Spring beans in your configuration.
But in order to show you what happens I will use a stand-alone Java example with AspectJ, not a Spring application. I only put spring-web.jar and sprint-context.jar on my classpath so as to resolve the Spring annotations.
Annotation:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface LooseController {}
Abstract base class:
package de.scrum_master.app;
import org.springframework.web.bind.annotation.RequestMapping;
public abstract class PutController {
#RequestMapping("/{id}")
public void update(String id) {}
}
Subclass + main method:
package de.scrum_master.app;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
#Component
#LooseController
#RequestMapping("/something")
public class Controller1 extends PutController {
public static void main(String[] args) {
new Controller1().update("foo");
}
}
The aspect which I guess you might use:
Please note that your own pointcut within(#blabla.LooseController) is syntactically invalid, this is why I changed it to #within(blabla.LooseController).
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyAspect {
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
private static void requestMapping() {}
#Pointcut("#within(de.scrum_master.app.LooseController)")
private static void looseController() {}
#Before("requestMapping() && looseController()")
public void myAdvice(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
Console log when running Controller1.main:
staticinitialization(de.scrum_master.app.Controller1.<clinit>)
call(void de.scrum_master.app.Controller1.update(String))
Now you see the problem: AspectJ can intercept a few joinpoints for the given pointcut, but neither call nor staticinitialization are supported by Spring AOP according to the documentation.
So either you need to switch to AspectJ with LTW or devise another pointcut strategy.
To be continued, I have to interrupt the answer here for a while because I have an appointment, but will add some more info later.
Update: Okay, here is your problem and a solution: #within(de.scrum_master.app.LooseController) looks inside classes with the #LooseController annotation, but the parent class with the annotated method you are trying to intercept does not have that annotation. Thus, #within() is not the right pointcut type for you. What you want to express is that the instance, i.e. the current target object upon which the method is called, belongs to an annotated class. Therefore, you need a #target() pointcut:
#Pointcut("#target(de.scrum_master.app.LooseController)")
private static void looseController() {}
Alternatively you could also use this workaround (a little ugly, but works too):
#Pointcut("target(#de.scrum_master.app.LooseController Object)")
private static void looseController() {}
Now the console log says:
execution(void de.scrum_master.app.PutController.update(String))
This should also work in Spring AOP because execution() is a supported pointcut type there.
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.