Spring基础入门3 - AoP

上文我们一起学习了Spring IoC控制反转,下面我们继续学习一下AoP(Aspect Orient Programming), 面向切面编程。AoP同样是一种设计思想,AoP要达到的效果是,在不修改源代码的前提下,为系统中的业务组件添加某种功能增强。

AoP实现分为:静态AoP和动态AoP。

  • 静态AoP在编译阶段对程序源代码进行修改, 生成了静态的静态AoP代理类, 来实现增强功能的织入;
  • 动态AOP在运行阶段对动态生成代理对象(使用JDK动态代理或CGlib动态地生成AoP代理类), 来实现增强功能的织入;

Spring AoP使用的是动态代理,AoP框架并非Spring独有,市场上AoP框架有很多种。因为Spring AoP使用了aspectJ AoP框架的注解和表达式,而Spring-context包含了Spring-aop,所以我们只需要在pom.xml中额外引入aspectJ的依赖:

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjrt</artifactId>
	<version>1.9.7</version>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.7</version>
</dependency>

Eclipse项目对应的spring容器的配置文件也需要修改, 开启AspectJAutoProxy:

<?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" <!-- 引入aop的xmml名称空间-->
	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-4.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<bean name="bankingServiceImpl1" class="org.littlestar.learning.package6.BankingServiceImpl1"></bean>
	<bean name="bankingServiceImpl2" class="org.littlestar.learning.package6.BankingServiceImpl2"></bean>
	
	<bean name="onlineBank" class="org.littlestar.learning.package6.OnlineStore">
		<property name="bankingService" ref="bankingServiceImpl2"></property>
	</bean>	
	<!--
	<context:annotation-config />
	<context:component-scan base-package="org.littlestar.learning.package6" />
	 -->
	<bean name="withdrawAspect" class="org.littlestar.learning.package6.WithdrawAspect"></bean>
	<!--打开aspectj-autoproxy, 也可以通过@EnableAspectJAutoProxy注解来开启 -->
	<aop:aspectj-autoproxy /> 
	<!--aop:aspectj-autoproxy proxy-target-class="true"  expose-proxy="false" /--> 
</beans>

还是网上商城的案例,假设由于政策或者法规的变化,需要在取款服务(withdraw)之前做一些检查和控制,如反洗钱/反诈骗,我们如何在不修改代码的情况下,实现这个需求呢?哪天boss又提出需求, 想看看withdraw方法的执行耗时,你该如何以最小的代价,快速响应频繁的需求变化? 使用Spring AoP是一个完美答案。

我们需要BankingService.withdraw()方前添加检查控制功能,那么withdraw在AoP中就称为Pointcut(切入点)表示需要切入的位置。我们需要编写一个新的类WithdrawAspect,在该类中实现取款服务功能增强(前置检查控制功能),WithdrawAspect在Aop中称为Aspect(切面)表示一个横切进业务(withdraw())的一个对象。
有了切面(标注的类-WithdrawAspect), 还要在切面类内编写(相应功能增强)方法, 并在AoP框架中定义这些方法执行的时机,如在BankingService.withdraw()方法之前执行,这些有特殊配置的切面方法称为通知(Advice), 在Spring中有5种Advice注解: @Before, @After, @AfterReturning, @AfterThrowing和@Around 。
除了使用Advice注解也是可以通过XML配置文件配置,但没有使用注解直观简单。

WithdrawAspect.java

package org.littlestar.learning.package6;

import java.util.Date;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect    // 注解为一个切面(Aspect)
//@Component // 必须是一个Bean
public class WithdrawAspect {
	// @Pointcut: 切入点声明, 即切入到哪个(些)目标方法;
	// 这样Advice注解(@After/@Before)只需要关联此切入点声明即可,无需再重复写切入点表达式;
	// 我们可以表达式来指定要切入那些方法, 表达式可以使用通配符, 非常灵活;
	@Pointcut("execution(public boolean org.littlestar.learning.package6.BankingService.withdraw(..))")
	//@Pointcut("execution(* org.littlestar.learning.package6.BankingService.withdraw(..))")  //与上等价
	public void withdrawPointcut() {
		// @Pointcut注解的方法体要留空;
		// 函数名withdrawPointcut()是切入点的名称;
	}
	
	// 通知(Advice): 执行其注解方法在切入点执行的时机或者说位置, Spring AoP有5种Advice:
	//  @Before: Advice(通知)的一种, 切入点的方法体执行之前执行;
	//  @After : Advice(通知)的一种, 切入点的方法体正常运行结束后执行;
	//  @AfterReturning: Advice(通知)的一种, 在切入点正常运行结束后执行, 异常则不执行;
	//  @AfterThrowing:  Advice(通知)的一种, 在切入点运行异常时执行;
	//  @Around: Advice(通知)的一种, 切入点的方法在@Around标注的方法内部执行;
	@Before(value = "withdrawPointcut()") 
	//@Before("execution(public boolean org.littlestar.learning.package6.BankingService.withdraw(..))") //与上等价
	public void withdrawBeforeAdvice(JoinPoint joinPoint) { //
		Object[] args = joinPoint.getArgs();
		long id = Long.parseLong(args[0].toString()); 
		double amount = Double.parseDouble(args[1].toString());
		//System.out.println("Before BankingService.withdraw: checking );
		System.out.println("@Before withdrawPointcut: account-id = " + id +", amount = " + amount);
	}
	
	@Around(value = "withdrawPointcut()") 
	public Object withdrawAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
		long beforeTimestamp = new Date().getTime();
		System.out.println("@Around withdrawPointcut -> Before procced" );
		// if(...) return false;
		Object result  = joinPoint.proceed(joinPoint.getArgs());
		System.out.println("@Around withdrawPointcut -> After procced " );
		long afterTimestamp = new Date().getTime();
		System.out.println("@Around withdrawPointcut: return: " + result +"; elapse: " + (afterTimestamp-beforeTimestamp)+" ms");
		return result;
	}
}

启动方法:

package org.littlestar.learning.package6;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App6 {
	public static void main(String[] args) {
		try (ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
				"org/littlestar/learning/package6/package6-bean-config.xml")) {
			OnlineStore onlineBank = context.getBean(OnlineStore.class);
			//System.out.println(onlineBank.getProviderName());
			onlineBank.withdraw(10, 1000.00);
		}
	}
}
执行结果:
@Around withdrawPointcut -> Before procced
@Before withdrawPointcut: account-id = 10, amount = 1000.0
Calling withdraw(10, 1000.0)
@Around withdrawPointcut -> After procced 
@Around withdrawPointcut: return: true; elapse: 3 ms

我们要做的是将WithdrawAspect测试打包,发布到生产环境,修改Spring容器配置文件即可实现上述两个需求,已有的代码不需要修改和重新编译。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值