1 背景介绍
AOP: Aspect Oriented Programming , 面向切面的编程;
目的: 为了减少重复代码, 易维护; 为原始的业务代码进行功能增强;
场景:
(1) 比如AccountServiceImpl类中有增删改查四个方法; 最原始开发方法要在这四个方法中都增加事务管理的代码,我们称为业务代码;
使用AOP技术,我们可以把这部分事务管理的代码提取出来, 并且只有当程序运行时, 自动将事务管理这部分代码加入到业务代码中执行;
原理: 动态代理技术实现了AOP的思想, Spring框架继承了Cglib动态代理工具类.
推荐使用: 环绕通知.
关键术语:
连接点 JointPoint : 业务代码中的方法;
切入点 Pointcut : 即哪些类中的方法需要功能增强;
切面类: 通过AOP提取出来的增强功能代码;
切面: 将切面类中的增强方法 加入到 切入点中的业务代码中;
2 举例
IDEA工具下maven工程,
(1) 需要的jar包依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
(2) 业务接口和接口实现类
public interface AccountService {
/**
* 模拟保存操作
*/
void save();
/**
* 模拟更新操作
* @param i
*/
void update(int i);
/**
* 模拟删除操作
* @return
*/
int delete();
}
public class AccountServiceImpl implements AccountService {
@Override
public void save() {
System.out.println("模拟保存账户...");
int i = 1/0;
}
@Override
public void update(int i) {
System.out.println("更新账户..." + i);
}
@Override
public int delete() {
System.out.println("删除账户....");
return 1000;
}
}
(3) 切面类
public class LogUtil {
//前置通知: 作用, 可以在切入点方法执行前, 获取到切入点的方法参数, 实现对参数进行增强
public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
//获取切入点的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知beforePrintLog = " + Arrays.toString(args));
}
//后置通知: 作用, 可以对切入点方法的返回值 进行增强
public void afterReturningPrintLog(JoinPoint joinPoint, Object rtValue) throws Throwable {
//获取切入点方法的返回值
System.out.println("后置通知afterReturningPrintLog = " + rtValue);
}
//异常通知: 作用: 可以在切入点方法执行过程中,出现异常, 对异常进行处理, 进行增强
public void afterThrowingPrintLog(JoinPoint joinPoint, Throwable t) throws Throwable {
//打印异常信息
t.printStackTrace();
System.out.println("异常通知afterThrowingPrintLog = " + t.getMessage());
}
//最终通知: 作用: 用于是否资源
public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
System.out.println("最终通知afterPrintLog");
}
//环绕通知: 作用: 围绕着切入点方法进行执行, 更加灵活的对切入点方法进行增强
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object obj = null;
try {
//前置通知
this.beforePrintLog(joinPoint);
//业务方法执行 (获取参数, 执行方法)
Object[] args = joinPoint.getArgs();
obj = joinPoint.proceed(args);
//后置通知
this.afterReturningPrintLog(joinPoint, obj);
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常通知
this.afterThrowingPrintLog(joinPoint, throwable);
} finally {
//最终通知
this.afterPrintLog(joinPoint);
}
return obj;
}
}
3 xml实现AOP
AOP支持的5种通知:
try{
事前通知: aop:before, 常用于开启事务
事后通知: aop:after-returning , 常用于提交事务, 对返回值进行增强;
}catch{
异常通知: aop:after-throwing, 常用于异常时,异常信息提取;
}finally{
最终通知: aop:after, 常用于释放资源
}
环绕通知: round, 更加灵活, 可以指定每个通知的具体顺序
<?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.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置Service-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--配置AOP-->
<!--1.配置日志切面类-->
<bean id="logUtil" class="com.itheima.utils.LogUtil"></bean>
<!--2.AOP的配置-->
<aop:config>
<!--配置通用的切入点表达式-->
<aop:pointcut id="pc" expression="execution(* com.itheima.service..*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logUtil" ref="logUtil">
<!--配置Service中的业务方法 与 日志切面类中的增强方法 进行关联-->
<!--前置通知: 执行时机, 在切入点方法执行前 执行-->
<aop:before method="beforePrintLog" pointcut-ref="pc"></aop:before>
<!--后置通知: 执行时机, 在切入点方法执行完成后, 执行-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pc" returning="rtValue"></aop:after-returning>
<!--异常通知: 执行时机, 在切入点方法执行过程中抛出异常时, 执行-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc" throwing="t"></aop:after-throwing>
<!--最终通知: 执行时机, 在切入点方法执行完成后, 最终执行(不关心是否出现了异常, 都会执行)-->
<aop:after method="afterPrintLog" pointcut-ref="pc"></aop:after>
<!--环绕通知: 执行时机, 围绕着切入点方法进行执行, 更加灵活的对切入点方法进行增强-->
<aop:around method="around" pointcut-ref="pc"></aop:around>
</aop:aspect>
</aop:config>
</beans>
4. 半注解半XML方式实现AOP
(1) spring核心配置文件开启SpringIOC和AOP的注解扫描
开启SPringIOC的 注解扫描
<context:component-scan base-package="com.itheima"></context:component-scan>
开启AOP的注解扫描
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(2) 其他AOP配置改为注解配置
@Component("logUtil")
//切面注解
@Aspect
public class LogUtil {
//配置通用的切入点表达式
@Pointcut("execution(* com.itheima.service..*.*(..))")
public void pointCut(){
}
//前置通知: 作用, 可以在切入点方法执行前, 获取到切入点的方法参数, 实现对参数进行增强
//@Before("pointCut()")
public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
//获取切入点的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知beforePrintLog = " + Arrays.toString(args));
}
//后置通知: 作用, 可以对切入点方法的返回值 进行增强
//@AfterReturning(value = "pointCut()", returning = "rtValue")
public void afterReturningPrintLog(JoinPoint joinPoint, Object rtValue) throws Throwable {
//获取切入点方法的返回值
System.out.println("后置通知afterReturningPrintLog = " + rtValue);
}
//异常通知: 作用: 可以在切入点方法执行过程中,出现异常, 对异常进行处理, 进行增强
//@AfterThrowing(value = "pointCut()", throwing="t")
public void afterThrowingPrintLog(JoinPoint joinPoint, Throwable t) throws Throwable {
//打印异常信息
t.printStackTrace();
System.out.println("异常通知afterThrowingPrintLog = " + t.getMessage());
}
//最终通知: 作用: 用于是否资源
//@After("pointCut()")
public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
System.out.println("最终通知afterPrintLog");
}
//环绕通知: 作用: 围绕着切入点方法进行执行, 更加灵活的对切入点方法进行增强
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object obj = null;
try {
//前置通知
this.beforePrintLog(joinPoint);
//业务方法执行 (获取参数, 执行方法)
Object[] args = joinPoint.getArgs();
obj = joinPoint.proceed(args);
//后置通知
this.afterReturningPrintLog(joinPoint, obj);
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常通知
this.afterThrowingPrintLog(joinPoint, throwable);
} finally {
//最终通知
this.afterPrintLog(joinPoint);
}
return obj;
}
}
5. 纯注解实现AOP
SpringIOC和AOP注解扫描 这两句xml语句, 改为注解方法,
在类前@Configuration 注解表示该类时一个Spring配置文件;
//表示当前类是一个SPring配置文件
@Configuration
//开启SpringIOC的注解扫描
@ComponentScan(value ="com.itheima")
//开启SpringAOP注解的扫描的支持
@EnableAspectJAutoProxy
public class SpringConfig {
}
6. 测试类
半注解半xml的测试类写法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
纯注解的测试类写法
@RunWith(SpringJUnit4ClassRunner.class)
//加载配置文件 注解的形式( 加载的是 java的字节码文件)
@ContextConfiguration(classes= {SpringConfig.class })
public class AccountServiceTest {
//依赖注入
@Autowired
private AccountService accountService;
@Test
public void save() {
accountService.save();
}
}
7. 事务管理的xml的AOP实现
在init()方法中,配置事务同步管理器, 底层是ThreadLocal, 以此确保service层使用的connection和mybatis执行sql时使用的connection是同一个连接.
TransactionSynchronizationManager.initSynchronization();
<!--1.配置切面类 TranscationManager-->
<bean id="transcationManager" class="com.itheima.utils.TranscationManager" init-method="init">
<!--配置相关内容 (数据源)-->
<property name="dataSource" ref="druidDataSource"/>
</bean>
public class TranscationManager {
//依赖注入一个连接对象
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
//保证Service层使用的Connection 与 Mybatis执行sql语句时 用的Connection 是同一个对象
public void init(){
TransactionSynchronizationManager.initSynchronization();
}
//开启事务 ( 前置通知 )
public void begin(){
try {
DataSourceUtils.getConnection(dataSource).setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务 ( 后置通知 )
public void commit(){
try {
DataSourceUtils.getConnection(dataSource).commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务 ( 异常通知 )
public void rollback(){
try {
DataSourceUtils.getConnection(dataSource).rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放资源 ( 最终通知 )
public void close(){
try {
DataSourceUtils.getConnection(dataSource).close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}