AOP(Aspect Oriented Programming)也就是面向方面编程的技术AOP基于IoC基础是对OOP的有益补充
AOP将应用系统分为两部分核心业务逻辑(Core business concerns)及横向的通用逻辑也就是所谓的方面Crosscutting enterprise concerns例如所有大中型应用都要涉及到的持久化管理(Persistent)事务管理(Transaction Management)安全管理(Security)日志管理(Logging)和调试管理(Debugging)等
AOP正在成为软件开发的下一个光环使用AOP你可以将处理aspect的代码注入主程序通常主程序的主要目的并不在于处理这些aspectAOP可以防止代码混乱
Spring framework是很有前途的AOP技术作为一种非侵略性的轻型的AOP framework你无需使用预编译器或其他的元标签便可以在Java程序中使用它这意味着开发团队里只需一人要对付AOP framework其他人还是像往常一样编程
AOP概念
让我们从定义一些重要的AOP概念开始
— 方面(Aspect)一个关注点的模块化这个关注点实现可能另外横切多个对象事务管理是JEE应用中一个很好的横切关注点例子方面用Spring的Advisor或拦截器实现
— 连接点(Joinpoint)程序执行过程中明确的点如方法的调用或特定的异常被抛出
— 通知(Advice)在特定的连接点AOP框架执行的动作各种类型的通知包括aroundbefore和throws通知通知类型将在下面讨论许多AOP框架包括Spring都是以拦截器做通知模型维护一个围绕连接点的拦截器链
— 切入点(Pointcut)指定一个通知将被引发的一系列连接点的集合AOP框架必须允许开发者指定切入点例如使用正则表达式
— 引入(Introduction)添加方法或字段到被通知的类Spring允许引入新的接口到任何被通知的对象例如你可以使用一个引入使任何对象实现IsModified接口来简化缓存
— 目标对象(Target Object)包含连接点的对象也被称作被通知或被代理对象
— AOP代理(AOP Proxy)AOP框架创建的对象包含通知在Spring中AOP代理可以是JDK动态代理或CGLIB代理
— 编织(Weaving)组装方面来创建一个被通知对象这可以在编译时完成(例如使用AspectJ编译器)也可以在运行时完成Spring和其他纯Java AOP框架一样在运行时完成织入
各种通知类型包括
— Around通知包围一个连接点的通知如方法调用这是最强大的通知Aroud通知在方法调用前后完成自定义的行为它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行
— Before通知在一个连接点之前执行的通知但这个通知不能阻止连接点前的执行(除非它抛出一个异常)
— Throws通知在方法抛出异常时执行的通知Spring提供强制类型的Throws通知因此你可以书写代码捕获感兴趣的异常(和它的子类)不需要从Throwable或Exception强制类型转换
— After returning通知在连接点正常完成后执行的通知例如一个方法正常返回没有抛出异常
Around通知是最通用的通知类型大部分基于拦截的AOP框架(如Nanning和Jboss )只提供Around通知
如同AspectJSpring提供所有类型的通知我们推荐你使用最为合适的通知类型来实现需要的行为例如如果只是需要用一个方法的返回值来更新缓存你最好实现一个after returning通知而不是around通知虽然around通知也能完成同样的事情使用最合适的通知类型使编程模型变得简单并能减少潜在错误例如你不需要调用在around通知中所需使用的MethodInvocation的proceed()方法因此就调用失败
切入点的概念是AOP的关键它使AOP区别于其他使用拦截的技术切入点使通知独立于OO的层次选定目标例如提供声明式事务管理的around通知可以被应用到跨越多个对象的一组方法上 因此切入点构成了AOP的结构要素
下面让我们实现一个Spring AOP的例子在这个例子中我们将实现一个before advice这意味着advice的代码在被调用的public方法开始前被执行以下是这个before advice的实现代码
package comascenttechspringaoptest;
import javalangreflectMethod;
import orgspringframeworkaopMethodBeforeAdvice;
public class TestBeforeAdvice implements MethodBeforeAdvice {
public void before(Method m Object[] args Object target)
throws Throwable {
Systemoutprintln(Hello world! (by
+ thisgetClass()getName()
+ ));
}
}
接口MethodBeforeAdvice只有一个方法before需要实现它定义了advice的实现before方法共用个参数它们提供了相当丰富的信息参数Method m是advice开始后执行的方法方法名称可以用作判断是否执行代码的条件Object[] args是传给被调用的public方法的参数数组当需要记日志时参数args和被执行方法的名称都是非常有用的信息你也可以改变传给m的参数但要小心使用这个功能编写最初主程序的程序员并不知道主程序可能会和传入参数的发生沖突Object target是执行方法m对象的引用
在下面的BeanImpl类中每个public方法调用前都会执行advice代码如下
package comascenttechspringaoptest;
public class BeanImpl implements Bean {
public void theMethod() {
Systemoutprintln(thisgetClass()getName()
+ + new Exception()getStackTrace()[]getMethodName()
+ ()
+ says HELLO!);
}
}
类BeanImpl实现了下面的接口Bean代码如下
package comascenttechspringaoptest;
public interface Bean {
public void theMethod();
}
虽然不是必须使用接口但面向接口而不是面向实现编程是良好的编程实践Spring也鼓励这样做
pointcut和advice通过配置文件来实现因此接下来你只需编写主方法的Java代码代码如下
package comascenttechspringaoptest;
import orgntextApplicationContext;
import orgntextsupportFileSystemXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//Read the configuration file
ApplicationContext ctx
= new FileSystemXmlApplicationContext(springconfigxml);
//Instantiate an object
Bean x = (Bean) ctxgetBean(bean);
//Execute the public method of the bean (the test)
xtheMethod();
}
}
我们从读入和处理配置文件开始接下来马上要创建它这个配置文件将作为粘合程序不同部分的胶水读入和处理配置文件后我们会得到一个创建工厂ctx任何一个Spring管理的对象都必须通过这个工厂来创建对象通过工厂创建后便可正常使用
仅仅用配置文件便可把程序的每一部分组装起来代码如下
<?xml version= encoding=UTF?>
<!DOCTYPE beans PUBLIC //SPRING//DTD BEAN//EN org/dtd/springbeansdtd>
<beans>
<!CONFIG>
<bean id=bean class=orgspringframeworkaopframeworkProxyFactoryBean>
<property name=proxyInterfaces>
<value>comascenttechspringaoptestBean</value>
</property>
<property name=target>
<ref local=beanTarget/>
</property>
<property name=interceptorNames>
<list>
<value>theAdvisor</value>
</list>
</property>
</bean>
<!CLASS>
<bean id=beanTarget class=comascenttechspringaoptestBeanImpl/>
<!ADVISOR>
<!Note: An advisor assembles pointcut and advice>
<bean id=theAdvisor class=orgspringframeworkaopsupportRegexpMethod PointcutAdvisor>
<property name=advice>
<ref local=theBeforeAdvice/>
</property>
<property name=pattern>
<value>com\ascenttech\springaop\test\Bean\theMethod</value>
</property>
</bean>
<!ADVICE>
<bean id=theBeforeAdvice class=comascenttechspringaoptestTestBefore Advice/>
</beans>
个bean定义的次序并不重要我们现在有了一个advice一个包含了正则表达式pointcut的advisor一个主程序类和一个配置好的接口通过工厂ctx这个接口返回自己本身实现的一个引用
BeanImpl和TestBeforeAdvice都是直接配置我们用一个惟一的ID创建一个bean元素并指定了一个实现类这就是全部的工作
advisor通过Spring framework提供的一个RegexMethodPointcutAdvisor类来实现我们用advisor的第一个属性来指定它所需的advicebean第二个属性则用正则表达式定义了pointcut确保良好的性能和易读性
最后配置的是bean它可以通过一个工厂来创建bean的定义看起来比实际上要复杂bean是ProxyFactoryBean的一个实现它是Spring framework的一部分这个bean的行为通过以下的个属性来定义
— 属性proxyInterface定义了接口类
— 属性target指向本地配置的一个bean这个bean返回一个接口的实现
— 属性interceptorNames是惟一允许定义一个值列表的属性这个列表包含所有需要在beanTarget上执行的advisor注意advisor列表的次序是非常重要的
原文