Today we are going to think about this question: In Spring AOP, is the proxied object a singleton? Every time we get a proxy object, will we get a new proxy object? Or is the proxied object always the same?
Why should we think about this issue? Because this knowledge point is involved in the advanced usage of @Scope annotation that Song Ge will talk about next.
Contents
1. Problem presentation
Let’s say I have a calculator interface like this:
public interface ICalculator { void add ( int a, int b) ; int minus ( int a, int b) ; }
Then provide an implementation class for this interface:
public class CalculatorImpl implements ICalculator { @Override public void add ( int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); } @Override public int minus ( int a, int b) { return a - b; } }
Now suppose I want to generate a proxy object programmatically, the code is as follows:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvice( new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name+ "The method has started to be executed..." ); Object proceed = invocation.proceed(); System.out.println(name+ "Method execution has ended..." ); return proceed; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add( 3 , 4 );
Here are several methods that should be easy to understand:
- The setTarget method is to set the real proxy object. Why can this @Lazy annotation before us break the infinite loop? Everyone has already come across it in this article.
- addInterface, a dynamic proxy based on JDK requires an interface. This method is to set the interface of the proxy object.
- The addAdvice method is to add enhancements/advices.
- Finally, a proxy object is obtained through the getProxy method and then executed.
This is a simple AOP case.
Now our problem lies with the setTarget method.
Let’s click in to the setTarget method to see what this method does:
public void setTarget (Object target) { setTargetSource( new SingletonTargetSource (target)); }
As you can see, the setTarget method calls the setTargetSource method internally. This method sets a SingletonTargetSource as the targetSource. As can be seen from the name, this SingletonTargetSource is a singleton targetSource.
Therefore, for the above code, we can infer that the same proxy object is held in multiple different proxy objects, such as the following code:
ProxyFactory proxyFactory = new ProxyFactory (); proxyFactory.setTarget( new CalculatorImpl ()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvice( new MethodInterceptor () { @Override public Object invoke (MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String name = method.getName(); System.out.println(name+ "The method has started to be executed..." ); Object proceed = invocation.proceed(); System.out.println(name+ "Method execution has ended..." ); return proceed; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); ICalculator calculator2 = (ICalculator) proxyFactory.getProxy(); calculator2.add( 2 , 3 );
We obtained two proxy objects, calculator and calculator2 respectively, but in fact, these two proxy objects hold the same proxy object, as shown below:
As can be seen from this picture, the proxy objects are not the same, but the proxy objects are actually the same.
2. TargetSource
In Spring AOP, the interface for handling proxy objects is TargetSource. TargetSource has many implementation classes, and different implementation classes have different capabilities:
The characteristics of many implementation classes can be seen simply from their names.
Let’s take a look at the TargetSource interface first:
public interface TargetSource extends TargetClassAware { @Override @Nullable Class<?> getTargetClass(); boolean isStatic () ; @Nullable Object getTarget () throws Exception; void releaseTarget (Object target) throws Exception; }
This interface has a total of four methods:
- getTargetClass: This is the type of the proxy object returned.
- isStatic: This method determines whether the proxied object is unchanged. It can also be understood as returning whether the proxied object is a singleton. However, this method does not control the implementation of the singleton. The significance of this method is that if the method returns true , indicating that the proxied object is a singleton, then there is no need to call the releaseTarget method to release the object in the future. On the contrary, if this method returns false, it means that the proxied object is not a singleton, then you need to finish using the proxied object. After that, call the releaseTarget method to release it.
- getTarget: This method returns the proxy object.
- releaseTarget: Release the proxied object.
There are many implementation classes of TargetSource. Let’s take a look at a few typical implementation classes.
2.1 SingletonTargetSource
Let’s first look at the definition of this class:
public class SingletonTargetSource implements TargetSource , Serializable { @SuppressWarnings("serial") private final Object target; public SingletonTargetSource (Object target) { Assert.notNull(target, "Target object must not be null" ); this .target = target; } @Override public Class<?> getTargetClass() { return this .target.getClass(); } @Override public Object getTarget () { return this .target; } @Override public void releaseTarget (Object target) { // nothing to do } @Override public boolean isStatic () { return true ; } }
If the proxied object is a singleton, then we will choose to use SingletonTargetSource. The proxied object is always called in the getTarget method. However, this method always returns the same object, so the final proxied object is a singleton. Example.
At the same time, since the proxy object is a singleton, the isStatic method returns true, and no additional operations are required in releaseTarget.
2.2 SimpleBeanTargetSource
SimpleBeanTargetSource is more typical. Whenever needed, it searches for the corresponding proxied Bean in the Spring container. As for whether the proxied Bean is a singleton, it is controlled by the Spring container:
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource { @Override public Object getTarget () throws Exception { return getBeanFactory().getBean(getTargetBeanName()); } } public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSource , BeanFactoryAware, Serializable { @Nullable private String targetBeanName; @Nullable private volatile Class<?> targetClass; @Nullable private BeanFactory beanFactory; public void setTargetBeanName (String targetBeanName) { this .targetBeanName = targetBeanName; } public String getTargetBeanName () { Assert.state( this .targetBeanName != null , "Target bean name not set" ); return this .targetBeanName; } public void setTargetClass (Class<?> targetClass) { this .targetClass = targetClass; } @Override public void setBeanFactory (BeanFactory beanFactory) { this .beanFactory = beanFactory; } public BeanFactory getBeanFactory () { Assert.state( this .beanFactory != null , "BeanFactory not set" ); return this .beanFactory; } @Override @Nullable public Class<?> getTargetClass() { Class<?> targetClass = this .targetClass; if (targetClass != null ) { return targetClass; } synchronized ( this ) { targetClass = this .targetClass; if (targetClass == null && this .beanFactory != null && this .targetBeanName != null ) { targetClass = this .beanFactory.getType( this .targetBeanName); if (targetClass == null ) { Object beanInstance = this .beanFactory.getBean( this .targetBeanName); targetClass = beanInstance.getClass(); } this .targetClass = targetClass; } return targetClass; } } @Override public boolean isStatic () { return false ; } @Override public void releaseTarget (Object target) throws Exception { // Nothing to do here. } }
As you can see from the source code above, when using SimpleBeanTargetSource, you need to pass in the targetBeanName, which is the name of the bean being proxied, and you also need to pass in the Spring container BeanFactory, so that every time you need to proxy the object When calling the getTarget method, the target Bean is directly queried from the container. Therefore, whether the proxied object is a singleton depends on whether the object returned by the Spring container is a singleton!
Friends, please remember the characteristics of SimpleBeanTargetSource, because in the next article, Brother Song will talk to you about the advanced usage of @Scope annotation, which involves this point.
2.3 LazyInitTargetSource
LazyInitTargetSource is somewhat similar to SimpleBeanTargetSource. It also searches for proxied beans from the Spring container. The difference is that LazyInitTargetSource has the ability to delay initialization, that is, it will only obtain the proxied object when it is called for the first time:
public class LazyInitTargetSource extends AbstractBeanFactoryBasedTargetSource { @Nullable private Object target; @Override public synchronized Object getTarget () throws BeansException { if ( this .target == null ) { this .target = getBeanFactory().getBean(getTargetBeanName()); postProcessTargetObject( this .target); } return this .target; } protected void postProcessTargetObject (Object targetObject) { } }
Okay, I won’t talk about the other classes one by one. Interested friends can check it out by themselves. The source code of this piece is relatively easy to understand~