与aop相关的术语
1,切面 :切面是我们要实现的交叉功能。他是应用系统模块化的的一些领域,比如日志功能就是一个切面。
2,连接点 :指在应用程序执行的过程中插入切面的地点,这个地点可能是方法的调用,或者异常的抛出。
* Spring只支持方法连接点。
3,通知 :通知只得是一个切面的具体实现,通知在连接点处被插入到程序中 。
4,切入点:定义通知因该应用在那些连接点,不是所有的连接点上都要使用通知,所以可以认为切入点是连接点的子集。
5,引入 :为已存在的类增加新的方法或属性,其实质是通过动态代理的方法来实现的。
6,目标对象 :就是被通知的对象。通过AOP引入通知,可以使目标对象更加更加专注于业务逻辑。而以前目标对象中还要处理一些,日志,安全等交叉逻辑。
7,代理:代理是将通知应用到目标对象上之后生成的对象,对于目标对象的使用者来说,目标对象和代理对象是一样的,只不过代理对象为目标对象增加了交叉功能。
8,织入 :是创建代理对象的过程 ,也就是说是将切面应用到目标对象从而创建代理对象的过程。织入发生在目标对象生命周期的多个点上: 编译时,类加载时和运行时
SPRING AOP 的实现
AOP实现的关键是 :能够创建连接点 , 定义切面在哪些连接点上切入 。在spring 框架中,所有的通知都是使用java编写,在那些连接点上应用通知,通常定义在配置文件中。
Spring 的运行时通知对象 :
代理Bean只有在第一次被系统使用的时候才被创建,如果使用ApplicationContext,代理对象在BeanFactory载入所有Bean是被创建。
Spring 有两种方式创建代理 :
1) 目标对象实现了一个或多个借口暴露的方法,Spring 将采用动态代理方式创建
代理类。
2) 如果目标对象没有实现任何接口,Spring采用CGLIB生成目标对象的子类。在
创建这个子类时织入通知 , 并且将目标对象的调用委托给这个子类。
* 接口创建代理优于对类创建代理, 因为这样会产生更加松散的耦合 ,
* final 方法 不能通过CGLIB类创建代理 。
Spring 中的通知类型 :
通知类型 | 实现接口 | 描述 |
Around | org.springframework.interceptor.MehtodInterceptor | 拦截目标对象的方法调用,可以决定目标对象是否被调用,也可以改变方法的返回值 |
After | Org.springframework.aop .AfterReturnningAdvice | 在方法调用之后被执行,不能决定目标方法是否被调用,但是可以获得目标对象的返回值。但是不能改变这个值 |
Before | Org.springframework.MethodBeforeAdvice | 在方法调用之前被执行,目标方法一定被执行 |
Throws | Org.springframework.ThrowsAdvice | 当方法抛出异常的时候被调用。 |
public interface.MethodInterceptor extends org.aopalliance.intercept.Interceptor {
/**
@param invocation 可以通过ivocation.proceed()方法来调用目标对象,并且
* 获得返回值,MethodInterceptor一个很重要的特征是可以真正的控制目标
* 对象的方法是否可以被调用。并且可以改变目标方法的返回值。
*/
public Object invoke(org.aopalliance.intercept.MethodInvocation invocation)
throws Throwable;
}
public interface AfterReturningAdvice extends AfterAdvice {
/**
* Callback after a given method successfully returned.
* @param 目标方法调用后的返回值
* @param 被调用的目标方法
* @param args 被调用的目标方法的参数
* @param target 目标方法所属的对象.
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable;
}
public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* Callback before a given method is invoked.
* @param method 即将被执行的方法
* @param args 即将被执行的方法所需要的参数。
* @param target 即将被调用的方法所属的对象.
void before(Method method, Object[] args, Object target) throws Throwable;
}
使用Spring生成代理的配置代码 :
<bean id=”proxyBean” class=”org.springframework.aop.framework.ProxyFactoryBean”>
<property name=”proxyInterfaces”>
<value>interfaceName</value>
</property>
<property name=”interceptorNames”>
<value>YourAdvice</value>
</property>
<property name=”target”>
<ref bean=”targetBean”/>
</property>
</bean>
单独对一个对象引入通知,通知会被织入到目标对象的所有方法中,这种通知的引入方式不够灵活,所以通知一般情况下会合切入点配合使用
定义切入点 :
上面我们说到单独引入通知,只能将通知织入到一个目标对象的所有的方法上面,缺乏灵活性,引入切入点的目的就是可以更加灵活的指定在目标对象的那些方法上织入通知。
切入点定义了一个特定的类型的特定方法是否满足一条特定的规则 。
如果这个方法满足,Spring就在这个方法上应用通知 。通过切入点,我们可以灵活
的将定义将通知应用在什么样的地方。
简单的说 切入点 = 类名字 + 方法名称
Spring 根据需要织入的通知的类型和目标对象的方法来定义切入点。定义其入点的基本参考依据是 类名和方法签名。
切入点框架的核心接口是 : Pointcut
public interface Pointcut{
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
ClassFilter 接口用于判断一个类是否满足切入点的要求 :
public interface ClassFilter{
boolean matches( Class clazz ) ;
}
ClassFilter 有一个简单的实现类, ClassFilter.TRUE( ClassFilter的一个内部类) ,该类适合任何类的ClassFilter的实现,即matches方法永远返回true .在根据方法决定切入点的时候,适合使用这个子类。很多spring自带的Pointcut类型,使用的都是这个内部类
MehtodMatcher 是通过方法来确定切入点的。
public interface MehtodMatcher{
boolean matches( Method m Class targetClass );
boolean isRuntime();
boolean matches( Mehtod m , Class targetClass , Object[] arguments ) ;
}
该接口的三个方法分别对应目标对象生命周期的特定时期。
第一个方法根据目标类和方法判定是否需要织入通知,因为可以动态判定所以可以在AOP代理被创建的时候调用一次。
如果第一个方法返回为true , isRuntime方法会被调用 ,用来决定切入点的类型
切入点的类型一共分为两种,静态和动态, 静态的含义是通知总是被执行,动态的含义是要根据参数的值来决定通知是否被执行。如果采用的是静态切入点,那么第三个方法永远不会被调用。
* 从性能的角度考虑,尽量的使用静态切入点。
使用Spring提供的静态切入点 :
由于静态切入点只在代理创建的时候执行一次,而不需要再运行的过程当中每次都执行
所以在性能上比动态切入要好。是首选的切入点方式。 Spring为创建静态切入点提供了方便的父类 :StaticMatchMehtodPoincut,如果需要创建静态切入点,可以从这个类中继承。
但是对于大多是情况而言,我们使用Spring提供的静态切入点就可以了( NamedMatchMethodPointcut )。
StaticMatchMethodPointcut 主要是通过方法名称进行过滤,即只要方法的名字符合其要求那么类名字就不做判断,我们可以认为StaticMatchMethodPointcut中,getClassFilter返回的是ClassFilter.TRUE类型。为什么可以忽略类名字的过滤和ProxyFactoryBean的使用方式有关。因为我们在配置ProxyFactoryBean时总是制定一个目标对象,然后对其生成代理,所以目标对象的类型就可以在过滤中忽略了。
public interface StaticMatchMethodPointcut{
public void setMappedName( String ) ;
public void setMappedNames( String ) ;
........
}
使用Advisor (切面)
大多数切面是由定义切面行为的通知和定义切面在什么地方执行的切入点组合而成的。
即 切面 = 通知 + 切入点。 在spring中使用PointcutAdvisor 来表示 通知( advice ) 和
切入点( Pointcut )的组合
public interface PointcutAdvisor{
Pointcut getPointcut() ;
Advice getAdvice();
}
Spring提供了一个Advisor的实现类 :
org.springframework.aop.support.NamedMatchMethodPointcutAdvisor
该类在Pointcut的基础之上增加了setAdvice的方法,将Pointcut和Advice组和成了一个整体。
使用ProxyBeanFactory 对目标对象生成通知代理
通过使用ProxyBeanFactory可以指定在那些类中使用Advisor .
使用自动代理( BeanNameAutoProxyCreator ,DufaultAdvisorAutoProxyCreator )
可以简化对类名字的配置。
ProxyFactoryBean是通过代理实现将通知织入目标对象的.
AOP 编码基本步骤
1,编写业务接口
2,编写业务接口的实现( 及 目标对象 )
3, 在配置文件中声明目标对象
<bean id="target" class="xxxxx"/>
4, 编写通知(Advice),即要织入到业务对象中的切面功能的实现。
5, 在配置文件中声明通知对象
<bean id="advice" class=""/>
6,在配置文件中定义切入点,将通知和方法关联起来。
<bean id="advisor"
class="org.springframework.aop.support.
NamedMatchedMehtodPointcutAdvisor">
<property name="mappedName">
<value>order*</value>
</property>
<property name="advice">
<bean ref=”advise”/>
</property>
</bean>
切入点的类型可以采用Spring 的静态切入实现类:
NamMatchMethodPointcutAdvisor :通过方法名称匹配。
也可以使用RegexpMethodPointcut :通过正则表达式匹配
7,使用ProxyBeanFactory 定义业务接口,将切入点和业务接口关联起来:
<bean id="service" class="org.springframework.aop.framework.ProxyBeanFactory">
<property name="proxyInterfaces">
<value>xxx.xxService</value>
</property>
<property name="interceptorNames">
<list>
<value>advisor</value>
</list>
</property>
<property name="target">
<ref name="target"/>
</property>
</bean>
使用自动代理
我们可以使用ProxyBeanFactory将通知引入到一个类中,但是当需要引入通知的类很多是,使用ProxyBeanFactory将会带来很大的工作量,这时候,我们就需要使用自动代理,通过自动代理,可以方便的同时为多个类引入通知。
Spring 提供的自动代理包括有,BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxy
BeanNameAutoProxyCreater 通过匹配类的名称来引入通知 。
<bean id=”creator”
class=”org.springframewrok.aop.framework.autoproxy.BeanNameAutoProxyCreator”>
<property name=”beanNames”>
<value>*Service</value>
</property>
<property name=”interceptorNames”>
<list>
<value>xxxAdvisor</value>
</list>
</property>
</bean>
上述配置文件表明将xxAdisor织入到所有以Service结尾的类中。
DefaultAdivsorAutoProxyCreator 将所有Advisor 织入到所有与Advisor匹配的Bean中
使用时只需要在配置文件中声明一个该类的实例即可。其本身是个
<bean id=”autoProxyCreator”
class=”org.springframework.aop.framework.autoproxy
.DefaultAdvisorAutoProxyCreator”/>”
AOP整理
单独使用ProxyFactoryBean引入通知,会导致通知被织入到目标对象的所有方法中
<bean id=”proxyBean” class=”xxxx.ProxyFactoryBean”>
<property name=”proxyInterfaces”>
<value></value>
</property>
<property name=”interceptors”>
<list>
<value>xxxAdvice/value>
</list>
</property>
<property name=”target”>
<ref bean=”targetBean”/>
</property>
</bean>
结合Advisor 可以对目标对象的不同方法织入不同的通知。
<bean id=”myAdvice” class=”package.MyAdivce”></bean>
<bean id=”advisor” class=”org.springframework.xxxx.NameMatchMethodPointcutAdvisor”>
<property name=”mapNames>
<value>methodA</value>
<value>methodB</value>
</property>
<property name=”advice”>
<ref bean=”myAdvice”/>
</property>
</bean>
<bean id=”proxyBean” class=”xxxxxxxx.ProxyFactoryBean”>
<property name=”proxyInterfaces”>
<value>xxxxx</value>
</property>
<property name=”interceptors”>
<list>
<value>此处事一个advisor,而不是单独得通知了</value>
</list>
</property>
<property name=”target”>
<bean ref=”targetBean”>
</property>
</bean>
使用了BeanNameAutoProxyCreator 可以按照名字将Advisor 织入到多个类中
<bean id=”xxx” class=”xxx。BeanNaneAutoProxyCreator”>
<property name=”beanNames”>…</property>
<property name=”interceptorNames”>
<value></value>
<value></value>
</property>
</bean>
使用DefaultProxyAdvisorProxyCreator 可以将所有Advisor 织入到所有Bean中
<bean id=”xxx” class=”xxxx.DefaultAdvisorProxyAutoCreator”>
</bean>