Skip to main content

Spring AOP的织入1

本文相关代码(来自官方源码spring-test模块)请参见spring-framework org.springframework.mylearntest包下。

AspectJ采用ajc编译器作为它的织入器;JBoss AOP使用自定义的ClassLoader作为它的织入器;而在Spring AOP中,使用类org.springframework.aop.framework.ProxyFactory作为织入器。

使用方法:

  1. 传入需要织入的对象 ProxyFactory weaver = new ProxyFactory(target);

  2. 将要应用到目标对象的Advisor绑定到织入器上

  • 如果不是Introduction的Advice类型,Proxy内部就会为这些Advice构造相应的Advisor,只不过在为它们构造Advisor中使用的Pointcut为Pointcut.TRUE。
  • 如果是Introduction类型,则会根据该Introduction具体类型进行区分;如果是Introduction的子类实现,框架内部会为其构造一个DefaultIntroductionAdvisor ;如果是DynamicIntroductionAdvice的子实现类,框架内部将抛出AOPConfigException异常(因为无法从DynamicIntroductionAdvice取得必要的目标对象信息)
  • weaver.addAdvisor(advisor);
  1. 获取代理对象 Object proxyObject = weaver.getProxy();

基于接口的代理

Test4ProxyFactory
package org.springframework.mylearntest.aop.weaver;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;

import java.util.Date;

/**
* @Author: whalefall
* @Date: 2020/7/15 22:53
*/

@SuppressWarnings({"rawtypes", "Deprecated"})
public class Test4ProxyFactory {
public static void main(String[] args) {
/*// 1. 传入需要织入的对象
ProxyFactory weaver = new ProxyFactory(new Tester());
// weaver.setTarget(new Tester());

// 2. 将要应用到目标对象的Advisor绑定到织入器上
ApplicationContext context = new ClassPathXmlApplicationContext("advisor/defaultadvisor/defaultadvisor.xml");
Advisor advisor = (Advisor) context.getBean("advisor");
weaver.addAdvisor(advisor);

Object proxyObject = weaver.getProxy();
System.out.println(proxyObject.getClass());
// out: class org.springframework.mylearntest.aop.advice.perinstance.Tester$$EnhancerBySpringCGLIB$$8e739b5b
*/

// 目标类有实现接口的用法
// 只要不将ProxyFactory的optimize和proxyTargetClass设置为true
// 那么ProxyFactory都会按照面向接口进行代理
MockTask task = new MockTask();
ProxyFactory weaver = new ProxyFactory(task);
// weaver.setInterfaces(new Class[]{ITask.class});
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedNames("execute");
advisor.setAdvice(new PerformanceMethodInterceptor());
weaver.addAdvisor(advisor);
ITask proxyObj = (ITask)weaver.getProxy();
// com.sun.proxy.$Proxy0
// System.out.println(proxyObj.getClass());
// 只能强制转化为接口类型,不能转化为实现类类型 否则会抛出ClassCastException
// ITask proxyObj = (MockTask)weaver.getProxy();
proxyObj.execute(new Date());

}
}

基于类代理

TestCGLib
package org.springframework.mylearntest.aop.weaver.baseonclass;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor;

/**
* @Author: whalefall
* @Date: 2020/7/17 23:31
*/
public class Test4CGLib {
public static void main(String[] args) {
ProxyFactory weaver = new ProxyFactory(new Executable());
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();

advisor.addMethodName("execute");
advisor.setAdvice(new PerformanceMethodInterceptor());
weaver.addAdvisor(advisor);

Executable proxyObject = (Executable)weaver.getProxy();
proxyObject.execute();
// org.springframework.mylearntest.aop.weaver.baseonclass.Executable$$EnhancerBySpringCGLIB$$37e40619
System.out.println("proxyObject class: " + proxyObject.getClass());
}
}

如果目标类没有实现任何接口,不管proxyTargetClass的属性是什么,ProxyFactoy会采用基于类的代理

如果ProxyFactoy的proxyTargetClass属性值被设置为true,ProxyFactoy会采用基于类的代理

如果ProxyFactoy的optimize属性被设置为true,ProxyFactory会采用基于类的代理。

Introduction的织入

Introduction可以为已经存在的对象类型添加新的行为,只能应用于对象级别的拦截,而不是通常Advice的方法级别的拦截,所以在Introduction的织入过程中,不需要指定Pointcut,而只需要指定目标接口类型。

Spring的Introduction支持只能通过接口定义为当前对象添加新的行为。所以,我们需要在织入的时机,指定新织入的接口类型。

Test4Introduction
package org.springframework.mylearntest.aop.weaver.introduction;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.mylearntest.aop.advice.perinstance.Developer;
import org.springframework.mylearntest.aop.advice.perinstance.IDeveloper;
import org.springframework.mylearntest.aop.advice.perinstance.ITester;
import org.springframework.mylearntest.aop.advice.perinstance.TesterFeatureIntroductionInterceptor;

/**
* @Author: whalefall
* @Date: 2020/7/19 0:02
*/

@SuppressWarnings("rawtypes")
public class Test4Introduction {
public static void main(String[] args) {
ProxyFactory weaver = new ProxyFactory(new Developer());
weaver.setInterfaces(new Class[]{IDeveloper.class, ITester.class});
TesterFeatureIntroductionInterceptor advice = new TesterFeatureIntroductionInterceptor();
weaver.addAdvice(advice);
// DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice,advice);
// weaver.addAdvisor(advisor);

Object proxy = weaver.getProxy();
((ITester)proxy).testSoftware();
((IDeveloper)proxy).developSoftware();
System.out.println("proxy = " + proxy);

}
}

ProxyFactory本质

Spring AOP框架内使用AopProxy对使用的不用的代理实现机制进行了适度的抽象,主要有针对JDK动态代理和CGLIB两种机制的AopProxy两种实现,分别是Cglib2AopProxy和JdkDynamicAopProxy两种实现。动态代理需要通过InvocationHandler提供调用拦截,所以JdkDynamicAopProxy同时实现了InvocationHandler接口。采用抽象工厂模式,通过org.springframework.aop.framework.AopProxyFactory进行。

AopProxyFactory
public interface AopProxyFactory {
AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}

AopProxyFactory根据传入的AdvisedSupport实例提供的相关信息,来决定生成什么类型的AopProxy,具体的工作由AopProxyFactory具体的实现类来完成。即org.springframework.aop.framework.DefaultAopProxyFactory。

DefaultAopProxyFactory
package org.springframework.aop.framework;

import java.io.Serializable;
import java.lang.reflect.Proxy;

import org.springframework.aop.SpringProxy;

@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果传入的AdvisedSupport实例的isOptimize或者isProxyTargetClass方法返回true,
// 或者目标对象没有实现任何接口,则采用CGLIB生成代理对象,否则使用动态代理。
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}

private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}

}

AdvisedSupport是一个生成代理对象所需要的信息的载体。

  • 一类以org.springframework.aop.framework.ProxyConfig为首的,记载生成代理对象的控制信息;
  • 一类以org.springframework.aop.framework.Advised为首,承载生成代理对象的所需要的必要信息,比如相关目标类、Advice、Advisor等。

ProxyConfig就是普通的JavaBean,定义了五个boolean型的属性,分别控制在生成代理对象的时候,应该采取哪些措施。

  • ProxyTargetClass:如果这个属性设置如true,则ProxyFactory将会使用CGLIB对目标对象进行代理。默认值为false。
  • optimize:该属性主要用于告知代理对象是否需要采取进一步的优化措施。如果代理对象生成之后,即使为其添加或者移除了相应的人Advice,代理对象也可以忽略这种变动。如果这个属性设置如true,则ProxyFactory将会使用CGLIB对目标对象进行代理。默认值为false。
  • opaque:该属性用于控制生成的代理对象是否可以强制转化为Advised,默认值为false,表示任何生成的代理对象都可以强制转型为Advised,我们可以通过Advised查询代理对象的一些状态。
  • exposeProxy:设置exposeProxy,可以让Spring AOP框架在生成代理对象时,将当前代理对象绑定到ThreadLocal。如果目标对象需要访问当前代理对象,可以通过AopContext.currentProxy()拿到代理对象。出于性能方面考虑,该属性默认为false。
  • frozen:如果将frozen设置为true,那么一旦针对dialing对象生成的各项信息配置完成,则不容许更改。比如ProxyFactory的设置完毕,并且frozen为true,则不能对Advice进行任何变动,这样可以优化代理对象的性能,默认情况下为false。

要生成代理对象,只有ProxyConfig提供的信息还不够,我们还需要生成代理对象的一些具体信息,比如,要针对哪些目标类生成代理对象,要为代理对象加入何种横切逻辑等,这些信息可以通过org.springframework.aop.framework.Advised设置。默认情况下Spring AOP框架返回的代理对象都可以强制转型为Advised,已查询代理对象的相关信息。

我们可以使用Advised接口访问相应代理对象所有持有的Advisor,进行添加Advisor、一处Advisor等相关动作。即使代理对象已经生成完毕,也可对其进行操作,直接操作Advised,更多时候用于测试场景,可以帮助我们检查生成的代理对象是否如所期望的那样。

ProxyFactory集AopProxy和AdvisedSupport于一身,可以通过AdvisedSupport设置生成代理对象所需要的相关信息,可以通过AopProxy生成代理对象。为了重用相关逻辑,Spring AOP框架在实现的时候,将一些公用的逻辑抽取到了org.springframework.aop.frameworkx.ProxyCreatorSupport中,自身继承了AdvisedSupport,所以就能具有设置生成代理对象所需要的相关信息。

为了简化生成不同类型AopProxy的工作,ProxyCreatorSupport内部持有一个AopProxyFactory实例,默认采用的是DefaultAopProxyFactory。

ProxyFactoryBean的本质

ProxyFactoryBean本质上是一个用来产生Proxy的FactoryBean,FactoryBean的作用

如果某个对象持有某个FactoryBean的引用,它取得的不是FactoryBean本身,而是FactoryBean的getObject()方法返回的对象。所以,如果容器中某个对象依赖于ProxyFactoryBean,那么它将会使用到ProxyFactoryBean的getObject()方法返回的代理对象。

要让ProxyFactoryBean的getObject()方法返回相应目标对象的代理对象其实很简单。因为ProxyFactoryBean继承了ProxyFactory共有的父类ProxyCreatorSupport,而ProxyCreatorSupport基本上已经把要做的事情(设置目标对象、配置其他部件、生成对应的AopProxy等)全部完成了。我们只用在ProxyFactoryBean的getObject()方法中通过父类的createAopProxy()拿到代理对象,然后return AopProxy.getObject()即可。

getObject()
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}

ProxyBean定义中要求表明返回的对象是以singleton的scope返回,还是prototype的scope返回。针对这两种情况返回不同的代理对象,以满足FactoryBean的isSingleton()方法的语义。

如果将ProxyFactoryBean的singleton属性设置为true,则ProxyFactoryBean在第一次生成代理对象之后,会通过内部实例变量singletonInstance(Object类型)缓存生成的代理对象。之后所有的请求都返回这一缓存实例,从而满足singleton的语义。反之,如果将ProxyFactoryBean的singleton属性设置为false,那么,ProxyFactoryBean每次都会重新检测各项设置,并为当前调用准备一套新的环境,然后再根据最新的环境数据,返回一个新的代理对象。因此,如果singleton属性为false,在生成代理对象的性能上存在损失。

ProxyFactoryBean的使用

与ProxyFactory一样,通过ProxyFactoryBean,我们可以在生成目标对象的代理对象的时候,指定使用基于接口的代理还是基于类的代理方式,而且,因为它们全部继承自同一个父类,大部分设置项目都相同。ProxyFactoryBean在继承了ProxyCreatorSupport的所有配置属性之外还添加了自己独有的:

proxyInterfaces:如果我们要采用基于接口的代理方式,那莪需要通过该属性配置相应的接口类型,通过Collection对象传入配置元素的接口信息。ProxyFactoryBean 有一个autodetectInterfaces属性,该属性默认为true,如果没有明确指定要代理的接口类型,ProxyFactoryBean会自动检测目标对象实现的接口类型并进行代理。

interceptorNames:通过该属性,我们可以指定多个将要织入到目标对象的Advice、拦截器以及Advisor,而再也不通过ProxyFactory那样的addAdvice或者addAdvisor 方法添加,通常我们会使用配置元素<list>添加需要的拦截器名称。

  • 如果没有设置目标对象,那么可以在interceptorNames的最后一个元素的位置,放置对象的Bean定义名称。建议直接定义目标对象。
  • 通过指定的interceptorNames某个元素名称之后添加*通配符,可以让ProxyFactoryBean在容器中搜索符合条件的所有Advisro并应用到目标对象。

使用通配符的范例

<beans>
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="..."/>
<property name="interceptorNames">
<list>
<value>global*</value>
</list>
</property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor">
</beans>

singleton:ProxyFactoryBean本质上是一个FactoryBean,所以我们可以通过singleton属性,指定getObject调用是返回同一个代理对象还是新的。

ProxyFactoryBean生成代理对象案例

配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<!-- 目标对象的Bean定义-->
<bean id="task"
class="org.springframework.mylearntest.aop.weaver.baseoninterface.MockTask" scope="prototype"/>

<!-- ProxyFactoryBean定义-->
<bean id="introducedTask" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype">
<property name="targetName">
<value>task</value>
</property>
<property name="proxyInterfaces">
<list>
<value>org.springframework.mylearntest.aop.weaver.baseoninterface.ITask</value>
<value>org.springframework.mylearntest.aop.weaver.proxyfactorybean.ICounter</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>introductionInterceptor</value>
</list>
</property>
</bean>

<!-- introductionInterceptor定义-->
<bean id="introductionInterceptor"
class="org.springframework.aop.support.DelegatingIntroductionInterceptor" scope="prototype">
<constructor-arg>
<bean class="org.springframework.mylearntest.aop.weaver.proxyfactorybean.CounterImpl"/>
</constructor-arg>
</bean>

</beans>
ICounter
package org.springframework.mylearntest.aop.weaver.proxyfactorybean;

/**
* @Author: whalefall
* @Date: 2020/7/22 23:34
*/
public interface ICounter {
void resetCounter();
int getCounter();
}
CounterImpl
package org.springframework.mylearntest.aop.weaver.proxyfactorybean;

/**
* @Author: whalefall
* @Date: 2020/7/22 23:35
*/
public class CounterImpl implements ICounter{
private int counter;

@Override
public void resetCounter() {
counter = 0;
}

@Override
public int getCounter() {
counter ++;
return counter;
}
}
TestProxyFactoryBean
package org.springframework.mylearntest.aop.weaver.proxyfactorybean;

import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* @Author: whalefall
* @Date: 2020/7/22 23:51
* @see DelegatingIntroductionInterceptor
*/
public class Test4ProxyFactoryBean {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("proxyfactorybean\\proxyfactorybean.xml");
Object proxy1 = context.getBean("introducedTask");
Object proxy2 = context.getBean("introducedTask");

System.out.println(((ICounter)proxy1).getCounter());//1
System.out.println(((ICounter)proxy1).getCounter());//2
System.out.println(((ICounter)proxy2).getCounter());//1
}
}

参考资料

  1. 书籍名称:Spring揭秘 作者:王福强