什么是AOP?
- AOP为Aspect Oriented Programming的缩写,即面向切面编程,通过预编译方式和运行期间
动态代理
实现程序功能的统一维护的一种技术。 - AOP也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 的作用及优势
- 作用
在程序运行期间,不修改源码对已有方法进行增强。
- 优势
减少重复代码、提高开发效率、维护方便
AOP 相关术语
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
连接点。
Pointcut(切入点):
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方
法或 Field。
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。
基于XML的AOP配置
1、不同通知类型单独配置方式
a.把通知类用 bean 标签配置起来
<!-- 配置通知 -->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<property name="dbAssit" ref="dbAssit"></property>
</bean>
<!-- ============================================================= -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
<!-- 指定 connection 和线程绑定 -->
<property name="useCurrentConnection" value="true"></property>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
b.使用 aop:config 声明 aop 配置
<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>
c.使用 aop:aspect 配置切面
<aop:aspect id="txAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
</aop:aspect>
d.使用 aop:pointcut 配置切入点表达式
<aop:pointcut expression="execution(public void com.itheima.service.impl.AccountServiceImpl.transfer(java.lang.String, java.lang.String, java.lang.Float) )"
id="pt1"/>
- 切入点表达式说明
表达式语法:execution( [修饰符] 返回值类型 包名.类名.方法名(参数))
全匹配方式:d中的写法。
全通配方式:* * . . * . * (. .)
通常情况下
,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类
。
execution(* com.itheima.service.impl . * . * (. .))
e.使用 aop:xxx 配置对应的通知类型
<--前置通知-->
<aop:before method="beginTransaction" pointcut-ref="pt1"/>
<--后置通知-->
<aop:after-returning method="commit" pointcut-ref="pt1"/>
<--异常通知-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"/>
<--最终通知-->
<aop:after method="release" pointcut-ref="pt1"/>
2、利用环绕通知进行手动配置的方式
- 通常情况下,环绕通知都是独立使用的 (环绕方法中可以包含其他四种通知方法)
<aop:config>
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置环绕通知 -->
<aop:around method="transactionAround" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
- spring 框架为我们提供了一个接口:
ProceedingJoinPoint
(包含了所拦截方法的相关信息),它可以作为环绕通知的方法参数。在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object rtValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
rtValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return rtValue;
}
利用代码先后的执行顺序,就可以达到 间接实现多种不同的通知类型 的效果。
基于注解的 AOP 配置
1、不同通知类型单独配置方式
a.在 spring 配置文件中开启 spring 对注解 AOP 的支持
<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy/>
b.在配置类上声明配置方式
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy //声明:不使用 XML 的配置方式
public class SpringConfiguration {
}
c.在通知类上使用@Aspect 注解声明为切面
@Component("txManager") //将该类交给spring容器管理
@Aspect //表明当前类是一个切面类
public class TransactionManager {
//定义一个 DBAssit
@Autowired
private DBAssit dbAssit ;
...
}
d.指定切入点 并 在增强的方法上使用注解配置通知
//切入点表达式
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1() {}
//前置通知
@Before("pt1()") //注意:千万别忘了写括号
public void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//后置通知
@AfterReturning("pt1()")
...
//异常通知
@AfterThrowing("pt1()")
...
//最终通知
@After("pt1()")
...
2、配置环绕通知
@Around("pt1()")//注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object rtValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
rtValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return rtValue;
}