關于Spring AOP的兩個高級功能,你用過嗎?
環境: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對象
實例化切面模型除非你需要更細粒度控制切面實例生命周期的場景下使用,一般應該是很少使用的。