博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Hello Spring Framework——面向切面编程(AOP)
阅读量:5294 次
发布时间:2019-06-14

本文共 8830 字,大约阅读时间需要 29 分钟。

本文主要参考了Spring官方文档第10章以及第11章和第40章的部分内容。如果要我总结Spring AOP的作用,不妨借鉴文档里的一段话:One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP, meaning you do not need to use AOP if you don’t want to, AOP complements Spring IoC to provide a very capable middleware solution.(译:如果你不想使用AOP完全可以忽略,只单独使用IoC。但是作为重点,Spring AOP框架是对IoC的有力补充)。

***************************以下是正文部分***************************

官方文档对AOP的讲述有点复杂,我稍微修改了一下组织结构,试图从实用性的角度介绍Spring AOP。有些词汇的译法并没有采用公认的翻译。

一、几个重要的概念

切面(Aspect):能够嵌入多个类的模块。Spring AOP框架使用普通类实现切面。

连接点(Join point):正常业务流上的作用点,例如一个方法或一段异常处理。

建言(Advice):在切点上执行的方法。不同类型的建言包括:"around", "before" 和 "after"。

切点(Pointcut):代表了嵌入模块上的作用点。只有切点上才能运行建言。

AOP代理(AOP proxy):SpringAOP通常采用JDK Dynamic Proxy或CGLIB两种方式实现AOP代理。

织入(Weaving):织入的概念是所有AOP框架所共有的。它指将切面对象与建言对象连接的行为。织入行为能够始于编译期(compile time)、加载期(load time)或运行期(runtime)。Spring AOP的织入是在运行期。

二、对于@AspectJ的支持

Spring AOP框架支持@AspectJ风格的注解声明。

AspectJ也是一种AOP框架,它是对JVM的一种嵌入式开发。AspectJ的AOP织入始于编译期。Spring AOP框架仅借鉴了AspectJ提供的注解方案,因此织入依然是在运行期完成的。

(1)通过Maven添加依赖

org.springframework
spring-aspects
4.2.2.RELEASE
maven依赖

注意:使用IDE查询相关依赖关系可以看到spring-aspects仅仅依赖了aspectjweaver包,在有些教材中建议大家引入整个AspectJ包其实是不准确的。对于依赖关系的管理,我的建议是尽量做到精确引入,以防未知异常。

(2)声明对@AspectJ注解的支持

方法一:

@Configuration@EnableAspectJAutoProxypublic class AppConfig {}
AppConfig

方法二:

XML文档声明

方法二是常用的方式,第一种了解即可。

(3)声明切面

package org.xyz;import org.aspectj.lang.annotation.Aspect;@Aspectpublic class AnyClazz {    //...}
增加@Aspect

在XML文档中配置Bean

配置bean

 @Aspect仅代表你希望将类声明为一个切面,如果希望使用Spring提供的包扫描自动初始化还需要增加@Component注解

package org.xyz;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Component@Aspectpublic class AnyClazz {}
支持包扫描的切面注解

(4)声明切点

切点包含三个方面的内容:切点的注解、连接点表达式、切点方法签名

/* * 最常用的声明方式 * @Pointcut代表此方法是一个切点 * 切点的名字是anyMethod * 代表连接点对象的表达式execution(public packagename.*.*(..)) */@Pointcut("execution(public packagename.*.*(..))")public void anyMethod() {    //...}
最常用的声明方式

连接点表达式的前缀有三种,第一种最常见,后面两种知道含义足够

//execution意味着连接点的指定精确到类中的方法@Pointcut("execution(public * *(..))")private void anyPublicOperation() {}//within代表了连接点的指定只精确到包,为包中的所有类和方法都添加代理@Pointcut("within(com.xyz.someapp.trading..*)")private void inTrading() {}//只为符合方法名的连接点添加代理@Pointcut("anyPublicOperation() && inTrading()")private void tradingOperation() {}
连接点表达式前缀

常用的连接点表达式语句

// 所有public方法execution(public * *(..))//所有以set开头的方法execution(* set*(..))//AccountService接口上的所有方法execution(* com.xyz.service.AccountService.*(..))//service包中的所有类上的所有方法(最常用)execution(* com.xyz.service.*.*(..))//service包及其子包里的所有类上的所有方法within(com.xyz.service..*)
连接点表达式语句

(5)声明建言

初学者往往会在学习了建言之后混淆建言、切点和连接点三者的关系。这主要是因为Spring AOP建议将三者放在一条语句中声明。

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class BeforeExample {        // 请注意()中的字符串并不是连接点表达语句而是指声明了@Aspect的方法,dataAccessOperation是切点的签名    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")    public void doAccessCheck() {        // ...    }}
BeforeExample

以上提供的是单独的建言声明,对建言正确的理解应该是:建言对应切点,切点对应连接点。如果将三者合成一条注解声明,可以让代码显得更加紧凑也更加常用。

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class BeforeExample {        // 三者的声明结合成一条注解    @Before("execution(* com.xyz.myapp.dao.*.*(..))")    public void doAccessCheck() {        // ...    }}
BeforeExample

另外几种常用建言举例:

i.正常返回的建言

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.AfterReturning;@Aspectpublic class AfterReturningExample {    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")    public void doAccessCheck() {        // ...    }}
AfterReturning

ii.处理异常的建言

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.AfterThrowing;@Aspectpublic class AfterThrowingExample {    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")    public void doRecoveryActions() {        // ...    }}
AfterThrowing

iii.针对finally的建言

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.After;@Aspectpublic class AfterFinallyExample {    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")    public void doReleaseLock() {        // ...    }}
After

vi.Around建言(重点)

import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.ProceedingJoinPoint;@Aspectpublic class AroundExample {    @Around("com.xyz.myapp.SystemArchitecture.businessService()")    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {        // start stopwatch        Object retVal = pjp.proceed();        // stop stopwatch        return retVal;    }}
Around

Around建言比较特殊,需要用ProceedingJoinPoint对象作为参数。熟悉JDK DynamicProxy的人应该对类似方法不陌生。

(6)为多层建言指定顺序

对某个连接点指定多层建言时就有必要为此提供一个执行顺序,让切面类实现Ordered接口就能指定这个顺序。

package aop.order;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.Ordered;import org.springframework.stereotype.Component;@Component@Aspectpublic class AdvicerFirst implements Ordered {    // 设定的值越小执行顺序越靠前    private final int order = 1;    public int getOrder() {        return order;    }    @Before("execution(* aop.order.*.*(..))")    public void display() {        System.out.println("advicer 1st");    }}
AdvicerFirst
package aop.order;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.core.Ordered;import org.springframework.stereotype.Component;@Component@Aspectpublic class AdvicerThen implements Ordered {    //设定的值越大执行顺序越靠后    private final int order = 10;        public int getOrder() {        return order;    }    @Around("execution(* aop.order.*.*(..))")    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {        System.out.println("advicer then start");        Object retVal = pjp.proceed();        System.out.println("advicer then end");        return retVal;    }}
AdvicerThen

上面只是对Ordered接口的简单演示,实际开发中一般会增加setOrder(int order)方法,再在xml文档中通过IoC容器注入order值。

注入order值

(7)使用CGLIB生成代理

有关JDK DynamicProxy和CGLIB生成代理的差异不在本文的讲解范围。配置CGLIB的支持标签

CGLIB支持标签

由于在Spring 3.2版本之后官方已经不再要求显式配置上述标签,针对JDK DynamicProxy无法实现代理的情况下Spring AOP框架会自动调用CGLIB。因此结论就是,你可以更放心的让Spring去自动生成代理了(感觉文档描述Spring AOP框架对CGLIB的支持就是广告)。

三、基于xml文档的AOP配置

上面详细介绍了如何通过注解来完成AOP代理,但通常情况下我们无法修改源代码或者我们能够获得的是已经经过编译的二进制文件。这种情形下如何使用Spring AOP框架来实现代理呢。

(1)为xml文档添加命名前缀

常用命名前缀

(2)声明切面

...
...
aop:aspect

(3)声明切点

aop:pointcut

(4)声明建言

...
aop:before

除了上面的常规配置手段以外,Spring AOP框架还提供了一种高度集成化的配置方案,但是只适合于方便修改源码的情况下使用。

(5)实现切面接口

i.Around接口

public class DebugInterceptor implements MethodInterceptor {    public Object invoke(MethodInvocation invocation) throws Throwable {        System.out.println("Before: invocation=[" + invocation + "]");        Object rval = invocation.proceed();        System.out.println("Invocation returned");        return rval;    }}
Interception around advice

ii.Before接口

public class CountingBeforeAdvice implements MethodBeforeAdvice {    private int count;    public void before(Method m, Object[] args, Object target) throws Throwable {        System.out.println("Before...");    }    public int getCount() {        return count;    }}
Before advice

iii.AfterReturn接口

public class CountingAfterReturningAdvice implements AfterReturningAdvice {    private int count;    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)            throws Throwable {        System.out.println("After...");    }    public int getCount() {        return count;    }}
After Returning advice

(6)声明顾问(advisor)

对于实现了切面接口的类,只需要在xml文档中使用aop:advisor标签就可以了。

aop:advisor

注意:顾问标签在实际的开发中并不常用,在Spring集成Hibernate的时候会用来声明事务管理。原因正如前文所述,使用切面接口仅限于方便修改源码的情况,而在类似的情况下采用注解方式配置AOP框架才是更合理的选择。所有接口在org.springframework.aop包中,需要了解的可以自己查阅API文档。

四、总结

根据Spring官方文档最后对AOP框架的总结,说明了以下两个问题:

(1)选择Spring AOP还是完整的AspectJ?

基本上如果你打算使用Spring IoC框架那么你就应该采用Spring AOP框架。除非你仅仅是希望使用AOP,而切面对象又不是通过Spring容器产生,那么你只能使用AspectJ。

(2)使用注解还是xml文档配置?

通常情况下两者几乎等价,官方文档的建议是使用xml配置。大概是显得思路清晰,易于修改。采用xml配置的局限性在于生成的切面只能是单例对象,并且无法实现组合式切面配置。

@Pointcut(propertyAccess() && operationReturningAnAccount())public void accountPropertyAccess() {}
组合式切面配置

 

Spring IoC和AOP框架构成了它的底层实现,也是Spring学习的基础。了解这些知识并不能说明你已经精通了Spring,而是说明你可以继续更深入的学习了。

转载于:https://www.cnblogs.com/learnhow/p/5160599.html

你可能感兴趣的文章
关键字
查看>>
Pycharm安装Markdown插件
查看>>
上传图片并预览
查看>>
哈夫曼编码_静态库
查看>>
【转】redo与undo
查看>>
C#更新程序设计
查看>>
常用Request对象获取请求信息
查看>>
解决升级系统导致的 curl: (48) An unknown option was passed in to libcurl
查看>>
Shell命令-内置命令及其它之watch、date
查看>>
Java Session 介绍;
查看>>
spoj TBATTLE 质因数分解+二分
查看>>
Django 模型层
查看>>
第8章-方法
查看>>
dedecms讲解-arc.listview.class.php分析,列表页展示
查看>>
Microsoft SQL Server Transact-SQL
查看>>
Font: a C++ class
查看>>
Extjs6 经典版 combo下拉框数据的使用及动态传参
查看>>
Java四种引用包括强引用,软引用,弱引用,虚引用
查看>>
【NodeJS】http-server.cmd
查看>>
iOS bundle identifier 不一致,target general的Bundle Identifier后面总是有三条灰色的横线...
查看>>