循序渐进之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.实现 ...
随机推荐
- Web Storage的方法
1.分为两种:localStorage与sessionStorage.2.存储形式:key-value的形式.sessionStorage 1.session定义:session指用户在浏览某个网站时 ...
- mysql存储过程详解(入门)
delimiter // #修改结束符号为// create procedure pro_wyx() /*创建存储过程*/ begin declare i int ; #定义变量 set i=1 ...
- Python 爬虫学习 网页图片下载
使用正则表达式匹配 # coding:utf-8 import re import urllib def get_content(url): """ Evilxr, &q ...
- 怎样创建FTP服务器
怎样创建FTP服务器 2008-05-06 08:42永远的探索|分类:操作系统/系统故障| 浏览6382次 我准备用局域网内的一台机器做FTP服务器,创建FTP服务器一定要用Windows serv ...
- Openvas 使用
最新版的kali没有安装,配好源,就可以安装. 一. 简介: Nessus是其中一个最流行的和有强力的漏洞扫描器,尤其是对UNIX系统.它最初是自由和开放源码,但他们在2005年关闭了源代码,在200 ...
- docker swarm
1.docker pull swarm 2.docker run --rm swarm create #创建cluster id b1442105f281c7eaa31f8e5d815fe0e2 3. ...
- Redis客户端之Spring整合Jedis,ShardedJedisPool集群配置
Jedis设计 Jedis作为推荐的java语言redis客户端,其抽象封装为三部分: 对象池设计:Pool,JedisPool,GenericObjectPool,BasePoolableObjec ...
- c#定义全局条件编译符号
在"工程"上单机右键,"属性"--->"生成"--->"条件编译符号"后边的输入框中,输入自定义的条件编译变 ...
- linux下socket编程-进程间通信
一.什么是Socket Socket接口是TCP/IP网络通信的API,Socket接口定义了许多函数或例程,可以用它们来开发TCP/IP网络上的应用程序. Socket类型有两种:流式Socket ...
- WPF中嵌入普通Win32程序的方法
公司现在在研发基于.Net中WPF技术的产品,由于要兼容旧有产品,比如一些旧有的Win32程序.第三方的Win32程序等等,还要实现自动登录这些外部Win32程序,因此必须能够将这些程序整合到我们的系 ...