本文为 SSM 框架系列之 Spring 第五部分:面向切面编程
其它内容的链接如下:
1. HelloSpring
2. 控制反转
3. 依赖注入与装配机制
4. 代理模式
5. 面向切面编程
6. 声明式事务
1 AOP 的定义
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
面向切面编程是面向对象编程的补充,它将面向对象编程产生的各个业务模块所共同调用的部分封装起来,达到与主业务逻辑解耦的目的。同时,AOP 可以在不改变原来的代码的情况下,实现对原有功能的增强。(代理模式,开闭原则)
2 Spring 中的 AOP
AOP 是 Spring 的特性之一,它可以让我们通过少量的代码就可以完成面向切面编程的目的。
Spring AOP 就是基于动态代理的,Spring 中的 AOP 目前支持 JDK 动态代理和 Cglib 代理。
Spring 中的 AOP 定义如下:
其中的核心术语的含义如下:
- 横切关注点:我们需要关注的部分,就是横切关注点;
- 切面:横切关注点被模块化的特殊对象。是一个类;
- 通知:切面需要完成的工作。是类中的一个方法;
- 切入点:用来匹配特定接入点的表达式,通过切入点表达式匹配接入点是 AOP 的核心,Spring 默认使用AspectJ的切点表达式;
- 织入:将一个或多个切面与类或对象链接在一起创建一个被增强对象;
- 接入点:程序执行期的一个点,例如方法执行、类初始化、异常处理。
Spring AOP 中常用的四种 Advice 方式:
通知类型 | 连接点 |
---|---|
前置通知 | 方法执行前 |
后置通知 | 方法放回后 |
环绕通知 | 方法前后 |
异常抛出通知 | 方法抛出异常时 |
下面感受一下同一 aspect,不同 advice 的执行顺序:
更多形式的通知的执行流程请看这位大神的博客:【传送门】
3 使用 Spring 实现 AOP
3.1 通过使用 Spring API 接口实现
1 导入 AOP 所需要的依赖包
<!-- 经过实践证明,这段代码确实是必须要写的 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- AOP 所需要的依赖包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
<!-- 静态资源导出 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2 编写原来的业务接口和实现类
package sharm.service;
public interface Rent {
public void rent();
}
package sharm.service;
public class Host implements Rent {
@Override
public void rent() {
System.out.println("我是房东,我想要出租房子。");
}
}
3 书写两个增强类,一个是前置增强,一个是后置增强。即我们需要织入的切面
3.1 前置增强
package sharm.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//args : 被调用的方法的参数
//target : 目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了");
// 额外加上的领域业务
seeHouse();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
}
3.2 后置增强
package sharm.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//returnValue:返回值
//method:被调用的方法
//args:被调用的方法的对象的参数
//target:被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
// 额外加上的领域业务
fare();
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
}
4 配置 Spring 的配置文件,并且实现 AOP 切入
<?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="host" class="sharm.service.Host"/>
<bean id="beforeLog" class="sharm.log.BeforeLog"/>
<bean id="afterLog" class="sharm.log.AfterLog"/>
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* sharm.service.*.*(..))"/>
<!--执行通知。advice-ref:执行方法,pointcut-ref切入点-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
在切入点表达式execution(* sharm.service.*.*(..))
中,各参数的含义如下:
-
execution() 表示表达式主体;
-
第一个 * 表示返回类型为所有的类型;
-
sharm.service.
表示包名。尾部的一个点表示当前包,两个点表示当前包下的所有子包; -
第二个 * 表示类名为所有的类;
-
*(..)
中的星号表示所有的方法,后面的括号表示方法的参数,两个句点表示任何参数。
5 编写测试类
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sharm.service.Rent;
public class AopTest {
@Test
public void myTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// Bean 中用的是实现类,而返回值采用的是接口
Rent hostService = (Rent)context.getBean("host");
hostService.rent();
}
}
6 控制面板输出
3.2 通过自定义类来实现
1 导入 AOP 所需要的依赖包
2 编写原来的业务接口和实现类
3 自定义切入类来实现 AOP
package sharm.log;
public class CustomAspect {
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
}
4 配置 Spring,非常关键
<!--注册bean-->
<bean id="host" class="sharm.service.Host"/>
<bean id="customAspect" class="sharm.log.CustomAspect"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="customAspect">
<aop:pointcut id="customPointCut" expression="execution(* sharm.service.*.*(..))"/>
<aop:before pointcut-ref="customPointCut" method="seeHouse"/>
<aop:after pointcut-ref="customPointCut" method="fare"/>
</aop:aspect>
</aop:config>
6 编写测试类
7 控制面板输出
3.3 使用注解来实现
1 导入 AOP 所需要的依赖包
2 编写原来的业务接口和实现类
3 编写一个注解实现的增强类
package sharm.log;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationAspect {
//看房
@Before("execution(* sharm.service.*.*(..))")
public void seeHouse(){
System.out.println("带房客看房");
}
@After("execution(* sharm.service.*.*(..))")
// 收中介费
public void fare(){
System.out.println("收中介费");
}
}
4 在 Spring 配置文件中注册 bean,并增加支持注解的配置
<!--注册bean-->
<bean id="host" class="sharm.service.Host"/>
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="sharm.log.AnnotationAspect"/>
<aop:aspectj-autoproxy/>
通过aop命名空间的<aop:aspectj-autoproxy />
声明自动为 spring 容器中那些配置@aspectJ
切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator
进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />
隐藏起来了
<aop:aspectj-autoproxy />
有一个proxy-target-class
属性,默认为false
,表示使用 jdk 动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>
时,表示使用 CGLib 动态代理技术织入增强。不过即使proxy-target-class
设置为 false,如果目标类没有声明接口,则 spring 将自动使用 CGLib 动态代理。
5 编写测试类
6 控制面板输出