1. 程式人生 > >Springboot原始碼分析之TargetSource

Springboot原始碼分析之TargetSource

摘要:

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

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

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

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講解了其使用方式。