Spring AOP 入门

553 查看

引言

AOP是软件开发思想发展到一定阶段的产物,AOP的出现并不是为了代替OOP,仅作为OOP的有益补充,在下面的例子中这个概念将会得到印证。AOP的应用场合是受限制的,一般适用于那些具有横切逻辑的应用场合,例如性能监测,访问控制,事务管理,日志记录。在平常的应用开发中AOP很难被使用到,但是AOP是Spring的亮点之一,有必要一看。

一 AOP以及术语

AOP是Aspect Oriented Programing的简称,被译为面向切面编程。AOP希望将散落在业务逻辑函数中的相同代码抽取到一个独立的模块中。举个例子:

javaclass A{
    public void run()
    {
        doSomething();
        doAthings();
        doOtherthing();
    }
}

class B{
    public void run()
    {
         doSomething();
         doBthings();
         doOtherthing();
    }
}

class C{
    public void run()
    {
        doSomething();
        doCthings();
        doOtherthing();
        doMorethings();
    }
}

例如上述三个类中的run方法,都有doSomething和doOtherthing代码,AOP就是要把这些代码抽取出来。我们知道,抽取代码很容易,但是如何将这些独立的逻辑代码再融合到业务类中完成和原来一样的业务操作,这才是AOP要解决的问题。

术语

Joinpoint连接点:程序执行的某个特定位置,例如类初始化前,类初始化后,方法执行前,方法执行后等,Spring只支持方法的连接点,即方法执行前,方法执行后,方法抛出异常时。
Pointcut切点:每个类中都拥有多个连接点,如拥有两个方法的类,这两个方法都是连接点,切点可以在连接点中定位一个或多个连接点。
Advice增强(通知):增强是织入目标类连接点上的一段程序代码,即当程序到达一个执行点后会执行相对应的一段代码,Spring提供的Advice都带有接入点方位,例如BeforeAdvice,AftereturningAdvice,ThrowsAdvice等。只有结合切点和通知才能确定特定的连接点并执行对应代码。
Target目标对象:被嵌入代码的类。
Introductiony引介:可以为类添加属性和方法。
Weaving织入:AOP就像一台织布机,将Advice代码嵌入到对应的类中。
Proxy代理:一个类被AOP织入后,就会产生一个代理对象,他融合了原有类代码和Advice代码。
Aspect切面:由切点和增强组成,他包括了连接点定义和横切逻辑代码的定义,SpringAOP就是负责实施切面的框架。

AOP有三种织入方式:

编译期织入:这要求是用特殊的Java编译器
类装载期织入:要求使用特殊的类装载器
动态代理织入:在运行时期为目标对象生成代理对象的方式实现织入

Spring采用动态代理织入,AspectJ采用编译期织入和类装载织入。

二 Advice增强(通知)

Spring使用Advice类定义横切逻辑,Advice类还包括了在方法的哪一点加入代码的信息。

Advice类型:
前置增强:在方法执行前实施增强org.apringframework.aop.MethodBeforeAdvice
后置增强:在方法返回后实施增强org.springframework.aop.AfterReturningAdvice
异常抛出增强:在目标方法抛出异常后实施增强org.springframework.aop.ThrowsAdvice
环绕增强:在方法执行前和执行后实施增强org.aopaliance.intercept.MethodInterceptor
引介增强:在目标类中加入新的方法和属性org.springframework.aop.IntroductionInterceptor

这是典型的基于代理的AOP,使用方法如下

1 创建一个接口,并且实现它
2 创建Advice,实现上述任意接口
3 使用ProxyFactory来生成代理

接下来,我以简单的示例解释前四种Advice

java//Dog.java
//定义狗的接口
package test.aop;
/**
 * Created by gavin on 15-7-18.
 */
public interface Dog {
    public void shout(String name);
    public void sleep(String name);
}

//ChinaDog.java
//设计中华田园犬
package test.aop;
/**
 * Created by gavin on 15-7-18.
 */
public class ChinaDog implements Dog {
    @Override
    public void shout(String name) {
        System.out.println("中华田园犬"+name+" 叫了一声");
    }
    @Override
    public void sleep(String name) {
        System.out.println("中华田园犬"+name+" 睡着了");
    }
    public void error() throws Exception {
        throw new Exception("shout exception");
    }
}

//BeforeAdvice.java
//前置增强类
package test.aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * Created by gavin on 15-7-18.
 */
public class BeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        String name = (String)objects[0];    //参数获取
        System.out.println("中华田园犬"+name+" 前置增强");
    }
}

//AfterReturnAdvice.java
//后置增强类
package test.aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
 * Created by gavin on 15-7-18.
 */
public class AfterReturnAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        String name = (String)objects[0];
        System.out.println("中华田园犬"+name+" 后置增强");
    }
}

//SurroundAdvice.java
//环绕增强类
package test.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * Created by gavin on 15-7-18.
 */
public class SurroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object[] objects = methodInvocation.getArguments();
        String name = (String)objects[0];
        System.out.println("环绕增强----前"+name);

        Object object = methodInvocation.proceed();    //此处执行的是原有函数

        System.out.println("环绕增强----后"+name);

        return object;
    }
}

//ThrowAdvice.java
//异常抛出增强类
package test.aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
/**
 * Created by gavin on 15-7-18.
 */
public class ThrowAdvice implements ThrowsAdvice {
    @Override
    public void afterThrowing(Method method,Object[] args, Object obj ,Exception ex)throws Throwable
    {
        System.out.println("------------------------");
        System.out.println("------异常抛出增强");
        System.out.println("------method "+method.getName());
        System.out.println("------exception "+ex.getMessage());
        System.out.println("------------------------");
    }
}

//AopTest.java
//AOP测试类
package test.aop;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
/**
 * Created by gavin on 15-7-18.
 */
public class AopTest {
    private Dog dog;
    private Advice advice;
    private ProxyFactory pf;


    @BeforeTest
    public void init()
    {
        dog = new ChinaDog();
        pf = new ProxyFactory();
        pf.setTarget(dog);
    }

    @Test
    public void beforeAdvice()
    {
        advice = new BeforeAdvice();
        pf.addAdvice(advice);
        Dog dog = (Dog)pf.getProxy();
        dog.shout("jim");
        dog.sleep("tom");
    }

    @Test
    public void afterReturndvice()
    {
        advice = new AfterReturnAdvice();
        pf.addAdvice(advice);
        Dog dog = (Dog)pf.getProxy();
        dog.shout("jim");
        dog.sleep("tom");
    }

    @Test
    public void arroundAdvice()
    {
        SurroundAdvice advice = new SurroundAdvice();
        pf.addAdvice(advice);
        Dog dog = (Dog)pf.getProxy();
        dog.shout("jim");
        dog.sleep("tom");
    }

    @Test
    public void throwAdvice()
    {
        advice = new ThrowAdvice();
        pf.addAdvice(advice);
        ChinaDog dog = (ChinaDog)pf.getProxy();
        try {
            dog.error();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

分别对前四种增强方式进行测试,得到以下结果:
前置增强

后置增强

环绕增强

异常抛出增强

三 创建切面

Spring通过org.springframework.aop.Pointcut接口描述切点,通过这个接口的ClassFilter定位到特定的类,通过MethodMatcher定位到特定的方法。

切面的创建方法
1 添加目标对象
2 添加Advice
3 定义切点Pointcut
4 定义Advisor
5 设置代理对象

在第二节的基础上,在applicationContext.xml中设置如下

xml<!--aop-->
<!-- 被代理对象 -->
<bean class="test.aop.ChinaDog" id="chinaDog"/>

<!-- 前置通知 -->
<bean class="test.aop.BeforeAdvice" id="beforeAdvice"/>
<!-- 后置通知 -->
<bean class="test.aop.AfterReturnAdvice" id="afterReturnAdvice"/>
<!-- 环绕通知 -->
<bean class="test.aop.SurroundAdvice" id="surroundAdvice"/>
<!-- 异常通知 -->
<bean class="test.aop.ThrowAdvice" id="throwAdvice"/>

<!-- 定义切点 在sleep方法上 通过静态正则表达式方法匹配切点-->
<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
       <property name="pattern" value=".*sleep"/>
</bean>
<!-- 定义通知者 将前置Advice绑在sleep方法上-->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
       <property name="advice" ref="beforeAdvice"/>
       <property name="pointcut" ref="sleepPointcut"/>
</bean>
<!-- 代理对象 -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxyFactoryBean">
       <!-- 设置代理接口集合 -->
       <property name="proxyInterfaces">
              <list>
                     <!--接口要写全-->
                     <value>test.aop.Dog</value>
              </list>
       </property>
       <!-- 把通知者(切面)织入代理对象 -->
       <property name="interceptorNames">
              <list>
                     <value>sleepHelperAdvisor</value>
              </list>
       </property>
       <!-- 配置被代理对象 -->
       <property name="target" ref="chinaDog"/>
</bean>

在AopTest.java中调用进行测试:

javapublic static void main(String[] args)
{
    //PropertyConfigurator.configure("web/WEB-INF/log4j.properties");
    ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml");
    Dog dog = (Dog)ac.getBean("proxyFactoryBean");//注意这里,调用的是代理bean,返回的是Dog的代理对象
    dog.shout("jim");
    dog.sleep("tom");
}

结果为:

至于前置Advice为什么出现了三次我也不懂,如果有人了解的话,请赐教。

四 基于@AspectJ配置切面

我们先用@AspectJ定义一个切面:

javapackage test.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by gavin on 15-7-18.
 */
@Aspect
public class PreAspect {
    @Before("execution(* shout(..))")//意思是返回值任意 参数任意
    public void beforeShout()
    {
        System.out.println("准备叫");
    }
}

在applicationContext.xml中设置:

xml<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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">

<!--基于@AspectJ切面的驱动器-->
<aop:aspectj-autoproxy/>
<bean class="test.aop.PreAspect" />
<bean class="test.aop.ChinaDog" id="chinaDog"/>

加入的内容有:
xmlns:aop="http://www.springframework.org/schema/aop

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

在AopTest.java中进行测试,代码如下:

javapublic static void main(String[] args)
{
    ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml");
    Dog dog = (Dog)ac.getBean("chinaDog");    //注意这里调用的是chinaDog
    dog.shout("jim");
    dog.sleep("tom");
}

结果为:

只有shout方法前调用了beforeShout方法,与我们设想的相同。

五 使用Spring来定义纯粹的POJO切面

使用方法也非常简单,使用spring的aop标签。xml如下:

xml<!-- POJO -->
<bean class="test.aop.PreAspect" id="preAspect" />
<bean class="test.aop.ChinaDog" id="chinaDog"/>
<aop:config>
       <aop:aspect ref="preAspect">
              <aop:before method="beforeShout" pointcut="execution(* *.shout(..))"/>
       </aop:aspect>
</aop:config>

java代码如下

javapublic static void main(String[] args)
{
    ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml");
    Dog dog = (Dog)ac.getBean("chinaDog");//注意这里调用的是chinaDog
    dog.shout("jim");
    dog.sleep("tom");
}

最终效果和@AspectJ相同

总结

AOP是OOP的有益补充,他为程序开发提供了一个新的思考角度,可以将重复性的横切逻辑抽取到统一的模块中。只有通过OOP的纵向抽象和AOP的横向抽取,程序才可以真正的解决重复性代码问题。

Spring采用JDK动态代理和CGLib动态代理技术在运行期间织入Advice。所以用户不用装备特殊的编译器或类装载器。要使用JDK动态代理,目标对象必须实现接口,而CGLib不对目标类采取限制。

JDK创建代理对象效率比CGLib高,而CGLib创建的代理对象运行效率比JDK的要高。

Spring实现AOP的三种方式:
经典代理@AspectJPOJO