循序渐进之Spring AOP(5) - 创建切面

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

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

1 静态方法切面

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

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

public class Horseman {
	public void rush(String enemy) {
		System.out.println(this.getClass().getSimpleName() + "冲刺攻击" + enemy);
	}
	
	public void chop(String enemy) {
		System.out.println(this.getClass().getSimpleName() + "砍劈攻击" + enemy);
	}
}
public class Swordman {
	public void block(String enemy) {
		System.out.println(this.getClass().getSimpleName() + "格挡" + enemy);
	}
	
	public void chop(String enemy) {
		System.out.println(this.getClass().getSimpleName() + "砍劈攻击" + enemy);
	}
}

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

public class BeforeAdvice implements MethodBeforeAdvice {
	@Override
	public void before(Method method, Object[] args, Object obj)
			throws Throwable {
		System.out.println("Advice:" + obj.getClass().getSimpleName() + "蓄力");
	}
}
下面是切点类,定义了两个选项参数methodOption和classOption,以方便察看不同切点定义下的执行效果

public class StoragePointcut extends StaticMethodMatcherPointcut {

	@Override
	public boolean matches(Method method, Class<?> cls) {
		switch(methodOption) {
		case 1:
			return "chop".equals(method.getName());
		case 2:
			return "rush".equals(method.getName());
		default:
			return true;
		}
	}

	public ClassFilter getClassFilter() {

		return new ClassFilter() {
			public boolean matches(Class<?> cls) {
				switch(classOption) {
				case 1:
					return (Horseman.class.isAssignableFrom(cls));
				case 2:
					return (Swordman.class.isAssignableFrom(cls));
				default:
					return true;
				}
			}
		};
	}

	private int methodOption;
	private int classOption;

	public void setMethodOption(int methodOption) {
		this.methodOption = methodOption;
	}

	public void setClassOption(int classOption) {
		this.classOption = classOption;
	}
}

配置文件applicationContext.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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />
	<bean id="horsemanTarget" class="examples.chap01.Horseman" />
	<bean id="swordmanTarget" class="examples.chap01.Swordman" />
	<bean id="pointcut" class="examples.chap01.StoragePointcut"
		p:methodOption="0"
		p:classOption="0" />
	<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
		p:advice-ref="beforeAdvice"
		p:pointcut-ref="pointcut" />
	
	<bean id="horseman" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="horsemanTarget"
		p:interceptorNames="advisor"
		p:proxyTargetClass="true" />
	<bean id="swordman" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="swordmanTarget"
		p:interceptorNames="advisor"
		p:proxyTargetClass="true" />
</beans>
测试代码

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("examples/chap01/applicationContext.xml");
		Horseman hm = (Horseman)context.getBean("horseman");
		Swordman sm = (Swordman)context.getBean("swordman");
		hm.rush("Ghoul");
		hm.chop("Ghoul");
		sm.block("Ghoul");
		sm.chop("Ghoul");
	}

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


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

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

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

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

public class StoragePointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {

	@Override
	public boolean matches(Method method, Class<?> cls) {
		switch(methodOption) {
		case 1:
			return "chop".equals(method.getName());
		case 2:
			return "rush".equals(method.getName());
		default:
			return true;
		}
	}

	public ClassFilter getClassFilter() {

		return new ClassFilter() {
			public boolean matches(Class cls) {
				switch(classOption) {
				case 1:
					return (Horseman.class.isAssignableFrom(cls));
				case 2:
					return (Swordman.class.isAssignableFrom(cls));
				default:
					return true;
				}
			}
		};
	}

	private int methodOption;
	private int classOption;

	public void setMethodOption(int methodOption) {
		this.methodOption = methodOption;
	}

	public void setClassOption(int classOption) {
		this.classOption = classOption;
	}
}
简化配置文件

	<bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />
	<bean id="horsemanTarget" class="examples.chap01.Horseman" />
	<bean id="swordmanTarget" class="examples.chap01.Swordman" />
	<bean id="advisor" class="examples.chap01.StoragePointcutAdvisor"
		p:advice-ref="beforeAdvice" />
	
	<bean id="horseman" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="horsemanTarget"
		p:interceptorNames="advisor"
		p:proxyTargetClass="true" />
	<bean id="swordman" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="swordmanTarget"
		p:interceptorNames="advisor"
		p:proxyTargetClass="true" />

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

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

修改配置文件applicationContext.xml

	<bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />
	<bean id="horsemanTarget" class="examples.chap01.Horseman" />
	<bean id="swordmanTarget" class="examples.chap01.Swordman" />
	<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
		p:advice-ref="beforeAdvice"
		p:patterns=".*chop" />
	
	<bean id="horseman1" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="horsemanTarget"
		p:interceptorNames="regexpAdvisor"
		p:proxyTargetClass="true" />
	<bean id="swordman1" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="swordmanTarget"
		p:interceptorNames="regexpAdvisor"
		p:proxyTargetClass="true" />
测试代码

		ApplicationContext context = new ClassPathXmlApplicationContext("examples/chap01/applicationContext.xml");
		Horseman hm1 = (Horseman)context.getBean("horseman1");
		Swordman sm1 = (Swordman)context.getBean("swordman1");
		hm1.rush("Ghoul");
		hm1.chop("Ghoul");
		sm1.block("Ghoul");
		sm1.chop("Ghoul");

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

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

3 动态切面

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

public class DynamicStoragePointcut extends DynamicMethodMatcherPointcut {

	//对方法做动态的输入参数匹配检查
	@Override
	public boolean matches(Method method, Class<?> cls, Object... args) {
		return args[0].equals("Abomination");
	}
	
	@Override
	public boolean matches(Method method, Class<?> cls) {
		switch(methodOption) {
		case 1:
			return "chop".equals(method.getName());
		case 2:
			return "rush".equals(method.getName());
		default:
			return true;
		}
	}

	public ClassFilter getClassFilter() {

		return new ClassFilter() {
			public boolean matches(Class<?> cls) {
				switch(classOption) {
				case 1:
					return (Horseman.class.isAssignableFrom(cls));
				case 2:
					return (Swordman.class.isAssignableFrom(cls));
				default:
					return true;
				}
			}
		};
	}

	private int methodOption;
	private int classOption;

	public void setMethodOption(int methodOption) {
		this.methodOption = methodOption;
	}

	public void setClassOption(int classOption) {
		this.classOption = classOption;
	}
}
修改配置文件

	<bean id="beforeAdvice" class="examples.chap01.BeforeAdvice" />
	<bean id="horsemanTarget" class="examples.chap01.Horseman" />
	<bean id="swordmanTarget" class="examples.chap01.Swordman" />
	<bean id="pointcut" class="examples.chap02.DynamicStoragePointcut"
		p:methodOption="1"
		p:classOption="0" />
	<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
		p:advice-ref="beforeAdvice"
		p:pointcut-ref="pointcut" />
	
	<bean id="horseman" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="horsemanTarget"
		p:interceptorNames="advisor"
		p:proxyTargetClass="true" />
	<bean id="swordman" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:target-ref="swordmanTarget"
		p:interceptorNames="advisor"
		p:proxyTargetClass="true" />
测试代码

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("examples/chap02/applicationContext.xml");
		Horseman hm = (Horseman)context.getBean("horseman");
		Swordman sm = (Swordman)context.getBean("swordman");
		hm.rush("Ghoul");
		hm.chop("Ghoul");
		sm.block("Ghoul");
		sm.chop("Ghoul");
		hm.rush("Abomination");
		hm.chop("Abomination");
		sm.block("Abomination");
		sm.chop("Abomination");
	}

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值