相关文章: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(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("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 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>