AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
一 AOP的基本概念
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。
二 Spring AOP
Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。
三 基于注解的AOP配置方式
1.启用@AsjectJ支持
在applicationContext.xml中配置下面一句:
<aop:aspectj-autoproxy />
2.通知类型介绍
(1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
(2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
(3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名
来访问目标方法中所抛出的异常对象
(4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
(5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
3.例子:
(1)Operator.java --> 切面类
@Component@Aspectpublic class Operator {@Pointcut("execution(* com.aijava.springcode.service..*.*(..))") public void pointCut(){}@Before("pointCut()") public void doBefore(JoinPoint joinPoint){System.out.println("AOP Before Advice..."); } @After("pointCut()")publicvoiddoAfter(JoinPoint joinPoint){System.out.println("AOP After Advice..."); } @AfterReturning(pointcut="pointCut()",returning="returnVal")publicvoidafterReturn(JoinPoint joinPoint,Object returnVal){System.out.println("AOP AfterReturning Advice:"+ returnVal); } @AfterThrowing(pointcut="pointCut()",throwing="error")publicvoidafterThrowing(JoinPoint joinPoint,Throwable error){System.out.println("AOP AfterThrowing Advice..."+ error);System.out.println("AfterThrowing..."); } @Around("pointCut()")publicvoidaround(ProceedingJoinPoint pjp){System.out.println("AOP Aronud before...");try{pjp.proceed(); }catch(Throwable e) {e.printStackTrace(); }System.out.println("AOP Aronud after..."); } }
(2)UserService.java --> 定义一些目标方法
@ServicepublicclassUserService{publicvoidadd(){ System.out.println("UserService add()"); }publicbooleandelete(){ System.out.println("UserService delete()");returntrue; }publicvoidedit(){ System.out.println("UserService edit()");inti =5/0; } }
(3).applicationContext.xml
(4).Test.java
publicclassTest{publicstaticvoidmain(String[] args){ ApplicationContext ctx =newClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.add(); }}
上面是一个比较简单的测试,基本涵盖了各种增强定义。注意:做环绕通知的时候,调用ProceedingJoinPoint的proceed()方法才会执行目标方法。
4.通知执行的优先级
进入目标方法时,先织入Around,再织入Before,退出目标方法时,先织入Around,再织入AfterReturning,最后才织入After。
注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用!同时使用也没啥意义。
如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。
5.切入点的定义和表达式
切入点表达式的定义算是整个AOP中的核心,有一套自己的规范
Spring AOP支持的切入点指示符:
(1)execution:用来匹配执行方法的连接点
A:@Pointcut("execution(* com.aijava.springcode.service..*.*(..))")
第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个,上面的第一个..表示service包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个..表示
方法的任意参数个数
B:@Pointcut("within(com.aijava.springcode.service.*)")
within限定匹配方法的连接点,上面的就是表示匹配service包下的任意连接点
C:@Pointcut("this(com.aijava.springcode.service.UserService)")
this用来限定AOP代理必须是指定类型的实例,如上,指定了一个特定的实例,就是UserService
D:@Pointcut("bean(userService)")
bean也是非常常用的,bean可以指定IOC容器中的bean的名称
后言: spring 的环绕通知和前置通知,后置通知有着很大的区别,主要有两个重要的区别:
1) 目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知 是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。
2) 环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用
6.基于XML形式的配置方式
开发中如果选用XML配置方式,通常就是POJO+XML来开发AOP,大同小异,无非就是在XML文件中写切入点表达式和通知类型
例子:
(1)Log.java
publicclassLog{privateIntegerid;//操作名称,方法名privateStringoperName;//操作人privateStringoperator;//操作参数privateStringoperParams;//操作结果 成功/失败privateStringoperResult;//结果消息privateStringresultMsg;//操作时间privateDateoperTime =newDate(); setter,getter}
(2).Logger.java
/**
* 日志记录器 (AOP日志通知)
*/publicclass Logger { @ResourceprivateLogService logService;publicObjectrecord(ProceedingJoinPoint pjp){ Loglog=newLog();try{log.setOperator("admin");Stringmname = pjp.getSignature().getName();log.setOperName(mname);//方法参数,本例中是User userObject[] args = pjp.getArgs();log.setOperParams(Arrays.toString(args));//执行目标方法,返回的是目标方法的返回值,本例中 voidObjectobj = pjp.proceed();if(obj !=null){log.setResultMsg(obj.toString()); }else{log.setResultMsg(null); }log.setOperResult("success");log.setOperTime(newDate());returnobj; }catch(Throwable e) {log.setOperResult("failure");log.setResultMsg(e.getMessage()); }finally{ logService.saveLog(log); }returnnull; }}
(3).applicationContext.xml
注意切入点表达式,!bean(logService) 做日志通知的时候,不要给日志本身做日志,否则会造成无限循环!
如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。
有关更详细的Spring AOP知识,可以查看Spring官方文档第9章Aspect Oriented Programming with Spring
7.JDK动态代理介绍
例子:
(1)UserService.java
public interface UserService { public voidadd();}
(2)UserServiceImpl.java
publicclassUserServiceImplimplementsUserService{publicvoidadd(){ System.out.println("User add()..."); } }
(3)ProxyUtils.java
publicclassProxyUtilsimplementsInvocationHandler{privateObjecttarget;publicProxyUtils(Objecttarget){this.target=target; }publicObjectgetTarget(){returntarget; }publicvoidsetTarget(Objecttarget){this.target=target; }publicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{ System.out.println("do sth before..."); method.invoke(target, args); System.out.println("do sth after...");returnnull; }}
(4)Test.java
publicclassTest{publicstaticvoidmain(String[] args){ UserService userService =newUserServiceImpl(); ProxyUtils proxyUtils =newProxyUtils(userService); UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),UserServiceImpl.class.getInterfaces(), proxyUtils); proxyObject.add(); }}
JDK动态代理核心还是一个InvocationHandler,记住这个就行了。