Spring Aop之Target Source詳解

       在上文中(Spring Aop標籤解析原理詳解)咱們講解了Spring是如何解析<aop:aspectj-autoproxy/>標籤,而且生成了一個AnnotationAwareAspectJAutoProxyCreatorBeanDefinition的。在Spring代理目標bean的時候,其並非直接建立一個目標bean的對象實例的,而是經過一個TargetSource類型的對象將目標bean進行封裝,Spring Aop獲取目標對象始終是經過TargetSource.getTarget()方法進行的。本文首先會講解Spring Aop是如何封裝目標對象到TargetSource中的,而後會講解TargetSource各個方法的使用原理,接着會對Spring提供的常見的TargetSource的實現類進行講解,最後會講解如何實現自定義的TargetSourcejava

1. 封裝TargetSource對象

        咱們知道,Spring Aop標籤解析的最終結果就是生成了一個AnnotationAwareAspectJAutoProxyCreatorBeanDefinition,咱們查看這個類的繼承結構能夠發現其實現了InstantiationAwareBeanPostProcessorBeanPostProcessor兩個接口,而且分別實現了下面兩個方法:spring

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) 
        throws BeansException {
        return null;
    }
}
public interface BeanPostProcessor {
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) 
        throws BeansException {
		return bean;
	}
}

       這裏省略了其他的不相關方法。上述第一個方法會在Spring實例化一個bean以前執行,若是這裏第一個方法可以返回目標bean對象,那麼這裏就直接使用該對象,Spring不會繼續生成目標bean對象,這種方式能夠實現自定義的bean對象;而第二個方法會在Spring實例化一個bean以後執行,主要做用是對已經生成的bean進行必定的處理。這裏AnnotationAwareAspectJAutoProxyCreator對這兩個方法都進行了重寫,對於重寫的第一個方法,其主要目的在於若是用戶使用了自定義的TargetSource對象,則直接使用該對象生成目標對象,而不會使用Spring的默認邏輯生成目標對象,而且這裏會判斷各個切面邏輯是否能夠應用到當前bean上,若是能夠,則直接應用,也就是說TargetSource爲使用者在Aop中提供了一個自定義生成目標bean邏輯的方式,而且會應用相應的切面邏輯。對於第二個方法,其主要做用在於Spring生成某個bean以後,將相關的切面邏輯應用到該bean上,這個方法在後續將會詳細講解。這裏主要講解第一方法的原理,以下是其實現源碼:緩存

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) 
    throws BeansException {
    Object cacheKey = getCacheKey(beanClass, beanName);

    // 判斷TargetSource緩存中是否包含當前bean,若是不包含,則判斷當前bean是不是已經被代理的bean,
    // 若是代理過,則不對當前傳入的bean進行處理,若是沒代理過,則判斷當前bean是否爲系統bean,或者是
    // 切面邏輯不會包含的bean,若是是,則將當前bean緩存到advisedBeans中,不然繼續往下執行。
    // 通過這一步的處理以後,只有在TargetSource中沒有進行緩存,而且應該被切面邏輯環繞,可是目前還未
    // 生成代理對象的bean纔會經過此方法。
    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

    // 獲取封裝當前bean的TargetSource對象,若是不存在,則直接退出當前方法,不然從TargetSource
    // 中獲取當前bean對象,而且判斷是否須要將切面邏輯應用在當前bean上。
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            this.targetSourcedBeans.add(beanName);
        }
        
        // 獲取可以應用當前bean的切面邏輯
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, 
           beanName, targetSource);
        // 根據切面邏輯爲當前bean生成代理對象
        Object proxy = createProxy(beanClass, beanName, specificInterceptors, 
           targetSource);
        // 對生成的代理對象進行緩存
        this.proxyTypes.put(cacheKey, proxy.getClass());
        // 直接返回生成的代理對象,從而使後續bean的建立工做短路
        return proxy;
    }

    return null;
}

2. TargetSource使用原理

       以下是TargetSource接口的聲明:多線程

public interface TargetSource extends TargetClassAware {

    // 本方法主要用於返回目標bean的Class類型
	@Override
	@Nullable
	Class<?> getTargetClass();

    // 這個方法用戶返回當前bean是否爲靜態的,好比常見的單例bean就是靜態的,而prototype就是動態的,
    // 這裏這個方法的主要做用是,對於靜態的bean,spring是會對其進行緩存的,在屢次使用TargetSource
    // 獲取目標bean對象的時候,其獲取的老是同一個對象,經過這種方式提升效率
	boolean isStatic();

    // 獲取目標bean對象,這裏能夠根據業務須要進行自行定製
	@Nullable
	Object getTarget() throws Exception;

    // Spring在完目標bean以後會調用這個方法釋放目標bean對象,對於一些須要池化的對象,這個方法是必須
    // 要實現的,這個方法默認不進行任何處理
	void releaseTarget(Object target) throws Exception;
}

3. Spring提供的TargetSource對象

       經過第二節對TargetSource的聲明和使用原理講解,咱們能夠看到,TargetSource接口的設計幾乎爲咱們使用該接口實現自定義的對象實現了各類可能性:單例,多例,池化對象等等。下面咱們看看Spring爲咱們提供了哪些常見的TargetSource實現類:app

3.1 SingletonTargetSource

       SingletonTargetSource,顧名思義,即爲單例的TargetSource,其只是對目標bean進行了簡單的封裝。以下是其實現源碼:dom

public class SingletonTargetSource implements TargetSource, Serializable {
	private static final long serialVersionUID = 9031246629662423738L;
	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) {}

	@Override
	public boolean isStatic() {
		return true;
	}
}

       能夠看到SingletonTargetSource經過構造方法傳入一個目標bean對象,在使用getTarget()方法時,也只是將該對象直接返回;而且這裏isStatic()方法返回的是true,也就是說,Spring是能夠緩存SingletonTargetSource的。ide

3.2 PrototypeTargetSource

       與SingletonTargetSource相似,PrototypeTargetSource表示其將生成prototype類型的bean,即其生成的bean並非單例的,於是使用這個類型的TargetSource時須要注意,封裝的目標bean必須是prototype類型的。以下是其實現源碼:post

public class PrototypeTargetSource extends AbstractPrototypeBasedTargetSource {

	@Override
	public Object getTarget() throws BeansException {
		return newPrototypeInstance();
	}

	@Override
	public void releaseTarget(Object target) {
		destroyPrototypeInstance(target);
	}
}

       能夠看到PrototypeTargetSource主要重寫了getTarget()releaseTarget()方法,而且委託給newPrototypeInstance()destroyPrototypeInstance()執行。咱們這裏看看AbstractPrototypeBasedTargetSource的源碼:學習

public abstract class AbstractPrototypeBasedTargetSource 
    extends AbstractBeanFactoryBasedTargetSource {

    // 繼承自BeanFactoryAware接口,將當前Spring使用的BeanFactory傳進來
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		super.setBeanFactory(beanFactory);
		if (!beanFactory.isPrototype(getTargetBeanName())) {
			throw new BeanDefinitionStoreException(
				"Cannot use prototype-based TargetSource 
                   + "against non-prototype bean with name '" 
                   + getTargetBeanName() + "': instances would not be independent");
		}
	}

    // 使用BeanFactory獲取目標bean的對象,getTargetBeanName()方法將返回目標bean的名稱,
    // 因爲目標bean是prototype類型的,於是這裏也就能夠經過BeanFactory獲取prototype類型的bean
    // 這也是PrototypeTargetSource可以生成prototype類型的bean的根本緣由
	protected Object newPrototypeInstance() throws BeansException {
		if (logger.isDebugEnabled()) {
			logger.debug("Creating new instance of bean '" + getTargetBeanName() + "'");
		}
		return getBeanFactory().getBean(getTargetBeanName());
	}

    // 若是生成的bean使用完成,則會調用當前方法銷燬目標bean,因爲目標bean可能實現了DisposableBean
    // 接口,於是這裏銷燬bean的方式就是調用其實現的該接口的方法,從而銷燬目標bean
	protected void destroyPrototypeInstance(Object target) {
		if (this.logger.isDebugEnabled()) {
			this.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.error("Couldn't invoke destroy method of bean with name '" 
                    + getTargetBeanName() + "'", ex);
			}
		}
	}
}

       能夠看到,PrototypeTargetSource的生成prototype類型bean的方式主要是委託給BeanFactory進行的,由於BeanFactory自有一套生成prototype類型的bean的邏輯,於是PrototypeTargetSource也就具備生成prototype類型bean的能力,這也就是咱們要生成的目標bean必須聲明爲prototype類型的緣由。this

3.3 CommonsPool2TargetSource

       這裏CommonsPool2TargetSource也就是池化的TargetSource,其基本具備日常所使用的「池」的概念的全部屬性,好比:最小空閒數,最大空閒數,最大等待時間等等。實際上,CommonsPool2TargetSource的實現是將其委託給了ObjectPool進行,具體的也就是GenericObjectPool,其實現了ObjectPool接口。以下是CommonsPool2TargetSource的主要實現:

public class CommonsPool2TargetSource extends AbstractPoolingTargetSource implements PooledObjectFactory<Object> {

    // 保存池化對象的池
	@Nullable
	private ObjectPool pool;
    
    public CommonsPool2TargetSource() {
		setMaxSize(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL);
	}

	@Override
	protected final void createPool() {
		logger.debug("Creating Commons object pool");
        // 建立池化對象
		this.pool = createObjectPool();
	}

    // 設置池化對象的基本屬性
	protected ObjectPool createObjectPool() {
		GenericObjectPoolConfig config = new GenericObjectPoolConfig();
		config.setMaxTotal(getMaxSize());
		config.setMaxIdle(getMaxIdle());
		config.setMinIdle(getMinIdle());
		config.setMaxWaitMillis(getMaxWait());
		config.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis());
		config.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
		config.setBlockWhenExhausted(isBlockWhenExhausted());
		return new GenericObjectPool(this, config);
	}

    // 從池中請求目標對象
	@Override
	public Object getTarget() throws Exception {
		Assert.state(this.pool != null, "No Commons ObjectPool available");
		return this.pool.borrowObject();
	}

    // 將目標對象歸還到池中
	@Override
	public void releaseTarget(Object target) throws Exception {
		if (this.pool != null) {
			this.pool.returnObject(target);
		}
	}
}

       能夠看到CommonsPool2TargetSource實現是很是簡單的,其將主要功能都委託給了對象池進行,這裏的對象池實現也比較簡單,其主要使用LinkedBlockingDeque,也就是可阻塞的雙端隊列實現對象池的功能。這裏關於隊列鎖的使用並非本文的研究範疇,讀者可閱讀本人前面的文章進行多線程的學習。

3.4 ThreadLocalTargetSource

       ThreadLocalTargetSource也就是和線程綁定的TargetSource,能夠理解,其底層實現必然使用的是ThreadLocal。既然使用了ThreadLocal,也就是說咱們須要注意兩個問題:

  • 目標對象必須聲明爲prototype類型,由於每一個線程都會持有一個不同的對象;
  • 目標對象必須是無狀態的,由於目標對象是和當前線程綁定的,而Spring是使用的線程池處理的請求,於是每一個線程可能處理不一樣的請求,於是爲了不形成問題,目標對象必須是無狀態的。

       以下是ThreadLocalTargetSource的源碼:

public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource
		implements ThreadLocalTargetSourceStats, DisposableBean {

    // 保存目標對象的ThreadLocal對象
	private final ThreadLocal<Object> targetInThread =
		new NamedThreadLocal<>("Thread-local instance of bean '" 
			+ getTargetBeanName() + "'");

    // 將生成過的目標對象保存起來,以便於後續進行統一銷燬
	private final Set<Object> targetSet = new HashSet<>();
	
	// 生成目標對象,這裏的生成方式是ThreadLocal很典型的一種使用策略,即首先從ThreadLocal中取,
	// 若是取到了,則直接返回,若是沒取到,則使用「消耗「大一些的方式獲取,並緩存到ThreadLocal中
	@Override
	public Object getTarget() throws BeansException {
	    // 記錄目標對象的獲取次數
		++this.invocationCount;
		// 從ThreadLocal中獲取
		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() + "'");
			}
			// 若是ThreadLocal中不存在,則經過最基本的方式獲取目標對象,
			// 並將生成的對象保存到ThreadLocal中
			target = newPrototypeInstance();
			this.targetInThread.set(target);
			// 將生成的對象進行緩存
			synchronized (this.targetSet) {
				this.targetSet.add(target);
			}
		}
		else {
			++this.hitCount;
		}
		return target;
	}

    // 銷燬當前TargetSource對象和生成的目標對象
	@Override
	public void destroy() {
		logger.debug("Destroying ThreadLocalTargetSource bindings");
		synchronized (this.targetSet) {
			for (Object target : this.targetSet) {
			    // 銷燬生成的目標對象
				destroyPrototypeInstance(target);
			}
			this.targetSet.clear();
		}
		// 清除ThreadLocal中的緩存
		this.targetInThread.remove();
	}
}

       這裏ThreadLocalTargetSource主要集成了AbstractPrototypeBasedTargetSourceDisposableBean。關於AbstractPrototypeBasedTargetSource前面已經講過了,讀者能夠到前面翻看;而DisposableBean的做用主要是提供一個方法,以供給Spring在銷燬當前對象的時候調用。也就是說Spring在銷燬當前TargetSource對象的時候會首先銷燬其生成的各個目標對象。這裏須要注意的是,TargetSource和生成的目標對象是兩個對象,前面講的TargetSouce都是單例的,只是生成的目標對象多是單例的,也多是多例的。

4. 實現自定義的TargetSource

       對前面各個TargetSource掌握以後,要實現自定義的TargetSource實際上也很是的簡單,假設咱們這裏要生成兩個對象進行訪問均衡,此時就可使用自定義的TargetSource。以下是咱們要生成的目標對象的聲明:

public class Apple {
  private int id;

  public Apple(int id) {
    this.id = id;
  }

  public void eat() {
    System.out.println("eat apple, id: " + id);
  }
}

       這裏Apple對象使用id屬性進行當前對象的標識,並在eat()方法中將id打印出來了。以下是自定義TargetSource實現:

public class AppleTargetSource implements TargetSource {
  private Apple apple1;
  private Apple apple2;

  public AppleTargetSource() {
    this.apple1 = new Apple(1);
    this.apple2 = new Apple(2);
  }

  @Override
  public Class<?> getTargetClass() {
    return Apple.class;
  }

  @Override
  public boolean isStatic() {
    return false;
  }

  @Override
  public Object getTarget() throws Exception {
    ThreadLocalRandom random = ThreadLocalRandom.current();
    int index = random.nextInt(2);
    return index % 2 == 0 ? apple1 : apple2;
  }

  @Override
  public void releaseTarget(Object target) throws Exception {}
}

       實現自定義TargetSource主要有兩個點要注意,一個是getTarget()方法,該方法中須要實現獲取目標對象的邏輯,另外一個是isStatic()方法,這個方法告知Spring是否須要緩存目標對象,在非單例的狀況下通常是返回false。以下是xml文件配置和驅動類的實現:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="targetSource" class="chapter7.eg10.AppleTargetSource"/>
    <aop:aspectj-autoproxy/>
</beans>
public class CustomTargetSourceApp {
  public static void main(String[] args) throws Exception {
    ApplicationContext context = new ClassPathXmlApplicationContext("chapter7/eg10/applicationContext.xml");
    TargetSource targetSource = (TargetSource) context.getBean("targetSource");
    for (int i = 0; i < 10; i++) {
      Apple apple = (Apple) targetSource.getTarget();
      apple.eat();
    }
  }
}

       執行結果以下:

eat apple, id: 1
eat apple, id: 1
eat apple, id: 2
eat apple, id: 1
eat apple, id: 1
eat apple, id: 2
eat apple, id: 1
eat apple, id: 1
eat apple, id: 1
eat apple, id: 1

       從執行結果來看,自定義TargetSource的random特性是實現了,只是這裏使用id爲1的Apple執行次數要多一些,這主要是因爲多線程執行會更傾向於使用當前已經得到鎖的線程執行鎖定代碼。

5. 小結

       本文主要首先講解了Spring是若是在源碼層面支持TargetSource的,而後講解了TargetSource的使用原理,接着對Spring提供的常見TargetSource進行了講解,最後使用一個自定義的TargetSource講解了其使用方式。

相關文章
相關標籤/搜索