四、基于Spring的AOP
1、AOP的基本概念
- 什么是AOP?
Aop:全称是Aspect Oriented Programming ,即面向切面编程,即将重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
- AOP的作用及优势
作用:在程序运行期间,不修改源码对已有的方法进行增强。
优势:减少重复代码、提高开发效率、维护方便
- 实现方式
使用动态代理技术
2、动态代理技术
- 特点
字节码随用随创建,随用随加载
它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载
装饰者模式就是静态代理的一种体现。
- 动态代理的的常用的两种方式
- 基于接口的动态代理
提供者:JDK官方的Proxy类
要求:被代理类最少实现一个接口
- 基于子类的的动态代理
提供者:第三方的CGLib,如果报asmxxxx异常,需要导入ass.jar
要求:被代理类不能用final修饰的类(最终类)
3、Spring中的AOP
3.1、AOP中的相关术语
Jointpoint(连接点):所谓连接点是指哪些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。(在接口中定义的所有方法都是连接点)
Pointcut(切入点):所谓且入点是指我们要对哪些Joinpoint进行拦截的定义。(被增强的方法)
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知类型:前置通知、后置通知、异常通知、最终通知、环绕通知。
try { //1.开启事务 txManager.beginTransaction();//前置通知 //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit();//后置通知 //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e);//异常通知 } finally { //6.释放连接 txManager.release();//最终通知 }
环绕通知是从前置通知到最终通知
Introduction(引介):引介是一种特殊的通知,Introduction可以在运行期为类动态地添加一些方法和Field
Target(目标对象):代理的目标对象
Weaving:是把增强应用的目标对象来创建新的代理对象的过程,spring采用动态代理织入,而AspectJ采用编译器织入和类装载器织入。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合
3.2、开发步骤
- 开发阶段
- 编写核心业务代码
- 把公用的代码抽取出来,制作出通知
- 在配置文件中,声明切入点与通知间的关系,即切面
- 运行阶段
运行阶段由Spring框架来完成,监控切入点的执行。一旦监控到切入点方法被执行,使用代理机制,动态创建目标对象的代理机制,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的逻辑代码的执行。
3.3、基于XML的AOP配置
- 步骤
1、配置用于通知的bean,交给Spring来管理
2、使用aop:config标签表名开始aop的配置
3、使用aop:aspect标签表明配置切面
id属性:给切面提供唯一的标识
ref属性:时指定通知类bean的id
4、在aop:aspect标签的内部使用对用的标签来配置通知类型(前置通知、后置等)
以前置通知为例
aop:before表示前置通知
method属性:用于指定Logger类中的那个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义是指对业务中的那些方法进行增强
切入点表达式的写法:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
public void com.simon.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.simon.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.simon.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包,但是有几级包,就需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用…表示当钱包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名可以使用*来匹配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但必须有参数
可以使用…表示有无参数均可,有参数可以是任意类型
全通配写法
* *..*.*()
实际开发中切入点表达式的通常写法,切到业务层实现类下的所有方法
* com.simon.service.impl.*.*(..)
- 常用的通知类型
前置通知:在切入点方法执行之前执行
后置通知:在切点点方法正常执行执行,它和异常通知永远只能执行一个
异常通知:在切入点方法执行产生异常之后执行,它和后置通知永远只能执行一个
最终通知:无论切入点方法是否执行正确,都会执行
- 通用切入点表达式
id:用于指定表达式的唯一标识,expression用于指定表达式的内容
note:若此标签卸载aop:aspect标签内部,只能在当前切面使用,若在aop:aspect外面,此时就变成了所有切面可用,但其位置必须是放在配置通知的前面。
<aop:pointcut id="advice" expression="execution(* com.simon.service.impl.*.*(..)))"></aop:pointcut>
<bean id="accountService" class="com.simon.service.impl.AccountServiceImpl"></bean>
<!--配置Logger类-->
<bean id="logger" class="com.simon.utils.Logger"></bean>
<!--配置Aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知类型,并且建立通知方法和切入点方法的关联-->
<!--前置通知-->
<aop:before method="beforPrintLog" pointcut-ref="advice"></aop:before>
<!--后置通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="advice"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="advice"></aop:after-throwing>
<!--最终通知-->
<aop:after method="afterPrintLog" pointcut-ref="advice"></aop:after>
<!--通用切入点表达式-->
<aop:pointcut id="advice" expression="execution(* com.simon.service.impl.*.*(..)))"></aop:pointcut>
</aop:aspect>
</aop:config>
- 环绕通知
Spring框架为我们提供了一个接口:ProceedJoinPoint。该接口有一个方法proceed(),此方法相当于明确调用切入点的方法。该接口可以作为环绕通知的方法和参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
它是Spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
Note:通常情况下,环绕通知都是独立使用的。
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
Object[] args=pjp.getArgs();//得到方法执行所需的参数
System.out.println("前置通知:Logger类中的printLog方法开始记录日志了");
rtValue=pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("后置通知:Logger类中的printLog方法开始记录日志了");
return rtValue;
}catch (Throwable e){
System.out.println("异常通知:Logger类中的printLog方法开始记录日志了");
throw new RuntimeException(e);
}finally {
System.out.println("最终通知:Logger类中的printLog方法开始记录日志了");
}
}
配置方式
<aop:around method="aroundPrintLog" pointcut-ref="advice"></aop:around>
<!--通用切入点表达式-->
<aop:pointcut id="advice" expression="execution(* com.simon.service.impl.*.*(..)))"></aop:pointcut>
3.4基于注解的AOP配置
@Component("logger")
@Aspect//表明当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.simon.service.impl.*.*(..)))")//定义切点表达式
private void pt1() {
}
@Before("pt1()")
public void beforPrintLog() {
System.out.println("前置通知:Logger类中的printLog方法开始记录日志了");
}
@AfterReturning("pt1()")
public void afterReturningPrintLog() {
System.out.println("后置通知:Logger类中的printLog方法开始记录日志了");
}
@AfterThrowing("pt1()")
public void afterThrowingPrintLog() {
System.out.println("异常通知:Logger类中的printLog方法开始记录日志了");
}
@After("pt1()")
public void afterPrintLog() {
System.out.println("最终通知:Logger类中的printLog方法开始记录日志了");
}
-
配置文件
<context:component-scan base-package="com.simon"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
NOTE:也可以不采用配置文件,但是需要定义一个配置类(容器类)。
@Configuration
@ComponentScan(basePackages="com.simon")
@EnableAspectJAutoProxy
public class SpringConfiguration { }