(一) AOP的几种实现机制
1.静态代理
缺点 :
不灵活.
原因 : 需要其他编译器预编译,再织入到系统中,方可使用.
2.动态代理
缺点 :
a.性能 比 静态代理 稍逊. 随着jvm的发展 , 差距越来越小 .
b.模块类需要实现接口 . 动态代理只对接口有效 .
3.动态字节码增强
缺点 :
使用final修饰过的类和方法 是无法动态增强的.
原因 : 该方案的思想是 生成目标类的子类,达到将横切逻辑加入其中的新方法覆盖父类的.因此,使用final修饰符的无法达到.
4.java代码生成
缺点 : 需要专门的部署接口 / 部署工具 .目前基本退役 .
5.自定义类加载器
缺点 : 本身这种策略 是很强大的 .
但是 , 问题出现在类加载器本身 . 很多应用程序都有自己的类加载体系控制整个系统 , 这种情况使用它 ,很可能造成问题.
6.AOL 扩展
缺点 : 很强大 , 但很难掌握的一种方式. 首先需要学一门拓展了旧有语言的aol语言 , 甚至是新的一门aol语言.
(二) 一些aop概念(基于AspectJ)
1.Joinpoint :
解释 :
织入操作的系统执行点.
包含 :
方法调用 , 方法调用执行 , 构造方法调用 , 构造方法执行 , 字段设置 , 字段获取 , 异常处理执行 , 类初始化(静态类型/静态块初始化)
2.Pointcut :
解释 :
joinpoint的表述形式 . pointcut中包含了joinpoint的相关信息 .
包含 :
确定的方法名称 , 正则表达式 , 特定的pointcut表述语言 .
pointcut的运算 : 可以使用 逻辑与 或 逻辑或 (&& , ||) 进行运算 .
3.Advice :
解释 : 代表了 将要织入到joinpoint的横切逻辑 . 注意 代表的是 横切逻辑 , 逻辑 , 辑 ..
包含 :
before advice
after advice :
after returning advice
after throwing advice
after (final) advice
around advice (也称 interceptor , 拦截器)
Introduction :
解释 : 与前几种advice 不同 .
introduction 不是根据切入时机来区别对待(或说独立出来) , 而是因为它提供的功能与前几个advice不同.
introduction 可以为原有的对象添加新的特性或行为.
4.Aspect :
解释 : aspect 是 对系统中 横切关注点 以及 横切逻辑 进行封装的 aop概念的实体.
它可以包含 多个pointcut 以及 相关的advice 定义.
5.Weaver :
解释 : 织入器.
将所有 包含了pointcut,advice的aspect 织入到系统中的工具.
它可能是 编译器 , 也可以是 自定义类加载器 , 或者 proxyfactory类 etc.
6.TargetObject :
解释 : 目标对象. 将符合pointcut的条件/规则 的 横切逻辑 以及 横切点 , 在织入过程中 织入到指定的对象.这个对象成为目标对象 .
(三) Spring AOP
1.实现机制 : 采用动态代理 以及 字节码生成技术.
a.动态代理
代理模式 : (可以让请求转发 , 最重要的是在转发过程中 可以添加 访问限制等功能)
代理类 实现 和 目标类相同的 接口 , 并持有改接口的引用 , 代理方法中调用目标类的目标方法(通常是利用接口的引用调用)
但是对于 aop来讲 , 当有一个新的接口 , 以及新接口的实现类 中也有要拦截的方法(即joinpoint相同) , 就需要创建一个新的代理类,这样不好不好~
动态代理模式 :
动态代理模式机制的实现 主要由一个类(java.lang.reflect.Proxy) 与 一个接口(java.lang.reflect.InvocationHandler) 组成.
具体用法 :
①.代理类 实现InvocationHandler接口 , 进行横切逻辑书写.
②.使用Proxy类创建对应的目标类. 通常要提供一个 loader , interface , impl class
Demo :
public class RequestCtrlInvocationHandler implements InvocationHandler{
private static final Logger logger = LoggerFactory.getLogger(RequestCtrlInvocationHandler.class);
Object target ;
public RequestCtrlInvocationHandler(Object target){
this.target = target ;
}
@Override
public Object invoke(Object proxy , Method method , Object[] args) throws Throwable{
if ( "request".equals(method.getMethodName()) ){
TimeOfDay startTime = new TimeOfDay(0,0,0);
TimeOfDay endTime = new TimeOfDay(5,59,59);
TimeOfDay currentTime = new TimeOfDay();
if(currentTime.isAfter(startTime) && currentTime.isBefore(endTime) ){
logger.warn("between 0:00 AM and 6:00 AM , the service call is not avilibale ~");
return null ;
}
return method.invoke(target , args);
}
return null ;
}
public static void main(String[] args){
IRequestable requestable = Proxy.newProxyInstance(
ProxyRunner.class.getClassLoader()
, new Class[]{IRequestable.class}
, new RequestCtrlInvocationHandler(new RequestableImpl())
);
requestable.request();
ISubject subject = Proxy.newProxyInstance(
ProxyRunner.class.getClassLoader()
, new Class[]{ISubject.class}
, new RequestCtrlInvocationHandler(new SubjectImpl())
);
subject.request();
}
}
缺点:
动态代理实现的aop , 只能对 实现了某个接口的实现类(目标对象) 进行代理 , 如果该目标类没有实现任何接口 , 就拿爪儿了 ~
b.字节码生成(通常借助CGLIB动态字节码生成库).
a.原理 : 通过字节码动态生成 技术 , 为指定的目标类 生成一个 相应的子类.子类当然可以对父类进行拓展啦.
b.步骤 : 利用cglib库
①.代理类需要实现 net.sf.cglib.proxy.CallBack 接口 , 或者实现 net.sf.cglib.proxy.MethodInterceptor接口 , 进行横切逻辑书写
②.通过Enhancer类为 目标对象动态生成一个子类.并将代理类的横切逻辑附加到子类中.
Demo :
public class RequestCtrlCallback implements MethodInterceptor{
private static fianl Logger logger = Logger.getLogger(RequestCtrlCallback.class);
@Override
public Object intercept(Object target , Method method , Object[] args , MethodProxy proxy) throws Throwable{
if ( "request".equals(method.getMethodName()) ){
TimeOfDay startTime = new TimeOfDay(0,0,0);
TimeOfDay endTime = new TimeOfDay(5,59,59);
TimeOfDay currentTime = new TimeOfDay();
if(currentTime.isAfter(startTime) && currentTime.isBefore(endTime) ){
logger.warn("between 0:00 AM and 6:00 AM , the service call is not avilibale ~");
return null ;
}
return proxy.invokeSuper(target , args);
}
return null ;
}
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperClass(IRequestable.class);
enhancer.setCallback( new RequestCtrlCallback() );
IRequestable proxy = (IRequestable) enhancer.create();
proxy.request();
}
}
Spring AOP 相关概念
1.JoinPoint
只支持方法级别的joinpoint , 确切的说 只支持 "方法执行" 类型的joinpoint .
2.PointCut
a.接口定义 :
public interface Pointcut {
// 匹配将要被执行织入操作的 对象
ClassFilter getClassFilter();
// 匹配将要被执行织入操作的 方法
MethodMatcher getMethodMatcher();
// 默认对所有对象 以及对象上的所有支持的 pointcut进行匹配
Pointcut TRUE = TrueClassFilter.INSTANCE ;
}
// 对pointcut所处的对象进行 Class级别类型的匹配
public interface ClassFilter{
boolean matches(Class clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
// 重头戏
public interface MethodMatcher{
// false --> 不考虑目标方法的参数 ; true --> 对目标方法参数进行操作
boolean isRuntime();
// isRuntime() 返回false时 调用; 称为 "StaticMethodMatcher"
boolean matches(Method method , Class targetClass);
// isRuntime() 返回true时 调用 ; 称为 "DynamicMethodMatcher" -- 最好使用static的 避免使用这个的
boolean matches(Method method , Class targetClass , Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE ;
}
b.Spring提供的常见的PointCut
①.NameMatchMethodPointcut
解释 : 其为StaticMethodMatcherPointcut的子类.
Demo :
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
// 单个方法匹配
pointcut.setMappedName("matches");
// 或者传入多个 匹配方法名称
pointcut.setMappedNames(new String[]{"matches","isRuntime"});
// 支持 * 通配符
pointcut.setMappedNames(new String[]{"match*","*matches","mat*es"});
缺点 : 只能匹配指定的方法名 , 无法匹配方法重载 , 因为没有 函数签名的描述.
②.JdkRegexpMethodPointcut
解释 : 其为StaticMethodMatcherPointcut的子类. 需要 JDK1.4 以上.
必须匹配整个方法签名
Demo :
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
// 单个表达式
pointcut.setPattern(".*match.*");
// 多个表达式
pointcut.setPatterns(new String[]{".*match.*",".*matches"});
③.Perl5RegexpMethodPointcut
略过, 都有 JDK 1.4 以上了. 用JdkRegexpMethodPointcut替代.
④.AnnotationMatchingPointcut
解释 : JDK5 以上版本可以使用.
Demo :
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassLevelAnnotation{
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation{
}
@ClassLevelAnnotation
public class GenericTargetObject{
@MethodLevelAnnotation
public void withAnnotation(){
System.out.println("i have an apple , i have a pen");
}
public void noAnnotation(){
System.out.println("no apple , no pen !!!-.-");
}
public static void main(String[] args){
// 这两种写法表达的 都是对标注了 @ClassLevelAnnotation 的类中所有的 method进行匹配
AnnotationMatchingPointcut clazzLvPc = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);
AnnotationMatchingPointcut clazzLvPc1 = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
// 对所有 标注了 @MethodLevelAnnotation 的方法进行匹配 , 而不局限于某一个类 , 比如GenericTargetObject
AnnotationMatchingPointcut methodLvPc = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);
// 对标注了 @ClassLevelAnnotation 的类中 标注了 @MethodLevelAnnotation 的方法 进行匹配. (这里只匹配到 withAnnotation() )
AnnotationMatchingPointcut methodLvPc1 = new AnnotationMatchingPointcut(ClassLevelAnnotation.class,MethodLevelAnnotation.class);
}
}
⑤.ComposablePointcut
解释 : 提供 逻辑运算 功能的pointcut类.
⑥.ControlFlowPointcut
解释 : 可以帮助你 在调用流程中的 只有指定调用类(special caller)调用了目标类的pointcut才触发织入操作, 忽略其他的调用类
用法 :
public static void main(String[] args){
//指定调用类
ControlFlowPointcut pointcut = new ControlFlowPointcut(XxxTargetCaller.class);
//指定调用类 , 精确到指定方法
ControlFlowPointcut pointcut1 = new ControlFlowPointcut(XxxTargetCaller.class,"xxxMethodName");
}
3.Advice
解释: 两种类型 (per-class , per-instance) ,前者实例间共享,后者每个实例有自己的advice.
在Spring中 这些 Advice 都是 普通的POJO . 配置上和普通的Bean没有区别.
a.BeforeAdvice
实现org.springframework.aop.MethodBeforeAdvice接口.
通常, before advice 不会中断调用流程 , 如果有需要 ,可以用异常来调用.
b.ThrowsAdvice
实现org.springframework.aop.ThrowsAdvice接口.
ThrowsAdvice接口没有定义方法 , 但是 子类要遵循以下规则 , []中的参数可以省略.
void afterThrowing( [ method , args , target] , ThrowableSubClass);
Demo :
public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice{
public void afterThrowing(Throwable t){
// 普通异常处理
}
public void afterThrowing(RuntimeException e){
// 运行时异常处理
}
public void afterThrowing(Method method , Object[] args , Object target , XxxAppException e){
// 处理应用程序生成的异常
}
}
c.AfterReturningAdvice
解释 : 只有方法正常返回时才可以执行.
可以访问到方法的返回值 , 但是不可以更改返回值.
还可以获得方法,方法参数 以及目标对象的相关信息.
d.AroundAdvice
解释 :
Spring没有提供相应的 after (finally) advice .
但是 提供了 AroundAdvice , 它可以做到所有上面提到的advice所能做的.
还可以修改返回值.非常强大 , 应用场景最多 .
Spring中并没有AroundAdvice接口 , 而是直接使用了 org.aoplliance.intercept.MethodInterceptor 接口.
Demo :
public class PerformanceMethodInterceptor implements MethodInterceptor{
private static final Logger logger = LoggerFactory.getLogger(this.getClass());
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
StopWatch watch = new StopWatch();
try{
watch.start();
return methodInvocation.proceed();
}catch(Exception ex){
logger.error("exception happens : " + ex.getCauseMessage());
}finally{
watch.stop();
logger.info(watch.toString());
}
}
}
e.Introduction (Spring中唯一的一个 per-instance类型的Advice)
//todo 这部分 有点绕 , 麻烦 , 如非真的需要 不用算了 , 毕竟两个实现类的拓展性不好(不知道spring team改了没有) 待补充 ... 2333333
两个具体实现类 :
①.DelegatingIntroductionInterceptor
该实现类 并不能做到 per-instance , 如果想达到 需要用它的兄弟类,见②.
②.DelegatePerTargetObjectIntroductionInterceptor
4.Advisor
解释 :
Spring提供的 Advisor 对应着 概念中的 Aspect. 可以说Advisor是特殊的/弱化版的 Aspect.
Advisor 包含 一个Advice 以及一个Pointcut , 而Aspect可以包含 多个Advice 以及多个Pointcut.
两个Advisor家族 :
①.PointcutAdvisor
1).DefaultPointcutAdvisor
PointcutAdvisor 门派的大弟子 , 除了IntroductionAdvice , 其他类型的pointcut , advice都可以使用.
可以在构造方法,setter中设置pointcut,advice. 实际生产中,推荐使用spring的xml配置,和普通bean的配置没什么不同.
2).NameMatchMethodPointcutAdvisor
对DefaultPointcutAdvisor的细化:
pointcut方面 限制了只可使用NameMatchPointcut,无法更改.
advice方面 可以使用除IntroductionAdvice类型以外的advice.
3).RegexpMatchPointcutAdvisor
限制了只可使用正则表达式进行设置pointcut.
除了IntroductionAdvice , 其他类型的advice均可使用.
4).DefaultBeanFactoryPointcutAdvisor
使用比较少的advisor.
通过绑定容器中advice注册的beanName来实例化advice.前提是pointcut匹配成功,否则不实例化.
减少了容器启动初期advisor和advice之间的耦合.但会依赖并强行绑定到spring的ioc容器.
②.IntroductionAdvisor
只能应用于类级别的拦截.
只能使用IntroductionInterceptor , 仅限Introduction使用场景.
参数有两个 : 一个IntroductionInterceptor的impl类 , 一个接口类型.
Ordered接口的作用.
当有多个advisor可以拦截到 同一个方法 , 那么指定他们的拦截顺序有的时候就很有必要,这时可以通过设置order来解决.
5.Spring AOP 的织入
①.ProxyFactory
解释 :
Spring AOP中 使用org.springframework.aop.framework.ProxyFactory 作为最基本的织入器.
AspectJ AOP 使用 ajc编译器 ; JBOSS AOP 使用自定义ClassLoader
ProxyFactory 只需要 advisor 和 targetObject 相关信息.
对于 非IntroductionInterceptor 类型的advice , ProxyFactory可以内部构造相应的advisor.
对于 IntroductionInterceptor 类型 分为两种情况 :
如果是DynamicIntroductionAdvice的子类 , 会抛出AopConfigExecption , 因为该子类没有必要的对象目标信息.
如果是其他的 , ProxyFactory可以为其配置一个 DefaultIntroductionAdvisor
Demo :
ProxyFactory weaver = new ProxyFactory(XxxTargetObject);
Advisor advisor = xxxx;
weaver.addAdvisor(advisor);
Object proxy = weaver.getProxy();
// 可以使用 proxy 做你想做的事儿了~
使用方式 :
1).基于接口的代理
ProxyFactory weaver = new ProxyFactory(new MockTask());
weaver.setInterfaces(new Class[]{ITask.class}); //可以省略 , ProxyFactory会检测到 目标类所实现的接口.
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(XxxAdviceImpl());
weaver.addAdvisor(advisor);
ITask taskProxy = (ITask) weaver.getProxy();
taskProxy.execute();
2).基于类的代理 (使用CGLIB)
ProxyFactory weaver = new ProxyFactory(new Executable());
// 如果目标类(这里是Executable)没有实现任何接口 ,可以省略设置 ,默认使用基于类的代理 ;
// 反之,可以强制使用类的代理而非接口代理 : 设置 proxyTargetClass 或者 optimize 为true ,
weaver.setProxyTargetClass(true);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(XxxAdviceImpl());
weaver.addAdvisor(advisor);
Executable taskProxy = (Executable) weaver.getProxy();
taskProxy.execute();
3).对于 Introduction的织入
类似对于 基于接口的代理 的织入.
不同的是
a.必须setInterfaces(xxx);
b.可以不用设置advisor , 而是设置advice , ProxyFactory会为Introduction这种使用 DefaultIntroductionAdvisor
②.ProxyFactoryBean
解释 :
另外一个SpringAOP提供的织入器 , 是ProxyFactory的兄弟类 , 同样继承自ProxyCreatorSupport 添加了特有的属性 :
proxyInterfaces :
与 父容器的interfaces属性一样的功效.
interceptorNames :
可以 指定多个要织入到目标对象的advice , 拦截器 以及 advisor , 不需要像ProxyFactory那样一个一个指定了.
singleton :
为true时 , 每次调用getObejct() 返回同一个 proxy实例 ; 反之 , 每次返回不同的实例 .
使用方式 : (前两者 和 ProxyFactory 并没有什么不同)
1).基于接口的代理
2).基于类的代理
3).对于 Introduction的织入(略)
③.AutoProxy
解释 :
SpringAOP提供的自动代理 , 自动化织入方式.
需要以ApplicationContext为IoC容器(使用BeanFactory的话,需要进一步编码,否则不是自动化).
它的实现是建立在IoC容器的BeanPostProcessor概念上.
fake code :
for(Bean bean : IocContainer){
if(bean.isSatifySomeCondition){ // 关键! 可以通过配置文件 , 或是注解
Proxy proxy = createProxyFor(bean); // 无非就是利用 ProxyFactory 或 ProxyFactoryBean 来实现
}else{
Object object = createInstancce(bean);
return object ;
}
}
具体实现类 :
BeanNameAutoProxyCreator --半自动步枪 , 需要配置自己应该持有那些target 以及相应的 advice
DefaultAdvisorAutoProxyCreator --全自动步枪 , 需要配置 自己 以及 各个单独的 advisor 即可. 可以看出 它只对advisor 有效.
6.TargetSource
解释 :
spring对target object的封装 , 提供的实现类如下 :
1).SingletonTargetSource -- ProxyFactory 以及 ProxyFactoryBean 都是使用的它 , 每次getTarget(),返回同一个目标类代理的引用.
2).PrototypeTargetSource -- 与 SingletonTargetSource相反 .
3).HotSwappableTargetSource
比较有用的一个 TargetSource . //todo
4).CommonsPoolTargetSource
5).ThreadLocalTargetSoure
6).自定义TargetSource
@AspectJ 更多相关话题 :
1.Advice的执行顺序
①.如果这些advice是在同一个 Aspect/Advisor中 , 那么按照定义顺序 排列优先级.
@Before的 先定义的先执行 ; @AfterReturningAdvice 先定义的最后执行 .
②.当这些advice是在不同的Aspect中 , 那么就需要借助 Ordered 接口(数值越小 , 优先级越高)
public class AnotherAspect implements Ordered {
public int getOrder(){
return 100 ;
}
}
2.Aspect的实例化模式
默认采用的 是singleton模式.
SpringAOP还支持perthis 以及 pertarget
Demo :
@Aspect(perthis(execution(boolean *.execute(String,..))))
public class MultiAdviceAspect{
...
}
perthis 会为相应的 代理对象 实例化各自的Aspect实例.
pertarget 为匹配的 单独的目标对象 实例化Aspect实例 .
注意 , 当使用这两种模式的 Aspect , 其Xml中的Bean定义 就不能再使用 scope="singleton" , 否则会报错. 最好连带相应的 目标对象也配置为 prototype类型.
基于Schema的AOP :
即 使用XSD 取代了 DTD . 同一个XML可以配置多个 <aop:config />
虽然 @AspectJ 模式的AOP支持 三种模式 , 但是 XSD的 只支持 singleton模式.
当你就喜欢用XML模式配置,或者JVM是5以下的 ,可以使用这种方式来管理AOP , 一般用基于@AspectJ就ok了.
<!-- 只有proxy-target-class属性 , 默认为 fasle 即基于 接口的代理 todo-->
<aop:config proxy-target-class="fasle">
<aop:pointcut />
<aop:advisor />
<aop:aspect />
</aop:config>
<aop:config proxy-target-class="true">
<aop:pointcut />
<aop:advisor />
<aop:aspect />
</aop:config>
Demo :
<aop:config>
<aop:advisor
id="performanceAdvisor"
pointcut-ref="targetPointcut"
advice-ref="targetAdvice"
order="1"
/>
</aop:config>
<bean id="targetPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" >
<property name="pattern" value="..." />
</bean>
<bean id="performanceInterceptor" class="...PerformanceMethodInterceptor" />
其中,pointcut-ref
a.可以用pointcut替代 , 直接指定pointcut="execution(...)"
b.可以在 <aop:advisor>中定义 一个或多个 <aop:pointcut> , 然后 在<aop:advisor>甚至<aop:aspect>中 使用pointcut-ref属性来指定.
如果<aop:pointcut>中的type="regex" ,那么 可以书写expression="xx正则表达式" .
默认type="aspectj" , 采用的是 pointcut 表达式的 处理方案.
但是 type只是语义上如此 , 实际情况 是 type="regex"时, 仍然使用的是默认的类型,而不是正则表达式 , 该bug不知道目前是否修复.todo