Spring AOP
AOP面向切面是spring的一项强大功能,通过代理模式方式实现 ,主要的应用需求是实现在单个或多个方法执行之前或之后执行某些特定的操作,应用场景是日志记录、安全控制、事务管理、异常处理等。
在本例中,主要讲解的是如何用注解的方式实现面向切面AOP。spring的面向切面编程有五种通知类型,分别是前置通知、后置通知、环绕通知、返回通知、异常通知,这些通知就是针对切入点方法之前或之后或异常时运行的方法。
前置通知:切入点方法执行之前运行
后置通知:切入点方法执行之后运行
环绕通知:前置通知执行前以及后置通知执行后都会运行
返回通知:环绕通知执行之后运行
异常通知:切入点方法出现异常时运行
第一步:配置spring的bean和aop实现方法
<bean name="userService" class="com.entor.spring.aop.UserServiceImpl"></bean>
<bean name="secureHandler" class="com.entor.spring.aop.SecureHandler"></bean>
<!-- 自动扫描,注解方法实现AOP -->
<aop:aspectj-autoproxy/>
第二步:定义需要切入的类及方法
public interface UserService { public String saveUser(String name,int id); public String saveExt(); }
public class UserServiceImpl implements UserService{ //切入点方法 public String saveUser(String name,int id) { // TODO Auto-generated method stub System.out.println("切入点方法:-----执行UserServiceImpl类saveUser方法 ------返回:"+name+id); return name + id; } //异常通知的测试方法 public String saveExt() { // TODO Auto-generated method stub System.out.println("-----UserServiceImpl saveExt异常通知测试 ------"); int t = 5 / 0; return "SUCCESS"; } }
第三步:定义面向切面的方法
//注解方式配置AOP,声明这是一个面向切面的类
@Aspect
public class SecureHandler {
//allMethod方法不会被执行
/*@Pointcut注解作用是声明需要切入方法的定义
在这里是切入aop包下的所有子包中的所有类以save开头的方法*/
@Pointcut("execution(* com.entor.spring.aop.*.save*(..))")
public void allMethod(){
System.out.println("allMethod");
}
@Before("allMethod()")
public void checkSecure(JoinPoint jp){
System.out.println("前置通知:----------切入点方法名:"+jp.getSignature().getName()+"------------");
}
@After("allMethod()")
public void after(JoinPoint jp){
//System.out.println(jp.getClass());
Object[] arg0 = jp.getArgs();
System.out.println("后置通知:");
System.out.println("\t获取类名:"+jp.getTarget().getClass().getName());
System.out.println("\t获取方法名:"+jp.getSignature().getName());
System.out.print("\t获取参数值:");
for(int i=0;i<arg0.length;i++)
System.out.print(arg0[i].toString());
System.out.println("----注解配置的Logger-----");
}
/**
* 方法正常结束后执行的代码
* 返回通知是可以访问到方法的返回值的
*/
@AfterReturning(value="allMethod()", returning="result")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("返回通知:The method " + methodName + " return with " + result);
}
@Around("allMethod()")
public Object around(ProceedingJoinPoint jp) throws Throwable{
Object[] arg0 = jp.getArgs();
String targetName = jp.getTarget().getClass().getName();
String methodName = jp.getSignature().getName();
System.out.println("环绕通知,前置通知及切入点执行之前:");
System.out.println("\t获取类名:"+targetName);
System.out.println("\t获取方法名:"+methodName);
//这里可以控制切入点方法的执行
Object result = jp.proceed();
System.out.println("环绕通知,切入点及后置通知执行之后:");
for(int i=0;i<arg0.length;i++)
System.out.println("\t入参"+(i+1)+":"+arg0[i].toString());
System.out.println("\t返回值:"+result.toString());
return result;
}
@AfterThrowing(value="allMethod()",throwing="ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex ){
Object object = joinPoint.getSignature();
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String rightnow=sdf.format(date);
System.out.println("异常通知:"+rightnow+"执行了【"+object+"方法发生异常......】"+"【异常报告:"+ex+"】");
System.out.println("xxxxxxxxxxxxxxxxxx方法发生异常结束xxxxxxxxxxxxxxxxxx");
}
}
各注解的作用:
@Aspect :声明这是一个面向切面的类
@Pointcut:声明需要切入方法的定义,告诉程序我们需要切入哪些方法
@Before: 声明前置通知
@After: 声明后置通知
@AfterReturning 声明返回通知
@Around 声明环绕通知
@AfterThrowing: 声明异常通知
前置、后置、返回、异常通知方法可以接收一个org.aspectj.lang.JoinPoint类对象,在方法中可以调用这个类对象的相应方法获取切入点方法的信息,环绕通知会有一点不同,接收的是一个org.aspectj.lang.ProceedingJoinPoint类对象。
getTarget().getClass().getName() :获取切入点方法所在类的类名
getSignature().getName(): 获取切入点方法的方法名
getArgs(): 获取切入点方法的入参数组
环绕通知接收的ProceedingJoinPoint对象不同之处在于它可以调用proceed()方法来控制切入点方法执行与否,调用proceed()切入点方法就会被执行,反之那么切入点方法什么都不会做。
/** * 面向切面AOP的练习,使用了注解方式和xml配置文件方式 * @author max * */ public class AopTest { public static void main(String[] args) { BeanFactory bean = new ClassPathXmlApplicationContext("applicationContext*.xml"); UserService userService = (UserService)bean.getBean("userService"); //savaUser是切入点方法 System.out.println("--------------------执行saveUser方法-----------------------"); userService.saveUser("面向切面",100); System.out.println("\n\n\n--------------------执行saveExt方法-----------------------"); userService.saveExt(); } }
测试类中执行saveUser()和saveExt()两个方法,saveUser()会正常结束,saveExt()会抛出异常,我们可以对比一下两种情况的不同。
--------------------执行saveUser方法----------------------- 环绕通知,前置通知及切入点执行之前: 获取类名:com.entor.spring.aop.UserServiceImpl 获取方法名:saveUser 前置通知:----------切入点方法名:saveUser------------ 切入点方法:-----执行UserServiceImpl类saveUser方法 ------返回:面向切面100 后置通知: 获取类名:com.entor.spring.aop.UserServiceImpl 获取方法名:saveUser 获取参数值:面向切面100----注解配置的Logger----- 环绕通知,切入点及后置通知执行之后: 入参1:面向切面 入参2:100 返回值:面向切面100 返回通知:The method saveUser return with 面向切面100
--------------------执行saveExt方法----------------------- 环绕通知,前置通知及切入点执行之前: 获取类名:com.entor.spring.aop.UserServiceImpl 获取方法名:saveExt 前置通知:----------切入点方法名:saveExt------------ 切入点方法:-----UserServiceImpl saveExt异常通知测试 ------ 后置通知: 获取类名:com.entor.spring.aop.UserServiceImpl 获取方法名:saveExt 获取参数值:----注解配置的Logger----- 异常通知:2016-12-12 03:49:55执行了【String com.entor.spring.aop.UserService.saveExt()方法发生异常......】【异常报告:java.lang.ArithmeticException: / by zero】 xxxxxxxxxxxxxxxxxx方法发生异常结束xxxxxxxxxxxxxxxxxx
对比执行结果可以得出通知的执行顺序
方法运行正常的情况的执行顺序:
1.环绕通知
2.前置通知
3.切入点方法执行
4.后置通知
5.环绕通知
6.返回通知
方法运行出现异常的情况的执行顺序 :
1.环绕通知
2.前置通知
3.切入点方法执行
4.后置通知
5.异常通知
注:环绕通知、返回通知不执行