【原创】Spring AOP小结

来源:互联网 发布:云计算龙头股 编辑:程序博客网 时间:2024/06/05 11:08

我自己编写的Word文档形式的总结,希望能帮助大家。下面开始讲第一节

1.1   什么是AOP

AOPAspect-Oriented Programming 的缩写,中文意思是面向切面编程,也有译作面向方面编程的,因为Aspect有“方面、见地”的意思。AOP实际上是一种编程思想,最初是由Xerox PARC研究中心的研究人员首先提出的,其目标是通过提供一些方法和技术,把各种问题分解成一系列的functional component(功能组件)和一系列横跨多个functional componentaspect(方面),然后组合这些componentaspect,获得系统的实现。

在传统的面向对象(Object-Oriented Progr ammingOOP)编程中,一直基于三层开发模式编写应用系统的我们总是在关注如何将业务代码在垂直切面下与表示层和数据访问层分离,“横切面(方面)”关注却很少。也就是说,我们利用OOP思想可以很好的处理业务流程,却不能把系统中的某些特定的重复性行为封装在某个模块中

AOP的编程思想,关注系统的“横切面(方面)”,允许我们在处理业务流程时不必再考虑日志、安全、事务等功能,而是把它交给一个特定的处理日志、安全或事务“拦截器”去完成,在适当的时候拦截程序的执行流程。这样,业务流程就完全的从其它无关的代码中解放出来,各模块之间的分工更加明确,程序维护也变得容易多了。

   

    以下是一些与AOP相关的名词解释:

切面或方面(aspect:指“横切面”的关注点,如把“日志功能”看成是系统中的一个关注点或一个切面。

连接点(join point:是程序执行中一个执行点,比如类中的一个方法,也就是触发了“通知(advice)”执行的那个方法。例如,连接点是个抽象的概念。

切入点(pointcut:切入点是连接点的集合,它通常和advice(通知)联系在一起,是切面和程序流程的交叉点。比如说,定义一个pointcut,它将抛出异常ClassNotFoundException和某个advice(通知)联系起来,那么在程序执行过程中,如果抛出了该异常,那么相应的通知就会被触发执行。

通知(adcice):也可以叫做“装备”,指切面在程序运行到某个连接点所触发的动作。在这个动作中我们可以定义自己的处理逻辑。“通知”需要切入点和连接点联系起来才会被触发。目前AOP定义了五种通知:前置通知(Before advice)、后置通知(After returning advice)、环绕通知(Around Advice)、异常通知(After throwing advice)、最终通知(After advice)。

目标对象(target object):被一个或者多个切面“通知”的对象。如已经配置在Spring中成为一个BeanFwxxBizImpl,就可以被认为是一个目标对象。

1.2   Spring AOP

AOP并非是OOP的替代品,而是对OOP的一种补充,帮助OOP以更好的解决“横切面”问题。AOP一共有两种形式:

l  静态AOP:即第一代AOP,指在编译时期就职入Aspect代码,以最初的AspectJ为杰出代表。其特点是,相应的横切关注点以Aspect形式实现之后,会通过特定的编译器将实现后的Aspect(方面)代码编译并职入到系统的静态类中。

l  动态AOP:又称为第二代AOP,该时代的AOP实现大都通过Java语句提供的各种动态特性如“动态代理”,实现Aspect(方面)代码职入到当前系统的过程。利用JDK动态特效完成AOP的框架有:JBossAOPSpringAOPAOP框架。

 

静态AOP在编译期间完成职入过程,所以性能较好,但缺乏一定的灵活性;而动态AOP是通过JDK的“动态代理”机制完成的职入,所以灵活性较好,但损失了性能。在选择使用哪种AOP时,需要根据项目中的具体清空来考虑。

 

“拦截”的方式被Spring定义成4种:

前置通知(Before:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常),这里所说的连接点一般是指类中的“方法”。

 

后置通知(After Returning:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常,则“横切面(方面)”代码执行。

 

环绕通知(Around:包围一个连接点的通知,如方法调用。这是最强大的通知。Around通知在方法调用前后完成自定义的行为,它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出汽车来中断连接点的执行。

 

异常通知(Throws:在方法抛出异常时执行的通知。

 

2.1 Spring 1.x风格的AOP

         我们以模拟的方式为一个网上书店系统的业务方法添加“业务日志”功能。要求在其业务方法调用前记录日志,记录被调用的方法名称、时间和参数。

 

 

         按照AOP的开发原则,我们先模拟两个业务方法buy()(购买)和comment()(评论)的实现,然后再专心利用Spring AOP实现日志方面的代码,最后在Spring配置文件中将日志Pojo职入到业务系统中。

 

业务代码清单如下:

package com.ssh.ct_13.biz;

 

public interface IBookBiz {

 

    //购买图书(顾客名,书名,价钱)

    public void buy(String uname,String bookName,double money);

    //发表评论(顾客名,评论内容)

    public void comment(String uname,String comment);

}

package com.ssh.ct_13.biz.impl;

 

import com.ssh.ct_13.biz.IBookBiz;

 

public class BookBizImpl implements IBookBiz {

 

    public void buy(String uname, String bookName, double money) {

       System.out.println("--业务buy方法开始执行--");

       System.out.println(uname+"购买图书:"+bookName);

       System.out.println(uname+"增加积分:"+(int)money/10);

       System.out.println("向物流系统发送货单");

       System.out.println("--业务方法执行完毕--");

    }

 

    public void comment(String uname, String comment) {

       System.out.println("--评论方法开始执行--");

       System.out.println(uname+"发表了评论:"+comment);

       System.out.println("--评论方法执行完成--");

    }

 

}

1.       前置通知(Before

不论业务方法是否执行成功,我们都应该将这次调用记录下来,所以使用前置通知来完成。

前置通知代码如下:

package com.ssh.ct_13.aop;

 

import java.lang.reflect.Method;

import java.text.SimpleDateFormat;

import java.util.Arrays;

import java.util.Date;

 

import org.apache.commons.logging.LogFactory;

import org.springframework.aop.MethodBeforeAdvice;

 

public class LogAdvice implements MethodBeforeAdvice {

 

         public static final org.apache.commons.logging.Log log = LogFactory.getLog(LogAdvice.class);

        

         public void before(Method m, Object[] arg1, Object arg2)

                            throws Throwable {

                   // TODO Auto-generated method stub

                   SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd hh:mm:ss");

                   log.warn("【系统日志】:" + sf.format(new Date()) + "调用" + m.getName() + "(" + Arrays.toString(arg1) + ")");

         }

 

}

log4j.properties文件配置如下:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.Target=System.out

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n

log4j.appender.file=org.apache.log4j.FileAppender

log4j.appender.file.File=house.log

log4j.appender.file.layout=org.apache.log4j.PatternLayout

log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %1 %m%n

log4j.rootLogger=warn,stdout,file

前置通知需要实现MethodBeforeAdvice接口,参数中m为被通知的目标方法对象,args为被通知目标方法的参数列表,target是被调用方法所属的对象实例。

 

Spring采用“代理”的方式将通知职入到原Bean中。代理类org.springframework.aop.framework.ProxyFactoryBean将“业务Bean”和“方面代码”组装在一起。配置完成后,我们通过代理类访问原Bean时,代理类可以自动根据通知类型决定执行什么通知和原Bean

Spring配置文件的配置(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"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

 

    <!-- 业务组件 -->

    <bean id="bookBizTarget" class="com.ssh.ct_13.biz.impl.BookBizImpl"></bean>

    <!-- 日志组件 -->

    <bean id="logAdvice" class="com.ssh.ct_13.aop.LogAdvice"></bean>

    <!-- 代理 ProxyFactoryBean为代理工厂类-->

    <bean id="bookBiz" class="org.springframework.aop.framework.ProxyFactoryBean">

       <!-- proxyInterfaces是被代理的接口 -->

       <property name="proxyInterfaces">

           <value>com.ssh.ct_13.biz.IBookBiz</value>

       </property>

       <!-- interceptorNames是通知的列表,可以有多个 -->

       <property name="interceptorNames">

           <list>

              <value>logAdvice</value>

           </list>

       </property>

       <!-- target是被代理的实现类 -->

       <property name="target" ref="bookBizTarget"></property>

    </bean>

</beans>

测试代码如下:

package com.ssh.ct_13.test;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

import com.ssh.ct_13.biz.IBookBiz;

 

public class AOPTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

       IBookBiz bookBiz = (IBookBiz)context.getBean("bookBiz");

       bookBiz.buy("Abe", "重构-改善既有代码的设计", 56.0);

       bookBiz.comment("佚名", "软件工程领域的超级经典著作");

    }

 

}

运行程序,查看程序执行结果。

2.       后置通知(After returning

因商家要举行促销活动,在活动期间,买书和发表评论均额外增加1积分。

业务方法已经完成,本机只需要专心编写“方面代码”即可,代码如下:

package com.ssh.ct_13.aop;

 

import java.lang.reflect.Method;

 

import org.springframework.aop.AfterReturningAdvice;

 

public class ActiveAfterAdvice implements AfterReturningAdvice {

 

         public void afterReturning(Object arg0, Method arg1, Object[] arg2,

                            Object arg3) throws Throwable {

                   System.out.println("后置通知:节日活动期间,买书和发表评论额外赠送1积分。");

         }

}

Spring配置文件中只需要新建一个“通知”Bean,再修改代理Bean的配置即可。修改后的Spring配置内容如下:

<!-- 后置通知 -->

    <bean id="activeAdvice" class="com.ssh.ct_13.aop.ActiveAfterAdvice"></bean>

    <!-- 代理 ProxyFactoryBean为代理工厂类-->

    <bean id="bookBiz" class="org.springframework.aop.framework.ProxyFactoryBean">

       <!-- proxyInterfaces是被代理的接口 -->

       <property name="proxyInterfaces">

           <value>com.ssh.ct_13.biz.IBookBiz</value>

       </property>

       <!-- interceptorNames是通知的列表,可以有多个 -->

       <property name="interceptorNames">

           <list>

              <value>logAdvice</value>

              <!-- 仅仅是在这增加了一个通知” -->

              <value>activeAdvice</value>

           </list>

       </property>

       <!-- target是被代理的实现类 -->

       <property name="target" ref="bookBizTarget"></property>

    </bean>

测试代码不变,运行查看控制台输出结果。

2.2  基于<aop:config/>元素的AOP

1.       异常通知

在客户的“生产环境”,记录程序在项目实施后放生的异常,是一项重要的工作,它有助于在客户无法用语言描述清楚发生了什么程序BUG的情况下,找到解决问题的突破口。

 

现在,我们使用Spring2.x风格的<aop:config/>来开发异常通知。我们重用已经开发完成的业务类,但因为要实现异常通知,所以还需要将业务类修改出一个异常。代码如下:

package com.ssh.ct_13.biz.impl;

 

import com.ssh.ct_13.biz.IBookBiz;

 

public class BookBizImpl implements IBookBiz {

 

    public void buy(String uname, String bookName, double money) {

       System.out.println("--业务buy方法开始执行--");

       System.out.println(uname+"购买图书:"+Integer.parseInt(bookName));

       System.out.println(uname+"增加积分:"+(int)money/10);

       System.out.println("向物流系统发送货单");

       System.out.println("--业务方法执行完毕--");

    }

 

    public void comment(String uname, String comment) {

       System.out.println("--评论方法开始执行--");

       System.out.println(uname+"发表了评论:"+comment);

       System.out.println("--评论方法执行完成--");

    }

}

“异常通知”类的代码会发生一些变化。基于Spring2.x风格的AOP已经不需要我们实现指定的接口和方法,而仅仅是一个普通的Pojo。我们可以自定义类名和方法名,但方法参数一般须指定为JoinPoint,这个对象中存储被职入方法的参数值,方法名等信息,由Spring管理并传入。在本例中,由于是异常通知,所以还需多指定一个异常参数。代码如下:

package com.ssh.ct_13.aop;

 

import java.util.Arrays;

 

import org.apache.commons.logging.LogFactory;

 

public class LogThrowingAdvice {

 

    private static final org.apache.commons.logging.Log log = LogFactory.getLog(LogThrowingAdvice.class);

   

    public void afterThrowing(org.aspectj.lang.JoinPoint jp,Throwable throwable){

        log.error(jp.getSignature().getDeclaringType()+"."+jp.getSignature().getName()+"("+Arrays.toString(jp.getArgs())+")",throwable);

       System.out.println("异常通知启动");

    }

}

   虽然已经不需要实现任何指定的接口和方法,但仍需要告诉Spring我们的意愿,想在哪做pointcut?想为哪个类职入通知?等等这一切需要通过配置来完成。基于Spring2.xAOP配置如下:

<?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"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

    http://www.springframework.org/schema/aop

    http://www.springframework.org/schema/aop/spring-aop.xsd

    ">

    <!-- 异常通知日志组件 -->

    <bean id="logThrowingAdvice" class="com.ssh.ct_13.aop.LogThrowingAdvice"></bean>

    <!-- Spring2.x AOP配置 -->

    <aop:config>

       <aop:pointcut id="ex" expression="execution(* com.ssh.ct_13.aop.*.*(..))"/>

       <aop:aspect ref="logThrowingAdvice">

           <aop:after-throwing pointcut-ref="ex" method="afterThrowing" throwing="throwable"/>

       </aop:aspect>

    </aop:config>

</beans>

注意:使用Spring2.xAOP配置需要在配置文件头部导入AOP命名空间。

expression属性是在定义切面为execution(* com.ssh.ct_13.aop.*.*(..)) 。表示通知将职入到com.ssh.ct_13.aop包下的所有类的所有方法。

Method属性是在指定“异常通知”中的方法。throwing属性是在指定“异常通知”类的方法中能够传入异常的参数。

 

最后,编写测试类如下:

package com.ssh.ct_13.test;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

import com.ssh.ct_13.biz.IBookBiz;

 

public class AOPTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

       // TODO Auto-generated method stub

       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

       IBookBiz bookBiz = (IBookBiz)context.getBean("bookBizTarget");

       bookBiz.buy("Abe", "重构-改善既有代码的设计", 56.0);

       bookBiz.comment("佚名", "软件工程领域的超级经典著作");

    }

}

运行查看控制台结果和日志文件如下: