How should aspect weaving be limited to classes referenced by aop:advisor pointcuts? - spring

I'm trying to trace execution of an app running on ServiceMix 3.2 which uses spring 2.5 under the hood. I'm using CGLIB (advising classes, not interfaces) and I would like to direct tracing using pointcuts. I therefore configured spring to perform load-time weaving in one of my service unit xbean.xml files like so:
<bean id="debugInterceptor"
class="org.springframework.aop.interceptor.SimpleTraceInterceptor"/>
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="debugInterceptor"
pointcut="within(my.package.AClass)" order="1"/>
</aop:config>
Classes get advised, but it isn't limited to what I specified in the pointcut, i.e. methods of classes other than my.package.AClass get advised and, for reasons not important here, break class loading.
I tried defining the pointcut this way, but it made no difference:
<aop:advisor advice-ref="debugInterceptor"
pointcut="execution(* my.package.AClass.*(..))" order="1"/>
In general, I would like to advise my.package..* classes except my.package.no_aop.*, but I don't seem to be making progress.
Why does CGLIB process classes outside of my.package.AClass? How do I prevent it? Would switching to Spring AOP (as opposed to AspectJ) make a difference?

I did it using Spring 3.0.x and #AspectJ annotations, but it should be analogous using 2.5 and XML.
Class A from package my.pkg, that needs to be adviced:
package my.pkg;
public class ClassA {
public void doFromClassA() {
System.out.println("Hello from A!");
}
}
Class B from package my.pkg.noaop, that needs not to be adviced:
package my.pkg.noaop;
public class ClassB {
public void doFromClassB() {
System.out.println("Hello from B!");
}
}
The aspect:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class AopTestAspect {
#Around("within(my.pkg..*) && !within(my.pkg.noaop..*)")
public void advice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Hello from adviced!");
pjp.proceed();
}
}
The configuration (let me know if You need XML version):
import my.pkg.ClassA;
import my.pkg.noaop.ClassB;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AopTestConfig {
#Bean
public ClassA classA() {
return new ClassA();
}
#Bean
public ClassB classB() {
return new ClassB();
}
#Bean
public AopTestAspect aspect() {
return new AopTestAspect();
}
#Bean
public AnnotationAwareAspectJAutoProxyCreator autoProxyCreator() {
AnnotationAwareAspectJAutoProxyCreator autoProxyCreator = new AnnotationAwareAspectJAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
}
The test:
import my.pkg.ClassA;
import my.pkg.noaop.ClassB;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AopTest {
#Test
public void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AopTestConfig.class);
applicationContext.refresh();
ClassA a = BeanFactoryUtils.beanOfType(applicationContext, ClassA.class);
ClassB b = BeanFactoryUtils.beanOfType(applicationContext, ClassB.class);
a.doFromClassA();
b.doFromClassB();
}
}
And the output from the test:
Hello from adviced!
Hello from A!
Hello from B!
As You can see only the ClassA got adviced.
Conclusion
The key is the pointcut experssion:
within(my.pkg..*) && !within(my.pkg.noaop..*)

Related

Is it possible to parameterize a JUnit Jupiter test with beans from a Spring ApplicationContext?

I would like to write a unit test which is executed for every Spring bean of a given type. JUnit5's parameterized tests offer a lot of possibilities, but I don't know how to inject beans into a method source as it has to be a static method.
Is there a way to determine the parameters of a JUnit5 test based on Spring's application context?
For starters, a factory method configured via #MethodSource does not have to be static. The second sentence in the User Guide explains that.
Factory methods within the test class must be static unless the test class is annotated with #TestInstance(Lifecycle.PER_CLASS); whereas, factory methods in external classes must always be static.
Thus, if you use #TestInstance(PER_CLASS) semantics, your #MethodSource factory method can be non-static and can therefore access the ApplicationContext injected into the test instance.
Here's an example that demonstrates that for beans of type String, with an intentional failure for the bar bean.
import java.util.stream.Stream;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
#SpringJUnitConfig
#TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {
#Autowired
ApplicationContext applicationContext;
#ParameterizedTest
#MethodSource
void stringBeans(String bean) {
assertEquals(3, bean.length());
}
Stream<String> stringBeans() {
return applicationContext.getBeansOfType(String.class).values().stream();
}
#Configuration
static class Config {
#Bean
String foo() {
return "foo";
}
#Bean
String bar() {
return "barf";
}
}
}
If you don't want to work directly with the ApplicationContext, you can simplify the solution by having the collection of all such beans of a given type (String in this example) injected directly, as follows.
#SpringJUnitConfig
#TestInstance(PER_CLASS)
class SpringBeansParameterizedTests {
#Autowired
List<String> stringBeans;
#ParameterizedTest
#MethodSource
void stringBeans(String bean) {
assertEquals(3, bean.length());
}
Stream<String> stringBeans() {
return this.stringBeans.stream();
}
#Configuration
static class Config {
#Bean
String foo() {
return "foo";
}
#Bean
String bar() {
return "barf";
}
}
}
The usage of the #TestFactory might help.
Actually I stumbled across a post that does a pretty similar (or the same) thing as you do on github.
Let your Test run with the SpringExtenion and use the injected Beans as parameters for our Test.

Pointcut expression not matching the Spring Data method despite specfying exact name in expression

In my Spring Boot project, I have AddressRepository that brings all addresses from Database. I have an Aspect class and a pointcut expression that executes after the findAll() method called. When I execute my test case, the Advice is not being triggered and other methods like findAll(Sort sort), findAll(Pageable pageable) work just fine. I am not sure if this is a bug with Spring Boot or my expression. I tried with Spring Boot 2.0.5 and 2.1.0, nothing seemed to solve my problem
AddressLogging.java
#Aspect
#Configuration
public class AddressLogging {
private Logger log=LoggerFactory.getLogger(AddressLogging.class);
//#Pointcut("execution(* com.springtesting.repo.AddressRepository.*(..))")
#Pointcut("execution(* com.springtesting.repo.AddressRepository.findAll())")
public void getAddresses() {}
#After("getAddresses()")
public void afterAdvice() {
log.error("Log Message: Inside afterAdvice() advice");
}
}
AopTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
public class AopTest {
#Autowired
private AddressRepository addressRepository;
#Test
public void getAddresses() {
//addressRepository.findAll(PageRequest.of(0,20, Sort.by("id")));
addressRepository.findAll();
}
#Test
public void findAddressById() {
addressRepository.findById(1L);
}
}
AddressRepository
public interface AddressRepository extends JpaRepository<Address,Long> {}
A Spring AOP aspect should also be a #Component and be picked up by component scan. I have no idea why you added #Configuration to the aspect instead because there is no configuration here.
Maybe your test of a separate configuration class should bear the #Configuration annotation and you should also activate something like #EnableAspectJAutoProxy(proxyTargetClass = true) and #ComponentScan(basePackages = { "de.scrum_master" }).
Here is a snippet from one of my Spring AOP playground projects (I hardly use it, I don't use Spring AOP or even Spring itself, usually I use the more powerful AspectJ:
package de.scrum_master.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ComponentScan(basePackages = { "de.scrum_master" })
public class Application2 {
public static void main(String[] args) throws Exception {
ApplicationContext appContext = new AnnotationConfigApplicationContext(Application2.class);
B b = (B) appContext.getBean("b");
System.out.println(b.getData("bbb"));
A a = (A) appContext.getBean("b");
System.out.println(a.getData("aaa"));
}
}

How Do I Unit Test A Jersey REST API Without Running A Server?

I am working with a REST API that is using Jersey with Spring Boot (so no specific application context in XML or Java) and Spring Data JPA.
I want to write unit tests on the GET and POST endpoints, however, I don't want to start a web server as it takes too long.
If I use JerseyTest my Spring Beans don't get loaded into the context.
public class InMemoryContainerPackageTest extends
JerseyTestNg.ContainerPerClassTest {
#Override
protected TestContainerFactory getTestContainerFactory() {
return new InMemoryTestContainerFactory();
}
#Override
public Application configure() {
ResourceConfig config = new ResourceConfig()
.register(SpringLifecycleListener.class)
.register(RequestContextFilter.class)
.register(this)
.register(MyController.class)
.packages("com.my.service");
return config;
}
If I use SpringBootTest it starts up a web server which takes about 30 seconds and ideally I want all my tests to complete in under 5 seconds otherwise developers won't run them.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestNGClass1 extends AbstractTestNGSpringContextTests {
I don't think MockMvc works with Jersey endpoints.
If I use JerseyTest my Spring Beans don't get loaded into the context.
What you can do is set the property "contextConfig" in your ResourceConfig. The value will be a Spring ApplicationContext instance. So if you are using Java configuration, you would just use an AnnotationConfigApplicationContext.
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.property("contextConfig",
new AnnotationConfigApplicationContext(SpringConfig.class));
}
Here, SpringConfig is an arbitrary Spring #Configuration class. Below is a complete example.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
public class SpringTest extends JerseyTest {
public static class MessageService {
public String getMessage() {
return "Hello World";
}
}
#Configuration
public static class SpringConfig {
#Bean
public MessageService service() {
return new MessageService();
}
}
#Path("test")
public static class TestResource {
#Autowired
private MessageService service;
#GET
public String get() {
return service.getMessage();
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.property("contextConfig",
new AnnotationConfigApplicationContext(SpringConfig.class));
}
#Override
public TestContainerFactory getTestContainerFactory() {
return new InMemoryTestContainerFactory();
}
#Test
public void testIt() {
Response res = target("test")
.request()
.get();
String msg = res.readEntity(String.class);
System.out.println(msg);
assertThat(msg).isEqualTo("Hello World");
}
}
As far as the JPA, you are going to have to configure that yourself. When using Spring Boot, all of the JPA bootstrapping is taken care of. If you are going to use Jersey Test Framework, then you are ignoring all Spring Boot configuration.
It's really not that hard to configure JPA yourself. It basically consists of configuring a DataSource, a TransactionManager, a JpaVendorAdaptor, and a LocalContainerEntityManagerFactoryBean. And to enable the Spring Data repositories, you just need to use the #EnableJpaRepositories.
Have a look at this complete example configuration.
Another thing to be wary of is that when we use the Jersey Test Framework, we will not have the test scoped transactions (and rollbacks) that you will get when working with Spring Test. So when you write your tests, you need to take this into consideration.

How to make Spring IoC container available through out project

I feel stupid to even ask for this but I spent days looking for the answer and I'm still with nothing.
I wanna include simple Spring IoC container in my project. All I want it to do is to allow me Injecting/Autowiring some reusable objects in other classes. What I've done so far looks like this:
-> Project structure here <-
Configuration code:
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.Random;
#Configuration
#ComponentScan(basePackages = "com.example")
public class AppConfig {
#Bean
public Random rand() {
return new Random(42);
}
#Bean
public String string() {
return "Hello World!";
}
}
Main class code:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class Main {
#Autowired
Random rand;
#Autowired
String string;
public static void main(String[] args) {
// workflow
Main main = new Main();
System.out.println(main.string);
}
}
AnotherClass code:
package com.example.deeperpackage;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class AnotherClass {
#Autowired
Random rand;
#Autowired
String string;
public void methodToBeCalled() {
// TODO
System.out.println(string);
}
}
How can I make these #Autowired annotations work? Do I have to instantiate container in every single class in which I want to autowire components? I've seen in work a oracle app which used Spring and #Inject to distribute objects to numerous classes and there was no container logic in any class available for me. Just fields with #Inject annotation. How to achieve that?
Simply add the annotation #Component on the classes you want to inject :
#Component
public class AnotherClass {
...
}
But you cannot inject static attributes and when you do new Main(), no Spring context is being created. If you use Spring Boot, you should look at how to write a main with it.
https://spring.io/guides/gs/spring-boot/

Autowire working in unit test but not in main java class

I've a domain class that I want to auto-populate from external config. Here is my domain class:
#Data
#Configuration
#PropertySource("classpath:application.properties")
public class StudioVo {
#Value("${studio.code}")
private code;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Here is my context xml:
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean id="ItemReader" class="com.sdm.studio.reader.StudioReader" scope="step">
<property name="studioVo" ref="StudioVo" />
</bean>
<bean id="StudioConfigVo" class="com.sdm.studio.domain.StudioVo" />
</bean>
Here is the class where I want to use the vo:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
private StudioVo studioVo;
public List<Studio> read() throws Exception {
System.out.println("getCode: " + studioVo.getCode()); //code is null here
return null;
}
}
However when I run it via unit test by autowiring, it runs fine. Like this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class StudioTest {
#Autowired
private StudioVo studioVo;
#Test
public void testAutoPopulationOfStudio(){
System.out.println("getCode: "+ studioVo.getCode()); // works!
// Assert.assertTrue(studioVo.getCode().equals("102"));
}
}
Not sure what's going on here - I'm working with an old Spring Batch application wrapped in Spring Boot (so there is a mix of XML based and Java based config - and may be that is the cause of this issue). What am I missing?
In your StudioTest, you are autowiring StudioReader where as you missed the #Autowired in your StudioReader code, so add it as shown below:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
#Autowired //add this so that studioVo can be injected
private StudioVo studioVo;
//add other code
}
Please be certain to note that using #Autowire requires a chain of Spring-managed beans below it from wherever you are using it including the class in which you are using #Autowire. That is because Spring needs the precedent references to match up the object-reference hierarchy. I.e., in business logic layer ClassA, you want to #Autowire a field. ClassA itself needs to be a managed bean. Further, if the field you want to #Autowire holds an object that has referential dependencies to other objects (and most do), these also must be Spring-managed.
For example, the following will work:
package com.example.demo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MessageRunner {
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage = (SetterMessage) (new AnnotationConfigApplicationContext(DemoConfiguration.class)).getBean("setterMessage");
setterMessage.setMessage("Finally it works.");
p(setterMessage.getMessage());
}
private static void p(String s) {
System.out.println(s);
}
}
DemoConfiguration.java looks like this:
package com.example.demo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.example.demo")
public class DemoConfiguration {
}
SetterMessage.java, this:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage {
private String message = null;
#Autowired
private SetterMessage2 setterMessage2;
public String getMessage(){
return message+setterMessage2.getSubMessage();
}
public void setMessage(String message) {
this.message = message;
setterMessage2.setSubMessage("("+message+")");
}
}
SetterMessage2.java:
package com.example.demo;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage2 {
private String subMsg = "";
public void setSubMessage(String msg) {
subMsg = msg;
}
public String getSubMessage() {
return subMsg;
}
}
Note that SetterMessage2.java is annotated as a Component (#Service) but no field in it is autowired. That is because it's the end of the object reference chain. But because it is a Component, it can be autowired into SetterMessage.java. However look at MessageRunner.java's main() method and field declarations. Note that the class field SetterMessage is NOT autowired. If it were annotated as #Autowired, main() would fail at runtime, throwing an NPE with the reference to setterMessage in main(). This is because MessageRunner.java is not marked as some kind of component. So we need to grab a valid instance of MessageSetter from the application context and use it.
To emphasize, the following version of MessageRunner.java's main() method WILL FAIL, throwing an NPE, if MessageRunner.java looked like this:
...
public class MessageRunner {
#Autowired // <-- This will not do the job for us
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage.setMessage("Finally it works."); // NPE here on ref to setterMessage
p(setterMessage.getMessage());
}
...
This is a real gotchya for people new to Spring. In fact, I'd place it among the Top Five Spring Newbie Discouragers and a really evil, pernicious detail that has caused new Spring programmers countless hours in aggravation and Google searches. So I do hope that noting this phenom here will save at least some newbies time and high blood pressure spikes.
Note: If you go to create the above classes in your IDE, bear in mind these were developed with Spring Boot enabled.

Resources