Spring Aop之Target Source詳解

在Spring代理目標bean的時候,其並不是直接創建一個目標bean的對象實例的,而是通過一個TargetSource類型的對象將目標bean進行封裝,Spring Aop獲取目標對象始終是通過TargetSource.getTarget()方法進行的。本文首先會講解Spring Aop是如何封裝目標對象到TargetSource中的,然後會講解TargetSource各個方法的使用原理,接着會對Spring提供的常見的TargetSource的實現類進行講解,最後會講解如何實現自定義的TargetSource

1. 封裝TargetSource對象

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

  1. public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
  2. @Nullable
  3. default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
  4. throws BeansException {
  5. return null;
  6. }
  7. }
  1. public interface BeanPostProcessor {
  2. @Nullable
  3. default Object postProcessAfterInitialization(Object bean, String beanName)
  4. throws BeansException {
  5. return bean;
  6. }
  7. }

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

  1. @Override
  2. public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
  3. throws BeansException {
  4. Object cacheKey = getCacheKey(beanClass, beanName);
  5. // 判斷TargetSource緩存中是否包含當前bean,如果不包含,則判斷當前bean是否是已經被代理的bean,
  6. // 如果代理過,則不對當前傳入的bean進行處理,如果沒代理過,則判斷當前bean是否爲系統bean,或者是
  7. // 切面邏輯不會包含的bean,如果是,則將當前bean緩存到advisedBeans中,否則繼續往下執行。
  8. // 經過這一步的處理之後,只有在TargetSource中沒有進行緩存,並且應該被切面邏輯環繞,但是目前還未
  9. // 生成代理對象的bean纔會通過此方法。
  10. if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
  11. if (this.advisedBeans.containsKey(cacheKey)) {
  12. return null;
  13. }
  14. if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
  15. this.advisedBeans.put(cacheKey, Boolean.FALSE);
  16. return null;
  17. }
  18. }
  19. // 獲取封裝當前bean的TargetSource對象,如果不存在,則直接退出當前方法,否則從TargetSource
  20. // 中獲取當前bean對象,並且判斷是否需要將切面邏輯應用在當前bean上。
  21. TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
  22. if (targetSource != null) {
  23. if (StringUtils.hasLength(beanName)) {
  24. this.targetSourcedBeans.add(beanName);
  25. }
  26. // 獲取能夠應用當前bean的切面邏輯
  27. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass,
  28. beanName, targetSource);
  29. // 根據切面邏輯爲當前bean生成代理對象
  30. Object proxy = createProxy(beanClass, beanName, specificInterceptors,
  31. targetSource);
  32. // 對生成的代理對象進行緩存
  33. this.proxyTypes.put(cacheKey, proxy.getClass());
  34. // 直接返回生成的代理對象,從而使後續bean的創建工作短路
  35. return proxy;
  36. }
  37. return null;
  38. }

2. TargetSource使用原理

       如下是TargetSource接口的聲明:

  1. public interface TargetSource extends TargetClassAware {
  2. // 本方法主要用於返回目標bean的Class類型
  3. @Override
  4. @Nullable
  5. Class<?> getTargetClass();
  6. // 這個方法用戶返回當前bean是否爲靜態的,比如常見的單例bean就是靜態的,而prototype就是動態的,
  7. // 這裏這個方法的主要作用是,對於靜態的bean,spring是會對其進行緩存的,在多次使用TargetSource
  8. // 獲取目標bean對象的時候,其獲取的總是同一個對象,通過這種方式提高效率
  9. boolean isStatic();
  10. // 獲取目標bean對象,這裏可以根據業務需要進行自行定製
  11. @Nullable
  12. Object getTarget() throws Exception;
  13. // Spring在完目標bean之後會調用這個方法釋放目標bean對象,對於一些需要池化的對象,這個方法是必須
  14. // 要實現的,這個方法默認不進行任何處理
  15. void releaseTarget(Object target) throws Exception;
  16. }

3. Spring提供的TargetSource對象

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

3.1 SingletonTargetSource

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

  1. public class SingletonTargetSource implements TargetSource, Serializable {
  2. private static final long serialVersionUID = 9031246629662423738L;
  3. private final Object target;
  4. public SingletonTargetSource(Object target) {
  5. Assert.notNull(target, "Target object must not be null");
  6. this.target = target;
  7. }
  8. @Override
  9. public Class<?> getTargetClass() {
  10. return this.target.getClass();
  11. }
  12. @Override
  13. public Object getTarget() {
  14. return this.target;
  15. }
  16. @Override
  17. public void releaseTarget(Object target) {}
  18. @Override
  19. public boolean isStatic() {
  20. return true;
  21. }
  22. }

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

3.2 PrototypeTargetSource

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

  1. public class PrototypeTargetSource extends AbstractPrototypeBasedTargetSource {
  2. @Override
  3. public Object getTarget() throws BeansException {
  4. return newPrototypeInstance();
  5. }
  6. @Override
  7. public void releaseTarget(Object target) {
  8. destroyPrototypeInstance(target);
  9. }
  10. }

       可以看到PrototypeTargetSource主要重寫了getTarget()releaseTarget()方法,並且委託給newPrototypeInstance()destroyPrototypeInstance()執行。我們這裏看看AbstractPrototypeBasedTargetSource的源碼:

  1. public abstract class AbstractPrototypeBasedTargetSource
  2. extends AbstractBeanFactoryBasedTargetSource {
  3. // 繼承自BeanFactoryAware接口,將當前Spring使用的BeanFactory傳進來
  4. @Override
  5. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  6. super.setBeanFactory(beanFactory);
  7. if (!beanFactory.isPrototype(getTargetBeanName())) {
  8. throw new BeanDefinitionStoreException(
  9. "Cannot use prototype-based TargetSource
  10. + "against non-prototype bean with name '"
  11. + getTargetBeanName() + "': instances would not be independent");
  12. }
  13. }
  14. // 使用BeanFactory獲取目標bean的對象,getTargetBeanName()方法將返回目標bean的名稱,
  15. // 由於目標bean是prototype類型的,因而這裏也就可以通過BeanFactory獲取prototype類型的bean
  16. // 這也是PrototypeTargetSource能夠生成prototype類型的bean的根本原因
  17. protected Object newPrototypeInstance() throws BeansException {
  18. if (logger.isDebugEnabled()) {
  19. logger.debug("Creating new instance of bean '" + getTargetBeanName() + "'");
  20. }
  21. return getBeanFactory().getBean(getTargetBeanName());
  22. }
  23. // 如果生成的bean使用完成,則會調用當前方法銷燬目標bean,由於目標bean可能實現了DisposableBean
  24. // 接口,因而這裏銷燬bean的方式就是調用其實現的該接口的方法,從而銷燬目標bean
  25. protected void destroyPrototypeInstance(Object target) {
  26. if (this.logger.isDebugEnabled()) {
  27. this.logger.debug("Destroying instance of bean '"
  28. + getTargetBeanName() + "'");
  29. }
  30. if (getBeanFactory() instanceof ConfigurableBeanFactory) {
  31. ((ConfigurableBeanFactory) getBeanFactory())
  32. .destroyBean(getTargetBeanName(), target);
  33. } else if (target instanceof DisposableBean) {
  34. try {
  35. ((DisposableBean) target).destroy();
  36. } catch (Throwable ex) {
  37. logger.error("Couldn't invoke destroy method of bean with name '"
  38. + getTargetBeanName() + "'", ex);
  39. }
  40. }
  41. }
  42. }

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

3.3 CommonsPool2TargetSource

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

  1. public class CommonsPool2TargetSource extends AbstractPoolingTargetSource implements PooledObjectFactory<Object> {
  2. // 保存池化對象的池
  3. @Nullable
  4. private ObjectPool pool;
  5. public CommonsPool2TargetSource() {
  6. setMaxSize(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL);
  7. }
  8. @Override
  9. protected final void createPool() {
  10. logger.debug("Creating Commons object pool");
  11. // 創建池化對象
  12. this.pool = createObjectPool();
  13. }
  14. // 設置池化對象的基本屬性
  15. protected ObjectPool createObjectPool() {
  16. GenericObjectPoolConfig config = new GenericObjectPoolConfig();
  17. config.setMaxTotal(getMaxSize());
  18. config.setMaxIdle(getMaxIdle());
  19. config.setMinIdle(getMinIdle());
  20. config.setMaxWaitMillis(getMaxWait());
  21. config.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis());
  22. config.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
  23. config.setBlockWhenExhausted(isBlockWhenExhausted());
  24. return new GenericObjectPool(this, config);
  25. }
  26. // 從池中請求目標對象
  27. @Override
  28. public Object getTarget() throws Exception {
  29. Assert.state(this.pool != null, "No Commons ObjectPool available");
  30. return this.pool.borrowObject();
  31. }
  32. // 將目標對象歸還到池中
  33. @Override
  34. public void releaseTarget(Object target) throws Exception {
  35. if (this.pool != null) {
  36. this.pool.returnObject(target);
  37. }
  38. }
  39. }

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

3.4 ThreadLocalTargetSource

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

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

       如下是ThreadLocalTargetSource的源碼:

  1. public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource
  2. implements ThreadLocalTargetSourceStats, DisposableBean {
  3. // 保存目標對象的ThreadLocal對象
  4. private final ThreadLocal<Object> targetInThread =
  5. new NamedThreadLocal<>("Thread-local instance of bean '"
  6. + getTargetBeanName() + "'");
  7. // 將生成過的目標對象保存起來,以便於後續進行統一銷燬
  8. private final Set<Object> targetSet = new HashSet<>();
  9. // 生成目標對象,這裏的生成方式是ThreadLocal很典型的一種使用策略,即首先從ThreadLocal中取,
  10. // 如果取到了,則直接返回,如果沒取到,則使用“消耗“大一些的方式獲取,並緩存到ThreadLocal中
  11. @Override
  12. public Object getTarget() throws BeansException {
  13. // 記錄目標對象的獲取次數
  14. ++this.invocationCount;
  15. // 從ThreadLocal中獲取
  16. Object target = this.targetInThread.get();
  17. if (target == null) {
  18. if (logger.isDebugEnabled()) {
  19. logger.debug("No target for prototype '" + getTargetBeanName()
  20. + "' bound to thread: " + "creating one and binding it to thread '"
  21. + Thread.currentThread().getName() + "'");
  22. }
  23. // 如果ThreadLocal中不存在,則通過最基本的方式獲取目標對象,
  24. // 並將生成的對象保存到ThreadLocal中
  25. target = newPrototypeInstance();
  26. this.targetInThread.set(target);
  27. // 將生成的對象進行緩存
  28. synchronized (this.targetSet) {
  29. this.targetSet.add(target);
  30. }
  31. }
  32. else {
  33. ++this.hitCount;
  34. }
  35. return target;
  36. }
  37. // 銷燬當前TargetSource對象和生成的目標對象
  38. @Override
  39. public void destroy() {
  40. logger.debug("Destroying ThreadLocalTargetSource bindings");
  41. synchronized (this.targetSet) {
  42. for (Object target : this.targetSet) {
  43. // 銷燬生成的目標對象
  44. destroyPrototypeInstance(target);
  45. }
  46. this.targetSet.clear();
  47. }
  48. // 清除ThreadLocal中的緩存
  49. this.targetInThread.remove();
  50. }
  51. }

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

4. 實現自定義的TargetSource

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

  1. public class Apple {
  2. private int id;
  3. public Apple(int id) {
  4. this.id = id;
  5. }
  6. public void eat() {
  7. System.out.println("eat apple, id: " + id);
  8. }
  9. }

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

  1. public class AppleTargetSource implements TargetSource {
  2. private Apple apple1;
  3. private Apple apple2;
  4. public AppleTargetSource() {
  5. this.apple1 = new Apple(1);
  6. this.apple2 = new Apple(2);
  7. }
  8. @Override
  9. public Class<?> getTargetClass() {
  10. return Apple.class;
  11. }
  12. @Override
  13. public boolean isStatic() {
  14. return false;
  15. }
  16. @Override
  17. public Object getTarget() throws Exception {
  18. ThreadLocalRandom random = ThreadLocalRandom.current();
  19. int index = random.nextInt(2);
  20. return index % 2 == 0 ? apple1 : apple2;
  21. }
  22. @Override
  23. public void releaseTarget(Object target) throws Exception {}
  24. }

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  4. 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">
  5. <bean id="targetSource" class="chapter7.eg10.AppleTargetSource"/>
  6. <aop:aspectj-autoproxy/>
  7. </beans>
  1. public class CustomTargetSourceApp {
  2. public static void main(String[] args) throws Exception {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("chapter7/eg10/applicationContext.xml");
  4. TargetSource targetSource = (TargetSource) context.getBean("targetSource");
  5. for (int i = 0; i < 10; i++) {
  6. Apple apple = (Apple) targetSource.getTarget();
  7. apple.eat();
  8. }
  9. }
  10. }

       執行結果如下:

  1. eat apple, id: 1
  2. eat apple, id: 1
  3. eat apple, id: 2
  4. eat apple, id: 1
  5. eat apple, id: 1
  6. eat apple, id: 2
  7. eat apple, id: 1
  8. eat apple, id: 1
  9. eat apple, id: 1
  10. eat apple, id: 1
從執行結果來看,自定義TargetSource的random特性是實現了,只是這裏使用id爲1的Apple執行次數要多一些,這主要是由於多線程執行會更傾向於使用當前已經獲得鎖的線程執行鎖定代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章