小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
前言:学习AOP之前,要先了解代理模式,AOP其实是代理模式的一种实现
1、什么是AOP
-
面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式,AOP是OOP的延续。
-
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
-
AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
-
AOP本质上只是一种代理模式的实现方式
-
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ
- Spring AOP 使用纯 Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
- AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
2、AOP术语
为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,
| 名称 | 说明 |
|---|---|
| Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。 |
| Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
| Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。方法 |
| Target(目标) | 指代理的目标对象。 |
| Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
| Proxy(代理) | 指生成的代理对象。 |
| Aspect(切面) | 切入点和通知的结合。类 |
Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口。
Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型,如下表所示。
| 名称 | 说明 |
|---|---|
| org.springframework.aop.MethodBeforeAdvice(前置通知) | 在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。 |
| org.springframework.aop.AfterReturningAdvice(后置通知) | 在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。 |
| org.aopalliance.intercept.MethodInterceptor(环绕通知) | 在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。 |
| org.springframework.aop.ThrowsAdvice(异常通知) | 在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。 |
| org.springframework.aop.IntroductionInterceptor(引介通知) | 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。 |
3、Spring使用AspectJ实现AOP的方式
首先,我们通过Maven引入Spring对AOP的支持:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
上述依赖会自动引入AspectJ,使用AspectJ实现AOP比较方便,因为它的定义比较简单。
3.1、方式一:使用原生Spring API接口
接口
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
真实角色
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
日志的实现类,要在不影响原业务代码的情况下执行 原生接口
//前置日志
public class Log implements MethodBeforeAdvice {
//method要执行的目标对象的方法
//args参数
//target目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了");
}
}
//后置日志
public class AfterLog implements AfterReturningAdvice {
//returnValue执行后的返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为"+returnValue);
}
}
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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--注意:配置AOP需要导入约束-->
<bean id="userService" class="com.cheng.service.UserServiceImpl"/>
<bean id="log" class="com.cheng.log.Log"/>
<bean id="afterLog" class="com.cheng.log.AfterLog"/>
<!--方式一:使用Spring API接口-->
<aop:config>
<!--切入点 expression表达式 execution(切入要执行的切入点 UserServiceImpl类下的所有方法下的所有参数-->
<aop:pointcut id="pointcut" expression="execution(* com.cheng.service.UserServiceImpl.*(..))"/>
<!--日志实现通过Spring这个中间商执行AOP切入,并不干预目标对象程序的执行-->
<!--执行环绕增加(就是在切入点执行的代码) -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/><!--把log这个类切入到execution里的方法里面-->
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理的是接口,所以这里要返回一个接口
UserService userService = context.getBean("userService", UserService.class);
userService.delete();
}
}
3.2、方式二:自定义类实现AOP
自定义日志类
//自定义切入点类
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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--配置AOP需要导入约束-->
<bean id="userService" class="com.cheng.service.UserServiceImpl"/>
<!--方式二:自定义类-->
<bean id="diy" class="com.cheng.diy.DiyPointCut"/>
<aop:config>
<!--自定义一个切面(就是一个类) ref要引用的类-->
<aop:aspect ref="diy">
<aop:pointcut id="point" expression="execution(* com.cheng.service.UserServiceImpl.*(..))"/>
<!--在point切入点执行下面两个方法-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
测试代码不变
3.3、方式三:使用注解实现AOP
在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。
为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。
| 名称 | 说明 |
|---|---|
| @Aspect | 用于定义一个切面。 |
| @Before | 用于定义前置通知,相当于 BeforeAdvice。 |
| @AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
| @Around | 用于定义环绕通知,相当于MethodInterceptor。 |
| @AfterThrowing | 用于定义抛出通知,相当于ThrowAdvice。 |
| @After | 用于定义最终final通知,不管是否异常,该通知都会执行。 |
| @DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。 |
用注解定义一个切面类
package com.cheng.diy;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.cheng.service.UserServiceImpl.*(..))")//@Before注解里的内容就是切入点pointcut的内容
public void before(){
System.out.println("=======方法执行前=======");
}
@After("execution(* com.cheng.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=======方法执行后=======");
}
}
applicationContext.xml
<!--方式三:使用注解-->
<bean id="diy" class="com.cheng.diy.AnnotationPointCut"/>
<!--动态代理实现有两种方式:JDK接口,cglib类
当proxy-target-class="false" 为JDK(默认)
当proxy-target-class="true" 为cglib类
-->
<aop:aspectj-autoproxy proxy-target-class="true"/><!--开启注解支持-->
测试代码不变