AOP即面向切面编程,比如某个项目有很多个模块,每个模块都为特定的业务领域提供服务,但是这些模块都需要一些辅助功能,例如安全和事务管理。
和大多数技术一样,AOP有自己的术语。描述切面的常用术语有通知(advice)、切点(pointcut)、和连接点(join point)。
通知(advice)定义了切面什么时候使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。它应该应用于某个方法之前?之后?之前和之后?还是在方法发生异常时?
Spring切面可以应用5种类型的通知:
Before---在方法调用之前。
After---在方法调用之后,无论是否发生异常。
After-returning---在方法成功执行之后。
After-throwing---在方法发生异常之后。
Around---包裹被通知的方法,在方法调用之前和之后都执行。
Spring提供了4种各具特色的AOP支持:
基于代理的经典AOP
@AspectJ注解驱动的切面
纯POJO切面
注入式AspectJ切面
在Spring XML中声明切面
<aop:advisor> 定义AOP通知器
<aop:after> 定义AOP后置通知(不管方法是否执行成功)
<aop:after-returning>定义AOP after-returning通知
<aop:after-throwing>定义AOP after-throwing通知
<aop:around> 定义AOP环绕通知
<aop:aspect> 定义切面
<aop:aspect-autoproxy>启动@Aspect注解驱动的切面
<aop:before> 定义AOP前置通知
<aop:config> 顶层的AOP配置元素,大多数的<aop:*>都需要包含在<aop:config>中
<aop:declare-parents>为被通知的对象引入额外的接口,并透明的实现
<aop:pointcut> 定义切点
定义一个主人类Person
package com.lmy.spring; import org.springframework.stereotype.Component; @Component("xiaoming") public class Person{ //表演之前喂食 public void beforePlay(){ System.out.println("喂食"); } //表演之后鼓掌 public void afterPlay(){ System.out.println("鼓掌"); } }
宠物狗类Dog
package com.lmy.spring; import org.springframework.stereotype.Component; @Component("kala") public class Dog{ public void play(){ System.out.println("做算术"); } }
宠物在表演之前主人应该先喂食,通过注解@Component把两个类定义成Spring Bean。
然后在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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" > <context:component-scan base-package="com.lmy.spring"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <aop:config> <aop:aspect ref="person"> <aop:pointcut expression="execution(* com.lmy.spring.Dog.play(..))" id="dog" /> <aop:before method="beforePlay" pointcut-ref="dog" /> <aop:after method="afterPlay" pointcut-ref="dog" /> </aop:aspect> </aop:config> </beans>
编写代码测试结果
@Test public void test1(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml"); Dog kala = (Dog)ctx.getBean("kala"); kala.play(); }
输出:
喂食
做算术
鼓掌
两个类的代码从表面上没有任何关联的地方,但是通过AOP通知,在Dog类的Bean对象kala调用play方法时,会被Spring拦截,aop:before定义了匹配的切点方法之前执行的前置方法,aop:after定义了在切点匹配的方法结束之后执行的方法。
上面的例子虽然实现了表演前喂食和表演后鼓掌的功能,但是Person根本不知道是给谁喂食和鼓掌,所以代码还需要改进一下。
Dog在play的时候传一个名字进去
public void play(String name) { System.out.println(name+"在做算术"); }
修改Person类的两个方法,都加上参数String name
// 表演之前喂食 public void beforePlay(String name) { System.out.println("给"+dog.getName()+"喂食"); } // 表演之后鼓掌 public void afterPlay(String name) { System.out.println("给"+dog.getName()+"鼓掌"); }
现在要做的就是在匹配的切点方法之前和之后执行的方法添加参数。
修改XML配置
<aop:config>
<aop:aspect ref="xiaoming">
<aop:pointcut expression="execution(* com.lmy.spring.Dog.play(com.lmy.spring.Dog)) and args(kala)"
id="dog" />
<aop:before method="beforePlay" pointcut-ref="dog" arg-names="kala" />
<aop:after method="afterPlay" pointcut-ref="dog" arg-names="kala" />
</aop:aspect>
</aop:config>
然后调用
@Test public void test1(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml"); Dog pf = (Dog)ctx.getBean("kala"); pf.play("kala"); }
这样就会输入带名字的信息了。
PS:为了简单,所以没有使用接口,严格来说应该有Animal接口,Dog实现该接口,切点的配置就应该配置成
<aop:pointcut expression="execution(* com.lmy.spring.Animal.play(String))
and args(kala)"
id="animal" />
所有实现了Animal接口的类当调用play方法时都会触发。