Spring AOP
AOP(Aspect Oriented Programing)面向切面编程:扩展功能不通过修改源代码实现
AOP采用横向抽取机制,取代传统纵向继承体系实现响应的功能(性能监控、事务、安全检查、缓存)
AOP的设计原理和思想
AOP横向抽取机制介绍
public class User{
//增加用户
public int addUser(){
}
//查询用户
public User getUser(){
}
。。。。
}
//扩展功能
所有的方法增加日志功能
//优化1:纵向抽取机制解决
public class LogUtil{
//打印日志功能
public void printLog(){
System.out.println("....");
System.cucurrentMills();
}
}
public class User extends LogUtil{
//增加用户
public int addUser(){
printLog();
}
//查询用户
public User getUser(){
printLog()
}
。。。。
}
对日志功能的修改只需要在Logutils中做修改,减少代码开发量
问题:父类中Logutils方法名称发生变化,在子类调用的方法也需要发生变化
AOP横向抽取机制
底层使用: 动态代理方式实现 JDK自带的动态代理、CGLib方式实现动态代理
采用JDK自带的动态代理来介绍,必须包含接口
//接口类
public interface User{
public int addUser();
public User getUser();
}
//实现类
public Class UserImpl implements User {
public int addUser(){
伪代码
}
public User getUser(){
伪代码
}
}
实现一个代理辅助类实现invokeHandler接口,作用是增加新的功能
动态代理创建的一个UserImpl平级的对象,真正实现的对象是和UserImpl持有共同的接口,代理对象本身是增加了新的功能,
JDK动态代理的实现中会基于代理辅助类的方法在运行时动态的产生一个新的对象,该新的对象包含原有实现类的功能实现UserImpl,
还包好新增加的功能
Java程序的执行流
程序运行的过程就是方法调用的过程。我们按照方法执行的顺序,将方法调用排成一串,这样就构成了Java程序流。将上述的线程栈里的方法调用按照执行流排列,会有如下类似的图
基于时间序列,我们可以将方法调用排成一条线。而每个方法调用则可以看成Java执行流中的一个节点。这个节点在AOP的术语中,被称为Join Point,即连接点。一个Java程序的运行的过程,就是若干个连接点连接起来依次执行的过程。通常面向对象的程序,代码都是按照时间序列纵向展开的,而他们都有一个共性:即都是以方法调用作为基本执行单位展开的。将方法调用当做一个连接点,那么由连接点串起来的程序执行流就是整个程序的执行过程。AOP(Aspect Oriented Programming)则是从另外一个角度来考虑整个程序的,AOP将每一个方法调用,即连接点作为编程的入口,针对方法调用进行编程。从执行的逻辑上来看,相当于在之前纵向的按照时间轴执行的程序横向切入。相当于将之前的程序横向切割成若干的面,即Aspect.每个面被称为切面。所以,AOP本质上是针对方法调用的编程思路。
因为切面本质上是每一个方法调用,选择切面的过程实际上就是选择方法的过程。那么,被选择的切面(Aspect)在AOP术语里被称为切入点(Point Cut). 切入点实际上也是从所有的连接点(Join point)挑选自己感兴趣的连接点的过程。
第三方
使用代理模式的Java程序执行流
假设在Java代码里,都为实例对象通过代理模式创建了代理对象,访问这些实例对象必须要通过代理,那么,加入了proxy对象的Java程序执行流会变得稍微复杂起来,Java程序执行流的示意图:
由上图可以看出,只要想调用某一个实例对象的方法时,都会经过这个实例对象相对应的代理对象, 即执行的控制权先交给代理对象。代理模式属于Java代码中经常用到的、也是比较重要的设计模式。代理模式可以为某些对象除了实现本身的功能外,提供一些额外的功能,大致作用如下图所示:
加入了代理模式的Java程序执行流,使得所有的方法调用都经过了代理对象。对于Spring AOP框架而言,它负责控制着整个容器内部的代理对象。当我们调用了某一个实例对象的任何一个非final的public方法时,整个Spring框架都会知晓。
此时的SpringAOP框架在某种程度上扮演着一个上帝的角色:它知道你在这个框架内所做的任何操作,你对每一个实例对象的非final的public方法调用都可以被框架察觉到!
既然Spring代理层可以察觉到你所做的每一次对实例对象的方法调用,那么,Spring就有机会在这个代理的过程中插入Spring的自己的业务代码
Spring AOP的工作原理
先将代理模式下的某个连接点细化,示意图如下:
代理模式的代理角色最起码要考虑三个阶段:
- 1.在****调用真正对象的方法之前,应该需要做什么?
- 2.在调****用真正对象的方法过程中,如果抛出了异常,需要做什么?
- 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):是切入点和通知的结合,把通知应用到切入点的过程
- 引介(Introduction),引介是一种特殊的通知在不修改代码的前提下,引介可以在运行期为类动态的添加一些方法或属性
- 目标对象(Target):代理的目标对象,要增强的类
- 代理(Proxy):一个被AOP织入增强后,就铲射了一个结果代理类
- 织入(Weaving):是把通知应用到目标对象的过程,把advice应用到target的过程
AOP基于Aspectj配置的实现
在spring中AOP的使用是通过Aspectj第三方框架实现的,Aspectj是java语言实现的一个专门的面向切面编程的框架,Aspectj不是spring框架的一部分,和Spring一起实现AOP的操作
引入AOP相关依赖
spring的基本依赖之外,再添加AOP的依赖以及AOP相关约束
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>springTest</artifactId>
<version>1.0-SNAPSHOT</version>
<name>springTest</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.8.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>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
基础实体类
public class Student {
public void addStudent(){
System.out.println("Student addStudent!");
}
}
增强类:添加日志功能
public class addLog{
public void writeLog(){
System.out.println("DIYLog.writeLog");
}
}
通过表达式来配置切入点
execution函数介绍
在实现通知的过程中,通过execution函数,可以定义切入点的方法切入:
格式:execution(<访问修饰符>?<返回类型><方法名>(<参数>) <异常> )
回顾:方法格式:访问限定符 返回类型 方法名(参数) 异常
- (1)execution(* com.tulun.bean.Book.show(..)) 表类里面的某一个方法
- (2)execution(* com.tulun.bean.Book.*(..)) 表类某个包里类所有方法
- (3)execution(* *.*(..)) 表示所有
例:
- -匹配所有类public方法 execution(public *.*(..))
- -匹配指定包下所有类方法 execution(* com.tulun.bean.*(..)) (不包含子包)
- - execution(* com.tulun.bean..*(..)) (包含包、子包下所有类)
- -匹配指定类所有方法 execution(* com.tulun.bean.Book.*(..))
- -匹配实现特定接口所有类方法 execution(* com.tulun.bean.Book+.*(..))
- -匹配所有com开头的方法 execution(* com*(..))
spring-aop.xml配置
<!--配置对象-->
<bean id="student" class="org.example.bean.Student"></bean>
<bean id="log" class="org.example.bean.addLog"></bean>
<!--配置AOP操作-->
<aop:config>
<!--配置切入点:使用execution表达式-->
<aop:pointcut id="pointcut1" expression="execution(* com.tulun.bean.Student.addStudent())"/>
<!--配置切面:把通知应用到方法的过程-->
<aop:aspect ref="log">
<!--配置增强类型 method属性:指定曾倩类中的那个方法-->
<aop:before method="writeLog" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
AOP的实现类
//获取IOC容器,通过读取classpath路径下的spring的配置文件
String path = "spring-aop.xml";
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext(path);
//在IOC容器获取需要的对象实例
Student student = (Student) applicationContext.getBean("student");
student.addStudent();
四种通知类型
<!--前置通知:aop:before-->
<aop:before method="writeLog1" pointcut-ref="pointcut1"/>
<!--后置通知:aop:after-->
<aop:after method="writeLog1" pointcut-ref="pointcut1"/>
<!--最终通知:aop:after-returning-->
<aop:after-returning method="writeLog2" pointcut-ref="pointcut1"/>
<!--异常通知:aop:after-throwing-->
<aop:after-throwing method="around" pointcut-ref="pointcut1"/>
<!--环绕通知:aop:around-->
<aop:around method="around" pointcut-ref="pointcut1"/>
AOP基于注解形式实现
在spring-aop.xml中开启注解
<!--打开AOP注解-->
<aop:aspectj-autoproxy/>
在类上添加注解
package org.example.bean;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* Description :
* Created by Resumebb
* Date :2021/4/11
*/
@Aspect
public class addLog {
@Before(value = "execution(* org.example.bean.Student.addStudent(..))")
public void writeLog1(){
System.out.println("DIYLog.writeLog1");
}
public void writeLog2(){
System.out.println("DIYLog.writeLog2");
}
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("执行方法之前");
joinPoint.proceed();
System.out.println("执行方法之后");
}
}
注意:
AOP相关操作的注解一定是在增强类和方法上
在增强类上添加注解@Aspect
在增强类中响应方法上根据不同的增强类型添加上不同注解
- @Around(value = "execution(* com.tulun.bean.Student.addStudent(..))") //环绕通知
- @Before(value = "execution(* com.tulun.bean.Student.addStudent(..))")
- @After(value = "execution(* com.tulun.bean.Student.addStudent(..))") //后置通知
- @AfterReturning //最终通知
- @AfterThrowing //异常通知