五 基于注解的AOP开发
5.1 快速入门
步骤分析
- 1. 创建java项目,导入AOP相关坐标
- 2. 创建目标接口和目标实现类(定义切入点)
- 3. 创建通知类(定义通知)
- 4. 将目标类和通知类对象创建权交给spring
- 5. 在通知类中使用注解配置织入关系,升级为切面类
- 6. 在配置文件中开启组件扫描和 AOP 的自动代理
- 7. 编写测试代码
5.1.1 创建 java 项目,导入 AOP 相关坐标
<dependencies>
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!--spring整合junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
5.1.2 创建目标接口和目标实现类
public interface AccountService {
public void transfer();
}
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账方法执行了");
}
}
5.1.3 创建通知类
/*
* 通知类
* */
public class MyAdvice {
public void before(){
System.out.println("前置通知执行了");
}
}
5.1.4 将目标类和通知类对象创建权交给spring
@Service
public class AccountServiceImpl implements AccountService {}
@Component
public class MyAdvice {}
5.1.5 在通知类中使用注解配置织入关系,升级为切面类
/*
* 通知类
* */
@Component
@Aspect // 升级为切面类:配置切入点和通知的关系
public class MyAdvice {
@Before("execution(* com.lagou..*.*(..))")
public void before(){
System.out.println("前置通知执行了");
}
}
5.1.6 在配置文件中开启组件扫描和 AOP 的自动代理
<!--组件扫描-->
<context:component-scan base-package="com.lagou"/>
<!--aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5.1.7 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer();
}
}
5.2 注解配置AOP详解
5.2.1 切点表达式
切点表达式的抽取
/*
* 通知类
* */
@Component
@Aspect // 升级为切面类:配置切入点和通知的关系
public class MyAdvice {
@Pointcut("execution(* com.lagou..*.*(..))")
public void myPoint(){
}
@Before("MyAdvice.myPoint()")
public void before(){
System.out.println("前置通知执行了");
}
}
5.2.2 通知类型
通知的配置语法: @ 通知注解 (“ 切点表达式 ")
名称 | 标签 | 说明 |
前置通知 | @Before | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | @AfterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
异常通知 | @AfterThrowing | 用于配置异常通知。指定增强的方法出现异常后执行 |
最终通知
| @After | 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行 |
环绕通知
| @Around | 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行 |
注意:
- 当前四个通知组合在一起时,执行顺序如下:
- @Before -> @After -> @AfterReturning(如果有异常:@AfterThrowing)
5.2.3 纯注解配置
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy // 开启AOP的自动代理 替代了<aop:aspectj-autoproxy>标签
public class SpringConfig {
}
5.3 知识小结
- * 使用@Aspect注解,标注切面类
- * 使用@Before等注解,标注通知方法
- * 使用@Pointcut注解,抽取切点表达式
- * 配置aop自动代理 <aop:aspectj-autoproxy/> 或 @EnableAspectJAutoProxy
六 AOP优化转账案例
依然使用前面的转账案例,将两个代理工厂对象直接删除!改为 spring 的 aop 思想来实现
6.1 xml配置实现
1 )配置文件
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.lagou"/>
<!--开启AOP注解支持-->
<aop:aspectj-autoproxy/>
<!--加载jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--把数据库连接池交给IOC容器-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--把QueryRunner交给IOC容器-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
</beans>
2 )事务管理器(通知)
/*
* 事务管理器工具类:包含:开启事务,提交事务,回滚事务,释放资源
* 通知类
* */
@Component
@Aspect // 表名该类为切面类
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
/*
* 开启事务
* */
public void beginTransaction(){
// 获取connection对象
Connection connection = connectionUtils.getThreadConnection();
try {
// 开启了一个手动事务
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/*
* 提交事务
* */
public void commit(){
// 获取connection对象
Connection connection = connectionUtils.getThreadConnection();
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*
* 回滚事务
* */
public void rollback(){
// 获取connection对象
Connection connection = connectionUtils.getThreadConnection();
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/*
* 释放资源
* */
public void release(){
// 获取connection对象
Connection connection = connectionUtils.getThreadConnection();
try {
// 将手动事务改回成自动提交事务
connection.setAutoCommit(true);
// 将连接归还到连接池
connectionUtils.getThreadConnection().close();
// 解除线程绑定
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
6.2 注解配置实现
1 )配置文件
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启注解扫描-->
<context:component-scan base-package="com.lagou"/>
<!--引入properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!--开启AOP的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--AOP配置-->
<!--<aop:config>
<!–切点表达式–>
<aop:pointcut id="myPointcut" expression="execution(* com.lagou.service.impl.AccountServiceImpl.*(..))"/>
<!–切面配置–>
<aop:aspect ref="transactionManager">
<aop:before method="beginTransaction" pointcut-ref="myPointcut"/>
<aop:after-returning method="commit" pointcut-ref="myPointcut"/>
<aop:after-throwing method="rollback" pointcut-ref="myPointcut"/>
<aop:after method="release" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>-->
</beans>
2 )事务管理器(通知)
/*
* 事务管理器工具类:包含:开启事务,提交事务,回滚事务,释放资源
* 通知类
* */
@Component
@Aspect // 表名该类为切面类
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Around("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws SQLException {
Object proceed = null;
try {
// 开启手动事务
connectionUtils.getThreadConnection().setAutoCommit(false);
// 切入点方法执行
proceed = pjp.proceed();
// 手动提交事务
connectionUtils.getThreadConnection().commit();
} catch (Throwable throwable) {
throwable.printStackTrace();
// 手动回滚事务
connectionUtils.getThreadConnection().rollback();
} finally {
// 释放资源
connectionUtils.getThreadConnection().setAutoCommit(true);
connectionUtils.getThreadConnection().close();
connectionUtils.removeThreadConnection();
}
return null;
}
}