Spring框架(二)--AOP面向切面编程

相关文章:Spring学习笔记-IOC

在学习了Spring IOC的基础上,继续学习Spring AOP。

AOP(Aspect Oriented Programming)面向切面编程。什么叫面向切面编程?就是在不修改原来类的基础上,添加我们自己的操作,在哪个地方添加操作,相当于在原来的程序中“切”出来,切出来的那个地方,我们称之为“切点”。切出来以后,进行我们自己的操作的地方,称之为“切面”。在切面类里,写我们自己的程序,就是面向切面编程了。

AOP的主要作用是什么呢?就是在不修改原来类的基础上,进行添加我们自己的一系列操作,例如对类的方法调用进行日志记录,或者在类的方法调用前,对参数进行检验等。

1、前期准备

AOP依赖的jar包:

spring-beans-4.2.2.RELEASE.jar
spring-context-4.2.2.RELEASE.jar
spring-core-4.2.2.RELEASE.jar
spring-expression-4.2.2.RELEASE.jar
commons-logging-1.2.jar

前五个是IOC中必要的包,必须包含,因为AOP是以IOC为基础的。

spring-aop-4.2.2.RELEASE.jar
spring-aspects-4.2.2.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar ( 单独下载 )
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar ( 单独下载 )

这五个包就是为AOP准备的,其中最后两个包没有在spring-framework的文件压缩包下,必须单独下载,不知为什么o(╯□╰)o。

接着就是在配置文件xml中引入aop命名空间。

2、基于注解的方式配置aop

接口ArithmeticCaculator

public interface ArithmeticCaculator {


	int add(int i,int j);
	int sub(int i,int j);
	int mul(int i,int j);
	int div(int i,int j);
}
实现类ArithmeticCaculatorImpl,并注解方式配置bean

@Component
public class ArithmeticCaculatorImpl implements ArithmeticCaculator{

	@Override
	public int add(int i, int j) {
		int result = i +j;
		return result;
	}

	@Override
	public int sub(int i, int j) {
		int result = i-j;
		return result;
	}

	@Override
	public int mul(int i, int j) {
		int result = i*j;
		return result;
	}

	@Override
	public int div(int i, int j) {
		int result= i/j;
		return result;
	}

}
现在我们要在ArithmeticCaculator这个类的方法使用前后进行一系列的操作,那么就使用aop。

创建一个“切面”类,我们在这个类里完成需要的操作,使用@Aspect对类进行注解,就成了切面类。@Aspect要与@Component一起使用,因为切面类也要在IOC中注册bean。

前置通知Before与后置通知After

@Before("execution(public int 全类名.method(int , int ))")放在切面类的方法前,对方法进行注解,该方法会在调用  全类名.method(int , int )方法前调用,称为“前置通知“

同样@After为后置通知(无论目标方法是否异常),在调用 目标方法后进行调用,但不能获取到 目标方法产生的结果。

JoinPoint 这个类可以获取到切点类方法的名字和参数,也可以不写JoinPoint类,但是要使用就要写在参数的位置,否则异常:

使用joinPoint.getSignature().getName()获得切点方法名字,joinPoint,getArgs()获得切点方法的传入的参数

@Aspect
@Component
public class LoggingAspect {

	@Before("execution(public int com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.add(int, int))")
	public void before(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		System.out.println("Before method "+methodName+" begins with: "+args);
	}
	
	@After("execution(public int com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.add(int, int))")
	public void after(){
		System.out.println("After method.");
	}
}

要是我们配置的切面类被调用,就要在xml文件中配置:配置之后,才会在spring中为目标类生产动态代理。

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

Main使用

public static void main(String[] args) {

		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		ArithmeticCaculator arithmeticCaculator = (ArithmeticCaculator) ctx.getBean("arithmeticCaculatorImpl");
		
		int result = arithmeticCaculator.add(1, 2);
		System.out.println("result: "+result);

}
输出结果:

Before method add begins with: [1, 2]
After method.
result: 3
此时,在main中使用除add以外的方法,就不会调用在我们在切面类里配置的操作了,可以将@Before改为

@Before("execution(public int com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(int, int))")

调用类的所有符合public int 方法时,都会调用切面类的方法了。如果还有非public int的方法呢,也用通配符

@Before("execution(* com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(int, int))")
如果不单是ArithmeticCaculatorImpl的方法呢

@Before("execution(* com.cqupt.spring.aop.impl.*.*(int, int))")

如果不受参数形式限制呢?

@Before("execution(* com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(..))")

返回通知AfterReturning与异常通知AfterThrowing

前置通知是目标方法执行前执行,后置通知是目标方法执行后执行,无论目标方法异常与否。

返回通知是目标方法执行后执行,可以获取目标方法的返回值,如果目标方法异常,则返回通知不执行。使用@AfterReturning(value="execution()", returning="result")

异常通知是目标方法执行出现异常,才执行的通知,可以获取到异常原因,也可以限定出现哪种异常的类型,才执行异常通知。使用@AfterThrowing(value="execution()", throwing="ex")

	//returning="result"与Object result 的名字result必须保持一致,如果没有returning="result",出错
	@AfterReturning(value="execution(* com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(..))", returning="resul")
	public void afterReturning(JoinPoint joinPoint,Object resul){
		System.out.println("The method "+joinPoint.getSignature().getName()+" ends with result :"+resul);
	}
	
	@AfterThrowing(value="execution(* com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(..))",throwing ="ex")
	public void afterThrowing(JoinPoint joinPoint, Exception ex){//可以是别的异常类型 NullPointerException,限制为特定异常时,才执行异常通知
		System.out.println("The method "+joinPoint.getSignature().getName()+" occurs exception "+ex);
		
	}
其实,aop就是用动态代理实现的,那么这四种通知在动态代理中的位置可以在invoke函数中表示:

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				System.out.println("Method:"+method.getName()+"before :"+Arrays.asList(args)  );
				Object result = null;
				try{
					//前置通知
					method.invoke(target, args);
					//返回通知
				}
				catch (Exception e){
					//异常通知
				}
				//后置通知
					System.out.println("Method:"+method.getName()+"after :"+result );
				return result;
			}

环绕通知Around

环绕通知,顾名思义,就是环绕着目标方法执行的通知,目标方法的任意四个位置都可以用环绕通知实现,环绕通知功能最强,但最少使用。

@Around("Execution()")

用环绕通知完成前四个通知的功能,实际上环绕通知取代了动态代理,因为环绕通知的返回值就是调用动态代理的返回值,所以环绕通知必须有返回值。

环绕通知必须有一个 参数ProceedingsJoinPoint pcd,通过这个参数,可以调用目标函数的执行,并得到目标函数的返回值作为自己的返回值

	@Around("execution(* com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(..))")
	public Object around(ProceedingJoinPoint pcd){
		
		String methodName = pcd.getSignature().getName();
		List<Object> args = Arrays.asList(pcd.getArgs());
		Object result =null;
		try {
			//前置通知
			System.out.println("Around The method "+methodName+" begins with: "+args);
			result = pcd.proceed();
			//返回通知
			System.out.println("Around The method "+methodName+" ends with result :"+result);
		} catch (Throwable e) {
			e.printStackTrace();
			//异常通知
			System.out.println("Around The method "+methodName +" occurs exception "+e);
		}
		//后置通知
		System.out.println("Around The method "+methodName+" ends");
		return result;//如果返回100,那么程序的结果就是100,相当于环绕通知就是动态代理
	}

环绕通知和前四种通知重叠时,依次调用四种通知,Around的通知在优先,其实环绕通知就是取代了动态代理。

当环绕通知返回值为100,而非result时,

	public static void main(String[] args) {

		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		ArithmeticCaculator arithmeticCaculator = (ArithmeticCaculator) ctx.getBean("arithmeticCaculatorImpl");
		
		int result = arithmeticCaculator.add(1, 2);
		System.out.println("result: "+result);
}
结果为:

Around The method add begins with: [1, 2]
The method add begins with: [1, 2]
Around The method add ends with result :3
Around The method add ends
The method add ends
The method add ends with result :100  注意,可以看出返回通知所得到的结果是依赖环绕通知的返回值的
result: 100 程序的到的结果也就是Around的返回值

切面优先级Order

当同时有两个切面对同一个切点进行作用时,两个的优先级如何区分?在切面类上注解@Order(数值),数值越小的优先级越高。默认的没有注解@Order的切面类优先级最低。
创建一个验证切面,ValidateAspect并注解优先级
@Order(1)
@Aspect
@Component
public class ValidateAspect {

	@Before("execution(public int com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.add(int, int))")
	public void before(JoinPoint joinPoint){
		System.out.println("Validate method "+joinPoint.getSignature().getName()+" begins");
	}
}

重用切点表达式

可以看出。在一个切面内,前置后置、返回异常等通知都需要在注解中配置切入点表达式,即目标函数,重复配置切点表达式,造成代码冗余麻烦,通过重用可以解决。
使用@Pointcut注解一个空函数,将切点表达式配置好,在四种通知中可以使用。使用时,把函数名称嵌入在注解中即可。
	@Pointcut("execution(* com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(..))")
	public void declareJoinPointExpression(){}
	
	@Before("declareJoinPointExpression()")
	public void before(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		System.out.println("The method "+methodName+" begins with: "+args);
	}
	
	@After("declareJoinPointExpression()")
	public void after(JoinPoint joinPoint){
		System.out.println("The method "+joinPoint.getSignature().getName()+" ends");
	}

那么在其他切面类中,使用重用切面表达式时,如果在同一包中,只需指定类名.函数名(),不在一个包中,就需要指定全类名:

@Order(1)
@Aspect
@Component
public class ValidateAspect {

	@Before("LoggingAspect.declareJoinPointExpression()")//同一个包中,只需指定类名
	public void before(JoinPoint joinPoint){
		System.out.println("Validate method "+joinPoint.getSignature().getName()+" begins");
	}
}

基于配置文件配置AOP

基于配置文件,
首先,将每个普通类、切面类都配置bean,因为切面类配置为bean才能使用;
其次,如果有重用的切点表达式,配置重用切点表达式,可以放在所有切面类的外边(如果多个切面使用同一个切点表达式),也可以配置在某个切面类内<aop:pointcut id="" expression="execution()">;
第三,配置切面类,所以切面类的配置均在<aop:config/>配置,每个子节点<aop:aspect ref="beanid" order="2"/>代表一个切面类,并可以指定优先级属性order="2"
最后,配置单个切面类<aop:aspect/>子节点下的通知:
前置通知 <aop:before method="" pointcut-ref=""/>  
后置通知 <aop:after method="" pointcut-ref=""/>  
返回通知<aop:after-returning method="" pointcut-ref="" returning="result">必须有returning属性,否则出错。
异常通知<aop:after-throwing method="" pointcut-ref="" throwing="ex"/>必须要配置throwing属性,否则出错。
环绕通知<aop:around method="" pointcut-ref="">
	<bean id="arithmeticCaculator"
		class="com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl"></bean>
	<bean id="loggingAspect"
		class="com.cqupt.spring.aop.impl.LoggingAspect"></bean>
	<bean id="validateAspect"
		class="com.cqupt.spring.aop.impl.ValidateAspect"></bean>
	
	<aop:config>
		<aop:pointcut id ="pointcut" expression="execution(* com.cqupt.spring.aop.impl.ArithmeticCaculatorImpl.*(..))"></aop:pointcut>
		<aop:aspect ref="loggingAspect" order="2">
			<aop:before method="before" pointcut-ref="pointcut"></aop:before>
			<aop:after method="after" pointcut-ref="pointcut"></aop:after>
			<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="resul"></aop:after-returning>
			<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
		</aop:aspect>
		<aop:aspect ref="validateAspect" order="1">
			<aop:before method="before" pointcut-ref="pointcut"/>
		</aop:aspect>
	</aop:config>


















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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值