首页 > 代码库 > Spring中的AOP

Spring中的AOP

什么是AOP?

(以下内容来自百度百科)

      面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。是软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP与OOP的关系

      很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 的接口也一样能够做到,我想这个观点是值得商榷的。AOP和定义良好的 OOP 的接口可以说都是用来解决并且实现需求中的横切问题的方法。但是对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法避免的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会这样,你只需要修改相应的 Aspect,再重新编织(weave)即可。 当然,AOP 也绝对不会代替 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,以此之长,补彼之短。

     主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改

变这些行为的时候不影响业务逻辑的代码

AOP具体实现

AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),目前AOP具体实现有以下几个项目:

AspectJ (TM): 创建于Xerox PARC. 有近十年历史,成熟
缺点:过于复杂;破坏封装;需要专门的Java编译器。

动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术

Spring中的AOP

Spring对AOP编程提供了丰富的支持,是spring的两大核心(IOC和AOP)之一。Spring中的AOP是用java来实现的,在spring3.2.5官方文档对于AOP Proxies的描述如下:

1、Spring中默认使用标准的J2EE动态代理来生成AOP代理,这使得任何实现接口(或者一组接口)的类可以被代理。

2、Spring中的AOP也使用CGLIB代理,被代理的类必须是一个类而且是没有实现任何接口的。如果一个类没有实现接口,那么这样的类就可以用CGLIB来实现代理。

注:在Spring3.2.5中Spring-core中默认加入了CGLIB这个jar包,不需要自己再添加jar包了。如果要改变生成类的方式可以用如下的配置:proxy-target-class=‘true’ 
    <aop:aspectj-autoproxy proxy-target-class="true" />

关于Spring的一些术语

    • Aspect(切面):业务流程运行的某个特定的步骤,也就是应用运行过程的关注点。关注点可以横切多个对象,所以常常称为横切关注点。
    • JoinPoint(连接点):程序运行过程中的一个点,例如一个方法的执行或者是异常的处理。在Spring AOP中,一个连接点总是代表者方法的执行。
    • Advice(增强处理):AOP在特定的切入点中执行的增强处理。有"around," "before" 和 "after" 增强处理。
    • PointCut(切入点):可以插入增强处理的连接点,简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就 变成 了切入点。Spring默认使用AspectJ的切入点语法。
    • Introduction(引入):将方法或字段添加到被处理的类中。Spring允许你引用新的接口给任何的处理类。例如,你可以使用一个bean实现IsModified接口,以些来简化缓存。
    • Target Object(目标对象):对象可以被一个或多个切面处理,因此也被称为增强对象,因为Spring的AOP是用运行时代理来实现的,所以目标对象总是一个代理对象。
    • AOP proxy(AOP 代理):AOP框架创建的对象,简单的说代理对象就是对目标对象的增强。在Spring AOP框架中,即包括JDK动态代理,也包括CGLIB代理。
    • Weaving(织入):连接目标对象和切面并生成一个增强的对象就是叫做织入,织入有两个种实现方式:编译时增强(如AspectJ)和运行时增强(如:CGLIB)。Spring AOP中使用的是运行时增强。

 

好了说了这么多的概念性的东西,来看代码吧

请看下面的类图 :

image

这是一个很简单的model2框架,里出全是业务实现代码并没有其它功能(里的类的注入用的是SpringIOC)。如果你想给某天对这个项目进行修改、重构的时候想在saveUser()的前后添加日志记录怎么办?

可以新手最先想到的是:直接添加不就行了。但是这是一个类,如果在以后的项目中10或者20个这样的类应该怎么做?你还要一个一个类的打开进行复制、修改。想想这都是一个噩梦27

如果我们用AOP的思想去思考呢?

我们可以把所有对数据库保存的方法就是以save*开头的方法当成一个切面对方法前后添加记录日志的功能。

解决方案:利用SpringAOP来实现 (Annotation方式)

以下代码在Myeclipse2013和JDK1.7 Spring3.2.5、JUnit4中测试通过

第一步:在Spring配置文件中启用Spring的AOP

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

    <!-- 启用Annotation配置 -->
    <context:annotation-config />
    <!-- 告诉spring要扫描那个包下的类 -->
    <context:component-scan base-package="com.zxd"></context:component-scan>

    <!-- 启用AspectJ对Annotation的支持 -->
    <!-- 从Spring3.2开始在Spring-core中默认加入了CGLIB这个jar包,不需要自己再添加jar包了 proxy-target-class=‘true’ 
        改变生成代理的方式 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <!-- Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。 -->

    <!-- 启用AspectJ支持 如果不使用XML Schema配置方式,可以使用在配置文件中添加以下的内容 -->
    <!-- <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" 
        /> -->
</beans>

注:请注意要先引用命名空间。

第二步:建立一个专用添加日志的拦截器

LogInterceptor .java

package com.zxd.aop;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 这个类是用来进行测试Spring的AOP切面类
 * 
 * @author zhang
 * 
 */

@Aspect
@Component
public class LogInterceptor {
    // 声明一个切入点
    @Pointcut("execution(* com.zxd.service..*.add(..))")
    public void actionMethod() {
    };

    /*
     * 使用Before增强处理,通常要指定一个value值,用来指定切入点 这个增强处理发生在目标方法执行之前织入执行 注:无法访问目标方法的返回值
     */
    @Before("actionMethod()") public void addLog() {
        System.out.println("添加日志"); 
    }
    
    @After("actionMethod()")
    public void overLog2() {
        System.out.println("添加日志结束");
    }
}

DAO层对应的实现类

UserDAOImpl.java

package com.zxd.dao.impl;

import org.springframework.stereotype.Component;

import com.zxd.dao.UserDAO;
import com.zxd.model.User;


//默认使用类名首字母小写
@Component("userDAO")
public class UserDAOImpl implements UserDAO {

    public void save(User user) {
        //Hibernate
        //JDBC
        //XML
        //NetWork
        System.out.println("user saved!");
    }

}

JUnit测试类 test.java

package com.zxd.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.zxd.model.User;
import com.zxd.service.UserService;

//Dependency Injection
//Inverse of Control
public class UserServiceTest {
    
    @Test
    public void testAdd() throws Exception {
        
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");                
        UserService us = context.getBean("userService",UserService.class);            
        us.add(new User());
    }

}

打印结果如下:

image

在Spring中增强处理有以下几种:

    1. Before增强处理:使用Before增强处理,通常要指定一个value值,用来指定切入点 这个增强处理发生在目标方法执行之前织入执行 注:无法访问目标方法的返回值
    2. AfterReturning增强处理:这个增强处理会在目标方法执行完成后被织入执行。returning:指定一个返回值的形参名,增强处理定义的方法可以通过该形参名来访问目标方法的返回值
      注:AfterReturning还可以限定切入点的只匹配具有对应返回值类型的方法可以过滤一些其它返回值类型的方法,虽然AfterReturning可以访问目标参数的方法,但是不能改变目标方法的返回值。例如:
/*
     * 对于AfterReturning增强处理的说明 这个增强处理会在目标方法执行完成后被织入执行
     * returning:指定一个返回值的形参名,增强处理定义的方法可以通过该形参名来访问目标方法的返回值
     * 注:AfterReturning还可以限定切入点的只匹配具有对应返回值类型的方法可以过滤一些其它返回值类型的方法
     * 虽然AfterReturning可以访问目标参数的方法,但是不能改变目标方法的返回值
     */
    /*
     @AfterReturning(pointcut = "actionMethod()", returning = "rvt") public
      void log(Double rvt) { System.out.println("获得目标方法的返回值:" + rvt);
      System.out.println("AferReturning方法执行》》》》》》》");
     }

 

    1. AfterThrowing 增强处理:主要用于处理程序中未处理的异常,throwing:指定一个形参,通过这个方法可以得到该形参名来访问目标方法中所抛出的异常对象
      注:如果形参的类型是Throwable类型的话,是可以匹配任何的异常 。AfterThrowing与catch的区别:catch意味着完全处理该异常,如果catch块中没有重新抛出该异常,则该方法可以正常结束;而AfterThrowing处理虽然处理该异常,但它并不能完全处理该异常,该异常依然传播到上一级,最终导致程序的结束
    2. After增强处理:After增强处理与AfterReturning的区别:
      1. AfterReturning是在目标方法正确执行的情况下,才能被织入的
      2. After是无论目标方法是否正常的结束,都会被织入,相当于finnally中的方法
      3. 综上,所以After增强处理必须准备处理两种情况,正常返回和异常返回,(通常用于资源的释放)。
    3. Around增强处理:即可以在目标方法执行前织入动作,也可以在目标方法之后织入动作。与Before和AfterReturning的增强处理不同的是,Around增强处理甚至可以决定目标方法什么时候执行,如何执行,甚至完全可以阻止目标方法的执行 如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用Around增强处理。关于Around使用的注意
      1. 定义一个Around方法,该方法的第一个参数必须为ProceedingJoinPoint类型(至少包含一个形参),在方法体内部调用ProceedingJoinPoint方法的 proceed方法才会执行目标方法
      2. 当调用proceed方法传入的Object[ * ]对象方法,作为目标方法的参数时,如果传入的Object[]参数长度与目标方法所需要的参数不相等,或者类型不同都会出现异常
      3. 在Around方法中获取目标参数的方法 Object[] getArgs(): 返回执行目标方法时的参数 Signature , getSignature:返回被增强方法的相关信息 Object getTarget():返回被织入增强处理的目标对象 Object ,getThis:返回AOP框架为目标对象生成的代理对象

关于Spring中拦截器的先后顺序:

如果在一个目标对象的方法里有两个增强处理对这个方法进行了拦截那么Spring是如何对这个顺序进行排序的呢?

Spring采用和AspectJ一样的优先顺序来织入增强处理在“进入时”(Before)优先级高的先被织入,在“退出”(After)时, * 优先级高的会被后织入 Spring提供了如下两种方案来解决处理程序的优先级问题:

1、让切面类来实现org.springframework.core.Ordered接口,实现该接口只需要实现一个 int getOrder()方法, 该方法的返回值越小,则优先级越高

2、直接使用@Order  Annotation来修饰一个切面类,里面指定一个int类型的值,值越小优先级越高,比如:@Order(2)

注:在before时优先级越高先进行织入,如果在After之后优先级越高越在最后进行织入。

关于XML的配置方式

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:context="http://www.springframework.org/schema/context"
    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/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/aop 
         http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 启用Annotation配置 -->
    <context:annotation-config />
    <!-- 告诉spring要扫描那个包下的类 -->
    <context:component-scan base-package="com.zxd"></context:component-scan>
    <bean id="log" class="com.zxd.aop.LogInterceptor"></bean>
    <!-- XML中对于AOP的配置 -->
    <aop:config>
        <!-- 声明一个切点 -->
        <aop:pointcut expression="execution(* com.zxd.service..*.test(..))"
            id="logPoint" />
        <!-- 定义切面类中的具体操作 -->
        <aop:aspect ref="log" id="logAspect">
            <!-- 定义一个before处理 -->
            <aop:before method="before" pointcut-ref="logPoint" />
        </aop:aspect>
    </aop:config>
</beans>