面向切面编程——Spring AOP

1.基本概念

  • 切面(Aspect): 切入业务流程的一个独立模块,一般是将和核心业务逻辑无关的公用功能抽出来作为切面,横切入业务代码需要的地方。spring中的声明式事务管理就是一种实现,日志、权限校验也是不错的应用场景。可以使用注解@Aspect标识某个类是切面类。

  • 连接点(Join point): 就是和实际业务的哪个点切入,这个点就叫连接点。在Spring AOP中,一个连接点就是一个具体的方法(如UserService.login(…)),因为Spring只支持方法类型的连接点。

  • 通知或者可以叫增强(Advice): 即指切面的行为发生在连接点执行的什么时刻。一般常用前置通知(before)、后置(通知)、环绕通知(around)等,具体将在后面介绍。

  • 切入点(Pointcut): 可以看做是匹配的连接点的集合,通常使用切入点表达式,比如某个类的所有公用方法。

  • 引入(Introduction): Declaring additional methods or fields on behalf of a type. 简单理解就是在不改变原来代码的前提下,提供一个切面接口和实现,通过引入的方式将这个切面的功能加到原来的业务逻辑上。

  • 目标对象(Target object): 即需要被增强或者说通知(Advise)的对象,这个对象一般都是会被代理的,不会直接使用。

  • 代理对象(AOP proxy): 通过AOP框架创建的对象,负责代理目标对象,对外提供服务。在 Spring Framework中,AOP代理有两种:JDK 动态代理和CGLIB代理。目标类是接口实现类的时候采用JDK动态代理,因为代理类会继承Proxy类并且Java是单继承。CGLIB是通过操作字节码继承目标类实现的代理,可以设置强制使用CGLIB代理不适用JDK代理。

  • 织入(Weaving): 将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,Spring AOP在运行时执行编织。

2.Advice通知(增强)类型

  • 前置通知(Before advice): 在执行连接点之前运行,但是它不能阻止执行流程继续进行到连接点(除非它引发异常)。通过注解@Before指定切入点表达式即可。

  • 成功返回后通知(After returning advice): 在连接点正常执行返回后(例如没有异常)执行,通过注解@AfterReturning指定切入点表达式,还可以指定一个返回值形参名returning,代表目标方法的返回值。

  • 抛出异常后通知(After throwing advice): 方法因抛出异常而退出时执行,通过注解@AfterThrowing指定切入点表达式,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象。

  • 后置通知(After (finally) advice): 无论执行连接点是否正常结束,都会执行,通过注解@After指定切入点表达式即可。

  • 环绕通知(Around advice): 在调用连接点方法之前和之后执行,核心是一个ProceedingJoinPoint,通过他去执行连接点。

环绕通知是最通用的一种类型,比如事务,日志等等。但是建议使用功能最弱的类型,以实现切入的行为。例如,如果只需要使用方法的返回值更新缓存,则最好使用返回后的通知而不是环绕,使用最合适的类型可提供更简单的编程模型,并减少出错的可能性。

3.@AspectJ使用

1.通过配置类加@EnableAspectJAutoProxy注解启用AspectJ支持

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

2.使用@Aspect 注解声明切面
With @AspectJ support enabled, any bean defined in your application context with a class that is an @AspectJ aspect (has the @Aspect annotation) is automatically detected by Spring and used to configure Spring AOP.
However, note that the @Aspect annotation is not sufficient for autodetection in the classpath. For that purpose, you need to add a separate @Component annotation.

import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component
public class LogAspect {

}

3.使用@Pointcut注解声明切入点
Spring AOP仅支持Spring Bean的方法执行连接点,因此定义的连接点都是需要匹配具体方法的。切入点的声明包括两部分:一个包含名称和任何参数的签名(比如一个方法),以及一个切入点表达式,该切入点表达式精确确定需要增强的方法。

@Pointcut("execution(* add(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示符(PCD):

  • execution:用于匹配方法执行的连接点。这是使用Spring AOP时要使用的主要切入点指示符(粒度更细到方法)。

  • within:将匹配限制为某些类型内的连接点(使用Spring AOP时,在匹配类型内声明的方法的执行)。

  • this:限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。

  • target:在目标对象(代理的应用程序对象)是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。

  • args:在参数是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。

  • @target:在执行对象的类具有给定类型的注解的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。

  • @args:限制匹配的连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解。

  • @within:将匹配限制为具有给定注解的类型内的连接点(使用Spring AOP时,使用给定注解的类型中声明的方法的执行)。

  • @annotation:将匹配限制在连接点(Spring AOP中正在执行的方法)具有给定注解的连接点上。

可以使用 &&、 || 和 ! 组合切入点表达式,也可以按名称引用切入点表达式。
注:由于AOP使用代理方式实现,JDK只能代理有接口的类,顾只能代理public方法,CGLIB基于子类代理,顾只能代理public和protected方法
execution语法:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
? 表示可以省略部分
modifiers-pattern:修饰符
ret-type-pattern:返回值类型
declaring-type-pattern:声明类型
name-pattern:方法名称
param-pattern:方法参数
throws-pattern:异常类型
*最常用作返回类型模式,它匹配任何返回类型
()匹配不带参数的方法,而(…)匹配任意数量(零个或多个)的参数。的(*)模式任何类型的一个参数的方法匹配。 (*,String)与采用两个参数的方法匹配第一个可以是任何类型,而第二个必须是String

//所用public方法
@Pointcut("execution(public * (..))")
private void anyPublicOperation() {} 
//service包下的所有方法
@Pointcut("within(com.qqxhb.service..)")
private void inService() {} 
//service下的所有public方法
@Pointcut("anyPublicOperation() && inService()")
private void serviceOperation() {} 

一些常用的切入点表达式:
任何公共方法的执行:execution(public * *(…))
名称以set开头的任何方法的执行:execution(* set*(…))
AccountService接口定义的任何方法的执行:execution(* com.qqxhb.service.AccountService.*(…))
service包中定义的任何方法的执行:execution(* com.qqxhb.service…(…))
服务包或其子包之一中定义的任何方法的执行:execution(* com.qqxhb.service…(…))
服务包中的任何连接点(仅在Spring AOP中执行方法):within(com.qqxhb.service.*)
服务包或其子包之一中的任何连接点(仅在Spring AOP中执行方法): within(com.qqxhb.service…*)
代理实现AccountService接口的任何连接点(仅在Spring AOP中是方法执行):this(com.qqxhb.service.AccountService)
目标对象实现AccountService接口的任何连接点(仅在Spring AOP中执行方法):target(com.xyz.service.AccountService)
任何采用单个序列化参数且运行时传递的参数为的连接点(仅在Spring AOP中是方法执行):args(java.io.Serializable)
目标对象带有@Transactional注解的任何连接点(仅在Spring AOP中执行方法):@target(org.springframework.transaction.annotation.Transactional)
执行方法带有@Transactional注解的任何连接点(仅在Spring AOP中为方法执行) :@annotation(org.springframework.transaction.annotation.Transactional)
名称与通配符表达式*Service匹配的Spring bean上的任何连接点:bean(*Service)

4.定义增强类型,增强类型需要和切入点表达式关联。

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AOPExample {
	
	@Pointcut("execution(* com.qqxhb.service..(..))")
	public void pointCut() {}

    @Before("pointCut()")
    public void doAccessCheck() {
        // ...
    }

	@Before("execution(* com.qqxhb.dao..(..))")
    public void doAccessCheck2() {
        // ...
    }


	@AfterReturning("pointCut()")
    public void updateCache() {
        // ...
    }
	
	//限制了只能匹配到返回指定类型,object任意返回值
	@AfterReturning(pointcut="pointCut()",returning="retVal")
    public void updateCache2(Object retVal) {
        // ...
    }

	@AfterThrowing("pointCut()")
    public void errorLog() {
        // ...
    }


	@After("pointCut()")
    public void errorLog() {
        // ...
    }

	@Around("pointCut()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        // 记录开始日志
        Object retVal = pjp.proceed();//执行切入点
        // 记录结束日志
        return retVal;
    }
}

所有通知方法都可以将org.aspectj.lang.JoinPoint申明第方法的第一个参数,不过环绕通知要求声明为它的子类ProceedingJoinPoint,因为需要子类中的proceed()方法去执行连接点。JoinPoint接口提供了一下几个有用的方法:
getArgs(): 返回方法参数
getThis(): 返回代理对象.
getTarget(): 返回目标对象
getSignature(): 返回增强方法的描述信息
toString(): 打印增强方法描述信息

官网文档:Aspect Oriented Programming with Spring

发布了105 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/103251788
今日推荐