摘要:

其实我第一次看见这个东西的时候也是不解,代理目标源不就是一个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讲解了其使用方式。

Springboot源码分析之TargetSource的更多相关文章

  1. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  2. Springboot源码分析之项目结构

    Springboot源码分析之项目结构 摘要: 无论是从IDEA还是其他的SDS开发工具亦或是https://start.spring.io/ 进行解压,我们都会得到同样的一个pom.xml文件 4. ...

  3. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  4. Springboot源码分析之代理三板斧

    摘要: 在Spring的版本变迁过程中,注解发生了很多的变化,然而代理的设计也发生了微妙的变化,从Spring1.x的ProxyFactoryBean的硬编码岛Spring2.x的Aspectj注解, ...

  5. springboot源码分析-SpringApplication

    SpringApplication SpringApplication类提供了一种方便的方法来引导从main()方法启动的Spring应用程序 SpringBoot 包扫描注解源码分析 @Spring ...

  6. Springboot源码分析之jar探秘

    摘要: 利用IDEA等工具打包会出现springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面说过它们之间的关系了, ...

  7. Springboot源码分析之事务拦截和管理

    摘要: 在springboot的自动装配事务里面,InfrastructureAdvisorAutoProxyCreator ,TransactionInterceptor,PlatformTrans ...

  8. Springboot源码分析之Spring循环依赖揭秘

    摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效.或许刚说到这,有的小伙伴就会大惊失色了.Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我 ...

  9. SpringBoot源码分析(二)启动原理

    Springboot的jar启动方式,是通过IOC容器启动 带动了Web容器的启动 而Springboot的war启动方式,是通过Web容器(如Tomcat)的启动 带动了IOC容器相关的启动 一.不 ...

随机推荐

  1. http面试笔试常考知识点(一)

    1.什么是http HTTP是客户端和服务器端请求和应答的标准.通过使用Web浏览器.网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请求.(我们称这个客户端)叫用 ...

  2. C#各版本新增加功能

    本系列文章主要整理并介绍 C# 各版本的新增功能. C# 8.0 C#8.0 于 2019年4月 随 .NET Framework 4.8 与 Visual Studio 2019 一同发布,但是当前 ...

  3. AQS初体验

    AQS初体验 AQS是AbstractQueuedSynchronizer的简称.AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架.所谓框架,AQS使用了模板方法的设计模式,为我们 ...

  4. php 排序和置顶功能实现

    (1)排序操作思路 一般来说都是按照发布时间排序.时间戳大的靠前,所以用倒序desc,而不是asc (2)置顶操作思路: 点击置顶时,修改数据库addtime字段值为当前时间即可.因为排序是按照时间戳 ...

  5. Java计时新姿势

    为获得更好的阅读体验,请访问原文:传送门 前言: 最近公司来了个大佬,从他那里学到不少东西,其中一个就是计时 的新姿势「StopWatch」,赶紧来一起了解了解吧! 一.最简单的计时 在我们的程序中不 ...

  6. 【Android】drawable VS mipmap

    Android Studio 创建工程后默认的资源文件夹如下图所示: 一直有些疑惑的是 mipmap 和 drawable 文件夹有什么区别,以及是否还需要创建 drawable-xhdpi, dra ...

  7. Android Studio 蓝牙开发实例——基于Android 6.0

    因项目需要做一个Android 的蓝牙app来通过手机蓝牙传输数据以及控制飞行器,在此,我对这段时间里写的蓝牙app的代码进行知识梳理和出现错误的总结. 该应用的Compile Sdk Version ...

  8. Wtm携手LayUI -- .netcore 开源生态我们是认真的!

    经过WTM团队和LayUI团队多次深入协商,双方于2019年7月29日在北京中国国际展览中心正式达成战略合作意向, 双方签署了战略合作框架协议,LayUI团队承诺使用WTM框架的任何项目都可以免费使用 ...

  9. Linux基础文件类型

    一.文件时间 ls -l 文件名 仅看的是文件的修改时间 [root@linux ~]# ls -l /etc/passwd -rw-r--r-- root root 5月 : /etc/passwd ...

  10. [Abp vNext 源码分析] - 7. 权限与验证

    一.简要说明 在上篇文章里面,我们在 ApplicationService 当中看到了权限检测代码,通过注入 IAuthorizationService 就可以实现权限检测.不过跳转到源码才发现,这个 ...