I am trying to implement a simple Spring AOP example.
Use case:
I have a compact disc (SgtPeppers) class with attributes such as artist, title. It has a method to play a specific track. I have created an aspect to count the number of times a track is being played. I have a test class to test the same.
Problem:
Compact disc class is in a different package from the aspect and the test class. Beans are configured using spring java config method. However, autowiring doesn't seem to work for compact disc class. It throws BeanCreationException.
Please find the code below: Compact disc class (Here, it's configured as SgtPeppers)
package com.springinaction.soundsystem.autoconfig;
import java.util.List;
public class SgtPeppers implements CompactDisc {
private String title, artist;
public SgtPeppers(String title, String artist) {
super();
this.title = title;
this.artist = artist;
}
public SgtPeppers() {
// TODO Auto-generated constructor stub
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
@Override
public void play() {
System.out.println("Playing "+title+" by "+ artist);
}
public void playTrack(int tracknumber) {
System.out.println("Playing track "+ tracknumber);
}
}
TrackCounter Aspect:
package com.springinaction.aspects.concert.trackCounter;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TrackCounter {
private Map<Integer,Integer> trackCounts = new HashMap<Integer, Integer>();
@Pointcut("execution(** com.springinaction.aspects.concert.trackCounter.SgtPeppers.playTrack(int)) && args(trackNumber)")
public void trackPlayed(int trackNumber){}
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int trackCount = getTrackCount(trackNumber);
trackCounts.put(trackNumber, trackCount+1);
}
public int getTrackCount(int trackNumber) {
return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
}
}
TrackCounterConfig:
package com.springinaction.aspects.concert.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.springinaction.aspects.concert.trackCounter.SgtPeppers;
import com.springinaction.aspects.concert.trackCounter.TrackCounter;
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {
@Bean
public SgtPeppers sgtPeppers() {
SgtPeppers cd = new SgtPeppers("Sgt. Pepper's Lonely Hearts Club Band", "The Beatles");
return cd;
}
@Bean
public TrackCounter trackCounter() {
return new TrackCounter();
}
}
TrackCounterTest:
package com.springinaction.aspects.concert.trackCounter;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.springinaction.aspects.concert.config.TrackCounterConfig;
import com.springinaction.soundsystem.autoconfig.SgtPeppers;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=TrackCounterConfig.class)
public class TrackCounterTest {
@Autowired
private TrackCounter trackCounter;
@Autowired
private SgtPeppers sgtPeppers;
@Test
public void testTrackCounter() {
sgtPeppers.playTrack(1);
sgtPeppers.playTrack(1);
sgtPeppers.playTrack(2);
sgtPeppers.playTrack(2);
sgtPeppers.playTrack(3);
sgtPeppers.playTrack(3);
sgtPeppers.playTrack(3);
sgtPeppers.playTrack(3);
sgtPeppers.playTrack(7);
sgtPeppers.playTrack(7);
assertEquals(2, trackCounter.getTrackCount(1));
assertEquals(2, trackCounter.getTrackCount(2));
assertEquals(4, trackCounter.getTrackCount(3));
assertEquals(2, trackCounter.getTrackCount(7));
}
}
Here, SgtPeppers class is in com.springinaction.soundsystem.autoconfig package. TrackCounter and TrackCounterconfig are in com.springinaction.aspects.concert.config package. I get the following exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.springinaction.aspects.concert.trackCounter.TrackCounterTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.springinaction.soundsystem.autoconfig.SgtPeppers com.springinaction.aspects.concert.trackCounter.TrackCounterTest.sgtPeppers; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.springinaction.soundsystem.autoconfig.SgtPeppers] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:293) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:384) at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110) at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:331) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:213) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:290) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:292) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:233) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:87) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:176) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.springinaction.soundsystem.autoconfig.SgtPeppers com.springinaction.aspects.concert.trackCounter.TrackCounterTest.sgtPeppers; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.springinaction.soundsystem.autoconfig.SgtPeppers] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:509) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:290) ... 26 more Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.springinaction.soundsystem.autoconfig.SgtPeppers] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1118) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:967) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:481) ... 28 more
Note: I have tried ComponentScan with basePackages, basePackageClasses. But, it didn't work. I don't use spring boot configuration as it's for learning purpose.
I have created the same SgtPeppers bean inside com.springinaction.aspects.concert.config package. Autowiring worked. Why java config way of bean creation doesn't work for beans defined in different packages?
Any suggestion would be helpful.
Thanks.