Target Source for Springboot Source Analysis

Posted by luis_santos on Mon, 26 Aug 2019 15:43:29 +0200

Summary:

In fact, when I first saw this thing, I was also puzzled. The source of the proxy target is not just a class, but also encapsulation.

In fact, proxy agent is not target, but Target Source, which is very important, we must be clear!!!

Usually, a proxy object can only proxy one target, and the target of each method call is the only fixed target. However, if you let proxy proxy proxy proxy TargetSource, you can make the target instances of each method call different (and, of course, the same, depending on the TargetSource implementation). This mechanism makes method invocation flexible and extends many advanced functions, such as simplicity, prototype, local threads, target object pool, hot replacement of target source by runtime target object, and so on.

Spring's built-in Target Source

SingletonTargetSource
    public class SingletonTargetSource implements TargetSource, Serializable {
    
        /** Target cached and invoked using reflection. */
        private final Object target;
        //Eliminate irrelevant code...
        @Override
        public Object getTarget() {
            return this.target;
        }
        //Eliminate irrelevant code...
    }

The target object obtained from this target source is singleton. The member variable target caches the target object, and each getTarget() returns the object.

PrototypeTargetSource
    public class PrototypeTargetSource extends AbstractPrototypeBasedTargetSource {
    
       /**
        * Obtain a new prototype instance for every call.
        * @see #newPrototypeInstance()
        */
       @Override
       public Object getTarget() throws BeansException {
          return newPrototypeInstance();
       }
    
       /**
        * Destroy the given independent instance.
        * @see #destroyPrototypeInstance
        */
       @Override
       public void releaseTarget(Object target) {
          destroyPrototypeInstance(target);
       }
      //Eliminate irrelevant code...
    }

Each getTarget() generates a prototype type type type type bean, that is, the generated bean is not a singleton, so when using this type of TargetSource, it is important to note that the encapsulated target bean must be prototype type type type. Prototype Target Source inherits AbstractBean FactoryBasedTarget Source and has the ability to create beans.

    public abstract class AbstractPrototypeBasedTargetSource extends AbstractBeanFactoryBasedTargetSource {
    
       //Eliminate irrelevant code...
       /**
        * Subclasses should call this method to create a new prototype instance.
        * @throws BeansException if bean creation failed
        */
       protected Object newPrototypeInstance() throws BeansException {
          if (logger.isDebugEnabled()) {
             logger.debug("Creating new instance of bean '" + getTargetBeanName() + "'");
          }
          return getBeanFactory().getBean(getTargetBeanName());
       }
    
       /**
        * Subclasses should call this method to destroy an obsolete prototype instance.
        * @param target the bean instance to destroy
        */
       protected void destroyPrototypeInstance(Object target) {
          if (logger.isDebugEnabled()) {
             logger.debug("Destroying instance of bean '" + getTargetBeanName() + "'");
          }
          if (getBeanFactory() instanceof ConfigurableBeanFactory) {
             ((ConfigurableBeanFactory) getBeanFactory()).destroyBean(getTargetBeanName(), target);
          }
          else if (target instanceof DisposableBean) {
             try {
                ((DisposableBean) target).destroy();
             }
             catch (Throwable ex) {
                logger.warn("Destroy method on bean with name '" + getTargetBeanName() + "' threw an exception", ex);
             }
          }
       }
    
      //Eliminate irrelevant code...
    
    }

As you can see, Prototype Target Source generates prototype-type beans mainly by delegating them to BeanFactory, because BeanFactory has its own logic to generate prototype-type beans, so Prototype Target Source also has the ability to generate prototype-type beans, which is the target beans we want to generate. The reason for the prototype type type type must be declared.

ThreadLocalTargetSource
    public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource
          implements ThreadLocalTargetSourceStats, DisposableBean {
    
       /**
        * ThreadLocal holding the target associated with the current
        * thread. Unlike most ThreadLocals, which are static, this variable
        * is meant to be per thread per instance of the ThreadLocalTargetSource class.
        */
       private final ThreadLocal<Object> targetInThread =
             new NamedThreadLocal<>("Thread-local instance of bean '" + getTargetBeanName() + "'");
    
       /**
        * Set of managed targets, enabling us to keep track of the targets we've created.
        */
       private final Set<Object> targetSet = new HashSet<>();
    
     //Eliminate irrelevant code...
       /**
        * Implementation of abstract getTarget() method.
        * We look for a target held in a ThreadLocal. If we don't find one,
        * we create one and bind it to the thread. No synchronization is required.
        */
       @Override
       public Object getTarget() throws BeansException {
          ++this.invocationCount;
          Object target = this.targetInThread.get();
          if (target == null) {
             if (logger.isDebugEnabled()) {
                logger.debug("No target for prototype '" + getTargetBeanName() + "' bound to thread: " +
                      "creating one and binding it to thread '" + Thread.currentThread().getName() + "'");
             }
             // Associate target with ThreadLocal.
             target = newPrototypeInstance();
             this.targetInThread.set(target);
             synchronized (this.targetSet) {
                this.targetSet.add(target);
             }
          }
          else {
             ++this.hitCount;
          }
          return target;
       }
    
       /**
        * Dispose of targets if necessary; clear ThreadLocal.
        * @see #destroyPrototypeInstance
        */
       @Override
       public void destroy() {
          logger.debug("Destroying ThreadLocalTargetSource bindings");
          synchronized (this.targetSet) {
             for (Object target : this.targetSet) {
                destroyPrototypeInstance(target);
             }
             this.targetSet.clear();
          }
          // Clear ThreadLocal, just in case.
          this.targetInThread.remove();
       }
    //Eliminate irrelevant code...
    }

ThreadLocal Target Source is a Target Source bound to threads. Understandably, its underlying implementation must use ThreadLocal. Since ThreadLocal is used, we need to pay attention to two issues:

  • The target object must be declared as prototype because each thread will hold a different object.
  • The target object must be stateless, because the target object is bound to the current thread and Spring is the request processed by the thread pool, so each thread may process different requests, so in order to avoid problems, the target object must be stateless.
Implementing custom Target Source
    package com.github.dqqzj.springboot.target;
    
    import org.springframework.aop.TargetSource;
    import org.springframework.util.Assert;
    
    import java.lang.reflect.Array;
    import java.util.concurrent.ThreadLocalRandom;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author qinzhongjian
     * @date created in 2019-08-25 12:43
     * @description: TODO
     * @since JDK 1.8.0_212-b10z
     */
    public class DqqzjTargetSource implements TargetSource {
        private final AtomicInteger idx = new AtomicInteger();
        private final Object[] target;;
        public DqqzjTargetSource(Object[]  target) {
            Assert.notNull(target, "Target object must not be null");
            this.target = target;
        }
        @Override
        public Class<?> getTargetClass() {
            return target.getClass();
        }
    
        @Override
        public boolean isStatic() {
            return false;
        }
    
        @Override
        public Object getTarget() throws Exception {
            return this.target[this.idx.getAndIncrement() & this.target.length - 1];
        }
    
        @Override
        public void releaseTarget(Object target) throws Exception {
    
        }
    }

There are two main points to note in implementing a custom TargetSource. One is the getTarget() method, which needs to implement the logic of acquiring the target object. The other is the isStatic() method, which tells Spring whether it needs to cache the target object or not, and generally returns false in non-singular cases.

Summary
   In this paper, we first explain how Spring supports Target Source at the source level, then explain the principle of Target Source, then explain the common `Target Source'provided by Spring, and finally use a custom Target Source to explain its use.

Topics: Java Spring github SpringBoot