Spring Aop之Target Source详解

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> hot3.png

       在上文中(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讲解了其使用方式。

6. 广告

       读者朋友如果觉得本文还不错,可以点击下面的广告链接,这可以为作者带来一定的收入,从而激励作者创作更好的文章,非常感谢!

在项目开发过程中,企业会有很多的任务、需求、缺陷等需要进行管理,CORNERSTONE 提供敏捷、任务、需求、缺陷、测试管理、WIKI、共享文件和日历等功能模块,帮助企业完成团队协作和敏捷开发中的项目管理需求;更有甘特图、看板、思维导图、燃尽图等多维度视图,帮助企业全面把控项目情况。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值