文章目录
概念
AOP(Aspect Oriented Programing) 面向切面编程:扩展功能不通过修改源代码实现。
AOP采用横向抽取机制,取代传统的编码方式(纵向继承的体系),来实现相应的功能(性能监控、事务、安全检查、缓存)。
一、AOP的设计原理和思想
AOP横向抽取机制
public class User{
//
public int addUser(){
}
public User getUser(){
}
...
}
//扩展功能:所有的方法增加日志功能
- 纵向抽取机制解决
对日志功能的修改只需要在LogUtil中做修改,减少代码并发量。存在的问题:父类中LogUtil方法名称发生变化,在子类调用的方法也需要发生变化。
public class logUtil{
public void printLog(){
System.out.print("...");
System.cucurrentMills();//打印时间
}
public class User extends LogUtil{
//
public int addUser(){
}
public User getUser(){
}
...
}
- AOP横向抽取机制
底层使用:动态代理方式实现,Java中有一种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.每个面被称为切面(本质上是每一个方法调用)。(AOP是针对方法调用的编程思路。)
- 选择切面的过程实际上就是选择方法的过程。被选择的切面(Aspect)在AOP术语里被称为切入点(Point Cut). 切入点就是从所有的连接点(Join point)中挑选一部分的过程。( 切入点可以称为连接点,但不是所有的连接点都可以称为切入点 )
使用代理模式的Java程序执行流
假设在Java代码里,都为实例对象通过代理模式创建了代理对象,访问这些实例对象必须要通过代理。想调用某一个实例对象的方法时,都会经过这个实例对象相对应的代理对象, 即执行的控制权先交给代理对象。代理模式可以为某些对象除了实现本身的功能外,提供一些额外的功能(日志、事务…)。
Spring AOP的工作原理
Spring AOP 根据proxy提供的类型名和方法签名,确定了在其感兴趣的切入点内,则返回 AfterReturingAdivce 处理建议,proxy得到这个处理建议,然后执行建议;根据proxy提供的 特定类的特定方法 执行的 特定时期阶段 给出相应的处理建议。要完成该工作,Spring AOP应该实现:
- 确定 AOP的切入点(Point Cut),这个可以通过切入点(Point Cut)表达式来完成;
- 对应的的类的方法的执行特定时期 给出处理建议 ,需要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的依赖
<!--spring AOP相关jar-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.7.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>
引入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"
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 Student {
public void addStudent(){
System.out.println("Student.addStudent");
}
}
增强类:添加日志功能
public class DIYLog {
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*(..))
配置AOP操作
<!--配置对象-->
<bean id="student" class="bean.Student"/>
<bean id="log" class="bean.DIYLog"/>
<!--配置AOP操作-->
<aop:config>
<!--配置切入点:使用 execution表达式-->
<aop:pointcut id="pointcut1" expression="execution(* bean.Student.addStudent())"/>
<!--配置切面:把通知应用到方法的过程-->
<aop:aspect ref="log">
<!--配置增强类型 method属性:指定曾倩类中的那个方法-->
<aop:before method="writeLog" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
使用
//获取IOC容器,通过读取 classpath路径下的spring的配置文件
String path = "spring-aop.xml";
String path1 = "applicationcontext.xml";
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
//在IOC容器获取需要的对象实例
Student student = (Student) applicationContext.getBean("student");
student.addStudent();
注意:环绕通知的特点
/**
* 环绕通知
*/
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法之前执行");
//执行真正的实现方法
joinPoint.proceed();
System.out.println("方法之后执行");
}
各种通知类型配置如下:
<!--前置通知: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基于注解形式实现
开启AOP的注解操作
<!--打开AOP注解-->
<aop:aspectj-autoproxy/>
增强类添加上注解
@Aspect
public class DIYLog {
@Before(value = "execution(* bean.Student.addStudent(..))") //前置通知
@After(value = "execution(* bean.Student.addStudent(..))") //后置通知
@AfterReturning //最终通知
@AfterThrowing //异常通知
public void writeLog1(){//打印日志
System.out.println("DIYLog.writeLog1");
}
public void writeLog2(){//打印日志
System.out.println("DIYLog.writeLog2");
}
/**
* 环绕通知
*/
@Around(value = "execution(* bean.Student.addStudent(..))")//环绕通知
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 //异常通知