Springboot源碼分析之TargetSource

  • 2019 年 10 月 3 日
  • 筆記

摘要:

其實我第一次看見這個東西的時候也是不解,代理目標源不就是一個class嘛還需要封裝幹嘛。。。

其實proxy代理的不是target,而是TargetSource,這點非常重要,一定要分清楚!!!

通常情況下,一個代理對象只能代理一個target,每次方法調用的目標也是唯一固定的target。但是,如果讓proxy代理TargetSource,可以使得每次方法調用的target實例都不同(當然也可以相同,這取決於TargetSource實現)。這種機制使得方法調用變得靈活,可以擴展出很多高級功能,如:單利,原型,本地執行緒,目標對象池、運行時目標對象熱替換目標源等等。

file

Spring內置的TargetSource

SingletonTargetSource
    public class SingletonTargetSource implements TargetSource, Serializable {            /** Target cached and invoked using reflection. */          private final Object target;          //省略無關程式碼......          @Override          public Object getTarget() {              return this.target;          }          //省略無關程式碼......      }

從這個目標源取得的目標對象是單例的,成員變數target快取了目標對象,每次getTarget()都是返回這個對象。

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);         }        //省略無關程式碼......      }

每次getTarget()將生成prototype類型的bean,即其生成的bean並不是單例的,因而使用這個類型的TargetSource時需要注意,封裝的目標bean必須是prototype類型的。PrototypeTargetSource繼承了AbstractBeanFactoryBasedTargetSource擁有了創建bean的能力。

    public abstract class AbstractPrototypeBasedTargetSource extends AbstractBeanFactoryBasedTargetSource {           //省略無關程式碼......         /**          * 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);               }            }         }          //省略無關程式碼......        }

可以看到,PrototypeTargetSource的生成prototype類型bean的方式主要是委託給BeanFactory進行的,因為BeanFactory自有一套生成prototype類型的bean的邏輯,因而PrototypeTargetSource也就具有生成prototype類型bean的能力,這也就是我們要生成的目標bean必須聲明為prototype類型的原因。

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<>();         //省略無關程式碼......         /**          * 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();         }      //省略無關程式碼......      }

ThreadLocalTargetSource也就是和執行緒綁定的TargetSource,可以理解,其底層實現必然使用的是ThreadLocal。既然使用了ThreadLocal,也就是說我們需要注意兩個問題:

  • 目標對象必須聲明為prototype類型,因為每個執行緒都會持有一個不一樣的對象;
  • 目標對象必須是無狀態的,因為目標對象是和當前執行緒綁定的,而Spring是使用的執行緒池處理的請求,因而每個執行緒可能處理不同的請求,因而為了避免造成問題,目標對象必須是無狀態的。
實現自定義的TargetSource
    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 {            }      }

實現自定義TargetSource主要有兩個點要注意,一個是getTarget()方法,該方法中需要實現獲取目標對象的邏輯,另一個是isStatic()方法,這個方法告知Spring是否需要快取目標對象,在非單例的情況下一般是返回false

小結
   本文主要首先講解了Spring是如果在源碼層面支援TargetSource的,然後講解了TargetSource的使用原理,接著對Spring提供的常見`TargetSource`進行了講解,最後使用一個自定義的TargetSource講解了其使用方式。