基于XML的AOP开发

本文最后更新于:1 年前

AOP开发步骤:

  1. 创建切面类,在切面类定义切点方法

  2. 将切面类配置给Spring容器

  3. 声明切入点

  4. 配置AOP的通知策略

如果要使用Spring aop面向切面编程,调用切入点方法的对象必须通过Spring容器获取:

  • 如果一个类中的方法被声明为切入点并且织入了切点之后,通过Spring容器获取该类对象,实则获取到的是一个代理对象;
  • 如果一个类中的方法没有被声明为切入点,通过Spring容器获取的就是这个类真实创建的对象。

AOP开发步骤

  1. 创建切面类,在切面类定义切点方法

  2. 将切面类配置给Spring容器

  3. 声明切入点

  4. 配置AOP的通知策略

案例一

1. 添加依赖

  • context
  • aspects
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>

<!-- aspectj的植入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>

2. 创建目标接口和目标类(内部有切点)

StudentDAOImpl.java

3. 创建一个类,定义要添加的业务逻辑

1
2
3
4
5
6
7
8
9
10
11
public class TxManager {

public void begin(){
System.out.println("-----------开启事务");
}

public void commit(){
System.out.println("-----------提交事务");
}

}

4. 创建spring配置文件,配置aop

引入aop的命名空间,在DAO的方法添加开启事务和提交事务的逻辑。

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 id="studentDAO" class="com.qfedu.dao.StudentDAOImpl"></bean>

<bean id="txManager" class="com.qfedu.utils.TxManager"></bean>

<aop:config>
<!--声明切入点-->
<aop:pointcut id="student_update" expression="execution(* cc.gaojie.dao.StudentDAOImpl.update())"/>

<!--声明txManager为切面类-->
<aop:aspect ref="txManager">
<!--通知/增强-->
<aop:before method="begin" pointcut-ref="student_update"/>
<aop:after method="commit" pointcut-ref="student_update"/>
</aop:aspect>
</aop:config>

</beans>

5. 测试

案例二

1. 导入 AOP 相关坐标

  • context
  • aspects
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>

<!-- aspectj的植入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>

2. 创建目标接口和目标类(内部有切点)

1
2
3
public interface TargetInterface {
public void save();
}
1
2
3
4
5
6
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running……");
}
}

3. 创建切面类(内部有增强方法)

1
2
3
4
5
6
7
public class MyAspect {

public void before(){
System.out.println("前置增强……");
}

}

4. 将目标类和切面类交给Spring管理

1
2
3
4
5
6
7
applicationContext.xml

<!--目标对象-->
<bean id="target" class="cc.gaojie.aop.Target" ></bean>

<!--切面对象-->
<bean id="myAspect" class="cc.gaojie.aop.MyAspect"></bean>

5. 在 applicationContext.xml 中配置织入关系

1
2
3
4
5
6
7
8
9
10
applicationContext.xml

<!--配置织入:告诉Spring框架哪些方法(切点)需要进行哪些增强-->
<aop:config>
<!--声明切面:切点+通知(增强)-->
<aop:aspect ref="myAspect">
<!--访问save()方法时,使用myAspect的before()方法对它进行前置增强-->
<aop:before method="before" pointcut="execution(public void cc.gaojie.aop.Target.save())"></aop:before>
</aop:aspect>
</aop:config>

6. 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {

@Autowired
private TargetInterface target;

@Test
public void test1(){
target.save();
}
}

切入点的声明

切点表达式的写法

1
execution(	[修饰符]	返回值类型	包名.类名.方法名(参数)	)
  • 访问修饰符可以省略。
  • 返回值类型、包名、类名、方法名可以使用星号 * 代表任意。
  • 包名与类名之间一个点 . 代表当前包下的类;两个点 .. 表示当前包及其子包下的类。
  • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--使用aop:pointcut标签声明切入点:切入点可以是一个方法-->
<aop:pointcut id="book_insert" expression="execution(* cc.gaojie.BookDAOImpl.insert())"/>

<!--BookDAOImpl类中所有无参数无返回值的方法-->
<aop:pointcut id="book_pc1" expression="execution(void cc.gaojie.dao.BookDAOImpl.*())"/>

<!--BookDAOImpl类中所有无返回值的方法-->
<aop:pointcut id="book_pc2" expression="execution(void cc.gaojie.dao.BookDAOImpl.*(..))"/>

<!--BookDAOImpl类中所有无参数的方法-->
<aop:pointcut id="book_pc3" expression="execution(* cc.gaojie.dao.BookDAOImpl.*())"/>

<!--BookDAOImpl类中所有方法-->
<aop:pointcut id="book_pc4" expression="execution(* cc.gaojie.dao.BookDAOImpl.*(..))"/>

<!--dao包中所有类中的所有方法-->
<aop:pointcut id="pc5" expression="execution(* cc.gaojie.dao.*.*(..))"/>

<!--dao包中所有类中的insert方法-->
<aop:pointcut id="pc6" expression="execution(* cc.gaojie.dao.*.insert(..))"/>

<!--dao包中所有类中的insert方法-->
<aop:pointcut id="pc7" expression="execution(* *(..))"/>

通知策略

AOP通知策略:就是声明将切面类中的切点方法如何织入到切入点

  • before
  • after
  • after-throwing
  • after-returning
  • around
1
<aop:通知类型	method=“切面类中方法名”	pointcut=“切点表达式" ></aop:通知类型>

通知的类型

案例一

后置增强

环绕增强

异常抛出增强

最终增强

可以看到,只有【异常抛出通知】和【最终通知】遇到异常后会继续执行。

案例二

  1. 定义切面类

    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
    public class MyAspect {

    public void method1(){
    System.out.println("~~~~~~~method1");
    }
    public void method2(){
    System.out.println("~~~~~~~method2");
    }
    public void method3(){
    System.out.println("~~~~~~~method3");
    }
    public void method4(){
    System.out.println("~~~~~~~method4");
    }

    //环绕通知的切点方法,必须准守如下的定义规则:
    //1.必须带有一个ProceedingJoinPoint类型的参数
    //2.必须有Object类型的返回值
    //3.在前后增强的业务逻辑之间执行Object v = point.proceed();
    //4.方法最后返回v
    public Object method5(ProceedingJoinPoint point) throws Throwable {
    System.out.println("~~~~~~~method5---before");
    //此代码的执行,就表示切入点方法的执行
    Object v = point.proceed();
    System.out.println("~~~~~~~method5---after");
    return v;从
    }

    }
  2. 配置切面类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <bean id="myAspect" class="com.qfedu.utils.MyAspect"></bean>
    <aop:config>
    <!--使用aop:pointcut标签声明切入点:切入点可以是一个方法-->
    <aop:pointcut id="book_insert" expression="execution(* com.qfedu.dao.BookDAOImpl.insert())"/>

    <aop:aspect ref="myAspect">
    <!--aop:before 前置通知,切入到指定切入点之前-->
    <aop:before method="method1" pointcut-ref="book_insert"/>
    <!--aop:after 后置通知,切入到指定切入点之后-->
    <aop:after method="method2" pointcut-ref="book_insert"/>
    <!--aop:after-throwing 异常通知,切入点抛出异常之后-->
    <aop:after-throwing method="method3" pointcut-ref="book_insert"/>
    <!--aop:after-returning 方法返回值返回之后,对于一个Java方法而言return返回值也是方法的一部分
    因此“方法返回值返回之后”和“方法执行结束”是同一个时间点,after 和 after-returning根据配置的顺序决定执行顺序-->
    <aop:after-returning method="method4" pointcut-ref="book_insert"/>
    <aop:around method="method5" pointcut-ref="book_insert"/>
    </aop:aspect>

    </aop:config>
  3. 运行结果

切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。(便于维护)

AOP使用注意事项

如果要使用Spring aop面向切面编程,调用切入点方法的对象必须通过Spring容器获取。

  • 如果一个类中的方法被声明为切入点并且织入了切点之后,通过Spring容器获取该类对象,实则获取到的是一个代理对象;
  • 如果一个类中的方法没有被声明为切入点,通过Spring容器获取的就是这个类真实创建的对象。

知识要点

AOP开发步骤

  1. 创建切面类,在切面类定义切点方法

  2. 将切面类配置给Spring容器

  3. 声明切入点

  4. 配置AOP的通知策略

aop织入的配置

1
2
3
4
5
<aop:config>
<aop:aspect ref=“切面类”>
<aop:before method=“通知方法名称” pointcut=“切点表达式"></aop:before>
</aop:aspect>
</aop:config>

通知的类型

前置通知、后置通知、环绕通知、异常抛出通知、最终通知。

只有【异常抛出通知】和【最终通知】遇到异常后会继续执行。

切点表达式的写法

1
execution( [修饰符]	返回值类型	包名.类名.方法名(参数)	)
  • 访问修饰符可以省略。
  • 返回值类型、包名、类名、方法名可以使用星号 * 代表任意。
  • 包名与类名之间一个点 . 代表当前包下的类;两个点 .. 表示当前包及其子包下的类。
  • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表。

AOP使用注意事项

如果要使用Spring aop面向切面编程,调用切入点方法的对象必须通过Spring容器获取。

  • 如果一个类中的方法被声明为切入点并且织入了切点之后,通过Spring容器获取该类对象,实则获取到的是一个代理对象;
  • 如果一个类中的方法没有被声明为切入点,通过Spring容器获取的就是这个类真实创建的对象。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!