@
什么是AOP?
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,和 OOP(面向对象编程)类似,也是一种编程思想。
AOP的作用
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。
AOP框架
目前最流行的 AOP 框架有两个
- Spring AOP
- Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。
- Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
- 有两种实现方式:基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。
- AspectJ
- AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
AOP术语
为了更好地理解 AOP,我们需要了解一些它的相关术语。这些专业术语并不是 Spring 特有的,有些也同样适用于其它 AOP 框架,如 AspectJ。
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点和通知的结合。 |
Advice 直译为通知,也有的资料翻译为“增强处理”,共有 5 种类型
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
AOP的优点
AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。在 Spring 框架中使用 AOP 主要有以下优势。
- 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品。最重要的是,这种服务是声明式事务管理。
- 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
- 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。
SpringAOP AspectJ开发AOP
基于XML开发
基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 <aop:config>
元素中。
使用XML开发需要注意以下两点:
- 需要导入一个依赖包
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>x.x.x</version>
</dependency>
</dependencies>
- 使用
<aop:config>
元素时,我们需要先导入 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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
</beans>
基于XML开发有两种方式:自定义类开发和使用Spring的API接口开发
1、自定义类开发AOP
- 定义切面
<aop:aspect>
在 Spring 配置文件中,使用<aop:aspect>
元素定义切面,该元素可以将定义好的 Bean 转换为切面 Bean,所以使用<aop:aspect>
之前需要先定义一个普通的 Spring Bean
<!-- 自定义类实现-->
<bean id="diyPoint" class="xxx.xxx"/>
<aop:config>
<!-- 自定义切面-->
<aop:aspect ref="diyPoint">
...
</aop:aspect>
</aop:config>
- 定义切入点
<aop:pointcut>
<aop:pointcut>
用来定义一个切入点,当<aop:pointcut>
元素作为<aop:config>
元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut>
元素作为<aop:aspect>
元素的子元素时,表示该切入点只对当前切面有效。
<aop:config>
<aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
</aop:config>
其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。
execution 格式解析:
符号 | 含义 |
---|---|
execution() | 表达式的主体; |
第一个*符号 | 表示返回值的类型任意; |
service | AOP所切的服务的包名,即我们的业务部分 |
UserServiceImpl | 表示UserServiceImpl类 |
.*(..) | 表示任何方法名,括号表示参数,两个点表示任何参数类型 |
-
定义通知
AspectJ 支持 5 种类型的 advice
<aop:aspect ref="diyPoint">
<aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
<!-- 前置通知 -->
<aop:before pointcut-ref="point" method="..."/>
<!-- 后置通知 -->
<aop:after-returning pointcut-ref="point" method="..."/>
<!-- 环绕通知 -->
<aop:around pointcut-ref="point" method="..."/>
<!-- 异常通知 -->
<aop:after-throwing pointcut-ref="point" method="..."/>
<!-- 最终通知 -->
<aop:after pointcut-ref="point" method="..."/>
</aop:aspect>
下面使用IDEA演示自定义类开发AOP
如图所示创建如下包、类、接口、配置文件
UserService接口代码如下:
public interface UserService {
public void add();
}
UserServiceImpl类代码如下:
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
}
自定义类diy
public class diyPointCut {
public void before(){
System.out.println("==========执行前===========");
}
public void after(){
System.out.println("==========执行后===========");
}
}
applicationContext.xml文件代码如下:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="service.UserServiceImpl"/>
<!-- 自定义类实现-->
<bean id="diyPoint" class="diy.diyPointCut"/>
<aop:config>
<!-- 自定义切面,ref要引用的类-->
<aop:aspect ref="diyPoint">
<aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
测试类代码如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
测试结果为:
2、使用Spring的API接口开发AOP
各接口介绍如下:
- Advice:一个用于标识的接口,也是所有通知的最终父接口
- BeforeAdvice:前置通知接口,指的是某些行为触发前的通知
- MethodBeforeAdvice:方法前置通知接口,指的是方法被执行前会触发的通知
- AfterAdvice:后置通知接口,指的是某些行为触发后的通知
- AfterReturningAdvice:返回后置通知接口,指的是方法执行后触发的通知
- ThrowsAdvice:异常通知接口,指的是方法出现异常时触发的通知。方法出现异常也代表方法已经返回了结果,需要注意,此接口也是一个标识接口。
这里只演示了MethodBeforeAdvice和AfterReturningAdvice接口,其他接口读者可查阅资料学习。
同样的,创建如上包、类、接口、配置文件
service包下的内容、测试类均与上面代码一致,这里不再赘述
Log代码如下:
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//在调用bean的方法前执行
public class Log implements MethodBeforeAdvice {
//method 要执行目标对象的方法
//object 参数
// o 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行");
}
}
AfterLog代码如下:
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
//接口AfterReturningAdvice为实现方法执行后通知
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了"+method.getName()+"方法");
}
}
applicationContext.xml文件代码如下:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="service.UserServiceImpl"/>
<bean id="log" class="log.Log"/>
<bean id="afterLog" class="log.AfterLog"/>
<!-- 使用原生的API接口-->
<aop:config>
<!-- 切入点-->
<aop:pointcut id="pointcut" expression="execution(* service.UserServiceImpl.*(..))"/>
<!-- 执行环绕-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
advisor标签
- 用于把一个Advice和一个Pointcut组合起来。一个advisor标签对应的就是一个Advisor接口的实现类,默认是DefaultBeanFactoryPointcutAdvisor实现。
运行结果如下:
基于注解开发
在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。
AspectJ 允许使用注解定义切面、切入点和增强处理,Spring 框架可以根据这些注解生成 AOP 代理。
名称 | 说明 |
---|---|
@Aspect | 用于定义一个切面。 |
@Pointcut | 用于定义一个切入点。 |
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于ThrowAdvice。 |
@After | 用于定义最终final通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。 |
- 定义切面@Aspect的方法
AspectJ 类和其它普通的 Bean 一样,可以有方法和字段,不同的是 AspectJ 类需要使用 @Aspect 注解
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AspectPointCut {
}
- 定义切入点@Pointcut
@Pointcut 注解用来定义一个切入点
// 要求:方法必须是private,返回值类型为void,名称自定义,没有参数,id为PointCut()
@Pointcut("execution(*net.biancheng..*.*(..))")
private void PointCut() {
}
- 定义通知advice
@AspectJ 支持 5 种类型的 advice
@Before("PointCut()")
public void beforeAdvice(){
...
}
下面使用IDEA展示基于注解开发AOP
- 创建一个干净的Maven项目,并导入相关依赖的jar包
- 如图创建相应的包,类
- 同样的service包下的类、测试类MyTest与上面代码均一致,这里不再赘述。
AnnocationPointCut类代码如下:
import org.aspectj.lang.annotation.*;
//使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class AnnocationPointCut {
// 要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(* service.UserServiceImpl.*(..))")
private void selectAll(){
}
@Before("selectAll()")
public void before(){
System.out.println("==========执行前===========");
}
@After("selectAll()")
public void after(){
System.out.println("==========执行后===========");
}
}
applicationContext.xml配置文件代码如下:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="service.UserServiceImpl"/>
<bean id="userPointCut" class="AnnocationPointCut"/>
<!-- 此时不需要在配置文件中编写切面切入点等代码,只需开启注解支持-->
<aop:aspectj-autoproxy/>
</beans>
运行结果: