环境:SpringBoot3.2.5
1. 引入功能
Spring AOP中有非常特殊的类型增强"Introductions",它允许切面为被代理的对象动态地引入新的接口,从而使得这些对象在运行时具备了这些接口所声明的方法。
工作原理:
定义接口:首先,声明一个你需要目标类实现的接口。在目标类创建代理时,代理类会实现该接口
实现接口:这是对上面接口的实现,当我们通过代理类调用上面声明的接口时内部会调用该实现接口对应的方法。
创建切面:在切面中,通过@DeclareParents注解(Spring AOP的特定功能)来声明哪些目标对象将被引入什么接口。这个注解实际上是在创建代理对象时,将这些接口的实现动态地“插入”到代理对象中。
有点晦涩,接下来直接上代码你就明白怎么一回事了。
定义接口
public interface DAO {
void remove() ;
}
该接口会被代理类实现。
实现上面的接口
public class DefaultDAO implements DAO {
@Override
public void remove() {
System.out.println("默认删除功能") ;
}
}
最终生成的代理类实现了DAO接口,而具体方法实现细节是调用这里的DefaultDAO。
创建切面
接下来就是定义切面,你需要增强的类及声明接口(代理类要实现的接口)。
@Aspect
public class LogAspect {
@Pointcut("@annotation(log)")
private void recordLog(Log log) {}
// 通过该注解声明PersonService类,在生成代理时需要实现DAO接口
// 并且调用DAO中的方法时是调用的这里DefaultDAO中实现的方法
@DeclareParents(value = "com.pack.aop.PersonService", defaultImpl = DefaultDAO.class)
private DAO dao ;
@Around("recordLog()")
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("日志执行前...") ;
Object ret = pjp.proceed() ;
System.out.println("日志执行后...") ;
return ret ;
}
}
声明PersonService
@Service
public class PersonService {
public void save() {
System.out.println("保存Person对象") ;
}
}
接下来进行测试
@SpringBootTest
public class AppTest {
private final PersonService ps ;
public AppTest(PersonService ps) {
this.ps = ps ;
}
@Test
public void testSave() {
this.ps.save() ;
// 判断是否是DAO类型
if (ps instanceof DAO dao) {
dao.remove() ;
}
}
}
输出结果
日志执行前...
保存Person对象
日志执行后...
默认删除功能
PersonService生成的代理类,同时也实现了DAO接口,方法调用是DefaultDAO中的实现。
2. 切面实例化模型
看标题又是一个晦涩的东西。在Spring AOP中,切面实例化模型主要关注于切面对象是如何被创建和管理的。Spring提供了几种不同的方式来实例化切面,每种方式都有其特定的用途和优点。这其中包括:perthis、pertarget 和 pertypewithin 实例化模型。这里我们就介绍perthis和pertarget。
perthis
perthis子句的作用是为每个执行业务服务的唯一服务对象创建一个方面实例(每个与此方面在连接点匹配的绑定到此对象的唯一对象)
pertarget
pertarget 实例化模型的工作方式与 perthis 完全相同,但它会在匹配的连接点上为每个唯一的目标对象创建一个方面实例。
示例:
定义切面
@Component
@Scope("prototype")
@Aspect("perthis(execution(* com.pack.aop.DAO.*(..)))")
public class LogAspect {
@Before("bean(*Service)")
public void beforeLog(JoinPoint jp) {
System.out.println("before 记录日志 - " + this) ;
}
}
业务对象
public interface DAO {
public void save() ;
}
public class PersonService implements DAO {
public void save() {
System.out.println("保存Person对象") ;
}
}
public class StudentService implements DAO {
public void save() {
System.out.println("保存Student对象") ;
}
}
测试
@SpringBootTest
public class AppTest {
@Resource
private PersonService ps ;
@Resource
private StudentService ss ;
@Test
public void testAll() {
ps.save();
System.out.println("===============") ;
ss.save();
}
}
输出结果
before 记录日志 - com.pack.aop.LogAspect@3ed242a4
保存Person对象
====================
before 记录日志 - com.pack.aop.create.LogAspect@73a2e526
保存Student对象
实例化切面模型除非你需要更细粒度控制切面实例生命周期的场景下使用,一般应该是很少使用的。