AOP(Aspect Oriented Programing)(面向切面编程),是通过预编译方式或者是运行期期间动态代理实现功能扩展而不用修改源代码,通过AOP技术,实现一种通用的逻辑解耦,解决一些系统层面的问题,如日志,事务,权限等,从而实现高可用的可重用性和可维护性
AOP的设计原理和思想
AOP横向抽象技术的介绍
pubic class User{
//添加用户方法
public void add(){
//添加逻辑
//修改源代码,添加日志功能
}
}
//扩展功能
添加日志功能
添加之后,记录在什么时候添加那个用户
纵向抽取机制解决
public class BaseUser{
//创建日志添加的方法
public void writeLog() {
//添加日志的逻辑
}
}
pubic class User Extends BaseUser{
//添加用户方法
public void add(){
//添加逻辑
//功能拓展,添加日志功能
//调用服务的方法实现日志功能
super.writelog();
}
}
问题:
父类中的方法名称发生变化
在子类调用的方法也需要变化
AOP横向抽象机制
底层使用 动态代理方式实现
第一种方式:
public interface dao{
public void add();
}
| 使用动态代理方式,创建接口实现类代理对象
public class DaoImpl implements Dao {
public void add(){
//方法逻辑
}
}
创建一个DaoImpl平级对象,这个对象不是真正对象,是代理对象实现和DaoImpl相同的功能
有接口情况,使用JDK动态代理机制,没有接口的情况,使用CGLib代理方式创建类的子类代理对象
在子类中调用父类的方法完成增强
AOP实现核心原理:动态代理
java程序的执行流
程序运行的过程就是方法调用的过程。我们按照方法执行的顺序,将方法调用排成一串,这样就构成了Java程序流。将上述的线程栈里的方法调用按照执行流排列,会有如下类似的图
基于时间序列,我们可以将方法调用排成一条线。而每个方法调用则可以看成Java执行流中的一个节点。这个节点在AOP的术语中,被称为Join Point,即连接点。一个Java程序的运行的过程,就是若干个连接点连接起来依次执行的过程。
通常面向对象的程序,代码都是按照时间序列纵向展开的,而他们都有一个共性:即都是以方法调用作为基本执行单位展开的。将方法调用当做一个连接点,那么由连接点串起来的程序执行流就是整个程序的执行过程。
AOP(Aspect Oriented Programming)则是从另外一个角度来考虑整个程序的,AOP将每一个方法调用,即连接点作为编程的入口,针对方法调用进行编程。从执行的逻辑上来看,相当于在之前纵向的按照时间轴执行的程序横向切入。相当于将之前的程序横向切割成若干的面,即Aspect.每个面被称为切面。
所以,根据我的理解,AOP本质上是针对方法调用的编程思路。
AOP是针对切面进行的编程,那么,你需要选择哪些切面(即 连接点Joint Point)作为你的编程对象呢?
因为切面本质上是每一个方法调用,选择切面的过程实际上就是选择方法的过程。那么,被选择的切面(Aspect)在AOP术语里被称为切入点(Point Cut). 切入点实际上也是从所有的连接点(Join point)挑选自己感兴趣的连接点的过程。
既然AOP是针对方法调用(连接点)的编程, 现在又选取了你自己感兴趣的链接点---切入点(Point Cut)了,那么,AOP能对它做什么类型的编程呢?AOP能做什么呢?
了解这个之前,我们先要知道一个非常重要的问题:既然AOP是对方法调用进行的编程,那么,AOP如何捕获方法调用的呢?弄清楚这个问题,下面我们先来了解一下引入了代理模式的Java程序执行流是什么样子的。
使用代理模式的Java执行流
我们假设在我们的Java代码里,都为实例对象通过代理模式创建了代理对象,访问这些实例对象必须要通过代理,那么,加入了proxy对象的Java程序执行流会变得稍微复杂起来。
我们来看下加入了proxy对象后,Java程序执行流的示意图:
由上图可以看出,只要想调用某一个实例对象的方法时,都会经过这个实例对象相对应的代理对象, 即执行的控制权先交给代理对象。
代理模式属于Java代码中经常用到的、也是比较重要的设计模式。代理模式可以为某些对象除了实现本身的功能外,提供一些额外的功能,大致作用如下图所示:
加入了代理模式的Java程序执行流,使得所有的方法调用都经过了代理对象。对于Spring AOP框架而言,它负责控制着整个容器内部的代理对象。当我们调用了某一个实例对象的任何一个非final的public方法时,整个Spring框架都会知晓。
此时的SpringAOP框架在某种程度上扮演着一个上帝的角色:它知道你在这个框架内所做的任何操作,你对每一个实例对象的非final的public方法调用都可以被框架察觉到!
既然Spring代理层可以察觉到你所做的每一次对实例对象的方法调用,那么,Spring就有机会在这个代理的过程中插入Spring的自己的业务代码
Spring AOP的工作原理
已经介绍了AOP编程首先要选择它感兴趣的连接点----即切入点(Point cut),那么,AOP能对切入点做什么样的编程呢?我们先将代理模式下的某个连接点细化,你会看到如下这个示意图所表示的过程:
为了降低我们对Spring的AOP的理解难度,我在这里将代理角色的职能进行了简化,方便大家理解。**(注意:真实的Spring AOP的proxy角色扮演的只能比这复杂的多,这里只是简化,方便大家理解,请不要先入为主)
代理模式的代理角色最起码要考虑三个阶段:
1.在****调用真正对象的方法之前,应该需要做什么?
在调****用真正对象的方法过程中,如果抛出了异常,需要做什么?
3.在调用真正对象的方法后,返回了结果了,需要做什么?
Spring AOP 根据proxy提供的类型名和方法签名,确定了在其感兴趣的切入点内,则返回AfterReturingAdivce处理建议,proxy得到这个处理建议,然后执行建议;
上述的示意图中已经明确表明了Spring AOP应该做什么样的工作:根据proxy提供的特定类的特定方法执行的特定时期阶段给出相应的处理建议。要完成该工作,Spring AOP应该实现:
1.确定自己对什么类的什么方法感兴趣?-----即确定 AOP的切入点(Point Cut),这个可以通过切入点(Point Cut)表达式来完成;
2. 对应的的类的方法的执行特定时期给出什么处理建议?------这个需要Spring AOP提供相应的建议 ,即我们常说的Advice。
AOP相关术语
连接点(Joinpoint):所谓的连接点是指被拦截到的点,在spring中,这些点指的就是方法,Spring中只支持方法类型的连接点的类中可以被增强,这些方法称之为连接点
切入点(pointCut):切入点是指对那些连接点进行拦截的定义,在类中可以有很多的方法被增强,实际增强的方法称之为切入点
增强/通知(Advice):在拦截到连接点之后要做的事情就是增强,增强分为5种方式
前置增强:在连接点前面执行,前置增强不会影响连接点的执行,除非此处抛出异常
后置增强:在连接点正常执行完后执行,无论连接点是否抛出异常,后置增强都会执行
异常增强:在连接点抛出异常后才会执行,会导致连接点不能正常执行结束
最终增强:在连接点执行完成后执行,不管正常执行还是异常抛出,都会执行返回最后的通知内容
环绕增强:围绕在连接点的前后,比如一个方法调用的前后,这种通知是最强大的通知,能在方法调用前后定义一些操作
切面(Aspect):是切入点和增强的结合,把增强应用到切入点的过程,通常是一个类,可以在里面定义切入点的增强
引介、目标对象、织入、代理...
AOP的实现有两种方式
基于XML配置实现
基于注解实现
第一步:引入依赖
<!--AOP相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
第二步:添加全局配置文件并引入约束
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
第三步:给定自定义类
public class User {
public void talk(){
System.out.println("用户说...");
}
public void seat() {
System.out.println("用户走...");
}
}
第四步:给定增强类
/**
* 增强类
*/
public class Advice {
//前置增强
public void before(){
System.out.println("前置增强");
}
}
使用表达式来配置切入点
execution函数
在增强中通过value属性来定义切点,通过execution函数,可以定义切点的方法织入表达式格式:execution(<访问限定符>?<返回类型><方法名>(<参数>) <异常>)
(1)execution(* com.tulun.Spring.AOP.User*.talk(..)) 表类里面的某一个方法
(2)execution(* com.tulun.Spring.AOP.*(..))表示某个包里类的所有方法
(3)execution(* *.*(..))表示所有
例:
匹配所有类public方法 execution(public *.*(..))
匹配指定包下所有类方法execution(* com.tulun.Spring.AOP.*(..))(不含字包)
execution(* com.tulun.Spring.AOP..*(..))(包含包、子包下所有类)
匹配指定类所有方法 execution(* com.tulun.bean.Book.*(..))
匹配实现特定接口所有类方法 execution(* com.tulun.bean.Book+.*(..))
匹配所有com开头的方法 execution(* com*(..)
AOP基于XML配置实现
配置文件如下:
<!--配置对象-->
<bean id="user" class="com.tulun.Spring.AOP.User"/>
<bean id="advice" class="com.tulun.Spring.AOP.Advice"/>
<!--配置AOP操作-->
<aop:config>
<!--
配置切入点
对与增强的连接点称之为切入点
-->
<aop:pointcut id="pointcut1" expression="execution(* com.tulun.Spring.AOP.User.talk(..))"/>
<aop:pointcut id="pointcut2" expression="execution(* com.tulun.Spring.AOP.User.*(..))"/>
<!--
配置切面
把增强应用到切入点的过程
-->
<aop:aspect ref="advice">
<!--前置增强-->
<aop:before method="before" pointcut-ref="pointcut2"/>
<!--异常增强-->
<aop:after-throwing method="throwMethod" pointcut-ref="pointcut2"/>
<!--后置增强-->
<aop:after method="after" pointcut-ref="pointcut2"/>
<!--环绕增强-->
<aop:around method="around" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
注意:环绕增强的写法
//环绕增强
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("在连接点之前执行");
//执行被增强方法
joinPoint.proceed();
System.out.println("在连接点之后执行");
}
环绕增强方法需要引用ProceedingJoinPoint类型的参数,通过该参数指定连接点执行的位置,可以在该位置前后进行增强功能开发
AOP基于注解实现
全局配置文件开启aop注解扫描
<?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:context="http://www.springframework.org/schema/context"
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.xsd">
<!--扫描注解-->
<context:component-scan base-package="com.tulun.Spring.AOP"/>
<!--开启AOP注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
在增强类和方法上添加信息
/**
* 增强类
*/
@Component
@Aspect //该注解添加在增强类上
public class Advice {
//前置增强
@Before(value = "execution(* com.tulun.Spring.AOP.User.talk(..))")
public void before(){
System.out.println("前置增强");
}
@AfterThrowing(value = "execution(* com.tulun.Spring.AOP.User.seat(..))")
public void throwMethod(){
System.out.println("异常增强");
}
public void after(){
System.out.println("后置增强");
}
@AfterReturning(value = "execution(* com.tulun.Spring.AOP.User.*(..))")
public void returnAdvice(){
System.out.println("最终增强");
}
//环绕增强
@Around(value = "execution(* com.tulun.Spring.AOP.User.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("在连接点之前执行");
//执行被增强方法
joinPoint.proceed();
System.out.println("在连接点之后执行");
}
注:各类型的增强执行的先后顺序是不同的
总结:
- AOP的出现是对程序进行解耦,减少系统的重复代码,提高代码的扩展性和可维护性
- 常见的应用场景有权限管理、缓存、事务、日志
- AOP的通知顺序问题