软件开发的时候,提倡“高内聚,低耦合”,如何减少模块之间的耦合程度是一件麻烦的事情。比如,有时候我们的业务需要在某些操作的时候加上一些记录日志、加上一些验证、加上保存缓存等等。这些操作都是夹杂在整个业务之中的,如果把这些业务代码也写在一起,就变成了“大杂烩”,模块之间的耦合程度非常的高,使得开发不能专注于某一个方面,出了问题也不好调试。
Spring AOP(Aspect Oriented Programming) 面向切面编程,正是解决上述问题的法宝。Spring AOP位于Spring Core之上,但不是不可或缺的,如果你的程序不需要切面编程,完全可以不用AOP模块。
这样,当使用Spring AOP的时候,我们的程序可以大体上分成两个方面,主题业务逻辑与切面逻辑。先来了解一下AOP的一些术语:
方面(Aspect): 一个关注点的模块化,这个关注点实现可能 另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。
连接点(Joinpoint): 程序执行过程中明确的点,如方法的调 用或特定的异常被抛出。
通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类 型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架 包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
切入点(Pointcut): 指定一个通知将被引发的一系列连接点 的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。
引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。
目标对象(Target Object): 包含连接点的对象。也被称作 被通知或被代理对象。
AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时 完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样, 在运行时完成织入。
这些词可能有些抽象,或者相近。下面以一个例子来说明AOP。
就以买东西为例,暂且抽象这类人群为买手Buyer,可能有男买手ManBuyer和女买手
WomenBuyer,所以抽象出来一个接口Buyer
package test.spring.aop;
public interface Buyer {
public void buy();
}
两个实现类WomenBuyer和ManBuyer
package test.spring.aop;
public class WomenBuyer implements Buyer {
public void buy() {
System.out.println("A women is buying something...");
}
}
package test.spring.aop;
public class ManBuyer implements Buyer {
public void buy() {
System.out.println("A man is buying something...");
}
}
但在实际买东西的过程中,这些职业买手们往往会在买东西之前货比三家,所以我们在买东西的之前加上比价的过程,所以,上述两个实现类就变成了这样
package test.spring.aop;
public class WomenBuyer implements Buyer {
public void buy() {
System.out.println("-------购买前比价--------");
System.out.println("A women is buying something...");
}
}
package test.spring.aop;
public class ManBuyer implements Buyer {
public void buy() {
System.out.println("-------购买前比价--------");
System.out.println("A man is buying something...");
}
}
试想一下,如果这个Buyer接口有几十个实现类,这样的比价过程要重复几十遍,当这个比较过程很复杂的时候,为此付出的代价非常大,并且把比价行为和购买行为合在一起,不符合“高内聚,低耦合”的标准。于是乎,这时候就可以使用AOP,把比价过程作为一个切面独立出去,这样我们可以专注于方面编程。
我们将比价行为抽出,放到切面Aspect中
package test.spring.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class CompareAspect implements MethodBeforeAdvice {
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("-------购物前比价----------");
}
}
这个切面Aspect实现了MethodBeforeAdvice接口,用于在主体业务执行之前,执行切面逻辑(这里也就是我们的比价行为)。
将WomenBuyer和ManBuyer中的比较语句去掉。那么现在我们可以在切面中专注比价行为的逻辑,可以中实现类的buy( )方法中专注主题的业务逻辑。
现在的问题是怎么把切面与主题业务逻辑联系在一起,这就是“织入”,怎么把切面中的代码织入进主题业务中,然后程序就可以像我们第一次写的代码一样,顺序执行切面和主程序。织入的方式有以下3种:
- 在编译器织入,通过编译器在编译的时候就把切面织入进去,AspectJ就是通过编译器在编译器织入的。
- 在类加载的时候,通过ClassLoader加载切面的字节码进行织入。
- 在运行期的某个时刻,通过JDK的动态代理进行切面的织入,SpringAOP就是通过这种动态代理的方式进行织入的。
回到上述买东西的问题上,在applicationContext.xml我们定义这种关系
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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" >
<bean id="womenBuyer" class="test.spring.aop.WomenBuyer"></bean>
<bean id="compareAspect" class="test.spring.aop.CompareAspect"></bean>
<!--通知者,整合通知Advice和切入点Point-->
<bean id="buyCompareAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="compareAspect"></property>
<property name="pattern" value=".*buy"></property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
</beans>
在写一个测试类:
package test.spring.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Buyer buyer = (Buyer)ac.getBean("womenBuyer");
buyer.buy();
}
}
这样就可以按照我们设计的逻辑进行输出了。这里我们利用容器管理我们的对象,然后直接在容器中获取,容器会帮助我们管理对象间的依赖关系(IoC),并且会在特定的切入点引入切面的逻辑。
除了利用<bean>的配置方案之外,还可以利用spring提供的aop命名空间进行配置。
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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" >
<bean id="womenBuyer" class="test.spring.aspectj.aop.WomenBuyer"></bean>
<bean id="compareAspect" class="test.spring.aspectj.aop.CompareAspect"></bean>
<aop:config>
<aop:aspect ref="compareAspect">
<aop:before method="beforeBuy" pointcut="execution(* buy(..))"/>
<aop:after method="afterBuy" pointcut="execution(* buy(..))"/>
</aop:aspect>
</aop:config>
</beans>
上面是通过xml进行AOP配置的,下面使用AspectJ注解驱动AOP,当然你要加入相关的jar包
切面:
package test.spring.aspectj.aop;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CompareAspect {
public CompareAspect(){}
@Pointcut("execution(* buy())")
public void buypoint() {
System.out.println("This is buypoint");
}
@Before("buypoint()")
public void beforeBuy() {
System.out.println("-------购买前比价----------");
}
@AfterReturning("buypoint()")
public void afterBuy() {
System.out.println("--------购买后使用----------------");
}
}
配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" >
<bean id="womenBuyer" class="test.spring.aspectj.aop.WomenBuyer"></bean>
<bean id="compareAspect" class="test.spring.aspectj.aop.CompareAspect"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
其他不变,程序输出:
-------购买前比价----------
A women is buying something...
--------购买后使用----------------
最后,附上整个项目所用到的jar包