Spring学习(8)--AOP(面向切面编程)基础

7 篇文章 0 订阅

一、什么是AOP

AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全监测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护,所以就有了一个对面向对象编程的补充,即AOP。

二、AOP中的核心术语

切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。切面用Spring的 Advisor或拦截器实现。

连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

通知(或增强)(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。

目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

上述定义对于我这种初学者来说有些难以理解,在网上看到一位老师对这些术语进行了通俗的解释,下面介绍下那位老师对于其中几个常用的重要术语的介绍,便于理解。

连接点:即某个类里面可以被增强的方法,这些方法称为连接点。

切入点:在类里面可以有很多可以被增强的方法,但在实际操作中,只增强了个别方法,对于实际增强的方法称为切入点。

通知/增强:增强的逻辑,称为通知。例如扩展日志功能,这个日志功能就是通知/增强。

切面:把通知应用到具体方法或者说应用到切入点的过程称为切面。


三、通知(Advice)类型
前置通知(Before advice) :
在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
后置通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
返回通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。
异常通知(After throwing advice) : 在方法抛出异常退出时执行的通知。 


四、使用AOP的好处

1、降低模块之间的耦合度

2、使系统容易扩展

3、更好的代码复用。


五、使用AspectJ基于注解的方式开发Spring AOP

AspectJ不是Spring的一部分,它和Spring一起使用进行AOP操作。Spring 2.0 以后新增了对AspectJ的支持。

Java代码:

进行两个数字加减乘除运算的接口:

public interface ArithmeticCalculator {
	int add(int i,int j);
	int sub(int i,int j);
	int mul(int i,int j);
	int div(int i,int j);
}
接口的具体实现类:

@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator{

	@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 ;
	}
}

1.导入jar包。

除了Spring基础的jar外,还需要以下jar包。(下载地址:http://download.csdn.net/download/wxhcr912/10168490)


2.在配置文件中加入aop命名空间

xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
    	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

3.在配置文件中加入如下配置

<!--使AspectJ注解起作用:为匹配的类自动生成代理对象  -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4.为切点声明一个切面类,抽取通知代码。

(1)先把该类放进IOC容器中即加@component注解,再声明为切面即加@Aspect注解。

(2)在类中声明各种通知。即在类中的方法上加@Before/@After/@Around/@AfterReturning/@AfterThrowing注解。需要注意的是在这些注解后要加上切入点表达式如execution(* com.spring.service.*.*(..)),具体其中每个位置的占位符主要含义就是访问修饰符、包、类、方法,就不在这里赘述了。

(3)可以在通知方法中传入JoinPoint (org.aspectj.lang.JoinPoint)类型的参数,这样就能访问到连接点细节,例如方法名称和方法参数。

前置通知代码示例:

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

//把这个类声明为一个日志切面:先把该类放进IOC容器中即加@component注解,再声明为切面即加@Aspect注解
@Aspect
@Component
public class LoggingAspect {
	//声明该方法是一个前置通知:在目标方法执行之前执行
	@Before("execution(public int com.study.aop.impl.ArithmeticCalculator.add(int, int))")//表示作用于 ArithmeticCalculator类中的 add()方法
	public void before(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();//利用JoinPoint访问方法名 
		List<Object> args = Arrays.asList(joinPoint.getArgs());//利用JoinPoint访问方法参数 
		System.out.println("The method "+methodName+" begins "+args);
	} 
}
测试代码:

public class Test {
	public static void main(String[] args) {
		ApplicationContext app=new ClassPathXmlApplicationContext("aop-impl.xml");
		ArithmeticCalculator arithmeticCalculator=(ArithmeticCalculator) app.getBean("arithmeticCalculatorImpl");
		System.out.println(arithmeticCalculator.add(3, 3));
	}
}

运行结果:

The method add begins [3, 3]
6
后置通知代码示例1:(无异常)

//后置通知,在目标方法执行之后(无论是否发生异常),执行的通知
//因为方法可能出现异常,所以访问不到方法的返回值
@After("execution(public void com.study.aop.impl.ArithmeticCalculator.*(int, int))")//表示作用于 ArithmeticCalculator类中的所有方法
public void after(JoinPoint joinPoint){
	String methodName = joinPoint.getSignature().getName();//利用JoinPoint访问方法名 
	System.out.println("The method "+methodName+" end");
} 

测试代码:

public class Test {
	public static void main(String[] args) {
		ApplicationContext app=new ClassPathXmlApplicationContext("aop-impl.xml");
		ArithmeticCalculator arithmeticCalculator=(ArithmeticCalculator) app.getBean("arithmeticCalculatorImpl");
		arithmeticCalculator.add(3, 3);
		System.out.println("==========================");
		arithmeticCalculator.div(6, 3);
	}
}

运行结果:
The method add begins [3, 3]
6
The method add end
==========================
The method div begins [6, 3]
2
The method div end
后置通知代码示例2:(有异常)
public static void main(String[] args) {
		ApplicationContext app=new ClassPathXmlApplicationContext("aop-impl.xml");
		ArithmeticCalculator arithmeticCalculator=(ArithmeticCalculator) app.getBean("arithmeticCalculatorImpl");
		arithmeticCalculator.add(3, 3);
		System.out.println("==========================");
		arithmeticCalculator.div(6, 0);//被除数为0,会抛算术异常(ArithmeticException)
	}
测试结果:

The method add begins [3, 3]
6
The method add end
==========================
The method div begins [6, 0]
The method div end
Exception in thread "main" java.lang.ArithmeticException: / by zero
从上面的测试结果中可以看到,就算有异常后置通知依然执行。

返回通知,使用returning访问目标方法的返回值

//返回通知,正常方法执行之后执行的通知,能够访问到目标方法的返回值
@AfterReturning(value="execution(public int com.study.aop.impl.ArithmeticCalculator.*(int, int))",returning="result")//表示作用于 ArithmeticCalculator类中的 所有方法
public void afterReturning(JoinPoint joinPoint,Object result){
	String methodName = joinPoint.getSignature().getName();//利用JoinPoint访问方法名 
	System.out.println("The method "+methodName+" end "+result);
} 
测试类:

public static void main(String[] args) {
		ApplicationContext app=new ClassPathXmlApplicationContext("aop-impl.xml");
		ArithmeticCalculator arithmeticCalculator=(ArithmeticCalculator) app.getBean("arithmeticCalculatorImpl");
		arithmeticCalculator.mul(3, 3);}
测试结果:

The method mul end 9

异常通知,在目标方法出现异常时会执行的通知。可以访问到异常对象,且可以指定在出现特定异常时再执行异常通知

使用throwing表示目标方法遇到的异常

/**
* 异常通知,在目标方法出现异常时会执行的通知。
* 可以访问到异常对象,且可以指定在出现特定异常时再执行异常通知
*/
@AfterThrowing(value="execution(public int com.study.aop.impl.ArithmeticCalculator.*(int, int))",throwing="ex")//表示作用于 ArithmeticCalculator类中的 所有方法
public void afterThrowing(JoinPoint joinPoint,Object ex){
	String methodName = joinPoint.getSignature().getName();//利用JoinPoint访问方法名 
	System.out.println("The method "+methodName+" occurs "+ex);
} 

测试代码:

public class Test {
	public static void main(String[] args) {
		ApplicationContext app=new ClassPathXmlApplicationContext("aop-impl.xml");
		ArithmeticCalculator arithmeticCalculator=(ArithmeticCalculator) app.getBean("arithmeticCalculatorImpl");
		arithmeticCalculator.div(6, 0);
	}
}
测试结果:

The method div occurs java.lang.ArithmeticException: / by zero

以上四种通知的执行位置如下图所示:

上图也可以在环绕通知中体现出来。

环绕通知,需要携带ProceedingJoinPoint类型的参数。环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。且环绕通知必须要有返回值,返回值即为目标方法的返回值。

/**
	 * 环绕通知,需要携带ProceedingJoinPoint类型的参数。
	 * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
	 * 且环绕通知必须要有返回值,返回值即为目标方法的返回值。
	 */
	@Around("execution(public int com.study.aop.impl.ArithmeticCalculator.*(int, int))")//表示作用于 ArithmeticCalculator类中的 所有方法
	public Object around(ProceedingJoinPoint pjp){
		Object result=null;
		String methodName = pjp.getSignature().getName(); 
		List<Object> args = Arrays.asList(pjp.getArgs());
		try{
			//前置通知
			System.out.println("The method "+methodName+" begins "+args);
			//执行目标方法
			result=pjp.proceed();
			//返回通知
			System.out.println("The method "+methodName+" end "+result);
		}catch(Throwable e){
			//异常通知
			System.out.println("The method "+methodName+" occurs "+e);
			throw new RuntimeException();
		}
		//后置通知
		System.out.println("The method "+methodName+" end");
		return result;
	} 

5.切面的优先级

当需要为一个目标对象声明多个切面时,就存在哪个切面先执行的问题,我们可以使用@order(i)注解来定义优先级,i的值越小表示优先级越高,就越先执行。

@Order(1)//设置优先级
@Aspect
@Component
public class LoggingAspect {

}
6.使用@PointCut注解来声明切入点表达式
定义一个方法用于声明切入点表达式,一般地,该方法中不需要再添加其他代码

/**
* 定义一个方法用于声明切入点表达式,一般地,该方法中不需要再添加其他代码
* 使用@PointCut注解来声明切入点表达式
*/
@Pointcut("execution(public int com.study.aop.impl.ArithmeticCalculator.*(int, int))")
public void declareJointPointExpression(){}
这样其他通知中就可以直接引用该方法名即可指定切入点表达式

@Before("declareJointPointExpression()")
public void before(JoinPoint joinPoint){
	String methodName = joinPoint.getSignature().getName();//利用JoinPoint访问方法名 
	List<Object> args = Arrays.asList(joinPoint.getArgs());//利用JoinPoint访问方法参数 
	System.out.println("The method "+methodName+" begins "+args);
} 


六、使用AspectJ基于xml配置的方式开发Spring AOP
将上文代码中的注解去掉,xml文件配置如下:
<!--配置bean  -->
<bean id="arithmeticCalculator" class="com.study.aop.xml.ArithmeticCalculatorImpl"></bean>
<!--配置切面  -->
<bean id="loggingAspect" class="com.study.aop.xml.LoggingAspect"></bean>

<!--配置AOP  -->
<aop:config>
	<!--配置切点表达式 -->
	<aop:pointcut expression="execution(* com.study.aop.xml.ArithmeticCalculator.* (..))" 
		id="pointcut"/>
	<!--配置切面及通知  -->
	<aop:aspect ref="loggingAspect" order="1">
		<!--前置通知  -->
		<aop:before method="before" pointcut-ref="pointcut" arg-names="JoinPoint"/>
		<!--后置通知  -->
		<aop:after method="after" pointcut-ref="pointcut" arg-names="JoinPoint"/>
		<!--返回通知  -->
		<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
		<!--异常通知 -->
		<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
		<!--环绕通知  -->
		<aop:around method="around" pointcut-ref="pointcut" arg-names="ProceedingJoinPoint"/>
	</aop:aspect>
</aop:config>
运行结果与基于注解的方式相同。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值