關(guān)于Spring AOP的兩個高級功能,你用過嗎?
環(huán)境:SpringBoot3.2.5
1. 引入功能
Spring AOP中有非常特殊的類型增強"Introductions",它允許切面為被代理的對象動態(tài)地引入新的接口,從而使得這些對象在運行時具備了這些接口所聲明的方法。
工作原理:
定義接口:首先,聲明一個你需要目標類實現(xiàn)的接口。在目標類創(chuàng)建代理時,代理類會實現(xiàn)該接口
實現(xiàn)接口:這是對上面接口的實現(xiàn),當我們通過代理類調(diào)用上面聲明的接口時內(nèi)部會調(diào)用該實現(xiàn)接口對應(yīng)的方法。
創(chuàng)建切面:在切面中,通過@DeclareParents注解(Spring AOP的特定功能)來聲明哪些目標對象將被引入什么接口。這個注解實際上是在創(chuàng)建代理對象時,將這些接口的實現(xiàn)動態(tài)地“插入”到代理對象中。
有點晦澀,接下來直接上代碼你就明白怎么一回事了。
定義接口
public interface DAO {
void remove() ;
}
該接口會被代理類實現(xiàn)。
實現(xiàn)上面的接口
public class DefaultDAO implements DAO {
@Override
public void remove() {
System.out.println("默認刪除功能") ;
}
}
最終生成的代理類實現(xiàn)了DAO接口,而具體方法實現(xiàn)細節(jié)是調(diào)用這里的DefaultDAO。
創(chuàng)建切面
接下來就是定義切面,你需要增強的類及聲明接口(代理類要實現(xiàn)的接口)。
@Aspect
public class LogAspect {
@Pointcut("@annotation(log)")
private void recordLog(Log log) {}
// 通過該注解聲明PersonService類,在生成代理時需要實現(xiàn)DAO接口
// 并且調(diào)用DAO中的方法時是調(diào)用的這里DefaultDAO中實現(xiàn)的方法
@DeclareParents(value = "com.pack.aop.PersonService", defaultImpl = DefaultDAO.class)
private DAO dao ;
@Around("recordLog()")
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("日志執(zhí)行前...") ;
Object ret = pjp.proceed() ;
System.out.println("日志執(zhí)行后...") ;
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() ;
}
}
}
輸出結(jié)果
日志執(zhí)行前...
保存Person對象
日志執(zhí)行后...
默認刪除功能
PersonService生成的代理類,同時也實現(xiàn)了DAO接口,方法調(diào)用是DefaultDAO中的實現(xiàn)。
2. 切面實例化模型
看標題又是一個晦澀的東西。在Spring AOP中,切面實例化模型主要關(guān)注于切面對象是如何被創(chuàng)建和管理的。Spring提供了幾種不同的方式來實例化切面,每種方式都有其特定的用途和優(yōu)點。這其中包括:perthis、pertarget 和 pertypewithin 實例化模型。這里我們就介紹perthis和pertarget。
perthis
perthis子句的作用是為每個執(zhí)行業(yè)務(wù)服務(wù)的唯一服務(wù)對象創(chuàng)建一個方面實例(每個與此方面在連接點匹配的綁定到此對象的唯一對象)
pertarget
pertarget 實例化模型的工作方式與 perthis 完全相同,但它會在匹配的連接點上為每個唯一的目標對象創(chuàng)建一個方面實例。
示例:
定義切面
@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) ;
}
}
業(yè)務(wù)對象
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();
}
}
輸出結(jié)果
before 記錄日志 - com.pack.aop.LogAspect@3ed242a4
保存Person對象
====================
before 記錄日志 - com.pack.aop.create.LogAspect@73a2e526
保存Student對象
實例化切面模型除非你需要更細粒度控制切面實例生命周期的場景下使用,一般應(yīng)該是很少使用的。