Spring之面向切面的编程AOP

参考文章:http://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/


Aspect Orient Programming

AOP(Aspect Orient Programming),也就是面向切面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为代表;而动态代理则以 Spring AOP 为代表。静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强


使用 AspectJ 的编译时增强进行 AOP

AspectJ 是一个基于 Java 语言的 AOP 框架,提供了强大的 AOP 功能,其他很多 AOP 框架都借鉴或采纳其中的一些思想。

AspectJ 是 Java 语言的一个 AOP 实现,其主要包括两个部分:第一个部分定义了如何表达、定义 AOP 编程中的语法规范,通过这套语言规范,我们可以方便地用 AOP 来解决 Java 语言中存在的交叉关注点问题;另一个部分是工具部分,包括编译器、调试工具等。

AspectJ 是最早、功能比较强大的 AOP 实现之一,对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现,也借鉴或采纳了 AspectJ 中很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。

成功安装了 AspectJ 之后,将会在 E:\Java\AOP\aspectj1.6 路径下(AspectJ 的安装路径)看到如下文件结构:

  1. bin:该路径下存放了 aj、aj5、ajc、ajdoc、ajbrowser 等命令,其中 ajc 命令最常用,它的作用类似于 javac,用于对普通 Java 类进行编译时增强

  2. docs:该路径下存放了 AspectJ 的使用说明、参考手册、API 文档等文档。

  3. lib:该路径下的 4 个 JAR 文件是 AspectJ 的核心类库。

  4. 相关授权文件。

详见http://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/


使用 Spring AOP(默认使用JDK的动态代理)

与 AspectJ 相同的是,Spring AOP 同样需要对目标类进行增强,也就是生成新的 AOP 代理类;与 AspectJ 不同的是,Spring AOP 无需使用任何特殊命令对 Java 源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成 AOP 代理

Spring 允许使用 AspectJ Annotation 用于定义方面(Aspect)、切入点(Pointcut)和增强处理(Advice),Spring 框架则可识别并根据这些 Annotation 来生成 AOP 代理。Spring 只是使用了和 AspectJ 5 一样的注解,但并没有使用 AspectJ 的编译器或者织入器(Weaver),底层依然使用的是 Spring AOP,依然是在运行时动态生成 AOP 代理,并不依赖于 AspectJ 的编译器或者织入器。

简单地说,Spring 依然采用运行时生成动态代理的方式来增强目标对象,所以它不需要增加额外的编译,也不需要 AspectJ 的织入器支持;而 AspectJ 在采用编译时增强,所以 AspectJ 需要使用自己的编译器来编译 Java 文件,还需要织入器。


下面看一个Spring AOP的例子

在运行这个例子时,引用了aopalliance-1.0.jar,aspectjrt.jar,aspectjtools.jar等jar文件

声明一个Person接口,实现一个Chinese类(使用jdk代理必须定义一个接口)

Person.java

package aop;

public interface Person {
    String sayHello(String name);

    void eat(String food);
}


Chinese.java

package aop;

import org.springframework.stereotype.Component;

@Component
public class Chinese implements Person {
    @Override
    public String sayHello(String name) {
        System.out.println("-- 正在执行 sayHello 方法 --");
        // 返回简单的字符串
        return name + " Hello , Spring AOP";
    }

    @Override
    public void eat(String food) {
        System.out.println("我正在吃 :" + food);
    }
}


定义切面

package aop;

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

// 定义一个方面
@Aspect
public class AfterReturningAdviceTest {
    // 匹配 aop 包下所有类的、所有方法的执行作为切入点
    @AfterReturning(returning = "rvt",
            pointcut = "execution(* aop.*.*(..))")
    public void log(Object rvt) {
        System.out.println("获取目标方法返回值 :" + rvt);
        System.out.println("模拟记录日志功能 ...");
    }
}


定义切面

package aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

// 定义一个方面
@Aspect
public class AroundAdviceTest {
    // 匹配 org.crazyit.app.service.impl 包下所有类的、
    // 所有方法的执行作为切入点
    @Around("execution(* aop.*.*(..))")
    public Object processTx(ProceedingJoinPoint jp)
            throws java.lang.Throwable {
        System.out.println("执行目标方法之前,模拟开始事务 ...");
        // 执行目标方法,并保存目标方法执行后的返回值
        Object rvt = jp.proceed(new String[]{"被改变的参数"});
        System.out.println("执行目标方法之后,模拟结束事务 ...");
        return rvt + " 新增的内容";
    }
}


spring配置文件信息

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

    <!--spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component
    @Controller @Service等这些注解的类,则把这些类注册为bean-->
    <!-- 指定自动搜索 Bean 组件、自动搜索方面类 -->
    <!--use-default-filters="false"不使用默认的扫描方式-->
    <context:component-scan base-package="aop" use-default-filters="true">
        <context:include-filter type="annotation"
                                expression="org.aspectj.lang.annotation.Aspect"/>
    </context:component-scan>
    <!-- 启动 AspectJ 注解的支持 -->
    <!--只支持基于接口的动态代理,不支持基于类的动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>


<context:include-filter>和<context:exclude-filter>的使用:

<context:include-filter>和<context:exclude-filter>各代表引入和排除的过滤。而上例把use-default-filters属性设为false,意即在base-package所有被宣告为@Component等target class不予注册为bean,由filter子标签代劳。


测试方法:

使用接口

@Test
public void test9876() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
            new String[]{"spring-config.xml"});
    Person p = context.getBean("chinese", Person.class);
    System.out.println(p.sayHello("lyx"));
    p.eat("sdsd");
}

运行结果:

执行目标方法之前,模拟开始事务 ...

-- 正在执行 sayHello 方法 --

执行目标方法之后,模拟结束事务 ...

获取目标方法返回值 :被改变的参数 Hello , Spring AOP 新增的内容

模拟记录日志功能 ...

被改变的参数 Hello , Spring AOP 新增的内容

执行目标方法之前,模拟开始事务 ...

我正在吃 :被改变的参数

执行目标方法之后,模拟结束事务 ...

获取目标方法返回值 :null 新增的内容

模拟记录日志功能 ...

System.out.println(p.getClass().getName());这句代码打印结果是:com.sun.proxy.$Proxy8,说明p实际是动态代理类的一个实例。。。。。。。


Spring AOP借助cglib使用基于类的动态代理

请看下面的这个基于上面的一个例子,把上面的配置改成

 <aop:aspectj-autoproxy proxy-target-class="true"/>

这时Chinese类可以不实现Person接口。。。。。。

同时引入cglib-3.1.jar,asm-4.2.jar(版本一定要对)

同时看测试方法

@Test
public void test7876() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
            new String[]{"spring-config.xml"});
    Chinese p = context.getBean("chinese", Chinese.class);
    System.out.println(p.getClass().getName());
    System.out.println(p.sayHello("lyx"));
    p.eat("sdsd");
}

这里直接使用Chinese类

运行结果:

aop.Chinese$$EnhancerByCGLIB$$7c5c07d2   //使用cglib基于类的动态代理

执行目标方法之前,模拟开始事务 ...

-- 正在执行 sayHello 方法 --

执行目标方法之后,模拟结束事务 ...

获取目标方法返回值 :被改变的参数 Hello , Spring AOP 新增的内容

模拟记录日志功能 ...

被改变的参数 Hello , Spring AOP 新增的内容

执行目标方法之前,模拟开始事务 ...

我正在吃 :被改变的参数

执行目标方法之后,模拟结束事务 ...

获取目标方法返回值 :null 新增的内容

模拟记录日志功能 ...


如果目标类实现了接口,Spring AOP 则无需 CGLIB 的支持,直接使用 JDK 提供的 Proxy 和 InvocationHandler 来生成 AOP 代理即可。


使用 CGLIB 生成代理类

CGLIB(Code Generation Library),简单来说,就是一个代码生成类库。它可以在运行时候动态是生成某个类的子类。

此处使用前面定义的 Chinese 类,现在改为直接使用 CGLIB 来生成代理,这个代理类同样可以实现 Spring AOP 代理所达到的效果。

定义切面类,继承MethodInterceptor

package aop;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class AroundAdvice implements MethodInterceptor {

    public Object intercept(Object target, Method method,
                            Object[] args, MethodProxy proxy) throws java.lang.Throwable {
        System.out.println("执行目标方法之前,模拟开始事务 ...");
        // 执行目标方法,并保存目标方法执行后的返回值
        Object rvt = proxy.invokeSuper(target, new String[]{"被改变的参数"});
        System.out.println("执行目标方法之后,模拟结束事务 ...");
        return rvt + " 新增的内容";
    }
}


Chinese.java(不实现Person接口)

package aop;

import org.springframework.stereotype.Component;

@Component
public class Chinese {
    public String sayHello(String name) {
        System.out.println("-- 正在执行 sayHello 方法 --");
        // 返回简单的字符串
        return name + " Hello , Spring AOP";
    }

    public void eat(String food) {
        System.out.println("我正在吃 :" + food);
    }
}


ChineseProxyFactory.java

package aop;

import net.sf.cglib.proxy.Enhancer;

public class ChineseProxyFactory {

    public static Chinese getAuthInstance() {
        Enhancer en = new Enhancer();
        // 设置要代理的目标类
        en.setSuperclass(Chinese.class);
        // 设置要代理的拦截器
        en.setCallback(new AroundAdvice());
        // 生成代理类的实例
        return (Chinese) en.create();
    }
}


测试方法:

@Test
public void test897() {
    Chinese chin = ChineseProxyFactory.getAuthInstance();
    System.out.println(chin.sayHello("孙悟空"));
    chin.eat("西瓜");
    System.out.println(chin.getClass());
}

运行结果:

执行目标方法之前,模拟开始事务 ...

-- 正在执行 sayHello 方法 --

执行目标方法之后,模拟结束事务 ...

被改变的参数 Hello , Spring AOP 新增的内容

执行目标方法之前,模拟开始事务 ...

我正在吃 :被改变的参数

执行目标方法之后,模拟结束事务 ...

class aop.Chinese$$EnhancerByCGLIB$$f92da8c6

从上面输出结果来看,CGLIB 生成的代理完全可以作为 Chinese 对象来使用,而且 CGLIB 代理对象的 sayHello()、eat() 两个方法已经增加了事务控制(只是模拟),这个 CGLIB 代理其实就是 Spring AOP 所生成的 AOP 代理。


总结:

AOP 广泛应用于处理一些具有横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,它使得开发者能用更优雅的方式处理具有横切性质的服务。不管是那种 AOP 实现,不论是 AspectJ、还是 Spring AOP,它们都需要动态地生成一个 AOP 代理类,区别只是生成 AOP 代理类的时机不同:AspectJ 采用编译时生成 AOP 代理类,因此具有更好的性能,但需要使用特定的编译器进行处理;而 Spring AOP 则采用运行时生成 AOP 代理类,因此无需使用特定编译器进行处理。由于 Spring AOP 需要在每次运行时生成 AOP 代理,因此性能略差一些。

=====END=====


你可能感兴趣的:(Spring之面向切面的编程AOP)