文章目录
SpringAOP(2)
1. Spring对AOP的支持
Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP
- 纯POJO切面
- @AspectJ注解驱动的切面(借鉴了另一个AOP的开源项目AspectJ)
- 注入式AspectJ切面
由于第一种过于笨重、复杂,最后一种涉及到另一门语言,本章我们将着重介绍第二和第三种方式。
2. AOP术语
在上一章编写动态代理案例的时候,我们把很多辅助逻辑都封装在了各种Handler中。这些售前服务、售后服务也就是所谓的横切关注点,它是可以被模块化为特殊的类的,这些类被称为切面(aspect)。这样做有两个好处:
- 首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中;
- 其次,模块服务更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中去了。
为了能更准确的使用Spring提供的AOP技术,我们首先有必要了解和掌握一部分常见的AOP术语:
2.1 通知Advice
通知定义了切面是什么(what)以及何时使用(when)。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。它应该应用在某个方法被调用之前?之后?还是只在方法抛出异常时调用?
Spring切面可以应用5种类型的通知:
通知类型 | 说明 |
---|---|
前置通知Before |
在目标方法被调用之前调用 |
后置通知After |
在目标方法完成之后调用通知,此时不会关心方法的输出是什么 |
返回通知After-returning |
在目标方法成功执行之后调用 |
异常通知After-throwing |
在目标方法抛出异常后调用 |
环绕通知Around |
包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为。 |
2.2 切点Pointcut
如果说通知定义了切面的“什么”(what)和“何时”(when)的话,那么切点就定义了“何处”(where)。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
2.3 切面Aspect
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。
在接下来,我们将使用两种方式对上一章的购买二手车案例进行改造。
3. 纯POJO切面
首先,需要给项目增加spring-aspects模块:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.19.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.19.RELEASE</version>
</dependency>
将所有的辅助逻辑编写为一个单独的处理类,这里面实际上就是所谓的横切关注点
public class CarHandler {
public void beforeBuy() {
System.out.println("售前服务:寻找车源、质量检测");
}
public void afterBuy() {
System.out.println("售后服务:过户服务、售后咨询");
}
}
目标类Customer
public class Customer {
public void buy() {
System.out.println("客户选车、付款");
}
}
可以看到,以上两个类分别为封装了切面关注点的CarHandler和代表目标类的Customer,从代码上就是纯粹的POJO,不带有任何一点Spring的痕迹。
aop配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="customer" class="com.turing.pojo.Customer"></bean>
<bean id="carHandler" class="com.turing.pojo.CarHandler"></bean>
<aop:config>
<aop:aspect ref="carHandler">
<aop:pointcut
expression="execution(* com.turing.pojo.Customer.buy(..))" id="cut1" />
<aop:before method="beforeBuy" pointcut-ref="cut1" />
<aop:after-returning method="afterBuy"
pointcut-ref="cut1" />
</aop:aspect>
</aop:config>
</beans>
要使用spring的aop首先需要在xml的头文件中声明引入aop这个命名空间,注意头部较之前多出来的三个字符串。
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
其中bean节点,用来配置两个POJO,customer
和carHandler
。接下来,我们把所有关于切面的信息都配置在aop:config
中,aop:aspect
用来声明一个单独的切面,它引入了一个POJO也就是carHandler,aop:pointcut
用来定义一个切点表达式,aop:before
和aop:after-returning
分别使用了前置通知和返回通知。
关于切点表达式:
- execution:在方法执行时触发
- *:返回任意类型
- com.turing.pojo.Customer:方法所属的类,也可以使用通配符
- buy:被通知的方法
- (…):使用任意参数
测试代码:
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("com/turing/pojo/spring.xml");
Customer customer = (Customer) ctx.getBean("customer");
customer.buy();
ctx.close();
}
4. @AspectJ注解驱动的切面
目标类
@Component
public class Customer {
public void buy() {
System.out.println("客户选车、付款");
}
}
使用@AspectJ声明一个切面
@Aspect
@Component
public class CarHandler {
@Pointcut("execution(* com.turing.aspectj.Customer.buy(..))")
public void cut1() {
}
@Before("cut1()")
public void beforeBuy() {
System.out.println("售前服务:寻找车源、质量检测");
}
@AfterReturning("cut1()")
public void afterBuy() {
System.out.println("售后服务:过户服务、售后咨询");
}
}
需要注意,CarHandler
需要标识为@Component
,让它作为一个POJO被spring容器管理,@AspectJ
同时让它成为一个切面的配置类。配置信息中,cut1
这个方法,本身并没有实际的意义,它的方法体里面什么也没有,这个方法只是作为@Pointcut
注解的依附,用来定义一个切点表达式的,方法名cut1
就是它的标识。
测试代码
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Test.class);
Customer customer = (Customer) ctx.getBean("customer");
customer.buy();
ctx.close();
}
}
@EnableAspectJAutoProxy
用来启用自动代理功能,如果没有它的话,@AspectJ
注解将不会被解析。
5. 使用AOP自定义事务管理切面
现在我们通过前面掌握的AOP技术,来进行JDBC的事务管理,仍然采用最经典的银行转账的案例来进行模拟。
dao层代码
@Component
public class CountDao {
@Autowired
private DataSource dataSource;
public void update(int id, int money) throws SQLException {
System.out.println("update:" + id);
Connection connection = dataSource.getConnection();
PreparedStatement st = connection.prepareStatement("update countinfo set deposit=deposit+? where id=?");
st.setBigDecimal(1, new BigDecimal(money));
st.setInt(2, id);
st.executeUpdate();
}
}
service层代码
@Component
public class CountService {
@Autowired
private CountDao countDao;
public void transferMoney() throws SQLException {
countDao.update(1, 1000);
countDao.update(2, -1000);
}
}
service层,通过连续两次调用dao,达到转账目的。这一层,也应该是事务边界层,即是,两次更新账户的操作,应属于同一次事务单元。
使用@Around
环绕通知封装事务处理逻辑
@Aspect
@Component
public class TransactionManager {
@Autowired
private DataSource dataSource;
@Pointcut("execution(* com.turing.tx.*Service.transferMoney(..))")
public void cut1() {
}
@Around("cut1()")
public void execute(ProceedingJoinPoint pj) {
Connection connection = null;
try {
connection = dataSource.getConnection();
System.out.println("开启事务");
connection.setAutoCommit(false);
pj.proceed();
System.out.println("提交事务");
connection.commit();
} catch (Throwable e) {
try {
System.out.println("回滚事务");
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
if (connection != null) {
try {
System.out.println("释放资源");
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
测试代码
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class Test {
public static void main(String[] args) throws SQLException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Test.class);
CountService service = (CountService) ctx.getBean("countService");
service.transferMoney();
ctx.close();
}
}
如果我们制造一次精度异常的话,这两次操作都将进行顺利回滚。
{
public static void main(String[] args) throws SQLException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Test.class);
CountService service = (CountService) ctx.getBean("countService");
service.transferMoney();
ctx.close();
}
}
如果我们制造一次精度异常的话,这两次操作都将进行顺利回滚。
至此,我们就已经通过AOP技术对事务管理进行了一次改造,在往后的学习中,我们还将使用由Spring提供的更为成熟和完善的Spring事务管理方案,本案例主要是为了让大家更好的理解AOP的基本原理。
转载:https://blog.csdn.net/yml_jack/article/details/101622385