目录
一:什么是AOP
AOP(Aspect-Oriented Programming)是一种编程思想,也是其中非常重要的一个特征之一。它基于切面(Aspect)的编程技术,将应用程序中的业务逻辑与系统逻辑分离,提高代码的可复用性、可维护性和可扩展性。
二:AOP的底层原理
AOP的实现原理是基于动态代理
动态代理:具体来说,SpringAOP支持两种不同类型的代理模式:JDK动态代理和CGLIB代理。如果要代理的目标对象实现了至少一个接口,则会使用JDK动态代理;如果目标对象没有实现任何接口,则会使用CGLIB来实现代理对象。
三:演示JDK动态代理模式
JDK动态代理是一种基于接口的代理技术,通过Proxy.newProxyInstance()方法和InvocationHandler接口实现。在这种代理技术中,代理对象和目标对象必须实现同样的接口。
Proxy.newProxyInstance()方法用于创建一个实现了指定接口的代理对象。方法有三个参数:public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException
- ClassLoader:类加载器:用于指定生成代理类的加载器;
- Class[]:要实现的接口:代理类需要实现的接口;
- InvocationHandler:调用处理器:代理对象的调用会转发到该处理器的invoke()方法中进行处理
- InvocationHandler:实现这个接口,接口中只声明了一个方法invoke(),该方法用于处理代理对象的方法调用,每次代理对象的方法被调用时,都会触发invoke()方法的执行,并将代理对象、调用的方法对象以及方法参数传入参数中,我们可以在invoke()方法中定义对这些参数的操作,比如记录日志信息、控制访问权限等。
- invoke中的三个参数:
- proxy:表示代理对象本身:在invoke方法中,我们可以通过这个参数来调用代理对象的其他方法,或者将代理对象传递给其他方法使用
- method:正在被代理的方法:通过这个参数,我们可以获取到当前正在被代理的方法信息,如方法名、参数类型
- args:方法的参数:通过这个参数,,我们可以获取到当前正在被代理的方法的所有参数,这些参数以数组形式传递
演示:
package com.juju.spring5;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author lcy
* @version 1.0
* @date 2023/3/30 17:36
*/
public interface User {
void add();
static void main(String[] args) {
}
}
//被代理对象
class UserImpl implements User {
@Override
public void add() {
System.out.println("add方法被执行了");
}
}
//代理处理器
class UserProxy implements InvocationHandler {
//接收一个被代理对象,保存在成员变量object中
private Object object;
public UserProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
//method.getName()获取被代理对象的方法名 args:被代理的参数列表
System.out.println("方法之前..." + method.getName());
//原来的方法
//通过反射机制来调用被代理对象的方法
Object invoke = method.invoke(object, args);
//方法之后
System.out.println("方法之后");
//返回出原来的值之后返回给代理对象
return invoke;
}
}
class Test{
public static void main(String[] args) {
//创建被代理对象
User hello = new UserImpl();
//创建代理处理器
//new YanShiProxy代表要处理被代理对象
UserProxy userProxy = new UserProxy(hello);
//创建代理对象
User pp = (User) Proxy.newProxyInstance(
//类加载器
hello.getClass().getClassLoader(),
//获取被代理类实现的接口列表
hello.getClass().getInterfaces(),
userProxy);
pp.add();
}
}
输出:
四:AOP操作术语
1:连接点:类中哪些方法可以增强,这些方法被称为切入点
2:切入点:实际被真正增强的方法,称为切入点
3:通知(增强):(1):实际增强的逻辑代码就是通知,加进来的代码
(2):通知有多种:前置通知、后置通知、环绕通知、异常通知(目标方法抛出异常后执行的通知)、最终通知;
4:切面:由切点和通知组成的,通过将切点和通知装在一起,形成一个可复用的模块,以便在程序中的多个位置进行使用
五:AOP的实现(基于AspectJ)
AspectJ是基于Java语言的切面编程框架,它扩展了Java语言本身,提供了丰富的面向切面编程支持。AspectJ可以与spring框架集成使用,是spring AOP的一种实现方式
可以使用xml文件实现
可以使用注解实现
5.1:导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
5.2:切入点表达式
切入点表达式用于确定哪些方法需要被增强,springAOP支持两种类型的切入点表达式:基于方法签名的切入点和基于注解的切入点表达式
格式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
基于签名的切入点
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
举例说明:excution(public * com.example.service.UserService.*(..))
代表对com.example.service.UserService类中的所有public方法,并且参数列表是任意类型返回类型是任意类型的的所有类增强
5.3:注解实现AOP
第一步:创建两个类,把增强类中的方法写入add方法之前
public class User {
public void add(){
System.out.println("add...方法");
}
}
public class UserProxy {
public void proxyUser(){
System.out.println("方法之前....");
}
}
第二步:开启注解扫描
xml文件中引入context、aop命名空间
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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描-->
<context:component-scan base-package="com.juju.spring5.ProxyUser"></context:component-scan>
</beans>
第三步:使用注解创建User和UserProxy对象
@Component创建对象
第四步:在增强类上添加@Aspect
第五步:在xml配置文件中开启生成代理对象
<!-- 开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
第六步:在增强类中方法添加前置通知
前置通知:@Before
最终通知:@After
后置通知:@AfterReturning
异常通知:@AfterThrowing
环绕通知:@Around
package com.juju.spring5.ProxyUser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author lcy
* @version 1.0
* @date 2023/3/31 14:12
*/
@Component
@Aspect
public class UserProxy {
//前置通知
//Before注解表示作为前置通知
//里面填入切点表达式,哪个类中的那个方法需要被增强
@Before(value = "execution(public * com.juju.spring5.ProxyUser.User1.add(..))")
public void proxyUser(){
System.out.println("Before--前置....");
}
//最终通知
@After(value = "execution(public * com.juju.spring5.ProxyUser.User1.add(..))")
public void after(){
System.out.println("After--最终...");
}
//后置通知
@AfterReturning(value = "execution(public * com.juju.spring5.ProxyUser.User1.add(..))")
public void after1(){
System.out.println("AfterReturning--后置...");
}
//异常通知
@AfterThrowing(value = "execution(public * com.juju.spring5.ProxyUser.User1.add(..))")
public void after2(){
System.out.println("AfterThrowing--异常...");
}
//环绕通知
//ProceedingJoinPoint
@Around(value = "execution(public * com.juju.spring5.ProxyUser.User1.add(..))")
public void after3(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("Around--环绕之前...");
//该方法用于调用目标对象的原有方法
proceedingJoinPoint.proceed();
System.out.println("Around--环绕之后...");
}
}
细节一:相同切入点的抽取
发现上面的切入表达式一样的内容,我们可以@pointcut注解抽取相同的切入点。@pointcut注解定义了一个切入点表达式,可以在其他通知中通过引用该切入点表达式来复用代码
需要注意的是,@Pointcut注解不能直接使用在通知方法上,必须定义一个独立的方法来使用。同时,切入点表达式可以在其他地方进行引用,例如可以在@Before注解中使用该切入点表达式。这样大大减少代码重复,使AOP的代码更加清晰和简洁
细节二:有多个增强类对同一个方法进行增强,设置增强类优先级
@order注解:用于指定切面的执行顺序。当有多个切面对同一个连接点进行增强时,可以使用@order注解为它们指定执行先后顺序,数值越小的切面优先级越高,默认优先级为int类型最大值