Spring AOP


AOP基础知识

概念和术语

连接点:应用程序执行期间明确定义的一个点。eg:方法调用、方法调用本身、类初始化、对象实例化;

通知(Advice):在特定连接点执行的代码就是通知。通知类型举例:前置通知(在连接点之前执行),后置通知(在连接点之后执行);

切入点(Pointcut):用于定义何时执行的 连接点集合

切面(Advisor):封装 类中 通知和切入点的集合

织入:在适当的位置 将切面插入到 应用程序 代码中的过程;编译时AOP:在生成时完成运行时AOP:在运行态执行;AspectJ还支持另一种加载时织入(LTW)的植入机制;拦截底层的JVM类加载器,在加载字节码时织入

目标对象(target):由AOP进程修改的对象 称为 目标对象;通常也被称为 被通知对象

引入:通过引入其他方法 来改变 对象结构 的过程。可以通过引入 AOP来使任何对象实现特定的接口,而无需对象的类显式实现该接口;



AOP类型

静态AOP

织入过程 是 应用程序生成的一个步骤;

修改 实际的字节码 来实现 AOP织入过程,最终结果是Java字节码,并且运行时无需特殊技巧确定应该何时执行;

缺点:对切面做的任何修改都需要 重新编译整个程序;

AspectJ的编译时织入 就是 静态AOP;

动态AOP

织入过程 在运行时 动态执行;

缺点:性能不如静态AOP;

优点:可以轻松修改 应用程序的整个切面集,而无需重新编译主应用程序代码;

选择AOP类型

静态AOP和动态AOP各有优点

静态AOP: 实现时间长,倾向于实现更多功能丰富的实现;具有更多的可用连接点;而且如果性能很重要,就倾向于静态AOP;

如果需要SpringAOP未实现的功能 就需要 AspectJ;



Spring AOP示例

Spring AOP实现两个逻辑部分:

1、AOP内核:完全解耦的纯编程 AOP功能;

2、使 AOP 更易于使用的 一组框架服务;

AOP Alliance

许多开源项目共同定制的 一组AOP标准接口。

Spring使用了AOP Alliance 接口

官网:http://aopalliance.sourceforge.net/

AOP Alliance示例

1、pom.xml中引入jar包

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.6.RELEASE</version>
</dependency>

2、定义目标对象

1
2
3
4
5
public class Agent {
public void speak() {
System.out.print("Bond");
}
}

3、定义增强类 AgentDecorator

目标:打印 James Bond!

1
2
3
4
5
6
7
8
9
10
11
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class AgentDecorator implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.print("James ");
Object ret = invocation.proceed();
System.out.println("!");
return ret;
}
}

实现 AOP Alliance 标准接口 MethodInterceptor,围绕 invocation.proceed() 实现 环绕通知(around advice);

4、将invocation.proceed()方法 织入 目标对象 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.aop.framework.ProxyFactory;

public class AgentAOPDemo {
public static void main(String[] args) {
Agent target = new Agent();
target.speak();
System.out.println();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvice(new AgentDecorator());
proxyFactory.setTarget(target);
Agent proxy = ((Agent) proxyFactory.getProxy());
proxy.speak();
}
}

运行结果:

1
2
Bond
James Bond!


Spring AOP架构

Spring AOP的核心架构 基于 代理。

想要创建一个类的被通知实例时,必须使用ProxyFactory创建该类的代理实例

大多数情况下,我们可以使用声明式AOP配置(ProxyFactoryBean、AOP名称空间和@AspectJ样式注解)来完成代理的创建;

但我们需要了解代理的创建过程;因此,首先延时代理创建的编程方法,再讨论 Spring的声明式AOP配置;

运行时,Spring会分析为 ApplicationContext中bean定义的横切关注点,并动态生成代理 bean(封装了底层的目标bean)。不会直接调用目标bean,而是将调用者注入代理bean。然后代理bean分析运行条件,并相应的织入适当的通知。

Spring中的两种代理实现:JDK动态代理、CGLIB代理

Spring中的连接点

Spring只支持一种连接点类型:方法调用;

如果需要其他连接点:则可以使用Spring和AspectJ;

Spring中的切面

在Spring AOP中,切面由实现了 Advisor 接口的类的实例表示

Advisor有两个子接口:PointcutAdvisor 和 IntroductionAdvisor。

关于ProxyFactory类

ProxyFactory 控制 SpringAOP 中织入和代理创建过程;

1
Agent proxy = ((Agent) proxyFactory.getProxy());

ProxyFactory将代理创建委托给 DefaultProxyFactory的一个实例,继而又委托给 ObjenesisCglibAopProxy 或 JdkDynamicAopProxy

1
proxyFactory.addAdvice(new AgentDecorator());

addAdvice将传入的通知封装到 DefaultPointcutAdvisor的一个实例中,并使用默认的方法进行配置

ProxyFactory可以创建多个代理,每个代理都有不同的切面

1
2
3
4
proxyFactory.addAdvice();
proxyFactory.removeAdvice();
proxyFactory.addAdvisor();
proxyFactory.removeAdvisor();

在Spring中创建通知

Spring中支持6中通知

通知名称 接口 描述
前置通知 org.springframework.aop.MethodBeforeAdvice 在连接点之前执行
后置返回通知 org.springframework.aop.AfterReturningAdvice 在连接点return之后 执行
后置通知 org.springframework.aop.AfterAdvice 连接点执行完成之后执行
环绕通知 org.aopalliance.intercept.MethodInterceptor 在连接点前后都执行
异常通知 org.springframework.aop.ThrowsAdvice 在异常执行以后执行,捕获特定异常
引入通知 org.springframework.aop.IntroductionAdvisor 将引入建模为特殊类型的拦截器

通知(Advice)的接口

通知(Advice)和顾问(Advisor)的区别在于:Advisor可以携带相关切入点的通知,从而更细致的控制在哪个连接点上拦截通知。

Advice的层次结构图:

创建前置通知(MethodBeforeAdvice)

创建目标对象:

1
2
3
4
5
6
7
8
public interface Singer {
void sing();
}
public class Guitarist implements Singer {
public void sing() {
System.out.println("You're gonna live forever in me.");
}
}

创建前置通知:

1
2
3
4
5
6
7
8
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class SimpleBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before '" + method.getName() + "' , tune guitar.");
}
}

设置切入点并运行示例:

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.aop.framework.ProxyFactory;

public class SimpleBeforeAdviceDemo {
public static void main(String[] args) {
Guitarist johnMayer = new Guitarist();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvice(new SimpleBeforeAdvice());
proxyFactory.setTarget(johnMayer);
Guitarist proxy = ((Guitarist) proxyFactory.getProxy());
proxy.sing();
}
}

运行结果:

1
2
Before 'sing' , tune guitar.
You're gonna live forever in me.

创建后置返回通知

1
2
3
4
5
6
7
8
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class SimpleAfterReturningAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After "+ method.getName() +" return , return value: " + returnValue);
}
}

创建异常通知(ThrowsAdvice)

ThrowsAdvice内置四个接口,通过反射调用:

1
2
3
4
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException)
public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)

示例:

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
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import java.lang.reflect.Method;

public class SimpleThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("*****");
System.out.println("Caught: " + ex.getClass().getName());
System.out.println("Message: " + ex.getMessage());
}
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
System.out.println("*****");
System.out.println("Caught2: " + ex.getClass().getName());
System.out.println("Message2: " + ex.getMessage());
}

public static void main(String[] args) {
ErrBean errBean = new ErrBean();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(errBean);
proxyFactory.addAdvice(new SimpleThrowsAdvice());
ErrBean proxy = ((ErrBean) proxyFactory.getProxy());
try {
proxy.errMethod();
} catch (Exception e) {
e.printStackTrace();
}
}
}

class ErrBean {
public void errMethod() throws Exception {
throw new Exception("My Exception");
}
}

执行结果:

可以看到只执行了第二个方法,通过Cglib代理;



使用顾问(Advisor)和切入点(Pointcut)

避免硬编码 将方法检查 放入通知,尽可能使用切入点来控制通知目标上方法的适用性;

如果通知跟目标 之间有 目标关联性,则在通知内部检查也可以;

Pointcut接口

1
2
3
4
5
6
7
package org.springframework.aop;

public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
1
2
3
4
5
6
package org.springframework.aop;
// 判断 Pointcut是否适用于特定的方法
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
1
2
3
4
5
6
7
8
9
package org.springframework.aop;
import java.lang.reflect.Method;

public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

MethodMatcher 分为 静态MethodMatcher 和 动态 MethodMatcher;

Spring 通过 isRuntime() 判断 MethodMatcher是 静态(false),还是动态(true);

1、静态切入点

Spring 针对目标上的每个方法,调用一次MethodMatcher的matches(Method, Class<?> )方法,并缓存返回值;

2、动态切入点

Spring先调用 matches(Method, Class<?> )方法进行静态检查,返回true时,再调用 matches(Method, Class<?>, Object[]) 对每个参数进一步检查;

比较

静态Pointcut 执行的比较好,动态Pointcut更灵活;推荐尽量使用静态Pointcut;

使用动态Pointcut 可以 避免 不必要的通知调用;通知调用对性能影响很大;

可用的Pointcut实现

4.0开始Spring提供了八个Pointcut接口的实现:2个用作创建静态、动态切入点的抽象类,6个具体类;

每个具体类可以完成的操作:

  • 一起构成多个切入点

  • 处理控制流切入点

  • 执行简单的基于名称的匹配

  • 使用正则表达式定义切入点

  • 使用AspectJ定义切入点

  • 定义在类或者方法级别查找特定注解的切入点

实现类 描述
org.springframework.aop.support.annotation.
AnnotationMatchingPointcut
在类或方法上查找指定Java注解
org.springframework.aop.aspectj.
AspectJExpressionPointcut
使用AspectJ植入器以AspectJ语法评估切入点表达式
org.springframework.aop.support.
ComposablePointcut
ComposablePointcut类使用诸如union()和intersection()等操作组合两个或更多的切入点
org.springframework.aop.support.
ControlFlowPointcut
ControlFlowPointcut是一种特殊的切入点,他们匹配另一个方法的控制流中的所有方法,即任何作为另一个方法的结果而直接或间接调用的方法
org.springframework.aop.support.
DynamicMethodMatcherPointcut
构建动态切入点的基类
org.springframework.aop.support.
JdkRegexpMethodPointcut
正则表达式支持定义切入点
org.springframework.aop.support.
NameMatchMethodPointcut
创建一个切入点,对方法名称列表执行简单匹配
org.springframework.aop.support.
StaticMethodMatcherPointcut
构建静态切入点的基础

使用DefaultPointcutAdvisor

使用Pointcut之前,必须先创建 Advisor接口的实例,具体的说是创建一个PointcutAdvisor接口;

Advisor是Spring中某个切面的表示,它是 通知 和 切入点 的 结合体;

DefaultPointcutAdvisor 将 点那个Pointcut 和 Advice 相关联;

1、使用 StaticMethodMatcherPointcut 创建静态切入点

思路:创建两个目标对象类GoodGuitarist和GreatGuitarist,通过DefaultPointcutAdvisor创建这两个类的代理,但通知 只应用到GoodGuitarist;

创建两个目标对象类

1
2
3
4
5
6
7
8
9
10
public class GoodGuitarist implements Singer {
public void sing() {
System.out.println("I am GoodGuitarist");
}
}
public class GreatGuitarist implements Singer {
public void sing() {
System.out.println("I am GreatGuitarist");
}
}

创建静态切入点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcut;

import java.lang.reflect.Method;

public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method method, Class<?> cls) {
return "sing".equals(method.getName()); // 只把通知应用到sing()方法
}

@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return clazz == GoodGuitarist.class; // 只将通知应用于GoodGuitarist
}
};
}
}

创建通知

1
2
3
4
5
6
7
8
9
10
11
12
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SimpleAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println(">> Invoke: " + invocation.getMethod().getName());
Object ret = invocation.proceed();
System.out.println("<< Invoke Done!");
return ret;
}
}

使用DefaultPointcutAdvisor组织Pointcut和Advice:

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
import com.learn.aop.Singer;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class StaticPointcutDemo {
public static void main(String[] args) {
GoodGuitarist good = new GoodGuitarist();
GreatGuitarist great = new GreatGuitarist();

Singer goodProxy, greatProxy;
Pointcut pointcut = new SimpleStaticPointcut();
Advice advice = new SimpleAdvice();
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(good);
goodProxy = (Singer) proxyFactory.getProxy();

proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(great);
greatProxy = ((Singer) proxyFactory.getProxy());

goodProxy.sing();
System.out.println();
greatProxy.sing();
}
}

运行结果:

1
2
3
4
5
>> Invoke: sing
I am GoodGuitarist
<< Invoke Done!

I am GreatGuitarist

可以看到只有 GoodGuitarist的sing方法被通知,GreatGuitarist没有被通知;

2、使用DynamicMethodMatcherPointcut创建动态切入点

在本例中,只想通知foo()方法,而且 只有在 int参数 不等于 100 时,才通知该方法

创建目标对象:

1
2
3
4
5
6
7
8
public class SampleBean {
public void foo(int x) {
System.out.println("invoke foo() with: " + x);
}
public void bar() {
System.out.println("invoke bar() ");
}
}

创建针对foo的切入点:

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
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
import java.lang.reflect.Method;

public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
System.out.println("static check for " + method.getName() + "(), calss: " + targetClass.getName());
return "foo".equals(method.getName()); // 针对foo()方法
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object[] args) {
System.out.println("dynamic check for " + method.getName() + "(), calss: " + targetClass.getName());
int x = ((Integer) args[0]).intValue();
return x != 100; // 判断参数是否 等于100
}
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return clazz == SampleBean.class; // 判断类是否 SampleBean
}
};
}
}

创建Advisor组织切入点和通知,调用代理对象:

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 com.learn.aop.pointcut.staticp.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class DynamicPointcutDemo {
public static void main(String[] args) {
SampleBean target = new SampleBean();

Advisor advisor = new DefaultPointcutAdvisor(new SimpleDynamicPointcut(), new SimpleAdvice());

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
SampleBean proxy = ((SampleBean) proxyFactory.getProxy());

proxy.foo(1);
proxy.foo(10);
proxy.foo(100);
proxy.bar();
proxy.bar();
proxy.bar();
}
}

运行结果:

可以看到只有两个foo()被通知到,x=100时没有被通知;bar()没有被通知到;

foo进行了两次静态检查,一次在初始阶段,一次在被调用时;

3、使用简单名称匹配

无需考虑方法签名,只根据方法名匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Guitar {
public String play() {
return "G C GC AM D7";
}
}
public class GrammyGuitarist implements Singer{
@Override
public void sing() {
System.out.println("sing()");
}
public void sing(Guitar guitar) {
System.out.println("play(): " + guitar.play());
}
public void rest() {
System.out.println("zzz~~");
}
public void talk() {
System.out.println("talk()");
}
}

使用NameMatchMethodPointcut匹配:

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
import com.learn.aop.pointcut.staticp.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;

public class NamePointcutDemo {
public static void main(String[] args) {
GrammyGuitarist johnMayer = new GrammyGuitarist();

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("sing");
pointcut.addMethodName("rest");

Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(johnMayer);
proxyFactory.addAdvisor(advisor);

GrammyGuitarist proxy = ((GrammyGuitarist) proxyFactory.getProxy());
proxy.sing();
proxy.sing(new Guitar());
proxy.rest();
proxy.talk();
}
}

运行结果:

4、使用正则表达式创建切入点

创建目标对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.learn.aop.Singer;

public class Guitarist implements Singer {
@Override
public void sing() {
System.out.println("sing()");
}
public void sing2() {
System.out.println("sing2()");
}
public void rest() {
System.out.println("rest()");
}
}

创建Advisor组织切入点和通知,并调用:

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 com.learn.aop.pointcut.staticp.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;

public class RegexPointcutDemo {
public static void main(String[] args) {
Guitarist target = new Guitarist();

JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*sing.*");
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(target);

Guitarist proxy = (Guitarist) proxyFactory.getProxy();
proxy.sing();
proxy.sing2();
proxy.rest();
}
}

运行结果:

5、使用 AspectJ切入点表达式 创建切入点

AspectJ切入点表达式

在pom.xml中添加:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>

使用 AspectJ表达式 实现 JDK正则表达式 相同的功能:

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 com.learn.aop.pointcut.regex.Guitarist;
import com.learn.aop.pointcut.staticp.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class AspectjexpPointcutDemo {
public static void main(String[] args) {
Guitarist target = new Guitarist();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* sing*(..))");
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);

Guitarist proxy = ((Guitarist) proxyFactory.getProxy());
proxy.sing();
proxy.sing2();
proxy.rest();
}
}

运行结果:

6、创建注解匹配切入点

基于注解定义切入点AnnotationMatchingPointcut;

定义注解:

1
2
3
4
5
6
7
8
9
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AdviceRequired {
}

在方法上使用注解:

1
2
3
4
5
6
7
8
9
10
11
12
public class Guitarist implements Singer {
public void sing() {
System.out.println("sing()");
}
@AdviceRequired
public void sing(Guitar guitar) {
System.out.println("play: " + guitar.play());
}
public void rest() {
System.out.println("rest()");
}
}

对注解类织入切面:

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 com.learn.aop.pointcut.name.Guitar;
import com.learn.aop.pointcut.staticp.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;

public class AnnotationPointcutDemo {
public static void main(String[] args) {
Guitarist target = new Guitarist();

AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut
.forMethodAnnotation(AdviceRequired.class);
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);

Guitarist proxy = ((Guitarist) proxyFactory.getProxy());
proxy.sing(new Guitar());
proxy.rest();
}
}

运行结果:



了解代理ProxyFactory

到目前为止,只是粗略的了解了一下 ProxyFactory的使用;

代理的核心:拦截方法调用,必要时 执行适用于特定方法的通知链;

通知的管理和调用 独立于代理,由Spring AOP框架管理。而代理主要负责拦截对方法的调用,并将它们根据需要传递给AOP框架,以便应用通知。

Spring有两种代理:JDK代理、CGLIB代理;

JDK动态代理

只能生成接口的代理,不能生成类的代理;

如何处理特定方法调用的决策 都在 运行时 做出;

无法区分 被通知方法 和 未被通知方法;

1
2
3
4
5
6
7
8
9
10
public static void runJdkTests(Advisor advisor, SimpleBean target) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setInterfaces(SimpleBean.class);

SimpleBean proxy = ((SimpleBean) proxyFactory.getProxy());
System.out.println(">>> Running JDK Tests");
test(proxy);
}

CGLIB代理

CGLIB会为每个代理 动态生成新类的字节码,并尽可能重用已生成的类;

所生成的类 是 目标对象target类的子类

每次创建CGLIB代理时,CGLIB会询问Spring如何处理每个方法;

由于CGLIB生成实际的字节码,因此在处理方法上很灵活;

CGLIB生成固定通知链以后,可以减少执行通知链的运行时间开销;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void runCglibTests(Advisor advisor, SimpleBean target) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setProxyTargetClass(true);

SimpleBean proxy = ((SimpleBean) proxyFactory.getProxy());
System.out.println(">>> Running CGLIB (Standard) Tests");
test(proxy);
}
public static void runCglibFrozenTests(Advisor advisor, SimpleBean target) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setProxyTargetClass(true);
proxyFactory.setFrozen(true);

SimpleBean proxy = ((SimpleBean) proxyFactory.getProxy());
System.out.println(">>> Running CGLIB (Frozen) Tests");
test(proxy);
}

比较代理性能

定义Target:

1
2
3
4
public interface SimpleBean {
void advised();
void unadvised();
}
1
2
3
4
5
6
7
8
9
public class DefaultSimpleBean implements SimpleBean {
private long dummy = 0;
public void advised() {
dummy = System.currentTimeMillis();
}
public void unadvised() {
dummy = System.currentTimeMillis();
}
}

定义通知:

1
2
3
4
5
6
7
8
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class NoOpBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
// 什么都不做
}
}

定义切入点:

1
2
3
4
5
6
7
8
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import java.lang.reflect.Method;

public class TestPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method method, Class<?> targetClass) {
return "advise".equals(method.getName());
}
}

分别调用JDK代理,CGLIB代理,CGLIB Frozen代理,比较他们的性能:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class ProxyPerfTest {
public static void main(String[] args) {
SimpleBean target = new DefaultSimpleBean();

Advisor advisor = new DefaultPointcutAdvisor(new TestPointcut(), new NoOpBeforeAdvice());

runCglibTests(advisor, target);
runCglibFrozenTests(advisor, target);
runJdkTests(advisor, target);
}

public static void runCglibTests(Advisor advisor, SimpleBean target) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setProxyTargetClass(true);

SimpleBean proxy = ((SimpleBean) proxyFactory.getProxy());
System.out.println(">>> Running CGLIB (Standard) Tests");
test(proxy);
}
public static void runCglibFrozenTests(Advisor advisor, SimpleBean target) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setProxyTargetClass(true);
proxyFactory.setFrozen(true);

SimpleBean proxy = ((SimpleBean) proxyFactory.getProxy());
System.out.println(">>> Running CGLIB (Frozen) Tests");
test(proxy);
}
public static void runJdkTests(Advisor advisor, SimpleBean target) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setInterfaces(SimpleBean.class);

SimpleBean proxy = ((SimpleBean) proxyFactory.getProxy());
System.out.println(">>> Running JDK Tests");
test(proxy);
}

private static void test(SimpleBean bean) {
long before = 0;
long after = 0;
System.out.println("Testing advised() Method");
before = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
bean.advised();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + " ms");

System.out.println("Testing unadvised() Method");
before = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
bean.unadvised();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + " ms");

System.out.println("Test equals() method:");
before = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
bean.equals(bean);
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + " ms");

System.out.println("Test hashCode() method:");
before = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
bean.hashCode();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + " ms");

Advised advised = ((Advised) bean);
System.out.println("Test advised.getTargetClass() method:");
before = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
advised.getTargetClass();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after-before) + " ms");

System.out.println("<<<\n");
}
}

运行结果如下:

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
36
37
38
>>> Running CGLIB (Standard) Tests
Testing advised() Method
Took 84 ms
Testing unadvised() Method
Took 57 ms
Test equals() method:
Took 12 ms
Test hashCode() method:
Took 21 ms
Test advised.getTargetClass() method:
Took 9 ms
<<<

>>> Running CGLIB (Frozen) Tests
Testing advised() Method
Took 16 ms
Testing unadvised() Method
Took 51 ms
Test equals() method:
Took 10 ms
Test hashCode() method:
Took 19 ms
Test advised.getTargetClass() method:
Took 10 ms
<<<

>>> Running JDK Tests
Testing advised() Method
Took 130 ms
Testing unadvised() Method
Took 110 ms
Test equals() method:
Took 161 ms
Test hashCode() method:
Took 94 ms
Test advised.getTargetClass() method:
Took 68 ms
<<<

可以看到JDK代理的性能最低,其次,CGLIB Frozen的性能要明显好于 CGLIB;

选择要使用的代理

CGLIB 可以代理类和接口,而JDK只能代理 接口;

CGLIB Frozen模式性能明显好于 JDK代理和CGLIB代理;

如果要使用CGGLIB代理接口,必须使用proxyFactory.setOptimize(true);方法将ProxyFactory的optimize标志设置为true;



切入点的高级使用

前面讲了6个Pointcut的使用,如果如下更大的灵活性,可以使用 ComposablePointcut 和 ControlFlowPointcut

使用控制流切入点ControlFlowPointcut

定义target:

1
2
3
4
5
public class TestBean {
public void foo() {
System.out.println("foo()");
}
}

定义通知:

1
2
3
4
5
6
7
8
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class SimpleBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before method: " + method);
}
}

使用控制流ControlFlowPointcut:

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
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class ControlFlowDemo {
public static void main(String[] args) {
ControlFlowDemo ex = new ControlFlowDemo();
ex.run();
}
public void run() {
TestBean target = new TestBean();
Pointcut pointcut = new ControlFlowPointcut(ControlFlowDemo.class, "test");
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleBeforeAdvice());

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);

TestBean proxy = ((TestBean) proxyFactory.getProxy());
System.out.println("\tTrying normal invoke");
proxy.foo();
System.out.println("\n\tTrying under ControlFlowDemo.test()");
test(proxy);
}
private void test(TestBean bean) {
bean.foo();
}
}

运行结果:

1
2
3
4
5
6
	Trying normal invoke
foo()

Trying under ControlFlowDemo.test()
Before method: public void com.learn.aop.pointcut.controlflow.TestBean.foo()
foo()

关键代码:

1
Pointcut pointcut = new ControlFlowPointcut(ControlFlowDemo.class, "test");

表示 为ControlFlowDemo类的test()方法 创建了一个 ControlFlowPointcut实例, 切入从ControlFlowDemo.test() 调用 target 的所有方法

target 在 test()之外调用时,并不会被通知;只有在test()内调用时,才会被通知

缺点:在其他切入点上使用 ControlFlowPointcut 会大大降低性能

使用组合切入点ComposablePointcut

在前面的实例中,仅为每个Advisor使用了一个切入点。在大多数情况下,这通常是足够的,但有时需要多个Pointcut组合在一起 共同实现目标

ComposablePointcut 支持两种方法:union() 《等价于 “or”运算》和 intersection()《等价于 “and”运算》;

可以通过传入 ClassFilter、MethodMatcher、Pointcut 调用 union() 和 intersection();

使用案例:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import com.learn.aop.pointcut.controlflow.SimpleBeforeAdvice;
import com.learn.aop.pointcut.name.GrammyGuitarist;
import com.learn.aop.pointcut.name.Guitar;
import org.springframework.aop.Advisor;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcher;
import java.lang.reflect.Method;

public class ComposablePointcutExample {
private static class SingMethodMatcher extends StaticMethodMatcher {
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().startsWith("si");
}
}
private static class TalkMethodMatcher extends StaticMethodMatcher {
public boolean matches(Method method, Class<?> targetClass) {
return "talk".equals(method.getName());
}
}
private static class RestMethodMatcher extends StaticMethodMatcher {
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().endsWith("st");
}
}
private static GrammyGuitarist getProxy(ComposablePointcut pointcut, GrammyGuitarist target) {
Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleBeforeAdvice());
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
return ((GrammyGuitarist) proxyFactory.getProxy());
}
private static void testInvoke(GrammyGuitarist proxy) {
proxy.sing();
proxy.sing(new Guitar());
proxy.talk();
proxy.rest();
}
public static void main(String[] args) {
GrammyGuitarist target = new GrammyGuitarist();
ComposablePointcut pointcut = new ComposablePointcut(ClassFilter.TRUE, new SingMethodMatcher());
// 匹配si开头的方法
System.out.println("Test 1 >>> ");
GrammyGuitarist proxy = getProxy(pointcut, target);
testInvoke(proxy);
System.out.println();
// 匹配方法名为talk的方法
System.out.println("Test 2 >>> ");
pointcut.union(new TalkMethodMatcher());
proxy = getProxy(pointcut, target);
testInvoke(proxy);
System.out.println();
// 匹配st结尾的方法
System.out.println("Test 3 >>> ");
pointcut.intersection(new RestMethodMatcher());
proxy = getProxy(pointcut, target);
testInvoke(proxy);
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
Test 1 >>> 
Before method: public void com.learn.aop.pointcut.name.GrammyGuitarist.sing()
sing()
Before method: public void com.learn.aop.pointcut.name.GrammyGuitarist.sing(com.learn.aop.pointcut.name.Guitar)
play(): G C GC AM D7
talk()
zzz~~

Test 2 >>>
Before method: public void com.learn.aop.pointcut.name.GrammyGuitarist.sing()
sing()
Before method: public void com.learn.aop.pointcut.name.GrammyGuitarist.sing(com.learn.aop.pointcut.name.Guitar)
play(): G C GC AM D7
Before method: public void com.learn.aop.pointcut.name.GrammyGuitarist.talk()
talk()
zzz~~

Test 3 >>>
sing()
play(): G C GC AM D7
talk()
zzz~~
  • “Test 1” 匹配si开头的方法,所以两个 sing() 方法都被通知;

  • “Test 2”匹配si开头的方法 或 匹配方法名为talk的方法,所以两个 sing() 方法 和 talk() 都被通知;

  • “Test 3” 匹配( 以si 开头 || talk()) && 以st 结尾, 没有方法符合,所以没有方法被通知;如果有一个方法名为 si***st() 那么,应该会被调用;

组合和切入点接口

org.springframework.aop.support.Pointcuts 接口也能用来 组合两个Pointcut,但更多Pointcut的组合还是试用ComposablePointcut;



引入入门

引入基础知识

一种特殊类型的环绕通知;

适用于类级别,不能在使用引入时 使用切入点,两者语义不匹配;

实现 接口 IntroductionInterceptor来创建引入:

继承 类 DelegatingIntroductionInterceptor 来创建引入:

引入通知 构成了 被通知对象 状态的一部分;因此,每个被通知对象 都必须有一个独立的引入实例;

使用引入进行对象修改检测

定义接口:

1
2
3
public interface IsModified {
boolean isModified();
}

创建通知Advice类:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
// 创建通知
public class IsModifiedMixin extends DelegatingIntroductionInterceptor
implements IsModified {
private boolean isModified = false;
private Map<Method, Method> methodCache = new HashMap<Method, Method>();

public boolean isModified() {
return isModified;
}

private Method getGetter(Method setter) {
Method getter = methodCache.get(setter);
if (getter != null) {
return getter;
}
String getterName = setter.getName().replace("set", "get");
try {
getter = setter.getDeclaringClass().getMethod(getterName, null);
synchronized (methodCache) {
methodCache.put(setter, getter);
}
return getter;
} catch (NoSuchMethodException e) {
e.printStackTrace();
return null;
}
}

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
if (!isModified) {
if (mi.getMethod().getName().startsWith("set")
&& mi.getArguments().length == 1) {
Method getter = getGetter(mi.getMethod());
if (getter != null) {
Object newVal = mi.getArguments()[0];
Object oldVal = getter.invoke(mi.getThis(), null);
System.out.printf("old:%s, new:%s, %s \n", String.valueOf(oldVal), String.valueOf(newVal), mi.getThis().toString());
if (newVal == null && oldVal == null) {
isModified = false;
} else if (newVal == null && oldVal != null) {
isModified = true;
} else if (newVal != null && oldVal == null) {
isModified = true;
} else {
isModified = !newVal.equals(oldVal);
}
}
}
}
return super.invoke(mi);
}
}

创建顾问Advisor, 关联通知IsModifiedMixin:

1
2
3
4
5
6
7
import org.springframework.aop.support.DefaultIntroductionAdvisor;
// 创建顾问
public class IsModifiedAdvisor extends DefaultIntroductionAdvisor {
public IsModifiedAdvisor() {
super(new IsModifiedMixin());
}
}

创建目标对象Target:

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
// 创建对象target
public class Contact {
private String name;
private String phoneNumber;
private String email;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

组装 Advisor 通知 和 target:

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
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.framework.ProxyFactory;

// 组装 Advisor 通知 和 target
public class IntroductionDemo {
public static void main(String[] args) {
Contact target = new Contact();
target.setName("John Mayer");

IntroductionAdvisor advisor = new IsModifiedAdvisor();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(advisor);
proxyFactory.setOptimize(true); // 使用CGLIB代理接口

Contact proxy = ((Contact) proxyFactory.getProxy());
IsModified proxyInterface = ((IsModified) proxy);
System.out.println("Is Contact? : " + (proxy instanceof Contact));
System.out.println("Is IsModified? : " + (proxy instanceof IsModified));
System.out.println("\nHas Modified? : " + proxyInterface.isModified());
proxy.setName("John Mayer");
System.out.println("\nHas Modified? : " + proxyInterface.isModified());
proxy.setName("Eric Clapton");
System.out.println("\nHas Modified? : " + proxyInterface.isModified());
proxy.setName("Eric Clapton");
System.out.println("\nHas Modified? : " + proxyInterface.isModified());
proxy.setName("John Mayer");
System.out.println("\nHas Modified? : " + proxyInterface.isModified());
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
Is Contact? : true
Is IsModified? : true
Has Modified? : false

old:John Mayer, new:John Mayer, com.learn.aop.inroduction.Contact@7d4793a8
Has Modified? : false

old:John Mayer, new:Eric Clapton, com.learn.aop.inroduction.Contact@7d4793a8
Has Modified? : true

Has Modified? : true

Has Modified? : true

proxy对象 既是 target, 又是IsModified;

proxyInterface.isModified() 第一次调用 查看初始状态John Mayer:未修改;

proxyInterface.isModified() 第二次调用 设为同样的值John Mayer:未修改;

proxyInterface.isModified() 第三次调用 设为新的值Eric Clapton:已经修改;

proxyInterface.isModified() 第四次调用 设为新的值Eric Clapton:已经修改;

proxyInterface.isModified() 第吴次调用 设为新的值Eric Clapton:已经修改;

目前只能检测一次变更;如果去掉 IsModifiedMixin 中的 if (!isModified) 就能检测多次变更;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Is Contact? : true
Is IsModified? : true
Has Modified? : false

old:John Mayer, new:John Mayer, com.learn.aop.inroduction.Contact@7d4793a8
Has Modified? : false

old:John Mayer, new:Eric Clapton, com.learn.aop.inroduction.Contact@7d4793a8
Has Modified? : true

old:Eric Clapton, new:Eric Clapton, com.learn.aop.inroduction.Contact@7d4793a8
Has Modified? : false

old:Eric Clapton, new:John Mayer, com.learn.aop.inroduction.Contact@7d4793a8
Has Modified? : true

引入小结

引入时Spring AOP最强大的 功能之一:不仅可以扩展现有方法的功能; 还可以动态扩展接口和对象实现。



AOP 框架事务

我们不得不编写大量的代码 来生成代理;

Spring提供了额外的框架,将代理注入到目标bean中;

以声明的方式配置AOP

使用Spring AOP 声明式配置时,存在三个选项:

  • 使用ProxyFactoryBean, ProxyFactoryBean提供了一种声明方式配置Spring的ApplicationContext(以及底层的BeanFactory);
  • 使用Spring AOP 命名空间:
  • 使用@AspectJ 样式注解:

使用ProxyFactoryBean

ProxyFactoryBean 允许 指定一个bean作为目标,并且为 该 bean 提供一组通知Advice 和 顾问Advisor(这些Advice 和 Advisor最终被合并到一个AOP代理中);

ProxyFactoryBean 将 拦截器 应用于现有的 目标bean;

使用AOP名称空间



使用 @AspectJ 注解



AspectJ集成