SpringAOP原理及应用
一、背景 |
什么是AOP,英文直译是Aspect-OrientedProgramming,面向方面编程。从名字也可以看出,如果把我们代码的执行过程看成一条圆柱体,AOP就是一把刀,从这个圆柱体上选择任意一点切入进去,获得其内部的消息。
springAOP概念:
AOP是Aspect Oriented Programing的简称,面向切面编程。AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理、缓存、对象池管理以及日志记录。AOP将这些分散在各个业务逻辑中的代码通过横向切割的方式抽取到一个独立的模块中。AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
关键词:
切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。
连接点(Joinpoint) :程序执行过程中的某一行为。
通知(Advice) :“切面”对于某个“连接点”所产生的动作。
切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。
目标对象(Target Object) :被一个或者多个切面所通知的对象。
AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。
通知(Advice)类型:
前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。
后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。
返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。
抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。 ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。
切入点表达式 :如execution(* com.spring.service.*.*(..))
二、原理 |
要了解AOP的实现原理,就不得不提java代理。
1、代理模式简述: |
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
代码示例如下:
1 /** 2 3 * 定义一个账户接口 4 5 */ 6 7 public interface Count { 8 9 // 修改账户方法 10 11 public void updateCount(); 12 13 } 14 15 16 17 ** 18 19 * 委托类(包含业务逻辑) 20 21 */ 22 23 public class CountImpl implements Count { 24 25 26 27 @Override 28 29 public void updateCount() { 30 31 System.out.println("修改账户方法..."); 32 33 } 34 35 } 36 37 38 39 /** 40 41 * 这是一个代理类(增强CountImpl实现类) 42 43 */ 44 45 public class CountProxy implements Count { 46 47 private CountImpl countImpl; 48 49 50 51 /** 52 53 * 覆盖默认构造器 54 55 * 56 57 * @param countImpl 58 59 */ 60 61 public CountProxy(CountImpl countImpl) { 62 63 this.countImpl = countImpl; 64 65 } 66 67 68 69 @Override 70 71 public void updateCount() { 72 73 System.out.println("事务处理之前"); 74 75 // 调用委托类的方法; 76 77 countImpl.updateCount(); 78 79 System.out.println("事务处理之后"); 80 81 } 82 83 }
2、JDK动态代理 |
特点:只能对实现了接口的类生产代理,不能针对类
代码示例:
/** * 自定义简单的Invocation,对接口提供的方法进行增强 */ public class MyInvocationHandler implements InvocationHandler { //目标对象 private Object target; /** * 构造方法 * @param target 目标对象 */ public MyInvocationHandler(Object target) { super(); this.target=target; } /** * 执行目标对象的方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在目标方法执行前简单打印一下 System.out.println("----------before----------"); //执行目标方法对象 Object result=method.invoke(target, args); //在目标方法执行之后简单打印一下 System.out.println("----------after----------"); return result; } /** * 获取目标对象的代理对象 * @return 代理对象 */ public Object getProxy(){ return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), this.target.getClass().getInterfaces(),this); } } public class ProxyTest{ @Test public void testProxy() throws Throwable{ //实例化目标对象 Count count =new CountImpl (); //实例化Invocation MyInvocationHandler invocationHandler=new MyInvocationHandler(count); //根据目标生成代理对象 Count proxy=(Count)invocationHandler.getProxy(); //调用代理对象方法 proxy. updateCount(); } }
3、CGLIB动态代理: |
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLIB是一个强大的高性能的代码生成包。被广泛的许多AOP框架使用,如Spring的AOP和dynaop,为他们提供方法的interceptor(拦截),最流行的是OR Mapping工具Hibernate也是使用CGLIB来代理单端的single-ended(多对一和一对一)关联(对集合的延迟抓取是采用其他机制实现)。EsayMock和jMock是通过模仿(moke)对象来测试java代码的包。他们都是通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。
代码示例:
/** * 使用cglib动态代理 */ public class CountImplCglib implements MethodInterceptor { private Object target; /** * 创建代理对象 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override // 回调方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("事物开始"); proxy.invokeSuper(obj, args); System.out.println("事物结束"); return null; } } public class TestCglib { public static void main(String[] args) { CountImplCglib cglib=new CountImplCglib(); CountImpl countCglib=(CountImpl)cglib.getInstance(new CountImpl()); countCglib.updateCount(); } }
以上只是代理模式实现的代码,真正在springAOP中的实现更为复杂,若想了解可以参考文章《spring源码剖析(六)AOP实现原理剖析》。
链接http://blog.csdn.net/fighterandknight/article/details/51209822
三、运用添加依赖: |
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.1</version> </dependency>
新增一个业务类: |
public class Car { public void go(){ System.out.println("go go go!"); } }
写一个切面类: |
public class CarLogger { public void beforeRun(){ System.out.println("car is going to run"); } public void afterRun(){ System.out.println("car is running"); } }
新增配置文件: |
<!-- 把两个bean加入spring容器 --> <bean id="car" class="com.wowo.spring_aop_demo1.Car"/> <bean id="logger" class="com.wowo.spring_aop_demo1.CarLogger" /> <!--AOP配置--> <aop:config> <!-- 指明切面实现bean logger --> <aop:aspect ref="logger"> <!-- 指明切入点 --> <aop:pointcut expression="execution(* com.wowo.spring_aop_demo1.Car.go(..))" id="go"/> <!-- 配置通知 --> <aop:before pointcut-ref="go" method="beforeRun" /> <aop:after pointcut-ref="go" method="afterRun" /> </aop:aspect> </aop:config>
execution(* com.wowo.spring_aop_demo1.Car.go(..))是一个AspectJ切点表达式,execution表示在执行时触发,后面的*表示任意类型的返回值,com.wowo.spring_aop_demo1.Car指的是切点所在的类,go(..)是方法名,..表示任意参数。若想详细了解,参考文章《AspectJ的切入点表达式---execution表达式详解》。
链接http://blog.csdn.net/lk7688535/article/details/51989746
四、结论 |
AOP是和IOC并称的Spring两大核心之一。Spring的AOP实现比较简化,但借助AspectJ可以对AOP实现的更彻底。