Spring面向切面編程(AOP)
概念
AOP(Aspect Oriented Programming),即面向切面編程,利用一種稱為"橫切"的技術(shù),剖開封裝的對(duì)象內(nèi)部,并將那些影響了多個(gè)類的公共行為封裝到一個(gè)可重用模塊,并將其命名為"Aspect",即切面。所謂"切面",簡(jiǎn)單說就是那些與業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護(hù)性。
什么是AOP
面向切面編程:利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使業(yè)務(wù)邏輯的各部分之間耦合度降低,提高程序的可重用性,提高了開發(fā)效率,通俗的講,可以實(shí)現(xiàn)不修改源代碼的方式,在核心業(yè)務(wù)里面 添加新的功能。
AOP底層的原理就是動(dòng)態(tài)代理 ,真正干活的 bean 是 代理 bean , 代理 bean 對(duì)真實(shí) bean 功能增強(qiáng)。
AOP開發(fā)術(shù)語
-
連接點(diǎn)(Joinpoint):連接點(diǎn)是程序類中客觀存在的方法,可被Spring攔截并切入內(nèi)容
說白了,類中的哪些方法可以被增強(qiáng),這些方法就稱為是連接點(diǎn)
-
切入點(diǎn)(Pointcut):被Spring切入連接點(diǎn)
真正被增強(qiáng)的方法稱為切入點(diǎn)
-
通知、增強(qiáng)(Advice):可以為切入點(diǎn)添加額外功能,分為:前置通知、后置通知、異常通知、環(huán)繞通知,最終通知等
實(shí)際增強(qiáng)的邏輯部分,稱為通知(增強(qiáng))
目標(biāo)對(duì)象(Target):代理的目標(biāo)對(duì)象,真實(shí)對(duì)象
引介(Introduction):一種特殊的增強(qiáng),可在運(yùn)行期為類動(dòng)態(tài)添加Field和Method
織入(Weaving):把通知應(yīng)用到具體的類,進(jìn)而創(chuàng)建新的代理類的過程
代理(Proxy):被AOP織入通知后,產(chǎn)生的結(jié)果類
切面(Aspect):由切點(diǎn)和通知組成,將橫切邏輯織入切面所指定的連接點(diǎn)中。 把通知 應(yīng)用到 切入點(diǎn)的過程
作用
Spring的AOP編程即是通過動(dòng)態(tài)代理類為原始類的方法添加輔助功能
開發(fā)流程
環(huán)境搭建
引入AOP相關(guān)依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
spring-context.xml引入AOP命名空間
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd
">
</beans>
代碼演示
定義原始類
public interface UserService {
public void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("save method executed...");
}
}
基于Schema-based實(shí)現(xiàn)AOP
定義通知類(添加額外功能)
//實(shí)現(xiàn)前置通知接口
public class MyAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before advice executed...");
}
}
定義bean標(biāo)簽
<!--原始對(duì)象-->
<bean id="us" class="com.qf.aaron.aop.basic.UserServiceImpl" />
<!--輔助對(duì)象-->
<bean id="myAdvice" class="com.qf.aaron.aop.basic.MyAdvice" />
定義切入點(diǎn)(PointCut),形成切面(Aspect)
<aop:config>
<!--切點(diǎn)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--組裝切面 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>
基于AspectJ 實(shí)現(xiàn)AOP
創(chuàng)建 通知類
/**
* @ClassName : MyAspectJAdvice
* @Description : AspectJ 的 通知類 無需實(shí)現(xiàn)接口
*/
public class MyAspectJAdvice {
public void before(){
System.out.println("前置增強(qiáng)代碼");
}
public Object around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("環(huán)繞增強(qiáng)前面的代碼");
// 讓目標(biāo)方法繼續(xù)執(zhí)行
Object result = jp.proceed();
System.out.println("環(huán)繞增強(qiáng)后面的代碼");
return result;
}
public void after(){
System.out.println("最終增強(qiáng)的代碼,類似于finally,目標(biāo)方法有沒有異常都要執(zhí)行的");
}
public void throwing(Exception e){
System.out.println("異常拋出增強(qiáng)代碼,只有在目標(biāo)方法拋出異常時(shí)才能執(zhí)行");
System.out.println("異常信息是:" + e.getMessage());
}
public void afterReturning(Object result){
System.out.println("后置增強(qiáng),返回值是:"+result);
}
}
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"
xmlns:context="http://www.springframework.org/schema/context"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 其他bean的定義... -->
<!--了解 基于 AspectJ 的 xml 的 AOP實(shí)現(xiàn) 配置-->
<!-- 配置增強(qiáng)類 -->
<bean id="advice" class="com.qf.spring.aop.advice.MyAspectJAdvice" />
<!-- 配置AOP -->
<aop:config>
<!-- 配置切入點(diǎn) -->
<aop:pointcut id="pc" expression="execution(* save())"/>
<aop:aspect ref="advice">
<!-- aop:before 前置增強(qiáng)的配置 -->
<aop:before method="before" pointcut-ref="pc" />
<!-- 環(huán)繞目標(biāo)方法執(zhí)行 -->
<aop:around method="around" pointcut-ref="pc" />
<!-- 異常拋出增強(qiáng) -->
<aop:after-throwing method="throwing" pointcut-ref="pc" throwing="e" />
<!-- 最終增強(qiáng) -->
<aop:after method="after" pointcut-ref="pc" />
<!-- 后置增強(qiáng),目標(biāo)方法拋出異常時(shí) 后置增強(qiáng)不執(zhí)行 -->
<aop:after-returning method="afterReturning" pointcut-ref="pc" returning="result"/>
</aop:aspect>
</aop:config>
</beans>
測(cè)試
@Test
void save() {
ApplicationContext context = new ClassPathXmlApplicationContext("/beans.xml");
UserService service = context.getBean(UserService.class);
service.save();
}
AOP小結(jié)
通過AOP提供的編碼流程,更便利的定制切面,更方便的定制了動(dòng)態(tài)代理
進(jìn)而徹底解決了輔助功能冗余的問題
業(yè)務(wù)類中職責(zé)單一性得到更好保障
輔助功能也有很好的復(fù)用性
通知類【可選】
定義通知類,達(dá)到通知效果
前置通知:MethodBeforeAdvice
后置通知:AfterAdvice
后置通知:AfterReturningAdvice //有異常不執(zhí)行,方法會(huì)因異常而結(jié)束,無返回值
異常通知:ThrowsAdvice
環(huán)繞通知:MethodInterceptor
沒有必要把通知的執(zhí)行順序記得非常精確,因?yàn)?spring 新版本 5 和 之前的舊版本 通知的執(zhí)行順序 不一樣
通配切入點(diǎn)【可選】
根據(jù)表達(dá)式通配切入點(diǎn)
<!--匹配參數(shù)-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.qf.aaron.aop.basic.User))" />
<!--匹配方法名(無參)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意參數(shù))-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值類型-->
<aop:pointcut id="myPointCut" expression="execution(com.qf.aaron.aop.basic.User *(..))" />
<!--匹配類名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop..*.*(..))" />
切入點(diǎn)表達(dá)式:expression 知道對(duì)哪個(gè)類的那個(gè)方法進(jìn)行增強(qiáng)
語法結(jié)構(gòu): execution([權(quán)限修飾符] [返回值類型] [類全路徑] [方法名稱] ([參數(shù)列表])
JDK和CGLIB選擇【可選】
spring底層,包含了jdk代理和cglib代理兩種動(dòng)態(tài)代理生成機(jī)制
基本規(guī)則是:目標(biāo)業(yè)務(wù)類如果有接口則用JDK代理,沒有接口則用CGLib代理
class DefaultAopProxyFactory{
// 該方法中明確定義了 JDK代理和CGLib代理的選取規(guī)則
// 基本規(guī)則是:目標(biāo)業(yè)務(wù)類如果有接口則用JDK代理,沒有接口則用CGLib代理
public AopProxy createAopProxy(){...}
}
后處理器【可選】
spring中定義了很多后處理器
每個(gè)bean在創(chuàng)建完成之前 ,都會(huì)有一個(gè)后處理過程,即再加工,對(duì)bean做出相關(guān)改變和調(diào)整
-
spring-AOP中,就有一個(gè)專門的后處理器,負(fù)責(zé)通過原始業(yè)務(wù)組件(Service),再加工得到一個(gè)代理組件
后處理器定義
/**
* 定義bean后處理器
* 作用:在bean的創(chuàng)建之后,進(jìn)行再加工
*/
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* 在bean的init方法之前執(zhí)行
* @param bean 原始的bean對(duì)象
* @param beanName
* @return
* @throws BeansException
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后處理器 在init之前執(zhí)行```"+bean.getClass());
return bean;
}
/**
* 在bean的init方法之后執(zhí)行
* @param bean postProcessBeforeInitialization返回的bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后處理器 在init之后執(zhí)行```"+bean.getClass());
return bean;// 此處的返回是 getBean() 最終的返回值
}
}
配置后處理器
<!-- 配置后處理器,將對(duì)工廠中所有的bean聲明周期進(jìn)行干預(yù) -->
<bean class="com.qianfeng.beanpostprocessor.MyBeanPostProcessor"></bean>
bean的生命周期
bean 對(duì)象從創(chuàng)建到銷毀的過程
bean 的實(shí)例化 通過構(gòu)造方法創(chuàng)建bean 的實(shí)例 默認(rèn)是無參構(gòu)造
給bean 的屬性賦值
執(zhí)行初始化的方法
得到完整的bean 對(duì)象 ,這時(shí)的bean 對(duì)象才能夠使用
銷毀bean
要考慮 bean 的后置處理器 BeanPostProcessor創(chuàng)建一個(gè)類實(shí)現(xiàn)BeanPostProcessor 重寫 他的兩個(gè)方法
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在bean 初始化之前執(zhí)行");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在bean初始化之后執(zhí)行");
if(bean instanceof User){
User user = (User) bean;
user.setName("愿天下程序員少走彎路!");
return user;
}
return bean;
}
}
在配置文件 配置這個(gè) bean
<bean id="myPostProcessor" class="com.qf.postprocessor.MyBeanPost"></bean>
總結(jié)
-
bean 的實(shí)例化 通過構(gòu)造方法創(chuàng)建bean 的實(shí)例 默認(rèn)是無參構(gòu)造
-
給bean 的屬性賦值
-
把 bean 的 實(shí)例 傳遞給 bean的前置處理器的方法 postProcessBeforeInitialization
-
執(zhí)行初始化的方法
-
把 bean 的 實(shí)例 傳遞給 bean的后置處理器的方法 postProcessAfterInitialization
-
得到完整的bean 對(duì)象 ,這時(shí)的bean 對(duì)象才能夠使用
-
銷毀bean 當(dāng)容器關(guān)閉的時(shí)候 調(diào)用銷毀的方法
自定義初始化方法:添加“init-method”屬性,Spring則會(huì)在創(chuàng)建對(duì)象之后,調(diào)用此方法。
自定義銷毀方法:添加“destroy-method”屬性,Spring則會(huì)在銷毀對(duì)象之前,調(diào)用此方法。
銷毀:工廠的close()方法被調(diào)用之后,Spring會(huì)毀掉所有已創(chuàng)建的單例對(duì)象。
分類:Singleton對(duì)象由Spring容器銷毀、Prototype對(duì)象由JVM銷毀。
生命周期注解(初始化注解、銷毀注解)
@PostConstruct //初始化
public void init(){
System.out.println("init method executed");
}
@PreDestroy //銷毀
public void destroy(){
System.out.println("destroy method executed");
}
生命周期階段
隨工廠啟動(dòng)創(chuàng)建 ==》 構(gòu)造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 構(gòu)建完成 ==》隨工廠關(guān)閉銷毀(單例bean:singleton)
被使用時(shí)創(chuàng)建 ==》 構(gòu)造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 構(gòu)建完成 ==》JVM垃圾回收銷毀(多例bean:prototype)
流程圖
文章來源:http://www.zghlxwxcb.cn/news/detail-467301.html
完結(jié)撒花!愿每一位程序員少走彎路是我的創(chuàng)作的初心!
文章來源地址http://www.zghlxwxcb.cn/news/detail-467301.html
到了這里,關(guān)于Spring面向切面編程(AOP)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!