Spring AOP详解& AspectJ表达式

来源:互联网 发布:淘宝卖家售后常见问题 编辑:程序博客网 时间:2024/06/05 15:09

近期项目中用到了Spring AOP,开一篇文章记录我爬过的坑。不知道其他初次接触Spring AOP框架的同学有没有感同身受,Spring AOP中最大的障碍莫过于写AspectJ表达式,稍有不慎便可能出现如下错误。
1.Pointcut is not well-formed: expecting ‘)’ at character position 71 execution(* com.wonking.spring.hello.knights.Knight.emarkQuest(String) && args(s))……

2.warning no match for this type name: s [Xlint:invalidAbsoluteTypeName]……

3.error at ::0 formal unbound in pointcut……
先留个悬念在这里,后面我会对这些问题逐一进行解答,可能出现这些错误的各种情况以及对应的解决方案我都会详细罗列出来。由于本人水平有限,如有错误及不足的地方,还请大家帮忙指正补充。

还是按照我学习的习惯,先上一些概念帮助理解。

AOP基础知识

AOP(Aspect Oriented Programming)面向切面编程。先了解一下Spring 体系中另一大核心设计思想之一,IoC(Inversion of Control),即“控制反转”,依赖注入让相互协作的POJO组件保持松散耦合。而AOP实现了把遍布应用各处的相同的功能代码分离出来,重新组合成一个可重用的组件,它们经常被织入到其他核心业务逻辑组件中去,因为它们总是跨越系统的多个组件,所以这些系统服务通常被称为横切关注点。比如日志记录(我们可能希望在某些函数执行之前,或者在它抛出异常之后等类似的地方打印出一些日志),安全校验(比较典型的应用是在各种网站会员访问某个资源之前检查他有没有相应的权限访问),事务管理(应用在在数据库操作等可能出错需要回滚的场景)。

更多更具体的AOP应用场景如下:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging  调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence  持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务

AOP核心概念

1.通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置(Before)、后置(After)、异常(After-throwing)、返回(After-returning)、环绕通知(Around)五类

2.连接点(joinpoint)

程序执行过程中明确的点,如方法的调用或特定的异常被抛出。因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

3.切入点(pointcut)

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

4.横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

5.切面(aspect)

切面就是对横切关注点的抽象,通知和切入点便是通过切面组织在一起,前者定义了在切面中执行的动作,后者指定了动作应该在何处执行

6.目标对象

被代理的目标对象,切面被应用的对象

7.织入(Weaving)

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

为更形象的理解这些枯燥的名词,我们在这样一个场景中去将它们对应起来

有一个处理用户登录的LoginController,登录需要输入用户名和密码,为了防刷可能还需要一个验证码。拿到请求参数之后,首先检查所有参数是否都有,若存在缺失则抛出一个异常;然后打印参数日志;接下来需要校验用户名规则和密码长度,若校验不通过,则打印用户名错误日志;然后需要对验证码进行核实,若与服务器生成的验证码不符,则打印验证码错误日志;下面再拿用户名和密码进数据库查找,若查无结果,则打印用户名或密码错误日志;登录成后,系统返回一个视图,则打印用户登录日志。

我们将上例中所有需要打印日志的地方组装在一个RecordLog类中,这个类就叫切面(aspect);我们要将这个切面用于LoginController中,LoginController就是目标对象;切点(pointcut)就是LoginController中的各个处理方法,通知就是要切入到切点中的对应的打印日志的方法。切面定义了“做什么”,切点定义了“在何处”,通知定义了“何时做”,并且将切点与切面中的某一个具体的方法绑定在了一起。

AOP实现原理

1.Java SE动态代理:

Java标准类库中提供的代理类,使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。

2.字节码生成(CGLib 动态代理)

动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP。其常用工具是cglib。

3.定制的类加载器

当需要对类的所有对象都添加增强,动态代理和字节码生成本质上都需要动态构造代理对象,即最终被增强的对象是由AOP框架生成,不是开发者new出来的。解决的办法就是实现自定义的类加载器,在一个类被加载时对其进行增强。JBoss就是采用这种方式实现AOP功能。

Spring中使用AOP的几种方法

这里类的定义就不贴出来了,都是简单的POJO,在对应的方法中添加一些System.out.println()方法显示切面效果就行。

1.基于XML配置的实现

<aop:config>        <aop:aspect ref="audience">            <aop:before pointcut="execution(* com.wonking.spring.hello.MusicShow.beforeBegin(..))" method="turnonNotifiction"/>            <aop:after pointcut="execution(* com.wonking.spring.hello.MusicShow.beforeBegin(..))" method="turnoffNotifiction"/>            <aop:before pointcut="execution(* com.wonking.spring.hello.MusicShow.onShow(String))" method="buyTicket" arg-names="name"/>            <aop:after-returning pointcut="execution(* com.wonking.spring.hello.MusicShow.onShow(String))" method="afterSuccess" arg-names="name"/>            <aop:after-throwing pointcut="execution(* com.wonking.spring.hello.MusicShow.onShow(String))" method="afterFail" arg-names="name"/>        </aop:aspect></aop:config>

Question: 为什么我的XML配置完全正确,启动也不报错,但切面总是切不进去,还是普通的方法调用?
Answer: 我碰到的原因是,因为没有给类定义加上@Component注解,而是用自己new出的对象调用其方法。
Explain:自己new出来的对象不处于Spring的上下文环境,不受Spring的统一管理。所以我们必须给每个类加上@Component注解,这样才能把类对象的实例加入到Spring的上下文环境中,这样Spring才能利用AOP将切面织入进去

2.基于注解的实现

@Component("audience")@Aspectpublic class Audience {    @SuppressWarnings("static-access")    public void watchPerform(ProceedingJoinPoint joinPoint,String s){        try{            System.out.println("before performance:"+s);            long start=System.currentTimeMillis();            /*joinPoint.proceed(joinPoint.getArgs());            System.out.println("getKind:"+joinPoint.getKind());            System.out.println("getTarget:"+joinPoint.getTarget());            System.out.println("this class name:"+joinPoint.getThis().getClass().getName());            System.out.println("advice execution:"+joinPoint.ADVICE_EXECUTION);            System.out.println("method call:"+joinPoint.METHOD_CALL);*/            Thread.sleep(100);            long end=System.currentTimeMillis();            System.out.println("clap clap clap");            System.out.println("the performance time last is:"+(end-start));        }catch(Throwable t){            System.out.println("what the fuck");        }    }    // aspectJ表达式中的within不会把父类对象看做子类处理    // aspectJ表达式中的exection会把父类对象看做子类处理    @Pointcut(value="execution(* com.wonking.spring.hello.MusicShow.beforeBegin(..))")    public void begin(){}    @Pointcut(value="execution(* com.wonking.spring.hello.interfaces.Instrument.play(String)) and args(name)")    public void onshow(String name){}    @Before(value="begin()")    public void turnonNotifiction(){        System.out.println("---tickets sold begin---");    }    @After(value="begin()")    public void turnoffNotifiction(){        System.out.println("---tickets sold out---");    }    @Before(value="onshow(name)",argNames="name")    public void buyTicket(){        System.out.println("buying a ticket to see \'s show");    }    @Before(value="onshow(name)",argNames="name")    public void turnOffPhone(){        System.out.println("turning off phone");    }    @AfterReturning(value="onshow(name)",argNames="name")    public void afterSuccess(){        System.out.println("clap clap clap");    }    @AfterThrowing(value="onshow(name)",argNames="name")    public void afterFail(){        System.out.println("what the fuck");    }}
原创粉丝点击