Spring 面向切面编程 AOP

Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码 。

Spring AOP 底层实现原理

Spring AOP 底层是通过 JDK 动态代理或者 CGLIB 动态代理技术,为目标 Bean 执行横向织入。

如果目标对象实现了接口,Spring 使用 JDK 动态代理;如果目标对象没有实现接口,Spring 使用 CGLIB 动态代理。

Spring AOP 通知类型

Spring 按照通知 Advice 在目标类方法的连接点位置,可以分为5类:

  • 前置通知 org.springframework.aop.MethodBeforeAdvice :在目标方法执行前实施增强;
  • 后置通知 org.springframework.aop.AfterReturningAdvice :在目标方法执行后实施增强;
  • 环绕通知 org.aopalliance.intercept.MethodInterceptor :在目标方法执行前后实施增强;
  • 异常抛出通知 org.springframework.aop.ThrowsAdvice :在方法抛出异常后实施增强;
  • 引介通知 org.springframework.aop.IntroductionInterceptor: 在目标类中添加一些新的方法和属性。

Spring AOP 切面类型

  • Advisor : 代表一般切面,Advice 本身就是一个切面,对目标类所有方法进行拦截;
  • PointcutAdvisor : 代表具有切点的切面,可以指定拦截目标类哪些方法;
  • IntroductionAdvisor : 代表引介切面,针对引介通知而使用切面。

一般切面

添加依赖

除了 Spring 的四个基础包,还要 AOP 联盟的包和 spring-aop,Junit 和 spring-test 用于进行单元测试:

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
<properties>
<spring.version>4.3.15.RELEASE</spring.version>
...
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

案例代码

定义一个接口及其实现类:

1
2
3
4
5
6
7
8
package com.imtt.aop.demo1;

public interface StudentDao {
public void save();
public void update();
public void delete();
public void find();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.imtt.aop.demo1;

public class StudentDaoImpl implements StudentDao {
@Override
public void save() {
System.out.println("学生保存");
}
@Override
public void update() {
System.out.println("学生修改");
}
@Override
public void delete() {
System.out.println("学生删除");
}
@Override
public void find() {
System.out.println("学生查询");
}
}

Bean 配置文件 applicationContext.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="studentDao" class="com.imtt.aop.demo1.StudentDaoImpl"/>
</beans>

增强实现(以前置通知为例)

MethodBeforeAdvice 接口进行实现:

1
2
3
4
5
6
7
8
9
10
11
package com.imtt.aop.demo1;

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置增强/通知");
}
}

实现 before 方法,这个方法里的内容就是前置通知里会做的事。将这个类在配置文件中配置:

1
2
<!--前置通知-->
<bean id="myBeforeAdvice" class="com.imtt.aop.demo1.MyBeforeAdvice"/>

Spring 使用 ProxyFactoryBean 来配置 AOP 代理对象,常用可配置属性如下:

  • target : 代理的目标对象;

  • proxyInterfaces : 代理要实现的接口,如果多个接口可以使用此格式赋值 :

    1
    2
    3
    4
    <list>
    <value></value>
    ...
    </list>
  • proxyTargetClass : 是否对类代理而不是接口,设置为 true 时,使用 CGLib 代理;

  • interceptorNames : 需要织入目标的 Advice;

  • singleton : 返回代理是否为单实例,默认为单例;

  • optimize : 当设置为 true 时,强制使用 CGLib。

知道了如何配置格式,对示例来进行配置:

1
2
3
4
5
6
7
8
9
10
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置目标类-->
<property name="target" ref="studentDao"/>
<!--实现的接口-->
<property name="proxyInterfaces" value="com.imtt.aop.demo1.StudentDao"/>
<!--拦截的名称-->
<property name="interceptorNames" value="myBeforeAdvice"/>
<!--使用Cglib代理-->
<property name="optimize" value="true"/>
</bean>

单元测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.imtt.aop.demo1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
@Resource(name = "studentDaoProxy")
private StudentDao studentDao;

@Test
public void demo1() {
studentDao.find();
studentDao.save();
studentDao.delete();
studentDao.update();
}
}

这种方式的增强的缺点是对每个方法都进行了增强,即每个方法前面都打印了“前置增强/通知”这段话。

PointcutAdvisor 切点切面

像上面使用普通 Advice 作为切面,将对目标类所有方法进行拦截,不够灵活。实际常采用带有切点的切面。常用 PointcutAdvisor 实现类有两类:

  • DefaultPointcutAdvisor :最常用的切面类型,可以通过任意Pointcut和Advice 组合定义切面;
  • JdkRegexpMethodPointcut: 构造正则表达式切点。

案例使用第二种。

案例代码

先定义一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.imtt.aop.Demo2;

public class CustomerDao {
public void save() {
System.out.println("客户保存");
}
public void update() {
System.out.println("客户修改");
}
public void delete() {
System.out.println("客户删除");
}
public void find() {
System.out.println("客户查询");
}
}

接着在配置文件中配置:

1
<bean id="customerDao" class="com.imtt.aop.Demo2.CustomerDao"/>

增强实现(以环绕通知为例)

MethodInterceptor 接口进行实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.imtt.aop.Demo2;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前增强");
Object object = methodInvocation.proceed();
System.out.println("环绕后增强");
return object;
}
}

实现的 invoke 方法中 methodInvocation.proceed() 使用来执行目标方法的,再其前后就可以定义我们的增强内容。将这个类在配置文件中配置:

1
<bean id="myAroundAdvice" class="com.imtt.aop.Demo2.MyAroundAdvice"/>

接着使用 ProxyFactoryBean 来产生代理对象:

1
2
3
4
5
6
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="customerDao"/>
<!--没有接口要设为true-->
<property name="proxyTargetClass" value="true"/>
<property name="interceptorNames" value="myAdvisor"/>
</bean>

只对某一个或某一些方法进行增强还需要配置一个有切入点的切面:

1
2
3
4
5
6
7
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--pattern中配置正则表达式-->
<!--<property name="pattern" value=".*"/>-->
<!--<property name="pattern" value=".*save.*"/>-->
<property name="patterns" value=".*save.*, .*delete.*"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>

使用了属性 patternpatterns 设置一个方法或多个正则表达式来限制增强。

自动代理

上面的一般切面,每个代理都是通过 ProxyFactoryBean 织入切面代理,但是非常多的 Bean 每个都配置 ProxyFactoryBean 开发维护量巨大,可以使用自动创建代理的方式。

  • BeanNameAutoProxyCreator:根据 Bean 名称创建代理;
  • DefaultAdvisorAutoProxyCreator:根据 Advisor 本身包含信息创建代理;
  • AnnotationAwareAspectJAutoProxyCreator:基于 Bean 中的 AspectJ 注解进行自动代理。

基于 Bean 名称的自动代理

案例要求:对所有以 DAO 结尾 Bean 所有方法使用代理。

配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
<bean id="studentDao" class="com.imtt.aop.demo1.StudentDaoImpl"/>
<bean id="customerDao" class="com.imtt.aop.demo2.CustomerDao"/>

<!--配置增强-->
<bean id="myBeforeAdvisor" class="com.imtt.aop.demo1.MyBeforeAdvice"/>

<!-- 基于Bean名称的自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="myBeforeAdvisor"/>
</bean>

在使用过程中只需对目标属性注入,如 @Resource(name = "studentDao") 就能对所有方法增强。

基于切面信息的自动代理

配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="customerDao" class="com.imtt.aop.demo2.CustomerDao"/>

<!--配置增强-->
<bean id="myAroundAdvisor" class="com.imtt.aop.demo2.MyAroundAdvice"/>

<!--配置切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value="com\.imtt\.aop\.demo2\.CustomerDao\.save"/>
<property name="advice" ref="myAroundAdvisor"/>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

在切面配置中使用了正则,限制了只对 CustomerDao 实现类的 save 方法进行了环绕增强。

坚持原创技术分享,您的支持将鼓励我继续创作!