spring AOP学习笔记

时间:2021-10-06 10:01:06

参考spring手册:https://docs.spring.io/spring/docs/4.3.17.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#aop

aop是面向切面编程,它是对oop面向对象编程的一个补充,aop不是spring框架特有的,它有独立的框架,比如很强大的AspectJ,spring只是引入了对aop共的支持,aop是对spring的ioc容器的一个补充,对解决企业级问题提供更全面的方案。下面记录一下spring aop的使用方式

1.启动spring对aop的支持

  在这之前需要确保AspectJ的aspectjweaver.jar包位于您的应用程序的类路径上(版本1.6.8或更高版本)。该jar包可在AspectJ的“lib”目录中或通过Maven*存储库中找到。

  注解配置方式:

/**
通过@Configuration和@EnableAspectJAutoProxy注解来开启对aop编程的支持
*/
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

  xml配置方式:

<aop:aspectj-autoproxy/>

2.声明一个切面

  注解配置方式:

/**通过@Aspect来声明一个切面*/
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

  xml配置方式:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of aspect here as normal -->
</bean>

3.声明一个切入点

  注解配置方式:

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

 xml配置方式:

<aop:pointcut id="propertyAccess" expression="execution(* get*())"/>


Spring AOP中支持的切入点的类型有:
1.execution—用于匹配方法执行连接点,这是在使用Spring AOP时将使用的主要切入点指示器。
2.within---在某些类型中,限制匹配到连接点(简单地说,在使用Spring AOP时,在匹配类型中声明的方法的执行)
3.this--- 限制匹配连接点(在使用Spring AOP时执行方法),其中bean引用(Spring AOP代理)是给定类型的实例。
4.target---限制匹配的连接点(在使用Spring AOP时执行方法),目标对象(被代理的应用程序对象)是给定类型的实例。
5.args ---限制匹配的连接点(在使用Spring AOP时执行方法),其中的参数是给定类型的实例。
6.@target---限制匹配的连接点(在使用Spring AOP时执行方法),其中执行对象的类具有给定类型的注释。
7.@args ---限制匹配到连接点(使用Spring AOP时的方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释
8.@within ---限制对具有给定注释的类型的连接点进行匹配(在使用Spring AOP时,使用给定注释的类型执行的方法的执行)
9.@annotation ---限制匹配到连接点(在Spring AOP中执行的方法)的主题具有给定的注释。

 

4.编写切入点表达式

  注解配置方式:

/**表达式中可以使用&&,||,!来连接表达式*/

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

  例子:定义一个公共的切面来解决模块引用等问题

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.someapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.someapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.someapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
     * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

  关于切入点表达式的格式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)throws-pattern?)
这里用的切入点类型是execution,因为这是我们经常用到的一种

  除了返回类型模式(上面代码片段中的ret类型模式)、名称模式(nam)和参数模式(param)之外,所有部分都是可选的。返回的类型模式决定了要匹配的连接点的返回类型必须是什么。大多数情况下,您将使用*作为返回类型模式,它匹配任何返回类型。只有当方法返回给定类型时,完全限定类型名称才会匹配。名称模式与方法名相匹配。您可以使用*通配符作为名称模式的全部或部分。如果指定声明类型模式,则包括尾随。将其连接到名称模式组件。参数模式稍微复杂一些:()匹配一个不带参数的方法,而(..)匹配任意数量的参数(零个或多个)。模式(*)匹配任何类型的一个参数,(*,String)匹配一个方法取两个参数,第一个参数可以是任何类型,第二个参数必须是字符串。

  切入点表达式的例子

//匹配所有public方法
execution(public * *(..))

//匹配所有方法名以set开头的方法
execution(* set*(..))

//匹配AccountService接口下的所有方法
execution(* com.xyz.service.AccountService.*(..))

//匹配com.xyz.service包(不包括子包)下的所有方法
execution(* com.xyz.service.*.*(..))

//匹配com.xyz.service包及其子包下的所有方法
execution(* com.xyz.service..*.*(..))

//服务包内的任何连接点(只在Spring AOP中执行):
within(com.xyz.service.*)

//服务包或子包中的任何连接点(只在Spring AOP中执行):
within(com.xyz.service..*)

//实现AccountService接口的代理类的任何连接点:
this(com.xyz.service.AccountService)

//实现AccountService接口的目标类的任何连接点:
target(com.xyz.service.AccountService)

//任何一个连接点(只在Spring AOP中执行),它只接受一个参数,并且在运行时传递的参数是可序列化的:(注意它和execution(* *(java.io.Serializable))是不一样的,它强调的是参数类型,而args更强调的是运行时可序列化,)
args(java.io.Serializable)

//目标对象具有@Transactional注解的任何连接点:
@target(org.springframework.transaction.annotation.Transactional)

//其中执行方法有@Transactional注解的任何连接点
@within(org.springframework.transaction.annotation.Transactional)

//它只接受一个参数,并且通过的参数的运行时类型有@Classified注解的任何连接点
@args(com.xyz.security.Classified)

//在一个名为tradeService的Spring bean上的任何连接点
bean(tradeService)

//任何与通配符表达式*Service匹配的Spring bean上的连接点
bean(*Service)

 

5.编写通知

  首先说一下通知的分类

前置通知:在连接点之前执行的通知,但是它没有阻止执行流到连接点的能力(除非它抛出一个异常)。
后置通知:连接点完成后要执行的通知:例如,如果方法返回而不抛出异常。
异常通知:如果方法通过抛出异常,通知将被执行。
最后必然执行的通知:无论连接点退出(正常或异常返回)的方法,都要执行通知。
环绕型通知:围绕一个连接点的通知,例如方法调用。这是最有力的建议。环绕型通知可以在方法调用前后执行定制的行为。它还负责选择是否继续进行连接点,还是通过返回自己的返回值或抛出异常来快捷地执行建议的方法执行。

注意:虽然环绕型通知能够完成前其他通知的功能,但是我们为避免不必要的错误,应该根据需求选择功能最小的通知类型

  前置通知示例:(使用@Before注解)

  

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }

}

  后置通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }

}

  异常通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }

}

  最终通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}

  环绕型通知

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public 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;
    }

}