Skip to main content

Weaving in Spring AOP 2

Please refer to spring-framework under org.springframework.mylearntest package for code related to this article (from official source spring-test module).

Accelerating the Weaving Automation Process

Spring AOP automatic proxy implementation is built on concept of BeanPostProcessor in IoC container, using a BeanPostProcessor, and then implementing such logic inside BeanPostProcessor: when object is instantiated, generate proxy object for it and return, instead of instantiated target object itself, thus achieving purpose of automatic proxy.

for(bean in IoC container){
// Check if current bean definition satisfies interception condition, intercept if yes
if(isAssistentStatement){
Object proxy = createProxyFor(bean);
return proxy;
} else {
Object instance = createInstance(bean);
return instance;
}
}

Interception conditions

  • Pass in these interception condition information via external configuration files, for example Pointcut and Advisor etc. we registered in container configuration file include these information;

  • Can also make known specific interception conditions via metadata in definition file of specific class, for example via Jakarta Commons Attributes or Java 5 annotations, directly mark Pointcut and other interception information in code class.

AutoProxy Classes Available in Spring

Spring AOP provides two commonly used AutoProxyCreator under org.springframework.aop.framework.autoproxy package, namely BeanNameAutoProxyCreator and DefaultAdvisorAutoProxyCreator.

BeanNameAutoProxyCreator

Using BeanNameAutoProxyCreator allows applying a specified set of interceptors to these target objects by specifying BeanName corresponding to a set of target objects in container.

XML Configuration Case
<beans>
<bean id="target1" class="..."/>
<bean id="target2" class="..."/>

<bean id="mockTask" class="..."/>
<bean id="fakeTask" class="..."/>

<bean id="taskThrowsAdvice" class="...TaskThrowsAdvice"/>
<bean id="performanceInterceptor" class="...PerformanceInterceptor">

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!--Specify which beans to automatically generate proxy objects for-->
<property name="beanNames">
<list>
<value>target1</value>
<value>target2</value>
</list>
</property>

<!--Specify interceptors, Advice or Advisor etc. to be applied to target objects-->
<property name="interceptorNames">
<list>
<value>taskThrowsAdvice</value>
</list>
</property>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<!--Use * for wildcard-->
<list>
<value>mockTask*</value>
<value>fakeTask*</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>performanceInterceptor</value>
</list>
</property>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<!--For * wildcard case, can also use comma to separate-->
<list>
<value>target*,*Task,*service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>performanceInterceptor</value>
</list>
</property>
</bean>
</beans>

DefaultAdvisorAutoProxyCreator

Just need to register Bean in ApplicationContext, remaining tasks will be completed by DefaultAdvisorAutoProxyCreator. After injecting it into container, it will automatically search for all Advisor in container, then generate corresponding proxy objects for target objects in container that meet conditions according to interception information provided by each Advisor.

DefaultAdvisorAutoProxyCreator is only effective for Advisor, because only Advisor has both Pointcut information to capture target objects meeting conditions, and corresponding Advice.

XML Configuration Case
<beans>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!--Set object to use class-based proxy-->
<property name="proxyTargetClass">
<value>true</value>
</property>
</bean>

<bean id="target1" class="..."/>
<bean id="target2" class="..."/>

<bean id="mockTask" class="..."/>
<bean id="fakeTask" class="..."/>

<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
...
</property>
<property name="advice">
<bean id="performanceInterceptor"
class="org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor"></bean>
</property>
</bean>

<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
...
</property>
<property name="advice">
<bean id="taskThrowsAdvice" class="...TaskThrowsAdvice"></bean>

</property>
</bean>
</beans>

Extending AutoProxyCreator

Can implement corresponding subclasses based on AbstractAutoProxyCreator or AbstractAdvisorAutoProxyCreator provided by Spring AOP.

Implementation architecture regarding automatic proxy in Spring AOP framework

  • All AutoProxyCreator are InstantiationAwareBeanPostProcessor, this type of BeanPostProcessor is different from ordinary BeanPostProcessor. When Spring IoC container detects BeanPostProcessor of InstantiationAwareBeanPostProcessor type, it will directly construct object instance via logic in InstantiationAwareBeanPostProcessor and return, instead of going through normal object instantiation flow. I.e. "Short circuit". This way AutoProxyCreator will directly construct proxy object of target object and return, instead of original target object.

AspectJAwareAdvisorAutoProxyCreator is AutoProxyCreator implementation after Spring 2.0, also considered a custom implementation of AutoProxyCreator. It also has a subclass AnnotationAwareAspectJAutoProxyCreator, which can capture information based on Java 5 annotations to complete automatic proxy.

Spring AOP also supports automatic proxy mechanism based on Jakarta Commons Attributes metadata, to provide interception information.

Role of TargetSource

TargetSource is container of target object, when each method call to target object reaches end of call chain after layers of interceptions, it is time to call method defined on target object, at this time it does not directly call method on this target object, but interacts with actual target object via a certain TargetSource, regarding then call corresponding method on target object obtained from TargetSource.

Features of TargetSource

Every method call will trigger getTarget() method of TargetSource, getTarget() method will get specific target object from corresponding TargetSource implementation class, via this, we can control specific object instance that each method call acts on.

  • Provide a target object pool, target object obtained from TargetSource every time is obtained from this target object pool.
  • Let a TargetSource implementation class hold instances of multiple target objects, then return corresponding target object instance during each method call according to certain rules.

Can also let TargetSource hold only one target object, usually ProxyFactory or ProyxFactoryBean processes target object in this way too, they will construct a org.springframework.aop.target.SingletonTargetSource instance internally, and SingletonTargetSource will return instance reference of same target object for every method call.

TargetSource Implementation Classes

SingletonTargetSource

org.springframework.aop.target.SingletonTargetSource is most used TargetSource implementation class, although we may not know. Because after setting target object via setTarget() of ProxyFactory, ProxyFactory internal will automatically use a SingletonTargetSource to encapsulate set target object.

PrototypeTargetSource

PrototypeTargetSource Usage
<beans>
<bean id="target" class="org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask"
scope="prototype"/>

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName">
<value>target</value>
</property>
</bean>

<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource">
<ref bean="prototypeTargetSource"/>
</property>
<property name="interceptorNames">
<list>
<value>anyInterceptor</value>
</list>
</property>
</bean>
</beans>

Bean definition declaration of target object must be prototype. Specify bean definition name of target object via targetBeanName attribute, instead of reference.

HotSwappableTargetSource

Using HotSwappableTargetSource to encapsulate target object allows us to dynamically replace concrete implementation of target object class according to certain specific conditions while application is running, for example, IService has multiple implementation classes, if default IService implementation class has problem after program starts, we can immediately switch to another implementation of Iservice, and all these are transparent to caller.

xml configuration
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="task" class="org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask">

</bean>

<bean id="hotSwapTargetSource" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg>
<ref bean="task"/>
</constructor-arg>
</bean>

<bean id="taskProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="hotSwapTargetSource"/>
<property name="interceptorNames">
<list>
<value>performanceMethodInterceptor</value>
</list>
</property>
</bean>

<bean id="performanceMethodInterceptor"
class="org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor"/>

</beans>
Test4HotSwappableTargetSource
package org.springframework.mylearntest.aop.weaver.hotswaptargetsource;

import org.junit.Assert;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mylearntest.aop.weaver.baseoninterface.ITask;

import java.util.Date;

/**
* @Author: whalefall
* @Date: 2020/7/26 19:47
*/
public class Test4HotSwappableTargetSource {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("hotswappabletargetsource\\hotSwappableTargetSource.xml");
Object proxy = context.getBean("taskProxy");
Object initTarget = ((Advised)proxy).getTargetSource().getTarget();

HotSwappableTargetSource hotSwappableTargetSource = (HotSwappableTargetSource)context.getBean(
"hotSwapTargetSource");
Object oldTarget = hotSwappableTargetSource.swap(new ITask() {
@Override
public void execute(Date date) {
System.out.println("old target generated by hotSwapTargetSource");
}
});

Object newTarget = ((Advised)proxy).getTargetSource().getTarget();

// initTarget = org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask@72967906
// oldTarget = org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask@72967906
// newTarget = org.springframework.mylearntest.aop.weaver.hotswaptargetsource
// .Test4HotSwappableTargetSource$1@5b8dfcc1

Assert.assertSame(initTarget, oldTarget);
Assert.assertNotSame(initTarget, newTarget);
}
}

CommonsPoolTargetSource

Sometimes, we may want to return limited number of target object instances, these target object instances are equal in status, just like those Connection in database connection pool, we can provide a target object pool, then let a certain TargetSource implementation get target object from this object pool every time.

If Jakarta Commons Pool cannot be used, then can also implement corresponding TargetSource providing object pooling function by extending org.springframework.aop.target.AbstractPoolingTargetSource class.

ThreadLocalTargetSource

If want to provide different target objects for calls of different threads, then can use org.springframework.aop.target.ThreadLocalTargetSource. It can ensure calls to target object on respective threads can be assigned to instance of that target object corresponding to current thread. Actually, ThreadLocalTargetSource is just simple encapsulation of JDK standard ThreadLocal.

Custom TargetSource

AlternativeTargetSource
package org.springframework.mylearntest.aop.weaver.selfdefinetargetsource;

import org.springframework.aop.TargetSource;
import org.springframework.mylearntest.aop.weaver.baseoninterface.ITask;

/**
* @Author: whalefall
* @Date: 2020/7/27 22:27
*/
@SuppressWarnings("rawtypes")
public class AlternativeTargetSource implements TargetSource {
private ITask alternativeTask1;
private ITask alternativeTask2;

private int counter;

public AlternativeTargetSource(ITask task1, ITask task2) {
this.alternativeTask1 = task1;
this.alternativeTask2 = task2;
}

@Override
public Object getTarget() throws Exception {
try {
if (counter % 2 == 0)
return alternativeTask2;
else
return alternativeTask1;
} finally {
counter ++;
}
}

@Override
public Class getTargetClass() {
return ITask.class;
}

@Override
public boolean isStatic() {
return false;
}

@Override
public void releaseTarget(Object arg0) throws Exception {

}
}
Test4AlternativeTargetSource
package org.springframework.mylearntest.aop.weaver.selfdefinetargetsource;

import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.mylearntest.aop.weaver.baseoninterface.ITask;

import java.util.Date;

/**
* @Author: whalefall
* @Date: 2020/7/27 22:33
*/
public class Test4AlternativeTargetSource {
public static void main(String[] args) {
ITask task1 = new ITask() {
@Override
public void execute(Date date) {
System.out.println("execute in Task1");
}
};

ITask task2 = new ITask() {
@Override
public void execute(Date date) {
System.out.println("execute in Task2");
}
};

ProxyFactory pf = new ProxyFactory();
TargetSource targetSource = new AlternativeTargetSource(task1, task2);
pf.setTargetSource(targetSource);
Object proxy = pf.getProxy();
for (int i = 0; i < 100; i++) {
((ITask)proxy).execute(new Date());
}
}
}

References

  1. Book Name: Spring Unveiled Author: Wang Fuqiang
Agreement
The code part of this work is licensed under Apache License 2.0 . You may freely modify and redistribute the code, and use it for commercial purposes, provided that you comply with the license. However, you are required to:
  • Attribution: Retain the original author's signature and code source information in the original and derivative code.
  • Preserve License: Retain the Apache 2.0 license file in the original and derivative code.
The documentation part of this work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . You may freely share, including copying and distributing this work in any medium or format, and freely adapt, remix, transform, and build upon the material. However, you are required to:
  • Attribution: Give appropriate credit, provide a link to the license, and indicate if changes were made.
  • NonCommercial: You may not use the material for commercial purposes. For commercial use, please contact the author.
  • ShareAlike: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.