AOP
AOP,面向切面編程,是對面向對象編程OOP的升華。OOP是縱向對一個事物的抽象,一個對象包括靜態的屬性信息,包括動態的方法信息等。而AOP是橫向的對不同事物的抽象,屬性與屬性、方法與方法、對象與對象都可以組成一個切面,而用這種思維去設計編程的方式叫做面向切面編程
AOP思想的實現方案:動態代理技術,在運行期間,對目標對象的方法進行增強,代理對象同名方法內可以執行原有邏輯的同時嵌入執行其他增強邏輯或其他對象的方法
下面是AOP的實現思想
創建增強類,編寫前方法與后方法
public class MyAdvice {public void beforeAdvice(){System.out.println("執行增強前方法");}public void afterAdvice(){System.out.println("執行增前后方法");}
}
編寫被增強類,并編寫一個方法用于增強
public class UserServiceImpl implements UserService {@Overridepublic void show() {System.out.println("show...");}}
Bean后處理器去實現將Bean對象替換成代理類
public class MyBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {private ApplicationContext context;@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//對指定方法進行增強if (bean instanceof UserService) {//獲取增強方法MyAdvice myAdvice = context.getBean(MyAdvice.class);Object proxyInstance = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {myAdvice.beforeAdvice();method.invoke(bean, args);myAdvice.afterAdvice();return proxy;}});return proxyInstance;}return bean;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.context = applicationContext;}
}
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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><bean id="userService" class="com.zmt.service.impl.UserServiceImpl"></bean><bean class="com.zmt.processor.MyBeanPostProcessor"/><bean class="com.zmt.advice.MyAdvice"/>
</beans>
執行測試代碼
public class ApplicationTest {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");UserService userService = context.getBean(UserService.class);userService.show();}
}
執行結果如下?
這種實現方式存在兩個問題,一個是需要被增強的Bean對象需要硬編碼指定,其次是增強方法也是被寫死的,如果需要修改被增強的類或是修改增強方法都需要修改代碼。
那么我們在了解Spring中AOP時,需要先了解如下幾個概念
目標對象 | Taeget | 被增強的方法所在的對象 |
代理對象 | Proxy | 對目標方法進行增強后的對象,客戶端實際調用的對象 |
連接點 | Joinpoint | 目標對象中可以被增強的方法 |
切入點 | Pointcut | 目標對象中實際被增強的方法 |
通知\增強 | Advice | 增強部分的代碼邏輯 |
切面 | Aspect | 增強和切入點的組合 |
織入 | Weaving | 將通知和切入點動態組合的過程 |
那么接下來給出基于XML的實現AOP的示例代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="userService" class="com.zmt.service.impl.UserServiceImpl"/><!--將增強類交給Spring管理--><bean id="myAdvice" class="com.zmt.advice.MyAdvice"/><aop:config><!--配置切面表達式,指定需要被增強的方法--><aop:pointcut id="myPointcut" expression="execution(void com.zmt.service.impl.UserServiceImpl.show())"/><!--指定切點與哪些增強類結合--><aop:aspect ref="myAdvice"><aop:before method="beforeAdvice" pointcut-ref="myPointcut"/></aop:aspect></aop:config>
</beans>
public class UserServiceImpl implements UserService {@Overridepublic void show() {System.out.println("show...");}}
public class MyAdvice {public void beforeAdvice(){System.out.println("執行增強前方法");}public void afterAdvice(){System.out.println("執行增前后方法");}
}
測試代碼如下
public class ApplicationTest {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");UserService userService = context.getBean(UserService.class);userService.show();}
}
運行結果如下
execution語法
execution([訪問修飾符] 返回值類型 報名.類名.方法名(參數))
- 訪問修飾符可以省略不寫
- 返回值類型、某一級包名、類名、方法名可以使用 * 表示任意
- 包名與類名之間使用單點 . 表示該包下的類,使用雙點 .. 表示該包以及子包下的類
- 參數列表可以使用雙點 .. 表示任意參數
XML文件下通知的配置類型
通知名稱 | 配置方式 | 執行時機 |
前置通知 | < aop:before > | 目標方法執行之前執行 |
后置通知 | < aop:after-returning > | 目標方法執行之后執行,目標方法異常時不再執行 |
環繞通知 | < aop:around > | 目標方法執行前后執行,目標方法異常時,環繞后方法不再執行 |
異常通知 | < aop:after-throwing > | 目標方法拋出異常時執行 |
最終通知 | < aop:after > | 不管目標方法是否有異常,最終都會執行 |
環繞通知需要將連接點傳入,具體的增強類實現代碼如下
public class MyAdvice {public void beforeAdvice(){System.out.println("執行增強前方法");}public void afterAdvice(){System.out.println("執行增前后方法");}public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("環繞前通知。。。");Object proceed = joinPoint.proceed();System.out.println("環繞后通知。。。");return proceed;}
}
增強方法在被調用時,Spring可以為其傳遞一些必要參數
參數類型 | 作用 |
JoinPoint | 連接點對象,任何通知都可以使用,可以獲取當前目標對象,目標方法參數等信息 |
ProceedingJoinPoint | JoinPoint子類對象,主要是在環繞通知中執行proceed(),進而執行目標方法 |
Throwable | 異常對象,使用在異常通知中,需要在配置文件中指出異常對象名稱 |
除了上面實現AOP之外,基于XML文件配置的實現方式還存在另一種實現方式,通過實現接口來表示該增強類的具體操作。
public class MyAdvice2 implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("環繞前通知。。。。");Object res = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());System.out.println("環繞后通知。。。。");return res;}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="userService" class="com.zmt.service.impl.UserServiceImpl"/><bean id="myAdvice2" class="com.zmt.advice.MyAdvice2"/><aop:config><aop:pointcut id="myPointcut" expression="execution(void com.zmt.service.impl.UserServiceImpl.show())"/><!-- 指定增強類與切點 --><aop:advisor advice-ref="myAdvice2" pointcut-ref="myPointcut"/></aop:config>
</beans>
注意:一個增強類可以實現多個Advice接口
AOP配置的兩種語法形式不同點
語法形式不同:
- advisor是通過實現接口來確認通知的類型
- aspect是通過配置確認通知的類型,更加靈活
可配置的切面數量不同:
- 一個advisor只能配置一個固定通知和一個切點表達式
- 一個aspect可以配置多個通知和多個切點表達式任意組合
使用場景不同:
- 允許隨意搭配情況下可以使用aspect進行配置
- 如果通知類型單一、切面單一的情況下可以使用advisor進行配置
- 在通知類型已經固定,不用人為指定通知類型時,可以使用advisor進行配置