I am trying to do a full annotations (no xml) implementation of Spring. The autowired members are not being populated. From my research, there are 3 things to do:
Set up a config file that manages the bean
Use #Autowired to get the bean to the file
Instantiate an application context to get the ball rolling
It is difficult to find a complete example of this which uses annotations only, so I don't have much to reference. Most examples use at least some xml.
There is no error message, so I don't have any idea where the problem is. The value is just null. Here are my files:
Trivial.java
public class Trivial {
public TrivialBean trivialBean;
#Autowired
public void setTrivialBean(TrivialBean trivialBean) {
this.trivialBean = trivialBean;
}
public static void main(String...args) {
ApplicationContext context
= new AnnotationConfigApplicationContext(
TrivialConfig.class);
new Trivial().go();
}
private void go() {
System.out.println("trivialBean: " + trivialBean);
}
}
TrivialBean.java
public class TrivialBean {
public String foo = "TEST TEST TEST";
#Override
public String toString() {
return foo;
}
}
TrivialConfig.java
#Configuration
public class TrivialConfig {
#Bean
public TrivialBean trivialBean() {
return new TrivialBean();
}
}
I would expect this to output trivialBean: TEST TEST TEST, but is just outputs trivialBean: null
For the #Autowired in Trivial to work, you need to have Trivial instantiated by Spring. new Trivial() won't work. For your sample to work, I think you need the following:
Configure Trivial as a bean.
Change new Trivial() to context.getBean(Trivial.class).
However, note that it is considered bad practice to use context.getBean under normal circumstances.
Regular autowiring in annotation-based container configuration
In order for autowiring to work, the lifecycle of the instance of Trivial has to be managed by the Spring container.
Example
TrivialBean.java is the same
public class TrivialBean {
public String foo = "TEST TEST TEST";
#Override
public String toString() {
return foo;
}
}
TrivialConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class TrivialConfig {
#Bean
public TrivialBean trivialBean() {
return new TrivialBean();
}
#Bean
public Trivial trivial() {
return new Trivial();
}
}
Trivial.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Trivial {
public TrivialBean trivialBean;
#Autowired
public void setTrivialBean(TrivialBean trivialBean) {
this.trivialBean = trivialBean;
}
public static void main(String... args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TrivialConfig.class);
Trivial trivial = context.getBean(Trivial.class);
trivial.go();
}
private void go() {
System.out.println("trivialBean: " + trivialBean);
}
}
Output
trivialBean: TEST TEST TEST
Please consult Spring documentation for more information on Annotation-based container configuration.
AspectJ compile-time weaving and #Configurable
It is possible to autowire TrivialBean instance into Trivial instance created by new.
spring-aspects.jar contains an annotation-driven aspect that allows dependency injection for objects created outside of the control of the container. However, it should not be used in new Spring-based projects. It is intended to be used for legacy projects, where for some reason some instances are created outside of the Spring container.
Example for Spring 4.2.0 (the latest at the moment), AspectJ 1.8.6 (the latest at the moment), Maven and Java 1.8.
Additional dependencies on spring-aspects and aspectjrt
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
Compile time weaving via AspectJ Maven plugin
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<encoding>UTF-8</encoding>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<Xlint>warning</Xlint>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
TrivialBean.java is the same
public class TrivialBean {
public String foo = "TEST TEST TEST";
#Override
public String toString() {
return foo;
}
}
TrivialConfig.java
#EnableSpringConfigured is analogous to <context:spring-configured>. It signals the current application context to apply dependency injection to classes that are instantiated outside of the Spring bean factory.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
#Configuration
#EnableSpringConfigured
public class TrivialConfig {
#Bean
public TrivialBean trivialBean() {
return new TrivialBean();
}
}
Trivial.java
#Configurable applies Spring-driven configuration to Trivial
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
#Configurable
public class Trivial {
public TrivialBean trivialBean;
#Autowired
public void setTrivialBean(TrivialBean trivialBean) {
this.trivialBean = trivialBean;
}
public static void main(String... args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TrivialConfig.class);
Trivial trivial = new Trivial();
trivial.go();
}
private void go() {
System.out.println("trivialBean: " + trivialBean);
}
}
Output
trivialBean: TEST TEST TEST
It works! Please consult Spring documentation for more information on AspectJ and #Configurable.
Related
I am trying to enable loadtimeweaving without javaagent jar files of aspectweaver and spring-instrument. This what I have implemented to achieve the same But it's not working.
#ComponentScan("com.myapplication")
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class AopConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
/**
* Makes the aspect a Spring bean, eligible for receiving autowired components.
*/
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
A workaround I found was to hot-attach InstrumentationSavingAgent from spring-instrument instead of starting the agent via -javaagent command line parameter. But for that you need an Instrumentation instance. I just used the tiny helper library byte-buddy-helper (works independently of ByteBuddy, don't worry) which can do just that. Make sure that in Java 9+ JVMs the Attach API is activated if for some reason this is not working.
So get rid of implements LoadTimeWeavingConfigurer and the two factory methods in your configuration class and just do it like this:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.14</version>
</dependency>
#SpringBootApplication
public class Application {
public static void main(String[] args) {
Instrumentation instrumentation = ByteBuddyAgent.install();
InstrumentationSavingAgent.premain("", instrumentation);
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// ...
}
}
Feel free to ask follow-up questions if there is anything you do not understand.
Update: One more thing I noticed is that this only works for me with aspectjWeaving = ENABLED, not with AUTODETECT. And for one sample Spring bean I noticed that #Component did not work, probably because of some bootstrapping issue between Spring vs AspectJ. Hence, I replaced it by an explicit #Bean configuration, then it worked. Something like this:
#Configuration
#ComponentScan("com.spring.aspect.dynamicflow")
#EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
public class ApplicationConfig {
#Bean
public JobProcess jobProcess() {
return new JobProcessImpl();
}
}
I need to measure method-metrics using micrometer #Timed annotation. As it doesn't work on arbitrary methods; i added the configuration of #TimedAspect explicitly in my spring config. Have referred to this post for exact config
Note: have tried adding a separate config class JUST for this, as well as including the TimedAspect bean as part of my existing configuration bean
How to measure service methods using spring boot 2 and micrometer
Yet, it unfortunately doesn't work. The Bean is registred and the invocation from config class goes thru successfully on startup. Found this while debugging. However, the code in the #Around never seems to execute.
No error is thrown; and im able to view the default 'system' metrics on the /metrics and /prometheus endpoint.
Note: This is AFTER getting the 'method' to be invoked several times by executing a business flow. I'm aware that it probably doesn't show up in the metrics if the method isn't invoked at all
Versions: spring-boot 2.1.1, spring 5.3, micrometer 1.1.4, actuator 2.1
Tried everything going by the below posts:
How to measure service methods using spring boot 2 and micrometer
https://github.com/izeye/sample-micrometer-spring-boot/tree/timed-annotation
https://github.com/micrometer-metrics/micrometer/issues/361
Update: So, the issue seems to be ONLY when the Timed is on an abstract method, which is called via another method. Was able to reproduce it via a simple example. Refer to the #Timed("say_hello_example") annotation. It simply gets ignored and doesnt show up when i hit the prometheus endpoint.
Code:
Abstract Class
public abstract class AbstractUtil {
public abstract void sayhello();
public void sayhellowithtimed(String passedVar) {
System.out.println("Passed var =>"+passedVar);
System.out.println("Calling abstract sayhello....");
sayhello();
}
}
Impl Class
#Component
#Scope("prototype")
public class ExampleUtil extends AbstractUtil {
public static final String HELLO = "HELLO";
#Timed("dirwatcher_handler")
public void handleDirectoryWatcherChange(WatchEvent event){
System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context());
}
#Timed("say_hello_example")
#Override
public void sayhello() {
System.out.println(HELLO);
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
A simple DirWatcher implementation class...
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.*;
#Component
#Scope("prototype")
public class StartDirWatcher implements ApplicationListener<ApplicationStartedEvent> {
#Value("${directory.path:/apps}")
public String directoryPath;
#Autowired
private ExampleUtil util;
private void monitorDirectoryForChanges() throws IOException, InterruptedException {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get(directoryPath);
path.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
util.handleDirectoryWatcherChange(event);
util.sayhellowithtimed("GOD_OF_SMALL_THINGS_onAPPEvent");
}
key.reset();
}
}
#Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
try {
monitorDirectoryForChanges();
} catch (Throwable e) {
System.err.println("ERROR!! "+e.getMessage());
e.printStackTrace();
}
}
}
The Spring Boot Application Class
package com.example;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#EnableAspectJAutoProxy
#ComponentScan
#Configuration
#SpringBootApplication
public class ExampleStarter{
#Bean
MeterRegistryCustomizer<PrometheusMeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("app.name", "example.app");
}
#Bean
TimedAspect timedAspect(MeterRegistry reg) {
return new TimedAspect(reg);
}
public static void main(String[] args) {
SpringApplication.run(ExampleStarter.class, args);
}
}
The main pom.xml file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.metrics.timed.example</groupId>
<artifactId>example-app</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
I use spring boot 2.2.6.RELEASE and this MetricConfig works for me
#Configuration
public class MetricConfig {
#Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("application", "my app");
}
#Bean
TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
In application.yml
management:
endpoints:
web:
exposure:
include: ["health", "prometheus"]
endpoint:
beans:
cache:
time-to-live: 10s
#Timed use AOP(Aspect oriented programming) concept, in which proxy doesn't pass on to the second level of the method.
you can define the second level of method in new bean/class. this way #Timed will work for second level of method call.
I had the same problem, in my case I realised that the metric got visible under actuator/metrics only after the method had been called at least once.
Unlike with manually created timers/counters, where they get visible directly after startup.
I'm doing my fist steps with Springs AOP and wanted to start with a simple logging advice. My project is a multi module maven project with the following structure:
parentProject
|__aop
|__data
|__web
The web module has a user service class in the package de.my.awsome.project.web.service with a saveNewUser Method:
#Service
public class UserService {
...
public MdUser saveNewUser(UserModel user) {
MdUser newUser = this.save(user);
createGroupMembership(user, newUser);
return newUser;
}
}
The method works as expected so don't bother with details about that.
What I've done now is to create the following class in the aop module:
#Component
#Aspect
public class LoggingAspect {
Logger logger = Logger.getLogger(getClass());
#Before("execution(public * de.my.awsome.project.web.service.UserService.saveNewUser(..))")
public void newUserLog(JoinPoint joinpoint) {
logger.info(joinpoint.getSignature() + " with user " + joinpoint.getArgs()[0]);
}
}
I added a dependency for the web module in the pom of the aop module:
<dependency>
<groupId>de.my.awsome.project</groupId>
<artifactId>web</artifactId>
<version>${project.version}</version>
</dependency>
I even wrote a ConfigurationClasse even though I thought this would not be necessary with SpringBoot:
#Configuration
#ComponentScan(basePackages="de.fraport.bvd.mobisl.aop")
public class AspectsConfig {
}
The expected result is a log-message like "saveNewUser with user xyz". But the logging method is never called. What have I missed to do?
#Configuration - Indicates that this file contains Spring Bean Configuration for an Aspect.
Replace #Component with #Configuration for LoggingAspect.
Well, the answer that #sankar posted didn't work either but I found the solution by myself.
I had to add a dependency to my aop module in the web modules pom, not vice versa. Then I added an Import of my AspectsConfig to the web modules SpringBootApplication class and it worked.
#SpringBootApplication
#Import(value= {JPAConfig.class, AspectsConfig.class})
#EnableAspectJAutoProxy
public class WebApplication {
#Autowired
private JPAConfig config;
#Autowired
private AspectsConfig aspectConfig;
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
The following steps work for me - see below if anyone is looking for a solution
add the aop dependency in to the main parent pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
add your Aspect implementation in he aop-sub-module
#Aspect
#Component
public class SampleAspect {
#After("execution(* de.my.awsome.project.testMethod())")
public void logAuditActivity(JoinPoint jp) {
System.out.println("TESTING ****************");
System.out.println("The method is called");
}
In your other submodule (web-submodule) add the dependancy of the aspect-submodule
<dependency>
<groupId>de.my.awsome.project</groupId>
<artifactId>aop</artifactId>
</dependency>
In your web Project include the aop package for component scan
eg. if SampleAspect is under a package de.my.awsome.project you will need to add in as follow
#ComponentScan(basePackages = {"de.my.awsome.project", "de.my.awsome.aop" }
clean build and run the app
I've been experimenting with Contract Testing using Spring Cloud Contract (SCC) and am now trying to use Pact in combination with SCC to serve as an intermediate step before going pure Pact.
On my consumer project I've specified a simple contract:
#RunWith(SpringRunner.class)
#SpringBootTest
public class AccountServicePactTest {
#Autowired
TransactionService transactionService;
#Rule
public PactProviderRuleMk2 mockProvider = new PactProviderRuleMk2("account-service", "localhost", 8081, this);
#Pact(consumer = "transaction-service")
public RequestResponsePact createPact(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json;charset=UTF-8");
return builder
.given("An account with UUID 8ea0f76b-b7a6-49eb-b25c-073b664d2de3 exists")
.uponReceiving("Request for an account by UUID")
.path("/api/accounts/8ea0f76b-b7a6-49eb-b25c-073b664d2de3")
.method("GET")
.willRespondWith()
.headers(headers)
.status(200)
.body("{\n" +
" \"accountUUID\": \"8ea0f76b-b7a6-49eb-b25c-073b664d2de3\",\n" +
" \"customerId\": 1,\n" +
" \"balance\": 0.00,\n" +
"}")
.toPact();
}
#Test
#PactVerification
public void runTest() {
AccountRetrievalRequest request = new AccountRetrievalRequest(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
AccountDTO accountDTO = transactionService.retrieveAccount(request);
assertThat(accountDTO.getAccountUUID()).isEqualTo(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
}
}
(I know that hardcoding the UUID, customerId and balance makes the test brittle, but this is just a simple test)
Using the pact-jvm-provider-maven_2.12 plugin (provider plugin on consumer side, confusing I know) the Pact file gets pushed to a Pact Broker:
<plugin>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.12</artifactId>
<version>3.5.22</version>
<configuration>
<pactBrokerUrl>http://localhost:8888</pactBrokerUrl>
<pactBrokerUsername></pactBrokerUsername>
<pactBrokerPassword></pactBrokerPassword>
<trimSnapshot>true</trimSnapshot>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>publish</goal>
</goals>
</execution>
</executions>
</plugin>
So far so good.
On the provider project I run into issues trying to verify the the contract. Again, I'm using SCC and Pact together. pom.xml plugins snippet:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<contractsRepositoryUrl>pact://http://localhost:8888</contractsRepositoryUrl>
<contractDependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>+</version>
</contractDependency>
<contractsMode>REMOTE</contractsMode>
<packageWithBaseClasses>com.abnamro.internship.bank.accountservice.pacts</packageWithBaseClasses>
</configuration>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<version>${spring-cloud-contract.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.12</artifactId>
<version>3.5.11</version>
<configuration>
<serviceProviders>
<serviceProvider>
<name>account-service</name>
<pactBrokerUrl>http://localhost:8888/</pactBrokerUrl>
</serviceProvider>
</serviceProviders>
</configuration>
</plugin>
SCC Verifier required Base Class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = AccountServiceApplication.class)
public abstract class Account_serviceContractsBase {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private AccountRepository accountRepository;
#Before
public void setup() {
Account account = new Account(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"), Integer.toUnsignedLong(1),
0.00d);
accountRepository.save(account);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
System.out.println(account.getAccountUUID());
}
#After
public void teardown() {}
}
SCC generated test:
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ContractsTest extends Account_serviceContractsBase {
#Test
public void validate_0_account_service_pact() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/api/accounts/8ea0f76b-b7a6-49eb-b25c-073b664d2de3");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).isEqualTo("application/json;charset=UTF-8");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['accountUUID']").isEqualTo("8ea0f76b-b7a6-49eb-b25c-073b664d2de3");
assertThatJson(parsedJson).field("['customerId']").isEqualTo(1);
assertThatJson(parsedJson).field("['balance']").isEqualTo(0.0);
}
}
Running the test gives the following exception:
java.lang.IllegalArgumentException: json string can not be null or empty
I can't figure out why the string is either empty or null. Running the application and using Postman to test the endpoint works just fine.
This is part of the Controller:
#RestController
#RequestMapping("/api")
public class AccountController {
...
#GetMapping("/accounts/{accountUUID}")
public ResponseEntity<Account> retrieveAccount(#PathVariable("accountUUID") UUID accountUUID) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return new ResponseEntity<>(accountService.retrieveAccount(accountUUID),
httpHeaders, HttpStatus.OK);
}
I'm missing something but I can't find what. Thoughts?
EDIT: The (generated) test passed when just using SCC.
Turned out my Base Class wasn't setup properly. Working Base Class which simply mocks the called service method and returns a test account:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = AccountServiceApplication.class)
public abstract class Account_serviceContractsBase {
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private AccountService accountService;
#Before
public void setup() {
Account account = new Account();
account.setAccountUUID(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"));
account.setCustomerId(1L);
account.setBalance(0.00d);
when(accountService.retrieveAccount(UUID.fromString("8ea0f76b-b7a6-49eb-b25c-073b664d2de3"))).thenReturn(account);
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
#After
public void teardown() {}
}
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..*)