循序渐进之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()方法以通过静态检查排除掉大部分方法。
循序渐进之Spring AOP(5) - 创建切面的更多相关文章
- spring AOP AspectJ 定义切面实现拦截
总结记录一下AOP常用的应用场景及使用方式,如有错误,请留言. 1. 讲AOP之前,先来总结web项目的几种拦截方式 A: 过滤器 使用过滤器可以过滤URL请求,以及请求和响应的信息,但是过 ...
- Spring AOP:面向切面编程,AspectJ,是基于spring 的xml文件的方法
导包等不在赘述: 建立一个接口:ArithmeticCalculator,没有实例化的方法: package com.atguigu.spring.aop.impl.panpan; public in ...
- Spring AOP:面向切面编程,AspectJ,是基于注解的方法
面向切面编程的术语: 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象 通知(Advice): 切面必须要完成的工作 目标(Target): 被通知的对象 代理(Pr ...
- Spring -- aop(面向切面编程),前置&后置&环绕&抛异常通知,引入通知,自动代理
1.概要 aop:面向方面编程.不改变源代码,还为类增加新的功能.(代理) 切面:实现的交叉功能. 通知:切面的实际实现(通知要做什么,怎么做). 连接点:应用程序执行过程期间,可以插入切面的地点. ...
- Spring AOP(面向切面示例)
什么是AOP?基本概念切面(aspect):横切关注点被模块化的特殊对象.通知(advice):切面必须要完成的工作.切面中的每个方向称之为通知.通知是在切面对象中的.目标(target):被通知的对 ...
- 循序渐进之Spring AOP(3) - 配置代理
上一篇介绍了几种Advice(增强),并通过代码演示了生成代理的方式,下面来看通过配置文件配置方式把Advice织入目标类. 注意,配置文件方式仍然不是spring AOP的最好方式,学习配置方式也是 ...
- 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring AOP(面向切面编程)
面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式.Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的. AOP ...
- 循序渐进之Spring AOP(1) - 原理
AOP全称是Aspect Oriented Programing,通常译为面向切面编程.利用AOP可以对面向对象编程做很好的补充. 用生活中的改装车比喻,工厂用面向对象的方法制造好汽车后,车主往往有些 ...
- Spring AOP 自动创建代理
Spring为我们提供了自动代理机制,让容器为我们自动生成代理,把我们从烦琐的配置工作中解放出来,在内部,Spring 使用BeanPostProcessor自动地完成这项工作. 1.实现 ...
随机推荐
- 防刷新jq左侧滚动条导航展示
html代码: <div class="fangchan_navcont"> <div class="fangchan_nav" ...
- map的应用
1.map最基本的构造函数: map<string , int >mapstring; map<int ,string >mapint; map&l ...
- JavaScript中“typeof”运算符与“instanceof”运算符的差异
在JavaScript中,运算符“typeof”和“instanceof”都可以用来判断数据的类型,那么这两个运算符有什么不同之处呢? 差异一:使用方式不同. 最明显的差异就是这两个运算符的使用方式了 ...
- Android Listview 性能优化
首先我一般使用的适配器是BaseAdapter,其中有两个方法最主要,分别是: getCount,getView, 在对Listview 进行优化的时候,首先使用 convertview 和viewH ...
- Qt 手动添加ui文件到工程(转)
制作ui文件 先应该用Qt Designer绘制一个自己的界面,并存为myform.ui(这里的myform可以用自己喜欢的名字代替).在制作自己的界面文件时要注意以下几个要点: 1.要记住ui文件的 ...
- Boot loader: Grub进阶[转]
Boot loader: Grub进阶 本文记录grub的一些进阶配置 关於核心功能当中的 vga 配置 事实上,你的 tty1~tty6 除了 80x24 的解析度外,还能够有其他解析度的支持喔!但 ...
- jquery 复制DIV与相关事件
<div class="pages_img fix" name="fixpages"> <div class=& ...
- WPF外包公司——北京动点飞扬软件:开发企业WPF项目需要掌握些什么
做为企业开发一个WPF项目,对于很多不熟悉微软WPF技术和XAML语言开发团队而言,北京动点飞扬在此给各位一点建议: 1.首先开发团队要整体对于XAML和WPF的运作机制熟悉. 2.开发人员起码要会用 ...
- Junit4常用注解
Junit4注解 JUnit4的测试类不用再继承TestCase类了.使用注解会方便很多. @Before:初始化方法@After:释放资源@Test:测试方法,在这里可以测试期望异常和超时时间@Ig ...
- Ubuntu16.04安装配置Eclipse 以及opencv的使用
安装配置http://www.oracle.com下载与自己电脑系统对应的JDK.我下载的是jdk-8u101-linux64.tar.gz 解压:sudo tar -zxvf jdk-8u101-l ...