← Back to list

Spring AOP

Published on: | Views: 81

本文90%来自于https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/core.html#aop

AOP概念

AOP(Aspect-oriented Programming)是对OOP的一个补充,通过切面将原来分散在多个对象中的功能集中到一起进行模块化。 AOP相关概念: - Aspect(切面):跨越多个类的关注点的模块化,在Sprint AOP中是一个普通的类。 - Join point(连接点):程序执行的点,比如方法执行或者异常处理, 在Spring AOP中仅为方法执行。 - Pointcut(切点):符合某种条件的连接点。 - Advice(增强): 切面中针对连接点采取的行为。 - Introduction(引入): 给目标附加额外的方法或者属性。 - Target object(目标对象): 被多个切面增强后的对象,也叫advised object 。 - AOP proxy(AOP代理):由AOP框架创建的,实现切面功能的对象。 - Weaving(织入):联合多个切面和其他应用类型或对象,创建一个advised object的过程。

Spring AOP

Spring AOP的目标是提供AOP功能与IOC功能紧密集成,可以说它是为了增强IOC, 以实现某些通用的企业功能,比如事务。

Spring AOP中advice类型: - before advice: 在调用方法前增强 - after returning advice:在返回结果之后增强 - after throwing advice:在抛出异常之后增强 - after (finally) advice:在返回结果或者抛出异常之后增强 - around advice:完全掌握目标方法的执行,基至可以选择调用或者不调用目标方法

Spring AOP proxy

Spring AOP使用AspectJ切点语言,但是底层是通过代理(JDK或者CGLib)实现运行时织入。 Spring AOP有两种实现切面的方式: - 基于模式(XML) - 基于AspectJ注解

Spring AOP @AspectJ方式使用

引入依赖
compile('org.springframework.boot:spring-boot-starter-aop:版本)
启用@AspectJ支持

配置类上加@EnableAspectJAutoProxy

定义一个Aspect
@Component  //注意要加这个,不然切面不会生效
@Aspect
public class TestAspect {

}
定义切点
@Component  //注意要加这个,不然切面不会生效
@Aspect
public class TestAspect {
    @Pointcut("execution(* com.example.demo.TestService.add(..))")
    private void executionTestServiceAdd(){}

    @Pointcut("within(com.example..*)")
    private void withinComExample(){}

    @Pointcut("@within(TestAnnotation)")
    private void withinTestAnnotation(){}

    @Pointcut("this(com.example.demo.TestService)")
    private void thisTestService(){}

    @Pointcut("target(com.example.demo.TestService)")
    private void targetTestService(){}

    @Pointcut("args(..)")
    private void argsAny(){}
}

切点由标志符(参数)形式组成, Spring AOP中可用标志符如下: - execution: 匹配方法执行连接点。 - within: 要求连接点在指定类型中(Spring AOP中指执行方法所在的类)。 - @within:要求连接点所在的类型拥有指定的注解。 - this: 要求增强后的对象是指定类型。 - target: 要求增强后的对象是指定类型。 - @target: 要求增强后的对象拥有指定的注解。 - args: 要求参数为指定类型。 - @args: 要求参数为拥有指定的注解。 - @annotation: 要求被增强的方法拥有指定注解。 - bean(idOrNameOfBean):要求匹配bean名称或者ID。 切点可以有多个标志符,用 “&&”,"||" 组合,或者用"!"表示取反。 切点也可以使用其他切点进行组合:

    @Pointcut(value = "executionTestServiceAdd() && argsAny()")
    private void combinePointcut() {}

最常用的标志符是execution,它的完整定义是: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) |pattern|是否必选|含义 |--|--|--| |ret-type-pattern|是|返回类型,可以用*号作为通配符 |name-pattern|是|方法名,可以用*号作为通配符 |param-pattern|是|方法参数,()表示无参数,(..)表示任意参数, (*)表示一个参数任意类型,(*,String)表示匹配两个参数 |modifiers-pattern|否|方法修饰符,public, protected等 |declaring-type-pattern|否|类型名,例如[com.xxx.yy.] [com.xx.*.] [com.*.*.] [com..*.] |throws-pattern|否|抛出异常类型

定义切点的增强
    @Before("executionTestServiceAdd()")
    public void beforeAdd() {
        log.info("before add");
    }

    @After("executionTestServiceAdd()")
    public void afterAdd() {
        log.info("after add");
    }

    @AfterReturning(value = "executionTestServiceAdd()", returning = "retVal")
    public void afterReturning(Object retVal) {
        log.info("after returning {}", retVal);
    }

    @Around("executionTestServiceAdd()")
    public Object aroundAdd(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("around add ---1---");
        Object retVal = joinPoint.proceed();
        log.info("around add ---2---");
        return retVal;
    }
参数

有两种方法获取参数,一是使用JointPoint, 二是直接传递参数。

使用JointPoint

所有的advice方法, 都可以将JoinPoint作为第一个参数(@Around是ProceedingJoinPoint),这个参数提供了一些有用的功能: - getArgs(): Returns the method arguments. - getThis(): Returns the proxy object. - getTarget(): Returns the target object. - getSignature(): Returns a description of the method that is being advised. - toString(): Prints a useful description of the method being advised.

使用直接传递

在args标志符中,传入参数的名称(不是参数类型)的时候, Spring AOP会将对应的参数传递给增强函数:

    @Before(value = "executionTestServiceAdd() && args(a,b)")
    public void testAdd2(int a,int b) {
        log.info("before add a={}, b={}",a, b);
    }

或者这样写:

    @Pointcut("args(a,b)")
    private void argsPointcut(int a, int b){}

    @Before(value = "executionTestServiceAdd() && argsPointcut(a,b)")
    public void testAdd2(int a,int b) {
        log.info("before add a={}, b={}",a, b);
    }

除了args可以这样用外, 像this、target、@within、@target、@annotation都可以这样用,例如

    @Before("executionTestServiceAdd() && @target(testAnnotation)")
    public void testParams(TestAnnotation testAnnotation){
        log.info("annotation value={}",testAnnotation.value());
    }
模板参数方法
public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}

可以拦截自己需要的类型

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}
参数名称

确定参数命名步骤: - If the parameter names have been explicitly specified by the user, the specified parameter names are used(use argNames). - 如果没有argsNames, Spring AOP looks at the debug information for the class and tries to determine the parameter names from the local variable table. - 如果没有调试信息,Spring AOP tries to deduce the pairing of binding variables to parameters. - If all of the above strategies fail, an IllegalArgumentException is thrown.

Introductions(引入)

    @DeclareParents(value = "com.example.demo.TestServiceImpl", defaultImpl = TestIntroductionImpl.class)
    public static TestIntroduction xxx;

    @Before("executionTestServiceAdd() && this(testIntroduction)")
    public void beforeAdd(TestIntroduction testIntroduction) {
        testIntroduction.sayHello();
    }

问题

今天有个切面死活不生效,试了各种办法, 后面发现这个切面都不出现在beans列表里面, 最后找到原因是在创建的时候(intelij)选择了aspect文件,这种文件无法被扫描到,即使把aspect修改成class, 文件后缀名还是.aj, 吭了我几个小时。