循序渐进之Spring AOP(5)

在掌握了可用的增强后,接下来要做的就是精确的描述切点。前面的示例都是指定一个目标类并把增强织入到所有方法中,实际开发显然会有更精细的筛选需求,比如对所有类中名称以test结尾的方法加入监控执行时间,或者指定某些方法仅在输入参数是指定值时做某些特殊处理以解决临时性需求。

spring中用Pointcut接口表示一个切点,其下设有多个实现类,按使用场景分主要有静态切点、动态切点、流程切点和复合切点等。

1 静态方法切面

使用静态方法切点,通常是继承StaticMethodMatcherPointcut,通过覆盖getClassFilter()方法用于确定匹配的类,覆盖matches方法用于确定匹配的方法,Spring对目标类及其上面的方法调用两个方法以确定是否织入增强,检查结果被缓存以提高性能。来看示例:

首先定义两个类用于演示类和方法的筛选, 两个类各有一个同名方法和一个不同名方法

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class Horseman {  
  2.     public void rush(String enemy) {  
  3.         System.out.println(this.getClass().getSimpleName() + "冲刺攻击" + enemy);  
  4.     }  
  5.       
  6.     public void chop(String enemy) {  
  7.         System.out.println(this.getClass().getSimpleName() + "砍劈攻击" + enemy);  
  8.     }  
  9. }  
[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class Swordman {  
  2.     public void block(String enemy) {  
  3.         System.out.println(this.getClass().getSimpleName() + "格挡" + enemy);  
  4.     }  
  5.       
  6.     public void chop(String enemy) {  
  7.         System.out.println(this.getClass().getSimpleName() + "砍劈攻击" + enemy);  
  8.     }  
  9. }  

设定一个增强,这里选了一个方法前增强

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class BeforeAdvice implements MethodBeforeAdvice {  
  2.     @Override  
  3.     public void before(Method method, Object[] args, Object obj)  
  4.             throws Throwable {  
  5.         System.out.println("Advice:" + obj.getClass().getSimpleName() + "蓄力");  
  6.     }  
  7. }  

下面是切点类,定义了两个选项参数methodOption和classOption,以方便察看不同切点定义下的执行效果

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class StoragePointcut extends StaticMethodMatcherPointcut {  
  2.   
  3.     @Override  
  4.     public boolean matches(Method method, Class<?> cls) {  
  5.         switch(methodOption) {  
  6.         case 1:  
  7.             return "chop".equals(method.getName());  
  8.         case 2:  
  9.             return "rush".equals(method.getName());  
  10.         default:  
  11.             return true;  
  12.         }  
  13.     }  
  14.   
  15.     public ClassFilter getClassFilter() {  
  16.   
  17.         return new ClassFilter() {  
  18.             public boolean matches(Class<?> cls) {  
  19.                 switch(classOption) {  
  20.                 case 1:  
  21.                     return (Horseman.class.isAssignableFrom(cls));  
  22.                 case 2:  
  23.                     return (Swordman.class.isAssignableFrom(cls));  
  24.                 default:  
  25.                     return true;  
  26.                 }  
  27.             }  
  28.         };  
  29.     }  
  30.   
  31.     private int methodOption;  
  32.     private int classOption;  
  33.   
  34.     public void setMethodOption(int methodOption) {  
  35.         this.methodOption = methodOption;  
  36.     }  
  37.   
  38.     public void setClassOption(int classOption) {  
  39.         this.classOption = classOption;  
  40.     }  
  41. }  

配置文件applicationContext.xml

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"  
  4.     xmlns:context="http://www.springframework.org/schema/context"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  6.   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  7.   http://www.springframework.org/schema/context  
  8.   http://www.springframework.org/schema/context/spring-context-3.0.xsd">  
  9.   
  10.     <bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />  
  11.     <bean id="horsemanTarget" class="examples.chap01.Horseman" />  
  12.     <bean id="swordmanTarget" class="examples.chap01.Swordman" />  
  13.     <bean id="pointcut" class="examples.chap01.StoragePointcut"  
  14.         p:methodOption="0"  
  15.         p:classOption="0" />  
  16.     <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"  
  17.         p:advice-ref="beforeAdvice"  
  18.         p:pointcut-ref="pointcut" />  
  19.       
  20.     <bean id="horseman" class="org.springframework.aop.framework.ProxyFactoryBean"  
  21.         p:target-ref="horsemanTarget"  
  22.         p:interceptorNames="advisor"  
  23.         p:proxyTargetClass="true" />  
  24.     <bean id="swordman" class="org.springframework.aop.framework.ProxyFactoryBean"  
  25.         p:target-ref="swordmanTarget"  
  26.         p:interceptorNames="advisor"  
  27.         p:proxyTargetClass="true" />  
  28. </beans>  

测试代码

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public static void main(String[] args) {  
  2.     ApplicationContext context = new ClassPathXmlApplicationContext("examples/chap01/applicationContext.xml");  
  3.     Horseman hm = (Horseman)context.getBean("horseman");  
  4.     Swordman sm = (Swordman)context.getBean("swordman");  
  5.     hm.rush("Ghoul");  
  6.     hm.chop("Ghoul");  
  7.     sm.block("Ghoul");  
  8.     sm.chop("Ghoul");  
  9. }  

可以修改pointcut配置中的methodOption和classOption值类观察切点的匹配效果。

注意配置文件中,使用了DefaultPointcutAdvisor类作为ProxyFactoryBean的interceptorNames属性。ProxyFactoryBean必须同时取得切点和增强才能创建代理,所以不能把Pointcut直接注入interceptorNames。Spring中接口Advisor表示切面,一个切面既包含了增强,也包含了切点信息。

在前面章节的示例中,可以直接用Advice作为interceptor,这是因为Advice包含了默认的切点信息,即目标类的所有方法。

实际上,上面的示例可以直接使用静态方法切面StaticMethodMatcherPointcutAdvisor来简化:

删除StoragePointcut类并增加一个新类StoragePointcutAdvisor

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class StoragePointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {  
  2.   
  3.     @Override  
  4.     public boolean matches(Method method, Class<?> cls) {  
  5.         switch(methodOption) {  
  6.         case 1:  
  7.             return "chop".equals(method.getName());  
  8.         case 2:  
  9.             return "rush".equals(method.getName());  
  10.         default:  
  11.             return true;  
  12.         }  
  13.     }  
  14.   
  15.     public ClassFilter getClassFilter() {  
  16.   
  17.         return new ClassFilter() {  
  18.             public boolean matches(Class cls) {  
  19.                 switch(classOption) {  
  20.                 case 1:  
  21.                     return (Horseman.class.isAssignableFrom(cls));  
  22.                 case 2:  
  23.                     return (Swordman.class.isAssignableFrom(cls));  
  24.                 default:  
  25.                     return true;  
  26.                 }  
  27.             }  
  28.         };  
  29.     }  
  30.   
  31.     private int methodOption;  
  32.     private int classOption;  
  33.   
  34.     public void setMethodOption(int methodOption) {  
  35.         this.methodOption = methodOption;  
  36.     }  
  37.   
  38.     public void setClassOption(int classOption) {  
  39.         this.classOption = classOption;  
  40.     }  
  41. }  

简化配置文件

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />  
  2. <bean id="horsemanTarget" class="examples.chap01.Horseman" />  
  3. <bean id="swordmanTarget" class="examples.chap01.Swordman" />  
  4. <bean id="advisor" class="examples.chap01.StoragePointcutAdvisor"  
  5.     p:advice-ref="beforeAdvice" />  
  6.   
  7. <bean id="horseman" class="org.springframework.aop.framework.ProxyFactoryBean"  
  8.     p:target-ref="horsemanTarget"  
  9.     p:interceptorNames="advisor"  
  10.     p:proxyTargetClass="true" />  
  11. <bean id="swordman" class="org.springframework.aop.framework.ProxyFactoryBean"  
  12.     p:target-ref="swordmanTarget"  
  13.     p:interceptorNames="advisor"  
  14.     p:proxyTargetClass="true" />  

即使简化后,代码仍然很臃肿。下面来看更简单直接的办法,使用正则表达式来定义切点

2 静态正则表达式方法切面

修改配置文件applicationContext.xml

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />  
  2. <bean id="horsemanTarget" class="examples.chap01.Horseman" />  
  3. <bean id="swordmanTarget" class="examples.chap01.Swordman" />  
  4. <bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"  
  5.     p:advice-ref="beforeAdvice"  
  6.     p:patterns=".*chop" />  
  7.   
  8. <bean id="horseman1" class="org.springframework.aop.framework.ProxyFactoryBean"  
  9.     p:target-ref="horsemanTarget"  
  10.     p:interceptorNames="regexpAdvisor"  
  11.     p:proxyTargetClass="true" />  
  12. <bean id="swordman1" class="org.springframework.aop.framework.ProxyFactoryBean"  
  13.     p:target-ref="swordmanTarget"  
  14.     p:interceptorNames="regexpAdvisor"  
  15.     p:proxyTargetClass="true" />  

测试代码

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. ApplicationContext context = new ClassPathXmlApplicationContext("examples/chap01/applicationContext.xml");  
  2. Horseman hm1 = (Horseman)context.getBean("horseman1");  
  3. Swordman sm1 = (Swordman)context.getBean("swordman1");  
  4. hm1.rush("Ghoul");  
  5. hm1.chop("Ghoul");  
  6. sm1.block("Ghoul");  
  7. sm1.chop("Ghoul");  

正则表达式 .*chop 表示所有目标类中的chop方法,如果对正则表达式语法有兴趣可以参考更专业的书籍。

上面两个切面都是在运行前指定匹配的类和方法,现在我们接到一个奇怪的需求,因为攻击前的"蓄力"会花费时间,所以仅当敌人是Abomination(憎恶,游戏中的大型兵种)时,才进行“蓄力”,这时,可以使用动态切面。

3 动态切面

动态切点DynamicMethodMatcherPointcut相对于StaticMethodMatcherPointcut增加了一个重载的matches方法用于检查运行时的输入参数

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class DynamicStoragePointcut extends DynamicMethodMatcherPointcut {  
  2.   
  3.     //对方法做动态的输入参数匹配检查  
  4.     @Override  
  5.     public boolean matches(Method method, Class<?> cls, Object... args) {  
  6.         return args[0].equals("Abomination");  
  7.     }  
  8.       
  9.     @Override  
  10.     public boolean matches(Method method, Class<?> cls) {  
  11.         switch(methodOption) {  
  12.         case 1:  
  13.             return "chop".equals(method.getName());  
  14.         case 2:  
  15.             return "rush".equals(method.getName());  
  16.         default:  
  17.             return true;  
  18.         }  
  19.     }  
  20.   
  21.     public ClassFilter getClassFilter() {  
  22.   
  23.         return new ClassFilter() {  
  24.             public boolean matches(Class<?> cls) {  
  25.                 switch(classOption) {  
  26.                 case 1:  
  27.                     return (Horseman.class.isAssignableFrom(cls));  
  28.                 case 2:  
  29.                     return (Swordman.class.isAssignableFrom(cls));  
  30.                 default:  
  31.                     return true;  
  32.                 }  
  33.             }  
  34.         };  
  35.     }  
  36.   
  37.     private int methodOption;  
  38.     private int classOption;  
  39.   
  40.     public void setMethodOption(int methodOption) {  
  41.         this.methodOption = methodOption;  
  42.     }  
  43.   
  44.     public void setClassOption(int classOption) {  
  45.         this.classOption = classOption;  
  46.     }  
  47. }  

修改配置文件

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />  
  2. <bean id="horsemanTarget" class="examples.chap01.Horseman" />  
  3. <bean id="swordmanTarget" class="examples.chap01.Swordman" />  
  4. <bean id="pointcut" class="examples.chap02.DynamicStoragePointcut"  
  5.     p:methodOption="1"  
  6.     p:classOption="0" />  
  7. <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"  
  8.     p:advice-ref="beforeAdvice"  
  9.     p:pointcut-ref="pointcut" />  
  10.   
  11. <bean id="horseman" class="org.springframework.aop.framework.ProxyFactoryBean"  
  12.     p:target-ref="horsemanTarget"  
  13.     p:interceptorNames="advisor"  
  14.     p:proxyTargetClass="true" />  
  15. <bean id="swordman" class="org.springframework.aop.framework.ProxyFactoryBean"  
  16.     p:target-ref="swordmanTarget"  
  17.     p:interceptorNames="advisor"  
  18.     p:proxyTargetClass="true" />  

测试代码

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public static void main(String[] args) {  
  2.     ApplicationContext context = new ClassPathXmlApplicationContext("examples/chap02/applicationContext.xml");  
  3.     Horseman hm = (Horseman)context.getBean("horseman");  
  4.     Swordman sm = (Swordman)context.getBean("swordman");  
  5.     hm.rush("Ghoul");  
  6.     hm.chop("Ghoul");  
  7.     sm.block("Ghoul");  
  8.     sm.chop("Ghoul");  
  9.     hm.rush("Abomination");  
  10.     hm.chop("Abomination");  
  11.     sm.block("Abomination");  
  12.     sm.chop("Abomination");  
  13. }  

注意,使用动态切面检查必须在执行期间根据输入参数值来确定,无法缓存结果,每次调用目标方法都会执行该检查,可能会影响性能,应慎重使用。同时开发时应覆盖matches(Method method, Class<?> cls)和getClassFilter()方法以通过静态检查排除掉大部分方法。

原文地址:https://www.cnblogs.com/sa-dan/p/6837202.html