Does the proxied object in Spring AOP have to be a singleton?

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.

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:

  1. 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.
  2. addInterface, a dynamic proxy based on JDK requires an interface. This method is to set the interface of the proxy object.
  3. The addAdvice method is to add enhancements/advices.
  4. 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:

  1. getTargetClass: This is the type of the proxy object returned.
  2. 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.
  3. getTarget: This method returns the proxy object.
  4. 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~