Spring AOP详解
一、AOP基本概念
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
二、AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象。
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
4、切入点(pointcut)
对连接点进行拦截的定义。
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
6、目标对象
代理的目标对象。
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程。
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
三、AOP代理模式
1、静态代理:需要增强原有类的哪个方法,就需要对在代理类中包装哪个方法。
1.1、需要知道核心类(被代理类)是哪一个类,并且有哪些方法。
1.2、非核心的代码需要重复写多次,显得代码的结构臃肿,形成代码冗余。
1.3、非核心类(代理类)需要实现核心类(被代理类)实现的接口,也就是他们需要实现共同的接口,但是以核心类实现的接口(被代理类)为准。
静态代理代码示例:
示例介绍:假定学生为核心类,学生的家人是代理类。学生需要做的核心业务有:在家学习(studyAtHome)、在学校学习(studyAtHome);家人需要做的非核心业务为:准备饭菜(炒菜、煮饭)。收拾碗筷(洗碗、擦桌)。
1)创建学生接口
1 package com.czr.aop; 2 3 /** 4 * 定义学生接口 5 */ 6 public interface Student { 7 //在家里学习 8 public void studyAtHome(String name); 9 //在学校学习 10 public void studyAtSchool(String name); 11 12 }
2)创建学生实现类
package com.czr.aop; /** * 学生实现类 */ public class StudentImpl implements Student { @override public void studyAtHome(String name) { System.out.println(name + "在家里学习"); } @override public void studyAtSchool(String name) { System.out.println(name + "在学校学习"); } }
3)创建学生代理package com.czr.aop;/** * 学生代理类
*/ public class StudentProxy implements Student { //定义一个学生 private Student student; public StudentProxy() { student = new StudentImpl(); } public void studyAtHome(String name){
//这个是代理准备饭菜的需要做的流程: System.out.println("代理:开始炒菜"); System.out.println("代理:开始煮饭"); System.out.println("-----------------"); //调用这个学生实例的在家里学习方法 student.studyAtHome(name); //这个是代理准备收拾需要做的流程:
System.out.println("-----------------"); System.out.println("代理 :开始洗碗"); System.out.println("代理 :开始擦桌");
System.out.println(); } public void studyAtSchool(String name) { //这个是代理准备饭菜的需要做的流程: System.out.println("代理:开始炒菜"); System.out.println("代理:开始煮饭"); System.out.println("-----------------"); //调用这个学生实例的在学校学习方法 student.studyAtSchool(name); //这个是代理准备收拾需要做的流程: System.out.println("-----------------"); System.out.println("代理 :开始洗碗"); System.out.println("代理 :开始擦桌");
System.out.println(); } }
4)创建一个测试类
package com.czr.aop; public class Test { public static void main(String[] args) { //创建一个代理对象,并且传入相对应的参数构造出具体的实例 Student student = new StudentProxy("小明"); //通过这个代理对象执行相关的方法 student.studyAtHome(); student.studyAtSchool(); } }
5)测试输出结果如下:
代理:开始炒菜 代理:开始煮饭 ----------------- 小明在家里学习 ----------------- 代理 :开始洗碗 代理 :开始擦桌 代理:开始炒菜 代理:开始煮饭 ----------------- 小明在学校学习 ----------------- 代理 :开始洗碗 代理 :开始擦桌
总结:由上面的示例我们可以简单模拟一个静态代理的实例,我们从中可以发现,这个代理做的事情会因为核心对象业务多而变得多起来,而且这些代理做的事情都是相同的没有变化的,如果我们要修改这些流程中的某一个流程的时候会发现要改多处,而且都是相同的代码,所以这个时候使用动态代理就比较适合,静态代理适用于明确知道切入点且各切入点单一不冗余。
2、动态代理:使用反射机制,方法和对象都是传入的变量,就可以经过传入的对象和方法而动态调用被代理对象的任何方法,jdk中提供了实现此动态代理的api,被代理类必须实现接口。
2.1、不需要知道核心类(被代理类)具体是什么类。
2.2、非核心类(代理类)需要实现InvocationHandler接口。
以下做出一个基于Spring AOP动态代理实现示例:
1)先导入Jar包:aopalliance.jar、aspectjweaver.jar
2)定义测试接口
packege com.czr.aop.model; public interface TestModel{ public void printTime(); }
3)定义测试接口实现类
packege com.czr.aop.model; public class TestModelImpl implements TestModel{ public void printTime(){ System.out.println("调用 TestModelImpl.printTime() 方法"); } }
4)定义横切关注点
packege com.czr.aop; public class TimeAop { public void printTime() { System.out.println("CurrentTime = " + System.currentTimeMillis()); } }
5)定义Spring AOP aop.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <bean id="testModel" class="com.czr.aop.model.TestModelImpl" /> <bean id="timeAop" class="com.czr.aop.TimeAop" /> <aop:config> <aop:aspect id="aspect" ref="timeAop"> <aop:pointcut id="printTime" expression="execution(* com.czr.aop.model.*(..))" /> <aop:before method="printTime" pointcut-ref="printTime" /> <aop:after method="printTime" pointcut-ref="printTime" /> </aop:aspect> </aop:config> </beans>
6)定义测试类
package com.czr.aop; public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml"); TestModel tm = (TestModel)ctx.getBean("testModel"); tm .printTime(); } }
7)测试输出结果:
CurrentTime = 1556179611998
调用 TestModelImpl.printTime() 方法
CurrentTime = 1556179612005