几天前,本教主接到mentor布置的任务:使用 AOP 来记日志,替代掉重复日志的代码。
本教主自号聪明机智寡言侠义小郎君,此事自然是小Case。
躬身亲做AOP
为了使用 AOP,首先我们得认识到 AOP 是什么对吧,术语原理什么的请翻以前的博文,此番写一下 AOP 的种类。
- 总所周知,spring 是个强大的容器,本身实现spring AOP。
- 除了spring AOP,业内还有个叫 AspectJ 的玩意,它比 spring AOP 强大了很多很多,它有多厉害?它强大到spring AOP只实现了它部分的功能,spring AOP 只能拦截普通方法,AspectJ 却能拦截构造方法和字段。
所以总结地说,下面将会有2种 AOP,一种是 spring AOP,一种是 AspectJ。
一、spring AOP
实现 spring AOP 有两种做法,一种是使用 xml 来定义切面、切点和通知,另一种是使用@Aspect注解(这种做法实际上是前一种的简化版)
使用 xml 来定义切面
此种方法稍微累赘(spring曾因为大量使用xml配置而被诟病,后来慢慢转向使用注解)。
1.首先要写一个切面(aspect):
1 | /** |
这个切面里的 OtaTaskLog就是一个 advice 通知,还记得我之前说过的话吗?
一个切面里,有切入点,有通知,如果切入点匹配到了被代理的函数,就启动通知。
上边代码里的OtaOrderLogAspect类就是个切面,里边的OtaTaskLog方法相当于通知,若是好运,匹配到目标方法,就将其拦截,调用通知后再执行目标方法。
2.其次需要将OtaOrderLogAspect托管给 spring。
1 | <bean id = "logAspect" |
到这里为止,spring 就知道该切面的存在了,那么,该怎么做,才能让 spring 在恰当的代码处启动通知,执行额外的动作呢?答案是使用 spring AOP。
3.使用 spring AOP
使用 spring AOP 特有的 xml 配置,逐个定义 aspect/pointcut/advice,如下:
1 | <aop:config> |
上面的配置中,定义了切面(就是OtaOrderLogAspect类),定义了一个 before通知,该通知对应的切入点是任意类里的任意方法(execution(* *(..))是一种 AspectJ 切入点表达式语言写出来的,该语言可以用来筛选我们想要拦截的方法,具体语法可浏览:http://www.cnblogs.com/javaee6/p/3779826.html),也定义了一个advice 的核心方法(advice分有 before/after/around 等几种),也就是OtaTaskLog,拦截到目标方法后,先执行OtaTaskLog方法,再执行目标方法。
使用@Aspect注解来创建切面
看了上面的那种写法,您是不是觉得有点繁琐呢?每次定义一个切点或切面都要额外写一个 xml 配置,神仙都觉得麻烦。
于是呢,在众多程序员的吐槽中,springAOP 增加了注解的用法,使用注解,完全不需要配置 xml。
1 |
|
使用@Aspect表明该类是切面,使用@Pointcut来过滤目标方法,使用@Around来创建通知。
除了代码,我们需开启 spring 的自动代理:
1 | <aop:aspectj-autoproxy/> |
做了以上两点,在任何一个拥有两个参数的方法前,加上一个@OtaAopLog注解,AOP 就能自动帮您记日志了。
二、AspectJ
AspectJ 的功能比 springAOP 强大多了,比如,AspectJ 可以在类执行构造方法时进行拦截。
为了使用AspectJ,需要声明独特的类型 aspect:
1 | public aspect LogAspect { |
如果要将 bean 注入到LogAspect中,需要特殊的 xml 声明:
1 | <bean class="com.xxx.LogAspect" factory-method="aspectOf"> |
之所以要这么声明,是因为 AspectJ的切面在 AspectJ 启动时就创建了,spring 无法干预,自然也就没法注入。
而使用切面提供的 aspectOf() 方法,就能获取由 AspectJ 创建的切面的实例,进而 spring 可以注入。