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.
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 try to initialize my cache with data, when my application start, and that's not working.
My code:
springBootApplication
package com.r2b.springcache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
#ComponentScan("com.r2b")
#SpringBootApplication
#EnableCaching
public class SpringCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCacheApplication.class, args);
}
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("student");
}
}
student
package com.r2b.model;
public class Student {
String id;
String name;
String clz;
public Student(String id, String name, String clz) {
super();
this.id = id;
this.name = name;
this.clz = clz;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClz() {
return clz;
}
public void setClz(String clz) {
this.clz = clz;
}
//Setters and getters
}
studentService
package com.r2b.service;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.r2b.model.Student;
#Service
public class StudentService
{
#Cacheable("student")
public Student getStudentByID(String id)
{
try
{
System.out.println("Going to sleep for 5 Secs.. to simulate backend call.");
Thread.sleep(1000*5);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return new Student(id,"Sajal" ,"V");
}
}
StudentController
package com.r2b.controller;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.r2b.model.Student;
import com.r2b.service.StudentService;
#RestController
public class StudentController
{
#Autowired
StudentService studentService;
#PostConstruct
public void init() {
studentService.getStudentByID("1");
}
#GetMapping("/student/{id}")
public Student findStudentById(#PathVariable String id)
{
System.out.println("Searching by ID : " + id);
return studentService.getStudentByID(id);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.r2b</groupId>
<artifactId>spring-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cache</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
when I go to http://localhost:8080/student/1 the first time, the cache is not active, and the response takes more than 5 seconds, but when I refresh, the cache responds, and the request takes a few milliseconds ! despite that I called the cache method in postConstruct, i have try with #AfterPropertiesSet and it not working too !
Any idea ?
Thank you
The answer is simple, but it took me almost a day to figure out methods decorated with #Cacheable have no effect in a #PostConstruct
Simply replace your #PostConstruct with a #EventListener(ApplicationReadyEvent.class)
#EventListener(ApplicationReadyEvent.class)
public void init() {
studentService.getStudentByID("1");
}
NB If an exception gets thrown from a method decorated with #PostConstruct or EventListener(ApplicationReadyEvent.class) event your application will exit...
This doesn't work because the proxy has not been initialized yet. This is actually documented in the user guide
In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object that calls another method of the target object) does not lead to actual caching at runtime even if the invoked method is marked with #Cacheable. Consider using the aspectj mode in this case. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, #PostConstruct).
This is exactly what you're doing here. Caching should be as transparent as possible so pre-loading the cache on startup looks a bit odd to me (and increase the startup time).
I am having difficulty getting an injection to work running in a standalone weld container. It is in library code that is also run on a wildfly container where it works. Other injects work in the weld container, just this one is giving me trouble. I’ve tried specifically adding both the class and the interface via the SeContainerInitializer. And I’ve tried creating a factory and using a produces method.
Annotation on the class ( it is the javax.inject.Singleton). It has a public no argument constructor
package com.ticomgeo.ftc.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Singleton;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
#Singleton
public class ExecutorImpl implements RITExecutor {
private static ScheduledExecutorService TIMER = executors.newSingleThreadScheduledExecutor();
private static final Logger LOG = LoggerFactory.getLogger(new Throwable().getStackTrace()[0].getClassName());
public ExecutorImpl() { super(); }
#PostConstruct
void initImpl() {
LOG.info("======================================Initializedingleton");
}
#Override
public void dispose() {
TIMER.shutdownNow();
}
#Override
public boolean isDisposed() {
return TIMER.isShutdown();
}
#Override
public <J> ScheduledFuture<J> schedule(Callable<J> job, long delay, TimeUnit unit) {
return TIMER.schedule(job, delay, unit);
}
#Override
public ScheduledFuture<?> schedule(Runnable job, long delay, TimeUnit unit) {
return TIMER.schedule(job, delay, unit);
}
#Override
public <J> Future<J> submit(Callable<J> job) {
return TIMER.submit(job);
}
#Override
public Future<?> submit(Runnable job) {
return TIMER.submit(job);
}
#Override
public <J> Future<J> submit(Runnable job, J result) {
return TIMER.submit(job, result);
}
}
Interface
package com.ticomgeo.ftc.util;
import javax.inject.Singleton;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public interface RITExecutor {
void dispose();
boolean isDisposed();
<J> ScheduledFuture<J> schedule(Callable<J> job, long delay, TimeUnit unit);
ScheduledFuture<?> schedule(Runnable job, long delay, TimeUnit unit);
<J> Future<J> submit(Callable<J> job);
Future<?> submit(Runnable job);
<J> Future<J> submit(Runnable job, J result);
}
Injection
#Inject
RITExecutor executor;
Weld bootstrap
public static void main(String[] args) {
SeContainerInitializer initializer = SeContainerInitializer.newInstance();
/* disable discovery and register classes manually */
try (SeContainer container = initializer.disableDiscovery()
.addPackages(FTCServer.class)
.addPackages(RITServer.class)
.addPackages(Config.class)
.addBeanClasses(ExecutorImpl.class, RITExecutor.class)
.addPackages(AbstractConnection.class)
.initialize()) {
container.select(RITServer.class);
}
When I try to access the injected bean in the weld-se instance I get a null pointer exception. I have no problems with it on the wildfly instance. There are no errors during deployment.
I have a beans.xml file in all my jars though I assume that is super-ceded by the SeContainerInitializer.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</beans>
I am using weld-se-core 3.0.2.Final. java version "1.8.0_121" AbstractConnection and ExecutorImpl are in the same package and jar file. RITExecutor interface is in another jar file.
I've upgraded to weld 3.0.3.Final, and tried the applicationScope annotation with not chance in behavior.
This is my test scenario. PostConstruct works fine on select of the bean.
```
package org.dpp.cdi.singleton;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import javax.inject.Singleton;
import org.jboss.weld.environment.se.Weld;
/**
*
* #author mochieng
*/
public class TestMain {
public static void main(String[] args) {
SeContainerInitializer weld = Weld.newInstance();
try (SeContainer se = weld.disableDiscovery().addBeanClasses(TestServiceImpl.class).initialize()) {
TestService service = se.select(TestService.class).get();
}
}
public static interface TestService {
void doTest();
}
#Singleton
public static class TestServiceImpl implements TestService {
private static final Logger LOG = Logger.getLogger(TestServiceImpl.class.getName());
#PostConstruct
void initImpl() {
LOG.info("Initialized singleton");
}
#Override
public void doTest() {
LOG.info("Singleton called");
}
}
}
```
And maven dependencies:
<?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>org.dpp</groupId>
<artifactId>cdi-singleton</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-shaded</artifactId>
<version>3.0.3.Final</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
The problem could be your weld version.
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 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.