文档章节

Spring Aop之Target Source详解

爱宝贝丶
 爱宝贝丶
发布于 2018/08/08 09:32
字数 3465
阅读 551
收藏 7

       在上文中(Spring Aop标签解析原理详解)我们讲解了Spring是如何解析<aop:aspectj-autoproxy/>标签,并且生成了一个AnnotationAwareAspectJAutoProxyCreatorBeanDefinition的。在Spring代理目标bean的时候,其并不是直接创建一个目标bean的对象实例的,而是通过一个TargetSource类型的对象将目标bean进行封装,Spring Aop获取目标对象始终是通过TargetSource.getTarget()方法进行的。本文首先会讲解Spring Aop是如何封装目标对象到TargetSource中的,然后会讲解TargetSource各个方法的使用原理,接着会对Spring提供的常见的TargetSource的实现类进行讲解,最后会讲解如何实现自定义的TargetSource

1. 封装TargetSource对象

        我们知道,Spring Aop标签解析的最终结果就是生成了一个AnnotationAwareAspectJAutoProxyCreatorBeanDefinition,我们查看这个类的继承结构可以发现其实现了InstantiationAwareBeanPostProcessorBeanPostProcessor两个接口,并且分别实现了下面两个方法:

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实现类:

3.1 SingletonTargetSource

       SingletonTargetSource,顾名思义,即为单例的TargetSource,其只是对目标bean进行了简单的封装。如下是其实现源码:

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的。

3.2 PrototypeTargetSource

       与SingletonTargetSource类似,PrototypeTargetSource表示其将生成prototype类型的bean,即其生成的bean并不是单例的,因而使用这个类型的TargetSource时需要注意,封装的目标bean必须是prototype类型的。如下是其实现源码:

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类型的原因。

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

© 著作权归作者所有

爱宝贝丶

爱宝贝丶

粉丝 324
博文 129
码字总数 427858
作品 0
武汉
程序员
私信 提问
Spring Aop标签解析原理详解

对于Spring Aop的实现,是非常复杂的,其实现过程主要包含xml标签的解析,切面表达式的解析,判断bean是否需要应用切面逻辑,以及使用Jdk代理或者是Cglib代理生成代理类。本文主要讲解Xml标签...

爱宝贝丶
2018/08/05
418
0
Spring AOP 创建代理的源码解析

相关文章 Spring AOP 注解方式源码解析 Spring AOP 功能使用详解 Spring 的 getBean 方法源码解析 Spring bean 创建过程源码解 Spring 中 bean 注册的源码解析 前言 在上篇文章 Spring AOP 注...

TSMYK
01/01
134
0
Spring AOP 功能使用详解

相关文章 Spring 中 bean 注册的源码解析 Spring bean 创建过程源码解析 Spring 的 getBean 方法源码解析 前言 AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可...

TSMYK
2018/12/25
233
2
Spring Aop之Jdk代理实现原理详解

Jdk代理,也称为动态代理,其代理目标对象的方式是生成一个与目标对象实现同一个接口的类,该类的构造函数中会传入一个类型的对象。因为对象是用户自定义的织入了切面逻辑的类,因而在需要使...

爱宝贝丶
2018/08/26
559
0
Spring Aop配置时的切入点表达式

对应的中文: 任意公共方法的执行: execution(public (..)) 任何一个以“set”开始的方法的执行: execution( set(..)) AccountService 接口的任意方法的执行: execution( com.xyz.service...

cswy
2013/12/05
179
0

没有更多内容

加载失败,请刷新页面

加载更多

Wifiphisher —— 非常非常非常流氓的 WIFI 网络钓鱼框架

编者注:这是一个非常流氓的 WIFI 网络钓鱼工具,甚至可能是非法的工具(取决于你的使用场景)。在没有事先获得许可的情况下使用 Wifiphisher 攻击基础网络设施将被视为非法活动。使用时请遵...

红薯
47分钟前
36
1
go-micro 入门教程1.搭建 go-micro环境

微服务的本质是让专业的人做专业的事情,做出更好的东西。 golang具备高并发,静态编译等特性,在性能、安全等方面具备非常大的优势。go-micro是基于golang的微服务编程框架,go-micro操作简单...

非正式解决方案
今天
6
0
vue v-html动态生成的html怎么加样式

1. v-html加样式 在vue开发中碰到需要动态生成html,并且需要有样式,这时候发现像往常一样写样式的时候不起作用,网上搜了一下,发现通过 v-html 创建的 DOM 内容不受作用域内的样式影响,但...

litCabbage
今天
13
0
Appium+python自动化(三十三)- 测试环境和本地环境傻傻滴分不清楚-Remote(超详解)

  简介   在前边所有涉及启动app的时候有这样一行代码driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps),很多小伙伴们和同学们不知道这个ip和端口哪里来的,我...

开源仔
今天
14
0
各种放大器电路之功率放大器的分析

  供给负载一定输出功率的放大器叫做功率放大器。它是收音机、扩音机或其他电子设备的末级,它推动扬声器发出声音,使电动机转动,使记录仪表动作等。功率放大器主要是考虑如何获得最大的输...

xyxyty
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部