前置知识

AOP是基于代理模式中动态代理实现的,而动态代理是基于反射实现的。

AOP

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

为什么需要 AOP

想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。

AOP 实现分类

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,看到这其实应该明白了,AOP 其实就是前面一篇文章讲的代理模式的典型应用。
按照 AOP 框架修改源代码的时机,可以将其分为两类:

  • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
类别 机制 原理 优点 缺点
静态AOP 静态织入 在编译起,切面直接以字节码的形式编译到目标字节码文件中 对系统无性能影响 灵活性不够
动态AOP JDK动态代理 在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中 相对静态AOP更加灵活 切入的关注点需要实现接口。对系统一点性能影响
动态字节码生成 GCLIB 在运行期,目标类加载后,动态生成目标类的子类,将切面逻辑加入到子类中 没有接口也可以织入 扩展类的实例方法用final修饰时,无法织入
自定义类加载器 在运行期,目标类加载前,将切面逻辑加到目标字节码里 可以对绝大部分类进行织入 代码中如果使用了其他类记载其,则这些类将不会织入
字节码转换 在运行期,所有类加载器加载字节码前进行拦截 可以对所有类进行记载 麻烦而已

AOP术语

  • 通知(Advice):AOP框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 连接点(JoinPoint):连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在springAOP中,连接点总是方法的调用。
  • 切点(PointCut):可以插入增强处理的连接点。
  • 切面(Aspect):切面是通知和切点的结合。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

springAOP中,通过Advice定义横切逻辑,spring中支持5中类型的Advice:
|通知类型|连接点|实现接口|
|-|-|-|
|前置通知|方法前|org.springframework.aop.MethodBeforeAdvice|
|后置通知|方法后|org.springframework.aop.AfterReturningAdvice|
|环绕通知|方法前后|org.aopalliance.intercept.MethodInterceptor|
|异常抛出通知|方法抛出异常|org.springframework.aop.ThrowsAdvice|
|引介通知|类中增加新的方法属性|org.springframework.aop.IntroductionInterceptor|

springAOP

springAOP特点

spring的AOP是通过动态代理实现的,默认是JDK动态代理,也支持CGLIB。springAOP不能拦截对对象字段的修改,也不支持构造器连接点,无法在Bean创建时应用通知。

代码示例

这里用maven进行构建,依赖如下。

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 包含了aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>
<!-- 支持切点表达等的,包含了aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>

业务接口

1
2
3
4
5
6
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}

业务实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("add a User");
}

@Override
public void delete() {
System.out.println("delete a User");
}

@Override
public void update() {
System.out.println("update a User");
}

@Override
public void select() {
System.out.println("select a User");
}
}

使用spring的API接口

实现类继承spring api

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 Log implements MethodBeforeAdvice,AfterReturningAdvice {

@Override
/**
* method:目标方法
* objects:参数
* o:目标对象
*/
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了!");
}

@Override
/**
* o:返回值
* method:目标方法
* objects:参数
* o1:目标对象
*/
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(o1.getClass().getName()+"的"+method.getName()+"被执行了!并返回了:"+o);
}
}

配置文件,配置aop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 注册 bean -->
<bean id="userService" class="com.snmlm.service.impl.UserServiceImpl"/>
<bean id="log" class="com.snmlm.log.Log"/>

<!-- 第一种方式:使用原生spring api接口 -->
<!-- 配置aop 需要aop约束 -->
<aop:config>
<!-- 切入点 expression:切入点表达式 execution(返回值类型 类.方法(参数))-->
<aop:pointcut id="pointcut" expression="execution(* com.snmlm.service.impl.UserServiceImpl.*(..))"/>
<!--环绕模式 根据代码-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
</beans>

测试

1
2
3
4
5
6
7
public class ApiTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("apicontext.xml");
UserService userService = (UserService)context.getBean("userService");
userService.add();
}
}

结果输出

com.snmlm.service.impl.UserServiceImpl的add被执行了!
add a User
com.snmlm.service.impl.UserServiceImpl的add被执行了!并返回了:null

自定义切面实现AOP

自定义切面方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DiyPointCut {
public void before(){
System.out.println("方法前执行");
}
public void after(){
System.out.println("方法后执行");
}
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Object result = jp.proceed();
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
25
26
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 注册 bean -->
<bean id="userService" class="com.snmlm.service.impl.UserServiceImpl"/>
<bean id="diy" class="com.snmlm.diy.DiyPointCut"/>

<!-- 第二种:自定义-->
<aop:config>
<!-- 切面 -->
<aop:aspect ref="diy">
<!-- 切点-->
<aop:pointcut id="point" expression="execution(* com.snmlm.service.impl.*.*(..))"/>
<!-- 通知 -->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point" />
<aop:around method="around" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>

测试类

1
2
3
4
5
6
7
public class DiyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("diycontext.xml");
UserService userService = (UserService)context.getBean("userService");
userService.add();
}
}

结果

方法前执行
环绕前
add a User
环绕后
方法后执行

使用注解实现AOP

定义切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Aspect //标志为切面
public class AnnotationPointCut {
@Before("execution(* com.snmlm.service.impl.*.*(..))")
public void before(){
System.out.println("方法前执行");
}
@After("execution(* com.snmlm.service.impl.*.*(..))")
public void after(){
System.out.println("方法后执行");
}
@Around("execution(* com.snmlm.service.impl.*.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Object result = jp.proceed();
System.out.println("环绕后");
}
}

配置切面,开启注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 注册 bean -->
<bean id="userService" class="com.snmlm.service.impl.UserServiceImpl"/>
<bean id="annotationPointCut" class="com.snmlm.annotation.AnnotationPointCut"/>

<!-- 第三种:注解-->
<!-- 开启AOP注解 -->
<!-- false为jdk动态代理,true为cglib -->
<aop:aspectj-autoproxy expose-proxy="false"/>

</beans>

测试类

1
2
3
4
5
6
7
public class AnnotationTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("annotationcontext.xml");
UserService userService = (UserService)context.getBean("userService");
userService.add();
}
}

结果

环绕前
方法前执行
add a User
方法后执行
环绕后