SpringAOP(2)

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,customercarHandler。接下来,我们把所有关于切面的信息都配置在aop:config中,aop:aspect用来声明一个单独的切面,它引入了一个POJO也就是carHandler,aop:pointcut用来定义一个切点表达式,aop:beforeaop: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的基本原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值