原理html
AOP(Aspect Oriented Programming),也就是面向方面编程的技术。AOP基于IoC基础,是对OOP的有益补充。java
AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,全部大中型应用都要涉及到的持久化管理(Persistent)、事务管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。web
AOP正在成为软件开发的下一个光环。使用AOP,你能够将处理aspect的代码注入主程序,一般主程序的主要目的并不在于处理这些aspect。AOP能够防止代码混乱。正则表达式
Spring framework是颇有前途的AOP技术。做为一种非侵略性的、轻型的AOP framework,你无需使用预编译器或其余的元标签,即可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOP framework,其余人仍是像往常同样编程。spring
AOP概念数据库
让咱们从定义一些重要的AOP概念开始。express
— 方面(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框架同样,在运行时完成织入。
各类通知类型包括:
— Around通知:包围一个链接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用先后完成自定义的行为,它们负责选择继续执行链接点或经过返回它们本身的返回值或抛出异常来短路执行。
— Before通知:在一个链接点以前执行的通知,但这个通知不能阻止链接点前的执行(除非它抛出一个异常)。
— Throws通知:在方法抛出异常时执行的通知。Spring提供强制类型的Throws通知,所以你能够书写代码捕获感兴趣的异常(和它的子类),不须要从Throwable或Exception强制类型转换。
— After returning通知:在链接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。
Around通知是最通用的通知类型。大部分基于拦截的AOP框架(如Nanning和Jboss 4)只提供Around通知。
如同AspectJ,Spring提供全部类型的通知,咱们推荐你使用最为合适的通知类型来实现须要的行为。例如,若是只是须要用一个方法的返回值来更新缓存,你最好实现一个after returning通知,而不是around通知,虽然around通知也能完成一样的事情。使用最合适的通知类型使编程模型变得简单,并能减小潜在错误。例如,你不须要调用在around通知中所需使用的MethodInvocation的proceed()方法,所以就调用失败。
切入点的概念是AOP的关键,它使AOP区别于其余使用拦截的技术。切入点使通知独立于OO的层次选定目标。例如,提供声明式事务管理的around通知能够被应用到跨越多个对象的一组方法上。 所以切入点构成了AOP的结构要素。
拦截器(也称拦截机)
拦截机 (Interceptor), 是 AOP (Aspect-Oriented Programming) 的另外一种叫法。AOP自己是一门语言,只不过咱们使用的是基于JAVA的集成到Spring 中的 SpringAOP。一样,咱们将经过咱们的例子来理解陌生的概念。
接口类
实现类
AOP拦截器
测试类
配置文件
输出:
user:shawn
printUser---!
printUser user:hello!
结论:调用方法的时候 传入的值被拦截修改了.
拦截器中的事务管理(事务拦截机)
若是不采用拦截机的机制时,在使用JDBC进行数据库访问时,存在两种状况:
自动提交模式是不被推荐的,由于每一个操做都将产生一个事务点,这对于大的应用来讲性能将受到影响;再有,对于常见的业务逻辑,这种模式显得无能为力。好比:
转账,从A账户取出100元,将其存入B账户;若是在这两个操做之间发生了错误,那么用户A将损失了100元,而原本应该给账户B的,却由于失败给了银行。
因此,建议在全部的应用中,若是使用 JDBC 都将不得不采用非自动提交模式(大家要能发现了在咱们的 JDBC 那个例子中,咱们采用的就是自动提交模式,咱们是为了把精力放在JDBC上,而不是事务处理上),即咱们不得不在每一个方法中:
这样代码在AOP的倡导者看来是“肮脏”的代码。他们认为,全部的与事务有关的方法都应当能够集中配置(见声明性事务控制),并自动拦截,程序应当关心他们的主要任务,即商业逻辑,而不该和事务处理的代码搅和在一块儿。
我先看看 Spring 是怎么作到拦截的:
这里由于要用到JpetStore项目中的代码,咱们将 applicationContext.xml 所有内容列出:
<?xml version="1.0" encoding="UTF-8"?> <!-- - Application context definition for JPetStore's business layer. - Contains bean references to the transaction manager and to the DAOs in - dataAccessContext-local/jta.xml (see web.xml's "contextConfigLocation"). Jpetstore 的应用上下文定义,包含事务管理和引用了在 dataAccessContext-local/jta.xml(具体使用了哪一个要看 web.xml 中的 'contextConfigLocation' 的配置)中注册的DAO --> <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"> <!-- ========================= GENERAL DEFINITIONS ========================= --> <!-- Configurer that replaces ${...} placeholders with values from properties files 占位符的值将从列出的属性文件中抽取出来 --> <!-- (in this case, mail and JDBC related properties) --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>WEB-INF/mail.properties</value> <value>WEB-INF/jdbc.properties</value> </list> </property> </bean> <!-- MailSender used by EmailAdvice 指定用于发送邮件的 javamail 实现者,这里使用了 spring 自带的实现。此 bean 将被 emailAdvice 使用 --> <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="host" value="${mail.host}"/> </bean> <!-- ========================= BUSINESS OBJECT DEFINITIONS ======================== --> <!-- 不须要,由于被 SpringMVC 的实现使用 Generic validator for Account objects, to be used for example by the Spring web tier --> <bean id="accountValidator" class="org.springframework.samples.jpetstore.domain.logic.AccountValidator"/> <!-- 不须要,由于被 SpringMVC 的实现使用 Generic validator for Order objects, to be used for example by the Spring web tier --> <bean id="orderValidator" class="org.springframework.samples.jpetstore.domain.logic.OrderValidator"/> <!-- 主要的商业逻辑对象,即咱们所说的门面对象
注入了全部的DAO,这些DAO是引用了 dataAccessContext-xxx.xml 中定义的DAO
门面对象中的全部方法的事务控制将经过下面的 aop:config 来加以控制 - JPetStore primary business object (default implementation). - Transaction advice gets applied through the AOP configuration below. --> <bean id="petStore" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl"> <property name="accountDao" ref="accountDao"/> <property name="categoryDao" ref="categoryDao"/> <property name="productDao" ref="productDao"/> <property name="itemDao" ref="itemDao"/> <property name="orderDao" ref="orderDao"/> </bean> <!-- ========================= ASPECT CONFIGURATION ======================== --> <!-- AOP配置,用来控制
哪些
方法将须要进行事务处理,采用了AspectJ 的语法 --> <aop:config> <!-- This definition creates auto-proxy infrastructure based on the given pointcut, expressed in AspectJ pointcut language. Here: applying the advice named "txAdvice" to all methods on classes named PetStoreImpl. --> <!-- 指出在 PetStoreFacade 的全部方法都将采用 txAdvice(在紧接着的元素中定义了)事务方针,注意,咱们这里虽然指定的是接口 PetStoreFacace, 但其暗示着其全部的实现类也将
一样具备这种性质,由于自己就是实现类的方法在执行的,接口是没有方法体的。 --> <aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/> <!-- This definition creates auto-proxy infrastructure based on the given pointcut, expressed in AspectJ pointcut language. Here: applying the advice named "emailAdvice" to insertOrder(Order) method of PetStoreImpl --> <!-- 当执行 PetStoreFacade.insertOrder方法,该方法最后一个参数为Order类型时(其实咱们的例子中只有一个 insertOrder 方法,但这告诉了咱们,当咱们的接口或类中有重载了的方法,
而且各个重载的方法可能使用不一样的拦截机机制时,咱们能够经过方法的参数加以指定),将执行emailAdvice(在最后定义的那个元素)--> <aop:advisor pointcut="execution(* *..PetStoreFacade.insertOrder(*..Order))" advice-ref="emailAdvice"/> </aop:config> <!-- 事务方针声明,用于控制采用
什么样
的事务策略 Transaction advice definition, based on method name patterns. Defaults to PROPAGATION_REQUIRED for all methods whose name starts with "insert" or "update", and to PROPAGATION_REQUIRED with read-only hint for all other methods. --> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="insert*"/> <tx:method name="update*"/> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 拦截机,用于在适当的时机(经过AOP配置,如上面)在方法执行成功后发送邮件 AOP advice used to send confirmation email after order has been submitted --> <!-- --> <bean id="emailAdvice" class="org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice"> <property name="mailSender" ref="mailSender"/> </bean> <!-- ========================= 忽略 REMOTE EXPORTER DEFINITIONS ======================== --> </beans>
这个配置比想象的要简单的多:
1. 全部的拦截机配置都放在 <aop:config> 配置元素中.
2. 下面仍是须要理解一下几个有关AOP的专用名词,不过,是挺抽象的,最好能会意出其的用意
由于 方法执行切入点 execution 为最多见的切入点类型,咱们着重介绍一下,execution 的彻底形式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
这是一个正则表达式,其中由 '?' 结尾的部分是可选的。翻译过来就是:
执行(方法访问修饰符? 方法返回类型 声明类型? 方法名(方法参数类型) 抛出异常?)
全部的这些都是用来定义执行切入点,即那些方法应该被侯选为切入点:
例如,全部dao代码被定义在包 com.xyz.dao 及子包 com.xyz.dao.hibernate, 或者其它,若是还有的话,子包中, 里面定义的是提供DAO功能的接口或类,那么表达式:
execution(* com.xyz.dao..*.*(..))
表示切入点为:执行定义在包 com.xyz.dao 及其子包(由于 .. 所致) 中的任何方法
详细状况能够参见 Spring refernce: 6.2.3.4. Examples
所以这个表达式为执行定义在类 PetStoreFacade 及其实现类中的全部方法,采起的动做定义在 txAdvice 中. 关于该 advice 的定义,(见声明性事务控制)一节
<aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/>
来为了进行事务控制,咱们只需简单地配置几下,全部的工做都由 Spring 来作。这样当然很好,但有时咱们须要有咱们特有的控制逻辑。由于Spring 不可能包含全部人须要的全部拦截机。因此它提供了经过程序的方式加以定制的方式。咱们的项目中就有这么一个拦截机,在用户确认付款后,将定单信息经过 email 的方式发送给注册用户的邮箱中。
<aop:config> ... <!-- 当执行 PetStoreFacade.insertOrder方法,该方法最后一个参数为Order类型时(其实咱们的例子中只有一个 insertOrder 方法,但这告诉了咱们,当咱们的接口或类中有重载了的方法,
而且各个重载的方法可能使用不一样的拦截机机制时,咱们能够经过方法的参数加以指定),将执行emailAdvice(在最后定义的那个元素)--> <aop:advisor pointcut="execution(* *..PetStoreFacade.insertOrder(*..Order))" advice-ref="emailAdvice"/> </aop:config>
红色的注释已经说的很清楚这个 Advisor 了,它的切入点(pointcut) 为 PetStoreFacade 的 void insertOrder(Order order) 方法,采起的动做为引用的 emailAdvice, 下面咱们就来看看 emailAdvice:
<bean id="emailAdvice" class="org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice"> <property name="mailSender" ref="mailSender"/> </bean>
它给了这个 advice 的实现类为 logic 包中 SendOrderConfirmationEmailAdvice, 该Bean 引用了咱们前面定义的邮件发送器(一个 Spring 内置的邮件发送器).
下面看看这个实现类:
public class SendOrderConfirmationEmailAdvice implements AfterReturningAdvice, InitializingBean { // user jes on localhost private static final String DEFAULT_MAIL_FROM = "test@pprun.org"; private static final String DEFAULT_SUBJECT = "Thank you for your order!"; private final Log logger = LogFactory.getLog(getClass()); private MailSender mailSender; private String mailFrom = DEFAULT_MAIL_FROM; private String subject = DEFAULT_SUBJECT; public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
} public void setMailFrom(String mailFrom) { this.mailFrom = mailFrom; } public void setSubject(String subject) { this.subject = subject; } public void throws Exception { if (this.mailSender == null) { throw new IllegalStateException("mailSender is required"); } } /**
*
* @param returnValue 被拦截的方法的返回值
* @param m 被拦截的方法的全部信息(Method类封装了这些信息)
* @param args 被拦截的方法的全部参数组成的数组
* @param target 目标对象,对于方法执行来讲,便是方法所在的类的实例(与 this 同,批当前对象)
* @throws java.lang.Throwable
*/ public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable { // 咱们被拦截的方法为 void insertOrder(Order order),方法只有一个参数,因此可知数据的第1个元素便是被传进的 order 对象 // 获得了order 对象,就能够将 order 对应的账户名及账单号发送到邮件中,以便确认无误。 Order order = (Order) args[0]; Account account = ((PetStoreFacade) target).getAccount(order.getUser().getUsername()); // don't do anything if email address is not set if (account.getEmail() == null || account.getEmail().length() == 0) { return; } StringBuffer text = new StringBuffer(); text.append("Dear ").append(account.getFirstname()). append(' ').append(account.getLastname()); text.append(", thank your for your order from JPetStore. " + "Please note that your order number is "); text.append(order.getId()); SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setTo(account.getEmail()); mailMessage.setFrom(this.mailFrom); mailMessage.setSubject(this.subject); mailMessage.setText(text.toString()); try { this.mailSender.send(mailMessage); } catch (MailException ex) { // just log it and go on logger.warn("An exception occured when trying to send email", ex); } } }
1. 红色的内容即为反向注入的 mailSender 属性2. 蓝色的内容为 Spring Bean 的一个通用的接口 InitializingBean ,实现类须要实现该接口定义的方法 afterPropertiesSet() ,该方法中通常是在Bean 被初始化后并设置了全部的 setter 注入后调用的。因此这里是保证邮件发送器配置正确。由于若是没有配置正确,下面的工做是没法进行的,因此与其等那时抛出异常,还不如早早地在部署时就告知(经过抛出 IllegalStateException 来提示)3. 绿色的内容为这个 Advise 的核心,即在切入点被切入后将采用的动做。由于 Advise 也一样有多种类型,好比咱们这里的“方法正常返回”,“方法执行前”,“方法执行后”,“环绕在方法执行先后”,“方法抛出异常时”等等(详情参见 Spring Reference: 6.2.4. Declaring advice)。可是咱们的逻辑为在用户确认定单而且执行成功(所谓的成功是指将这必定单插入到了表 Order 中了)后,将发送一确认信。因此”方法正常返回“彻底符合咱们的要求。接口AfterReturningAdvice 便是 Spring中表示”方法正常返回“ 这一语义的 Advice, 因此咱们实现这个接口及其必须的方法 afterReturning.方法代码的工做其实并不重要,只要咱们理解这些“魔法”同样的技术后,实现代码是很简单的。值得说起的是这个方法的参数,这些参数是封装了切入点的全部信息,请见上面的注释。在咱们的实现中只使用了被拦截方法的参数,在复杂的 Advice 实现中可能会用到切入点全部信息。