一、什麼是AOP?
AOP(Aspect Oriented Programming),即面向切面編程。
在我們的項目代碼中,有大量與日誌、事務、權限(AOP稱之爲橫切關注點)相關的代碼鑲嵌在業務代碼當中,造成大量代碼的重複與代碼的冗餘。
雖然可以將這些重複的代碼封裝起來再進行調用,但是這樣的調用方式比較單一,不夠靈活,無法更好地以模塊化的方式,對這些橫切關注點進行組織和實現。
AOP提出切面(Aspect)的概念,以模塊化的方式對橫切關注點進行封裝,再通過織入的方式將切面織入到業務邏輯代碼當中。這樣橫切關注點與業務邏輯代碼分離,業務邏輯代碼中就不再含有日誌、事務、權限等代碼的調用,可以很好的進行管理。
二、AOP中關鍵性概念
連接點(Joinpoint):程序執行過程中明確的點,如方法的調用,或者異常的拋出.
目標(Target):被通知(被代理)的對象.
注1:完成具體的業務邏輯
通知(Advice):在某個特定的連接點上執行的動作,同時Advice也是程序代碼的具體實現,例如一個實現日誌記錄的代碼(通知有些書上也稱爲處理)
注2:完成切面編程
代理(Proxy):將通知應用到目標對象後創建的對象(代理=目標+通知),
例子:外科醫生+護士
注3:只有代理對象纔有AOP功能,而AOP的代碼是寫在通知的方法裏面的
切入點(Pointcut):多個連接點的集合,定義了通知應該應用到那些連接點。
(也將Pointcut理解成一個條件 ,此條件決定了容器在什麼情況下將通知和目標組合成代理返回給外部程序)
適配器(Advisor):適配器=通知(Advice)+切入點(Pointcut)
三、AOP在項目中的使用。
下面示例代碼爲模擬項目開發中的操作(這裏以買書和評論爲例):
public interface IBookBiz {
/**
* 買書
* @param book 書名
* @return
*/
Integer buy(String book);
/**
* 評論
* @param comment 要評論的內容
* @return
*/
Integer comment(String comment);
}
public class BookBizImpl implements IBookBiz{
@Override
public Integer buy(String book) {
System.out.println("買書操作");
return 0;
}
@Override
public Integer comment(String comment) {
System.out.println("評論操作");
return 1;
}
}
1、前置通知:
/**
* 前置通知,需實現MethodBeforeAdvice接口
* @author lenovo
*
*/
public class MyMethodBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
String targetName = target.getClass().getName(); //被調用方法的所屬類
String methodName = method.getName(); //被調用的方法
String params = Arrays.toString(args); //被調用方法的參數
System.out.println("{前置日誌}系統日誌:"+targetName+"."+methodName+"被調用,攜帶了參數:"+params);
}
}
spring的配置文件:
<?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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 目標對象 -->
<bean class="com.zking.biz.impl.BookBizImpl" id="bookBiz"></bean>
<!-- 通知 -->
<!-- 前置通知 -->
<bean class="com.zking.aop.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean>
<!-- 代理工廠(這個類爲spring自帶的) -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
<!-- 目標 -->
<property name="target" ref="bookBiz"></property>
<!-- 代理所需要實現目標對象所實現的接口 -->
<property name="proxyInterfaces">
<list>
<value>com.zking.biz.IBookBiz</value>
</list>
</property>
<!-- 需要應用到目標對象上的通知Bean的名字。(List) -->
<property name="interceptorNames">
<list>
<value>myMethodBeforeAdvice</value>
</list>
</property>
</bean>
</beans>
測試方法(後面用的都是這一個測試方法,在這裏提前說明一下):
public static void main(String[] args) throws ServletException, IOException {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-context.xml");
BookBiz iBookBiz = (IBookBiz)applicationContext.getBean("bookProxy");
System.out.println(iBookBiz.buy("聖墟"));
System.out.println(iBookBiz.comment("聖墟"));
}
打印結果:
2、後置通知
/**
* 後置通知,需實現AfterReturningAdvice接口
* @author lenovo
*
*/
public class MyAfterReturningAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
String targetName = target.getClass().getName(); //被調用方法的所屬類
String methodName = method.getName(); //被調用的方法
String params = Arrays.toString(args); //被調用方法的參數
System.out.println("{後置日誌}買書返利日誌:"+targetName+"."+methodName+"被調用,繫帶了參數:"+params+",返回值:"+returnValue);
}
}
spring的配置文件:
<?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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 目標對象 -->
<bean class="com.zking.biz.impl.BookBizImpl" id="bookBiz"></bean>
<!-- 通知 -->
<!-- 後置通知 -->
<bean class="com.zking.aop.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
<!-- 代理工廠(這個類爲spring自帶的) -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
<!-- 目標 -->
<property name="target" ref="bookBiz"></property>
<!-- 代理所需要實現目標對象所實現的接口 -->
<property name="proxyInterfaces">
<list>
<value>com.zking.biz.IBookBiz</value>
</list>
</property>
<!-- 需要應用到目標對象上的通知Bean的名字。(List) -->
<property name="interceptorNames">
<list>
<value>myAfterReturningAdvice</value>
</list>
</property>
</bean>
</beans>
打印結果,(細心的話就不難發現,評論的時候也有返利的操作(虧錢虧大發了),這個bug後面會得到解決):
3、環繞通知
/**
* 環繞通知,需實現MethodInterceptor接口
* @author lenovo
*
*/
public class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String targetName = invocation.getThis().getClass().getName(); // 目標名
String methodName = invocation.getMethod().getName(); // 方法名
String params = Arrays.toString(invocation.getArguments()); // 參數
System.out.println("{環繞日誌}買書返利日誌:"+targetName+"."+methodName+"被調用,繫帶了參數:"+params);
// proceed方法的作用是讓目標方法執行,且返回值就是目標方法的返回值
Object returnValue = invocation.proceed(); // 方法放行
System.out.println("{環繞日誌}目標對象對應的方法調用返回值:"+returnValue);
// 而本方法的返回值,即爲目標方法在實際被調用時的放回值
return returnValue;
}
}
spring的配置文件:
<?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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 目標對象 -->
<bean class="com.zking.biz.impl.BookBizImpl" id="bookBiz"></bean>
<!-- 通知 -->
<!-- 環繞通知 -->
<bean class="com.zking.aop.MyMethodInterceptor" id="myMethodInterceptor"></bean>
<!-- 代理工廠(這個類爲spring自帶的) -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
<!-- 目標 -->
<property name="target" ref="bookBiz"></property>
<!-- 代理所需要實現目標對象所實現的接口 -->
<property name="proxyInterfaces">
<list>
<value>com.zking.biz.IBookBiz</value>
</list>
</property>
<!-- 需要應用到目標對象上的通知Bean的名字。(List) -->
<property name="interceptorNames">
<list>
<value>myMethodInterceptor</value>
</list>
</property>
</bean>
</beans>
打印結果:
打印結果:
4、適配器(這裏可以解決之前買書返利的bug):
適配器是Spring自帶的,所以無需創建類
spring的配置文件:
<?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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 目標對象 -->
<bean class="com.zking.biz.impl.BookBizImpl" id="bookBiz"></bean>
<!-- 通知 -->
<!-- 後置通知 -->
<bean class="com.zking.aop.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
<!-- 過濾通知 -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="myRegexp">
<!-- 這裏指過濾哪個通知 -->
<property name="advice" ref="myAfterReturningAdvice"></property>
<!-- 這裏爲表達式,指匹配方法名爲buy的方法 -->
<property name="pattern" value=".*buy"></property>
</bean>
<!-- 代理工廠(這個類爲spring自帶的) -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
<!-- 目標 -->
<property name="target" ref="bookBiz"></property>
<!-- 代理所需要實現目標對象所實現的接口 -->
<property name="proxyInterfaces">
<list>
<value>com.zking.biz.IBookBiz</value>
</list>
</property>
<!-- 需要應用到目標對象上的通知Bean的名字。(List) -->
<property name="interceptorNames">
<list>
<value>myRegexp</value>
</list>
</property>
</bean>
</beans>
打印結果(結果很明顯,評論並返利):
5、異常通知
/**
* 異常通知,需實現ThrowsAdvice接口,方法名必須叫afterThrowing
* 出現異常執行系統提示,然後進行處理。買書異常爲例
* @author lenovo
*
*/
public class MyThrowsAdvice implements ThrowsAdvice{
public void afterThrowing(RuntimeException re) {
System.out.println("買書異常!!!!");
}
}
對BookBizImpl.java稍做修改:
public class BookBizImpl implements IBookBiz{
@Override
public Integer buy(String book) {
System.out.println("買書");
//這裏拋出異常,模擬程序運行時意外出現的錯誤
if(true) {
throw new RuntimeException();
}
return 0;
}
@Override
public Integer comment(String comment) {
System.out.println("評論");
return 1;
}
}
spring的配置文件:
<?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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 目標對象 -->
<bean class="com.zking.biz.impl.BookBizImpl" id="bookBiz"></bean>
<!-- 通知 -->
<!--異常通知 -->
<bean class="com.zking.aop.MyThrowsAdvice" id="myThrowsAdvice"></bean>
<!-- 代理工廠(這個類爲spring自帶的) -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">
<!-- 目標 -->
<property name="target" ref="bookBiz"></property>
<!-- 代理所需要實現目標對象所實現的接口 -->
<property name="proxyInterfaces">
<list>
<value>com.zking.biz.IBookBiz</value>
</list>
</property>
<!-- 需要應用到目標對象上的通知Bean的名字。(List) -->
<property name="interceptorNames">
<list>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
</beans>