Spring: register advice programmatically at runtime - spring

Is it possible to register AOP advices programmatically, after the application has booted and the context has been initialized?
When I tried, the advices didn't work, supposedly because they need to wrap the bean BEFORE it gets available in the context.
Something like this (it doesn't work):
#Bean
private AspectJExpressionPointcutAdvisor createPointcutAdvisor(AWSXRayRecorder awsxRayRecorder, String name, String pointcut) {
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression("execution ...()");
advisor.setAdvice(new CustomAdvice("custom bean"));
return advisor;
}
Clarification: I need to read a list of advice from a config file, and register the pointcuts accordingly. I need the label for bookeeping purposes. The file contents are unknown at compile time.
label: execution(* com.my.ns.OtherClass(..))
label2: execution(* com.my.ns.Class(..))

The previous solution is too invasive as it not only creates advice on the fly but also handles advising beans. This replicates functionality of Spring's AbstractAdvisorAutoProxyCreator, specifically the getAdvicesAndAdvisorsForBean method, where Spring will locate and apply eligible Advisors to each bean. A better approach is to simply programmatically create Advisors and let Spring handle the rest of the plumbing of advising beans, creating proxies, and so forth.
A simple way of creating a Advisor is to create a Advisor bean using the #Bean annotation:
#Bean
public Advisor advisorBean() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.testit.MyAspectedService.*(..))");
return new DefaultPointcutAdvisor(pointcut, new MyMethodInterceptor());
}
Where the class MyMethodInterceptor implements the MethodInterceptor interface:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("in interceptor");
//get the method and arguments we are intercepting
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
//... do stuff before the method
//call the underlying method
return invocation.proceed();
}
}
What this does is to create a around advice Advisor bean named "advisorBean" for all methods calls to a Spring bean MyAspectedService declared as
#Service
public class MyAspectedService {
//various service methods
}
This approach focuses on only creating the necessary Advisors and interception implementation and delegates the weaving of the aspect to the Spring framework.

Maybe programmatic creation of #AspectJ Proxies according to the Spring AOP manual does what you want. Quoting from there because answers with external links only are frowned upon on SO:
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an #AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the
// object supplied must be an #AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
Update:
So actually I played around a bit, not being a Spring user but rather an AspectJ expert. But anyway I found a way to dynamically register an advisor with a custom pointcut. The thing is, though, you need to know which beans you want to apply it to, and be careful to differentiate between beans which are already proxied and ones which are not.
Question: When in your application lifecycle and to which beans do you want to add the advisors? Have your other beans already been instantiated and wired (injected) into others? I am asking because it is quite easy to register advisors to beans you have direct references to, wrapping them into proxies or adding the advisors to existing proxies. But there is no obvious way to wrap a bean which has already been injected into other beans and not proxied yet. So how easy or difficult the solution is depends on your requirements.
P.S.: I am still wondering why your pointcuts are in a properties file instead of just in a Spring XML config file, which would be the standard way. That XML file is also loaded during application start-up. Where does the requirement to use another file come from? Both are basically editable (text) resource files.
Update 2: Tedious manual solution, adapted from another sample project
Okay, I have created a GitHub repo for you. Just build with Maven and run the class with the main(..) method. It looks like this:
package de.scrum_master.performancemonitor;
import org.aopalliance.aop.Advice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class PerformanceApp {
public static DefaultPointcutAdvisor createAdvisor(String pointcutExpression, Advice advice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(pointcutExpression);
return new DefaultPointcutAdvisor(pointcut, advice);
}
public static Object adviseIfNecessary(Object bean, DefaultPointcutAdvisor advisor) {
final String pointcutExpression = advisor.getPointcut().toString().replaceAll(".*\\(\\) ", "");
if (!advisor.getPointcut().getClassFilter().matches(bean.getClass())) {
System.out.println("Pointcut " + pointcutExpression + " does not match class " + bean.getClass());
return bean;
}
System.out.println("Pointcut " + pointcutExpression + " matches class " + bean.getClass() + ", advising");
Advised advisedBean = createProxy(bean);
advisedBean.addAdvisor(advisor);
return advisedBean;
}
public static Advised createProxy(Object bean) {
if (bean instanceof Advised) {
System.out.println("Bean " + bean + " is already an advised proxy, doing nothing");
return (Advised) bean;
}
System.out.println("Creating proxy for bean " + bean);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(bean);
return (Advised) proxyFactory.getProxy();
}
public static void main(String[] args) {
DefaultPointcutAdvisor advisor = createAdvisor(
// Just load this from your YAML file as needed
"execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..))",
new MyPerformanceMonitorInterceptor(true)
);
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfiguration.class);
Person person = (Person) adviseIfNecessary(context.getBean("person"), advisor);
PersonService personService = (PersonService) adviseIfNecessary(context.getBean("personService"), advisor);
System.out.println();
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
System.out.println();
// BTW, you can also unadvise a bean like this.
// Write your own utility method for it if you need it.
((Advised) personService).removeAdvisor(advisor);
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
}
}
The console log looks like this:
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) does not match class class de.scrum_master.performancemonitor.Person
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) matches class class de.scrum_master.performancemonitor.PersonService$$EnhancerBySpringCGLIB$$965d1d14, advising
Bean de.scrum_master.performancemonitor.PersonService#2fd1433e is already an advised proxy, doing nothing
web - 2018-03-10 09:14:29,229 [main] TRACE d.s.performancemonitor.PersonService - StopWatch 'de.scrum_master.performancemonitor.PersonService.getFullName': running time (millis) = 2
Name: Albert Einstein
web - 2018-03-10 09:14:29,235 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution started at: Sat Mar 10 09:14:29 ICT 2018
web - 2018-03-10 09:14:29,332 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution lasted: 100 ms
web - 2018-03-10 09:14:29,332 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution ended at: Sat Mar 10 09:14:29 ICT 2018
web - 2018-03-10 09:14:29,332 [main] WARN d.s.performancemonitor.PersonService - Method execution longer than 10 ms!
Age: 146
web - 2018-03-10 09:14:29,334 [main] TRACE d.s.performancemonitor.PersonService - StopWatch 'de.scrum_master.performancemonitor.PersonService.getFullName': running time (millis) = 0
Name: Albert Einstein
Age: 146
You can nicely see how log output from the advisor gets printed. After detaching the advisor again, the log output goes away and only the log output from the advisor defined in class AopConfiguration remains. I.e. you can mix Spring configuration with your own dynamically attached advisors.
BTW, if you comment out the #Bean annotation in AopConfiguration like this
//#Bean
public Advisor performanceMonitorAdvisor() {
then class PersonService will not be proxied already by the time you attach your dynamic advisor and the console output changes to:
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) does not match class class de.scrum_master.performancemonitor.Person
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) matches class class de.scrum_master.performancemonitor.PersonService, advising
Creating proxy for bean de.scrum_master.performancemonitor.PersonService#6a03bcb1
Name: Albert Einstein
web - 2018-03-10 09:43:04,633 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution started at: Sat Mar 10 09:43:04 ICT 2018
web - 2018-03-10 09:43:04,764 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution lasted: 136 ms
web - 2018-03-10 09:43:04,769 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution ended at: Sat Mar 10 09:43:04 ICT 2018
web - 2018-03-10 09:43:04,769 [main] WARN d.s.performancemonitor.PersonService - Method execution longer than 10 ms!
Age: 146
Name: Albert Einstein
Age: 146
Please note that not only the log lines produces by the Spring-configured advisor go away as expected but that also the line
Bean de.scrum_master.performancemonitor.PersonService#2fd1433e is already an advised proxy, doing nothing
changes to
Creating proxy for bean de.scrum_master.performancemonitor.PersonService#6a03bcb1
Update 3: More elegant solution according to James W's answer
According to James W's answer, I have modified my solution in order to let Spring automatically create proxy, advisor and let it add the advisor, see commit #ff53e57. The credit for that goes completely to James! Like I said before, I am not a Spring user and was unaware of the handy base class MethodInterceptor which is key to this solution, like James suggested.
For reference, I kept my trick to unadvise the service manually on demand, just had to modify the code to get a reference to the advisor because now it is created by Spring. The updated main program looks like this:
public class PerformanceApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfiguration.class);
Person person = (Person) context.getBean("person");
PersonService personService = (PersonService) context.getBean("personService");
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
System.out.println();
System.out.println("Unadvising PersonService bean");
Arrays.stream(((Advised) personService).getAdvisors())
.filter(advisor -> advisor.getAdvice() instanceof MyPerformanceMonitorInterceptor)
.findFirst()
.ifPresent(((Advised) personService)::removeAdvisor);
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
}
}
It produces this output:
web - 2021-01-11 10:18:09,277 [main] INFO d.s.p.MyPerformanceMonitorInterceptor - Method public java.lang.String de.scrum_master.performancemonitor.PersonService.getFullName(de.scrum_master.performancemonitor.Person) execution started at: Mon Jan 11 10:18:09 ICT 2021
web - 2021-01-11 10:18:09,293 [main] INFO d.s.p.MyPerformanceMonitorInterceptor - Method public java.lang.String de.scrum_master.performancemonitor.PersonService.getFullName(de.scrum_master.performancemonitor.Person) execution lasted: 18 ms
web - 2021-01-11 10:18:09,293 [main] INFO d.s.p.MyPerformanceMonitorInterceptor - Method public java.lang.String de.scrum_master.performancemonitor.PersonService.getFullName(de.scrum_master.performancemonitor.Person) execution ended at: Mon Jan 11 10:18:09 ICT 2021
web - 2021-01-11 10:18:09,293 [main] WARN d.s.p.MyPerformanceMonitorInterceptor - Method execution longer than 10 ms!
Name: Albert Einstein
Age: 149
Unadvising PersonService bean
Name: Albert Einstein
Age: 149

Define a abstract Aop Class,like this:
public abstract class ControllerAop {
public abstract void webaop();
#Around(value = "webaop()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
...
}
}
in your project,you can define a child class extends above aop class.
import com.chuxingyouhui.pdsc.knc.aop.ControllerAop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
#Aspect
#Component
#Order(2)
public class ApiControllerAop extends ControllerAop {
#Override
#Pointcut(value = "execution(* com.chuxingyouhui.white..*.controller..*.*(..))")
public void webaop() {
}
}
if you have more pointcut path,you define more child class .

Related

error is prompted when I use #target in spring aop

I find the same question in there but didn`t find a useful answer, so I support more details. My code is the following.
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DS {
String value();
}
public class AnnotationAspect {
#Around("#target(com.yh.application.DS)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsName = getDataSourceAnnotation(joinPoint).value();
System.out.println("enter in aspect:" + dsName);
return joinPoint.proceed();
}
here is a demo,
just run the application you can see the error stack trace
Unable to proxy interface-implementing method
[public final void org.springframework.boot.web.servlet.RegistrationBean.onStartup
(javax.servlet.ServletContext) throws javax.servlet.ServletException]
because it is marked as final: Consider using interface-based JDK proxies instead!
seems I need to change the aop proxy type to JDK, but when I did this, another error is prompted.
The bean 'dispatcherServlet' could not be injected as a 'org.springframework.web.servlet.DispatcherServlet' because it is a JDK dynamic proxy
Does anyone help me? thank you!
R.G's solution is correct, you ought to limit the pointcut scope. BTW, looking at your aspect code, I noticed this contrived way of getting the annotation value:
private DS getDataSourceAnnotation(ProceedingJoinPoint joinPoint) {
Class<?> targetClass = joinPoint.getTarget().getClass();
DS dsAnnotation = targetClass.getAnnotation(DS.class);
if (Objects.nonNull(dsAnnotation)) {
return dsAnnotation;
}
else {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(DS.class);
}
}
I suggest you just bind the annotation to an advice method parameter like this:
package com.yh.application;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class AnnotationAspect {
#Before("#target(ds) && within(com.yh..*)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}
Update:
I forgot to explain why you were getting the error in the first place: Pointcuts like this(), target(), #this(), #target() can only be determined dynamically during runtime because they access active object instances. Hence, all possible Spring components (also internal ones) are being aspect-woven, which is also the reason why the workaround to limit the aspect scope by using statically evaluated pointcut designators like within() help you avoid the problem.
But actually, using a statically evaluated pointcut designator in the first place, if it is a viable alternative, is the best idea. It is also faster than weaving the world, creating dozens or hundreds of proxies, and then to dynamically evaluate pointcuts over and over again. Luckily, in this case such an alternative exists: #within().
#Aspect
#Component
public class AnnotationAspect {
#Before("#within(ds)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}

spring cloud contract verification at deployment

I have extensively gone through SpringCloudContract. It is very effective TDD. I want to verify the contract during actual deployment. I have n number of micro-services (Spring stream:Source/Processor/Sink) and want to allow user to link them when they define a stream (kafka)in dataflow server dashboard. I am passing certain Object in the stream which act as
input/out for micro-service. I want to check the compatibility for micro-services and warn the user accordingly. SpringCloudContract facilitate to verify the contract during the develpment time and not a run time.
Kindly help.
I am new to Spring cloud contract, but I have found a way to start StubRunner but when it trigger the certificate I get following.
2017-04-26 16:14:10,373 INFO main c.s.s.ContractTester:36 - ContractTester : consumerMessageListener >>>>>>>>>>>>>>>>>>>>>>>>>>>>org.springframework.cloud.contract.stubrunner.BatchStubRunner#5e13f156
2017-04-26 16:14:10,503 ERROR main o.s.c.c.v.m.s.StreamStubMessages:63 - Exception occurred while trying to send a message [GenericMessage [payload={"name":"First","description":"Valid","value":1}, headers={id=49c6cc5c-93c8-2498-934a-175f60f42c03, timestamp=1493203450482}]] to a channel with name [verifications]
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.input'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload={"name":"First","description":"Valid","value":1}, headers={id=49c6cc5c-93c8-2498-934a-175f60f42c03, timestamp=1493203450482}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:93)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373)
at org.springframework.cloud.contract.verifier.messaging.stream.StreamStubMessages.send(StreamStubMessages.java:60)
at org.springframework.cloud.contract.verifier.messaging.stream.StreamStubMessages.send(StreamStubMessages.java:
The same work fine with Maven install, but not with main class.
...
#RunWith(SpringRunner.class)
#AutoConfigureMessageVerifier
#EnableAutoConfiguration
#EnableIntegration
#Component
#DirtiesContext
public class ContractTester {
private static Logger logger = LoggerFactory.getLogger(ContractTester.class);
#Autowired StubTrigger stubTrigger;
#Autowired ConsumerMessageListener consumerMessageListener;
#Bean
public boolean validSimpleObject() throws Exception {
logger.info("ContractTester : consumerMessageListener >>>>>>>>>>>>>>>>>>>>>>>>>>>>"+stubTrigger);
stubTrigger.trigger("accepted_message");
if(consumerMessageListener ==null) {
logger.info("ContractTester : consumerMessageListener >>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
logger.info("ContractTester >>>>>>>>>>>>>>>>>>>>>>>>>>>>" +consumerMessageListener.toString());
SimpleObject simpleObject = (SimpleObject) consumerMessageListener.getSimpleObject();
logger.info("simpleObject >>>>>>>>>>>>>>>>>>>>>>>>>>>>" +simpleObject.toString());
assertEquals(1, simpleObject.getValue());
//then(listener.eligibleCounter.get()).isGreaterThan(initialCounter);
return true;
}
}

Correct use of Hazelcast Transactional Map in an Spring Boot app

I am working on a proof of concept of Hazelcast Transactional Map. To accomplish this I am writing an Spring Boot app and using Atomikos as my JTA/XA implementation.
This app must update a transactional map and also update a database table by inserting a new row all within the same transaction.
I am using JPA / SpringData / Hibernate to work with the database.
So the app have a component (a JAVA class annotated with #Component) that have a method called agregar() (add in spanish). This method is annotated with #Transactional (org.springframework.transaction.annotation.Transactional)
The method must performe two task as a unit: first must update a TransactionalMap retrieved from Hazelcast instance and, second, must update a database table using a repository extended from JpaRepository (org.springframework.data.jpa.repository.JpaRepository)
This is the code I have written:
#Transactional
public void agregar() throws NotSupportedException, SystemException, IllegalStateException, RollbackException, SecurityException, HeuristicMixedException, HeuristicRollbackException, SQLException {
logger.info("AGRENADO AL MAPA ...");
HazelcastXAResource xaResource = hazelcastInstance.getXAResource();
UserTransactionManager tm = new UserTransactionManager();
tm.begin();
Transaction transaction = tm.getTransaction();
transaction.enlistResource(xaResource);
TransactionContext context = xaResource.getTransactionContext();
TransactionalMap<TaskKey, TaskQueue> mapTareasDiferidas = context.getMap("TAREAS-DIFERIDAS");
TaskKey taskKey = new TaskKey(1L);
TaskQueue taskQueue = mapTareasDiferidas.get(taskKey);
Integer numero = 4;
Task<Integer> taskFactorial = new TaskImplFactorial(numero);
taskQueue = new TaskQueue();
taskQueue.getQueue().add(taskFactorial);
mapTareasDiferidas.put(taskKey, taskQueue);
transaction.delistResource(xaResource, XAResource.TMSUCCESS);
tm.commit();
logger.info("AGRENADO A LA TABLA ...");
PaisEntity paisEntity = new PaisEntity(100, "ARGENTINA", 10);
paisRepository.save(paisEntity);
}
This code is working: if one of the tasks throw an exception then both are rolled back.
My questions are:
Is this code actually correct?
Why #Transactional is not taking care of commiting the changes in the map and I must explicitylly do it on my own?
The complete code of the project is available en Github: https://github.com/diegocairone/hazelcast-maps-poc
Thanks in advance
Finally i realized that i must inject the 'UserTransactionManager' object and take the transaction from it.
Also is necessary to use a JTA/XA implementation. I have chosen Atomikos and XA transactions must be enable in MS SQL Server.
The working example is available at Github https://github.com/diegocairone/hazelcast-maps-poc on branch atomikos-datasource-mssql
Starting with Hazelcast 3.7, you can get rid of the boilerplate code to begin, commit or rollback transactions by using HazelcastTransactionManager which is a PlatformTransactionManager implementation to be used with Spring Transaction API.
You can find example here.
Also, Hazelcast can participate in XA transaction with Atomikos. Here's a doc
Thank you
I have updated to Hazelcast 3.7.5 and added the following code to HazelcastConfig class.
#Configuration
public class HazelcastConfig {
...
#Bean
public HazelcastInstance getHazelcastInstance() {
....
}
#Bean
public HazelcastTransactionManager getTransactionManager() {
HazelcastTransactionManager transactionManager = new HazelcastTransactionManager(getHazelcastInstance());
return transactionManager;
}
#Bean
public ManagedTransactionalTaskContext getTransactionalContext() {
ManagedTransactionalTaskContext transactionalContext = new ManagedTransactionalTaskContext(getTransactionManager());
return transactionalContext;
}
When I run the app I get this exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'transactionManager' available: No matching
PlatformTransactionManager bean found for qualifier
'transactionManager' - neither qualifier match nor bean name match!
The code is available at Github on a new branch: atomikos-datasource-mssql-hz37
Thanks in advance

logging at package level using spring AOP

I am trying to implement logging and exception framework for my application using spring AOP. I have defined beans for classes in ApplicationContext and using interceptors to log entry, exit and exceptions if any.
My query is : Do I need to create bean for every class in my ApplicationContext or is it possible to generalise this at a package level. If I have 30 classes in my package, I should create 30 beans in my AppContext, rather I would prefer doing it at a package level if it is possible. Kindly help me in this regard.
You need to use #Around advice. Here define the pointcut such that each class and method of the package are covered / intercepted. See sample below -
#Aspect
public class AllMethodLogging {
#Around(execution("* com.x.y..*.*(..)"))
public Object intercept(ProceddingJoinPoint pjp){
String name = pjp.getSignature().getName();
try {
logger.info("started " + name);
Object obj = pjp.proceed();
logger.info("finished " + name + " successfully");
return obj;
} catch (Throwable t) {
logger.error(name + " finished with Exception - " + t.getMessage());
throw t;
} finally {
// do some more logging if required else skip
}
}
}
The pointcut execution("* com.x.y..*.*(..)") means that any method within com.x.y package, and its sub-package, with all visibility should be intercepted by this advice.
See docs here for more examples to customize your pointcut as per your needs.

Can I programmatically add a qualifier to a bean?

I am registering transaction managers in my code, I would normally use annotation based configuration but as I don't know until runtime how many data sources (and hence transaction managers) there will be, I have to programmatically register these, as follows:
private final void registerTransactionManagerBean(final DataSource dataSource, ConfigurableApplicationContext context) {
String transactionManagerName = this.getName() + "-transactionManager";
context.getBeanFactory().registerSingleton(transactionManagerName, new DataSourceTransactionManager(dataSource));
LOG.info("Registering transaction manager under name : " + transactionManagerName);
}
Assuming this.getName() returned 'mydb', I originally expected to be able to qualify a transaction manager like this:
#Transactional("mydb-transactionManager")
What I've realised however is the value of that annotation refers to the qualifier and not the name. I did a quick test by declaring a bean as below and it works:
#Bean
#Qualifier("mydb-transactionManager")
public PlatformTransactionManager test() {
return new DataSourceTransactionManager(new EmbeddedDatabaseBuilder().build());
}
My question is, is there a way I can programmatically add a qualifier when registering a bean?
UPDATE
I've worked this out, I'm falling foul of this problem (in BeanFactoryAnnotationUtils:isQualifierMatch):
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programmatically register transaction managers as a runtime thing sadly.
I've worked this out, I'm falling foul of this problem:
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programatically register transaction managers as a runtime thing sadly.
Raised as a JIRA issue - https://jira.spring.io/browse/SPR-11915
public class RuntimeRegistrationWithQualifierTest {
private AnnotationConfigApplicationContext context;
#Test
public void beanWithQualifier() {
final GenericBeanDefinition helloBeanDefinition = new GenericBeanDefinition();
helloBeanDefinition.addQualifier(new AutowireCandidateQualifier(Hello.class));
final GenericBeanDefinition worldBeanDefinition = new GenericBeanDefinition();
worldBeanDefinition.addQualifier(new AutowireCandidateQualifier(World.class));
final DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();
factory.registerBeanDefinition("helloBean", helloBeanDefinition);
factory.registerSingleton("helloBean", "hello");
factory.registerBeanDefinition("worldBean", worldBeanDefinition);
factory.registerSingleton("worldBean", "world");
context.register(Foo.class);
context.refresh();
final Foo foo = context.getBean(Foo.class);
assertThat(foo.hello).isEqualTo("hello");
assertThat(foo.world).isEqualTo("world");
}
#Before
public void newContext() {
context = new AnnotationConfigApplicationContext();
}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface Hello {}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface World {}
static class Foo {
final String hello;
final String world;
Foo(#Hello final String hello, #World final String world) {
this.hello = hello;
this.world = world;
}
}
}

Resources