06-WEB技术-Spring-AOP

第06章-WEB技术-Spring-AOP

学习目标

  1. 了解 AOP 的原理
  2. 掌握 AOP 使用过程

AOP 思想

面向切面编程 (AOP) 是对面向对象编程 (OOP)的一种补充。

OOP 中的关键单位是类,而 AOP 中的单位是一个或者多个类中函数的执行过程的某个点。

我们把这个点称为“横切”关注点,也成为了切面。

image-20211015095914656

Spring core 就是一个 AOP 框架。

虽然 Spring IoC 容器不依赖于 AOP(意味着如果您不想使用 AOP,则不需要使用 AOP),但 AOP 补充了 Spring IoC 以提供一个非常强大的中间件解决方案。

使用 AOP 需要 添加额外依赖

1
2
3
4
5
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

Spring AOP 常用术语有**目标对象**(target)、**代理对象**(proxy)、**通知**(advice)、**切点**(pointcut)、**连接点**(Join point)、**切面**(aspect)。当然还有其他术语。

目标对象(target

被一个或多个切面选中的对象。也称为“建议对象”。由于 Spring AOP 是使用运行时代理实现的,所以这个对象始终是一个被代理的对象。

代理对象(proxy

由 AOP 框架创建的对象,用于实现切面方法的执行。在 Spring Framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理。

通知(Advice)

通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。它应该应用在某个方法被调用之 前?之后?之前和之后都调用?还是只在方法抛出异常时调用?

Spring 切面有 5 种类型的通知:

  1. 前置通知Before):在目标方法被调用之前调用通知功能;
  2. 后置通知After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  3. 返回通知AfterReturning):在目标方法成功执行之后调用通知;
  4. 异常通知AfterThrowing):在目标方法抛出异常后调用通知;
  5. 环绕通知Around(ProceedingJoinPoint point)):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。参数 point.proceed();可以调用原方法

连接点(Join point)

简单来说,连接点就是 在目标对象的哪一句代码 处植入额外的切面代码。

连接点是在应用执行过程中能够插入切面的一个点。这个点可 以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

切点(Poincut

切点表示在 where (哪个位置)植入 功能。如:哪些类的哪些函数、有没有返回值的函数,有没有参数的函数等。Spring AOP 使用 @Poincut 注解什么一个切点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Pointcut("execution(* com.demo.*.*(..))")	// 任意访问权限、com.demo.包下任意类下任意函数、任意参数
private void test() {}	// 使用函数表示切点,函数名就是切点名称,切点对应的函数无需任何代码,也可以接受匹配的参数

@Pointcut("execution(public * *(..))") // public 访问权限、任意包下任意类、任意函数、任意参数
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.myapp.trading..*)") // com.xyz.myapp.trading包中的所有类的所有方法
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()") // 引用 上面两个切点
private void tradingOperation() {}

@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {} // 接受 account 参数

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {} // 接受 account 参数

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

  • execution: 用于匹配**方法**执行连接点(这是 Spring AOP 主要方式)。
  • within:用于匹配特定**类**内的连接点(匹配类中方法)。
  • this: 用于匹配**AOP代理对象**是否是某种类型或者其子类。
  • target: 用于匹配**目标对象**是否是某种类型或者其子类。
  • args: 用于匹配**参数**是否是某种类型或者其子类。
  • @target: 用于匹配**目标对象**是否有给定类型的注释。
  • @args: 用于匹配**参数**是否有给定类型的注释。
  • @within: 用于匹配特定**类**否有给定类型的注释。
  • @annotation:用于匹配**方法**具有给定的注释。

AspectJ 类型匹配的通配符

  • *:匹配任何字符;
  • ..:匹配任何重复的字符,如任何个子包;在方法参数中匹配任何数量参数。
  • +:匹配指定类型的子类;仅能作为后缀放在类后边。

如:

1
2
3
4
5
6
7
8
java.lang.String	// 匹配String类型;  
java.*.String		// 匹配java包下的任何“一级子包”下的String类型;  
					// 如 匹配java.lang.String,但不匹配java.lang.ss.String  
java.lang..*				// 匹配java包及任何子包下的任何类型;  
					// 如匹配java.lang.String、java.lang.annotation.Annotation  
java.lang.*ing		// 匹配任何java.lang包下的以ing结尾的类型;  
java.lang.Number+	// 匹配java.lang包下的任何Number的子类;  
					// 如 匹配java.lang.Integer,也匹配java.math.BigInteger  

案例

(1)execution:使用“execution(方法表达式)”匹配方法执行

模式 描述
public * *(..) 任何公共方法的执行
* cn.javass..IPointcutService.*() cn.javass包及所有子包下IPointcutService接口中的任何无参方法
* cn.javass..*.*(..) cn.javass包及所有子包下任何类的任何方法
* cn.javass..IPointcutService.*(*) cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法
* (!cn.javass..IPointcutService+).*(..) 非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法
* cn.javass..IPointcutService+.*() cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法
* cn.javass..IPointcut*.test*(java.util.Date) cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的 如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的;
* cn.javass..IPointcut*.test*(..) throws IllegalArgumentException, ArrayIndexOutOfBoundsException cn.javass包及所有子包下IPointcut前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常
* (cn.javass..IPointcutService+ && java.io.Serializable+).*(..) 任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法
@java.lang.Deprecated * *(..) 任何持有@java.lang.Deprecated注解的方法
@java.lang.Deprecated @cn.javass..Secure * *(..) 任何持有@java.lang.Deprecated@cn.javass..Secure注解的方法
@(java.lang.Deprecated || cn.javass..Secure) * *(..) 任何持有@java.lang.Deprecated@ cn.javass..Secure注解的方法
(@cn.javass..Secure *) *(..) 任何返回值类型持有@cn.javass..Secure的方法
* (@cn.javass..Secure *).*(..) 任何定义方法的类型持有@cn.javass..Secure的方法
* *(@cn.javass..Secure (*) , @cn.javass..Secure (*)) 任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了, 如public void test(@Secure String str1, @Secure String str1);
* *((@ cn.javass..Secure *))或 * *(@ cn.javass..Secure *) 任何带有一个参数的方法,且该参数类型持有@ cn.javass..Secure; 如public void test(Model model);且Model类上持有@Secure注解
* *( @cn.javass..Secure (@cn.javass..Secure *) , @ cn.javass..Secure (@cn.javass..Secure *)) 任何带有两个参数的方法,且这两个参数都被@ cn.javass..Secure标记了;且这两个参数的类型上都持有@ cn.javass..Secure;
* *( java.util.Map<cn.javass..Model, cn.javass..Model> , ..) 任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.javass..Model, cn.javass..Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型; 如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *( java.util.HashMap<cn.javass..Model,cn.javass..Model> , ..)”进行匹配; 而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配
* *(java.util.Collection<@cn.javass..Secure *>) 任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解; 如public void test(Collection<Model> collection);Model类型上持有@cn.javass..Secure
* *(java.util.Set<? extends HashMap>) 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型继承与HashMap; Spring AOP目前测试不能正常工作
* *(java.util.List<? super HashMap>) 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型是HashMap的基类型;如public voi test(Map map); Spring AOP目前测试不能正常工作
* *(*<@cn.javass..Secure *>) 任何带有一个参数的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;Spring AOP目前测试不能正常工作

within:使用“within(类型表达式)”匹配指定类型内的方法执行;

模式 描述
within(cn.javass..*) cn.javass包及子包下的任何方法执行
within(cn.javass..IPointcutService+) cn.javass包或所有子包下IPointcutService类型及子类型的任何方法
within(@cn.javass..Secure *) 持有cn.javass..Secure注解的任何类型的任何方法必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

this:使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;

注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;

注意this中使用的表达式必须是类型全限定名,不支持通配符

模式 描述
this(cn.javass.spring.chapter6.service.IPointcutService) 当前AOP对象实现了 IPointcutService接口的任何方法
this(cn.javass.spring.chapter6.service.IIntroductionService) 当前AOP对象实现了 IIntroductionService接口的任何方法也可能是引入接口

target:使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;

注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;

注意target中使用的表达式必须是类型全限定名,不支持通配符;

模式 描述
target(cn.javass.spring.chapter6.service.IPointcutService) 当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法
target(cn.javass.spring.chapter6.service.IIntroductionService) 当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法不可能是引入接口

args:使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;

注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;

模式 描述
args(java.io.Serializable,..) 任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的

@within:使用“@within(注解类型)”匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名;

模式 描述
@within(cn.javass.spring.chapter6.Secure) 任何目标对象对应的类型持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

@target:使用“@target(注解类型)”匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;注解类型也必须是全限定类型名;

模式 描述
@target(cn.javass.spring.chapter6.Secure) 任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

@args:使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是全限定类型名;

模式 描述
@args(cn.javass.spring.chapter6.Secure) 任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符;

@annotation:使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;

模式 描述
@annotation(cn.javass.spring.chapter6.Secure ) 当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配

bean:使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring ASP扩展的,在AspectJ中无相应概念;

模式 描述
bean(*Service) 匹配所有以Service命名(id或name)结尾的Bean

切面(Aspect)

切面是通知和切点的结合。是写切点的地方,真正写代码的地方,是具体的。切面使用 @Aspect 注解申明。

切面使用流程

  1. 在配置类上启用 aspect 支持,@EnableAspectJAutoProxy
  2. 创建 目标对象(可选)
  3. 注册 目标对象 bean(可选)
  4. 在类上使用@Aspect申明切面
  5. 申明切入点@Pointcut
  6. 申明通知类型BeforeAfterAround
  7. 使用 目标对象,触发通知函数执行

如:

1
2
3
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}

要使用基于 XML 的配置启用 @AspectJ 支持,请使用该aop:aspectj-autoproxy 元素,如以下示例所示:

<aop:aspectj-autoproxy/>

申明切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//声明一个切面,使用AOP
@Aspect
@Component
public class UserAspect {
	// 申明切入点:任何返回值 aop_任意名(任意参数)的方法
	@Pointcut("execution(* com.example.demo.aoptest.*.*(..))")
	public void point1() {}
<span class="hljs-annotation">@Before</span>(<span class="hljs-string">"point1()"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> </span>{
	System.out.println(<span class="hljs-string">"执行前"</span>);
}

<span class="hljs-annotation">@After</span>(<span class="hljs-string">"point1()"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">after</span><span class="hljs-params">()</span> </span>{
	System.out.println(<span class="hljs-string">"执行后"</span>);
}

<span class="hljs-annotation">@AfterReturning</span>(<span class="hljs-string">"point1()"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterReturning</span><span class="hljs-params">()</span> </span>{
    System.out.println(<span class="hljs-string">"返回值以后"</span>);
}

<span class="hljs-annotation">@AfterThrowing</span>(<span class="hljs-string">"point1()"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterThrowing</span><span class="hljs-params">()</span> </span>{
	System.out.println(<span class="hljs-string">"抛出异常之后"</span>);
}

<span class="hljs-comment">//@Around("point1()")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">around</span><span class="hljs-params">(ProceedingJoinPoint point)</span> </span>{
System.out.println(<span class="hljs-string">"环绕前"</span>);
point.proceed();<span class="hljs-comment">//调用元方法</span>
System.out.println(<span class="hljs-string">"环绕后"</span>);
}

}

XML 中声明切面(自学)

在Spring的aop命名空间中,提供了多个元素用来在XML中声明切面

添加名称空间

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop 
        https://www.springframework.org/schema/aop/spring-aop.xsd">
<span class="hljs-comment">&lt;!-- bean definitions here --&gt;</span>

</beans>

声明切面

可以使用<aop:aspect>元素声明方面,并使用ref属性引用支持 bean ,如以下示例所示:

1
2
3
4
5
6
7
8
9
<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
...
</bean>

声明一个切入点

可以在<aop:config>元素内声明一个命名的切入点,让切入点定义在多个方面和顾问之间共享。

代表服务层中任何业务服务执行的切入点可以定义如下:

1
2
3
4
5
6
<aop:config>
<span class="hljs-tag">&lt;<span class="hljs-title">aop:pointcut</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"businessService"</span>
    <span class="hljs-attribute">expression</span>=<span class="hljs-value">"execution(* com.xyz.myapp.service.*.*(..))"</span>/&gt;</span>

</aop:config>

可以在切入点表达式中引用以类型 (@Aspects) 定义的命名切入点。

1
2
3
4
5
6
7
8
9
10
11
<aop:config>
<span class="hljs-tag">&lt;<span class="hljs-title">aop:aspect</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"myAspect"</span> <span class="hljs-attribute">ref</span>=<span class="hljs-value">"aBean"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-title">aop:pointcut</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"businessService"</span>
        <span class="hljs-attribute">expression</span>=<span class="hljs-value">"execution(* com.xyz.myapp.service.*.*(..))"</span>/&gt;</span>

    ...
<span class="hljs-tag">&lt;/<span class="hljs-title">aop:aspect</span>&gt;</span>

</aop:config>

与@AspectJ 方面非常相似,使用基于模式的定义样式声明的切入点可以收集连接点上下文。例如,以下切入点收集this对象作为连接点上下文并将其传递给通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
<aop:config>
<span class="hljs-tag">&lt;<span class="hljs-title">aop:aspect</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"myAspect"</span> <span class="hljs-attribute">ref</span>=<span class="hljs-value">"aBean"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-title">aop:pointcut</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"businessService"</span>
        <span class="hljs-attribute">expression</span>=<span class="hljs-value">"execution(* com.xyz.myapp.service.*.*(..)) &amp;amp;&amp;amp; this(service)"</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-title">aop:before</span> <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"businessService"</span> <span class="hljs-attribute">method</span>=<span class="hljs-value">"monitor"</span>/&gt;</span>

    ...
<span class="hljs-tag">&lt;/<span class="hljs-title">aop:aspect</span>&gt;</span>

</aop:config>

必须通过包含匹配名称的参数来声明通知以接收收集的连接点上下文,如下所示:

1
2
3
public void monitor(Object service) {
    // ...
}

申明通知

before 通知

1
2
3
4
5
6
7
8
<aop:aspect id="beforeExample" ref="aBean">
<span class="hljs-tag">&lt;<span class="hljs-title">aop:before</span>
    <span class="hljs-attribute">pointcut</span>=<span class="hljs-value">"execution(* com.xyz.myapp.dao.*.*(..))"</span>
    <span class="hljs-attribute">method</span>=<span class="hljs-value">"doAccessCheck"</span>/&gt;</span>

...

</aop:aspect>

after-returning 通知

1
2
3
4
5
6
7
8
<aop:aspect id="afterReturningExample" ref="aBean">
<span class="hljs-tag">&lt;<span class="hljs-title">aop:after-returning</span>
    <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"dataAccessOperation"</span>
    <span class="hljs-attribute">method</span>=<span class="hljs-value">"doAccessCheck"</span>/&gt;</span>

...

</aop:aspect>

与@AspectJ 风格一样,您可以在通知正文中获取返回值。为此,请使用该returning属性指定返回值应传递到的参数的名称,如以下示例所示:

1
2
3
4
5
6
7
8
9
<aop:aspect id="afterReturningExample" ref="aBean">
<span class="hljs-tag">&lt;<span class="hljs-title">aop:after-returning</span>
    <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"dataAccessOperation"</span>
    <span class="hljs-attribute">returning</span>=<span class="hljs-value">"retVal"</span>
    <span class="hljs-attribute">method</span>=<span class="hljs-value">"doAccessCheck"</span>/&gt;</span>

...

</aop:aspect>

doAccessCheck方法必须声明一个名为 的参数retVal。此参数的类型以与 中所述相同的方式限制匹配@AfterReturning。例如,您可以按如下方式声明方法签名:

public void doAccessCheck(Object retVal) {...}

after-throwing 通知

当匹配的方法执行通过抛出异常退出时,抛出后通知运行。它是<aop:aspect>通过使用after-throwing元素在 an 内部声明的,如以下示例所示:

1
2
3
4
5
6
7
8
<aop:aspect id="afterThrowingExample" ref="aBean">
<span class="hljs-tag">&lt;<span class="hljs-title">aop:after-throwing</span>
    <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"dataAccessOperation"</span>
    <span class="hljs-attribute">method</span>=<span class="hljs-value">"doRecoveryActions"</span>/&gt;</span>

...

</aop:aspect>

与@AspectJ 风格一样,您可以在建议正文中获取抛出的异常。为此,请使用该throwing属性指定应将异常传递给的参数的名称,如以下示例所示:

1
2
3
4
5
6
7
8
9
<aop:aspect id="afterThrowingExample" ref="aBean">
<span class="hljs-tag">&lt;<span class="hljs-title">aop:after-throwing</span>
    <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"dataAccessOperation"</span>
    <span class="hljs-attribute">throwing</span>=<span class="hljs-value">"dataAccessEx"</span>
    <span class="hljs-attribute">method</span>=<span class="hljs-value">"doRecoveryActions"</span>/&gt;</span>

...

</aop:aspect>

doRecoveryActions方法必须声明一个名为 的参数dataAccessEx。此参数的类型以与 中所述相同的方式限制匹配 @AfterThrowing。例如,方法签名可以声明如下:

public void doRecoveryActions(DataAccessException dataAccessEx) {...}

after 通知

无论匹配的方法执行如何退出,通知都会在(最终)之后运行。您可以使用after元素声明它,如以下示例所示:

1
2
3
4
5
6
7
8
<aop:aspect id="afterFinallyExample" ref="aBean">
<span class="hljs-tag">&lt;<span class="hljs-title">aop:after</span>
    <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"dataAccessOperation"</span>
    <span class="hljs-attribute">method</span>=<span class="hljs-value">"doReleaseLock"</span>/&gt;</span>

...

</aop:aspect>

around 通知

使用aop:around元素声明环绕通知。建议方法的第一个参数的类型必须为ProceedingJoinPoint。在通知的主体,要求proceed()ProceedingJoinPoint导致运行基本方法。该proceed方法也可以使用Object[]. 数组中的值在方法继续执行时用作方法执行的参数。见around通知的注意事项调用proceedObject[]。以下示例显示了如何在 XML 中声明环绕通知:

1
2
3
4
5
6
7
8
<aop:aspect id="aroundExample" ref="aBean">
<span class="hljs-tag">&lt;<span class="hljs-title">aop:around</span>
    <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"businessService"</span>
    <span class="hljs-attribute">method</span>=<span class="hljs-value">"doBasicProfiling"</span>/&gt;</span>

...

</aop:aspect>

doBasicProfiling通知的实现可以与@AspectJ 示例中的完全相同(当然,除了注释),如下面的示例所示:

1
2
3
4
5
6
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}

案例

实体bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package x.y.service;

public interface PersonService {

<span class="hljs-function">Person <span class="hljs-title">getPerson</span><span class="hljs-params">(String personName, <span class="hljs-keyword">int</span> age)</span></span>;

}

public class DefaultPersonService implements PersonService {

<span class="hljs-function"><span class="hljs-keyword">public</span> Person <span class="hljs-title">getPerson</span><span class="hljs-params">(String name, <span class="hljs-keyword">int</span> age)</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title">Person</span><span class="hljs-params">(name, age)</span></span>;
}

}

around 环绕通知回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">profile</span><span class="hljs-params">(ProceedingJoinPoint call, String name, <span class="hljs-keyword">int</span> age)</span> <span class="hljs-keyword">throws</span> Throwable </span>{
    StopWatch clock = <span class="hljs-keyword">new</span> StopWatch(<span class="hljs-string">"Profiling for '"</span> + name + <span class="hljs-string">"' and '"</span> + age + <span class="hljs-string">"'"</span>);
    <span class="hljs-keyword">try</span> {
        clock.start(call.toShortString());
        <span class="hljs-keyword">return</span> call.proceed();
    } <span class="hljs-keyword">finally</span> {
        clock.stop();
        System.out.println(clock.prettyPrint());
    }
}

}

xml 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<span class="hljs-comment">&lt;!-- this is the object that will be proxied by Spring's AOP infrastructure --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">bean</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"personService"</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"x.y.service.DefaultPersonService"</span>/&gt;</span>

<span class="hljs-comment">&lt;!-- this is the actual advice itself --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-title">bean</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"profiler"</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"x.y.SimpleProfiler"</span>/&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-title">aop:config</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-title">aop:aspect</span> <span class="hljs-attribute">ref</span>=<span class="hljs-value">"profiler"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-title">aop:pointcut</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"theExecutionOfSomePersonServiceMethod"</span>
            <span class="hljs-attribute">expression</span>=<span class="hljs-value">"execution(* x.y.service.PersonService.getPerson(String,int))
            and args(name, age)"</span>/&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-title">aop:around</span> <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"theExecutionOfSomePersonServiceMethod"</span>
            <span class="hljs-attribute">method</span>=<span class="hljs-value">"profile"</span>/&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-title">aop:aspect</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-title">aop:config</span>&gt;</span>

</beans>

主函数使用

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;

public final class Boot {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">final</span> String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    BeanFactory ctx = <span class="hljs-keyword">new</span> ClassPathXmlApplicationContext(<span class="hljs-string">"x/y/plain.xml"</span>);
    PersonService person = (PersonService) ctx.getBean(<span class="hljs-string">"personService"</span>);
    person.getPerson(<span class="hljs-string">"Pengo"</span>, <span class="hljs-number">12</span>);
}

}

输出:

1
2
3
4
5
秒表 'Pengo' 和 '12' 的分析:运行时间(毫秒)= 0
-----------------------------------------
ms % 任务名称
-----------------------------------------
00000 ? 执行(getFoo)

Spring Ploxy 代理

SpringAOP实现原理其实很简单,就是通过**动态代理**实现的。如果我们为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。

Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。JDK 动态代理内置于 JDK 中,而 CGLIB 是一个常见的开源类定义库(重新打包成spring-core)。

  • 如果要代理的目标对象至少实现了一个接口,则使用 JDK 动态代理。目标类型实现的所有接口都被代理。
  • 如果目标对象没有实现任何接口,则创建一个 CGLIB 代理。

要强制使用CGLIB代理,将元素的proxy-target-class属性值设置<aop:config>true,如下:

1
2
3
<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

要在使用 @AspectJ 自动代理支持时强制使用 CGLIB 代理,请将元素的proxy-target-class属性设置 <aop:aspectj-autoproxy>true,如下所示:

<aop:aspectj-autoproxy proxy-target-class="true"/>

代理模式

接口

1
2
3
Interface Subject {
    void doAction()
}

实现类

1
2
3
4
public class RealSubject implements Subject{
    @Override
    public void doAction() {};
}

静态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Proxy implements Subject{
       private RealSubject realSubject;
   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Proxy</span><span class="hljs-params">(RealSubject realSubject)</span> </span>{
         <span class="hljs-comment">//关系在编译时确定</span>
        <span class="hljs-keyword">this</span>.realSubject = realSubject;
   }

   <span class="hljs-annotation">@Override</span>
   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doAction</span><span class="hljs-params">()</span> </span>{
         <span class="hljs-comment">// Before</span>
         System.out.println(<span class="hljs-string">"Before"</span>);
         realSubject.doAction();
         <span class="hljs-comment">// After</span>
         System.out.println(<span class="hljs-string">"After"</span>);
   }

}

JDK 动态代理

面向接口

1
2
3
public interface ITarget {
    public void test();
}

目标对象

1
2
3
4
5
6
7
// 目标对象
public class Target implements ITarget{
    // 目标对象的测试方法
    public void test(){
        System.out.println("测试方法");
    }
}

使用代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
    public static void main(String[] args) {
        Target target = new Target();
        ITarget proxy = (ITarget) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // Before
                System.out.println("Before");
                // 调用原方法
                Object invoke = method.invoke(target, args);
                // After
                System.out.println("After");
                return invoke;
            }
        });
        proxy.test();
        System.out.println(target);
        System.out.println(proxy);
    }
}

cglib 代理

目标对象

1
2
3
4
5
public class Target {
    public void test(){
        System.out.println("测试");
    }
}

使用代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Sample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Target.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable
{
System.out.println("before ");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after");
return result;
}
});
Target test = (Target) enhancer.create();
test.test();
}
}

输出

1
2
3
before 
测试
after

spring 动态代理

有一个接口

1
2
3
4
public interface Pojo {
    void foo();
    void bar();
}

有一个普通的、未代理的、没什么特别的、直接的对象引用的场景,如以下代码片段所示:

1
2
3
4
5
6
7
8
9
10
public class SimplePojo implements Pojo {
    public void foo() {
        // 直接的对象引用的场景
        this.bar();
    }
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">bar</span><span class="hljs-params">()</span> </span>{
    System.out.println(<span class="hljs-string">"bar"</span>)
}

}

使用代理

1
2
3
4
5
6
7
8
9
10
11
12
13
public class App {
    public static void main(String[] args) {
        Pojo target = new ProxyPojo();
        ProxyFactory factory = new ProxyFactory(target);
        factory.addInterface(Pojo.class);
    Pojo pojo = (Pojo) factory.getProxy();
    pojo.foo();

    System.out.println(target); <span class="hljs-comment">// com.ProxyPojo@2b552920</span>
    System.out.println(pojo); <span class="hljs-comment">// com.ProxyPojo@2b552920</span>
}

}

输出

1
2
com.star.spring.proxy.Target@6aaa5eb0
com.star.spring.proxy.Target@6aaa5eb0

当客户端代码具有的引用是代理时,情况略有变化。考虑以下图表和代码片段:

aop 代理调用

作业

模拟 AOP

    </article>
posted @ 2021-11-20 17:03  柠檬色的橘猫  阅读(24)  评论(0)    收藏  举报