要谈AOP,那么AOP到底是什么呢?AOP即面向切面编程,相比OOP--面向对象编程,由于面向对象中最基本的单位是 类,实例
,很自然我们会想到AOP中最基本的单位可能就是所谓的 切面
了,你可能会问,那 切面
又是个什么东西,我想说,现在不懂没关系,下面我会讲到。我们先来看一段 Spring
中关于AOP的定义:
面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
上面谈到,AOP可以分离系统的业务逻辑和系统服务(日志,安全等),这个功能我想是不难明白(原理是使用了 代理模式
),但关键是为什么要将这两种进行分离呢?或者说这样做有什么好处?
在日常的软件开发中,拿日志来说,一个系统软件的开发都是必须进行日志记录的,不然万一系统出现什么bug,你都不知道是哪里出了问题。举个小栗子,当你开发一个登陆功能,你可能需要在用户登陆前后进行权限校验并将校验信息( 用户名
, 密码
, 请求登陆时间,ip地址
等)记录在日志文件中,当用户登录进来之后,当他访问某个其他功能时,也需要进行合法性校验。想想看,当系统非常地庞大,系统中专门进行权限验证的代码是非常多的,而且非常地散乱,我们就想能不能将这些权限校验、日志记录等非业务逻辑功能的部分独立拆分开,并且在系统运行时需要的地方( 连接点
)进行动态插入运行,不需要的时候就不理,因此AOP是能够解决这种状况的思想吧!
下图就很直观地展示这个过程:
不得不说,AOP的概念是真的多且难以理解,不过不用担心,聪明的你已经准备好战胜它们了。
通知有5种类型:
Before
在方法被调用之前调用
After
在方法完成后调用通知,无论方法是否执行成功
After-returning
在方法成功执行之后调用通知
After-throwing
在方法抛出异常后调用通知
Around
通知了好、包含了被通知的方法,在被通知的方法调用之前后调用之后执行自定义的行为
我们可能会问,那通知对应系统中的代码是一个方法、对象、类、还是接口什么的呢?我想说一点,其实都不是,你可以理解通知就是对应我们日常生活中所说的通知,比如‘某某人,你2019年9月1号来学校报个到’,通知更多地体现一种告诉我们(告诉系统何)何时执行,规定一个时间,在系统运行中的某个时间点(比如抛异常啦!方法执行前啦!), 并非对应代码中的方法!并非对应代码中的方法!并非对应代码中的方法!
切点(Pointcut)
哈哈,这个你可能就比较容易理解了,切点在Spring AOP中确实是对应系统中的方法。但是这个方法是定义在切面中的方法,一般和通知一起使用,一起组成了切面。
连接点(Join point)
比如:方法调用、方法执行、字段设置/获取、异常处理执行、类初始化、甚至是 for 循环中的某个点
理论上, 程序执行过程中的任何时点都可以作为作为织入点, 而所有这些执行时点都是 Joint point
但 Spring AOP 目前仅支持方法执行 (method execution) 也可以这样理解,连接点就是你准备在系统中执行切点和切入通知的地方(一般是一个方法,一个字段)
切面(Aspect)
切面是切点和通知的集合,一般单独作为一个类。通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能。
引入(Introduction)
引用允许我们向现有的类添加新的方法或者属性
织入(Weaving)
组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
首先AOP思想的实现一般都是基于代理模式,在JAVA中一般采用JDK动态代理模式,但是我们都知道,JDK动态代理模式只能代理接口,如果要代理类那么就不行了。因此,Spring AOP 会这样子来进行切换,因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理,当你的真实对象有实现接口时,Spring AOP会默认采用JDK动态代理,否则采用cglib代理。
话说饼干能解渴吗?
连接点
package wokao666.club.aop.spring01; public interface Subject { //登陆 public void login(); //下载 public void download(); }
package wokao666.club.aop.spring02; import wokao666.club.aop.spring01.Subject; public class SubjectImpl implements Subject { public void login() { System.err.println("借书中..."); } public void download() { System.err.println("下载中..."); } }
package wokao666.club.aop.spring01; import org.aspectj.lang.JoinPoint; public class PermissionVerification { /** * 权限校验 * @param args 登陆参数 */ public void canLogin() { //做一些登陆校验 System.err.println("我正在校验啦!!!!"); } /** * 校验之后做一些处理(无论是否成功都做处理) * @param args 权限校验参数 */ public void saveMessage() { //做一些后置处理 System.err.println("我正在处理啦!!!!"); } }
SpringAOP.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="SubjectImpl1" class="wokao666.club.aop.spring02.SubjectImpl" /> <bean id="SubjectImpl2" class="wokao666.club.aop.spring02.SubjectImpl" /> <bean id="PermissionVerification" class="wokao666.club.aop.spring01.PermissionVerification" /> <aop:config> <!-- 这是定义一个切面,切面是切点和通知的集合--> <aop:aspect id="do" ref="PermissionVerification"> <!-- 定义切点 ,后面是expression语言,表示包括该接口中定义的所有方法都会被执行--> <aop:pointcut id="point" expression="execution(* wokao666.club.aop.spring01.Subject.*(..))" /> <!-- 定义通知 --> <aop:before method="canLogin" pointcut-ref="point" /> <aop:after method="saveMessage" pointcut-ref="point" /> </aop:aspect> </aop:config> </beans>
pom.xml
文件 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>wokao666.club</groupId> <artifactId>aop</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>aop</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>4.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency> </dependencies> </project>
package wokao666.club.aop.spring01; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("SpringAOP.xml"); Subject subject1 = (Subject)ctx.getBean("SubjectImpl1"); Subject subject2 = (Subject)ctx.getBean("SubjectImpl2"); subject1.login(); subject1.download(); System.err.println("=================="); subject1.login(); subject1.download(); } }
三月 13, 2018 4:59:44 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@31cefde0: startup date [Tue Mar 13 16:59:44 CST 2018]; root of context hierarchy 三月 13, 2018 4:59:45 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [SpringAOP.xml] 我正在校验啦!!!! 借书中... 我正在处理啦!!!! 我正在校验啦!!!! 下载中... 我正在处理啦!!!! ================== 我正在校验啦!!!! 借书中... 我正在处理啦!!!! 我正在校验啦!!!! 下载中... 我正在处理啦!!!!
我想,上面的实现方式只是皮毛而已啦!如果想要深入学习,那么可以看下JDK动态代理的源码分析(里边非常地巧妙,还运用了二级缓存,本来我想写出来的,就留给下一篇文吧!)、Spring AOP源码分析等等,当然上面的实现方式是基于老式的xml文件,不推荐我们这样做,为什么?我觉得当你系统大了之后,那系统中将会有很多的 bean
、 xml
文件,这不容易维护和管理,我建议采用基于注解的方式进行AOP编程会更好!
好了,本来想结束的,但是在关掉eclipse前发现了一个小问题,既然切点是需要执行的方法,那么我们如何获取连接点的数据(或者说是校验参数呢?),这里推荐一篇博文,我就不写啦!小伙伴们,一起加油吧!
spring AOP 通知参数的传递