参考文章:http://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/
AOP(Aspect Orient Programming),也就是面向切面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为代表;而动态代理则以 Spring AOP 为代表。静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 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 的安装路径)看到如下文件结构:
bin:该路径下存放了 aj、aj5、ajc、ajdoc、ajbrowser 等命令,其中 ajc 命令最常用,它的作用类似于 javac,用于对普通 Java 类进行编译时增强。
docs:该路径下存放了 AspectJ 的使用说明、参考手册、API 文档等文档。
lib:该路径下的 4 个 JAR 文件是 AspectJ 的核心类库。
相关授权文件。
详见http://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/
与 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实际是动态代理类的一个实例。。。。。。。
请看下面的这个基于上面的一个例子,把上面的配置改成
<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(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=====