AOP简介
AOP,Aspect Orient Programming,面向切面编程。是一种概念,是一种思想,其实现者很多,Spring就是实现者之一。
AOP是OOP(面向对象编程)的一种补充。OOP是从静态角度考虑程序的结构,而AOP是动态角度考虑程序的运行过程。
Spring用于降低耦合度。IoC用于降低主业务逻辑之间的耦合度;AOP用于降低主业务逻辑与系统级服务(交叉业务逻辑)间的耦合度,将系统级服务在运行时织入到主业务逻辑之中。
Spring的AOP的底层工作原理,是使用动态代理对主业务进行增强。而动态代理使用的是JDK的Proxy与CGLIB两种动态代理。
AOP的知识点:
- 切面:切面指的就是交叉业务逻辑比如记录日志事务管理等等。就是要对主业务逻辑进行增强的部分。AOP编程常见的切面有两种,一种是通知,一种是顾问
- 织入:就是将切面切入到主业务逻辑的过程,称为织入。
- 连接点:可以织入切面的目标方法,称为连接点。
- 切入点:实际织入切面的目标方法。
- 目标对象:用于被增强的对象。主业务逻辑所在类的对象
- 通知:是一种切面,可以完成简单的织入功能。例如:可以指定切入的时间点。是方法执行前还是方法结束前或者执行过程中等等时间点。通知会将目标类中所有的方法作为切入点进行织入。
- 顾问:也是一种切面,其中包含了通知,可以完成更为复杂的织入功能。可以指定具体切入的类和具体方法
- 引入:一种切面,在不修改源码的前提下,给目标对象增加新的功能。
AOP编程的环境搭建
- 导入spring-aop的jar包
- 导入aop联盟的jar包
通知的具体分类:
- 前置通知MethodBeforeAdvice:用于在主业务方法执行前进行的操作,实现MethodBeforeAdvice接口并实现接口中的方法
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行前置通知-----------------");
}
}
- 后置通知AfterReturningAdvice:实现AfterReturningAdvice接口中的通知方法
public class MyAfterAdvice implements AfterReturningAdvice {
//后置通知可以获取到目标方法的返回值returnValue但是不能修改返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行后置通知------------------");
}
}
- 环绕通知:实现MethodInterceptor接口该接口是AOP联盟提供的,该方法可以调用执行目标方法,可以在目标方法执行前后进行功能增强,也可以修改,目标方法的返回值
public class MyInterceptorAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("方法执行前代码");
String result = (String) invocation.proceed();
System.out.println("方法执行后代码");
return result.toUpperCase();
}
}
- 异常通知:自定义通知类实现ThrowsAdvice接口,该接口中没有具体需要实现的方法,通过查看源码可知,有四个方法可以选择性的实现我们一般实现afterThrowing(Exception ex)方法,该方法可以重载,用来匹配不同类型的异常,异常通知会在目标方法发生异常时执行。
通知的织入
- 在spring容器中(配置文件)注册通知
- 注册目标对象
- 使用动态代理工厂Bean生成增强后的代理对象
<!-- 注册前置通知 -->
<bean id="beforeAdvice" class="com.lscw.advices.MyBeforeAdvice"/>
<!-- 注册目标方法 -->
<bean id="myCar" class="com.lscw.car.carImpl" />
<!-- 使用代理工厂生成代理对象 -->
<bean id="carProxy" class="org.springframework.aop.framework.ProxyFactoryBean" >
<!-- 指定目标类 -->
<property name="targetName" value="myCar" />
<!-- 指定织入的通知,可以指定多个,我这只指定一个前置通知 -->
<property name="interceptorNames" value="beforeAdvice" />
</bean>
- 从spring容器中获取代理对象调用执行其中的方法得出结果
顾问Advisor:
- 切入点顾问:用于指定切入点织入的位置,就是说,你要将切面织入到哪个目标方法。
- 引入顾问:在不修改目标类的前提下,给目标对象增加新的功能。
切入点顾问:
- 名称匹配方法切入点顾问
- 正则表达式方法切入点顾问:正则表达式方法切入点顾问中的属性pattern所匹配的是方法的全限定性名称
使用动态代理工厂Bean的缺点:
- 从spring中获取的是代理对象而不是目标对象
- 每一个目标类都要注册代理对象
默认顾问自动生成器:
Bean名称自动代理生成器:
通过注入属性beanNames和interceptorNames可以指定多个Bean和多个顾问或通知
AspectJ对AOP的实现
AspectJ概述:
- 什么是AspectJ:AspectJ是一个面向切面的框架,它扩展了Java语言。
AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。 - AspectJ Spring AOP 之间的关系:
AspectJ是另外一个框架,与Spring没有任何关系。但,AspectJ与Spring都是AOP的实现者。
下面要讲述的是:基于Spring环境下的AspectJ对AOP的实现。
AspectJ知识点:
- AspectJ的通知:前置通知 后置通知 环绕通知 异常通知 最终通知
- 最终通知:无论是否发生异常都会执行的通知
- 切入点表达式:使用规范execution(方法返回值 空格 方法的全限定名称 参数列表)
execution( * com.abc.service.*.*(..)):表示com.abc.service包下的所有方法都是切入点。
execution( * com.abc.service.*.open*(..)):表示com.abc.service包下的所有以open开头的方法都是切入点。
execution( * *.service.*.*(..)):表示service包(该service包前只能有一级父包)下的所有方法都是切入点。
com.service.ISomeService.doFirst()是匹配的。
com.abc.service.ISomeService.doFirst()是不匹配的。
execution( * *..service.*.*(..)):表示service包(该service包前可以有任意级的父包)下的所有方法都是切入点。
com.service.ISomeService.doFirst()是匹配的。
com.abc.service.ISomeService.doFirst()也是匹配的
AspectJ的环境搭建:
- AspectJ的核心jar包
- spring与AspectJ整合的jar包
- spring-aop的jar包
- aop联盟的jar包
- spring容器中引入对aop的约束
- 注册aop:aspectJ自动代理生成器
基于注解的AspectJ编程:
- 注解的介绍:
@Aspect:该注解作用于类上表示该类为切面类
@Before:表示前置通知,使用方式:@Before(“execution(“* *..aop15.类名或接口名.方法名(..)”)”)
@AfterReturning:表示后置通知
@Arround:环绕通知,定义在该注解下的方法需要添加ProceedingJoinPoint参数
@AfterThrowing:异常通知,定义了异常注解的方法,应添加Exception参数
@After:最终通知
@PointCut:使用了该注解的方法,其方法名可以作为切入点 - 代码示例:
@Aspect // 表示当前类为切面
public class MyAspect {
// 前置通知
@Before("execution(* *..aop15.ISomeService.doFirst(..))")
public void myBefore() {
System.out.println("执行前置通知方法");
}
// 前置通知
@Before("execution(* *..aop15.ISomeService.doFirst(..))")
public void myBefore(JoinPoint jp) {
System.out.println("执行前置通知方法 jp = " + jp);
}
// 后置通知
@AfterReturning("execution(* *..aop15.ISomeService.doSecond(..))")
public void myAfterReturning() {
System.out.println("执行后置通知方法");
}
// 后置通知
// 后置通知可以获取到目标方法的执行结果,但不能改变其值
@AfterReturning(value = "execution(* *..aop15.ISomeService.doSecond(..))", returning = "result")
public void myAfterReturning(Object result) {
System.out.println("执行后置通知方法 result = " + result);
}
// 环绕通知
// 环绕通知不仅可以读取到目标方法的执行结果,还可以改变其值
@Around("execution(* *..aop15.ISomeService.doSecond(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("执行环绕通知 -- 目标方法执行之前");
// 执行目标方法
Object proceed = pjp.proceed();
System.out.println("执行环绕通知 -- 目标方法执行之后");
if (proceed != null) {
proceed = ((String) proceed).toUpperCase();
}
return proceed;
}
// 异常通知
@AfterThrowing("execution(* *..aop15.ISomeService.doThird(..))")
public void myAfterThrowing() {
System.out.println("执行异常通知方法");
}
// 异常通知
@AfterThrowing(value = "doThirdPointcut()", throwing="ex")
public void myAfterThrowing(Exception ex) {
System.out.println("执行异常通知方法 ex = " + ex.getMessage());
}
// 最终通知
@After("execution(* *..aop15.ISomeService.doThird(..))")
public void myAfter() {
System.out.println("执行最终通知方法");
}
// 最终通知
@After("doThirdPointcut()")
public void myAfter2() {
System.out.println("执行最终通知方法2");
}
// 定义切入点
@Pointcut("execution(* *..aop15.ISomeService.doFirst(..))")
private void doFirstPointcut(){}
@Pointcut("execution(* *..aop15.ISomeService.doSecond(..))")
private void doSecondPointcut(){}
@Pointcut("execution(* *..aop15.ISomeService.doThird(..))")
private void doThirdPointcut(){}
}
基于XML的AspectJ编程:
- 前置通知:
- 后置通知:
- 环绕通知:
- 异常通知:如果带参数需要注册参数类型和参数名
- 最终通知: