spring aop是将系统中业务横切逻辑通过动态代理技术jdk动态代理或者cglib方式织入到指定类指定方法的指定位置上去。
所以springaop其实就是对jdk动态代理和cglib字节码生成技术的一种封装。
连接点:针对一个方法 方法调用前 还是调用后 还是异常抛出后 。。
切点:哪些类的哪些方法
增强:横切逻辑
切面:连接点+增强+切点
如果需要spring生成代理类 那么就需要提供切面信息,或者提供增强,连接点 目标对象。
封装点一advice:
spring中各种增强接口:其实已经包含了连接点的信息了。
org.springframework.aop.BeforeAdvice
org.springframework.aop.AfterReturningAdvice
org.aopalliance.intercept.MethodInterceptor
org.springframework.aop.ThrowsAdvice
ThrowsAdvice的设计比较特别。ThrowsAdvice是标致接口 什么都没有其实什么都有了
规定 方法名afterThrowing
前三个参数 Method method,Object[] args,Object[] target 要么都有要么没有
Throwable 或者其子类
这样异常抛出的时候 spring就会自动去匹配调用哪个方法了,有点模仿try cache设计
怎么自动匹配的呢:
最简单就是通过反射获取各个方法异常类型,这样就能确定调用哪个方法了。
还有就是在生成代理对象时候维护个列表信息,这种方法可能性比较大。
封装点二advisor:提供方便的切点配置
spring提供的切面接口:切面很重要 因为切面包含了连接点+增强+切点所以你如果有了切面的信息那么就能通过spring代理工厂产生代理对象。
1.StaticMethodMatcherPointcutAdvisor 匹配静态方法
2.RegexpmethodpointAdvisor 用正则表达式匹配
3.DynamicMethodMatcherPointcut 和 DefaultPointcutAdvisor 动态方法意思就是不光具有静态方法匹配 还能进行实际运行时候的值匹配。 运行时候判断实际参数DefaultPointcutAdvisor 里面包含DynamicMethodMatcherPointcut 和advice 这样进行配置 首先会根据静态切点生成代理类 ,具体是否需要加入增强逻辑在允许时候判断动态切点匹配方法是否能匹配上(真实的参数进行判断才能决定是否需要增强 效率低下)。
4.ControlFlowPointCut 和 DefaultPointcutAdvisor 允许时候判断方法堆栈信息。 同上
5.ComposablePointCut 和 DefaultPointcutAdvisor 同上 定义多个PointCut可以取交集
生成代理的方式发展
ProxyFactory->ProxyFactoryBean->BeanNameAutoProxyCreator->DefaultAdvisorAutoProxyCreator->AnnotationAwareAspectJAutoProxyCreator(注解方式)
ProxyFactory 增强接口实现类 或者直接提供切面接口实现类 必须提供目标对象 。
ProxyFactoryBean 同上 只是这里使用了工厂bean配置文件的方式。 这两种方式很麻烦必须提供目标对象才能获取代理对象。
BeanNameAutoProxyCreator
DefaultAdvisorAutoProxyCreator
比如你配置了<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter" /> 还有切面对象 和 DefaultAdvisorAutoProxyCreator
getBean取到的就已经是代理对象了,spring会在bean生命周期BeanPostProcessor里面作相关的处理 。
配置一个Advisor已经包含了切点和增强的信息spring是可以知道哪些需要创建代理对象的,
BeanPostProcessor会干预所有的容器中的bean且能够拦截到bean实例化前实例化后的处理。这样创建代理bean就有可能发生了。
同样:
类同于ProxyFactory 用法的有AspectProxyFactory 类 设置个目标对象添加切面就可以生成代理对象了。
AnnotationAwareAspectJAutoProxyCreator(注解方式) 注解扫描 <aop:aspectj-autoproxy />更加简化 基于注解的切面扫描 引入基于Schema的aop命名空间进行配置 属性proxy-target-class 为false
============================细节======================
jdk动态代理和cglib比对
查看jdk动态代理源码可以看到:获取所有的接口然后通过接口的方法属性生成代理类 在获取代理类的构造函数通过反射的方式创建对象,在代理对象中存在目标对象 每次对目标对象的调用都是通过反射进行调用的。
cglib通过生成子类的方式来实现代理的,所以不能对finally private方法进行代理。 这样cglib生成了子类直接构造子类的实例句柄。执行时候效率高 生成代理类时间长。
总结:jdk方式创建代理对象快 执行对象方法慢
cglib方式创建代理对象慢 执行对象方法快
创建代理对象jdk快10倍左右。
执行cglib快10倍左右。
这样的特点如果是单例 或者对象池化了 那么使用cglib很划算
如果不是单例 那么使用jdk方式比较划算。
spring ProxyFactory 对jdk和gblib进行了封装。
ProxyFactoryBean几个属性有必要了解下:
interfaces/proxyInterfaces 代理需要实现的接口,可以是多个或者一个
interceptorNames 就是那些增强
singleton 是否单例
optimize 设置为true使用cglib 如果单例最好使用cglib 其他作用域最好使用jdk动态代理
proxyTargetClass 设置填true表示使用cglib 前面的设置存在优先级关系。有了这个忽视了前面设置的接口。
<!-- 普通方法名匹配切面其他4个类同 -->
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter" />
<bean id="sellerTarget" class="com.baobaotao.advisor.Seller" />
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice" />
<bean id="greetingAdvisor" class="com.baobaotao.advisor.GreetingAdvisor"
p:advice-ref="greetingAdvice" />
<bean id="parent" abstract="true"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" />
<bean id="waiter" parent="parent" p:target-ref="waiterTarget" />
<bean id="seller" parent="parent" p:target-ref="sellerTarget" />
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
public boolean matches(Method method, Class clazz) {
return "greetTo".equals(method.getName());
}
public ClassFilter getClassFilter(){
return new ClassFilter(){
public boolean matches(Class clazz){
return Waiter.class.isAssignableFrom(clazz);
}
};
}
}
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object obj) throws Throwable {
String clientName = (String)args[0];
System.out.println(obj.getClass().getName()+"."+method.getName());
System.out.println("How are you!Mr."+clientName+".");
}
}
总结:以上代码是通过ProxyFactoryBean方式获取代理对象其实和DefaultAdvisorAutoProxyCreator大同小异
如果通过spring容器获取一个bean 首先ProxyFactoryBean获取者DefaultAdvisorAutoProxyCreator会查找到你是否配置了advisor如果配置了 查看这个classfilter 是否存在这样的类 如果有 查找methodMatcher
看是否存在匹配的方法 如果有根据advisor里面配置的advice获取到增强 生成代理对象返回给客户端,这就是代理对象的生成过程。
ProxyFactoryBean将逻辑放在工厂方法里面。
DefaultAdvisorAutoProxyCreator放到后处理bean里面。
还有
基于@AspectJ注解方式 <aop:aspectj-autoproxy />这个可以简洁扫描。
基于Schama配置切面如果不能使用jdk5那么就使用这种配置方式了。
eg:
<aop:config>
<aop:aspect ref="">
<aop:before pointcut="" method=""/>//这里只要一个类就行 指定方法名
</aop:aspect >
</aop:config>
或者
<aop:config>
<aop:advisor advice-ref="" pointcut=""/> //这里的advice需要实现增强接口
</aop:config>
可以参考spring3.x企业应用开发实战一书第七章 很详细
原理是一样 用法不一样 所以他们可以混用。
补充:同一对象内的嵌套方法调用拦截失败问题。
无论spring使用的jdk动态代理或者cglib对目标对象进行代理,最后方法调用肯定是在目标对象上的而不是代理对象。
public class TestClass{
public void testMethod1(){
};
public void testMethod2(){
testMethod1();
};
}
如果TestClass testMethod1 testMethod2 两个方法都被aop进行了代理那么在代理对象调用方法testMethod2()时候testMethod2方法确实被代理了 但是testMethod1却没有被代理 ,
原因 调用testMethod1其实发生在目标对象的内部并不是在代理对象上,所以如果一定要调用代理后的testMethod1可以这样:((TestClass)AopContext.currentProxy().testMethod1();
这样就没有问题了currentProxy()这个方法也是从Threadlocal中获取到的当前代理对象,由spring进行维护的。