aop是什么
aop是面向切面编程(aspect oriented programing)的简称。aop的出现并不是要完全替代oop,仅是作为oop的有益补充。
aop的应用场合是有限的,一般只适合于那些具有横切逻辑的应用场合。
性能监测
访问控制
事务管理
日志记录
...
aop中的概念
连接点(joinpoint)
一个类或一段程序代码拥有一些具有边界
性质的特定点,这些代码中的特定点就称为连接点
。比如,
类开始初始化前,后
类中某个方法调用前,后
方法抛出异常后
...
连接点由两个信息确定:
用方法表示的程序执行点
用相对点表示的方位
如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置。
spring使用切点对执行点定位,而方位则在增强类型中定义.
切点(pointcut)
每个程序类都可能有多个连接点,aop通过切点
定位特定点。类比于数据库查询:连接点相当于数据库中的记录,切点相当于查询条件。
切点和连接点不是一对一关系,一个切点可以匹配多个连接点。
切点只定位到某个方法上,如果希望定位到具体的连接点上,还需要提供方位信息。
增强(advice)
增强是织入到目标类连接点上的一段代码.它除用于描述一段代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位
。结合执行点方位信息和切点信息,就可以找到特定的连接点了。
spring提供的增强接口都是带方位名的
:BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等。
目标对象(target)
增强逻辑的织入目标类。
引介(introduction)
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原来没有实现某个接口,通过引介,也可以动态的为业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
织入(weaving)
织入是将增强添加对目标类具体连接点的过程。aop有三种织入方式:
编译期织入,这要求使用特殊的java编译器
类装载期织入,这要求使用特殊的类装载器
动态代理织入,在运行期为目标类添加增强,生成子类
spring使用第3种方式织入,aspectj使用第1,2种方式。
代理(proxy)
一个类被aop织入增强后,就产生一个结果类,它融合了原来类和增强逻辑的代理类。我们可以采用调用原来类相同的方式调用代理类。
切面(aspect)
切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义。spring aop负责实施切面,它将切面所定义的横切逻辑织入到切面所指定的连接点钟。
创建增强类
前置增强
场景:高级餐厅的服务员在回答顾客之前都会说'你好!...'.
public class Waiter {
public void check(String name){
System.out.println("结账?"+name);
}
public void serve(String name){
System.out.println("要点什么?"+name);
}
}
前置增强
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class GreetAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object obj)throws Throwable {
String clientName=args[0].toString();
System.out.println("你好!"+clientName);
}
}
测试
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class TestBeforeAdvice {
public static void main(String[] args){
Waiter target=new Waiter();
BeforeAdvice advice=new GreetAdvice();
ProxyFactory pf=new ProxyFactory();//spring提供的代理工厂
pf.setTarget(target);//设置代理目标
pf.addAdvice(advice);//添加增强
Waiter proxy=(Waiter)pf.getProxy();//代理实例
proxy.serve("TheViper");
proxy.check("TheViper");
}
}
结果
你好!TheViper
来点什么?TheViper
你好!TheViper
结账?TheViper
ProxyFactory
内部使用JDK代理或CGLib代理,将增强应用到目标类。还可以将接口设置为代理目标。
...
ProxyFactory pf=new ProxyFactory();
pf.setInterfaces(target,getClass().getInterfaces);
pf.setTarget(target);
...
在spring中配置
application-context.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd ">
<bean id='greetAdvice' class='com.GreetAdvice'></bean>
<bean id='target' class='com.Waiter'></bean>
<bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean'
p:target-ref='target' p:interceptorNames='greetAdvice'/>
</beans>
测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBeforeAdvice {
public static void main(String[] args){
ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml");
Waiter waiter=(Waiter)ctx.getBean("waiter");
waiter.serve("TheViper");
waiter.check("TheViper");
}
}
ProxyFactoryBean
常用配置:
target:代理的目标对象
proxyInterfaces:代理所要实现的接口,可以是多个接口。该属性还有一个别名属性interfaces
interceptorNames:需要植入目标对象的Bean列表。这些Bean必须是实现了
org.aopalliance.intercept.MethodInterceptor
或org.springframework.aop.Advisor
的Bean,配置中的顺序对应调用的顺序。singleton:返回的代理是否为单例,默认为单例
optimize:设置为true时,强制使用CGLib代理。对于singleton代理,推荐使用CGLib,对于其他作用域类型的代理,最好使用JDK代理。因为CGLib创建代理速度慢,而创建出的代理对象运行效率较高。JDK代理的表现与之相反
proxyTargetClass:是否对类进行代理(不是对接口进行代理),设置为true时,使用CGLib
从上面spring配置可以看到,ProxyFactoryBean
用的是JDK代理,如果将proxyTargetClass设置为true后,无需再设置proxyInterfaces属性,即使设置了也会被忽略。
后置增强
场景:服务员和顾客交流后,礼貌的说'please enjoy yourself'.
后置增强
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class GreetAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object returnObj,Method method,Object[] args,Object obj)throws Throwable {
//returnObj:目标实例方法返回的结果 method:目标类的方法 args:目标实例的方法参数 obj:目标类实例
System.out.println("please enjoy yourself!");
}
}
spring配置
...
<bean id='greetBeforeAdvice' class='com.GreetAdvice'></bean>
<bean id='greetAfterAdvice' class='com.GreetAfterAdvice'></bean>
<bean id='target' class='com.Waiter'></bean>
<bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean'
p:target-ref='target' p:interceptorNames='greetBeforeAdvice'/>
...
结果
你好!TheViper
要点什么?TheViper
please enjoy yourself!
你好!TheViper
结账?TheViper
please enjoy yourself!
环绕增强
环绕增强允许在目标类方法调用前后织入横切逻辑,它综合实现了前置,后置增强两种的功能。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class GreetingInterceptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object[] args=invocation.getArguments();
String clientName=args[0].toString();
System.out.println("你好!"+clientName);
Object obj=invocation.proceed();//通过反射调用目标方法
System.out.println("please enjoy yourself!");
return obj;
}
}
...
<bean id='greetingInterceptor' class='com.GreetingInterceptor'></bean>
<bean id='target' class='com.Waiter'></bean>
<bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean'
p:target-ref='target' p:interceptorNames='greetingInterceptor,greetAfterAdvice'/>
...
异常抛出增强
异常抛出增强最适合的场景是事务管理。
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
public class TransactionManager implements ThrowsAdvice{
public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{
System.out.println("method:"+method.getName());
System.out.println("抛出异常"+ex.getMessage());
System.out.println("回滚");
}
}
引介增强
引介增强不是在目标方法周围织入增强,而是为目标类创建新的方法和属性
。所以引介增强的连接点是类级别的,非方法级别.
创建切面
前面织入增强时,都是织入到目标类的所有方法中。这节将会介绍如何让增强提供连接点方位的信息,如织入到方法前面,后面等,而切点进一步描述具体织入哪些类的哪些方法上。
spring通过org.springframework.aop.Pointcut接口
描述切点,Pointcut
由ClassFilter
和MethodMatcher
构成。
通过ClassFilter
定位到某些特定类上,通过MethodMatcher
定位到某些特定方法上。
此外,spring还提供注解切点和表达式切点,两者都使用AspectJ的切点表达式语言。
切点类型
静态方法切点
动态方法切点
注解切点
表达式切点
流程切点
复合切点
静态普通方法名匹配切面
StaticMethodMatcherPointcutAdvisor
代表一个静态方法匹配切面,它通过StaticMethodMatcherPointcut
定义切点,通过类过滤和方法名匹配定义切点。
例子,Seller类也有serve方法
public class Seller {
public void serve(String name){
System.out.println("seller说:要点什么?"+name);
}
}
前置增强(advice)
public class GreetingBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object obj)throws Throwable {
String clientName=args[0].toString();
System.out.println("你好!"+clientName);
}
}
切面(advisor)
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
public class GreetAdvisor extends StaticMethodMatcherPointcutAdvisor{
@Override
public boolean matches(Method method, Class<?> cls) {//切点方法匹配
return "serve".equals(method.getName());
}
//切点类匹配规则:Waiter的类或子类
public ClassFilter getClassFilter(){
return new ClassFilter(){
public boolean matches(Class cls){
return Waiter.class.isAssignableFrom(cls);
}
};
}
}
spring配置
...
<bean id='greetBeforeAdvice' class='com.GreetingBeforeAdvice'></bean>
<bean id='waiterTarget' class='com.Waiter'></bean>
<bean id='sellerTarget' class='com.Seller'></bean>
<!-- 向切面注入前置增强 -->
<bean id='greetingAdvisor' class='com.GreetAdvisor' p:advice-ref='greetBeforeAdvice'></bean>
<bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean'
p:interceptorNames='greetingAdvisor'/>
<bean id='waiter' parent='parent' p:target-ref='waiterTarget'></bean><!-- waiter代理 -->
<bean id='seller' parent='parent' p:target-ref='sellerTarget'></bean><!-- seller代理 -->
...
<bean id='parent' abstract='true' ...>
表示通过一个父<bean>定义公共的配置信息。
测试
ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml");
Waiter waiter=(Waiter)ctx.getBean("waiter");
Seller seller=(Seller)ctx.getBean("seller");
waiter.serve("TheViper");
waiter.check("TheViper");
seller.serve("TheViper");
结果
你好!TheViper
waiter说:要点什么?TheViper
waiter说:结账?TheViper
seller说:要点什么?TheViper
可以看到切面只是织入到Waiter.serve()方法调用前的连接点上,而Waiter.check()和Seller.serve()都没有织入切面。
静态正则表达式匹配切面
RegexpMethodPointcutAdvisor
是正则表达式方法匹配的切面实现类。该类已经是功能齐备的的实现类了,一般情况下,无需扩展该类。
...
<bean id='regexpAdvisor' class='org.springframework.aop.support.RegexpMethodPointcutAdvisor'
p:advice-ref='greetBeforeAdvice'>
<property name="patterns">
<list>
<value>.*che.*</value>
</list>
</property>
</bean>
<bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean'
p:interceptorNames='regexpAdvisor'/>
...
这里定义了一个匹配模式.*che.*
,它会匹配check()方法。
...
waiter.serve("TheViper");
waiter.check("TheViper");
...
waiter说:要点什么?TheViper
你好!TheViper
waiter说:结账?TheViper
动态切面
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut{
private static List<String> specialCients=new ArrayList<String>();
static{
specialCients.add("Tom");//添加白名单
specialCients.add("TheViper");
}
//切点类匹配规则:Waiter的类或子类
public ClassFilter getClassFilter(){//静态匹配
return new ClassFilter(){
public boolean matches(Class cls){
System.out.println("对"+cls.getName()+"类做静态检查");
return Waiter.class.isAssignableFrom(cls);
}
};
}
public boolean matches(Method method, Class<?> cls) {//切点方法静态匹配
System.out.println("对"+cls.getName()+"类的"+method.getName()+"方法做静态检查");
return "serve".equals(method.getName());
}
@Override
public boolean matches(Method method, Class<?> cls, Object[] args) {//动态匹配
System.out.println("对"+cls.getName()+"类的"+method.getName()+"方法做动态检查");
String clientName=args[0].toString();
return specialCients.contains(clientName);
}
}
匹配规则:目标类为Waiter或其子类,方法名为serve,动态传入的参数name必须在白名单中存在。
<bean id='dynamicAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor'>
<property name="pointcut">
<bean class='com.GreetingDynamicPointcut'/>
</property>
<property name="advice">
<bean class='com.GreetingBeforeAdvice'/>
</property>
</bean>
<bean id='waiter1' class='org.springframework.aop.framework.ProxyFactoryBean'
p:interceptorNames='dynamicAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter1");
waiter.serve("Peter");
waiter.check("Peter");
waiter.serve("TheViper");
waiter.check("TheViper");
对com.Waiter类做静态检查
对com.Waiter类的serve方法做静态检查
对com.Waiter类做静态检查
对com.Waiter类的check方法做静态检查
对com.Waiter类做静态检查
对com.Waiter类的clone方法做静态检查
对com.Waiter类做静态检查
对com.Waiter类的toString方法做静态检查
//上面是织入前spring对目标类中的所有方法进行的静态切点检查
对com.Waiter类做静态检查
对com.Waiter类的serve方法做静态检查
对com.Waiter类的serve方法做动态检查
waiter说:要点什么?Peter
对com.Waiter类做静态检查
对com.Waiter类的check方法做静态检查
//静态方法检查没通过,不用动态检查了
waiter说:结账?Peter
对com.Waiter类的serve方法做动态检查
//第二次调用不用执行静态检查
你好!TheViper
//动态检查,满足白名单,执行前置增强
waiter说:要点什么?TheViper
waiter说:结账?TheViper
定义动态切点时,切勿忘记同时覆盖getClassFilter()和matches(Method method,Class cls)方法,通过静态切点检查可以排除掉大部分不符合匹配规则的方法。
流程切面
spring的流程切面由DefaultPointcutAdvisor
和ControlFlowPointcut
实现。流程切点代表由某个方法直接或间接发起调用的其他方法。
定义Waiter的代理
public class WaiterDelegate {
private Waiter waiter;
public void setWaiter(Waiter waiter) {
this.waiter = waiter;
}
public void service(String name){
waiter.serve(name);
waiter.check(name);
}
}
<bean id='controlFlowPointcut' class='org.springframework.aop.support.ControlFlowPointcut'>
<constructor-arg type='java.lang.Class' value='com.WaiterDelegate'/>
<constructor-arg type='java.lang.String' value='service'/><!-- 指定流程切点的方法 -->
</bean>
<bean id='controlFlowAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor'
p:pointcut-ref='controlFlowPointcut' p:advice-ref='greetBeforeAdvice'/>
<bean id='waiter2' class='org.springframework.aop.framework.ProxyFactoryBean'
p:interceptorNames='controlFlowAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter2");
WaiterDelegate wd=new WaiterDelegate();
wd.setWaiter(waiter);
waiter.serve("TheViper");
waiter.check("TheViper");
wd.service("TheViper");
waiter说:要点什么?TheViper
waiter说:结账?TheViper
//直接调用,增强不起作用
你好!TheViper
waiter说:要点什么?TheViper
你好!TheViper
waiter说:结账?TheViper
复合切面
spring提供ComposablePointcut
把两个切点组合起来,通过切点的复合运算表示。ComposablePointcut
本身也是一个切点,它实现了Pointcut
接口。
交集运算的方法
并集运算
import java.lang.reflect.Method;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.NameMatchMethodPointcut;
public class GreetingComposablePointcut {
public Pointcut getIntersectionPointcut(){
ComposablePointcut cp=new ComposablePointcut();
Pointcut pt1=new ControlFlowPointcut(WaiterDelegate.class,"service");
MethodMatcher pt2=new NameMatchMethodPointcut(){
public boolean matches(Method method, Class<?> cls) {// 切点方法静态匹配
return "check".equals(method.getName());
}
};
return cp.intersection(pt1).intersection(pt2);
}
}
<bean id='gcp' class='com.GreetingComposablePointcut'></bean>
<bean id='composableAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor'
p:pointcut='#{gcp.intersectionPointcut}' p:advice-ref='greetBeforeAdvice'></bean>
<bean id='waiter3' class='org.springframework.aop.framework.ProxyFactoryBean'
p:interceptorNames='composableAdvisor' p:target-ref='waiterTarget'/>
\ #{gcp.intersectionPointcut}表示引用gcp.getIntersectionPointcut()方法返回的复合切点
Waiter waiter=(Waiter)ctx.getBean("waiter3");
WaiterDelegate wd=new WaiterDelegate();
wd.setWaiter(waiter);
waiter.serve("TheViper");
waiter.check("TheViper");
wd.service("TheViper");
waiter说:要点什么?TheViper
waiter说:结账?TheViper
//直接调用,增强不起作用
waiter说:要点什么?TheViper
你好!TheViper//匹配check方法
waiter说:结账?TheViper
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。