Springboot源碼 TargetSource解析
摘要:
其實我第一次看見這個東西的時候也是不解,代理目標源不就是一個class嘛還需要封裝干嘛。。。
其實proxy代理的不是target,而是TargetSource,這點非常重要,一定要分清楚?。?!
通常情況下,一個代理對象只能代理一個target,每次方法調(diào)用的目標也是唯一固定的target。但是,如果讓proxy代理TargetSource,可以使得每次方法調(diào)用的target實例都不同(當(dāng)然也可以相同,這取決于TargetSource實現(xiàn))。這種機制使得方法調(diào)用變得靈活,可以擴展出很多高級功能,如:單利,原型,本地線程,目標對象池、運行時目標對象熱替換目標源等等。

Spring內(nèi)置的TargetSource
SingletonTargetSource
public class SingletonTargetSource implements TargetSource, Serializable {
/** Target cached and invoked using reflection. */
private final Object target;
//省略無關(guān)代碼......
@Override
public Object getTarget() {
return this.target;
}
//省略無關(guān)代碼......
}
從這個目標源取得的目標對象是單例的,成員變量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);
}
//省略無關(guān)代碼......
}
每次getTarget()將生成prototype類型的bean,即其生成的bean并不是單例的,因而使用這個類型的TargetSource時需要注意,封裝的目標bean必須是prototype類型的。
PrototypeTargetSource繼承了AbstractBeanFactoryBasedTargetSource擁有了創(chuàng)建bean的能力。
public abstract class AbstractPrototypeBasedTargetSource extends AbstractBeanFactoryBasedTargetSource {
//省略無關(guān)代碼......
/**
* 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);
}
}
}
//省略無關(guān)代碼......
}
可以看到,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<>();
//省略無關(guān)代碼......
/**
* 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();
}
//省略無關(guān)代碼......
}
ThreadLocalTargetSource也就是和線程綁定的TargetSource,可以理解,其底層實現(xiàn)必然使用的是ThreadLocal。既然使用了ThreadLocal,也就是說我們需要注意兩個問題:
目標對象必須聲明為prototype類型,因為每個線程都會持有一個不一樣的對象;
目標對象必須是無狀態(tài)的,因為目標對象是和當(dāng)前線程綁定的,而Spring是使用的線程池處理的請求,因而每個線程可能處理不同的請求,因而為了避免造成問題,目標對象必須是無狀態(tài)的。
實現(xiàn)自定義的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 {
}
}
實現(xiàn)自定義TargetSource主要有兩個點要注意,一個是getTarget()方法,該方法中需要實現(xiàn)獲取目標對象的邏輯,另一個是isStatic()方法,這個方法告知Spring是否需要緩存目標對象,在非單例的情況下一般是返回false。
小結(jié)
本文主要首先講解了Spring是如果在源碼層面支持TargetSource的,然后講解了TargetSource的使用原理,接著對Spring提供的常見`TargetSource`進行了講解,最后使用一個自定義的TargetSource講解了其使用方式。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 關(guān)于target目錄在idea沒顯示的問題
- idea 隱藏target,iml等不需要展示的文件(推薦)
- 微信小程序中target和currentTarget的區(qū)別小結(jié)
- idea maven 項目src下的配置文件沒有同步至target的解決操作
- 關(guān)于Tomcat出現(xiàn)The origin server did not find a current representation for the target resourc...的問題
- jQuery中event.target和this的區(qū)別詳解
- Springboot項目刪除項目同步target文件問題解決方案
相關(guān)文章
解決idea 拉取代碼出現(xiàn)的 “ Сannot Run Git Cannot identify version of
這篇文章主要介紹了解決idea 拉取代碼出現(xiàn)的 “ Сannot Run Git Cannot identify version of git executable: no response“的問題,需要的朋友可以參考下2020-08-08
Intellij IDEA實現(xiàn)SpringBoot項目多端口啟動的兩種方法
有時候使用springboot項目時遇到這樣一種情況,用一個項目需要復(fù)制很多遍進行測試,除了端口號不同以外,沒有任何不同。遇到這種情況怎么辦呢?這時候可以使用Intellij IDEA解決2018-06-06
Springboot使用thymeleaf動態(tài)模板實現(xiàn)刷新
這篇文章主要介紹了Springboot使用thymeleaf動態(tài)模板實現(xiàn)刷新,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08
Java 根據(jù)url下載網(wǎng)絡(luò)資源
這篇文章主要介紹了Java 根據(jù)url下載網(wǎng)絡(luò)資源的示例代碼,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-11-11
SpringBoot整合Mybatis-Plus分頁失效的解決
本文主要介紹了SpringBoot整合Mybatis-Plus分頁失效的解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
SpringBoot+Kotlin中使用GRPC實現(xiàn)服務(wù)通信的示例代碼
本文主要介紹了SpringBoot+Kotlin中使用GRPC實現(xiàn)服務(wù)通信的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
基于@AllArgsConstructor與@Value共用的問題解決
這篇文章主要介紹了基于@AllArgsConstructor與@Value共用的問題解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09

