此配置一般是这样使用:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
对于ref属性,只会在userManager初始化时注入一次。这会造成什么问题呢?以session的Scope为例,因为只会注入一次,所以,userManager引用的始终是同一个userPreferences对象,即使现在可能已经过时了。此配置便可以使userManager引用的其实是一个对代理的引用,所以可以始终获取到最新的userPreferences。
其作用和注解@ScopedProxy相同。
其解析由ScopedProxyBeanDefinitionDecorator完成,类图:
从类图可以看出,ScopedProxyBeanDefinitionDecorator和之前的解析器都不同,它的调用入口不同以往:
DefaultBeanDefinitionDocumentReader.processBeanDefinition:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 装饰
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
}
}
BeanDefinitionParserDelegate.decorateIfRequired:
public BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(node);
if (!isDefaultNamespace(namespaceUri)) {
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver()
.resolve(namespaceUri);
if (handler != null) {
return handler.
decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
}
}
return originalDef;
}
一目了然。
这么做(装饰)的原因就是此标签是用在bean内部的,从decorate的方法签名可以看出,第二个便是父(bean)BeanDefinition,所以叫做装饰。
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
boolean proxyTargetClass = true;
if (node instanceof Element) {
Element ele = (Element) node;
if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS));
}
}
BeanDefinitionHolder holder =
ScopedProxyUtils.
createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);
String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName());
// 空实现
parserContext.getReaderContext().fireComponentRegistered(
new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName));
return holder;
}
核心便是createScopedProxy方法,其源码较长,但是这个套路之前见识过了,就是一个偷天换日: 创建一个新的BeanDefinition对象,beanName为被代理的bean的名字,被代理的bean名字为scopedTarget.原名字。被代理的bean扔将被注册到容器中。
新的BeanDefintion的beanClass为ScopedProxyFactoryBean,其类图:
入口便是setBeanFactory方法:
@Override
public void setBeanFactory(BeanFactory beanFactory) {
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (!isProxyTargetClass() || beanType.isInterface() ||
Modifier.isPrivate(beanType.getModifiers())) {
// JDK动态代理可用的接口
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject
(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped proxy
// itself is not subject to auto-proxying! Only its target bean is.
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
这个套路上面也见过了。
核心的拦截逻辑是通过DelegatingIntroductionInterceptor来完成的,其类图:
AdvisedSupport.addAdvice方法将其转化为Advisor:
@Override
public void addAdvice(int pos, Advice advice) throws AopConfigException {
if (advice instanceof IntroductionInfo) {
// We don't need an IntroductionAdvisor for this kind of introduction:
// It's fully self-describing.
addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
} else if (advice instanceof DynamicIntroductionAdvice) {
// We need an IntroductionAdvisor for this kind of introduction.
} else {
addAdvisor(pos, new DefaultPointcutAdvisor(advice));
}
}
显然,DelegatingIntroductionInterceptor被包装为DefaultIntroductionAdvisor对象。
DelegatingIntroductionInterceptor到底是个什么东西呢?这其实就引出了Spring的Introduction(引入)概念。
通常意义上的Spring AOP一般是在方法层面上进行逻辑的改变,而引入指的是在不修改类源码的情况下,直接为一个类添加新的功能。下面是一个引入使用的例子:
SpringAOP中的IntroductionInterceptor
为了便于测试,我们定义一个生存周期仅仅在于一次调用的Scope,源码:
public class OneScope implements Scope {
private int index = 0;
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
System.out.println("get被调用");
return new Student("skywalker-" + (index++), index);
}
//忽略其它方法
}
将其注册到容器中,有两种方法:
此时就可以使用我们自己的Scope了:
<bean class="base.SimpleBean" id="simpleBean">
<property name="student" ref="student" />
</bean>
<bean id="student" class="base.Student" scope="one">
<aop:scoped-proxy />
</bean>
执行以下代码:
SimpleBean simpleBean = context.getBean(SimpleBean.class);
System.out.println(simpleBean.getStudent().getName());
System.out.println(simpleBean.getStudent().getName());
可以看到以下输出:
get被调用
skywalker-0
get被调用
skywalker-1
可以得出结论: 当调用被代理的bean的方法时才会触发Scoped的语义,只是获得其对象(getStudent)没有效果。
从根本上来说在于AbstractBeanFactory.doGetBean,部分源码:
//scope非prototype和Singleton
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
scopes是BeanFactory内部的一个 LinkedHashMap<String, Scope>类型的对象。scope.get实际上调用的就是我们的OneSocpe的get方法,没有用到ObjectFactory。
所以,每调用一次getBean,就会导致一个新的Student被创建并返回。
还有一个关键的问题,从上面可以知道SimpleBean内部的student引用其实是一个CGLIB代理子类的对象,那么当调用这个代理对象的相应方法(比如getName)时,是怎样导致Student重新创建(或是getBean被调用)的?
必须首先理解下CGLIB的这两个概念。
Callback是Cglib所有自定义逻辑(增强)的共同接口。
其简略类图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wTYZ3172-1632905556649)(images/Callback.jpg)]
在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
jdk并不支持这么搞,只支持设置一个InvocationHandler处理(拦截)所有的方法。其类图:
Cglib的Enhancer可以指定一个Callback数组,而accept方法的返回值是一个int值,其实就是Callback数组的下标,这样便达到了指定回调逻辑的目的。
参考:
一般的方法使用的是DynamicAdvisedInterceptor作为回调逻辑,其intercept关键源码:
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
Object target = getTarget();
}
target就是被代理对象。
getTarget:
protected Object getTarget() throws Exception {
return this.advised.getTargetSource().getTarget();
}
TargetSource前面说过了,默认是SimpleBeanTargetSource:
@Override
public Object getTarget() throws Exception {
return getBeanFactory().getBean(getTargetBeanName());
}
至此,真相大白。