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执行次数要多一些,这主要是由于多线程执行会更倾向于使用当前已经获得锁的线程执行锁定代码。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章